diff --git a/compiler.go b/compiler.go index 348a072898..39fe926528 100644 --- a/compiler.go +++ b/compiler.go @@ -90,19 +90,6 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (* } 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) - } - for k, v := range c.TaskfileEnv.All() { if err := rangeFunc(k, v); err != nil { return nil, err @@ -113,31 +100,60 @@ 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 !evaluateShVars { + // Add includedTaskfile.Vars, and replace/overwrite taskfile.Vars, + // _before_ calculating the t.Dir using the templater. Because + // evaluateShVars is not set, the dir used when creating rangeFunc + // will not be used (sh vars are evaluated on subsequent calls). + for k, v := range t.IncludedTaskfileVars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } + } + } + + // Calculate the t.Dir now based on values saved during AST parsing + // and then get a Task RangeFunc. + dir := c.Dir + if len(t.Dir) == 0 { + // Use the saved include task.Dir. + t.Dir = t.IncludeTaskDir + } else { + cache := &templater.Cache{Vars: result} + taskDir := templater.Replace(t.Dir, cache) + if err := cache.Err(); err != nil { + return nil, err + } + t.Dir = filepathext.SmartJoin(t.IncludeDir, taskDir) + // Update dir (esp. if taskDir is a relative path). + dir = filepathext.SmartJoin(c.Dir, taskDir) + } + 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 { - return result, nil - } - - for k, v := range call.Vars.All() { - if err := rangeFunc(k, v); err != nil { - return nil, err + if call != nil { + for k, v := range call.Vars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } + } } - } - for k, v := range t.Vars.All() { - if err := taskRangeFunc(k, v); err != nil { - return nil, err + for k, v := range t.Vars.All() { + if err := taskRangeFunc(k, v); err != nil { + return nil, err + } } } diff --git a/task_test.go b/task_test.go index cc8098fd32..39521df0c1 100644 --- a/task_test.go +++ b/task_test.go @@ -1310,6 +1310,23 @@ func TestIncludedTaskfileVarMerging(t *testing.T) { } } +func TestIncludedDirWithTemplate(t *testing.T) { + t.Parallel() + + const dir = "testdata/included_dir_with_template" + var buff bytes.Buffer + e := task.NewExecutor( + task.WithDir(dir), + task.WithStdout(&buff), + task.WithStderr(&buff), + task.WithSilent(true), + ) + require.NoError(t, e.Setup()) + err := e.Run(t.Context(), &task.Call{Task: "default"}) + require.NoError(t, err) + assert.Contains(t, buff.String(), "Build WORKING_DIR: /tmp") +} + func TestInternalTask(t *testing.T) { t.Parallel() diff --git a/taskfile/ast/task.go b/taskfile/ast/task.go index 325b713eff..c92950e37c 100644 --- a/taskfile/ast/task.go +++ b/taskfile/ast/task.go @@ -44,6 +44,8 @@ type Task struct { Location *Location // Populated during merging Namespace string `hash:"ignore"` + IncludeDir string + IncludeTaskDir string IncludeVars *Vars IncludedTaskfileVars *Vars diff --git a/taskfile/ast/tasks.go b/taskfile/ast/tasks.go index 6cda20bfc9..738c9d7490 100644 --- a/taskfile/ast/tasks.go +++ b/taskfile/ast/tasks.go @@ -11,7 +11,6 @@ import ( "go.yaml.in/yaml/v4" "github.com/go-task/task/v3/errors" - "github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/sort" ) @@ -171,7 +170,14 @@ func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) } if include.AdvancedImport { - task.Dir = filepathext.SmartJoin(include.Dir, task.Dir) + // Save the include.Dir and task.Dir so that the final task.dir can + // be calculated when the task is compiled (and templating is available). + task.IncludeDir = include.Dir + task.IncludeTaskDir = task.Dir + // If task.dir is not set then use the include.dir. + if len(task.Dir) == 0 { + task.Dir = include.Dir + } if task.IncludeVars == nil { task.IncludeVars = NewVars() } diff --git a/testdata/included_dir_with_template/taskfile.builder.yml b/testdata/included_dir_with_template/taskfile.builder.yml new file mode 100644 index 0000000000..91c86e903f --- /dev/null +++ b/testdata/included_dir_with_template/taskfile.builder.yml @@ -0,0 +1,8 @@ +version: '3' + +tasks: + build: + dir: '{{.WORKING_DIR}}' + cmds: + - | + echo "Build WORKING_DIR: $(pwd)" \ No newline at end of file diff --git a/testdata/included_dir_with_template/taskfile.yml b/testdata/included_dir_with_template/taskfile.yml new file mode 100644 index 0000000000..13354ba2dc --- /dev/null +++ b/testdata/included_dir_with_template/taskfile.yml @@ -0,0 +1,12 @@ +version: '3' + +vars: + WORKING_DIR: '/tmp' + +includes: + builder: ./taskfile.builder.yml + +tasks: + default: + cmds: + - task: builder:build \ No newline at end of file