Compare commits

...

25 Commits

Author SHA1 Message Date
Andrey Nering
200ba4ed04 v3.15.0 2022-09-03 18:21:40 -03:00
Andrey Nering
1e8939dd58 Merge pull request #857 from go-task/feature/add-dir-special-variables
Add ROOT_DIR and TASKFILE_DIR special variables
2022-09-03 18:18:43 -03:00
Andrey Nering
f45dd11e53 Add ROOT_DIR and TASKFILE_DIR special variables
Closes #215
2022-09-03 18:14:54 -03:00
Andrey Nering
1a0cc1d64d Update favicon.ico 2022-08-31 13:21:43 -03:00
Wes McNamee
421cb522d9 Merge pull request #846 from rootulp/patch-1
fix: grammar in docs
2022-08-24 22:07:23 -07:00
Rootul Patel
1b18b041d6 fix: grammar in docs 2022-08-24 20:30:21 -04:00
Andrey Nering
8788703ac6 CHANGELOG for #831
Closes #826
2022-08-23 18:43:04 -03:00
Andrey Nering
b6c25e3ad9 Mention #844 on CHANGELOG 2022-08-23 18:40:34 -03:00
Andrey Nering
73eaa68cd1 Merge pull request #844 from MarioSchwalbe/bash-completion
Use --silent to get the list of tasks (bash completion)
2022-08-23 18:39:47 -03:00
Andrey Nering
beb927f7b4 Merge pull request #831 from ilewin/check_path_for_symlinks_issue_826
Attempt to fix Task not following symlinks
2022-08-23 18:38:17 -03:00
ilewin
cdc969cd4e Added test to check if symlinks are evaluated for task source files 2022-08-23 18:36:19 +02:00
ilewin
2a67499f12 Issue #826. Replaced zglob.Glob func with GlobFollowSymlinks to evaluate symlinks 2022-08-23 18:25:11 +02:00
Mario Schwalbe
6a3cc79daa * Use --silent to get list of tasks 2022-08-23 18:03:15 +02:00
Andrey Nering
97d4a947ee Merge pull request #838 from pcgeek86/patch-1
Update releasing.md
2022-08-17 20:50:18 -03:00
Wes McNamee
e0e47ad9a0 Merge pull request #841 from cristaloleg/fix-linter
Fix go-critic suggestions
2022-08-17 12:38:12 -07:00
Oleg Kovalov
b08eac58e9 Fix go-critic suggestions 2022-08-17 19:37:58 +02:00
Trevor Sullivan
c2148a359d Update releasing.md 2022-08-16 04:33:54 -06:00
Andrey Nering
c172185a24 Update CHANGELOG 2022-08-15 09:55:11 -03:00
Andrey Nering
1140a5c4ae Merge pull request #835 from MarioSchwalbe/bash-completion
Improved bash completion
2022-08-15 09:52:07 -03:00
Mario Schwalbe
3cc378c960 * Convert indentation to 2 spaces 2022-08-13 21:55:35 +02:00
Andrey Nering
9b3a961303 Merge pull request #832 from jfhovinne/reprobuilds
Modify goreleaser defaults for reproducible builds
2022-08-13 12:04:27 -03:00
Mario Schwalbe
d048555149 Improved bash completion for task 2022-08-11 20:48:41 +02:00
jfhovinne
7533858a52 Modify goreleaser defaults for reproducible builds 2022-08-10 15:33:17 +02:00
Andrey Nering
c4e10ef0aa Refactor: Add SmartJoin to handle IsAbs automatically 2022-08-06 18:19:07 -03:00
Andrey Nering
c20842e7cd v3.14.1: Add CHANGELOG to website 2022-08-03 22:26:39 -03:00
33 changed files with 335 additions and 111 deletions

View File

@@ -17,6 +17,9 @@ build:
goarch: 386
env:
- CGO_ENABLED=0
mod_timestamp: '{{ .CommitTimestamp }}'
flags:
- -trimpath
ldflags:
- -s -w # Don't set main.version.

View File

