diff --git a/executor.go b/executor.go index 03b951a166..575b48991b 100644 --- a/executor.go +++ b/executor.go @@ -76,6 +76,8 @@ type ( executionHashes map[string]context.Context executionHashesMutex sync.Mutex watchedDirs *xsync.Map[string, bool] + taskMapCache map[string]map[string]any + taskMapCacheMutex sync.RWMutex } TempDir struct { Remote string diff --git a/task_test.go b/task_test.go index 52a147f02a..556903240a 100644 --- a/task_test.go +++ b/task_test.go @@ -2652,6 +2652,29 @@ func TestWildcard(t *testing.T) { } } +func TestReferenceSelf(t *testing.T) { + t.Parallel() + + const dir = "testdata/reference_self" + + var buff bytes.Buffer + e := task.NewExecutor( + task.WithDir(dir), + task.WithStdout(&buff), + task.WithStderr(&buff), + task.WithForceAll(true), + ) + require.NoError(t, e.Setup()) + require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"})) + + assert.Contains(t, buff.String(), `task: [check-sources-empty] echo []`) + assert.Contains(t, buff.String(), `[]`) + assert.Contains(t, buff.String(), `task: [check-generates] echo [a b *.txt]`) + assert.Contains(t, buff.String(), `[a b *.txt]`) + assert.Contains(t, buff.String(), `task: [check-generates-empty] echo []`) + assert.Contains(t, buff.String(), `[]`) +} + // enableExperimentForTest enables the experiment behind pointer e for the duration of test t and sub-tests, // with the experiment being restored to its previous state when tests complete. // diff --git a/testdata/reference_other_tasks/1.txt b/testdata/reference_other_tasks/1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testdata/reference_other_tasks/2.txt b/testdata/reference_other_tasks/2.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testdata/reference_other_tasks/taskfile.yaml b/testdata/reference_other_tasks/taskfile.yaml new file mode 100644 index 0000000000..90461f90b6 --- /dev/null +++ b/testdata/reference_other_tasks/taskfile.yaml @@ -0,0 +1,32 @@ +version: '3' + +method: none + +tasks: + + _ref: + sources: ["a", "b", "*.txt"] + generates: ["a", "b", "*.txt"] + cmd: ":" + + _ref-empty: + cmd: ":" + + check-sources: + cmd: echo {{ .TASKS._ref.sources }} + + check-sources-empty: + cmd: echo {{ (index .TASKS "_ref-empty").sources }} + + check-generates: + cmd: echo {{ .TASKS._ref.generates }} + + check-generates-empty: + cmd: echo {{ (index .TASKS "_ref-empty").generates }} + + default: + cmds: + - task: check-sources + - task: check-sources-empty + - task: check-generates + - task: check-generates-empty diff --git a/testdata/reference_self/1.txt b/testdata/reference_self/1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testdata/reference_self/2.txt b/testdata/reference_self/2.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testdata/reference_self/taskfile.yaml b/testdata/reference_self/taskfile.yaml new file mode 100644 index 0000000000..f6ec091f33 --- /dev/null +++ b/testdata/reference_self/taskfile.yaml @@ -0,0 +1,26 @@ +version: '3' + +method: none + +tasks: + + check-sources: + sources: ["a", "b", "*.txt"] + cmd: echo {{ .TASKS.self.sources }} + + check-sources-empty: + cmd: echo {{ .TASKS.self.sources }} + + check-generates: + generates: ["a", "b", "*.txt"] + cmd: echo {{ .TASKS.self.generates }} + + check-generates-empty: + cmd: echo {{ .TASKS.self.generates }} + + default: + cmds: + - task: check-sources + - task: check-sources-empty + - task: check-generates + - task: check-generates-empty diff --git a/variables.go b/variables.go index 9e40edb2b3..cb9d636d7a 100644 --- a/variables.go +++ b/variables.go @@ -75,6 +75,51 @@ func (e *Executor) CompiledTaskForTaskList(call *Call) (*ast.Task, error) { }, nil } +func taskToMap(t *ast.Task) map[string]any { + result := make(map[string]any) + + // Convert sources to a slice of strings + if len(t.Sources) > 0 { + sources := make([]string, 0, len(t.Sources)) + for _, glob := range t.Sources { + sources = append(sources, glob.Glob) + } + result["sources"] = sources + } else { + result["sources"] = []string{} + } + + // Convert generates to a slice of strings + if len(t.Generates) > 0 { + generates := make([]string, 0, len(t.Generates)) + for _, glob := range t.Generates { + generates = append(generates, glob.Glob) + } + result["generates"] = generates + } else { + result["generates"] = []string{} + } + + // Add other commonly used properties + result["task"] = t.Task + result["dir"] = t.Dir + result["label"] = t.Label + result["desc"] = t.Desc + result["method"] = t.Method + result["prefix"] = t.Prefix + result["run"] = t.Run + result["silent"] = t.Silent + result["interactive"] = t.Interactive + result["internal"] = t.Internal + result["ignore_error"] = t.IgnoreError + result["watch"] = t.Watch + result["failfast"] = t.Failfast + result["aliases"] = t.Aliases + result["dotenv"] = t.Dotenv + + return result +} + func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, error) { origTask, err := e.GetTask(call) if err != nil { @@ -141,6 +186,12 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err new.Prefix = new.Task } + // Add TASKS.self variable after basic properties are expanded + tasksMap := make(map[string]any) + tasksMap["self"] = taskToMap(&new) + vars.Set("TASKS", ast.Var{Value: tasksMap}) + cache.ResetCache() + dotenvEnvs := ast.NewVars() if len(new.Dotenv) > 0 { for _, dotEnvPath := range new.Dotenv {