diff --git a/compiler.go b/compiler.go index 311fd58423..7543d1f953 100644 --- a/compiler.go +++ b/compiler.go @@ -91,19 +91,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 @@ -114,31 +101,54 @@ 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 taskDir for evaluation of IncludeVars. If the original TaskDir + // was saved to t.IncludeTaskDir then that path needs to be used. + cache := &templater.Cache{Vars: result} + t.Dir = templater.Replace(t.Dir, cache) + if len(t.IncludeTaskDir) > 0 { + // If the included TaskDir is absolute, then it will become + // the TaskDir, otherwise join with the existing TaskDir. + taskDir := templater.Replace(t.IncludeTaskDir, cache) + t.Dir = filepathext.SmartJoin(t.Dir, taskDir) + } + taskRangeFunc := getRangeFunc(t.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/internal/filepathext/filepathext.go b/internal/filepathext/filepathext.go index f2a1ba15a0..3b8221be75 100644 --- a/internal/filepathext/filepathext.go +++ b/internal/filepathext/filepathext.go @@ -6,8 +6,8 @@ import ( "strings" ) -// SmartJoin joins two paths, but only if the second is not already an -// absolute path. +// SmartJoin joins two paths when the second path is not absolute, otherwise +// it returns the second (absolute) path. func SmartJoin(a, b string) string { if IsAbs(b) { return b @@ -28,6 +28,7 @@ func IsAbs(path string) bool { var knownAbsDirs = []string{ ".ROOT_DIR", ".TASKFILE_DIR", + ".TASK_DIR", ".USER_WORKING_DIR", } diff --git a/task_test.go b/task_test.go index 52a147f02a..6cef370bec 100644 --- a/task_test.go +++ b/task_test.go @@ -1333,6 +1333,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 57db5ee1c3..3759d2dcf0 100644 --- a/taskfile/ast/task.go +++ b/taskfile/ast/task.go @@ -45,6 +45,7 @@ type Task struct { Failfast bool // Populated during merging Namespace string `hash:"ignore"` + IncludeTaskDir string IncludeVars *Vars IncludedTaskfileVars *Vars diff --git a/taskfile/ast/tasks.go b/taskfile/ast/tasks.go index 6cda20bfc9..a8845c92b3 100644 --- a/taskfile/ast/tasks.go +++ b/taskfile/ast/tasks.go @@ -171,7 +171,15 @@ func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) } if include.AdvancedImport { - task.Dir = filepathext.SmartJoin(include.Dir, task.Dir) + if filepathext.IsAbs(task.Dir) { + // TaskDir with (obvious) absolute path has priority. + } else { + // TaskDir will be either or /, however + // task.Dir may still compile to an absolute path. Set/save now and resolve + // later in Compiler.getVariables. + task.IncludeTaskDir = task.Dir + 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