mirror of
https://github.com/go-task/task.git
synced 2026-06-18 21:31:50 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2abc13ce2 | ||
|
|
0f4621fb02 | ||
|
|
c6ff641f6d | ||
|
|
350f74a53d | ||
|
|
41cd7acc87 | ||
|
|
c6eea26660 | ||
|
|
61c5718663 | ||
|
|
9897f4b527 | ||
|
|
978a6e5ecb | ||
|
|
a018997ddc | ||
|
|
de09843467 | ||
|
|
78a57fdb4b | ||
|
|
0bc2fd72f0 | ||
|
|
dda5164efd | ||
|
|
3df2396b63 | ||
|
|
d3da84e724 | ||
|
|
eb61015477 | ||
|
|
40c644f006 | ||
|
|
c9aa0180a8 | ||
|
|
a06e46885d | ||
|
|
60fa6e6c0a | ||
|
|
2f18f7927d | ||
|
|
292cf75836 | ||
|
|
fc95061f4c | ||
|
|
1f1275255c | ||
|
|
7cdf0000d9 | ||
|
|
13606e5e00 | ||
|
|
35af240faa | ||
|
|
0ac56f8973 | ||
|
|
6e5f8b1fb0 | ||
|
|
15e831c0b0 | ||
|
|
d3e2fbf1e2 | ||
|
|
66748ab5e5 | ||
|
|
c73a2c8f84 | ||
|
|
4bbcd99b8b | ||
|
|
02e7ff27c7 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17.x
|
||||
go-version: 1.18.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -5,7 +5,7 @@ jobs:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.16.x, 1.17.x]
|
||||
go-version: [1.17.x, 1.18.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,6 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
## v3.11.0 - 2022-01-19
|
||||
## v3.12.0 - 2022-03-31
|
||||
|
||||
- The `--list` and `--list-all` flags can now be combined with the `--silent`
|
||||
flag to print the task names only, without their description
|
||||
([#691](https://github.com/go-task/task/pull/691)).
|
||||
- Added support for multi-level inclusion of Taskfiles. This means that
|
||||
included Taskfiles can also include other Taskfiles. Before this was limited
|
||||
to one level
|
||||
([#390](https://github.com/go-task/task/issues/390), [#623](https://github.com/go-task/task/discussions/623), [#656](https://github.com/go-task/task/pull/656)).
|
||||
- Add ability to specify vars when including a Taskfile.
|
||||
[Check out the documentation](https://taskfile.dev/#/usage?id=vars-of-included-taskfiles)
|
||||
for more information.
|
||||
([#677](https://github.com/go-task/task/pull/677)).
|
||||
|
||||
## v3.11.0 - 2022-02-19
|
||||
|
||||
- Task now supports printing begin and end messages when using the `group`
|
||||
output mode, useful for grouping tasks in CI systems.
|
||||
|
||||
@@ -158,6 +158,12 @@ func main() {
|
||||
|
||||
OutputStyle: output,
|
||||
}
|
||||
|
||||
if (list || listAll) && silent {
|
||||
e.ListTaskNames(listAll)
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.Setup(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,60 @@
|
||||
#compdef task
|
||||
|
||||
# Listing commands from Taskfile.yml
|
||||
function __list() {
|
||||
local -a scripts
|
||||
local context state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
if [ -f Taskfile.yml ] || [ -f Taskfile.yaml ]; then
|
||||
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/g'))
|
||||
_describe 'script' scripts
|
||||
# Listing commands from Taskfile.yml
|
||||
function __task_list() {
|
||||
local -a scripts cmd
|
||||
local -i enabled=0
|
||||
local taskfile item task desc
|
||||
|
||||
cmd=(task)
|
||||
taskfile="${(v)opt_args[(i)-t|--taskfile]}"
|
||||
|
||||
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
cmd+=(--taskfile "$taskfile")
|
||||
else
|
||||
for taskfile in Taskfile{,.dist}.{yaml,yml}; do
|
||||
if [[ -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
(( enabled )) || return 0
|
||||
|
||||
scripts=()
|
||||
for item in "${(@)${(f)$("${cmd[@]}" --list)}[2,-1]#\* }"; do
|
||||
task="${item%%:[[:space:]]*}"
|
||||
desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
done
|
||||
_describe 'Task to run' scripts
|
||||
}
|
||||
|
||||
_arguments \
|
||||
'(-d --dir)'{-d,--dir}': :_files' \
|
||||
'(--dry)'--dry \
|
||||
'(-f --force)'{-f,--force} \
|
||||
'(-i --init)'{-i,--init} \
|
||||
'(-l --list)'{-l,--list} \
|
||||
'(-s --silent)'{-s,--silent} \
|
||||
'(--status)'--status \
|
||||
'(-v --verbose)'{-v,--verbose} \
|
||||
'(--version)'--version \
|
||||
'(-w --watch)'{-w,--watch} \
|
||||
'(- *)'{-h,--help} \
|
||||
'*: :__list' \
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
|
||||
'(-c --color)'{-c,--color}'[colored output]' \
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]' \
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]' \
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]' \
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
|
||||
+ '(operation)' \
|
||||
{-l,--list}'[list describable tasks]' \
|
||||
{-a,--list-all}'[list all tasks]' \
|
||||
{-i,--init}'[create new Taskfile.yaml]' \
|
||||
'(-*)'{-h,--help}'[show help]' \
|
||||
'(-*)--version[show version and exit]' \
|
||||
'*: :__task_list'
|
||||
|
||||
@@ -25,6 +25,18 @@ right:
|
||||
sudo snap install task --classic
|
||||
```
|
||||
|
||||
#### **Chocolatey**
|
||||
|
||||
If you're on Windows and have [Chocolatey][choco] installed, getting
|
||||
Task is as simple as running:
|
||||
|
||||
```bash
|
||||
choco install go-task
|
||||
```
|
||||
|
||||
This installation method is community owned.
|
||||
|
||||
|
||||
#### **Scoop**
|
||||
|
||||
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
|
||||
@@ -150,4 +162,5 @@ env GO111MODULE=on go get -u github.com/go-task/task/v3/cmd/task@latest
|
||||
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
|
||||
[releases]: https://github.com/go-task/task/releases
|
||||
[godownloader]: https://github.com/goreleaser/godownloader
|
||||
[choco]: https://chocolatey.org/
|
||||
[scoop]: https://scoop.sh/
|
||||
|
||||
@@ -164,10 +164,6 @@ includes:
|
||||
> The included Taskfiles must be using the same schema version the main
|
||||
> Taskfile uses.
|
||||
|
||||
> Also, for now included Taskfiles can't include other Taskfiles.
|
||||
> This was a deliberate decision to keep use and implementation simple.
|
||||
> If you disagree, open an GitHub issue and explain your use case. =)
|
||||
|
||||
### Optional includes
|
||||
|
||||
Includes marked as optional will allow Task to continue execution as normal if
|
||||
@@ -187,6 +183,31 @@ tasks:
|
||||
- echo "This command can still be successfully executed if ./tests/Taskfile.yml does not exist"
|
||||
```
|
||||
|
||||
### Vars of included Taskfiles
|
||||
|
||||
You can also specify variables when including a Taskfile. This may be useful
|
||||
for having reusable Taskfile that can be tweaked or even included more than once:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
backend:
|
||||
taskfile: ./taskfiles/Docker.yml
|
||||
vars:
|
||||
DOCKER_IMAGE: backend_image
|
||||
|
||||
frontend:
|
||||
taskfile: ./taskfiles/Docker.yml
|
||||
vars:
|
||||
DOCKER_IMAGE: frontend_image
|
||||
```
|
||||
|
||||
> NOTE: Vars declared in the included Taskfile have preference over the
|
||||
included ones! If you want a variable in an included Taskfile to be overridable
|
||||
use the [default function](https://go-task.github.io/slim-sprig/defaults.html):
|
||||
`MY_VAR: '{{.MY_VAR | default "my-default-value"}}'`.
|
||||
|
||||
## Task directory
|
||||
|
||||
By default, tasks will be executed in the directory where the Taskfile is
|
||||
@@ -535,6 +556,8 @@ They are listed below in order of importance (e.g. most important first):
|
||||
- Variables declared in the task definition
|
||||
- Variables given while calling a task from another
|
||||
(See [Calling another task](#calling-another-task) above)
|
||||
- Variables of the [included Taskfile](#including-other-taskfiles) (when the task is included)
|
||||
- Variables of the [inclusion of the Taskfile](#vars-of-included-taskfiles) (when the task is included)
|
||||
- Global variables (those declared in the `vars:` option in the Taskfile)
|
||||
- Environment variables
|
||||
|
||||
|
||||
13
go.mod
13
go.mod
@@ -8,10 +8,19 @@ require (
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/radovskyb/watcher v1.0.7
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
mvdan.cc/sh/v3 v3.4.3
|
||||
)
|
||||
|
||||
go 1.16
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 // indirect
|
||||
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf // indirect
|
||||
)
|
||||
|
||||
go 1.17
|
||||
|
||||
4
go.sum
4
go.sum
@@ -46,8 +46,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
34
help.go
34
help.go
@@ -2,7 +2,11 @@ package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -70,3 +74,33 @@ func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
|
||||
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
|
||||
return
|
||||
}
|
||||
|
||||
// PrintTaskNames prints only the task names in a Taskfile.
|
||||
// Only tasks with a non-empty description are printed if allTasks is false.
|
||||
// Otherwise, all task names are printed.
|
||||
func (e *Executor) ListTaskNames(allTasks bool) {
|
||||
// if called from cmd/task.go, e.Taskfile has not yet been parsed
|
||||
if e.Taskfile == nil {
|
||||
if err := e.readTaskfile(); err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// use stdout if no output defined
|
||||
var w io.Writer = os.Stdout
|
||||
if e.Stdout != nil {
|
||||
w = e.Stdout
|
||||
}
|
||||
// create a string slice from all map values (*taskfile.Task)
|
||||
s := make([]string, 0, len(e.Taskfile.Tasks))
|
||||
for _, t := range e.Taskfile.Tasks {
|
||||
if allTasks || t.Desc != "" {
|
||||
s = append(s, strings.TrimRight(t.Task, ":"))
|
||||
}
|
||||
}
|
||||
// sort and print all task names
|
||||
sort.Strings(s)
|
||||
for _, t := range s {
|
||||
fmt.Fprintln(w, t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,12 +74,35 @@ func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluat
|
||||
}
|
||||
rangeFunc := getRangeFunc(c.Dir)
|
||||
|
||||
var taskRangeFunc func(k string, v taskfile.Var) error
|
||||
if t != nil {
|
||||
// NOTE(@andreynering): We're manually joining these paths here because
|
||||
// this is the raw task, not the compiled one.
|
||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
||||
dir := tr.Replace(t.Dir)
|
||||
if err := tr.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !filepath.IsAbs(dir) {
|
||||
dir = filepath.Join(c.Dir, dir)
|
||||
}
|
||||
taskRangeFunc = getRangeFunc(dir)
|
||||
}
|
||||
|
||||
if err := c.TaskfileEnv.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t != nil {
|
||||
if err := t.IncludedTaskfileVars.Range(taskRangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.IncludeVars.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if t == nil || call == nil {
|
||||
return result, nil
|
||||
@@ -88,19 +111,6 @@ func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluat
|
||||
if err := call.Vars.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// NOTE(@andreynering): We're manually joining these paths here because
|
||||
// this is the raw task, not the compiled one.
|
||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
||||
dir := tr.Replace(t.Dir)
|
||||
if err := tr.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !filepath.IsAbs(dir) {
|
||||
dir = filepath.Join(c.Dir, dir)
|
||||
}
|
||||
taskRangeFunc := getRangeFunc(dir)
|
||||
|
||||
if err := t.Vars.Range(taskRangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
15
task.go
15
task.go
@@ -104,10 +104,21 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// readTaskfile selects and parses the entrypoint.
|
||||
func (e *Executor) readTaskfile() error {
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(&read.ReaderNode{
|
||||
Dir: e.Dir,
|
||||
Entrypoint: e.Entrypoint,
|
||||
Parent: nil,
|
||||
Optional: false,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup setups Executor's internal state
|
||||
func (e *Executor) Setup() error {
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
|
||||
err := e.readTaskfile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
58
task_test.go
58
task_test.go
@@ -753,6 +753,35 @@ func TestIncludes(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludesMultiLevel(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_multi_level",
|
||||
Target: "default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"called_one.txt": "one",
|
||||
"called_two.txt": "two",
|
||||
"called_three.txt": "three",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludeCycle(t *testing.T) {
|
||||
const dir = "testdata/includes_cycle"
|
||||
expectedError := "task: include cycle detected between testdata/includes_cycle/Taskfile.yml <--> testdata/includes_cycle/one/two/Taskfile.yml"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
assert.EqualError(t, e.Setup(), expectedError)
|
||||
}
|
||||
|
||||
func TestIncorrectVersionIncludes(t *testing.T) {
|
||||
const dir = "testdata/incorrect_includes"
|
||||
expectedError := "task: Import with additional parameters is only available starting on Taskfile version v3"
|
||||
@@ -1218,3 +1247,32 @@ Bye!
|
||||
t.Log(buff.String())
|
||||
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
|
||||
}
|
||||
|
||||
func TestIncludedVars(t *testing.T) {
|
||||
const dir = "testdata/include_with_vars"
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
expectedOutputOrder := strings.TrimSpace(`
|
||||
task: [included1:task1] echo "VAR_1 is included1-var1"
|
||||
VAR_1 is included1-var1
|
||||
task: [included1:task1] echo "VAR_2 is included-default-var2"
|
||||
VAR_2 is included-default-var2
|
||||
task: [included2:task1] echo "VAR_1 is included2-var1"
|
||||
VAR_1 is included2-var1
|
||||
task: [included2:task1] echo "VAR_2 is included-default-var2"
|
||||
VAR_2 is included-default-var2
|
||||
task: [included3:task1] echo "VAR_1 is included-default-var1"
|
||||
VAR_1 is included-default-var1
|
||||
task: [included3:task1] echo "VAR_2 is included-default-var2"
|
||||
VAR_2 is included-default-var2
|
||||
`)
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task1"}))
|
||||
t.Log(buff.String())
|
||||
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type IncludedTaskfile struct {
|
||||
Dir string
|
||||
Optional bool
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
}
|
||||
|
||||
// IncludedTaskfiles represents information about included tasksfiles
|
||||
@@ -94,6 +95,7 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
Vars *Vars
|
||||
}
|
||||
if err := unmarshal(&includedTaskfile); err != nil {
|
||||
return err
|
||||
@@ -102,5 +104,6 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||
it.Dir = includedTaskfile.Dir
|
||||
it.Optional = includedTaskfile.Optional
|
||||
it.AdvancedImport = true
|
||||
it.Vars = includedTaskfile.Vars
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
|
||||
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
|
||||
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
||||
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
||||
|
||||
@@ -28,21 +26,29 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type ReaderNode struct {
|
||||
Dir string
|
||||
Entrypoint string
|
||||
Optional bool
|
||||
Parent *ReaderNode
|
||||
}
|
||||
|
||||
// Taskfile reads a Taskfile for a given directory
|
||||
// Uses current dir when dir is left empty. Uses Taskfile.yml
|
||||
// or Taskfile.yaml when entrypoint is left empty
|
||||
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
if dir == "" {
|
||||
func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
if readerNode.Dir == "" {
|
||||
d, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir = d
|
||||
readerNode.Dir = d
|
||||
}
|
||||
path, err := exists(filepath.Join(dir, entrypoint))
|
||||
path, err := exists(filepath.Join(readerNode.Dir, readerNode.Entrypoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readerNode.Entrypoint = filepath.Base(path)
|
||||
|
||||
t, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
@@ -62,6 +68,7 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
Dir: tr.Replace(includedTask.Dir),
|
||||
Optional: includedTask.Optional,
|
||||
AdvancedImport: includedTask.AdvancedImport,
|
||||
Vars: includedTask.Vars,
|
||||
}
|
||||
if err := tr.Err(); err != nil {
|
||||
return err
|
||||
@@ -73,9 +80,8 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
return err
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(dir, path)
|
||||
path = filepath.Join(readerNode.Dir, path)
|
||||
}
|
||||
|
||||
path, err = exists(path)
|
||||
if err != nil {
|
||||
if includedTask.Optional {
|
||||
@@ -84,12 +90,23 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
includedTaskfile, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
includeReaderNode := &ReaderNode{
|
||||
Dir: filepath.Dir(path),
|
||||
Entrypoint: filepath.Base(path),
|
||||
Parent: readerNode,
|
||||
Optional: includedTask.Optional,
|
||||
}
|
||||
|
||||
if err := checkCircularIncludes(includeReaderNode); err != nil {
|
||||
return err
|
||||
}
|
||||
if includedTaskfile.Includes.Len() > 0 {
|
||||
return ErrIncludedTaskfilesCantHaveIncludes
|
||||
|
||||
includedTaskfile, err := Taskfile(includeReaderNode)
|
||||
if err != nil {
|
||||
if includedTask.Optional {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
|
||||
@@ -99,12 +116,12 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
if includedTask.AdvancedImport {
|
||||
for k, v := range includedTaskfile.Vars.Mapping {
|
||||
o := v
|
||||
o.Dir = filepath.Join(dir, includedTask.Dir)
|
||||
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
|
||||
includedTaskfile.Vars.Mapping[k] = o
|
||||
}
|
||||
for k, v := range includedTaskfile.Env.Mapping {
|
||||
o := v
|
||||
o.Dir = filepath.Join(dir, includedTask.Dir)
|
||||
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
|
||||
includedTaskfile.Env.Mapping[k] = o
|
||||
}
|
||||
|
||||
@@ -112,6 +129,8 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
if !filepath.IsAbs(task.Dir) {
|
||||
task.Dir = filepath.Join(includedTask.Dir, task.Dir)
|
||||
}
|
||||
task.IncludeVars = includedTask.Vars
|
||||
task.IncludedTaskfileVars = includedTaskfile.Vars
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +144,7 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
}
|
||||
|
||||
if v < 3.0 {
|
||||
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
|
||||
path = filepath.Join(readerNode.Dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
|
||||
if _, err = os.Stat(path); err == nil {
|
||||
osTaskfile, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
@@ -175,3 +194,25 @@ func exists(path string) (string, error) {
|
||||
|
||||
return "", fmt.Errorf(`task: No Taskfile found in "%s". Use "task --init" to create a new one`, path)
|
||||
}
|
||||
|
||||
func checkCircularIncludes(node *ReaderNode) error {
|
||||
if node == nil {
|
||||
return errors.New("task: failed to check for include cycle: node was nil")
|
||||
}
|
||||
if node.Parent == nil {
|
||||
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)
|
||||
for curNode.Parent != nil {
|
||||
curNode = curNode.Parent
|
||||
curPath := filepath.Join(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),
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,25 +5,27 @@ type Tasks map[string]*Task
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
Task string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Task string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool
|
||||
Run string
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
|
||||
20
testdata/include_with_vars/Taskfile.yml
vendored
Normal file
20
testdata/include_with_vars/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
version: "3"
|
||||
|
||||
includes:
|
||||
included1:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
vars:
|
||||
VAR_1: included1-var1
|
||||
included2:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
vars:
|
||||
VAR_1: included2-var1
|
||||
included3:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
cmds:
|
||||
- task: included1:task1
|
||||
- task: included2:task1
|
||||
- task: included3:task1
|
||||
11
testdata/include_with_vars/include/Taskfile.include.yml
vendored
Normal file
11
testdata/include_with_vars/include/Taskfile.include.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
VAR_1: '{{.VAR_1 | default "included-default-var1"}}'
|
||||
VAR_2: '{{.VAR_2 | default "included-default-var2"}}'
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
cmds:
|
||||
- echo "VAR_1 is {{.VAR_1}}"
|
||||
- echo "VAR_2 is {{.VAR_2}}"
|
||||
12
testdata/includes_cycle/Taskfile.yml
vendored
Normal file
12
testdata/includes_cycle/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
'one': ./one/Taskfile.yml
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "called_dep" > called_dep.txt
|
||||
level1:
|
||||
cmds:
|
||||
- echo "hello level 1"
|
||||
9
testdata/includes_cycle/one/Taskfile.yml
vendored
Normal file
9
testdata/includes_cycle/one/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
'two': ./two/Taskfile.yml
|
||||
|
||||
tasks:
|
||||
level2:
|
||||
cmds:
|
||||
- echo "hello level 2"
|
||||
9
testdata/includes_cycle/one/two/Taskfile.yml
vendored
Normal file
9
testdata/includes_cycle/one/two/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
bad: "../../Taskfile.yml"
|
||||
|
||||
tasks:
|
||||
level3:
|
||||
cmds:
|
||||
- echo "hello level 3"
|
||||
11
testdata/includes_multi_level/Taskfile.yml
vendored
Normal file
11
testdata/includes_multi_level/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
'one': ./one/
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: one:default
|
||||
- task: one:two:default
|
||||
- task: one:two:three:default
|
||||
1
testdata/includes_multi_level/called_one.txt
vendored
Normal file
1
testdata/includes_multi_level/called_one.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
one
|
||||
1
testdata/includes_multi_level/called_three.txt
vendored
Normal file
1
testdata/includes_multi_level/called_three.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
three
|
||||
1
testdata/includes_multi_level/called_two.txt
vendored
Normal file
1
testdata/includes_multi_level/called_two.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
two
|
||||
7
testdata/includes_multi_level/one/Taskfile.yml
vendored
Normal file
7
testdata/includes_multi_level/one/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
'two': ./two/
|
||||
|
||||
tasks:
|
||||
default: echo one > called_one.txt
|
||||
7
testdata/includes_multi_level/one/two/Taskfile.yml
vendored
Normal file
7
testdata/includes_multi_level/one/two/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
'three': ./three/Taskfile.yml
|
||||
|
||||
tasks:
|
||||
default: echo two > called_two.txt
|
||||
4
testdata/includes_multi_level/one/two/three/Taskfile.yml
vendored
Normal file
4
testdata/includes_multi_level/one/two/three/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default: echo three > called_three.txt
|
||||
32
variables.go
32
variables.go
@@ -46,21 +46,23 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
r := templater.Templater{Vars: vars, RemoveNoValue: v >= 3.0}
|
||||
|
||||
new := taskfile.Task{
|
||||
Task: origTask.Task,
|
||||
Label: r.Replace(origTask.Label),
|
||||
Desc: r.Replace(origTask.Desc),
|
||||
Summary: r.Replace(origTask.Summary),
|
||||
Sources: r.ReplaceSlice(origTask.Sources),
|
||||
Generates: r.ReplaceSlice(origTask.Generates),
|
||||
Dir: r.Replace(origTask.Dir),
|
||||
Vars: nil,
|
||||
Env: nil,
|
||||
Silent: origTask.Silent,
|
||||
Interactive: origTask.Interactive,
|
||||
Method: r.Replace(origTask.Method),
|
||||
Prefix: r.Replace(origTask.Prefix),
|
||||
IgnoreError: origTask.IgnoreError,
|
||||
Run: r.Replace(origTask.Run),
|
||||
Task: origTask.Task,
|
||||
Label: r.Replace(origTask.Label),
|
||||
Desc: r.Replace(origTask.Desc),
|
||||
Summary: r.Replace(origTask.Summary),
|
||||
Sources: r.ReplaceSlice(origTask.Sources),
|
||||
Generates: r.ReplaceSlice(origTask.Generates),
|
||||
Dir: r.Replace(origTask.Dir),
|
||||
Vars: nil,
|
||||
Env: nil,
|
||||
Silent: origTask.Silent,
|
||||
Interactive: origTask.Interactive,
|
||||
Method: r.Replace(origTask.Method),
|
||||
Prefix: r.Replace(origTask.Prefix),
|
||||
IgnoreError: origTask.IgnoreError,
|
||||
Run: r.Replace(origTask.Run),
|
||||
IncludeVars: origTask.IncludeVars,
|
||||
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
||||
}
|
||||
new.Dir, err = execext.Expand(new.Dir)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user