mirror of
https://github.com/go-task/task.git
synced 2025-02-03 13:22:11 +02:00
feat: loop over a matrix (#1767)
This commit is contained in:
parent
1cb5daf73e
commit
281d259e6e
15
task_test.go
15
task_test.go
@ -2374,6 +2374,10 @@ func TestForCmds(t *testing.T) {
|
||||
name: "loop-explicit",
|
||||
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",
|
||||
expectedOutput: "bar\nfoo\n",
|
||||
@ -2431,6 +2435,17 @@ func TestForDeps(t *testing.T) {
|
||||
name: "loop-explicit",
|
||||
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",
|
||||
expectedOutputContains: []string{"bar\n", "foo\n"},
|
||||
|
@ -8,11 +8,12 @@ import (
|
||||
)
|
||||
|
||||
type For struct {
|
||||
From string
|
||||
List []any
|
||||
Var string
|
||||
Split string
|
||||
As string
|
||||
From string
|
||||
List []any
|
||||
Matrix map[string][]any
|
||||
Var string
|
||||
Split string
|
||||
As string
|
||||
}
|
||||
|
||||
func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
||||
@ -36,16 +37,21 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
||||
|
||||
case yaml.MappingNode:
|
||||
var forStruct struct {
|
||||
Var string
|
||||
Split string
|
||||
As string
|
||||
Matrix map[string][]any
|
||||
Var string
|
||||
Split string
|
||||
As string
|
||||
}
|
||||
if err := node.Decode(&forStruct); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
if forStruct.Var == "" {
|
||||
if forStruct.Var == "" && forStruct.Matrix == nil {
|
||||
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.Split = forStruct.Split
|
||||
f.As = forStruct.As
|
||||
@ -60,10 +66,11 @@ func (f *For) DeepCopy() *For {
|
||||
return nil
|
||||
}
|
||||
return &For{
|
||||
From: f.From,
|
||||
List: deepcopy.Slice(f.List),
|
||||
Var: f.Var,
|
||||
Split: f.Split,
|
||||
As: f.As,
|
||||
From: f.From,
|
||||
List: deepcopy.Slice(f.List),
|
||||
Matrix: deepcopy.Map(f.Matrix),
|
||||
Var: f.Var,
|
||||
Split: f.Split,
|
||||
As: f.As,
|
||||
}
|
||||
}
|
||||
|
8
testdata/for/cmds/Taskfile.yml
vendored
8
testdata/for/cmds/Taskfile.yml
vendored
@ -7,6 +7,14 @@ tasks:
|
||||
- for: ["a", "b", "c"]
|
||||
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-sources:
|
||||
sources:
|
||||
|
10
testdata/for/deps/Taskfile.yml
vendored
10
testdata/for/deps/Taskfile.yml
vendored
@ -9,6 +9,16 @@ tasks:
|
||||
vars:
|
||||
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-sources:
|
||||
sources:
|
||||
|
49
variables.go
49
variables.go
@ -271,9 +271,13 @@ func itemsFromFor(
|
||||
) ([]any, []string, error) {
|
||||
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
|
||||
// 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
|
||||
if len(f.List) > 0 {
|
||||
values = f.List
|
||||
return f.List, nil, nil
|
||||
}
|
||||
// Get the list from the task sources
|
||||
if f.From == "sources" {
|
||||
@ -322,3 +326,46 @@ func itemsFromFor(
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -1297,9 +1297,9 @@ tasks:
|
||||
|
||||
## Looping over values
|
||||
|
||||
As of v3.28.0, Task allows you to loop over certain values and execute a command
|
||||
for each. There are a number of ways to do this depending on the type of value
|
||||
you want to loop over.
|
||||
Task allows you to loop over certain values and execute a command for each.
|
||||
There are a number of ways to do this depending on the type of value you want to
|
||||
loop over.
|
||||
|
||||
### Looping over a static list
|
||||
|
||||
@ -1316,6 +1316,37 @@ tasks:
|
||||
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
|
||||
|
||||
You are also able to loop over the sources of your task:
|
||||
|
@ -431,6 +431,9 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/for_var"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/for_matrix"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -467,6 +470,12 @@
|
||||
"additionalProperties": false,
|
||||
"required": ["var"]
|
||||
},
|
||||
"for_matrix": {
|
||||
"description": "A matrix of values to iterate over",
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": ["matrix"]
|
||||
},
|
||||
"precondition": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user