Skip to content

Commit

Permalink
feat: loop over a matrix (#1767)
Browse files Browse the repository at this point in the history
  • Loading branch information
pd93 authored Sep 2, 2024
1 parent 1cb5daf commit 281d259
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 18 deletions.
15 changes: 15 additions & 0 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"},
Expand Down
35 changes: 21 additions & 14 deletions taskfile/ast/for.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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 changes: 8 additions & 0 deletions testdata/for/cmds/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions testdata/for/deps/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
49 changes: 48 additions & 1 deletion variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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
}
37 changes: 34 additions & 3 deletions website/docs/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions website/static/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,9 @@
},
{
"$ref": "#/definitions/for_var"
},
{
"$ref": "#/definitions/for_matrix"
}
]
},
Expand Down Expand Up @@ -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": [
{
Expand Down

0 comments on commit 281d259

Please sign in to comment.