Compare commits

...

101 Commits
v2 ... v3.0.0

Author SHA1 Message Date
Andrey Nering
dd2116c897 v3.0.0 2020-08-16 22:07:14 -03:00
Andrey Nering
c5566b3e94 Update version in the home page 2020-08-16 22:01:49 -03:00
Andrey Nering
30cbf02bff Update documentation to reflect the new default "method" 2020-08-16 21:59:42 -03:00
Andrey Nering
9e4e9b4f1a Doc: Update "Taskfile versions" documentation 2020-08-16 21:56:21 -03:00
Andrey Nering
6f290f28b6 On documentation: version: '2' -> version: '3' 2020-08-16 21:34:33 -03:00
Andrey Nering
6ff3c9015b On v3, treat all CLI variables as global variables
Closes #336
Ref #341

Co-authored-by: Egor Kovetskiy <e.kovetskiy@gmail.com>
2020-08-16 21:27:11 -03:00
Andrey Nering
e28b82b2b7 Upgrade mvdan.cc/sh to v3.1.2 2020-08-16 21:05:52 -03:00
Andrey Nering
3edf124f96 Merge pull request #220 from go-task/v3
v3 base branch
2020-08-16 15:54:20 -03:00
Andrey Nering
fb72b46a3c github.com/go-task/task/v2 -> github.com/go-task/task/v3 2020-08-16 15:48:19 -03:00
Andrey Nering
49bf395f61 Remove the vendor/ directory 2020-08-16 15:46:22 -03:00
Andrey Nering
eab14b6c49 Some improvements to #356 2020-08-15 19:13:30 -03:00
Chris Garrett
8b962fb8e8 #324 implement dotenv 2020-08-03 16:18:38 -06:00
Chris Garrett
e5a3c861cb Merge remote-tracking branch 'upstream/v3' into v3 2020-08-03 12:04:36 -06:00
Andrey Nering
8137517d93 Merge branch 'master' into v3 2020-06-14 17:13:46 -03:00
Andrey Nering
572f6a7fab CHANGELOG, docs and nits for #321 and #337 2020-06-14 17:12:20 -03:00
Andrey Nering
c6d9201680 Merge pull request #337 from adamwasila/subtask-alias
Allow overriding the task name in the logger output
2020-06-14 17:01:28 -03:00
Adam Wasila
4bc183a8a1 Add basic unit tests for label attribute 2020-06-14 15:12:48 +02:00
Adam Wasila
9f83311931 Add label field to task definition
Label is an alternative name for task that replace it when printed in following context eg.:

- log: when given task is up to date and is skipped from execution
- log: when given task is NOT up to date (`--status` command)
- in `--summary` and `--list` commands output
2020-06-14 13:42:20 +02:00
Andrey Nering
f4f6efa547 Skip cleanup if task doesn't have any sources listed
Ref #333
2020-05-31 15:48:23 -03:00
Andrey Nering
ee7f2a541f v3.0.0-preview4 2020-05-20 22:32:30 -03:00
Andrey Nering
59a00eae98 Merge branch 'master' into v3 2020-05-20 22:26:45 -03:00
Andrey Nering
9f0f18c5c4 v3: Allow interpolation on "includes"
The idea is to allow manual inclusion of a OS-dependant Taskfile, since it's
not automatically included anymore.
2020-05-17 16:03:03 -03:00
Andrey Nering
191c34c9c4 v3: Do not include Taskfile_{{OS}}.yml automatically 2020-05-17 15:42:27 -03:00
Andrey Nering
6a604b3002 v3: Taskvars is no more 2020-05-17 15:34:32 -03:00
Andrey Nering
5a435b533e v3: Disallow the "expansions" setting on Taskfiles in v3 2020-05-17 15:28:25 -03:00
Andrey Nering
4b027722b1 Merge pull request #311 from go-task/vars-refactor-for-v3
v3: Variables refactoring
2020-05-17 14:56:11 -03:00
Andrey Nering
68ce8642b1 Create v3 compiler which respects declaration order of variables
Also, fix "<no value>" been printed when a non-existing variable is printed.
2020-05-16 15:46:07 -03:00
Andrey Nering
4913b6a0f1 Merge branch 'v3' into vars-refactor-for-v3 2020-05-16 11:18:28 -03:00
Andrey Nering
aee0ab05f4 Merge branch 'master' into v3 2020-05-16 10:28:00 -03:00
Andrey Nering
b44432f24a Upgrade slim-sprig package version 2020-05-16 10:21:54 -03:00
Andrey Nering
cbb12b29bd v3: Fix bug where global vars were not being considered 2020-04-05 11:16:27 -03:00
Andrey Nering
6ed30f1add Refactor variables: Keep order of declaration
This shouldn't have any behavior changes for now. This is a code
refactor that should allow us to do further improvements on how
variables are handled, specially regarding respecting the declaration
order in Taskfiles, which should make it easier for the users.

Initial work on #218
2020-04-05 11:16:14 -03:00
Andrey Nering
a044c41c66 Upgrade github.com/go-yaml/yaml to v3 2020-03-28 11:27:49 -03:00
Andrey Nering
fb78e53a14 v3.0.0-preview3 2020-03-28 11:01:28 -03:00
Andrey Nering
acfbbaa549 Merge branch 'master' into v3 2020-03-28 10:48:49 -03:00
Andrey Nering
3f80a3b39e Improve documentation for included Taskfiles
Follow-up of #292
2020-02-16 11:21:06 -03:00
Andrey Nering
b2a56161bb Make ./docs/Taskfile.yml run on ./docs 2020-02-16 11:20:53 -03:00
Andrey Nering
5e75639244 Merge pull request #292 from evg4b/included_tasks
Added option to make included Taskfile run commands on its own directory
2020-02-16 10:50:35 -03:00
Evgeny Abramovich
cb2cd3e10f Updated CHANGELOG.md 2020-02-15 18:07:27 +03:00
Evgeny Abramovich
0acb911d6a Fixed absolute path resolving for included tasksfile 2020-02-15 18:07:09 +03:00
Evgeny Abramovich
17ad7060b3 Added version validation and updated tests 2020-02-15 17:24:06 +03:00
Evgeny Abramovich
f38ba7fcd3 Removed automatic inclusion of Taskfiles by OS and update tests 2020-02-15 17:19:09 +03:00
Evgeny Abramovich
a3464068bd Rename TaskFile to Taskfile 2020-02-12 10:42:00 +03:00
Evgeny Abramovich
d567e23e50 Added tests for new inport taskfile logic 2020-01-29 11:25:11 +03:00
Evgeny Abramovich
8ff81562d2 Added os-related files for included taskfiles 2020-01-29 10:39:43 +03:00
Evgeny Abramovich
7a8142ed92 Added included taskfile directory resolving 2020-01-29 10:39:26 +03:00
Evgeny Abramovich
eaba1b9cc8 Added structure for storage information about included tasks 2020-01-29 10:02:22 +03:00
Andrey Nering
15338ecb18 Merge branch 'master' into v3 2019-12-07 22:04:16 -03:00
Andrey Nering
b7b752b92f Allow shorter syntax for tasks with default configuration
Closes #194
Closes #240

Co-authored-by: Jaedle <dennis.jekubczyk@gmail.com>
2019-12-07 21:28:02 -03:00
Andrey Nering
b7bcd204b4 go fmt internal/taskfile/task.go 2019-12-07 20:09:16 -03:00
Andrey Nering
7373639f57 Expose .TASK variable with the task name
Closes #252
2019-12-07 19:43:10 -03:00
Andrey Nering
d718527a1f Merge branch 'master' into v3 2019-12-07 16:54:29 -03:00
Andrey Nering
1d3b93d88d Remove bold from colored text 2019-11-24 21:07:12 -03:00
Andrey Nering
62752ba7e1 Merge branch 'master' into v3 2019-11-24 21:02:33 -03:00
Andrey Nering
9eab74b595 Updating slim-sprig 2019-11-02 22:18:03 -03:00
Andrey Nering
5acdb041a9 Merge branch 'master' into v3 2019-11-02 22:16:44 -03:00
Andrey Nering
bc99509395 Merge branch 'master' into v3 2019-09-22 18:43:45 -03:00
Andrey Nering
d3060b0060 Add CHANGELOG for #216 2019-09-14 18:09:34 -03:00
Andrey Nering
14d7f04a81 Always expode .TIMESTAMP and .STATUS when using status: 2019-09-14 18:04:41 -03:00
Andrey Nering
1a28e5e0d4 Few code improvements on #216 2019-09-14 17:54:41 -03:00
Andrey Nering
884cd0d636 Merge branch 'CypherpunkArmory-report-timestamp-to-status' into v3 2019-09-14 17:18:42 -03:00
Andrey Nering
6a7a3c0ae8 Merge pull request #246 from go-task/method-on-v3
Add global "method:" option to allow setting a default for all tasks. Change default from "timestamp" to "checksum"
2019-09-08 23:02:40 -03:00
Andrey Nering
948e6bd57c Update v3 CHANGELOG 2019-09-08 22:59:27 -03:00
Andrey Nering
78595fba0b Make "checksum" the default method in v3 2019-09-08 22:51:56 -03:00
Andrey Nering
8020284b12 Add global method: option to set default method 2019-09-08 22:51:14 -03:00
Andrey Nering
d6a49da870 Merge branch 'master' into v3 2019-09-08 22:12:02 -03:00
Andrey Nering
bae1e1ee9f Update .gitignore 2019-09-07 14:46:46 -03:00
Andrey Nering
7138785500 Merge branch 'master' into v3 2019-09-07 14:44:21 -03:00
Andrey Nering
1a33f9168b Merge branch 'report-timestamp-to-status' of https://github.com/CypherpunkArmory/task into CypherpunkArmory-report-timestamp-to-status 2019-09-01 21:44:23 -03:00
Andrey Nering
ccae3d7383 Update CHANGELOG 2019-08-25 18:17:33 -03:00
Andrey Nering
847651a90a Improve note wording a bit 2019-08-25 18:15:26 -03:00
Andrey Nering
1b8998e7a2 Merge pull request #237 from jaedle/v3
Remove all code related support of version 1
2019-08-25 18:11:47 -03:00
Andrey Nering
dc8fb79759 Merge branch 'master' into v3 2019-08-25 18:05:40 -03:00
Stephen Prater
6b0935d6cf Fix tests 2019-08-25 13:47:29 -07:00
Stephen Prater
d1183ce272 Merge branch 'master' into report-timestamp-to-status
* master:
  Update CHANGELOG
  Small improvements to #228
  Fix a typo
  Fix Checksum.IsUpToDate
  Remove directory check
  Update glob.go
  Separate error handlings for readability
  Re-run the task if generated files do not exist
2019-08-25 13:46:02 -07:00
Stephen Prater
a1aec8178a Export Time Struct to Template 2019-08-25 13:36:48 -07:00
Stephen Prater
cb6fe4bb59 Merge remote-tracking branch 'upstream/v3' into report-timestamp-to-status
* upstream/v3:
  v3.0.0-preview1
  Update v3 changelog
  Only have colored output on v3
  Add --color=false flag to disable colored output
  Update documentation about sprig
  Update CHANGELOG
  Migrate from sprig to slim-sprig
  Fix build after merging master
  Use colors for some output messages
2019-08-25 10:33:13 -07:00
Stephen Prater
db36bc67f1 Changes per feedback 2019-08-25 10:30:00 -07:00
Stephen Prater
e0f72a6193 Apply suggestions from code review
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-25 09:39:39 -07:00
Stephen Prater
1ee684b7c0 Expose timestamp and checksum to status 2019-08-25 09:39:39 -07:00
jaedle
93005512b4 cleanp taskfile reader 2019-08-24 06:28:12 +02:00
jaedle
b8094fd771 bump version of auto generated taskfile 2019-08-19 21:01:36 +02:00
jaedle
af5d9c952d assert error message 2019-08-19 21:01:01 +02:00
jaedle
ce4e187cbc show documentation of version 1, add deprecation notice 2019-08-19 20:59:02 +02:00
jaedle
821c80b61e Fix error message
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-19 20:53:35 +02:00
jaedle
5a6fb7c973 Bump version of taskfile
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-19 20:53:20 +02:00
jaedle
0f385f9f4e remove v1 2019-08-18 17:37:21 +02:00
Andrey Nering
c47c15ee47 v3.0.0-preview1 2019-08-11 23:43:27 -03:00
Andrey Nering
6803ad2e59 Update v3 changelog 2019-08-11 23:10:12 -03:00
Andrey Nering
d5a791b470 Merge branch 'master' into v3 2019-08-11 23:03:58 -03:00
Andrey Nering
e6255081a8 Merge pull request #207 from go-task/colored-output
Use colors for some output messages
2019-07-07 14:41:25 -03:00
Andrey Nering
623db0ed94 Only have colored output on v3 2019-07-07 14:18:02 -03:00
Andrey Nering
0e575e9c25 Add --color=false flag to disable colored output 2019-07-07 14:13:53 -03:00
Andrey Nering
fb23ba9878 Merge branch 'v3' into colored-output 2019-07-07 14:04:12 -03:00
Andrey Nering
4e09fc7f43 Update documentation about sprig 2019-06-22 22:48:53 -03:00
Andrey Nering
64cfdd815f Update CHANGELOG 2019-06-22 22:47:23 -03:00
Andrey Nering
f6f31e0a8d Merge pull request #219 from go-task/migrate-from-sprig-to-slim-sprig
Migrate from sprig to slim-sprig
2019-06-22 22:37:42 -03:00
Andrey Nering
bd5fb9be03 Migrate from sprig to slim-sprig 2019-06-22 22:17:24 -03:00
Andrey Nering
dd9cdb0ec9 Fix build after merging master 2019-06-15 22:47:15 -03:00
Andrey Nering
7f082a821d Merge branch 'v3' into colored-output 2019-06-15 22:42:15 -03:00
Andrey Nering
8efc38ad82 Use colors for some output messages 2019-05-26 18:36:39 -03:00
685 changed files with 1882 additions and 298998 deletions

