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:
parent
1cb5daf73e
commit
281d259e6e
15
task_test.go
15
task_test.go
@ -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"},
|
||||||
|
@ -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,
|
||||||
|
8
testdata/for/cmds/Taskfile.yml
vendored
8
testdata/for/cmds/Taskfile.yml
vendored
@ -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:
|
||||||
|
10
testdata/for/deps/Taskfile.yml
vendored
10
testdata/for/deps/Taskfile.yml
vendored
@ -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:
|
||||||
|
49
variables.go
49
variables.go
@ -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
|
||||||
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user