diff --git a/hash.go b/hash.go index f7e7868c1a..2a0632c2d6 100644 --- a/hash.go +++ b/hash.go @@ -4,15 +4,12 @@ import ( "fmt" "github.com/go-task/task/v3/internal/hash" + "github.com/go-task/task/v3/internal/slicesext" "github.com/go-task/task/v3/taskfile/ast" ) func (e *Executor) GetHash(t *ast.Task) (string, error) { - r := t.Run - if r == "" { - r = e.Taskfile.Run - } - + r := slicesext.FirstNonZero(t.Run, e.Taskfile.Run) var h hash.HashFunc switch r { case "always": diff --git a/internal/hash/hash.go b/internal/hash/hash.go index 9ba6444827..625965954d 100644 --- a/internal/hash/hash.go +++ b/internal/hash/hash.go @@ -15,7 +15,7 @@ func Empty(*ast.Task) (string, error) { } func Name(t *ast.Task) (string, error) { - return t.Task, nil + return fmt.Sprintf("%s:%s", t.Location.Taskfile, t.LocalName()), nil } func Hash(t *ast.Task) (string, error) { diff --git a/internal/slicesext/slicesext.go b/internal/slicesext/slicesext.go index 8b539bc84f..b3149a23fe 100644 --- a/internal/slicesext/slicesext.go +++ b/internal/slicesext/slicesext.go @@ -18,3 +18,13 @@ func UniqueJoin[T cmp.Ordered](ss ...[]T) []T { slices.Sort(r) return slices.Compact(r) } + +func FirstNonZero[T comparable](values ...T) T { + var zero T + for _, v := range values { + if v != zero { + return v + } + } + return zero +} diff --git a/task_test.go b/task_test.go index 25ce8b4089..f94b57aaa5 100644 --- a/task_test.go +++ b/task_test.go @@ -1664,6 +1664,26 @@ func TestRunOnlyRunsJobsHashOnce(t *testing.T) { tt.Run(t) } +func TestRunOnceSharedDeps(t *testing.T) { + const dir = "testdata/run_once_shared_deps" + + var buff bytes.Buffer + e := task.Executor{ + Dir: dir, + Stdout: &buff, + Stderr: &buff, + ForceAll: true, + } + require.NoError(t, e.Setup()) + require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build"})) + + rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library"`) + matches := rx.FindAllStringSubmatch(buff.String(), -1) + assert.Len(t, matches, 1) + assert.Contains(t, buff.String(), `task: [service-a:build] echo "build a"`) + assert.Contains(t, buff.String(), `task: [service-b:build] echo "build b"`) +} + func TestDeferredCmds(t *testing.T) { const dir = "testdata/deferred" var buff bytes.Buffer diff --git a/taskfile/ast/task.go b/taskfile/ast/task.go index e749f73cdb..de6986c6b8 100644 --- a/taskfile/ast/task.go +++ b/taskfile/ast/task.go @@ -13,37 +13,39 @@ import ( // Task represents a task type Task struct { - Task string - Cmds []*Cmd - Deps []*Dep - Label string - Desc string - Prompt string - Summary string - Requires *Requires - Aliases []string - Sources []*Glob - Generates []*Glob - Status []string - Preconditions []*Precondition - Dir string - Set []string - Shopt []string - Vars *Vars - Env *Vars - Dotenv []string - Silent bool - Interactive bool - Internal bool - Method string - Prefix string - IgnoreError bool - Run string + Task string + Cmds []*Cmd + Deps []*Dep + Label string + Desc string + Prompt string + Summary string + Requires *Requires + Aliases []string + Sources []*Glob + Generates []*Glob + Status []string + Preconditions []*Precondition + Dir string + Set []string + Shopt []string + Vars *Vars + Env *Vars + Dotenv []string + Silent bool + Interactive bool + Internal bool + Method string + Prefix string + IgnoreError bool + Run string + Platforms []*Platform + Watch bool + Location *Location + // Populated during merging + Namespace string IncludeVars *Vars IncludedTaskfileVars *Vars - Platforms []*Platform - Location *Location - Watch bool } func (t *Task) Name() string { @@ -53,6 +55,13 @@ func (t *Task) Name() string { return t.Task } +func (t *Task) LocalName() string { + name := t.Task + name = strings.TrimPrefix(name, t.Namespace) + name = strings.TrimPrefix(name, ":") + return name +} + // WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values. func (t *Task) WildcardMatch(name string) (bool, []string) { // Convert the name into a regex string @@ -210,6 +219,7 @@ func (t *Task) DeepCopy() *Task { Platforms: deepcopy.Slice(t.Platforms), Location: t.Location.DeepCopy(), Requires: t.Requires.DeepCopy(), + Namespace: t.Namespace, } return c } diff --git a/taskfile/ast/tasks.go b/taskfile/ast/tasks.go index a400f9c13c..b4c3951a63 100644 --- a/taskfile/ast/tasks.go +++ b/taskfile/ast/tasks.go @@ -47,7 +47,7 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask { } func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) { - _ = t2.Range(func(k string, v *Task) error { + _ = t2.Range(func(name string, v *Task) error { // We do a deep copy of the task struct here to ensure that no data can // be changed elsewhere once the taskfile is merged. task := v.DeepCopy() @@ -95,7 +95,8 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) { } // Add the task to the merged taskfile - taskNameWithNamespace := taskNameWithNamespace(k, include.Namespace) + taskNameWithNamespace := taskNameWithNamespace(name, include.Namespace) + task.Namespace = include.Namespace task.Task = taskNameWithNamespace t1.Set(taskNameWithNamespace, task) diff --git a/testdata/run_once_shared_deps/Taskfile.yml b/testdata/run_once_shared_deps/Taskfile.yml new file mode 100644 index 0000000000..bd26ce6d71 --- /dev/null +++ b/testdata/run_once_shared_deps/Taskfile.yml @@ -0,0 +1,11 @@ +version: '3' + +includes: + service-a: ./service-a + service-b: ./service-b + +tasks: + build: + deps: + - service-a:build + - service-b:build diff --git a/testdata/run_once_shared_deps/library/Taskfile.yml b/testdata/run_once_shared_deps/library/Taskfile.yml new file mode 100644 index 0000000000..d24bf90605 --- /dev/null +++ b/testdata/run_once_shared_deps/library/Taskfile.yml @@ -0,0 +1,9 @@ +version: '3' + +tasks: + build: + run: once + cmds: + - echo "build library" + sources: + - src/**/* diff --git a/testdata/run_once_shared_deps/service-a/Taskfile.yml b/testdata/run_once_shared_deps/service-a/Taskfile.yml new file mode 100644 index 0000000000..ed3dcab208 --- /dev/null +++ b/testdata/run_once_shared_deps/service-a/Taskfile.yml @@ -0,0 +1,15 @@ +version: '3' + +includes: + library: + taskfile: ../library/Taskfile.yml + dir: ../library + +tasks: + build: + run: once + deps: [library:build] + cmds: + - echo "build a" + sources: + - src/**/* diff --git a/testdata/run_once_shared_deps/service-a/src/imasource.go b/testdata/run_once_shared_deps/service-a/src/imasource.go new file mode 100644 index 0000000000..06ab7d0f9a --- /dev/null +++ b/testdata/run_once_shared_deps/service-a/src/imasource.go @@ -0,0 +1 @@ +package main diff --git a/testdata/run_once_shared_deps/service-b/Taskfile.yml b/testdata/run_once_shared_deps/service-b/Taskfile.yml new file mode 100644 index 0000000000..b72c8680b3 --- /dev/null +++ b/testdata/run_once_shared_deps/service-b/Taskfile.yml @@ -0,0 +1,15 @@ +version: '3' + +includes: + library: + taskfile: ../library/Taskfile.yml + dir: ../library + +tasks: + build: + run: once + deps: [library:build] + cmds: + - echo "build b" + sources: + - src/**/* diff --git a/testdata/run_once_shared_deps/service-b/src/imasource.go b/testdata/run_once_shared_deps/service-b/src/imasource.go new file mode 100644 index 0000000000..06ab7d0f9a --- /dev/null +++ b/testdata/run_once_shared_deps/service-b/src/imasource.go @@ -0,0 +1 @@ +package main diff --git a/variables.go b/variables.go index 6234230127..1502988389 100644 --- a/variables.go +++ b/variables.go @@ -72,6 +72,7 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task, Location: origTask.Location, Requires: origTask.Requires, Watch: origTask.Watch, + Namespace: origTask.Namespace, } new.Dir, err = execext.Expand(new.Dir) if err != nil {