From a4f4e1b430863a805ef676ce1c24fb58543fdd15 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Mon, 2 Sep 2024 21:30:00 +0000 Subject: [PATCH] fix: matrix loops should be deterministic --- taskfile/ast/for.go | 11 ++++++----- variables.go | 22 ++++++++-------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/taskfile/ast/for.go b/taskfile/ast/for.go index 5a87acf2db..35d7ce7f60 100644 --- a/taskfile/ast/for.go +++ b/taskfile/ast/for.go @@ -5,12 +5,13 @@ import ( "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/deepcopy" + "github.com/go-task/task/v3/internal/omap" ) type For struct { From string List []any - Matrix map[string][]any + Matrix omap.OrderedMap[string, []any] Var string Split string As string @@ -37,7 +38,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error { case yaml.MappingNode: var forStruct struct { - Matrix map[string][]any + Matrix omap.OrderedMap[string, []any] Var string Split string As string @@ -45,10 +46,10 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error { if err := node.Decode(&forStruct); err != nil { return errors.NewTaskfileDecodeError(err, node) } - if forStruct.Var == "" && forStruct.Matrix == nil { + if forStruct.Var == "" && forStruct.Matrix.Len() == 0 { return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for") } - if forStruct.Var != "" && forStruct.Matrix != nil { + if forStruct.Var != "" && forStruct.Matrix.Len() != 0 { return errors.NewTaskfileDecodeError(nil, node).WithMessage("cannot use both var and matrix in for") } f.Matrix = forStruct.Matrix @@ -68,7 +69,7 @@ func (f *For) DeepCopy() *For { return &For{ From: f.From, List: deepcopy.Slice(f.List), - Matrix: deepcopy.Map(f.Matrix), + Matrix: f.Matrix.DeepCopy(), Var: f.Var, Split: f.Split, As: f.As, diff --git a/variables.go b/variables.go index 22e47aa9df..cfdb70ae46 100644 --- a/variables.go +++ b/variables.go @@ -11,6 +11,7 @@ import ( "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/fingerprint" + "github.com/go-task/task/v3/internal/omap" "github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/taskfile/ast" ) @@ -272,7 +273,7 @@ func itemsFromFor( 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 { + if f.Matrix.Len() != 0 { return asAnySlice(product(f.Matrix)), nil, nil } // Get the list from the explicit for list @@ -328,24 +329,16 @@ func itemsFromFor( } // product generates the cartesian product of the input map of slices. -func product(inputMap map[string][]any) []map[string]any { - if len(inputMap) == 0 { +func product(inputMap omap.OrderedMap[string, []any]) []map[string]any { + if inputMap.Len() == 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 { + _ = inputMap.Range(func(key string, slice []any) error { var newResult []map[string]any // For each combination in the current result @@ -358,14 +351,15 @@ func product(inputMap map[string][]any) []map[string]any { newComb[k] = v } // Add the current item with the corresponding key - newComb[keys[i]] = item + newComb[key] = item newResult = append(newResult, newComb) } } // Update result with the new combinations result = newResult - } + return nil + }) return result }