1
0
mirror of https://github.com/go-task/task.git synced 2025-04-25 12:25:07 +02:00

feat: loop over a matrix (#1767)

This commit is contained in:
Pete Davison 2024-09-02 20:29:00 +01:00 committed by GitHub
parent 1cb5daf73e
commit 281d259e6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 145 additions and 18 deletions

View File

@ -2374,6 +2374,10 @@ func TestForCmds(t *testing.T) {
name: "loop-explicit", name: "loop-explicit",
expectedOutput: "a\nb\nc\n", expectedOutput: "a\nb\nc\n",
}, },
{
name: "loop-matrix",
expectedOutput: "windows/amd64\nwindows/arm64\nlinux/amd64\nlinux/arm64\ndarwin/amd64\ndarwin/arm64\n",
},
{ {
name: "loop-sources", name: "loop-sources",
expectedOutput: "bar\nfoo\n", expectedOutput: "bar\nfoo\n",
@ -2431,6 +2435,17 @@ func TestForDeps(t *testing.T) {
name: "loop-explicit", name: "loop-explicit",
expectedOutputContains: []string{"a\n", "b\n", "c\n"}, expectedOutputContains: []string{"a\n", "b\n", "c\n"},
}, },
{
name: "loop-matrix",
expectedOutputContains: []string{
"windows/amd64\n",
"windows/arm64\n",
"linux/amd64\n",
"linux/arm64\n",
"darwin/amd64\n",
"darwin/arm64\n",
},
},
{ {
name: "loop-sources", name: "loop-sources",
expectedOutputContains: []string{"bar\n", "foo\n"}, expectedOutputContains: []string{"bar\n", "foo\n"},

View File

@ -10,6 +10,7 @@ import (
type For struct { type For struct {
From string From string
List []any List []any
Matrix map[string][]any
Var string Var string
Split string Split string
As string As string
@ -36,6 +37,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode: case yaml.MappingNode:
var forStruct struct { var forStruct struct {
Matrix map[string][]any
Var string Var string
Split string Split string
As string As string
@ -43,9 +45,13 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
if err := node.Decode(&forStruct); err != nil { if err := node.Decode(&forStruct); err != nil {
return errors.NewTaskfileDecodeError(err, node) return errors.NewTaskfileDecodeError(err, node)
} }
if forStruct.Var == "" { if forStruct.Var == "" && forStruct.Matrix == nil {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for") return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for")
} }
if forStruct.Var != "" && forStruct.Matrix != nil {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("cannot use both var and matrix in for")
}
f.Matrix = forStruct.Matrix
f.Var = forStruct.Var f.Var = forStruct.Var
f.Split = forStruct.Split f.Split = forStruct.Split
f.As = forStruct.As f.As = forStruct.As
@ -62,6 +68,7 @@ func (f *For) DeepCopy() *For {
return &For{ return &For{
From: f.From, From: f.From,
List: deepcopy.Slice(f.List), List: deepcopy.Slice(f.List),
Matrix: deepcopy.Map(f.Matrix),
Var: f.Var, Var: f.Var,
Split: f.Split, Split: f.Split,
As: f.As, As: f.As,

View File

@ -7,6 +7,14 @@ tasks:
- for: ["a", "b", "c"] - for: ["a", "b", "c"]
cmd: echo "{{.ITEM}}" cmd: echo "{{.ITEM}}"
loop-matrix:
cmds:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
# Loop over the task's sources # Loop over the task's sources
loop-sources: loop-sources:
sources: sources:

View File

@ -9,6 +9,16 @@ tasks:
vars: vars:
TEXT: "{{.ITEM}}" TEXT: "{{.ITEM}}"
loop-matrix:
deps:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
task: echo
vars:
TEXT: "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
# Loop over the task's sources # Loop over the task's sources
loop-sources: loop-sources:
sources: sources:

View File

@ -271,9 +271,13 @@ func itemsFromFor(
) ([]any, []string, error) { ) ([]any, []string, error) {
var keys []string // The list of keys to loop over (only if looping over a map) var keys []string // The list of keys to loop over (only if looping over a map)
var values []any // The list of values to loop over var values []any // The list of values to loop over
// Get the list from a matrix
if f.Matrix != nil {
return asAnySlice(product(f.Matrix)), nil, nil
}
// Get the list from the explicit for list // Get the list from the explicit for list
if len(f.List) > 0 { if len(f.List) > 0 {
values = f.List return f.List, nil, nil
} }
// Get the list from the task sources // Get the list from the task sources
if f.From == "sources" { if f.From == "sources" {
@ -322,3 +326,46 @@ func itemsFromFor(
} }
return values, keys, nil return values, keys, nil
} }
// product generates the cartesian product of the input map of slices.
func product(inputMap map[string][]any) []map[string]any {
if len(inputMap) == 0 {
return nil
}
// Extract the keys and corresponding slices
keys := make([]string, 0, len(inputMap))
slices := make([][]any, 0, len(inputMap))
for key, slice := range inputMap {
keys = append(keys, key)
slices = append(slices, slice)
}
// Start with an empty product result
result := []map[string]any{{}}
// Iterate over each slice in the slices
for i, slice := range slices {
var newResult []map[string]any
// For each combination in the current result
for _, combination := range result {
// Append each element from the current slice to the combinations
for _, item := range slice {
newComb := make(map[string]any, len(combination))
// Copy the existing combination
for k, v := range combination {
newComb[k] = v
}
// Add the current item with the corresponding key
newComb[keys[i]] = item
newResult = append(newResult, newComb)
}
}
// Update result with the new combinations
result = newResult
}
return result
}

View File

@ -1297,9 +1297,9 @@ tasks:
## Looping over values ## Looping over values
As of v3.28.0, Task allows you to loop over certain values and execute a command Task allows you to loop over certain values and execute a command for each.
for each. There are a number of ways to do this depending on the type of value There are a number of ways to do this depending on the type of value you want to
you want to loop over. loop over.
### Looping over a static list ### Looping over a static list
@ -1316,6 +1316,37 @@ tasks:
cmd: cat {{ .ITEM }} cmd: cat {{ .ITEM }}
``` ```
### Looping over a matrix
If you need to loop over all permutations of multiple lists, you can use the
`matrix` property. This should be familiar to anyone who has used a matrix in a
CI/CD pipeline.
```yaml
version: '3'
tasks:
default:
silent: true
cmds:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
```
This will output:
```txt
windows/amd64
windows/arm64
linux/amd64
linux/arm64
darwin/amd64
darwin/arm64
```
### Looping over your task's sources ### Looping over your task's sources
You are also able to loop over the sources of your task: You are also able to loop over the sources of your task:

View File

@ -431,6 +431,9 @@
}, },
{ {
"$ref": "#/definitions/for_var" "$ref": "#/definitions/for_var"
},
{
"$ref": "#/definitions/for_matrix"
} }
] ]
}, },
@ -467,6 +470,12 @@
"additionalProperties": false, "additionalProperties": false,
"required": ["var"] "required": ["var"]
}, },
"for_matrix": {
"description": "A matrix of values to iterate over",
"type": "object",
"additionalProperties": true,
"required": ["matrix"]
},
"precondition": { "precondition": {
"anyOf": [ "anyOf": [
{ {