@@ -1,5 +1,15 @@
# Changelog
## v3.15.0 - 2022-09-03
- 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
([#835](https://github.com/go-task/task/pull/835), [#844](https://github.com/go-task/task/pull/844)).
## v3.14.1 - 2022-08-03
- Always resolve relative include paths relative to the including Taskfile

View File

@@ -1,26 +1,43 @@
#!/bin/bash
# vim: set tabstop=2 shiftwidth=2 expandtab:
GO_TASK_PROGNAME=task
_GO_TASK_COMPLETION_LIST_OPTION='--list-all'
_go_task_completion()
function _task()
{
local cur
_get_comp_words_by_ref -n : cur
local cur prev words cword
_init_completion -n : || return
case "$cur" in
--*)
local options
options="$(_parse_help task)"
mapfile -t COMPREPLY < <(compgen -W "$options" -- "$cur")
# Handle special arguments of options.
case "$prev" in
-d|--dir)
_filedir -d
return $?
;;
*)
local tasks
tasks="$($GO_TASK_PROGNAME --list-all 2> /dev/null | awk 'NR>1 { sub(/:$/,"",$2); print $2 }')"
mapfile -t COMPREPLY < <(compgen -W "$tasks" -- "$cur")
-t|--taskfile)
_filedir yaml || return $?
_filedir yml
return $?
;;
-o|--output)
COMPREPLY=( $( compgen -W "interleaved group prefixed" -- $cur ) )
return 0
;;
esac
# Handle normal options.
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "$(_parse_help $1)" -- $cur ) )
return 0
;;
esac
# Prepare task name completions.
local tasks=( $( "${COMP_WORDS[@]}" --silent $_GO_TASK_COMPLETION_LIST_OPTION 2> /dev/null ) )
COMPREPLY=( $( compgen -W "${tasks[*]}" -- "$cur" ) )
# Post-process because task names might contain colons.
__ltrim_colon_completions "$cur"
}
complete -F _go_task_completion $GO_TASK_PROGNAME
complete -F _task task

View File

@@ -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.

View File