1
.gitignore vendored
View File

@@ -26,3 +26,4 @@ dist/
tags
/bin
/testdata/vars/v1

View File

@@ -1,5 +1,48 @@
# Changelog
## v3.0.0
- On `v3`, all CLI variables will be considered global variables
([#336](https://github.com/go-task/task/issues/336), [#341](https://github.com/go-task/task/pull/341))
- Add support to `.env` like files
([#324](https://github.com/go-task/task/issues/324), [#356](https://github.com/go-task/task/pull/356)).
- Add `label:` to task so you can override the task name in the logs
([#321](https://github.com/go-task/task/issues/321]), [#337](https://github.com/go-task/task/pull/337)).
- Refactor how variables work on version 3
([#311](https://github.com/go-task/task/pull/311)).
- Disallow `expansions` on v3 since it has no effect.
- `Taskvars.yml` is not automatically included anymore.
- `Taskfile_{{OS}}.yml` is not automatically included anymore.
- Allow interpolation on `includes`, so you can manually include a Taskfile
based on operation system, for example.
- Expose `.TASK` variable in templates with the task name
([#252](https://github.com/go-task/task/issues/252)).
- Implement short task syntax
([#194](https://github.com/go-task/task/issues/194), [#240](https://github.com/go-task/task/pull/240)).
- Added option to make included Taskfile run commands on its own directory
([#260](https://github.com/go-task/task/issues/260), [#144](https://github.com/go-task/task/issues/144))
- Taskfiles in version 1 are not supported anymore
([#237](https://github.com/go-task/task/pull/237)).
- Added global `method:` option. With this option, you can set a default
method to all tasks in a Taskfile
([#246](https://github.com/go-task/task/issues/246)).
- Changed default method from `timestamp` to `checksum`
([#246](https://github.com/go-task/task/issues/246)).
- New magic variables are now available when using `status:`:
`.TIMESTAMP` which contains the greatest modification date
from the files listed in `sources:`, and `.CHECKSUM`, which
contains a checksum of all files listed in `status:`.
This is useful for manual checking when using external, or even remote,
artifacts when using `status:`
([#216](https://github.com/go-task/task/pull/216)).
- We're now using [slim-sprig](https://github.com/go-task/slim-sprig) instead of
[sprig](https://github.com/Masterminds/sprig), which allowed a file size
reduction of about 22%
([#219](https://github.com/go-task/task/pull/219)).
- We now use some colors on Task output to better distinguish message types -
commands are green, errors are red, etc
([#207](https://github.com/go-task/task/pull/207)).
## v2.8.1 - 2019-05-20
- Fix error code for the `--help` flag

View File

@@ -1,9 +1,9 @@
version: '2'
# silent: true
version: '3'
includes:
docs: ./docs
docs:
taskfile: ./docs
dir: ./docs
vars:
GIT_COMMIT:
@@ -34,11 +34,6 @@ tasks:
- task: go-get
vars: {REPO: github.com/goreleaser/godownloader}
vendor:
desc: Sync vendor/ directory according to go.mod file
cmds:
- go mod vendor
update-deps:
desc: Updates dependencies
cmds:
@@ -74,15 +69,12 @@ tasks:
- cp ./install-task.sh ./docs/install.sh
ci:
cmds:
- task: go-get
vars: {REPO: golang.org/x/lint/golint}
- task: lint
- task: test
- task: go-get
vars: {REPO: golang.org/x/lint/golint}
- task: lint
- task: test
go-get:
cmds:
- go get -u {{.REPO}}
go-get: go get -u {{.REPO}}
packages:
cmds:

View File

@@ -9,8 +9,10 @@ import (
"path/filepath"
"syscall"
"github.com/go-task/task/v2"
"github.com/go-task/task/v2/internal/args"
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/internal/args"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/spf13/pflag"
)
@@ -28,12 +30,14 @@ Example: 'task hello' with the following 'Taskfile.yml' file will generate an
'output.txt' file with the content "hello".
'''
hello:
cmds:
- echo "I am going to write a file named 'output.txt' now."
- echo "hello" > output.txt
generates:
- output.txt
version: '3'
tasks:
hello:
cmds:
- echo "I am going to write a file named 'output.txt' now."
- echo "hello" > output.txt
generates:
- output.txt
'''
Options:
@@ -64,6 +68,7 @@ func main() {
dir string
entrypoint string
output string
color bool
)
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
@@ -81,6 +86,7 @@ func main() {
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
pflag.BoolVarP(&color, "color", "c", true, "colored output")
pflag.Parse()
if versionFlag {
@@ -125,6 +131,7 @@ func main() {
Entrypoint: entrypoint,
Summary: summary,
Parallel: parallel,
Color: color,
Stdin: os.Stdin,
Stdout: os.Stdout,
@@ -135,16 +142,27 @@ func main() {
if err := e.Setup(); err != nil {
log.Fatal(err)
}
v, err := e.Taskfile.ParsedVersion()
if err != nil {
log.Fatal(err)
return
}
if list {
e.PrintTasksHelp()
return
}
calls, globals := args.Parse(pflag.Args()...)
for name, value := range globals {
e.Taskfile.Vars[name] = value
var (
calls []taskfile.Call
globals *taskfile.Vars
)
if v >= 3.0 {
calls, globals = args.ParseV3(pflag.Args()...)
} else {
calls, globals = args.ParseV2(pflag.Args()...)
}
e.Taskfile.Vars.Merge(globals)
ctx := context.Background()
if !watch {
@@ -159,7 +177,8 @@ func main() {
}
if err := e.Run(ctx, calls...); err != nil {
log.Fatal(err)
e.Logger.Errf(logger.Red, "%v", err)
os.Exit(1)
}
}

View File

@@ -11,7 +11,7 @@ Once [installed](installation.md), you just need to describe your build tasks
using a simple [YAML][yaml] schema in a file called `Taskfile.yml`:
```yaml
version: '2'
version: '3'
tasks:
hello:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
install:
@@ -9,4 +9,4 @@ tasks:
serve:
desc: Serves the documentation site locally
cmds:
- docsify serve docs
- docsify serve .

View File

@@ -56,7 +56,7 @@ tasks:
```yaml
# bad
version: 2
version: '3'
includes:
docker: ./docker/Taskfile.yml
output: prefixed
@@ -70,7 +70,7 @@ tasks:
# good
version: 2
version: '3'
includes:
docker: ./docker/Taskfile.yml
@@ -92,7 +92,7 @@ tasks:
```yaml
# bad
version: 2
version: '3'
tasks:
foo:
@@ -107,7 +107,7 @@ tasks:
# good
version: 2
version: '3'
tasks:
foo:
@@ -127,7 +127,7 @@ tasks:
```yaml
# bad
version: 2
version: '3'
vars:
binary_name: myapp
@@ -139,7 +139,7 @@ tasks:
# good
version: 2
version: '3'
vars:
BINARY_NAME: myapp
@@ -154,7 +154,7 @@ tasks:
```yaml
# bad
version: 2
version: '3'
tasks:
greet:
@@ -163,7 +163,7 @@ tasks:
# good
version: 2
version: '3'
tasks:
greet:
@@ -177,7 +177,7 @@ This convention is also used by most people for any Go templating.
```yaml
# bad
version: 2
version: '3'
tasks:
do_something_fancy:
@@ -186,7 +186,7 @@ tasks:
# good
version: 2
version: '3'
tasks:
do-something-fancy:
@@ -198,7 +198,7 @@ tasks:
```yaml
# good
version: 2
version: '3'
tasks:
docker:build:

View File

@@ -15,6 +15,8 @@ available, but not `3.0.0+`.
## Version 1
> NOTE: Taskfiles in version 1 are not supported on Task >= v3.0.0 anymore.
In the first version of the `Taskfile`, the `version:` key was not available,
because the tasks was in the root of the YAML document. Like this:
@@ -161,3 +163,63 @@ Please check the [documentation][includes]
[output]: usage.md#output-syntax
[ignore_errors]: usage.md#ignore-errors
[includes]: usage.md#including-other-taskfiles
## Version 3
These are some major changes done on `v3`:
- Task's output will now be colored
- Added support for `.env` like files
- Added `label:` setting to task so one can override how the task name
appear in the logs
- A global `method:` was added to allow setting the default method,
and Task's default changed to `checksum`
- Two magic variables were added when using `status:`: `CHECKSUM` and
`TIMESTAMP` which contains, respectively, the md5 checksum and greatest
modification timestamp of the files listed on `sources:`
- Also, the `TASK` variable is always available with the current task name
- CLI variables are always treated as global variables
- Added `dir:` option to `includes` to allow choosing on which directory an
included Taskfile will run:
```yaml
includes:
docs:
taskfile: ./docs
dir: ./docs
```
- Implemented short task syntax. All below syntaxes are equivalent:
```yaml
version: '3'
tasks:
print:
cmds:
- echo "Hello, World!"
```
```yaml
version: '3'
tasks:
print:
- echo "Hello, World!"
```
```yaml
version: '3'
tasks:
print: echo "Hello, World!"
```
- There was a major refactor on how variables are handled. They're now easier
to understand. The `expansions:` setting was removed as it became unncessary.
This is the order in which Task will process variables, each level can see
the variables set by the previous one and override those.
- Environment variables
- Global + CLI variables
- Call variables
- Task variables

View File

@@ -8,7 +8,7 @@ The example below allows compiling a Go app and uses [Minify][minify] to concat
and minify multiple CSS files into a single one.
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -33,12 +33,14 @@ executable called must be available by the OS or in PATH.
If you omit a task name, "default" will be assumed.
## Environment
## Environment variables
### Task
You can use `env` to set custom environment variables for a specific task:
```yaml
version: '2'
version: '3'
tasks:
greet:
@@ -52,7 +54,7 @@ Additionally, you can set globally environment variables, that'll be available
to all tasks:
```yaml
version: '2'
version: '3'
env:
GREETING: Hey, there!
@@ -66,6 +68,30 @@ tasks:
> NOTE: `env` supports expansion and retrieving output from a shell command
> just like variables, as you can see on the [Variables](#variables) section.
### .env files
You can also ask Task to include `.env` like files by using the `dotenv:`
setting:
```
# .env
KEYNAME=VALUE
```
```yaml
# Taskfile.yml
version: '3'
dotenv: ['.env']
tasks:
greet:
cmds:
- echo "Using $KEYNAME"
```
## Operating System specific tasks
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
@@ -76,7 +102,7 @@ Example:
Taskfile.yml:
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -87,7 +113,7 @@ tasks:
Taskfile_linux.yml:
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -112,7 +138,7 @@ If you want to share tasks between different projects (Taskfiles), you can use
the importing mechanism to include other Taskfiles using the `includes` keyword:
```yaml
version: '2'
version: '3'
includes:
docs: ./documentation # will look for ./documentation/Taskfile.yml
@@ -124,6 +150,21 @@ namespace. So, you'd call `task docs:serve` to run the `serve` task from
`documentation/Taskfile.yml` or `task docker:build` to run the `build` task
from the `DockerTasks.yml` file.
### Directory of included Taskfile
By default, included Taskfile's tasks are ran in the current directory, even
if the Taskfile is in another directory, but you can force its tasks to run
in another directory by using this alternative syntax:
```yaml
version: '3'
includes:
docs:
taskfile: ./docs/Taskfile.yml
dir: ./docs
```
> The included Taskfiles must be using the same schema version the main
> Taskfile uses.
@@ -138,7 +179,7 @@ located. But you can easily make the task run in another folder informing
`dir`:
```yaml
version: '2'
version: '3'
tasks:
serve:
@@ -160,7 +201,7 @@ You may have tasks that depend on others. Just pointing them on `deps` will
make them run automatically before running the parent task:
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -179,7 +220,7 @@ In the above example, `assets` will always run right before `build` if you run
A task can have only dependencies and no commands to group tasks together:
```yaml
version: '2'
version: '3'
tasks:
assets:
@@ -204,7 +245,7 @@ If you want to pass information to dependencies, you can do that the same
manner as you would to [call another task](#calling-another-task):
```yaml
version: '2'
version: '3'
tasks:
default:
@@ -228,7 +269,7 @@ often result in a faster build pipeline. But in some situations you may need
to call other tasks serially. In this case, just use the following syntax:
```yaml
version: '2'
version: '3'
tasks:
main-task:
@@ -250,7 +291,7 @@ Overriding variables in the called task is as simple as informing `vars`
attribute:
```yaml
version: '2'
version: '3'
tasks:
main-task:
@@ -273,11 +314,13 @@ The above syntax is also supported in `deps`.
## Prevent unnecessary work
### By fingerprinting locally generated files and their sources
If a task generates something, you can inform Task the source and generated
files, so Task will prevent to run them if not necessary.
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -303,17 +346,17 @@ tasks:
```
`sources` and `generates` can be files or file patterns. When both are given,
Task will compare the modification date/time of the files to determine if it's
Task will compare the checksum of the files to determine if it's
necessary to run the task. If not, it will just print a message like
`Task "js" is up to date`.
If you prefer this check to be made by the content of the files, instead of
its timestamp, just set the `method` property to `checksum`.
You will probably want to ignore the `.task` folder in your `.gitignore` file
(It's there that Task stores the last checksum).
If you prefer this check to be made by the modification timestamp of the files,
instead of its checksum (content), just set the `method` property to `timestamp`.
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -328,11 +371,13 @@ tasks:
> TIP: method `none` skips any validation and always run the task.
### Using programmatic checks to indicate a task is up to date.
Alternatively, you can inform a sequence of tests as `status`. If no error
is returned (exit status 0), the task is considered up-to-date:
```yaml
version: '2'
version: '3'
tasks:
generate-files:
@@ -347,18 +392,37 @@ tasks:
- test -f directory/file2.txt
```
Normally, you would use `sources` in combination with
`generates` - but for tasks that generate remote artifacts (Docker images,
deploys, CD releases) the checksum source and timestamps require either
access to the artifact or for an out-of-band refresh of the `.checksum`
fingerprint file.
Two special variables `{{.CHECKSUM}}` and `{{.TIMESTAMP}}` are available
for interpolation within `status` commands, depending on the method assigned
to fingerprint the sources. Only `source` globs are fingerprinted.
Note that the `{{.TIMESTAMP}}` variable is a "live" Go `time.Time` struct, and
can be formatted using any of the methods that `time.Time` responds to.
See [the Go Time documentation](https://golang.org/pkg/time/) for more information.
You can use `--force` or `-f` if you want to force a task to run even when
up-to-date.
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
the tasks are not up-to-date.
If you need a certain set of conditions to be _true_ you can use the
`preconditions` stanza. `preconditions` are very similar to `status`
lines except they support `sh` expansion and they SHOULD all return 0.
### Using programmatic checks to cancel execution of an task and it's dependencies
In addition to `status` checks, there are also `preconditions` checks, which are
the logical inverse of `status` checks. That is, if you need a certain set of
conditions to be _true_ you can use the `preconditions` stanza.
`preconditions` are similar to `status` lines except they support `sh`
expansion and they SHOULD all return 0.
```yaml
version: '2'
version: '3'
tasks:
generate-files:
@@ -386,7 +450,7 @@ executing tasks that depend on it, a `precondition` will fail a task, along
with any other tasks that depend on it.
```yaml
version: '2'
version: '3'
tasks:
task_will_fail:
preconditions:
@@ -420,25 +484,19 @@ Example of sending parameters with environment variables:
$ TASK_VARIABLE=a-value task do-something
```
> TIP: A special variable `.TASK` is always available containg the task name.
Since some shells don't support above syntax to set environment variables
(Windows) tasks also accepts a similar style when not in the beginning of
the command. Variables given in this form are only visible to the task called
right before.
the command.
```bash
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
```
If you want to set global variables using this syntax, give it before any task:
```bash
$ task OUTPUT=file.txt generate-file
```
Example of locally declared vars:
```yaml
version: '2'
version: '3'
tasks:
print-var:
@@ -451,7 +509,7 @@ tasks:
Example of global vars in a `Taskfile.yml`:
```yaml
version: '2'
version: '3'
vars:
GREETING: Hello from Taskfile!
@@ -477,7 +535,7 @@ Variables are expanded 2 times by default. You can change that by setting the
variables together:
```yaml
version: '2'
version: '3'
expansions: 3
@@ -501,7 +559,7 @@ The value will be treated as a command and the output assigned. If there is one
or more trailing newlines, the last newline will be trimmed.
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -519,11 +577,11 @@ This works for all types of variables.
Task parse commands as [Go's template engine][gotemplate] before executing
them. Variables are accessible through dot syntax (`.VARNAME`).
All functions by the Go's [sprig lib](http://masterminds.github.io/sprig/)
All functions by the Go's [slim-sprig lib](https://go-task.github.io/slim-sprig/)
are available. The following example gets the current date in a given format:
```yaml
version: '2'
version: '3'
tasks:
print-date:
@@ -549,7 +607,7 @@ Task also adds the following functions:
Example:
```yaml
version: '2'
version: '3'
tasks:
print-os:
@@ -577,7 +635,7 @@ Running `task --list` (or `task -l`) lists all tasks with a description.
The following Taskfile:
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -612,7 +670,7 @@ Running `task --summary task-name` will show a summary of a task.
The following Taskfile:
```yaml
version: '2'
version: '3'
tasks:
release:
@@ -651,13 +709,37 @@ If the task does not have a summary or a description, a warning is printed.
Please note: *showing the summary will not execute the command*.
## Overriding task name
Sometimes you may want to override the task name print on summary, up-to-date
messates to STDOUT, etc. In this case you can just set `label:`, which can also
be interpolated with variables:
```yaml
version: '3'
tasks:
default:
- task: print
vars:
MESSAGE: hello
- task: print
vars:
MESSAGE: world
print:
label: 'print-{{.MESSAGE}}'
cmds:
- echo "{{.MESSAGE}}"
```
## Silent mode
Silent mode disables echoing of commands before Task runs it.
For the following Taskfile:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -683,7 +765,7 @@ There are four ways to enable silent mode:
* At command level:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -695,7 +777,7 @@ tasks:
* At task level:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -707,7 +789,7 @@ tasks:
* Globally at Taskfile level:
```yaml
version: '2'
version: '3'
silent: true
@@ -722,7 +804,7 @@ tasks:
If you want to suppress STDOUT instead, just redirect a command to `/dev/null`:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -741,7 +823,7 @@ You have the option to ignore errors during command execution.
Given the following Taskfile:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -754,7 +836,7 @@ Task will abort the execution after running `exit 1` because the status code `1`
However it is possible to continue with execution using `ignore_error`:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -785,7 +867,7 @@ options you can choose:
To choose another one, just set it to root in the Taskfile:
```yaml
version: '2'
version: '3'
output: 'group'
@@ -802,7 +884,7 @@ tasks:
with the `prefix:` attribute:
```yaml
version: '2'
version: '3'
output: prefixed
@@ -832,6 +914,22 @@ $ task default
> The `output` option can also be specified by the `--output` or `-o` flags.
## Short task syntax
Starting on Task v3, you can now write tasks with a shorter syntax if they
have the default settings (e.g. no custom `env:`, `vars:`, `silent:` , etc):
```yaml
version: '3'
tasks:
build: go build -v -o ./app{{exeExt}} .
build:
- task: build
- ./app{{exeExt}} -h localhost -p 8080
```
## Watch tasks
If you give a `--watch` or `-w` argument, task will watch for file changes

20
go.mod
View File

@@ -1,21 +1,17 @@
module github.com/go-task/task/v2
module github.com/go-task/task/v3
require (
github.com/Masterminds/semver v1.4.2 // indirect
github.com/Masterminds/sprig v2.16.0+incompatible
github.com/aokoli/goutils v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.0.0 // indirect
github.com/huandu/xstrings v1.1.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/fatih/color v1.7.0
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb
github.com/joho/godotenv v1.3.0
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-zglob v0.0.1
github.com/radovskyb/watcher v1.0.5
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc // indirect
github.com/stretchr/testify v1.5.1
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
gopkg.in/yaml.v2 v2.2.2
mvdan.cc/sh/v3 v3.1.1
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
mvdan.cc/sh/v3 v3.1.2
)
go 1.13

41
go.sum
View File

@@ -1,21 +1,18 @@
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY=
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb h1:/qbv1F67s6ehqX9mG23cJOeca3FWpOVKgtPfPUMAi0k=
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/huandu/xstrings v1.1.0 h1:9oZY6Z/H3A1gytJxzuicbmV5QoR8M1TAPcn9WTg7vqg=
github.com/huandu/xstrings v1.1.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
@@ -25,6 +22,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/pkg/diff v0.0.0-20190930165518-531926345625/go.mod h1:kFj35MyHn14a6pIgWhm46KVjJr5CHys3eEYxkuKD1EI=
@@ -39,20 +40,16 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867 h1:JoRuNIf+rpHl+VhScRQQvzbHed86tKkqwPMV34T8myw=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407 h1:5zh5atpUEdIc478E/ebrIaHLKcfVvG6dL/fGv7BcMoM=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
@@ -64,6 +61,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/editorconfig v0.1.1-0.20200121172147-e40951bde157/go.mod h1:Ge4atmRUYqueGppvJ7JNrtqpqokoJEFxYbP0Z+WeKS8=
mvdan.cc/sh/v3 v3.1.1 h1:niuYC5Ug0KzLuN6CNX3ru37v4MkVD5Wm9T4Mk2eJr9A=
mvdan.cc/sh/v3 v3.1.1/go.mod h1:F+Vm4ZxPJxDKExMLhvjuI50oPnedVXpfjNSrusiTOno=
mvdan.cc/sh/v3 v3.1.2 h1:PG5BYlwtrkZTbJXUy25r0/q9shB5ObttCaknkOIB1XQ=
mvdan.cc/sh/v3 v3.1.2/go.mod h1:F+Vm4ZxPJxDKExMLhvjuI50oPnedVXpfjNSrusiTOno=

View File

@@ -5,22 +5,23 @@ import (
"sort"
"text/tabwriter"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/taskfile"
)
// PrintTasksHelp prints help os tasks that have a description
func (e *Executor) PrintTasksHelp() {
tasks := e.tasksWithDesc()
if len(tasks) == 0 {
e.Logger.Outf("task: No tasks with description available")
e.Logger.Outf(logger.Yellow, "task: No tasks with description available")
return
}
e.Logger.Outf("task: Available tasks for this project:")
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
// Format in tab-separated columns with a tab stop of 8.
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
for _, task := range tasks {
fmt.Fprintf(w, "* %s: \t%s\n", task.Task, task.Desc)
fmt.Fprintf(w, "* %s: \t%s\n", task.Name(), task.Desc)
}
w.Flush()
}

View File

@@ -10,7 +10,7 @@ import (
const defaultTaskfile = `# https://taskfile.dev
version: '2'
version: '3'
vars:
GREETING: Hello, World!

View File

@@ -3,13 +3,38 @@ package args
import (
"strings"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile"
)
// Parse parses command line argument: tasks and vars of each task
func Parse(args ...string) ([]taskfile.Call, taskfile.Vars) {
// ParseV3 parses command line argument: tasks and global variables
func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) {
var calls []taskfile.Call
var globals taskfile.Vars
var globals *taskfile.Vars
for _, arg := range args {
if !strings.Contains(arg, "=") {
calls = append(calls, taskfile.Call{Task: arg})
continue
}
if globals == nil {
globals = &taskfile.Vars{}
}
name, value := splitVar(arg)
globals.Set(name, taskfile.Var{Static: value})
}
if len(calls) == 0 {
calls = append(calls, taskfile.Call{Task: "default"})
}
return calls, globals
}
// ParseV2 parses command line argument: tasks and vars of each task
func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) {
var calls []taskfile.Call
var globals *taskfile.Vars
for _, arg := range args {
if !strings.Contains(arg, "=") {
@@ -19,18 +44,16 @@ func Parse(args ...string) ([]taskfile.Call, taskfile.Vars) {
if len(calls) < 1 {
if globals == nil {
globals = taskfile.Vars{}
globals = &taskfile.Vars{}
}
name, value := splitVar(arg)
globals[name] = taskfile.Var{Static: value}
globals.Set(name, taskfile.Var{Static: value})
} else {
if calls[len(calls)-1].Vars == nil {
calls[len(calls)-1].Vars = make(taskfile.Vars)
calls[len(calls)-1].Vars = &taskfile.Vars{}
}
name, value := splitVar((arg))
calls[len(calls)-1].Vars[name] = taskfile.Var{Static: value}
name, value := splitVar(arg)
calls[len(calls)-1].Vars.Set(name, taskfile.Var{Static: value})
}
}

View File

@@ -4,17 +4,17 @@ import (
"fmt"
"testing"
"github.com/go-task/task/v2/internal/args"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/args"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/stretchr/testify/assert"
)
func TestArgs(t *testing.T) {
func TestArgsV3(t *testing.T) {
tests := []struct {
Args []string
ExpectedCalls []taskfile.Call
ExpectedGlobals taskfile.Vars
ExpectedGlobals *taskfile.Vars
}{
{
Args: []string{"task-a", "task-b", "task-c"},
@@ -27,30 +27,28 @@ func TestArgs(t *testing.T) {
{
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
ExpectedCalls: []taskfile.Call{
{
Task: "task-a",
Vars: taskfile.Vars{
"FOO": taskfile.Var{Static: "bar"},
},
},
{Task: "task-a"},
{Task: "task-b"},
{
Task: "task-c",
Vars: taskfile.Vars{
"BAR": taskfile.Var{Static: "baz"},
"BAZ": taskfile.Var{Static: "foo"},
},
{Task: "task-c"},
},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"FOO", "BAR", "BAZ"},
Mapping: map[string]taskfile.Var{
"FOO": taskfile.Var{Static: "bar"},
"BAR": taskfile.Var{Static: "baz"},
"BAZ": taskfile.Var{Static: "foo"},
},
},
},
{
Args: []string{"task-a", "CONTENT=with some spaces"},
ExpectedCalls: []taskfile.Call{
{
Task: "task-a",
Vars: taskfile.Vars{
"CONTENT": taskfile.Var{Static: "with some spaces"},
},
{Task: "task-a"},
},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"CONTENT"},
Mapping: map[string]taskfile.Var{
"CONTENT": taskfile.Var{Static: "with some spaces"},
},
},
},
@@ -60,8 +58,11 @@ func TestArgs(t *testing.T) {
{Task: "task-a"},
{Task: "task-b"},
},
ExpectedGlobals: taskfile.Vars{
"FOO": {Static: "bar"},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"FOO"},
Mapping: map[string]taskfile.Var{
"FOO": {Static: "bar"},
},
},
},
{
@@ -81,16 +82,121 @@ func TestArgs(t *testing.T) {
ExpectedCalls: []taskfile.Call{
{Task: "default"},
},
ExpectedGlobals: taskfile.Vars{
"FOO": {Static: "bar"},
"BAR": {Static: "baz"},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"FOO", "BAR"},
Mapping: map[string]taskfile.Var{
"FOO": {Static: "bar"},
"BAR": {Static: "baz"},
},
},
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
calls, globals := args.Parse(test.Args...)
calls, globals := args.ParseV3(test.Args...)
assert.Equal(t, test.ExpectedCalls, calls)
assert.Equal(t, test.ExpectedGlobals, globals)
})
}
}
func TestArgsV2(t *testing.T) {
tests := []struct {
Args []string
ExpectedCalls []taskfile.Call
ExpectedGlobals *taskfile.Vars
}{
{
Args: []string{"task-a", "task-b", "task-c"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
},
},
{
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
ExpectedCalls: []taskfile.Call{
{
Task: "task-a",
Vars: &taskfile.Vars{
Keys: []string{"FOO"},
Mapping: map[string]taskfile.Var{
"FOO": taskfile.Var{Static: "bar"},
},
},
},
{Task: "task-b"},
{
Task: "task-c",
Vars: &taskfile.Vars{
Keys: []string{"BAR", "BAZ"},
Mapping: map[string]taskfile.Var{
"BAR": taskfile.Var{Static: "baz"},
"BAZ": taskfile.Var{Static: "foo"},
},
},
},
},
},
{
Args: []string{"task-a", "CONTENT=with some spaces"},
ExpectedCalls: []taskfile.Call{
{
Task: "task-a",
Vars: &taskfile.Vars{
Keys: []string{"CONTENT"},
Mapping: map[string]taskfile.Var{
"CONTENT": taskfile.Var{Static: "with some spaces"},
},
},
},
},
},
{
Args: []string{"FOO=bar", "task-a", "task-b"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"FOO"},
Mapping: map[string]taskfile.Var{
"FOO": {Static: "bar"},
},
},
},
{
Args: nil,
ExpectedCalls: []taskfile.Call{
{Task: "default"},
},
},
{
Args: []string{},
ExpectedCalls: []taskfile.Call{
{Task: "default"},
},
},
{
Args: []string{"FOO=bar", "BAR=baz"},
ExpectedCalls: []taskfile.Call{
{Task: "default"},
},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"FOO", "BAR"},
Mapping: map[string]taskfile.Var{
"FOO": {Static: "bar"},
"BAR": {Static: "baz"},
},
},
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
calls, globals := args.ParseV2(test.Args...)
assert.Equal(t, test.ExpectedCalls, calls)
assert.Equal(t, test.ExpectedGlobals, globals)
})

View File

@@ -1,12 +1,12 @@
package compiler
import (
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile"
)
// Compiler handles compilation of a task before its execution.
// E.g. variable merger, template processing, etc.
type Compiler interface {
GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error)
GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
HandleDynamicVar(v taskfile.Var) (string, error)
}

View File

@@ -4,21 +4,17 @@ import (
"os"
"strings"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile"
)
// GetEnviron the all return all environment variables encapsulated on a
// taskfile.Vars
func GetEnviron() taskfile.Vars {
var (
env = os.Environ()
m = make(taskfile.Vars, len(env))
)
for _, e := range env {
func GetEnviron() *taskfile.Vars {
m := &taskfile.Vars{}
for _, e := range os.Environ() {
keyVal := strings.SplitN(e, "=", 2)
key, val := keyVal[0], keyVal[1]
m[key] = taskfile.Var{Static: val}
m.Set(key, taskfile.Var{Static: val})
}
return m
}

View File

@@ -1,137 +0,0 @@
package v1
import (
"bytes"
"context"
"fmt"
"strings"
"sync"
"github.com/go-task/task/v2/internal/compiler"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
)
var _ compiler.Compiler = &CompilerV1{}
type CompilerV1 struct {
Dir string
Vars taskfile.Vars
Logger *logger.Logger
dynamicCache map[string]string
muDynamicCache sync.Mutex
}
// GetVariables returns fully resolved variables following the priority order:
// 1. Call variables (should already have been resolved)
// 2. Environment (should not need to be resolved)
// 3. Task variables, resolved with access to:
// - call, taskvars and environment variables
// 4. Taskvars variables, resolved with access to:
// - environment variables
func (c *CompilerV1) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
merge := func(dest taskfile.Vars, srcs ...taskfile.Vars) {
for _, src := range srcs {
for k, v := range src {
dest[k] = v
}
}
}
varsKeys := func(srcs ...taskfile.Vars) []string {
m := make(map[string]struct{})
for _, src := range srcs {
for k := range src {
m[k] = struct{}{}
}
}
lst := make([]string, 0, len(m))
for k := range m {
lst = append(lst, k)
}
return lst
}
replaceVars := func(dest taskfile.Vars, keys []string) error {
r := templater.Templater{Vars: dest}
for _, k := range keys {
v := dest[k]
dest[k] = taskfile.Var{
Static: r.Replace(v.Static),
Sh: r.Replace(v.Sh),
}
}
return r.Err()
}
resolveShell := func(dest taskfile.Vars, keys []string) error {
for _, k := range keys {
v := dest[k]
static, err := c.HandleDynamicVar(v)
if err != nil {
return err
}
dest[k] = taskfile.Var{Static: static}
}
return nil
}
update := func(dest taskfile.Vars, srcs ...taskfile.Vars) error {
merge(dest, srcs...)
// updatedKeys ensures template evaluation is run only once.
updatedKeys := varsKeys(srcs...)
if err := replaceVars(dest, updatedKeys); err != nil {
return err
}
return resolveShell(dest, updatedKeys)
}
// Resolve taskvars variables to "result" with environment override variables.
override := compiler.GetEnviron()
result := make(taskfile.Vars, len(c.Vars)+len(t.Vars)+len(override))
if err := update(result, c.Vars, override); err != nil {
return nil, err
}
// Resolve task variables to "result" with environment and call override variables.
merge(override, call.Vars)
if err := update(result, t.Vars, override); err != nil {
return nil, err
}
return result, nil
}
func (c *CompilerV1) HandleDynamicVar(v taskfile.Var) (string, error) {
if v.Static != "" || v.Sh == "" {
return v.Static, nil
}
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
if c.dynamicCache == nil {
c.dynamicCache = make(map[string]string, 30)
}
if result, ok := c.dynamicCache[v.Sh]; ok {
return result, nil
}
var stdout bytes.Buffer
opts := &execext.RunCommandOptions{
Command: v.Sh,
Dir: c.Dir,
Stdout: &stdout,
Stderr: c.Logger.Stderr,
}
if err := execext.RunCommand(context.Background(), opts); err != nil {
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
}
// Trim a single trailing newline from the result to make most command
// output easier to use in shell commands.
result := strings.TrimSuffix(stdout.String(), "\n")
c.dynamicCache[v.Sh] = result
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}

View File

@@ -7,11 +7,11 @@ import (
"strings"
"sync"
"github.com/go-task/task/v2/internal/compiler"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/go-task/task/v3/internal/templater"
)
var _ compiler.Compiler = &CompilerV2{}
@@ -19,8 +19,8 @@ var _ compiler.Compiler = &CompilerV2{}
type CompilerV2 struct {
Dir string
Taskvars taskfile.Vars
TaskfileVars taskfile.Vars
Taskvars *taskfile.Vars
TaskfileVars *taskfile.Vars
Expansions int
@@ -36,9 +36,10 @@ type CompilerV2 struct {
// 3. Taskfile variables
// 4. Taskvars file variables
// 5. Environment variables
func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
vr := varResolver{c: c, vars: compiler.GetEnviron()}
for _, vars := range []taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} {
vr.vars.Set("TASK", taskfile.Var{Static: t.Task})
for _, vars := range []*taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} {
for i := 0; i < c.Expansions; i++ {
vr.merge(vars)
}
@@ -48,16 +49,16 @@ func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfil
type varResolver struct {
c *CompilerV2
vars taskfile.Vars
vars *taskfile.Vars
err error
}
func (vr *varResolver) merge(vars taskfile.Vars) {
func (vr *varResolver) merge(vars *taskfile.Vars) {
if vr.err != nil {
return
}
tr := templater.Templater{Vars: vr.vars}
for k, v := range vars {
vars.Range(func(k string, v taskfile.Var) error {
v = taskfile.Var{
Static: tr.Replace(v.Static),
Sh: tr.Replace(v.Sh),
@@ -65,10 +66,11 @@ func (vr *varResolver) merge(vars taskfile.Vars) {
static, err := vr.c.HandleDynamicVar(v)
if err != nil {
vr.err = err
return
return err
}
vr.vars[k] = taskfile.Var{Static: static}
}
vr.vars.Set(k, taskfile.Var{Static: static})
return nil
})
vr.err = tr.Err()
}
@@ -103,7 +105,7 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
result := strings.TrimSuffix(stdout.String(), "\n")
c.dynamicCache[v.Sh] = result
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}

View File

@@ -0,0 +1,98 @@
package v3
import (
"bytes"
"context"
"fmt"
"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/logger"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/go-task/task/v3/internal/templater"
)
var _ compiler.Compiler = &CompilerV3{}
type CompilerV3 struct {
Dir string
TaskfileVars *taskfile.Vars
Logger *logger.Logger
dynamicCache map[string]string
muDynamicCache sync.Mutex
}
func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
result := compiler.GetEnviron()
result.Set("TASK", taskfile.Var{Static: t.Task})
rangeFunc := func(k string, v taskfile.Var) error {
tr := templater.Templater{Vars: result, RemoveNoValue: true}
v = taskfile.Var{
Static: tr.Replace(v.Static),
Sh: tr.Replace(v.Sh),
}
if err := tr.Err(); err != nil {
return err
}
static, err := c.HandleDynamicVar(v)
if err != nil {
return err
}
result.Set(k, taskfile.Var{Static: static})
return nil
}
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
return nil, err
}
if err := call.Vars.Range(rangeFunc); err != nil {
return nil, err
}
if err := t.Vars.Range(rangeFunc); err != nil {
return nil, err
}
return result, nil
}
func (c *CompilerV3) HandleDynamicVar(v taskfile.Var) (string, error) {
if v.Static != "" || v.Sh == "" {
return v.Static, nil
}
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
if c.dynamicCache == nil {
c.dynamicCache = make(map[string]string, 30)
}
if result, ok := c.dynamicCache[v.Sh]; ok {
return result, nil
}
var stdout bytes.Buffer
opts := &execext.RunCommandOptions{
Command: v.Sh,
Dir: c.Dir,
Stdout: &stdout,
Stderr: c.Logger.Stderr,
}
if err := execext.RunCommand(context.Background(), opts); err != nil {
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
}
// Trim a single trailing newline from the result to make most command
// output easier to use in shell commands.
result := strings.TrimSuffix(stdout.String(), "\n")
c.dynamicCache[v.Sh] = result
c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}

View File

@@ -1,38 +1,64 @@
package logger
import (
"fmt"
"io"
"github.com/fatih/color"
)
type PrintFunc func(io.Writer, string, ...interface{})
var (
Default PrintFunc = color.New(color.Reset).FprintfFunc()
Blue PrintFunc = color.New(color.FgBlue).FprintfFunc()
Green PrintFunc = color.New(color.FgGreen).FprintfFunc()
Cyan PrintFunc = color.New(color.FgCyan).FprintfFunc()
Yellow PrintFunc = color.New(color.FgYellow).FprintfFunc()
Magenta PrintFunc = color.New(color.FgMagenta).FprintfFunc()
Red PrintFunc = color.New(color.FgRed).FprintfFunc()
)
// Logger is just a wrapper that prints stuff to STDOUT or STDERR,
// with optional color.
type Logger struct {
Stdout io.Writer
Stderr io.Writer
Verbose bool
Color bool
}
func (l *Logger) Outf(s string, args ...interface{}) {
// Outf prints stuff to STDOUT.
func (l *Logger) Outf(print PrintFunc, s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(l.Stdout, s+"\n", args...)
if !l.Color {
print = Default
}
print(l.Stdout, s+"\n", args...)
}
func (l *Logger) VerboseOutf(s string, args ...interface{}) {
// VerboseOutf prints stuff to STDOUT if verbose mode is enabled.
func (l *Logger) VerboseOutf(print PrintFunc, s string, args ...interface{}) {
if l.Verbose {
l.Outf(s, args...)
l.Outf(print, s, args...)
}
}
func (l *Logger) Errf(s string, args ...interface{}) {
// Errf prints stuff to STDERR.
func (l *Logger) Errf(print PrintFunc, s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(l.Stderr, s+"\n", args...)
if !l.Color {
print = Default
}
print(l.Stderr, s+"\n", args...)
}
func (l *Logger) VerboseErrf(s string, args ...interface{}) {
// VerboseErrf prints stuff to STDERR if verbose mode is enabled.
func (l *Logger) VerboseErrf(print PrintFunc, s string, args ...interface{}) {
if l.Verbose {
l.Errf(s, args...)
l.Errf(print, s, args...)
}
}

View File

@@ -6,7 +6,7 @@ import (
"io"
"testing"
"github.com/go-task/task/v2/internal/output"
"github.com/go-task/task/v3/internal/output"
"github.com/stretchr/testify/assert"
)

View File

@@ -23,6 +23,10 @@ type Checksum struct {
// IsUpToDate implements the Checker interface
func (c *Checksum) IsUpToDate() (bool, error) {
if len(c.Sources) == 0 {
return false, nil
}
checksumFile := c.checksumFilePath()
data, _ := ioutil.ReadFile(checksumFile)
@@ -84,11 +88,24 @@ func (c *Checksum) checksum(files ...string) (string, error) {
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
// Value implements the Checker Interface
func (c *Checksum) Value() (interface{}, error) {
return c.checksum()
}
// OnError implements the Checker interface
func (c *Checksum) OnError() error {
if len(c.Sources) == 0 {
return nil
}
return os.Remove(c.checksumFilePath())
}
// Kind implements the Checker Interface
func (*Checksum) Kind() string {
return "checksum"
}
func (c *Checksum) checksumFilePath() string {
return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task))
}

View File

@@ -5,7 +5,7 @@ import (
"path/filepath"
"sort"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v3/internal/execext"
"github.com/mattn/go-zglob"
)

View File

@@ -8,6 +8,15 @@ func (None) IsUpToDate() (bool, error) {
return false, nil
}
// Value implements the Checker interface
func (None) Value() (interface{}, error) {
return "", nil
}
func (None) Kind() string {
return "none"
}
// OnError implements the Checker interface
func (None) OnError() error {
return nil

View File

@@ -9,5 +9,7 @@ var (
// Checker is an interface that checks if the status is up-to-date
type Checker interface {
IsUpToDate() (bool, error)
Value() (interface{}, error)
OnError() error
Kind() string
}

View File

@@ -41,6 +41,29 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
return !generatesMinTime.Before(sourcesMaxTime), nil
}
func (t *Timestamp) Kind() string {
return "timestamp"
}
// Value implements the Checker Interface
func (t *Timestamp) Value() (interface{}, error) {
sources, err := globs(t.Dir, t.Sources)
if err != nil {
return time.Now(), err
}
sourcesMaxTime, err := getMaxTime(sources...)
if err != nil {
return time.Now(), err
}
if sourcesMaxTime.IsZero() {
return time.Unix(0, 0), nil
}
return sourcesMaxTime, nil
}
func getMinTime(files ...string) (time.Time, error) {
var t time.Time
for _, f := range files {

View File

@@ -3,8 +3,8 @@ package summary
import (
"strings"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/taskfile"
)
func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
@@ -20,8 +20,8 @@ func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
return
}
l.Outf("")
l.Outf("")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "")
}
func PrintTask(l *logger.Logger, t *taskfile.Task) {
@@ -50,14 +50,14 @@ func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
for i, line := range lines {
notLastLine := i+1 < len(lines)
if notLastLine || line != "" {
l.Outf(line)
l.Outf(logger.Default, line)
}
}
}
func printTaskName(l *logger.Logger, t *taskfile.Task) {
l.Outf("task: %s", t.Task)
l.Outf("")
l.Outf(logger.Default, "task: %s", t.Name())
l.Outf(logger.Default, "")
}
func hasDescription(t *taskfile.Task) bool {
@@ -65,11 +65,11 @@ func hasDescription(t *taskfile.Task) bool {
}
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
l.Outf(t.Desc)
l.Outf(logger.Default, t.Desc)
}
func printNoDescriptionOrSummary(l *logger.Logger) {
l.Outf("(task does not have description or summary)")
l.Outf(logger.Default, "(task does not have description or summary)")
}
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
@@ -77,11 +77,11 @@ func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
return
}
l.Outf("")
l.Outf("dependencies:")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "dependencies:")
for _, d := range t.Deps {
l.Outf(" - %s", d.Task)
l.Outf(logger.Default, " - %s", d.Task)
}
}
@@ -90,14 +90,14 @@ func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
return
}
l.Outf("")
l.Outf("commands:")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "commands:")
for _, c := range t.Cmds {
isCommand := c.Cmd != ""
if isCommand {
l.Outf(" - %s", c.Cmd)
l.Outf(logger.Default, " - %s", c.Cmd)
} else {
l.Outf(" - Task: %s", c.Task)
l.Outf(logger.Default, " - Task: %s", c.Task)
}
}
}

View File

@@ -5,9 +5,9 @@ import (
"strings"
"testing"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/summary"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/summary"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/stretchr/testify/assert"
)

View File

@@ -3,5 +3,5 @@ package taskfile
// Call is the parameters to a task call
type Call struct {
Task string
Vars Vars
Vars *Vars
}

View File

@@ -10,14 +10,14 @@ type Cmd struct {
Cmd string
Silent bool
Task string
Vars Vars
Vars *Vars
IgnoreError bool
}
// Dep is a task dependency
type Dep struct {
Task string
Vars Vars
Vars *Vars
}
var (
@@ -51,7 +51,7 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
var taskCall struct {
Task string
Vars Vars
Vars *Vars
}
if err := unmarshal(&taskCall); err == nil {
c.Task = taskCall.Task
@@ -70,7 +70,7 @@ func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
var taskCall struct {
Task string
Vars Vars
Vars *Vars
}
if err := unmarshal(&taskCall); err == nil {
d.Task = taskCall.Task

View File

@@ -0,0 +1,40 @@
package taskfile
import "errors"
var (
// ErrCantUnmarshalIncludedTaskfile is returned for invalid var YAML.
ErrCantUnmarshalIncludedTaskfile = errors.New("task: can't unmarshal included value")
)
// IncludedTaskfile represents information about included tasksfile
type IncludedTaskfile struct {
Taskfile string
Dir string
AdvancedImport bool
}
// IncludedTaskfiles represents information about included tasksfiles
type IncludedTaskfiles = map[string]IncludedTaskfile
// UnmarshalYAML implements yaml.Unmarshaler interface
func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
if err := unmarshal(&str); err == nil {
it.Taskfile = str
return nil
}
var includedTaskfile struct {
Taskfile string
Dir string
}
if err := unmarshal(&includedTaskfile); err == nil {
it.Dir = includedTaskfile.Dir
it.Taskfile = includedTaskfile.Taskfile
it.AdvancedImport = true
return nil
}
return ErrCantUnmarshalIncludedTaskfile
}

View File

@@ -22,25 +22,20 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
}
if t1.Includes == nil {
t1.Includes = make(map[string]string)
t1.Includes = make(IncludedTaskfiles)
}
for k, v := range t2.Includes {
t1.Includes[k] = v
}
if t1.Vars == nil {
t1.Vars = make(Vars)
t1.Vars = &Vars{}
}
for k, v := range t2.Vars {
t1.Vars[k] = v
}
if t1.Env == nil {
t1.Env = make(Vars)
}
for k, v := range t2.Env {
t1.Env[k] = v
t1.Env = &Vars{}
}
t1.Vars.Merge(t2.Vars)
t1.Env.Merge(t2.Env)
if t1.Tasks == nil {
t1.Tasks = make(Tasks)

View File

@@ -3,10 +3,10 @@ package taskfile_test
import (
"testing"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
func TestPreconditionParse(t *testing.T) {

View File

@@ -7,14 +7,18 @@ import (
"path/filepath"
"runtime"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/go-task/task/v3/internal/templater"
"gopkg.in/yaml.v2"
"github.com/joho/godotenv"
"gopkg.in/yaml.v3"
)
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")
)
// Taskfile reads a Taskfile for a given directory
@@ -28,8 +32,47 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
return nil, err
}
for namespace, path := range t.Includes {
path = filepath.Join(dir, path)
v, err := t.ParsedVersion()
if err != nil {
return nil, err
}
if v >= 3.0 && len(t.Dotenv) > 0 {
for _, dotEnvPath := range t.Dotenv {
if !filepath.IsAbs(dotEnvPath) {
dotEnvPath = filepath.Join(dir, dotEnvPath)
}
envs, err := godotenv.Read(dotEnvPath)
if err != nil {
return nil, err
}
for key, value := range envs {
if _, ok := t.Env.Mapping[key]; !ok {
t.Env.Set(key, taskfile.Var{Static: value})
}
}
}
}
for namespace, includedTask := range t.Includes {
if v >= 3.0 {
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
includedTask = taskfile.IncludedTaskfile{
Taskfile: tr.Replace(includedTask.Taskfile),
Dir: tr.Replace(includedTask.Dir),
AdvancedImport: includedTask.AdvancedImport,
}
if err := tr.Err(); err != nil {
return nil, err
}
}
if filepath.IsAbs(includedTask.Taskfile) {
path = includedTask.Taskfile
} else {
path = filepath.Join(dir, includedTask.Taskfile)
}
info, err := os.Stat(path)
if err != nil {
return nil, err
@@ -44,19 +87,34 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
if len(includedTaskfile.Includes) > 0 {
return nil, ErrIncludedTaskfilesCantHaveIncludes
}
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
return nil, ErrIncludedTaskfilesCantHaveDotenvs
}
if includedTask.AdvancedImport {
for _, task := range includedTaskfile.Tasks {
if !filepath.IsAbs(task.Dir) {
task.Dir = filepath.Join(includedTask.Dir, task.Dir)
}
}
}
if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
return nil, err
}
}
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
if err != nil {
return nil, err
}
if err = taskfile.Merge(t, osTaskfile); err != nil {
return nil, err
if v < 3.0 {
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
if err != nil {
return nil, err
}
if err = taskfile.Merge(t, osTaskfile); err != nil {
return nil, err
}
}
}

View File

@@ -6,14 +6,14 @@ import (
"path/filepath"
"runtime"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
// Taskvars reads a Taskvars for a given directory
func Taskvars(dir string) (taskfile.Vars, error) {
vars := make(taskfile.Vars)
func Taskvars(dir string) (*taskfile.Vars, error) {
vars := &taskfile.Vars{}
path := filepath.Join(dir, "Taskvars.yml")
if _, err := os.Stat(path); err == nil {
@@ -29,24 +29,17 @@ func Taskvars(dir string) (taskfile.Vars, error) {
if err != nil {
return nil, err
}
if vars == nil {
vars = osVars
} else {
for k, v := range osVars {
vars[k] = v
}
}
vars.Merge(osVars)
}
return vars, nil
}
func readTaskvars(file string) (taskfile.Vars, error) {
func readTaskvars(file string) (*taskfile.Vars, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
var vars taskfile.Vars
return vars, yaml.NewDecoder(f).Decode(&vars)
return &vars, yaml.NewDecoder(f).Decode(&vars)
}

View File

@@ -1,24 +1,96 @@
package taskfile
import (
"errors"
)
// Tasks represents a group of tasks
type Tasks map[string]*Task
// Task represents a task
type Task struct {
Task string
Cmds []*Cmd
Deps []*Dep
Desc string
Summary string
Sources []string
Generates []string
Status []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
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
Dir string
Vars *Vars
Env *Vars
Silent bool
Method string
Prefix string
IgnoreError bool
}
var (
// ErrCantUnmarshalTask is returned for invalid task YAML
ErrCantUnmarshalTask = errors.New("task: can't unmarshal task value")
)
func (t *Task) Name() string {
if t.Label != "" {
return t.Label
}
return t.Task
}
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
var cmd Cmd
if err := unmarshal(&cmd); err == nil && cmd.Cmd != "" {
t.Cmds = append(t.Cmds, &cmd)
return nil
}
var cmds []*Cmd
if err := unmarshal(&cmds); err == nil && len(cmds) > 0 {
t.Cmds = cmds
return nil
}
var task struct {
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
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
}
if err := unmarshal(&task); err == nil {
t.Cmds = task.Cmds
t.Deps = task.Deps
t.Label = task.Label
t.Desc = task.Desc
t.Summary = task.Summary
t.Sources = task.Sources
t.Generates = task.Generates
t.Status = task.Status
t.Preconditions = task.Preconditions
t.Dir = task.Dir
t.Vars = task.Vars
t.Env = task.Env
t.Silent = task.Silent
t.Method = task.Method
t.Prefix = task.Prefix
t.IgnoreError = task.IgnoreError
return nil
}
return ErrCantUnmarshalTask
}

View File

@@ -1,33 +1,37 @@
package taskfile
import (
"fmt"
"strconv"
)
// Taskfile represents a Taskfile.yml
type Taskfile struct {
Version string
Expansions int
Output string
Includes map[string]string
Vars Vars
Env Vars
Method string
Includes IncludedTaskfiles
Vars *Vars
Env *Vars
Tasks Tasks
Silent bool
Dotenv []string
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&tf.Tasks); err == nil {
tf.Version = "1"
return nil
}
var taskfile struct {
Version string
Expansions int
Output string
Includes map[string]string
Vars Vars
Env Vars
Method string
Includes IncludedTaskfiles
Vars *Vars
Env *Vars
Tasks Tasks
Silent bool
Dotenv []string
}
if err := unmarshal(&taskfile); err != nil {
return err
@@ -35,16 +39,30 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
tf.Version = taskfile.Version
tf.Expansions = taskfile.Expansions
tf.Output = taskfile.Output
tf.Method = taskfile.Method
tf.Includes = taskfile.Includes
tf.Vars = taskfile.Vars
tf.Env = taskfile.Env
tf.Tasks = taskfile.Tasks
tf.Silent = taskfile.Silent
tf.Dotenv = taskfile.Dotenv
if tf.Expansions <= 0 {
tf.Expansions = 2
}
if tf.Vars == nil {
tf.Vars = make(Vars)
tf.Vars = &Vars{}
}
if tf.Env == nil {
tf.Env = &Vars{}
}
return nil
}
// ParsedVersion returns the version as a float64
func (tf *Taskfile) ParsedVersion() (float64, error) {
v, err := strconv.ParseFloat(tf.Version, 64)
if err != nil {
return 0, fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, tf.Version, err)
}
return v, nil
}

View File

@@ -3,10 +3,10 @@ package taskfile_test
import (
"testing"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
func TestCmdParse(t *testing.T) {
@@ -33,9 +33,12 @@ vars:
{
yamlTaskCall,
&taskfile.Cmd{},
&taskfile.Cmd{Task: "another-task", Vars: taskfile.Vars{
"PARAM1": taskfile.Var{Static: "VALUE1"},
"PARAM2": taskfile.Var{Static: "VALUE2"},
&taskfile.Cmd{Task: "another-task", Vars: &taskfile.Vars{
Keys: []string{"PARAM1", "PARAM2"},
Mapping: map[string]taskfile.Var{
"PARAM1": taskfile.Var{Static: "VALUE1"},
"PARAM2": taskfile.Var{Static: "VALUE2"},
},
}},
},
{
@@ -46,9 +49,12 @@ vars:
{
yamlTaskCall,
&taskfile.Dep{},
&taskfile.Dep{Task: "another-task", Vars: taskfile.Vars{
"PARAM1": taskfile.Var{Static: "VALUE1"},
"PARAM2": taskfile.Var{Static: "VALUE2"},
&taskfile.Dep{Task: "another-task", Vars: &taskfile.Vars{
Keys: []string{"PARAM1", "PARAM2"},
Mapping: map[string]taskfile.Var{
"PARAM1": taskfile.Var{Static: "VALUE1"},
"PARAM2": taskfile.Var{Static: "VALUE2"},
},
}},
},
}

View File

@@ -3,6 +3,8 @@ package taskfile
import (
"errors"
"strings"
"gopkg.in/yaml.v3"
)
var (
@@ -11,26 +13,99 @@ var (
)
// Vars is a string[string] variables map.
type Vars map[string]Var
type Vars struct {
Keys []string
Mapping map[string]Var
}
// ToStringMap converts Vars to a string map containing only the static
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
if node.Kind != yaml.MappingNode {
return errors.New("task: vars is not a map")
}
// NOTE(@andreynering): on this style of custom unmarsheling,
// even number contains the keys, while odd numbers contains
// the values.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
var v Var
if err := valueNode.Decode(&v); err != nil {
return err
}
vs.Set(keyNode.Value, v)
}
return nil
}
// Merge merges the given Vars into the caller one
func (vs *Vars) Merge(other *Vars) {
other.Range(func(key string, value Var) error {
vs.Set(key, value)
return nil
})
}
// Set sets a value to a given key
func (vs *Vars) Set(key string, value Var) {
if vs.Mapping == nil {
vs.Mapping = make(map[string]Var, 1)
}
if !strSliceContains(vs.Keys, key) {
vs.Keys = append(vs.Keys, key)
}
vs.Mapping[key] = value
}
func strSliceContains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
// Range allows you to loop into the vars in its right order
func (vs *Vars) Range(yield func(key string, value Var) error) error {
if vs == nil {
return nil
}
for _, k := range vs.Keys {
if err := yield(k, vs.Mapping[k]); err != nil {
return err
}
}
return nil
}
// ToCacheMap converts Vars to a map containing only the static
// variables
func (vs Vars) ToStringMap() (m map[string]string) {
m = make(map[string]string, len(vs))
for k, v := range vs {
func (vs *Vars) ToCacheMap() (m map[string]interface{}) {
m = make(map[string]interface{}, len(vs.Keys))
vs.Range(func(k string, v Var) error {
if v.Sh != "" {
// Dynamic variable is not yet resolved; trigger
// <no value> to be used in templates.
continue
return nil
}
m[k] = v.Static
}
if v.Live != nil {
m[k] = v.Live
} else {
m[k] = v.Static
}
return nil
})
return
}
// Var represents either a static or dynamic variable.
type Var struct {
Static string
Live interface{}
Sh string
}

View File

@@ -6,7 +6,7 @@ import (
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/go-task/slim-sprig"
)
var (

View File

@@ -2,9 +2,10 @@ package templater
import (
"bytes"
"strings"
"text/template"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile"
)
// Templater is a help struct that allow us to call "replaceX" funcs multiple
@@ -12,10 +13,15 @@ import (
// happen will be assigned to r.err, and consecutive calls to funcs will just
// return the zero value.
type Templater struct {
Vars taskfile.Vars
Vars *taskfile.Vars
RemoveNoValue bool
strMap map[string]string
err error
cacheMap map[string]interface{}
err error
}
func (r *Templater) ResetCache() {
r.cacheMap = r.Vars.ToCacheMap()
}
func (r *Templater) Replace(str string) string {
@@ -29,15 +35,18 @@ func (r *Templater) Replace(str string) string {
return ""
}
if r.strMap == nil {
r.strMap = r.Vars.ToStringMap()
if r.cacheMap == nil {
r.cacheMap = r.Vars.ToCacheMap()
}
var b bytes.Buffer
if err = templ.Execute(&b, r.strMap); err != nil {
if err = templ.Execute(&b, r.cacheMap); err != nil {
r.err = err
return ""
}
if r.RemoveNoValue {
return strings.ReplaceAll(b.String(), "<no value>", "")
}
return b.String()
}
@@ -53,19 +62,22 @@ func (r *Templater) ReplaceSlice(strs []string) []string {
return new
}
func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars {
if r.err != nil || len(vars) == 0 {
func (r *Templater) ReplaceVars(vars *taskfile.Vars) *taskfile.Vars {
if r.err != nil || vars == nil || len(vars.Keys) == 0 {
return nil
}
new := make(taskfile.Vars, len(vars))
for k, v := range vars {
new[k] = taskfile.Var{
var new taskfile.Vars
vars.Range(func(k string, v taskfile.Var) error {
new.Set(k, taskfile.Var{
Static: r.Replace(v.Static),
Live: v.Live,
Sh: r.Replace(v.Sh),
}
}
return new
})
return nil
})
return &new
}
func (r *Templater) Err() error {

View File

@@ -4,8 +4,9 @@ import (
"context"
"errors"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/taskfile"
)
var (
@@ -22,7 +23,7 @@ func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task
})
if err != nil {
e.Logger.Errf("task: %s", p.Msg)
e.Logger.Errf(logger.Magenta, "task: %s", p.Msg)
return false, ErrPreconditionFailed
}
}

View File

@@ -4,9 +4,10 @@ import (
"context"
"fmt"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/status"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/status"
"github.com/go-task/task/v3/internal/taskfile"
)
// Status returns an error if any the of given tasks is not up-to-date
@@ -21,7 +22,7 @@ func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
return err
}
if !isUpToDate {
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Task)
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Name())
}
}
return nil
@@ -49,25 +50,37 @@ func (e *Executor) statusOnError(t *taskfile.Task) error {
}
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
switch t.Method {
case "", "timestamp":
return &status.Timestamp{
Dir: t.Dir,
Sources: t.Sources,
Generates: t.Generates,
}, nil
method := t.Method
if method == "" {
method = e.Taskfile.Method
}
switch method {
case "timestamp":
return e.timestampChecker(t), nil
case "checksum":
return &status.Checksum{
Dir: t.Dir,
Task: t.Task,
Sources: t.Sources,
Generates: t.Generates,
Dry: e.Dry,
}, nil
return e.checksumChecker(t), nil
case "none":
return status.None{}, nil
default:
return nil, fmt.Errorf(`task: invalid method "%s"`, t.Method)
return nil, fmt.Errorf(`task: invalid method "%s"`, method)
}
}
func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
return &status.Timestamp{
Dir: t.Dir,
Sources: t.Sources,
Generates: t.Generates,
}
}
func (e *Executor) checksumChecker(t *taskfile.Task) status.Checker {
return &status.Checksum{
Dir: t.Dir,
Task: t.Task,
Sources: t.Sources,
Generates: t.Generates,
Dry: e.Dry,
}
}
@@ -79,10 +92,10 @@ func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (
Env: getEnviron(t),
})
if err != nil {
e.Logger.VerboseOutf("task: status command %s exited non-zero: %s", s, err)
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err)
return false, nil
}
e.Logger.VerboseOutf("task: status command %s exited zero", s)
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s)
}
return true, nil
}

102
task.go
View File

@@ -6,19 +6,18 @@ import (
"fmt"
"io"
"os"
"strconv"
"sync"
"sync/atomic"
"github.com/go-task/task/v2/internal/compiler"
compilerv1 "github.com/go-task/task/v2/internal/compiler/v1"
compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/output"
"github.com/go-task/task/v2/internal/summary"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/taskfile/read"
"github.com/go-task/task/v3/internal/compiler"
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/logger"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/summary"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/go-task/task/v3/internal/taskfile/read"
"golang.org/x/sync/errgroup"
)
@@ -42,6 +41,7 @@ type Executor struct {
Dry bool
Summary bool
Parallel bool
Color bool
Stdin io.Reader
Stdout io.Writer
@@ -52,7 +52,7 @@ type Executor struct {
Output output.Output
OutputStyle string
taskvars taskfile.Vars
taskvars *taskfile.Vars
taskCallCount map[string]*int32
mkdirMutexMap map[string]*sync.Mutex
@@ -110,11 +110,19 @@ func (e *Executor) Setup() error {
if err != nil {
return err
}
e.taskvars, err = read.Taskvars(e.Dir)
v, err := e.Taskfile.ParsedVersion()
if err != nil {
return err
}
if v < 3.0 {
e.taskvars, err = read.Taskvars(e.Dir)
if err != nil {
return err
}
}
if e.Stdin == nil {
e.Stdin = os.Stdin
}
@@ -128,31 +136,28 @@ func (e *Executor) Setup() error {
Stdout: e.Stdout,
Stderr: e.Stderr,
Verbose: e.Verbose,
Color: e.Color,
}
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
if err != nil {
return fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
if v < 2 {
return fmt.Errorf(`task: Taskfile versions prior to v2 are not supported anymore`)
}
// consider as equal to the greater version if round
if v == 2.0 {
v = 2.6
}
if v < 1 {
return fmt.Errorf(`task: Taskfile version should be greater or equal to v1`)
}
if v > 2.6 {
return fmt.Errorf(`task: Taskfile versions greater than v2.6 not implemented in the version of Task`)
if v > 3.0 {
return fmt.Errorf(`task: Taskfile versions greater than v3.0 not implemented in the version of Task`)
}
if v < 2 {
e.Compiler = &compilerv1.CompilerV1{
Dir: e.Dir,
Vars: e.taskvars,
Logger: e.Logger,
}
} else { // v >= 2
// Color available only on v3
if v < 3 {
e.Logger.Color = false
}
if v < 3 {
e.Compiler = &compilerv2.CompilerV2{
Dir: e.Dir,
Taskvars: e.taskvars,
@@ -160,6 +165,12 @@ func (e *Executor) Setup() error {
Expansions: e.Taskfile.Expansions,
Logger: e.Logger,
}
} else {
e.Compiler = &compilerv3.CompilerV3{
Dir: e.Dir,
TaskfileVars: e.Taskfile.Vars,
Logger: e.Logger,
}
}
if v < 2.1 && e.Taskfile.Output != "" {
@@ -168,6 +179,9 @@ func (e *Executor) Setup() error {
if v < 2.2 && len(e.Taskfile.Includes) > 0 {
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
}
if v >= 3.0 && e.Taskfile.Expansions > 2 {
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
}
if e.OutputStyle != "" {
e.Taskfile.Output = e.OutputStyle
@@ -183,6 +197,14 @@ func (e *Executor) Setup() error {
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
}
if e.Taskfile.Method == "" {
if v >= 3 {
e.Taskfile.Method = "checksum"
} else {
e.Taskfile.Method = "timestamp"
}
}
if v <= 2.1 {
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
@@ -206,6 +228,14 @@ func (e *Executor) Setup() error {
}
}
if v < 3 {
for _, taskfile := range e.Taskfile.Includes {
if taskfile.AdvancedImport {
return errors.New(`task: Import with additional parameters is only available starting on Taskfile version v3`)
}
}
}
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
e.mkdirMutexMap = make(map[string]*sync.Mutex, len(e.Taskfile.Tasks))
for k := range e.Taskfile.Tasks {
@@ -242,24 +272,24 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
if upToDate && preCondMet {
if !e.Silent {
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Name())
}
return nil
}
}
if err := e.mkdir(t); err != nil {
e.Logger.Errf("task: cannot make directory %q: %v", t.Dir, err)
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v", t.Dir, err)
}
for i := range t.Cmds {
if err := e.runCommand(ctx, t, call, i); err != nil {
if err2 := e.statusOnError(t); err2 != nil {
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v", err2)
}
if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf("task: task error ignored: %v", err)
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v", err)
continue
}
@@ -316,7 +346,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
return nil
case cmd.Cmd != "":
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
e.Logger.Errf(cmd.Cmd)
e.Logger.Errf(logger.Green, "task: %s", cmd.Cmd)
}
if e.Dry {
@@ -347,7 +377,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
Stderr: stdErr,
})
if execext.IsExitError(err) && cmd.IgnoreError {
e.Logger.VerboseErrf("task: command error ignored: %v", err)
e.Logger.VerboseErrf(logger.Yellow, "task: command error ignored: %v", err)
return nil
}
return err
@@ -362,8 +392,10 @@ func getEnviron(t *taskfile.Task) []string {
}
environ := os.Environ()
for k, v := range t.Env.ToStringMap() {
environ = append(environ, fmt.Sprintf("%s=%s", k, v))
for k, v := range t.Env.ToCacheMap() {
if s, ok := v.(string); ok {
environ = append(environ, fmt.Sprintf("%s=%s", k, s))
}
}
return environ
}

View File

@@ -11,8 +11,8 @@ import (
"strings"
"testing"
"github.com/go-task/task/v2"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/internal/taskfile"
"github.com/stretchr/testify/assert"
)
@@ -69,43 +69,6 @@ func TestEnv(t *testing.T) {
tt.Run(t)
}
func TestVarsV1(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars/v1",
Target: "default",
TrimSpace: true,
Files: map[string]string{
// hello task:
"foo.txt": "foo",
"bar.txt": "bar",
"baz.txt": "baz",
"tmpl_foo.txt": "foo",
"tmpl_bar.txt": "<no value>",
"tmpl_foo2.txt": "foo2",
"tmpl_bar2.txt": "bar2",
"shtmpl_foo.txt": "foo",
"shtmpl_foo2.txt": "foo2",
"nestedtmpl_foo.txt": "{{.FOO}}",
"nestedtmpl_foo2.txt": "foo2",
"foo2.txt": "foo2",
"bar2.txt": "bar2",
"baz2.txt": "baz2",
"tmpl2_foo.txt": "<no value>",
"tmpl2_foo2.txt": "foo2",
"tmpl2_bar.txt": "<no value>",
"tmpl2_bar2.txt": "<no value>",
"shtmpl2_foo.txt": "<no value>",
"shtmpl2_foo2.txt": "foo2",
"nestedtmpl2_foo2.txt": "{{.FOO2}}",
"override.txt": "bar",
},
}
tt.Run(t)
// Ensure identical results when running hello task directly.
tt.Target = "hello"
tt.Run(t)
}
func TestVarsV2(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars/v2",
@@ -135,6 +98,7 @@ func TestVarsV2(t *testing.T) {
"nestedtmpl2_foo2.txt": "<no value>",
"override.txt": "bar",
"nested.txt": "Taskvars-TaskfileVars-TaskVars",
"task_name.txt": "hello",
},
}
tt.Run(t)
@@ -143,8 +107,22 @@ func TestVarsV2(t *testing.T) {
tt.Run(t)
}
func TestVarsV3(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars/v3",
Target: "default",
Files: map[string]string{
"missing-var.txt": "\n",
"var-order.txt": "ABCDEF\n",
"dependent-sh.txt": "123456\n",
"with-call.txt": "Hi, ABC123!\n",
},
}
tt.Run(t)
}
func TestMultilineVars(t *testing.T) {
for _, dir := range []string{"testdata/vars/v1/multiline", "testdata/vars/v2/multiline"} {
for _, dir := range []string{"testdata/vars/v2/multiline"} {
tt := fileContentTest{
Dir: dir,
Target: "default",
@@ -168,7 +146,7 @@ func TestMultilineVars(t *testing.T) {
func TestVarsInvalidTmpl(t *testing.T) {
const (
dir = "testdata/vars/v1"
dir = "testdata/vars/v2"
target = "invalid-var-tmpl"
expectError = "template: :1: unexpected EOF"
)
@@ -406,6 +384,118 @@ func TestStatusChecksum(t *testing.T) {
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
}
func TestLabelUpToDate(t *testing.T) {
const dir = "testdata/label_uptodate"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
func TestLabelSummary(t *testing.T) {
const dir = "testdata/label_summary"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Summary: true,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
func TestLabelInStatus(t *testing.T) {
const dir = "testdata/label_status"
e := task.Executor{
Dir: dir,
}
assert.NoError(t, e.Setup())
err := e.Status(context.Background(), taskfile.Call{Task: "foo"})
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "foobar")
}
}
func TestLabelWithVariableExpansion(t *testing.T) {
const dir = "testdata/label_var"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobaz")
}
func TestLabelInSummary(t *testing.T) {
const dir = "testdata/label_summary"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
func TestLabelInList(t *testing.T) {
const dir = "testdata/label_list"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
e.PrintTasksHelp()
assert.Contains(t, buff.String(), "foobar")
}
func TestStatusVariables(t *testing.T) {
const dir = "testdata/status_vars"
_ = os.RemoveAll(filepath.Join(dir, ".task"))
_ = os.Remove(filepath.Join(dir, "generated.txt"))
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Silent: false,
Verbose: true,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
assert.Contains(t, buff.String(), "d41d8cd98f00b204e9800998ecf8427e")
inf, err := os.Stat(filepath.Join(dir, "source.txt"))
assert.NoError(t, err)
ts := fmt.Sprintf("%d", inf.ModTime().Unix())
tf := fmt.Sprintf("%s", inf.ModTime())
assert.Contains(t, buff.String(), ts)
assert.Contains(t, buff.String(), tf)
}
func TestInit(t *testing.T) {
const dir = "testdata/init"
var file = filepath.Join(dir, "Taskfile.yml")
@@ -441,7 +531,6 @@ func TestTaskVersion(t *testing.T) {
Dir string
Version string
}{
{"testdata/version/v1", "1"},
{"testdata/version/v2", "2"},
}
@@ -511,7 +600,7 @@ func TestDry(t *testing.T) {
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
assert.Equal(t, "touch file.txt", strings.TrimSpace(buff.String()))
assert.Equal(t, "task: touch file.txt", strings.TrimSpace(buff.String()))
if _, err := os.Stat(file); err == nil {
t.Errorf("File should not exist %s", file)
}
@@ -549,14 +638,33 @@ func TestIncludes(t *testing.T) {
Target: "default",
TrimSpace: true,
Files: map[string]string{
"main.txt": "main",
"included_directory.txt": "included_directory",
"included_taskfile.txt": "included_taskfile",
"main.txt": "main",
"included_directory.txt": "included_directory",
"included_directory_without_dir.txt": "included_directory_without_dir",
"included_taskfile_without_dir.txt": "included_taskfile_without_dir",
"./module2/included_directory_with_dir.txt": "included_directory_with_dir",
"./module2/included_taskfile_with_dir.txt": "included_taskfile_with_dir",
"os_include.txt": "os",
},
}
tt.Run(t)
}
func TestIncorrectVersionIncludes(t *testing.T) {
const dir = "testdata/incorrect_includes"
expectedError := "task: Import with additional parameters is only available starting on Taskfile version v3"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
assert.EqualError(t, e.Setup(), expectedError)
}
func TestIncludesEmptyMain(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/includes_empty",
@@ -668,7 +776,7 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
}
// Ensure that the directory to be created doesn't actually exist.
_ = os.Remove(toBeCreated)
_ = os.RemoveAll(toBeCreated)
if _, err := os.Stat(toBeCreated); err == nil {
t.Errorf("Directory should not exist: %v", err)
}
@@ -679,5 +787,79 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
assert.Equal(t, expected, got, "Mismatch in the working directory")
// Clean-up after ourselves only if no error.
_ = os.Remove(toBeCreated)
_ = os.RemoveAll(toBeCreated)
}
func TestDisplaysErrorOnUnsupportedVersion(t *testing.T) {
e := task.Executor{
Dir: "testdata/version/v1",
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
err := e.Setup()
assert.Error(t, err)
assert.Equal(t, "task: Taskfile versions prior to v2 are not supported anymore", err.Error())
}
func TestShortTaskNotation(t *testing.T) {
const dir = "testdata/short_task_notation"
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: "default"}))
assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
}
func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/dotenv",
Target: "default",
TrimSpace: false,
Files: map[string]string{
"include.txt": "INCLUDE1='from_include1' INCLUDE2='from_include2'\n",
},
}
tt.Run(t)
}
func TestDotenvShouldErrorWithIncludeEnvPath(t *testing.T) {
const dir = "testdata/dotenv"
const entry = "Taskfile-errors1.yml"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Entrypoint: entry,
Summary: true,
Stdout: &buff,
Stderr: &buff,
}
err := e.Setup()
assert.Error(t, err)
assert.Contains(t, err.Error(), "no such file")
}
func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
const dir = "testdata/dotenv"
const entry = "Taskfile-errors2.yml"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Entrypoint: entry,
Summary: true,
Stdout: &buff,
Stderr: &buff,
}
err := e.Setup()
assert.Error(t, err)
assert.Contains(t, err.Error(), "move the dotenv")
}

View File

@@ -1,9 +1,12 @@
build:
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./**/glob-with-inexistent-file.txt
- ./source.txt
generates:
- ./generated.txt
method: checksum
version: '3'
tasks:
build:
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./**/glob-with-inexistent-file.txt
- ./source.txt
generates:
- ./generated.txt
method: checksum

View File

@@ -1,7 +1,10 @@
task-1:
deps:
- task: task-2
version: '3'
task-2:
deps:
- task: task-1
tasks:
task-1:
deps:
- task: task-2
task-2:
deps:
- task: task-1

View File

@@ -1,53 +1,56 @@
default:
deps: [d1, d2, d3]
version: '3'
d1:
deps: [d11, d12, d13]
cmds:
- echo 'Text' > d1.txt
tasks:
default:
deps: [d1, d2, d3]
d2:
deps: [d21, d22, d23]
cmds:
- echo 'Text' > d2.txt
d1:
deps: [d11, d12, d13]
cmds:
- echo 'Text' > d1.txt
d3:
deps: [d31, d32, d33]
cmds:
- echo 'Text' > d3.txt
d2:
deps: [d21, d22, d23]
cmds:
- echo 'Text' > d2.txt
d11:
cmds:
- echo 'Text' > d11.txt
d3:
deps: [d31, d32, d33]
cmds:
- echo 'Text' > d3.txt
d12:
cmds:
- echo 'Text' > d12.txt
d11:
cmds:
- echo 'Text' > d11.txt
d13:
cmds:
- echo 'Text' > d13.txt
d12:
cmds:
- echo 'Text' > d12.txt
d21:
cmds:
- echo 'Text' > d21.txt
d13:
cmds:
- echo 'Text' > d13.txt
d22:
cmds:
- echo 'Text' > d22.txt
d21:
cmds:
- echo 'Text' > d21.txt
d23:
cmds:
- echo 'Text' > d23.txt
d22:
cmds:
- echo 'Text' > d22.txt
d31:
cmds:
- echo 'Text' > d31.txt
d23:
cmds:
- echo 'Text' > d23.txt
d32:
cmds:
- echo 'Text' > d32.txt
d31:
cmds:
- echo 'Text' > d31.txt
d33:
cmds:
- echo 'Text' > d33.txt
d32:
cmds:
- echo 'Text' > d32.txt
d33:
cmds:
- echo 'Text' > d33.txt

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
whereami:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
whereami:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
whereami:

8
testdata/dotenv/Taskfile-errors1.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: '3'
dotenv: ['include1/.env', 'include1/envs/.env', 'file-does-not-exist']
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include-errors1.txt

9
testdata/dotenv/Taskfile-errors2.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
version: '3'
includes:
include1: './include1'
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include-errors2.txt

8
testdata/dotenv/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: '3'
dotenv: ['include1/.env', 'include1/envs/.env']
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include.txt

1
testdata/dotenv/include1/.env vendored Normal file
View File

@@ -0,0 +1 @@
INCLUDE1=from_include1

3
testdata/dotenv/include1/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
version: '3'
dotenv: ['.env']

1
testdata/dotenv/include1/envs/.env vendored Normal file
View File

@@ -0,0 +1 @@
INCLUDE2=from_include2

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
build:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
default:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
vars:
BAZ:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
pwd:

View File

@@ -1,41 +1,51 @@
abs.txt:
desc: generates dest file based on absolute paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '{{.BUILD_DIR}}/abs.txt'
sources:
- src.txt
generates:
- "{{.BUILD_DIR}}/abs.txt"
version: '3'
rel.txt:
desc: generates dest file based on relative paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '../rel.txt'
sources:
- src.txt
generates:
- "../rel.txt"
vars:
BUILD_DIR: $pwd
sub/src.txt:
desc: generate source file
cmds:
- mkdir -p sub
- echo "hello world" > sub/src.txt
status:
- test -f sub/src.txt
tasks:
abs.txt:
desc: generates dest file based on absolute paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '{{.BUILD_DIR}}/abs.txt'
method: timestamp
sources:
- src.txt
generates:
- "{{.BUILD_DIR}}/abs.txt"
'my text file.txt':
desc: generate file with spaces in the name
deps: [sub/src.txt]
cmds:
- cat sub/src.txt > 'my text file.txt'
sources:
- sub/src.txt
generates:
- 'my text file.txt'
rel.txt:
desc: generates dest file based on relative paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '../rel.txt'
method: timestamp
sources:
- src.txt
generates:
- "../rel.txt"
sub/src.txt:
desc: generate source file
cmds:
- mkdir -p sub
- echo "hello world" > sub/src.txt
method: timestamp
status:
- test -f sub/src.txt
'my text file.txt':
desc: generate file with spaces in the name
deps: [sub/src.txt]
cmds:
- cat sub/src.txt > 'my text file.txt'
method: timestamp
sources:
- sub/src.txt
generates:
- 'my text file.txt'

View File

@@ -1 +0,0 @@
BUILD_DIR: $pwd

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
task-should-pass:

View File

@@ -1,8 +1,19 @@
version: '2'
version: '3'
includes:
included: ./included
included_taskfile: ./Taskfile2.yml
included_without_dir:
taskfile: ./module1
included_taskfile_without_dir:
taskfile: ./module1/Taskfile.yml
included_with_dir:
taskfile: ./module2
dir: ./module2
included_taskfile_with_dir:
taskfile: ./module2/Taskfile.yml
dir: ./module2
included_os: ./Taskfile_{{OS}}.yml
tasks:
default:
@@ -10,6 +21,11 @@ tasks:
- task: gen
- task: included:gen
- task: included_taskfile:gen
- task: included_without_dir:gen_file
- task: included_taskfile_without_dir:gen_dir
- task: included_with_dir:gen_file
- task: included_taskfile_with_dir:gen_dir
- task: included_os:gen
gen:
cmds:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
gen:

4
testdata/includes/Taskfile_darwin.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
gen: echo 'os' > os_include.txt

4
testdata/includes/Taskfile_linux.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
gen: echo 'os' > os_include.txt

View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
gen: echo 'os' > os_include.txt

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
gen:

10
testdata/includes/module1/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: '3'
tasks:
gen_dir:
cmds:
- echo included_directory_without_dir > included_directory_without_dir.txt
gen_file:
cmds:
- echo included_taskfile_without_dir > included_taskfile_without_dir.txt

10
testdata/includes/module2/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: '3'
tasks:
gen_dir:
cmds:
- echo included_directory_with_dir > included_directory_with_dir.txt
gen_file:
cmds:
- echo included_taskfile_with_dir > included_taskfile_with_dir.txt

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
includes:
included: Taskfile2.yml

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
call-root:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
includes:
included: Taskfile2.yml

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
default:

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
includes:
included: Taskfile2.yml

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
vars:
FILE: file.txt

View File

@@ -0,0 +1,10 @@
version: '2.6'
includes:
included:
taskfile: ./included
tasks:
default:
cmds:
- task: gen

View File

@@ -0,0 +1,6 @@
version: '2.6'
tasks:
gen:
cmds:
- echo incorrect includes test

6
testdata/label_list/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: '3'
tasks:
foo:
label: "foobar"
desc: "task description"

7
testdata/label_status/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: '3'
tasks:
foo:
label: "foobar"
status:
- "false"

8
testdata/label_summary/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: '3'
tasks:
foo:
label: "foobar"
desc: description
status:
- echo "I'm ok"

7
testdata/label_uptodate/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: '3'
tasks:
foo:
label: "foobar"
status:
- echo "I'm ok"

10
testdata/label_var/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: '3'
vars:
BAR: baz
tasks:
foo:
label: "foo{{.BAR}}"
status:
- echo "I'm ok"

View File

@@ -1,37 +1,44 @@
default:
vars:
SPANISH: ¡Holla mundo!
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
GERMAN: "Welt!"
deps:
- task: write-file
vars: {CONTENT: Dependence1, FILE: dep1.txt}
- task: write-file
vars: {CONTENT: Dependence2, FILE: dep2.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH|replace \"mundo\" \"dependencia\"}}", FILE: spanish-dep.txt}
cmds:
- task: write-file
vars: {CONTENT: Hello, FILE: hello.txt}
- task: write-file
vars: {CONTENT: "$echo 'World'", FILE: world.txt}
- task: write-file
vars: {CONTENT: "!", FILE: exclamation.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH}}", FILE: spanish.txt}
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese.txt}
- task: write-file
vars: {CONTENT: "{{.GERMAN}}", FILE: german.txt}
- task: non-default
version: '3'
write-file:
cmds:
- echo {{.CONTENT}} > {{.FILE}}
vars:
PORTUGUESE_HELLO_WORLD: Olá, mundo!
GERMAN: Hello
non-default:
vars:
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
cmds:
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese2.txt}
tasks:
default:
vars:
SPANISH: ¡Holla mundo!
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
GERMAN: "Welt!"
deps:
- task: write-file
vars: {CONTENT: Dependence1, FILE: dep1.txt}
- task: write-file
vars: {CONTENT: Dependence2, FILE: dep2.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH|replace \"mundo\" \"dependencia\"}}", FILE: spanish-dep.txt}
cmds:
- task: write-file
vars: {CONTENT: Hello, FILE: hello.txt}
- task: write-file
vars: {CONTENT: "$echo 'World'", FILE: world.txt}
- task: write-file
vars: {CONTENT: "!", FILE: exclamation.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH}}", FILE: spanish.txt}
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese.txt}
- task: write-file
vars: {CONTENT: "{{.GERMAN}}", FILE: german.txt}
- task: non-default
write-file:
cmds:
- echo {{.CONTENT}} > {{.FILE}}
non-default:
vars:
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
cmds:
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese2.txt}

View File

@@ -1,2 +0,0 @@
PORTUGUESE_HELLO_WORLD: Olá, mundo!
GERMAN: "Hello"

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
foo:

View File

@@ -0,0 +1,12 @@
version: '3'
tasks:
default:
- task: string-slice
- task: string
string-slice:
- echo "string-slice-1"
- echo "string-slice-2"
string: echo "string"

View File

@@ -1,5 +1,8 @@
gen-foo:
cmds:
- touch foo.txt
status:
- test -f foo.txt
version: '3'
tasks:
gen-foo:
cmds:
- touch foo.txt
status:
- test -f foo.txt

1
testdata/status_vars/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
generated.txt

10
testdata/status_vars/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: '3'
tasks:
build:
sources:
- ./source.txt
status:
- echo "{{.CHECKSUM}}"
- echo '{{.TIMESTAMP.Unix}}'
- echo '{{.TIMESTAMP}}'

1
testdata/status_vars/source.txt vendored Normal file
View File

@@ -0,0 +1 @@
Hello, World!

View File

@@ -1,4 +1,4 @@
version: 2
version: '3'
tasks:
task-with-summary:

View File

@@ -1,48 +0,0 @@
default:
deps: [hello]
hello:
cmds:
- echo {{.FOO}} > foo.txt
- echo {{.BAR}} > bar.txt
- echo {{.BAZ}} > baz.txt
- echo '{{.TMPL_FOO}}' > tmpl_foo.txt
- echo '{{.TMPL_BAR}}' > tmpl_bar.txt
- echo '{{.TMPL_FOO2}}' > tmpl_foo2.txt
- echo '{{.TMPL_BAR2}}' > tmpl_bar2.txt
- echo '{{.SHTMPL_FOO}}' > shtmpl_foo.txt
- echo '{{.SHTMPL_FOO2}}' > shtmpl_foo2.txt
- echo '{{.NESTEDTMPL_FOO}}' > nestedtmpl_foo.txt
- echo '{{.NESTEDTMPL_FOO2}}' > nestedtmpl_foo2.txt
- echo {{.FOO2}} > foo2.txt
- echo {{.BAR2}} > bar2.txt
- echo {{.BAZ2}} > baz2.txt
- echo '{{.TMPL2_FOO}}' > tmpl2_foo.txt
- echo '{{.TMPL2_BAR}}' > tmpl2_bar.txt
- echo '{{.TMPL2_FOO2}}' > tmpl2_foo2.txt
- echo '{{.TMPL2_BAR2}}' > tmpl2_bar2.txt
- echo '{{.SHTMPL2_FOO}}' > shtmpl2_foo.txt
- echo '{{.SHTMPL2_FOO2}}' > shtmpl2_foo2.txt
- echo '{{.NESTEDTMPL2_FOO2}}' > nestedtmpl2_foo2.txt
- echo {{.OVERRIDE}} > override.txt
vars:
FOO: foo
BAR: $echo bar
BAZ:
sh: echo baz
TMPL_FOO: "{{.FOO}}"
TMPL_BAR: "{{.BAR}}"
TMPL_FOO2: "{{.FOO2}}"
TMPL_BAR2: "{{.BAR2}}"
SHTMPL_FOO:
sh: "echo '{{.FOO}}'"
SHTMPL_FOO2:
sh: "echo '{{.FOO2}}'"
NESTEDTMPL_FOO: "{{.TMPL_FOO}}"
NESTEDTMPL_FOO2: "{{.TMPL2_FOO2}}"
OVERRIDE: "bar"
invalid-var-tmpl:
vars:
CHARS: "abcd"
INVALID: "{{range .CHARS}}no end"

View File

@@ -1,12 +0,0 @@
FOO2: foo2
BAR2: $echo bar2
BAZ2:
sh: echo baz2
TMPL2_FOO: "{{.FOO}}"
TMPL2_BAR: "{{.BAR}}"
TMPL2_FOO2: "{{.FOO2}}"
TMPL2_BAR2: "{{.BAR2}}"
SHTMPL2_FOO2:
sh: "echo '{{.FOO2}}'"
NESTEDTMPL2_FOO2: "{{.TMPL2_FOO2}}"
OVERRIDE: "foo"

View File

@@ -1,43 +0,0 @@
default:
vars:
MULTILINE: "\n\nfoo\n bar\nfoobar\n\nbaz\n\n"
cmds:
- task: file
vars:
CONTENT:
sh: "echo 'foo\nbar'"
FILE: "echo_foobar.txt"
- task: file
vars:
CONTENT:
sh: "echo -n 'foo\nbar'"
FILE: "echo_n_foobar.txt"
- task: file
vars:
CONTENT:
sh: echo -n "{{.MULTILINE}}"
FILE: "echo_n_multiline.txt"
- task: file
vars:
CONTENT: "{{.MULTILINE}}"
FILE: "var_multiline.txt"
- task: file
vars:
CONTENT: "{{.MULTILINE | catLines}}"
FILE: "var_catlines.txt"
- task: enumfile
vars:
LINES: "{{.MULTILINE}}"
FILE: "var_enumfile.txt"
file:
cmds:
- |
cat << EOF > '{{.FILE}}'
{{.CONTENT}}
EOF
enumfile:
cmds:
- |
cat << EOF > '{{.FILE}}'
{{range $i, $line := .LINES| splitLines}}{{$i}}:{{$line}}
{{end}}EOF

View File

@@ -32,6 +32,7 @@ tasks:
- echo '{{.NESTEDTMPL2_FOO2}}' > nestedtmpl2_foo2.txt
- echo {{.OVERRIDE}} > override.txt
- echo '{{.NESTED3}}' > nested.txt
- echo '{{.TASK}}' > task_name.txt
vars:
FOO: foo
BAR: $echo bar

Some files were not shown because too many files have changed in this diff Show More