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", | 			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": [ | ||||||
|         { |         { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user