@@ -5,6 +5,24 @@ sidebar_position: 6
# Changelog
## v3.15.0 - 2022-09-03
- 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
([#835](https://github.com/go-task/task/pull/835), [#844](https://github.com/go-task/task/pull/844)).
## v3.14.1 - 2022-08-03
- Always resolve relative include paths relative to the including Taskfile
([#822](https://github.com/go-task/task/issues/822), [#823](https://github.com/go-task/task/pull/823)).
- Fix ZSH and PowerShell completions to consider all tasks instead of just the
public ones (those with descriptions)
([#803](https://github.com/go-task/task/pull/803)).
## v3.14.0 - 2022-07-08
- Add ability to override the `.task` directory location with the

View File

@@ -41,7 +41,7 @@ guide to check the full schema documentation and Task features.
`$PATH` and you're done! Or you can also install using [Homebrew][homebrew],
[Snapcraft][snapcraft], or [Scoop][scoop] if you want.
- Available on CIs: by adding [this simple command](installation.md#install-script)
to install on your CI script and you're done to use Task as part of your CI pipeline;
to install on your CI script and you're ready to use Task as part of your CI pipeline;
- Truly cross-platform: while most build tools only work well on Linux or macOS,
Task also supports Windows thanks to [this shell interpreter for Go][sh].
- Great for code generation: you can easily [prevent a task from running](/usage#prevent-unnecessary-work)

View File

@@ -31,9 +31,9 @@ the [Snapcraft dashboard][snapcraftdashboard].
# Scoop
Scoop is a community owned installation method. Scoop owners usually take care
of updating versions there by editing
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
Scoop is a command-line package manager for the Windows operating system.
Scoop package manifests are maintained by the community.
Scoop owners usually take care of updating versions there by editing [this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
If you think its Task version is outdated, open an issue to let us know.
# Nix

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -17,7 +17,7 @@ type taskNotFoundError struct {
}
func (err *taskNotFoundError) Error() string {
return fmt.Sprintf(`task: Task "%s" not found`, err.taskName)
return fmt.Sprintf(`task: Task %q not found`, err.taskName)
}
type TaskRunError struct {
@@ -26,7 +26,7 @@ type TaskRunError struct {
}
func (err *TaskRunError) Error() string {
return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
return fmt.Sprintf(`task: Failed to run task %q: %v`, err.taskName, err.err)
}
func (err *TaskRunError) ExitCode() int {
@@ -46,7 +46,7 @@ type MaximumTaskCallExceededError struct {
func (e *MaximumTaskCallExceededError) Error() string {
return fmt.Sprintf(
`task: maximum task call exceeded (%d) for task "%s": probably an cyclic dep or infinite loop`,
`task: maximum task call exceeded (%d) for task %q: probably an cyclic dep or infinite loop`,
MaximumTaskCall,
e.task,
)

View File

@@ -4,7 +4,8 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/go-task/task/v3/internal/filepathext"
)
const defaultTaskfile = `# https://taskfile.dev
@@ -23,13 +24,13 @@ tasks:
// InitTaskfile Taskfile creates a new Taskfile
func InitTaskfile(w io.Writer, dir string) error {
f := filepath.Join(dir, "Taskfile.yaml")
f := filepathext.SmartJoin(dir, "Taskfile.yaml")
if _, err := os.Stat(f); err == nil {
return ErrTaskfileAlreadyExists
}
if err := os.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
if err := os.WriteFile(f, []byte(defaultTaskfile), 0o644); err != nil {
return err
}
fmt.Fprintf(w, "Taskfile.yaml created in the current directory\n")

View File

@@ -4,12 +4,12 @@ import (
"bytes"
"context"
"fmt"
"path/filepath"
"strings"
"sync"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
@@ -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 {
@@ -83,9 +89,7 @@ func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluat
if err := tr.Err(); err != nil {
return nil, err
}
if !filepath.IsAbs(dir) {
dir = filepath.Join(c.Dir, dir)
}
dir = filepathext.SmartJoin(c.Dir, dir)
taskRangeFunc = getRangeFunc(dir)
}
@@ -167,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
}

View File

@@ -73,7 +73,7 @@ func IsExitError(err error) bool {
// if available.
func Expand(s string) (string, error) {
s = filepath.ToSlash(s)
s = strings.Replace(s, " ", `\ `, -1)
s = strings.ReplaceAll(s, " ", `\ `)
fields, err := shell.Fields(s, nil)
if err != nil {
return "", err

View File

@@ -0,0 +1,14 @@
package filepathext
import (
"path/filepath"
)
// SmartJoin joins two paths, but only if the second is not already an
// absolute path.
func SmartJoin(a, b string) string {
if filepath.IsAbs(b) {
return b
}
return filepath.Join(a, b)
}

View File

@@ -8,6 +8,8 @@ import (
"path/filepath"
"regexp"
"strings"
"github.com/go-task/task/v3/internal/filepathext"
)
// Checksum validades if a task is up to date by calculating its source
@@ -43,8 +45,8 @@ func (c *Checksum) IsUpToDate() (bool, error) {
}
if !c.Dry {
_ = os.MkdirAll(filepath.Join(c.TempDir, "checksum"), 0755)
if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
_ = os.MkdirAll(filepathext.SmartJoin(c.TempDir, "checksum"), 0o755)
if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0o644); err != nil {
return false, err
}
}

View File

@@ -2,12 +2,12 @@ package status
import (
"os"
"path/filepath"
"sort"
"github.com/mattn/go-zglob"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
)
func globs(dir string, globs []string) ([]string, error) {
@@ -25,17 +25,18 @@ func globs(dir string, globs []string) ([]string, error) {
func Glob(dir string, g string) ([]string, error) {
files := make([]string, 0)
if !filepath.IsAbs(g) {
g = filepath.Join(dir, g)
}
g = filepathext.SmartJoin(dir, g)
g, err := execext.Expand(g)
if err != nil {
return nil, err
}
fs, err := zglob.Glob(g)
fs, err := zglob.GlobFollowSymlinks(g)
if err != nil {
return nil, err
}
for _, f := range fs {
info, err := os.Stat(f)
if err != nil {

View File

@@ -19,11 +19,11 @@ func init() {
"OS": func() string { return runtime.GOOS },
"ARCH": func() string { return runtime.GOARCH },
"catLines": func(s string) string {
s = strings.Replace(s, "\r\n", " ", -1)
return strings.Replace(s, "\n", " ", -1)
s = strings.ReplaceAll(s, "\r\n", " ")
return strings.ReplaceAll(s, "\n", " ")
},
"splitLines": func(s string) []string {
s = strings.Replace(s, "\r\n", "\n", -1)
s = strings.ReplaceAll(s, "\r\n", "\n")
return strings.Split(s, "\n")
},
"fromSlash": func(path string) string {

View File

@@ -12,6 +12,7 @@ import (
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/taskfile"
@@ -19,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
}
@@ -52,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{
@@ -69,7 +91,7 @@ func (e *Executor) setupTempDir() error {
}
if os.Getenv("TASK_TEMP_DIR") == "" {
e.TempDir = filepath.Join(e.Dir, ".task")
e.TempDir = filepathext.SmartJoin(e.Dir, ".task")
} else if filepath.IsAbs(os.Getenv("TASK_TEMP_DIR")) || strings.HasPrefix(os.Getenv("TASK_TEMP_DIR"), "~") {
tempDir, err := execext.Expand(os.Getenv("TASK_TEMP_DIR"))
if err != nil {
@@ -77,9 +99,9 @@ func (e *Executor) setupTempDir() error {
}
projectDir, _ := filepath.Abs(e.Dir)
projectName := filepath.Base(projectDir)
e.TempDir = filepath.Join(tempDir, projectName)
e.TempDir = filepathext.SmartJoin(tempDir, projectName)
} else {
e.TempDir = filepath.Join(e.Dir, os.Getenv("TASK_TEMP_DIR"))
e.TempDir = filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR"))
}
return nil

View File

@@ -9,7 +9,7 @@ import (
)
// NOTE(@andreynering): This function intercepts SIGINT and SIGTERM signals
// so the Task process is not killed immediatelly and processes running have
// so the Task process is not killed immediately and processes running have
// time to do cleanup work.
func (e *Executor) InterceptInterruptSignals() {
ch := make(chan os.Signal, 3)

View File

@@ -181,7 +181,7 @@ func (e *Executor) mkdir(t *taskfile.Task) error {
defer mutex.Unlock()
if _, err := os.Stat(t.Dir); os.IsNotExist(err) {
if err := os.MkdirAll(t.Dir, 0755); err != nil {
if err := os.MkdirAll(t.Dir, 0o755); err != nil {
return err
}
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile"
)
@@ -37,12 +38,12 @@ func (fct fileContentTest) name(file string) string {
func (fct fileContentTest) Run(t *testing.T) {
for f := range fct.Files {
_ = os.Remove(filepath.Join(fct.Dir, f))
_ = os.Remove(filepathext.SmartJoin(fct.Dir, f))
}
e := &task.Executor{
Dir: fct.Dir,
TempDir: filepath.Join(fct.Dir, ".task"),
TempDir: filepathext.SmartJoin(fct.Dir, ".task"),
Entrypoint: fct.Entrypoint,
Stdout: io.Discard,
Stderr: io.Discard,
@@ -52,7 +53,7 @@ func (fct fileContentTest) Run(t *testing.T) {
for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
path := filepath.Join(fct.Dir, name)
path := filepathext.SmartJoin(fct.Dir, name)
b, err := os.ReadFile(path)
assert.NoError(t, err, "Error reading file")
s := string(b)
@@ -163,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"
@@ -235,7 +269,7 @@ func TestDeps(t *testing.T) {
}
for _, f := range files {
_ = os.Remove(filepath.Join(dir, f))
_ = os.Remove(filepathext.SmartJoin(dir, f))
}
e := &task.Executor{
@@ -247,7 +281,7 @@ func TestDeps(t *testing.T) {
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
for _, f := range files {
f = filepath.Join(dir, f)
f = filepathext.SmartJoin(dir, f)
if _, err := os.Stat(f); err != nil {
t.Errorf("File %s should exist", f)
}
@@ -263,7 +297,7 @@ func TestStatus(t *testing.T) {
}
for _, f := range files {
path := filepath.Join(dir, f)
path := filepathext.SmartJoin(dir, f)
_ = os.Remove(path)
if _, err := os.Stat(path); err == nil {
t.Errorf("File should not exist: %v", err)
@@ -273,7 +307,7 @@ func TestStatus(t *testing.T) {
var buff bytes.Buffer
e := &task.Executor{
Dir: dir,
TempDir: filepath.Join(dir, ".task"),
TempDir: filepathext.SmartJoin(dir, ".task"),
Stdout: &buff,
Stderr: &buff,
Silent: true,
@@ -283,7 +317,7 @@ func TestStatus(t *testing.T) {
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-bar"}))
for _, f := range files {
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
if _, err := os.Stat(filepathext.SmartJoin(dir, f)); err != nil {
t.Errorf("File should exist: %v", err)
}
}
@@ -360,10 +394,10 @@ func TestGenerates(t *testing.T) {
fileWithSpaces = "my text file.txt"
)
var srcFile = filepath.Join(dir, srcTask)
var srcFile = filepathext.SmartJoin(dir, srcTask)
for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
path := filepath.Join(dir, task)
path := filepathext.SmartJoin(dir, task)
_ = os.Remove(path)
if _, err := os.Stat(path); err == nil {
t.Errorf("File should not exist: %v", err)
@@ -379,7 +413,7 @@ func TestGenerates(t *testing.T) {
assert.NoError(t, e.Setup())
for _, theTask := range []string{relTask, absTask, fileWithSpaces} {
var destFile = filepath.Join(dir, theTask)
var destFile = filepathext.SmartJoin(dir, theTask)
var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
@@ -416,16 +450,16 @@ func TestStatusChecksum(t *testing.T) {
}
for _, f := range files {
_ = os.Remove(filepath.Join(dir, f))
_ = os.Remove(filepathext.SmartJoin(dir, f))
_, err := os.Stat(filepath.Join(dir, f))
_, err := os.Stat(filepathext.SmartJoin(dir, f))
assert.Error(t, err)
}
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
TempDir: filepath.Join(dir, ".task"),
TempDir: filepathext.SmartJoin(dir, ".task"),
Stdout: &buff,
Stderr: &buff,
}
@@ -433,7 +467,7 @@ func TestStatusChecksum(t *testing.T) {
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
for _, f := range files {
_, err := os.Stat(filepath.Join(dir, f))
_, err := os.Stat(filepathext.SmartJoin(dir, f))
assert.NoError(t, err)
}
@@ -577,13 +611,13 @@ func TestListCanListDescOnly(t *testing.T) {
func TestStatusVariables(t *testing.T) {
const dir = "testdata/status_vars"
_ = os.RemoveAll(filepath.Join(dir, ".task"))
_ = os.Remove(filepath.Join(dir, "generated.txt"))
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
_ = os.Remove(filepathext.SmartJoin(dir, "generated.txt"))
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
TempDir: filepath.Join(dir, ".task"),
TempDir: filepathext.SmartJoin(dir, ".task"),
Stdout: &buff,
Stderr: &buff,
Silent: false,
@@ -594,7 +628,7 @@ func TestStatusVariables(t *testing.T) {
assert.Contains(t, buff.String(), "d41d8cd98f00b204e9800998ecf8427e")
inf, err := os.Stat(filepath.Join(dir, "source.txt"))
inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
assert.NoError(t, err)
ts := fmt.Sprintf("%d", inf.ModTime().Unix())
tf := inf.ModTime().String()
@@ -605,7 +639,7 @@ func TestStatusVariables(t *testing.T) {
func TestInit(t *testing.T) {
const dir = "testdata/init"
var file = filepath.Join(dir, "Taskfile.yaml")
var file = filepathext.SmartJoin(dir, "Taskfile.yaml")
_ = os.Remove(file)
if _, err := os.Stat(file); err == nil {
@@ -694,7 +728,7 @@ func TestExpand(t *testing.T) {
func TestDry(t *testing.T) {
const dir = "testdata/dry"
file := filepath.Join(dir, "file.txt")
file := filepathext.SmartJoin(dir, "file.txt")
_ = os.Remove(file)
var buff bytes.Buffer
@@ -719,12 +753,12 @@ func TestDry(t *testing.T) {
func TestDryChecksum(t *testing.T) {
const dir = "testdata/dry_checksum"
checksumFile := filepath.Join(dir, ".task/checksum/default")
checksumFile := filepathext.SmartJoin(dir, ".task/checksum/default")
_ = os.Remove(checksumFile)
e := task.Executor{
Dir: dir,
TempDir: filepath.Join(dir, ".task"),
TempDir: filepathext.SmartJoin(dir, ".task"),
Stdout: io.Discard,
Stderr: io.Discard,
Dry: true,
@@ -776,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,
@@ -790,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) {
@@ -964,12 +994,12 @@ func TestSummary(t *testing.T) {
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
data, err := os.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
data, err := os.ReadFile(filepathext.SmartJoin(dir, "task-with-summary.txt"))
assert.NoError(t, err)
expectedOutput := string(data)
if runtime.GOOS == "windows" {
expectedOutput = strings.Replace(expectedOutput, "\r\n", "\n", -1)
expectedOutput = strings.ReplaceAll(expectedOutput, "\r\n", "\n")
}
assert.Equal(t, expectedOutput, buff.String())
@@ -1337,3 +1367,36 @@ func TestErrorCode(t *testing.T) {
assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
assert.Equal(t, 42, casted.ExitCode(), "unexpected exit code from task")
}
func TestEvaluateSymlinksInPaths(t *testing.T) {
const dir = "testdata/evaluate_symlinks_in_paths"
var buff bytes.Buffer
e := &task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Silent: false,
}
assert.NoError(t, e.Setup())
err := e.Run(context.Background(), taskfile.Call{Task: "default"})
assert.NoError(t, err)
assert.NotEqual(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
buff.Reset()
err = e.Run(context.Background(), taskfile.Call{Task: "test-sym"})
assert.NoError(t, err)
assert.NotEqual(t, `task: Task "test-sym" is up to date`, strings.TrimSpace(buff.String()))
buff.Reset()
err = e.Run(context.Background(), taskfile.Call{Task: "default"})
assert.NoError(t, err)
assert.NotEqual(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
buff.Reset()
err = e.Run(context.Background(), taskfile.Call{Task: "default"})
assert.NoError(t, err)
assert.Equal(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
buff.Reset()
err = e.Run(context.Background(), taskfile.Call{Task: "reset"})
assert.NoError(t, err)
buff.Reset()
err = os.RemoveAll(dir + "/.task")
assert.NoError(t, err)
}

View File

@@ -6,6 +6,7 @@ import (
"path/filepath"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"gopkg.in/yaml.v3"
)
@@ -133,7 +134,7 @@ func (it *IncludedTaskfile) resolvePath(path string) (string, error) {
return path, nil
}
result, err := filepath.Abs(filepath.Join(it.BaseDir, path))
result, err := filepath.Abs(filepathext.SmartJoin(it.BaseDir, path))
if err != nil {
return "", fmt.Errorf("task: error resolving path %s relative to %s: %w", path, it.BaseDir, err)
}

View File

@@ -2,11 +2,11 @@ package read
import (
"os"
"path/filepath"
"github.com/joho/godotenv"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
@@ -27,10 +27,8 @@ func Dotenv(c compiler.Compiler, tf *taskfile.Taskfile, dir string) (*taskfile.V
for _, dotEnvPath := range tf.Dotenv {
dotEnvPath = tr.Replace(dotEnvPath)
dotEnvPath = filepathext.SmartJoin(dir, dotEnvPath)
if !filepath.IsAbs(dotEnvPath) {
dotEnvPath = filepath.Join(dir, dotEnvPath)
}
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
continue
}

View File

@@ -9,6 +9,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
@@ -44,7 +45,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
readerNode.Dir = d
}
path, err := exists(filepath.Join(readerNode.Dir, readerNode.Entrypoint))
path, err := exists(filepathext.SmartJoin(readerNode.Dir, readerNode.Entrypoint))
if err != nil {
return nil, err
}
@@ -140,12 +141,10 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
}
for _, task := range includedTaskfile.Tasks {
if !filepath.IsAbs(task.Dir) {
task.Dir = filepath.Join(dir, task.Dir)
}
task.Dir = filepathext.SmartJoin(dir, task.Dir)
task.IncludeVars = includedTask.Vars
task.IncludedTaskfileVars = includedTaskfile.Vars
task.IncludedTaskfile = &includedTask
}
}
@@ -159,7 +158,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
}
if v < 3.0 {
path = filepath.Join(readerNode.Dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
path = filepathext.SmartJoin(readerNode.Dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
if err != nil {
@@ -191,29 +190,19 @@ func readTaskfile(file string) (*taskfile.Taskfile, error) {
return &t, yaml.NewDecoder(f).Decode(&t)
}
// exists finds a Taskfile at the stated location, returning a fully qualified path to the file
func exists(path string) (string, error) {
fi, err := os.Stat(path)
if err != nil {
return "", err
}
if fi.Mode().IsRegular() {
// File exists, return a fully qualified path
result, err := filepath.Abs(path)
if err != nil {
return "", err
}
return result, nil
return path, nil
}
for _, n := range defaultTaskfiles {
fpath := filepath.Join(path, n)
fpath := filepathext.SmartJoin(path, n)
if _, err := os.Stat(fpath); err == nil {
result, err := filepath.Abs(fpath)
if err != nil {
return "", err
}
return result, nil
return fpath, nil
}
}
@@ -228,14 +217,14 @@ func checkCircularIncludes(node *ReaderNode) error {
return errors.New("task: failed to check for include cycle: node.Parent was nil")
}
var curNode = node
var basePath = filepath.Join(node.Dir, node.Entrypoint)
var basePath = filepathext.SmartJoin(node.Dir, node.Entrypoint)
for curNode.Parent != nil {
curNode = curNode.Parent
curPath := filepath.Join(curNode.Dir, curNode.Entrypoint)
curPath := filepathext.SmartJoin(curNode.Dir, curNode.Entrypoint)
if curPath == basePath {
return fmt.Errorf("task: include cycle detected between %s <--> %s",
curPath,
filepath.Join(node.Parent.Dir, node.Parent.Entrypoint),
filepathext.SmartJoin(node.Parent.Dir, node.Parent.Entrypoint),
)
}
}

View File

@@ -3,11 +3,11 @@ package read
import (
"fmt"
"os"
"path/filepath"
"runtime"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile"
)
@@ -15,7 +15,7 @@ import (
func Taskvars(dir string) (*taskfile.Vars, error) {
vars := &taskfile.Vars{}
path := filepath.Join(dir, "Taskvars.yml")
path := filepathext.SmartJoin(dir, "Taskvars.yml")
if _, err := os.Stat(path); err == nil {
vars, err = readTaskvars(path)
if err != nil {
@@ -23,7 +23,7 @@ func Taskvars(dir string) (*taskfile.Vars, error) {
}
}
path = filepath.Join(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
path = filepathext.SmartJoin(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
if _, err := os.Stat(path); err == nil {
osVars, err := readTaskvars(path)
if err != nil {

View File

@@ -26,6 +26,7 @@ type Task struct {
Run string
IncludeVars *Vars
IncludedTaskfileVars *Vars
IncludedTaskfile *IncludedTaskfile
}
func (t *Task) Name() string {

View File

@@ -0,0 +1,17 @@
version: '3'
tasks:
default:
sources:
- src/**/*
cmds:
- echo "some job"
test-sym:
cmds:
- echo "shared file source changed" > src/shared/b
reset:
cmds:
- echo "shared file source" > src/shared/b
- echo "file source" > src/a

View File

@@ -0,0 +1 @@
shared file source

View File

@@ -0,0 +1 @@
inner shared file source

View File

@@ -0,0 +1 @@
file source

View File

@@ -0,0 +1 @@
../shared

18
testdata/special_vars/Taskfile.yml vendored Normal file
View File

@@ -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}}

View File

@@ -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}}

View File

@@ -1,10 +1,10 @@
package task
import (
"path/filepath"
"strings"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/status"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
@@ -68,8 +68,8 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
if err != nil {
return nil, err
}
if e.Dir != "" && !filepath.IsAbs(new.Dir) {
new.Dir = filepath.Join(e.Dir, new.Dir)
if e.Dir != "" {
new.Dir = filepathext.SmartJoin(e.Dir, new.Dir)
}
if new.Prefix == "" {
new.Prefix = new.Task