diff --git a/CHANGELOG.md b/CHANGELOG.md index 899769c3..f3d70143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Add new special variables `ROOT_DIR` and `TASKFILE_DIR`. This was a highly + requested feature + ([#215](https://github.com/go-task/task/issues/215), [Documentation](https://taskfile.dev/api/#special-variables)). - Follow symlinks on `sources` ([#826](https://github.com/go-task/task/issues/826), [#831](https://github.com/go-task/task/pull/831)). - Improvements and fixes to Bash completion diff --git a/docs/docs/api_reference.md b/docs/docs/api_reference.md index 6cfcd9cc..39433ef9 100644 --- a/docs/docs/api_reference.md +++ b/docs/docs/api_reference.md @@ -44,6 +44,19 @@ variable | | `--version` | `bool` | `false` | Show Task version. | | `-w` | `--watch` | `bool` | `false` | Enables watch of the given task. | +## Special Variables + +There are some special variables that is available on the templating system: + +| Var | Description | +| - | - | +| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI. | +| `TASK` | The name of the current task. | +| `ROOT_DIR` | The absolute path of the root Taskfile. | +| `TASKFILE_DIR` | The absolute path of the included Taskfile. | +| `CHECKSUM` | The checksum of the files listed in `sources`. Only available within the `status` prop and if method is set to `checksum`. | +| `TIMESTAMP` | The date object of the greatest timestamp of the files listes in `sources`. Only available within the `status` prop and if method is set to `timestamp`. | + ## ENV Some environment variables can be overriden to adjust Task behavior. diff --git a/internal/compiler/v3/compiler_v3.go b/internal/compiler/v3/compiler_v3.go index a1be19e1..5c33b1cc 100644 --- a/internal/compiler/v3/compiler_v3.go +++ b/internal/compiler/v3/compiler_v3.go @@ -44,7 +44,13 @@ func (c *CompilerV3) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*ta func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) { result := compiler.GetEnviron() if t != nil { - result.Set("TASK", taskfile.Var{Static: t.Task}) + specialVars, err := c.getSpecialVars(t) + if err != nil { + return nil, err + } + for k, v := range specialVars { + result.Set(k, taskfile.Var{Static: v}) + } } getRangeFunc := func(dir string) func(k string, v taskfile.Var) error { @@ -165,3 +171,23 @@ func (c *CompilerV3) ResetCache() { c.dynamicCache = nil } + +func (c *CompilerV3) getSpecialVars(t *taskfile.Task) (map[string]string, error) { + taskfileDir, err := c.getTaskfileDir(t) + if err != nil { + return nil, err + } + + return map[string]string{ + "TASK": t.Task, + "ROOT_DIR": c.Dir, + "TASKFILE_DIR": taskfileDir, + }, nil +} + +func (c *CompilerV3) getTaskfileDir(t *taskfile.Task) (string, error) { + if t.IncludedTaskfile != nil { + return t.IncludedTaskfile.FullDirPath() + } + return c.Dir, nil +} diff --git a/setup.go b/setup.go index 1b2e141a..ceacb6b3 100644 --- a/setup.go +++ b/setup.go @@ -20,6 +20,10 @@ import ( ) func (e *Executor) Setup() error { + if err := e.setCurrentDir(); err != nil { + return err + } + if err := e.readTaskfile(); err != nil { return err } @@ -53,6 +57,23 @@ func (e *Executor) Setup() error { return nil } +func (e *Executor) setCurrentDir() error { + if e.Dir == "" { + wd, err := os.Getwd() + if err != nil { + return err + } + e.Dir = wd + } else if !filepath.IsAbs(e.Dir) { + abs, err := filepath.Abs(e.Dir) + if err != nil { + return err + } + e.Dir = abs + } + return nil +} + func (e *Executor) readTaskfile() error { var err error e.Taskfile, err = read.Taskfile(&read.ReaderNode{ diff --git a/task_test.go b/task_test.go index cbc91d07..d26ea0f8 100644 --- a/task_test.go +++ b/task_test.go @@ -164,6 +164,39 @@ func TestMultilineVars(t *testing.T) { } } +func TestSpecialVars(t *testing.T) { + const dir = "testdata/special_vars" + const target = "default" + + var buff bytes.Buffer + e := &task.Executor{ + Dir: dir, + Stdout: &buff, + Stderr: &buff, + Silent: true, + } + assert.NoError(t, e.Setup()) + assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target})) + + toAbs := func(rel string) string { + abs, err := filepath.Abs(rel) + assert.NoError(t, err) + return abs + } + + output := buff.String() + + // Root Taskfile + assert.Contains(t, output, "root/TASK=print") + assert.Contains(t, output, "root/ROOT_DIR="+toAbs("testdata/special_vars")) + assert.Contains(t, output, "root/TASKFILE_DIR="+toAbs("testdata/special_vars")) + + // Included Taskfile + assert.Contains(t, output, "included/TASK=included:print") + assert.Contains(t, output, "included/ROOT_DIR="+toAbs("testdata/special_vars")) + assert.Contains(t, output, "included/TASKFILE_DIR="+toAbs("testdata/special_vars/included")) +} + func TestVarsInvalidTmpl(t *testing.T) { const ( dir = "testdata/vars/v2" @@ -777,12 +810,6 @@ func TestIncludesMultiLevel(t *testing.T) { func TestIncludeCycle(t *testing.T) { const dir = "testdata/includes_cycle" - wd, err := os.Getwd() - assert.Nil(t, err) - - message := "task: include cycle detected between %s/%s/one/Taskfile.yml <--> %s/%s/Taskfile.yml" - expectedError := fmt.Sprintf(message, wd, dir, wd, dir) - var buff bytes.Buffer e := task.Executor{ Dir: dir, @@ -791,7 +818,9 @@ func TestIncludeCycle(t *testing.T) { Silent: true, } - assert.EqualError(t, e.Setup(), expectedError) + err := e.Setup() + assert.Error(t, err) + assert.Contains(t, err.Error(), "task: include cycle detected between") } func TestIncorrectVersionIncludes(t *testing.T) { diff --git a/taskfile/read/taskfile.go b/taskfile/read/taskfile.go index 285df4b7..64f68e0c 100644 --- a/taskfile/read/taskfile.go +++ b/taskfile/read/taskfile.go @@ -144,6 +144,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) { task.Dir = filepathext.SmartJoin(dir, task.Dir) task.IncludeVars = includedTask.Vars task.IncludedTaskfileVars = includedTaskfile.Vars + task.IncludedTaskfile = &includedTask } } diff --git a/taskfile/task.go b/taskfile/task.go index 12484f0b..6fab26af 100644 --- a/taskfile/task.go +++ b/taskfile/task.go @@ -26,6 +26,7 @@ type Task struct { Run string IncludeVars *Vars IncludedTaskfileVars *Vars + IncludedTaskfile *IncludedTaskfile } func (t *Task) Name() string { diff --git a/testdata/special_vars/Taskfile.yml b/testdata/special_vars/Taskfile.yml new file mode 100644 index 00000000..e72d593e --- /dev/null +++ b/testdata/special_vars/Taskfile.yml @@ -0,0 +1,18 @@ +version: '3' + +includes: + included: + taskfile: ./included + dir: ./included + +tasks: + default: + cmds: + - task: print + - task: included:print + + print: + cmds: + - echo root/TASK={{.TASK}} + - echo root/ROOT_DIR={{.ROOT_DIR}} + - echo root/TASKFILE_DIR={{.TASKFILE_DIR}} diff --git a/testdata/special_vars/included/Taskfile.yml b/testdata/special_vars/included/Taskfile.yml new file mode 100644 index 00000000..6000abbc --- /dev/null +++ b/testdata/special_vars/included/Taskfile.yml @@ -0,0 +1,8 @@ +version: '3' + +tasks: + print: + cmds: + - echo included/TASK={{.TASK}} + - echo included/ROOT_DIR={{.ROOT_DIR}} + - echo included/TASKFILE_DIR={{.TASKFILE_DIR}}