From caeac469a9b4c4b126cc231699c4f4757fad895b Mon Sep 17 00:00:00 2001 From: semx <7532921+semx@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:00:03 +0400 Subject: [PATCH] fix: resolve templated task dir before running dynamic variables The task dir was templated against the variables gathered so far, which at that point only held the environment and the special variables. A dir like '{{.DIRECTORY}}', where DIRECTORY comes from the Taskfile vars, expanded to an empty string, so the task dynamic variables silently ran in the Taskfile dir instead of the task one. Resolve the dir after the Taskfile vars have been added to the result. The commands themselves already ran in the right dir, only the dynamic variables were affected. Signed-off-by: semx <7532921+semx@users.noreply.github.com> --- compiler.go | 53 +++++++++++-------- task_test.go | 17 ++++++ testdata/dir/templated_dir/Taskfile.yml | 14 +++++ .../dir/templated_dir/subdirectory/.gitignore | 1 + 4 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 testdata/dir/templated_dir/Taskfile.yml create mode 100644 testdata/dir/templated_dir/subdirectory/.gitignore diff --git a/compiler.go b/compiler.go index 2c2e56f632..ace04bb351 100644 --- a/compiler.go +++ b/compiler.go @@ -90,20 +90,8 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (* return nil } } - rangeFunc := getRangeFunc(c.Dir) - var taskRangeFunc func(k string, v ast.Var) error - if t != nil { - // NOTE(@andreynering): We're manually joining these paths here because - // this is the raw task, not the compiled one. - cache := &templater.Cache{Vars: result} - dir := templater.Replace(t.Dir, cache) - if err := cache.Err(); err != nil { - return nil, err - } - dir = filepathext.SmartJoin(c.Dir, dir) - taskRangeFunc = getRangeFunc(dir) - } + rangeFunc := getRangeFunc(c.Dir) for k, v := range c.TaskfileEnv.All() { if err := rangeFunc(k, v); err != nil { @@ -115,20 +103,39 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (* return nil, err } } - if t != nil { - for k, v := range t.IncludeVars.All() { - if err := rangeFunc(k, v); err != nil { - return nil, err - } + + if t == nil { + return result, nil + } + + for k, v := range t.IncludeVars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err } - for k, v := range t.IncludedTaskfileVars.All() { - if err := taskRangeFunc(k, v); err != nil { - return nil, err - } + } + + // The task dir may reference variables, so it can only be resolved once the + // Taskfile variables above have been added to the result. Resolving it any + // earlier makes those references expand to an empty string, which silently + // runs the dynamic variables below in the Taskfile dir instead of the task + // one. + // NOTE(@andreynering): We're manually joining these paths here because + // this is the raw task, not the compiled one. + cache := &templater.Cache{Vars: result} + dir := templater.Replace(t.Dir, cache) + if err := cache.Err(); err != nil { + return nil, err + } + dir = filepathext.SmartJoin(c.Dir, dir) + taskRangeFunc := getRangeFunc(dir) + + for k, v := range t.IncludedTaskfileVars.All() { + if err := taskRangeFunc(k, v); err != nil { + return nil, err } } - if t == nil || call == nil { + if call == nil { return result, nil } diff --git a/task_test.go b/task_test.go index b56930e77e..75326b3118 100644 --- a/task_test.go +++ b/task_test.go @@ -1936,6 +1936,23 @@ func TestDynamicVariablesShouldRunOnTheTaskDir(t *testing.T) { }) } +func TestDynamicVariablesShouldRunOnTheTemplatedTaskDir(t *testing.T) { + t.Parallel() + + tt := fileContentTest{ + Dir: "testdata/dir/templated_dir", + Target: "default", + TrimSpace: false, + Files: map[string]string{ + "subdirectory/from_templated_dir.txt": "subdirectory\n", + }, + } + t.Run("", func(t *testing.T) { + t.Parallel() + tt.Run(t) + }) +} + func TestDisplaysErrorOnVersion1Schema(t *testing.T) { t.Parallel() diff --git a/testdata/dir/templated_dir/Taskfile.yml b/testdata/dir/templated_dir/Taskfile.yml new file mode 100644 index 0000000000..d49c8a62d2 --- /dev/null +++ b/testdata/dir/templated_dir/Taskfile.yml @@ -0,0 +1,14 @@ +version: '3' + +vars: + DIRECTORY: subdirectory + +tasks: + default: + cmds: + - echo '{{.TASK_DIR}}' > from_templated_dir.txt + dir: '{{.DIRECTORY}}' + vars: + TASK_DIR: + sh: basename "$(pwd)" + silent: true diff --git a/testdata/dir/templated_dir/subdirectory/.gitignore b/testdata/dir/templated_dir/subdirectory/.gitignore new file mode 100644 index 0000000000..2211df63dd --- /dev/null +++ b/testdata/dir/templated_dir/subdirectory/.gitignore @@ -0,0 +1 @@ +*.txt