mirror of
				https://github.com/go-task/task.git
				synced 2025-10-30 23:58:01 +02:00 
			
		
		
		
	feat: loop over a matrix (#1767)
This commit is contained in:
		
							
								
								
									
										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": [ | ||||
|         { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user