mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
324 Commits
v2.4.0
...
v3.0.0-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee7f2a541f | ||
|
|
59a00eae98 | ||
|
|
df8293bee6 | ||
|
|
935216f179 | ||
|
|
f56bbd46fd | ||
|
|
9f0f18c5c4 | ||
|
|
191c34c9c4 | ||
|
|
6a604b3002 | ||
|
|
5a435b533e | ||
|
|
4b027722b1 | ||
|
|
68ce8642b1 | ||
|
|
4913b6a0f1 | ||
|
|
aee0ab05f4 | ||
|
|
b44432f24a | ||
|
|
86be13ff1f | ||
|
|
ee95df0e57 | ||
|
|
442c29f020 | ||
|
|
739037fc37 | ||
|
|
f8252020aa | ||
|
|
38b87439fe | ||
|
|
116879f7ea | ||
|
|
2eafb2f067 | ||
|
|
c36f0f6f7f | ||
|
|
bfc033959b | ||
|
|
814f350b6d | ||
|
|
05db8ce582 | ||
|
|
3fd36a0c72 | ||
|
|
cbb12b29bd | ||
|
|
6ed30f1add | ||
|
|
a044c41c66 | ||
|
|
fb78e53a14 | ||
|
|
acfbbaa549 | ||
|
|
d52d74c64c | ||
|
|
d36f73de01 | ||
|
|
628c4a7b5f | ||
|
|
ca07a663e1 | ||
|
|
66d008391e | ||
|
|
eef84bda26 | ||
|
|
e0defe71aa | ||
|
|
069257151e | ||
|
|
3f80a3b39e | ||
|
|
b2a56161bb | ||
|
|
5e75639244 | ||
|
|
cb2cd3e10f | ||
|
|
0acb911d6a | ||
|
|
17ad7060b3 | ||
|
|
f38ba7fcd3 | ||
|
|
a3464068bd | ||
|
|
347ecc028f | ||
|
|
94ac60fa09 | ||
|
|
d567e23e50 | ||
|
|
8ff81562d2 | ||
|
|
7a8142ed92 | ||
|
|
eaba1b9cc8 | ||
|
|
b08b3546d2 | ||
|
|
7453e688fd | ||
|
|
32b097b3f2 | ||
|
|
22394def78 | ||
|
|
68ecb7fbdd | ||
|
|
de98a53b43 | ||
|
|
1c9fbf92c6 | ||
|
|
c068b05232 | ||
|
|
15338ecb18 | ||
|
|
01e9a8f720 | ||
|
|
4bdfe64afb | ||
|
|
b7b752b92f | ||
|
|
b7bcd204b4 | ||
|
|
ec934ba3c0 | ||
|
|
7373639f57 | ||
|
|
d718527a1f | ||
|
|
48add0f293 | ||
|
|
a4685229c9 | ||
|
|
f0bc4d26a0 | ||
|
|
1d3b93d88d | ||
|
|
62752ba7e1 | ||
|
|
6a4f420187 | ||
|
|
6640632683 | ||
|
|
09d5d802d0 | ||
|
|
fea23ed6d4 | ||
|
|
10a6c4dc7a | ||
|
|
4cdaa72224 | ||
|
|
27bc1ca5d1 | ||
|
|
1ea49188c9 | ||
|
|
3084ef129c | ||
|
|
c0d112f858 | ||
|
|
2265dda84c | ||
|
|
263b094cab | ||
|
|
fbd13614a5 | ||
|
|
9eab74b595 | ||
|
|
5acdb041a9 | ||
|
|
0494d7ebe3 | ||
|
|
9a8442c946 | ||
|
|
e1dcd0b441 | ||
|
|
a152db7054 | ||
|
|
b9e092674e | ||
|
|
4162b5f41d | ||
|
|
67ae6f210f | ||
|
|
f6c5a46626 | ||
|
|
d6f7e01c53 | ||
|
|
46463e4e24 | ||
|
|
bc99509395 | ||
|
|
5c420f3a34 | ||
|
|
393712ead2 | ||
|
|
d3060b0060 | ||
|
|
14d7f04a81 | ||
|
|
1a28e5e0d4 | ||
|
|
884cd0d636 | ||
|
|
6a7a3c0ae8 | ||
|
|
948e6bd57c | ||
|
|
78595fba0b | ||
|
|
8020284b12 | ||
|
|
d6a49da870 | ||
|
|
84da80356d | ||
|
|
bcbb85eac3 | ||
|
|
0e1d8a72e6 | ||
|
|
bbdd698869 | ||
|
|
bae1e1ee9f | ||
|
|
7138785500 | ||
|
|
8f684ffa6d | ||
|
|
9be3666fe7 | ||
|
|
b7785678f4 | ||
|
|
d8005b4cf6 | ||
|
|
52028fc3bc | ||
|
|
5285ec23ae | ||
|
|
3c882e5c57 | ||
|
|
1a33f9168b | ||
|
|
ccae3d7383 | ||
|
|
847651a90a | ||
|
|
1b8998e7a2 | ||
|
|
dc8fb79759 | ||
|
|
6b0935d6cf | ||
|
|
d1183ce272 | ||
|
|
a1aec8178a | ||
|
|
ad569a8a36 | ||
|
|
0d9fdbaac1 | ||
|
|
f5cd3eab9e | ||
|
|
cb6fe4bb59 | ||
|
|
db36bc67f1 | ||
|
|
e0f72a6193 | ||
|
|
1ee684b7c0 | ||
|
|
93005512b4 | ||
|
|
8987cd64a0 | ||
|
|
fac51dcf03 | ||
|
|
01101a4c9b | ||
|
|
d561e40817 | ||
|
|
b8094fd771 | ||
|
|
af5d9c952d | ||
|
|
ce4e187cbc | ||
|
|
821c80b61e | ||
|
|
5a6fb7c973 | ||
|
|
0cb298ebdf | ||
|
|
0f385f9f4e | ||
|
|
a149368725 | ||
|
|
afeefe8259 | ||
|
|
690d3c27a2 | ||
|
|
3d56ea5ce5 | ||
|
|
fdff7f80a3 | ||
|
|
fe6978b107 | ||
|
|
57db6865d2 | ||
|
|
d235d5ab28 | ||
|
|
c47c15ee47 | ||
|
|
613dfe06d3 | ||
|
|
6803ad2e59 | ||
|
|
d5a791b470 | ||
|
|
a312d61d68 | ||
|
|
e414c1f7b0 | ||
|
|
955359b073 | ||
|
|
26e0c0887a | ||
|
|
4c295b564a | ||
|
|
2eb52da0db | ||
|
|
d8bfb3ab13 | ||
|
|
d970e93507 | ||
|
|
e6255081a8 | ||
|
|
623db0ed94 | ||
|
|
0e575e9c25 | ||
|
|
fb23ba9878 | ||
|
|
4e09fc7f43 | ||
|
|
64cfdd815f | ||
|
|
f6f31e0a8d | ||
|
|
bd5fb9be03 | ||
|
|
762714de68 | ||
|
|
82a3651a18 | ||
|
|
dd9cdb0ec9 | ||
|
|
7f082a821d | ||
|
|
abe0352de9 | ||
|
|
4cee4aa5a8 | ||
|
|
9c68c7c50b | ||
|
|
0608782cfa | ||
|
|
edeaf3794a | ||
|
|
fe2b8c8afa | ||
|
|
b66bf58064 | ||
|
|
957dfa9cdf | ||
|
|
cc9264854e | ||
|
|
d1463b3e24 | ||
|
|
f1082520e1 | ||
|
|
733c563194 | ||
|
|
0200d043c3 | ||
|
|
9c475c36e7 | ||
|
|
c663c5c507 | ||
|
|
1e93c38307 | ||
|
|
81baf808c9 | ||
|
|
74537689dc | ||
|
|
12ab01d5e6 | ||
|
|
044d3a0ff9 | ||
|
|
659cae6a4c | ||
|
|
8efc38ad82 | ||
|
|
bd5882f0f0 | ||
|
|
6ff9ba9df9 | ||
|
|
b2df398a12 | ||
|
|
83d618e1eb | ||
|
|
f0768b3af1 | ||
|
|
0233ce52ed | ||
|
|
6e6f337509 | ||
|
|
1546415b8f | ||
|
|
20725c69bf | ||
|
|
90613220c6 | ||
|
|
659fd2ae93 | ||
|
|
29d899f7da | ||
|
|
902a0a01a9 | ||
|
|
8001fb3915 | ||
|
|
e81e2802f0 | ||
|
|
1ee066ec42 | ||
|
|
53d54d1c4a | ||
|
|
10082b60b8 | ||
|
|
c5b9773922 | ||
|
|
de11323d28 | ||
|
|
9f269e1a95 | ||
|
|
e4204168a0 | ||
|
|
9c350f8ef1 | ||
|
|
db19fdac29 | ||
|
|
d516b238b1 | ||
|
|
f9330f6cd9 | ||
|
|
360da29e1f | ||
|
|
9cfac1642a | ||
|
|
db90e87d10 | ||
|
|
b7564080bc | ||
|
|
1d783bf6c7 | ||
|
|
1025c2e3a1 | ||
|
|
4fd82ab222 | ||
|
|
8eadfc1bf6 | ||
|
|
f66edbad50 | ||
|
|
c7f17b5319 | ||
|
|
23c4adcef6 | ||
|
|
808542bed0 | ||
|
|
93bfd57856 | ||
|
|
7e7e1bccba | ||
|
|
34f6da86c3 | ||
|
|
15c0381c3c | ||
|
|
c2f4a57e02 | ||
|
|
f945cf2343 | ||
|
|
5bca3cfd71 | ||
|
|
26ce4e6886 | ||
|
|
f5f0e0c376 | ||
|
|
9dea1e7f3e | ||
|
|
c2e0f8c81f | ||
|
|
d341bc25ce | ||
|
|
0379e2b51b | ||
|
|
e79026b840 | ||
|
|
fc34d6b56f | ||
|
|
2a1571a99e | ||
|
|
c158608255 | ||
|
|
3ca590b185 | ||
|
|
3f8ee21849 | ||
|
|
845b88a193 | ||
|
|
e252972c7f | ||
|
|
a9012ebfc5 | ||
|
|
5cfd9bbbbd | ||
|
|
c82a7240bb | ||
|
|
a4a20d92a4 | ||
|
|
890996f595 | ||
|
|
474f27c6d3 | ||
|
|
33f3894372 | ||
|
|
24436ac76e | ||
|
|
3ee66ef705 | ||
|
|
a1765e1d33 | ||
|
|
765e3dbf72 | ||
|
|
80f5cee599 | ||
|
|
4dcb124693 | ||
|
|
31ecf167cc | ||
|
|
3999480d64 | ||
|
|
9dbb503c23 | ||
|
|
a98f803d87 | ||
|
|
9e9ffeb5d5 | ||
|
|
33d4ad4d84 | ||
|
|
d05d418c4c | ||
|
|
06d0af7a1d | ||
|
|
9a3b726068 | ||
|
|
2676ab9a59 | ||
|
|
a1837d553e | ||
|
|
fdbc130d8d | ||
|
|
4b3cea3812 | ||
|
|
1c3082ffa6 | ||
|
|
0446cfdba0 | ||
|
|
db1d3183b6 | ||
|
|
fb666394fc | ||
|
|
1054c89a9d | ||
|
|
8dd87dc482 | ||
|
|
b2edbf05a1 | ||
|
|
6fb53a406b | ||
|
|
b05fa0821d | ||
|
|
0a808b1212 | ||
|
|
f1d83e92a7 | ||
|
|
31b60f7f60 | ||
|
|
c0f9af5daa | ||
|
|
b25a9e8884 | ||
|
|
3c0cf3cd55 | ||
|
|
1ac6f17e6a | ||
|
|
399a2b38f3 | ||
|
|
b97221cdb2 | ||
|
|
0164bc21ea | ||
|
|
5a23250d32 | ||
|
|
80d88d9789 | ||
|
|
31ead854c7 | ||
|
|
4b64fcb8a4 | ||
|
|
a951f2403d | ||
|
|
f9adeba7f1 | ||
|
|
5c823d51d0 | ||
|
|
9be7521b83 | ||
|
|
c73ddc3552 | ||
|
|
4b7f058f41 | ||
|
|
07221a1b20 | ||
|
|
13614fb3c4 | ||
|
|
4fa983bde7 | ||
|
|
9cb1db8c0a |
@@ -7,7 +7,6 @@ insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[*.{md,yml,yaml,json,toml,htm,html}]
|
||||
indent_style = space
|
||||
|
||||
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
open_collective: task
|
||||
patreon: andreynering
|
||||
26
.github/workflows/release.yml
vendored
Normal file
26
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.14.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
30
.github/workflows/test.yml
vendored
Normal file
30
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Test
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.13.x, 1.14.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
- name: Set up Go ${{matrix.go-version}}
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
|
||||
- name: Build
|
||||
run: go build -o ./bin/task -v ./cmd/task
|
||||
|
||||
- name: Test
|
||||
run: ./bin/task test
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -18,3 +18,12 @@
|
||||
dist/
|
||||
|
||||
.DS_Store
|
||||
|
||||
# intellij idea/goland
|
||||
.idea/
|
||||
|
||||
# exuberant ctags
|
||||
tags
|
||||
|
||||
/bin
|
||||
/testdata/vars/v1
|
||||
|
||||
@@ -14,12 +14,11 @@ build:
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
|
||||
archive:
|
||||
name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
archives:
|
||||
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
release:
|
||||
draft: true
|
||||
@@ -30,15 +29,15 @@ snapshot:
|
||||
checksum:
|
||||
name_template: "task_checksums.txt"
|
||||
|
||||
nfpm:
|
||||
vendor: Task
|
||||
homepage: https://github.com/go-task/task
|
||||
maintainer: Andrey Nering <andrey.nering@gmail.com>
|
||||
description: Simple task runner written in Go
|
||||
license: MIT
|
||||
conflicts:
|
||||
- taskwarrior
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||
nfpms:
|
||||
- vendor: Task
|
||||
homepage: https://github.com/go-task/task
|
||||
maintainer: Andrey Nering <andrey.nering@gmail.com>
|
||||
description: Simple task runner written in Go
|
||||
license: MIT
|
||||
conflicts:
|
||||
- taskwarrior
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||
|
||||
22
.travis.yml
22
.travis.yml
@@ -1,22 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- rpm
|
||||
|
||||
script:
|
||||
- go install github.com/go-task/task/cmd/task
|
||||
- task ci
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: curl -sL http://git.io/goreleaser | bash
|
||||
on:
|
||||
tags: true
|
||||
condition: $TRAVIS_OS_NAME = linux
|
||||
117
CHANGELOG.md
117
CHANGELOG.md
@@ -1,5 +1,122 @@
|
||||
# Changelog
|
||||
|
||||
# v3.0.0 - Preview 4
|
||||
|
||||
- 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.
|
||||
|
||||
# v3.0.0 - Preview 3
|
||||
|
||||
- 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))
|
||||
|
||||
# v3.0.0 - Preview 2
|
||||
|
||||
- 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)).
|
||||
|
||||
## v3.0.0 - Preview 1
|
||||
|
||||
- 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
|
||||
([#300](https://github.com/go-task/task/issues/300), [#330](https://github.com/go-task/task/pull/330)).
|
||||
- Print version to stdout instead of stderr
|
||||
([#299](https://github.com/go-task/task/issues/299), [#329](https://github.com/go-task/task/pull/329)).
|
||||
- Supress `context` errors when using the `--watch` flag
|
||||
([#313](https://github.com/go-task/task/issues/313), [#317](https://github.com/go-task/task/pull/317)).
|
||||
- Support templating on description
|
||||
([#276](https://github.com/go-task/task/issues/276), [#283](https://github.com/go-task/task/pull/283)).
|
||||
|
||||
## v2.8.0 - 2019-12-07
|
||||
|
||||
- Add `--parallel` flag (alias `-p`) to run tasks given by the command line in
|
||||
parallel
|
||||
([#266](https://github.com/go-task/task/pull/266)).
|
||||
- Fixed bug where calling the `task` CLI only informing global vars would not
|
||||
execute the `default` task.
|
||||
- Add hability to silent all tasks by adding `silent: true` a the root of the
|
||||
Taskfile.
|
||||
|
||||
## v2.7.1 - 2019-11-10
|
||||
|
||||
- Fix error being raised when `exit 0` was called
|
||||
([#251](https://github.com/go-task/task/issues/251)).
|
||||
|
||||
## v2.7.0 - 2019-09-22
|
||||
|
||||
- Fixed panic bug when assigning a global variable
|
||||
([#229](https://github.com/go-task/task/issues/229), [#243](https://github.com/go-task/task/issues/234)).
|
||||
- A task with `method: checksum` will now re-run if generated files are deleted
|
||||
([#228](https://github.com/go-task/task/pull/228), [#238](https://github.com/go-task/task/issues/238)).
|
||||
|
||||
## v2.6.0 - 2019-07-21
|
||||
|
||||
- Fixed some bugs regarding minor version checks on `version:`.
|
||||
- Add `preconditions:` to task
|
||||
([#205](https://github.com/go-task/task/pull/205)).
|
||||
- Create directory informed on `dir:` if it doesn't exist
|
||||
([#209](https://github.com/go-task/task/issues/209), [#211](https://github.com/go-task/task/pull/211)).
|
||||
- We now have a `--taskfile` flag (alias `-t`), which can be used to run
|
||||
another Taskfile (other than the default `Taskfile.yml`)
|
||||
([#221](https://github.com/go-task/task/pull/221)).
|
||||
- It's now possible to install Task using Homebrew on Linux
|
||||
([go-task/homebrew-tap#1](https://github.com/go-task/homebrew-tap/pull/1)).
|
||||
|
||||
## v2.5.2 - 2019-05-11
|
||||
|
||||
- Reverted YAML upgrade due issues with CRLF on Windows
|
||||
([#201](https://github.com/go-task/task/issues/201), [go-yaml/yaml#450](https://github.com/go-yaml/yaml/issues/450)).
|
||||
- Allow setting global variables through the CLI
|
||||
([#192](https://github.com/go-task/task/issues/192)).
|
||||
|
||||
## 2.5.1 - 2019-04-27
|
||||
|
||||
- Fixed some issues with interactive command line tools, where sometimes
|
||||
the output were not being shown, and similar issues
|
||||
([#114](https://github.com/go-task/task/issues/114), [#190](https://github.com/go-task/task/issues/190), [#200](https://github.com/go-task/task/pull/200)).
|
||||
- Upgraded [go-yaml/yaml](https://github.com/go-yaml/yaml) from v2 to v3.
|
||||
|
||||
## v2.5.0 - 2019-03-16
|
||||
|
||||
- We moved from the taskfile.org domain to the new fancy taskfile.dev domain.
|
||||
While stuff is being redirected, we strongly recommend to everyone that use
|
||||
[this install script](https://taskfile.dev/#/installation?id=install-script)
|
||||
to use the new taskfile.dev domain on scripts from now on.
|
||||
- Fixed to the ZSH completion
|
||||
([#182](https://github.com/go-task/task/pull/182)).
|
||||
- Add [`--summary` flag along with `summary:` task attribute](https://taskfile.org/#/usage?id=display-summary-of-task)
|
||||
([#180](https://github.com/go-task/task/pull/180)).
|
||||
|
||||
## v2.4.0 - 2019-02-21
|
||||
|
||||
- Allow calling a task of the root Taskfile from an included Taskfile
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
[](https://travis-ci.org/go-task/task)
|
||||

|
||||

|
||||
|
||||
# Task
|
||||
|
||||
Task is a task runner / build tool that aims to be simpler and easier to use
|
||||
than, for example, [GNU Make](https://www.gnu.org/software/make/).
|
||||
|
||||
See [taskfile.org](https://taskfile.org) for documentation.
|
||||
See [taskfile.dev](https://taskfile.dev) for documentation.
|
||||
|
||||
---
|
||||
|
||||
|
||||
19
Taskfile.yml
19
Taskfile.yml
@@ -1,7 +1,9 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
docs: ./docs
|
||||
docs:
|
||||
taskfile: ./docs
|
||||
dir: ./docs
|
||||
|
||||
vars:
|
||||
GIT_COMMIT:
|
||||
@@ -72,15 +74,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:
|
||||
|
||||
@@ -2,13 +2,16 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-task/task/v2"
|
||||
"github.com/go-task/task/v2/internal/args"
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -17,7 +20,7 @@ var (
|
||||
version = "master"
|
||||
)
|
||||
|
||||
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--dry] [task...]
|
||||
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]
|
||||
|
||||
Runs the specified task(s). Falls back to the "default" task if no task name
|
||||
was specified, or lists all tasks if an unknown task name was specified.
|
||||
@@ -26,12 +29,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:
|
||||
@@ -48,6 +53,7 @@ func main() {
|
||||
|
||||
var (
|
||||
versionFlag bool
|
||||
helpFlag bool
|
||||
init bool
|
||||
list bool
|
||||
status bool
|
||||
@@ -56,11 +62,16 @@ func main() {
|
||||
verbose bool
|
||||
silent bool
|
||||
dry bool
|
||||
summary bool
|
||||
parallel bool
|
||||
dir string
|
||||
entrypoint string
|
||||
output string
|
||||
color bool
|
||||
)
|
||||
|
||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||
pflag.BoolVarP(&helpFlag, "help", "h", false, "shows Task usage")
|
||||
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
|
||||
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
|
||||
pflag.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date")
|
||||
@@ -68,13 +79,22 @@ func main() {
|
||||
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
|
||||
pflag.BoolVarP(&verbose, "verbose", "v", false, "enables verbose mode")
|
||||
pflag.BoolVarP(&silent, "silent", "s", false, "disables echoing")
|
||||
pflag.BoolVarP(¶llel, "parallel", "p", false, "executes tasks provided on command line in parallel")
|
||||
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
|
||||
pflag.BoolVar(&summary, "summary", false, "show summary about a task")
|
||||
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 {
|
||||
log.Printf("Task version: %s\n", version)
|
||||
fmt.Printf("Task version: %s\n", version)
|
||||
return
|
||||
}
|
||||
|
||||
if helpFlag {
|
||||
pflag.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -89,13 +109,28 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if dir != "" && entrypoint != "" {
|
||||
log.Fatal("task: You can't set both --dir and --taskfile")
|
||||
return
|
||||
}
|
||||
if entrypoint != "" {
|
||||
dir = filepath.Dir(entrypoint)
|
||||
entrypoint = filepath.Base(entrypoint)
|
||||
} else {
|
||||
entrypoint = "Taskfile.yml"
|
||||
}
|
||||
|
||||
e := task.Executor{
|
||||
Force: force,
|
||||
Watch: watch,
|
||||
Verbose: verbose,
|
||||
Silent: silent,
|
||||
Dir: dir,
|
||||
Dry: dry,
|
||||
Force: force,
|
||||
Watch: watch,
|
||||
Verbose: verbose,
|
||||
Silent: silent,
|
||||
Dir: dir,
|
||||
Dry: dry,
|
||||
Entrypoint: entrypoint,
|
||||
Summary: summary,
|
||||
Parallel: parallel,
|
||||
Color: color,
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
@@ -112,16 +147,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
arguments := pflag.Args()
|
||||
if len(arguments) == 0 {
|
||||
log.Println("task: No argument given, trying default task")
|
||||
arguments = []string{"default"}
|
||||
}
|
||||
|
||||
calls, err := args.Parse(arguments...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
calls, globals := args.Parse(pflag.Args()...)
|
||||
e.Taskfile.Vars.Merge(globals)
|
||||
|
||||
ctx := context.Background()
|
||||
if !watch {
|
||||
@@ -129,14 +156,15 @@ func main() {
|
||||
}
|
||||
|
||||
if status {
|
||||
if err = e.Status(ctx, calls...); err != nil {
|
||||
if err := e.Status(ctx, calls...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.Run(ctx, calls...); err != nil {
|
||||
log.Fatal(err)
|
||||
e.Logger.Errf(logger.Red, "%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
completion/bash/task.bash
Normal file
21
completion/bash/task.bash
Normal file
@@ -0,0 +1,21 @@
|
||||
_task_completion()
|
||||
{
|
||||
local scripts;
|
||||
local curr_arg;
|
||||
|
||||
# Remove colon from word breaks
|
||||
COMP_WORDBREAKS=${COMP_WORDBREAKS//:}
|
||||
|
||||
scripts=$(task -l | sed '1d' | awk '{ print $2 }' | sed 's/:$//');
|
||||
|
||||
curr_arg="${COMP_WORDS[COMP_CWORD]:-"."}"
|
||||
|
||||
# Do not accept more than 1 argument
|
||||
if [ "${#COMP_WORDS[@]}" != "2" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -c | echo "$scripts" | grep $curr_arg));
|
||||
}
|
||||
|
||||
complete -F _task_completion task
|
||||
10
completion/ps/task.ps1
Normal file
10
completion/ps/task.ps1
Normal file
@@ -0,0 +1,10 @@
|
||||
$scriptBlock = {
|
||||
param($commandName, $wordToComplete, $cursorPosition)
|
||||
$curReg = "task{.exe}? (.*?)$"
|
||||
$startsWith = $wordToComplete | Select-String $curReg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||
$reg = "\* ($startsWith.+?):"
|
||||
$listOutput = $(task -l)
|
||||
$listOutput | Select-String $reg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value + " " }
|
||||
}
|
||||
|
||||
Register-ArgumentCompleter -Native -CommandName task -ScriptBlock $scriptBlock
|
||||
2
completion/zsh/_task
Normal file → Executable file
2
completion/zsh/_task
Normal file → Executable file
@@ -5,7 +5,7 @@ function __list() {
|
||||
local -a scripts
|
||||
|
||||
if [ -f Taskfile.yml ]; then
|
||||
scripts=($(task -l | sed '1d' | sed 's/://' | awk '{ print $2 }'))
|
||||
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/'))
|
||||
_describe 'script' scripts
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
taskfile.org
|
||||
taskfile.dev
|
||||
@@ -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 .
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
- [Installation](installation.md)
|
||||
- [Usage](usage.md)
|
||||
- [Styleguide](styleguide.md)
|
||||
- [Taskfile Versions](taskfile_versions.md)
|
||||
- [Examples](examples.md)
|
||||
- [Releasing Task](releasing_task.md)
|
||||
- [Alternative Task Runners](alternative_task_runners.md)
|
||||
- [Github](https://github.com/go-task/task)
|
||||
- [GitHub](https://github.com/go-task/task)
|
||||
|
||||
@@ -8,13 +8,15 @@ The `task_checksums.txt` file contains the sha256 checksum for each file.
|
||||
|
||||
## Homebrew
|
||||
|
||||
If you're on macOS and have [Homebrew][homebrew] installed, getting Task is
|
||||
as simple as running:
|
||||
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
|
||||
Task is as simple as running:
|
||||
|
||||
```bash
|
||||
brew install go-task/tap/go-task
|
||||
```
|
||||
|
||||
> This installation method is only currently supported on amd64 architectures.
|
||||
|
||||
## Snap
|
||||
|
||||
Task is available for [Snapcraft][snapcraft], but keep in mind that your
|
||||
@@ -38,21 +40,29 @@ scoop install task
|
||||
This installation method is community owned. After a new release of Task, it
|
||||
may take some time until it's available on Scoop.
|
||||
|
||||
## Arch Linux
|
||||
|
||||
If you're on Arch Linux you can install Task from
|
||||
[AUR](https://aur.archlinux.org/packages/taskfile-git) using your favorite
|
||||
package manager such as `yay`, `pacaur` or `yaourt`:
|
||||
|
||||
```cmd
|
||||
yay -S taskfile-git
|
||||
```
|
||||
|
||||
This installation method is community owned, but since it's `-git` version of
|
||||
the package, it's always latest available version based on the Git repository.
|
||||
|
||||
## Go
|
||||
|
||||
Task now uses [Go Modules](https://github.com/golang/go/wiki/Modules), which
|
||||
means you may have trouble compiling it on older Go versions.
|
||||
means you may have trouble compiling it on older Go versions or using
|
||||
`$GOPATH`.
|
||||
|
||||
For CI environments we recommend using the [Install Script](#install-script)
|
||||
instead, which is faster and more stable, since it'll just download the latest
|
||||
released binary, instead of compiling the edge (master branch) version.
|
||||
|
||||
Installing in your `$GOPATH`:
|
||||
|
||||
```bash
|
||||
go get -u -v github.com/go-task/task/cmd/task
|
||||
```
|
||||
|
||||
Installing in another directory:
|
||||
|
||||
```bash
|
||||
@@ -60,7 +70,7 @@ git clone https://github.com/go-task/task
|
||||
cd task
|
||||
|
||||
# compiling binary to $GOPATH/bin
|
||||
go install -v
|
||||
go install -v ./cmd/task
|
||||
|
||||
# compiling it to another location
|
||||
# use -o ./task.exe on Windows
|
||||
@@ -76,11 +86,24 @@ scenarios like CIs. Many thanks to [godownloader][godownloader] for allowing
|
||||
easily generating this script.
|
||||
|
||||
```bash
|
||||
curl -s https://taskfile.org/install.sh | sh
|
||||
curl -sL https://taskfile.dev/install.sh | sh
|
||||
```
|
||||
|
||||
> This method will download the binary on the local `./bin` directory by default.
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
If you want to install Task in GitHub Actions you can try using
|
||||
[this action](https://github.com/arduino/actions/tree/master/setup-taskfile)
|
||||
by the Arduino team:
|
||||
|
||||
```yaml
|
||||
- name: Install Task
|
||||
uses: Arduino/actions/setup-taskfile@master
|
||||
```
|
||||
|
||||
This installation method is community owned.
|
||||
|
||||
[go]: https://golang.org/
|
||||
[snapcraft]: https://snapcraft.io/
|
||||
[homebrew]: https://brew.sh/
|
||||
|
||||
@@ -4,9 +4,9 @@ The release process of Task is done with the help of
|
||||
[GoReleaser][goreleaser]. You can test the release process locally by calling
|
||||
the `test-release` task of the Taskfile.
|
||||
|
||||
The Travis CI should release automatically when a new
|
||||
Git tag is pushed to master, either for the artifact uploading (raw executables
|
||||
and DEB and RPM packages)
|
||||
[GitHub Actions](https://github.com/go-task/task/actions) should release
|
||||
artifacts automatically when a new Git tag is pushed to master
|
||||
(raw executables and DEB and RPM packages).
|
||||
|
||||
# Homebrew
|
||||
|
||||
@@ -28,7 +28,7 @@ the [Snapcraft dashboard][snapcraftdashboard]
|
||||
|
||||
Scoop is a community owned installation method. Scoop owners usually take care
|
||||
of updating versions there by editing
|
||||
[this file](https://github.com/lukesampson/scoop-extras/blob/master/task.json).
|
||||
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
||||
If you think its Task version is outdated, open an issue to let us know.
|
||||
|
||||
[goreleaser]: https://goreleaser.com/#continuous_integration
|
||||
|
||||
213
docs/styleguide.md
Normal file
213
docs/styleguide.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Styleguide
|
||||
|
||||
This is the official Task styleguide for `Taskfile.yml` files. This guide
|
||||
contains some basic instructions to keep your Taskfile clean and familiar to
|
||||
other users.
|
||||
|
||||
This contains general guidelines, but don't necessarely need to be strictly
|
||||
followed. Feel free to disagree and proceed differently in some point if you
|
||||
need or want to. Also, feel free to open issues or pull requests with
|
||||
improvements to this guide.
|
||||
|
||||
## Use `Taskfile.yml` and not `taskfile.yml`
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
taskfile.yml
|
||||
|
||||
|
||||
# good
|
||||
Taskfile.yml
|
||||
```
|
||||
|
||||
This is important especially for Linux users. Windows and macOS have case
|
||||
insensitive filesystems, so `taskfile.yml` will end up working, even that not
|
||||
officially supported. On Linux, only `Taskfile.yml` will work, though.
|
||||
|
||||
## Use the correct order of keywords
|
||||
|
||||
- `version:`
|
||||
- `includes:`
|
||||
- Configuration ones, like `output:`, `expansions:` or `silent:`
|
||||
- `vars:`
|
||||
- `env:`
|
||||
- `tasks:`
|
||||
|
||||
## Use 2 spaces for indentation
|
||||
|
||||
This is the most common convention for YAML files, and Task follows it.
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
tasks:
|
||||
foo:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
|
||||
|
||||
# good
|
||||
tasks:
|
||||
foo:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
```
|
||||
|
||||
## Separate with spaces the mains sections
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: 2
|
||||
includes:
|
||||
docker: ./docker/Taskfile.yml
|
||||
output: prefixed
|
||||
expansions: 3
|
||||
vars:
|
||||
FOO: bar
|
||||
env:
|
||||
BAR: baz
|
||||
tasks:
|
||||
# ...
|
||||
|
||||
|
||||
# good
|
||||
version: 2
|
||||
|
||||
includes:
|
||||
docker: ./docker/Taskfile.yml
|
||||
|
||||
output: prefixed
|
||||
expansions: 3
|
||||
|
||||
vars:
|
||||
FOO: bar
|
||||
|
||||
env:
|
||||
BAR: baz
|
||||
|
||||
tasks:
|
||||
# ...
|
||||
```
|
||||
|
||||
## Add spaces between tasks
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: 2
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
bar:
|
||||
cmds:
|
||||
- echo 'bar'
|
||||
baz:
|
||||
cmds:
|
||||
- echo 'baz'
|
||||
|
||||
|
||||
# good
|
||||
version: 2
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
|
||||
bar:
|
||||
cmds:
|
||||
- echo 'bar'
|
||||
|
||||
baz:
|
||||
cmds:
|
||||
- echo 'baz'
|
||||
```
|
||||
|
||||
## Use upper-case variable names
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: 2
|
||||
|
||||
vars:
|
||||
binary_name: myapp
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build -o {{.binary_name}} .
|
||||
|
||||
|
||||
# good
|
||||
version: 2
|
||||
|
||||
vars:
|
||||
BINARY_NAME: myapp
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build -o {{.BINARY_NAME}} .
|
||||
```
|
||||
|
||||
## Don't wrap vars in spaces when templating
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: 2
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo '{{ .MESSAGE }}'
|
||||
|
||||
|
||||
# good
|
||||
version: 2
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo '{{.MESSAGE}}'
|
||||
```
|
||||
|
||||
This convention is also used by most people for any Go templating.
|
||||
|
||||
## Separate task name words with a dash
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: 2
|
||||
|
||||
tasks:
|
||||
do_something_fancy:
|
||||
cmds:
|
||||
- echo 'Do something'
|
||||
|
||||
|
||||
# good
|
||||
version: 2
|
||||
|
||||
tasks:
|
||||
do-something-fancy:
|
||||
cmds:
|
||||
- echo 'Do something'
|
||||
```
|
||||
|
||||
## Use colon for task namespacing
|
||||
|
||||
```yaml
|
||||
# good
|
||||
version: 2
|
||||
|
||||
tasks:
|
||||
docker:build:
|
||||
cmds:
|
||||
- docker ...
|
||||
|
||||
docker:run:
|
||||
cmds:
|
||||
- docker-compose ...
|
||||
```
|
||||
|
||||
This is also done automatically when using included Taskfiles.
|
||||
@@ -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:
|
||||
|
||||
@@ -141,6 +143,21 @@ includes:
|
||||
docker: ./DockerTasks.yml
|
||||
```
|
||||
|
||||
## Version 2.6
|
||||
|
||||
Version 2.6 comes with `preconditions` stanza in tasks.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
upload_environment:
|
||||
preconditions:
|
||||
- test -f .env
|
||||
cmds:
|
||||
- aws s3 cp .env s3://myenvironment
|
||||
```
|
||||
|
||||
Please check the [documentation][includes]
|
||||
|
||||
[output]: usage.md#output-syntax
|
||||
|
||||
184
docs/usage.md
184
docs/usage.md
@@ -63,7 +63,7 @@ tasks:
|
||||
- echo $GREETING
|
||||
```
|
||||
|
||||
> NOTE: `env` supports expansion and and retrieving output from a shell command
|
||||
> NOTE: `env` supports expansion and retrieving output from a shell command
|
||||
> just like variables, as you can see on the [Variables](#variables) section.
|
||||
|
||||
## Operating System specific tasks
|
||||
@@ -124,6 +124,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.
|
||||
|
||||
@@ -148,6 +163,8 @@ tasks:
|
||||
- caddy
|
||||
```
|
||||
|
||||
If the directory doesn't exist, `task` creates it.
|
||||
|
||||
## Task dependencies
|
||||
|
||||
You may have tasks that depend on others. Just pointing them on `deps` will
|
||||
@@ -191,6 +208,9 @@ tasks:
|
||||
If there is more than one dependency, they always run in parallel for better
|
||||
performance.
|
||||
|
||||
> You can also make the tasks given by the command line run in parallel by
|
||||
> using the `--parallel` flag (alias `-p`). Example: `task --parallel js css`.
|
||||
|
||||
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):
|
||||
|
||||
@@ -264,6 +284,8 @@ 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.
|
||||
|
||||
@@ -319,6 +341,8 @@ 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:
|
||||
|
||||
@@ -338,18 +362,86 @@ 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.
|
||||
|
||||
### 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'
|
||||
|
||||
tasks:
|
||||
generate-files:
|
||||
cmds:
|
||||
- mkdir directory
|
||||
- touch directory/file1.txt
|
||||
- touch directory/file2.txt
|
||||
# test existence of files
|
||||
preconditions:
|
||||
- test -f .env
|
||||
- sh: "[ 1 = 0 ]"
|
||||
msg: "One doesn't equal Zero, Halting"
|
||||
```
|
||||
|
||||
Preconditions can set specific failure messages that can tell
|
||||
a user what steps to take using the `msg` field.
|
||||
|
||||
If a task has a dependency on a sub-task with a precondition, and that
|
||||
precondition is not met - the calling task will fail. Note that a task
|
||||
executed with a failing precondition will not run unless `--force` is
|
||||
given.
|
||||
|
||||
Unlike `status` which will skip a task if it is up to date, and continue
|
||||
executing tasks that depend on it, a `precondition` will fail a task, along
|
||||
with any other tasks that depend on it.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
tasks:
|
||||
task_will_fail:
|
||||
preconditions:
|
||||
- sh: "exit 1"
|
||||
|
||||
task_will_also_fail:
|
||||
deps:
|
||||
- task_will_fail
|
||||
|
||||
task_will_still_fail:
|
||||
cmds:
|
||||
- task: task_will_fail
|
||||
- echo "I will not run"
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
When doing interpolation of variables, Task will look for the below.
|
||||
They are listed below in order of importance (e.g. most important first):
|
||||
|
||||
- Variables declared locally in the task
|
||||
- Variables declared in the task definition
|
||||
- Variables given while calling a task from another.
|
||||
(See [Calling another task](#calling-another-task) above)
|
||||
- Variables declared in the `vars:` option in the `Taskfile`
|
||||
@@ -362,6 +454,8 @@ 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
|
||||
@@ -371,6 +465,12 @@ right before.
|
||||
$ 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
|
||||
@@ -379,7 +479,7 @@ version: '2'
|
||||
tasks:
|
||||
print-var:
|
||||
cmds:
|
||||
echo "{{.VAR}}"
|
||||
- echo "{{.VAR}}"
|
||||
vars:
|
||||
VAR: Hello!
|
||||
```
|
||||
@@ -455,7 +555,7 @@ 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
|
||||
@@ -542,6 +642,51 @@ would print the following output:
|
||||
* test: Run all the go tests.
|
||||
```
|
||||
|
||||
## Display summary of task
|
||||
|
||||
Running `task --summary task-name` will show a summary of a task
|
||||
The following Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
release:
|
||||
deps: [build]
|
||||
summary: |
|
||||
Release your project to github
|
||||
|
||||
It will build your project before starting the release it.
|
||||
Please make sure that you have set GITHUB_TOKEN before starting.
|
||||
cmds:
|
||||
- your-release-tool
|
||||
|
||||
build:
|
||||
cmds:
|
||||
- your-build-tool
|
||||
```
|
||||
|
||||
with running ``task --summary release`` would print the following output:
|
||||
|
||||
```
|
||||
task: release
|
||||
|
||||
Release your project to github
|
||||
|
||||
It will build your project before starting the release it.
|
||||
Please make sure that you have set GITHUB_TOKEN before starting.
|
||||
|
||||
dependencies:
|
||||
- build
|
||||
|
||||
commands:
|
||||
- your-release-tool
|
||||
```
|
||||
If a summary is missing, the description will be printed.
|
||||
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*.
|
||||
|
||||
## Silent mode
|
||||
|
||||
Silent mode disables echoing of commands before Task runs it.
|
||||
@@ -569,7 +714,7 @@ With silent mode on, the below will be print instead:
|
||||
Print something
|
||||
```
|
||||
|
||||
There's three ways to enable silent mode:
|
||||
There are four ways to enable silent mode:
|
||||
|
||||
* At command level:
|
||||
|
||||
@@ -595,6 +740,19 @@ tasks:
|
||||
silent: true
|
||||
```
|
||||
|
||||
* Globally at Taskfile level:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
silent: true
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Print something"
|
||||
```
|
||||
|
||||
* Or globally with `--silent` or `-s` flag
|
||||
|
||||
If you want to suppress STDOUT instead, just redirect a command to `/dev/null`:
|
||||
@@ -710,6 +868,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
|
||||
|
||||
25
go.mod
25
go.mod
@@ -1,23 +1,16 @@
|
||||
module github.com/go-task/task/v2
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.4.2
|
||||
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/kr/pretty v0.1.0 // indirect
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-zglob v0.0.1
|
||||
github.com/mitchellh/go-homedir v1.0.0
|
||||
github.com/radovskyb/watcher v1.0.5
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/stretchr/testify v1.3.0
|
||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 // indirect
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
mvdan.cc/sh v2.6.3+incompatible
|
||||
github.com/stretchr/testify v1.5.1
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
mvdan.cc/sh/v3 v3.1.1
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
||||
75
go.sum
75
go.sum
@@ -1,47 +1,66 @@
|
||||
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/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/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/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=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/pkg/diff v0.0.0-20190930165518-531926345625/go.mod h1:kFj35MyHn14a6pIgWhm46KVjJr5CHys3eEYxkuKD1EI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/radovskyb/watcher v1.0.5 h1:wqt7gb+HjGacvFoLTKeT44C+XVPxu7bvHvKT1IvZ7rw=
|
||||
github.com/radovskyb/watcher v1.0.5/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg=
|
||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 h1:T8D7l6WB3tLu+VpKvw06ieD/OhBi1XpJmG1U/FtttZg=
|
||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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-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/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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
mvdan.cc/sh v2.6.3+incompatible h1:uXnnFNSBQbKUwwh2iBSkVjG+GbwoMuI+UmBVPnNiWhA=
|
||||
mvdan.cc/sh v2.6.3+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
|
||||
9
help.go
9
help.go
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
@@ -12,10 +13,10 @@ import (
|
||||
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)
|
||||
@@ -29,6 +30,10 @@ func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
|
||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.Desc != "" {
|
||||
compiledTask, err := e.CompiledTask(taskfile.Call{Task: task.Task})
|
||||
if err == nil {
|
||||
task = compiledTask
|
||||
}
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
}
|
||||
|
||||
4
init.go
4
init.go
@@ -8,9 +8,9 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const defaultTaskfile = `# https://taskfile.org
|
||||
const defaultTaskfile = `# https://taskfile.dev
|
||||
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
|
||||
@@ -1,36 +1,45 @@
|
||||
package args
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrVariableWithoutTask is returned when variables are given before any task
|
||||
ErrVariableWithoutTask = errors.New("task: variable given before any task")
|
||||
)
|
||||
|
||||
// Parse parses command line argument: tasks and vars of each task
|
||||
func Parse(args ...string) ([]taskfile.Call, error) {
|
||||
func Parse(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
||||
var calls []taskfile.Call
|
||||
var globals *taskfile.Vars
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.Contains(arg, "=") {
|
||||
calls = append(calls, taskfile.Call{Task: arg})
|
||||
continue
|
||||
}
|
||||
|
||||
if len(calls) < 1 {
|
||||
return nil, ErrVariableWithoutTask
|
||||
if globals == nil {
|
||||
globals = &taskfile.Vars{}
|
||||
}
|
||||
name, value := splitVar(arg)
|
||||
globals.Set(name, taskfile.Var{Static: value})
|
||||
} else {
|
||||
if calls[len(calls)-1].Vars == nil {
|
||||
calls[len(calls)-1].Vars = &taskfile.Vars{}
|
||||
}
|
||||
name, value := splitVar(arg)
|
||||
calls[len(calls)-1].Vars.Set(name, taskfile.Var{Static: value})
|
||||
}
|
||||
|
||||
if calls[len(calls)-1].Vars == nil {
|
||||
calls[len(calls)-1].Vars = make(taskfile.Vars)
|
||||
}
|
||||
|
||||
pair := strings.SplitN(arg, "=", 2)
|
||||
calls[len(calls)-1].Vars[pair[0]] = taskfile.Var{Static: pair[1]}
|
||||
}
|
||||
return calls, nil
|
||||
|
||||
if len(calls) == 0 {
|
||||
calls = append(calls, taskfile.Call{Task: "default"})
|
||||
}
|
||||
|
||||
return calls, globals
|
||||
}
|
||||
|
||||
func splitVar(s string) (string, string) {
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
return pair[0], pair[1]
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ import (
|
||||
|
||||
func TestArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
Args []string
|
||||
Expected []taskfile.Call
|
||||
Err error
|
||||
Args []string
|
||||
ExpectedCalls []taskfile.Call
|
||||
ExpectedGlobals *taskfile.Vars
|
||||
}{
|
||||
{
|
||||
Args: []string{"task-a", "task-b", "task-c"},
|
||||
Expected: []taskfile.Call{
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
@@ -26,45 +26,88 @@ func TestArgs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||
Expected: []taskfile.Call{
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{
|
||||
Task: "task-a",
|
||||
Vars: taskfile.Vars{
|
||||
"FOO": taskfile.Var{Static: "bar"},
|
||||
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{
|
||||
"BAR": taskfile.Var{Static: "baz"},
|
||||
"BAZ": taskfile.Var{Static: "foo"},
|
||||
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"},
|
||||
Expected: []taskfile.Call{
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{
|
||||
Task: "task-a",
|
||||
Vars: taskfile.Vars{
|
||||
"CONTENT": taskfile.Var{Static: "with some spaces"},
|
||||
Vars: &taskfile.Vars{
|
||||
Keys: []string{"CONTENT"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"CONTENT": taskfile.Var{Static: "with some spaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"FOO=bar", "task-a"},
|
||||
Err: args.ErrVariableWithoutTask,
|
||||
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, err := args.Parse(test.Args...)
|
||||
assert.Equal(t, test.Err, err)
|
||||
assert.Equal(t, test.Expected, calls)
|
||||
calls, globals := args.Parse(test.Args...)
|
||||
assert.Equal(t, test.ExpectedCalls, calls)
|
||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ import (
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -9,16 +9,12 @@ import (
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
98
internal/compiler/v3/compiler_v3.go
Normal file
98
internal/compiler/v3/compiler_v3.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package v3
|
||||
|
||||
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 = &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
|
||||
}
|
||||
13
internal/execext/devnull.go
Normal file
13
internal/execext/devnull.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package execext
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var _ io.ReadWriteCloser = devNull{}
|
||||
|
||||
type devNull struct{}
|
||||
|
||||
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
|
||||
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
|
||||
func (devNull) Close() error { return nil }
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/sh/expand"
|
||||
"mvdan.cc/sh/interp"
|
||||
"mvdan.cc/sh/shell"
|
||||
"mvdan.cc/sh/syntax"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
// RunCommandOptions is the options for the RunCommand func
|
||||
@@ -49,8 +49,12 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
interp.Dir(opts.Dir),
|
||||
interp.Env(expand.ListEnviron(environ...)),
|
||||
|
||||
interp.Module(interp.DefaultExec),
|
||||
interp.Module(interp.OpenDevImpls(interp.DefaultOpen)),
|
||||
interp.OpenHandler(func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
if path == "/dev/null" {
|
||||
return devNull{}, nil
|
||||
}
|
||||
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
|
||||
}),
|
||||
|
||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||
)
|
||||
@@ -62,12 +66,10 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
|
||||
// IsExitError returns true the given error is an exis status error
|
||||
func IsExitError(err error) bool {
|
||||
switch err.(type) {
|
||||
case interp.ExitStatus, interp.ShellExitStatus:
|
||||
if _, ok := interp.IsExitStatus(err); ok {
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
type Group struct{}
|
||||
|
||||
func (Group) WrapWriter(w io.Writer, _ string) io.WriteCloser {
|
||||
func (Group) WrapWriter(w io.Writer, _ string) io.Writer {
|
||||
return &groupWriter{writer: w}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,6 @@ import (
|
||||
|
||||
type Interleaved struct{}
|
||||
|
||||
func (Interleaved) WrapWriter(w io.Writer, _ string) io.WriteCloser {
|
||||
return nopWriterCloser{w: w}
|
||||
}
|
||||
|
||||
type nopWriterCloser struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (wc nopWriterCloser) Write(p []byte) (int, error) {
|
||||
return wc.w.Write(p)
|
||||
}
|
||||
|
||||
func (wc nopWriterCloser) Close() error {
|
||||
return nil
|
||||
func (Interleaved) WrapWriter(w io.Writer, _ string) io.Writer {
|
||||
return w
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ import (
|
||||
)
|
||||
|
||||
type Output interface {
|
||||
WrapWriter(w io.Writer, prefix string) io.WriteCloser
|
||||
WrapWriter(w io.Writer, prefix string) io.Writer
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package output_test
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2/internal/output"
|
||||
@@ -24,7 +25,7 @@ func TestInterleaved(t *testing.T) {
|
||||
func TestGroup(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Group{}
|
||||
var w = o.WrapWriter(&b, "")
|
||||
var w = o.WrapWriter(&b, "").(io.WriteCloser)
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "", b.String())
|
||||
@@ -37,7 +38,7 @@ func TestGroup(t *testing.T) {
|
||||
func TestPrefixed(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Prefixed{}
|
||||
var w = o.WrapWriter(&b, "prefix")
|
||||
var w = o.WrapWriter(&b, "prefix").(io.WriteCloser)
|
||||
|
||||
t.Run("simple use cases", func(t *testing.T) {
|
||||
b.Reset()
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
type Prefixed struct{}
|
||||
|
||||
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.WriteCloser {
|
||||
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.Writer {
|
||||
return &prefixWriter{writer: w, prefix: prefix}
|
||||
}
|
||||
|
||||
@@ -34,12 +34,12 @@ func (pw *prefixWriter) Close() error {
|
||||
|
||||
func (pw *prefixWriter) writeOutputLines(force bool) error {
|
||||
for {
|
||||
line, err := pw.buff.ReadString('\n')
|
||||
if err == nil {
|
||||
switch line, err := pw.buff.ReadString('\n'); err {
|
||||
case nil:
|
||||
if err = pw.writeLine(line); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err == io.EOF {
|
||||
case io.EOF:
|
||||
// if this line was not a complete line, re-add to the buffer
|
||||
if !force && !strings.HasSuffix(line, "\n") {
|
||||
_, err = pw.buff.WriteString(line)
|
||||
@@ -47,7 +47,7 @@ func (pw *prefixWriter) writeOutputLines(force bool) error {
|
||||
}
|
||||
|
||||
return pw.writeLine(line)
|
||||
} else {
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,25 @@ import (
|
||||
// Checksum validades if a task is up to date by calculating its source
|
||||
// files checksum
|
||||
type Checksum struct {
|
||||
Dir string
|
||||
Task string
|
||||
Sources []string
|
||||
Dry bool
|
||||
Dir string
|
||||
Task string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Dry bool
|
||||
}
|
||||
|
||||
// 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)
|
||||
oldMd5 := strings.TrimSpace(string(data))
|
||||
|
||||
sources, err := glob(c.Dir, c.Sources)
|
||||
sources, err := globs(c.Dir, c.Sources)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -43,6 +48,23 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Generates) > 0 {
|
||||
// For each specified 'generates' field, check whether the files actually exist
|
||||
for _, g := range c.Generates {
|
||||
generates, err := glob(c.Dir, g)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(generates) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return oldMd5 == newMd5, nil
|
||||
}
|
||||
|
||||
@@ -50,21 +72,14 @@ func (c *Checksum) checksum(files ...string) (string, error) {
|
||||
h := md5.New()
|
||||
|
||||
for _, f := range files {
|
||||
// also sum the filename, so checksum changes for renaming a file
|
||||
if _, err := io.Copy(h, strings.NewReader(filepath.Base(f))); err != nil {
|
||||
return "", err
|
||||
}
|
||||
f, err := os.Open(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.IsDir() {
|
||||
continue
|
||||
}
|
||||
// also sum the filename, so checksum changes for renaming a file
|
||||
if _, err = io.Copy(h, strings.NewReader(info.Name())); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -73,11 +88,21 @@ 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 {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
@@ -9,21 +10,41 @@ import (
|
||||
"github.com/mattn/go-zglob"
|
||||
)
|
||||
|
||||
func glob(dir string, globs []string) (files []string, err error) {
|
||||
func globs(dir string, globs []string) ([]string, error) {
|
||||
files := make([]string, 0)
|
||||
for _, g := range globs {
|
||||
if !filepath.IsAbs(g) {
|
||||
g = filepath.Join(dir, g)
|
||||
}
|
||||
g, err = execext.Expand(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := zglob.Glob(g)
|
||||
f, err := glob(dir, g)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
files = append(files, f...)
|
||||
}
|
||||
sort.Strings(files)
|
||||
return
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func glob(dir string, g string) ([]string, error) {
|
||||
files := make([]string, 0)
|
||||
if !filepath.IsAbs(g) {
|
||||
g = filepath.Join(dir, g)
|
||||
}
|
||||
g, err := execext.Expand(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fs, err := zglob.Glob(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range fs {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.IsDir() {
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
sources, err := glob(t.Dir, t.Sources)
|
||||
sources, err := globs(t.Dir, t.Sources)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
generates, err := glob(t.Dir, t.Generates)
|
||||
generates, err := globs(t.Dir, t.Generates)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
103
internal/summary/summary.go
Normal file
103
internal/summary/summary.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package summary
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
|
||||
for i, call := range c {
|
||||
PrintSpaceBetweenSummaries(l, i)
|
||||
PrintTask(l, t.Tasks[call.Task])
|
||||
}
|
||||
}
|
||||
|
||||
func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
|
||||
spaceRequired := i > 0
|
||||
if !spaceRequired {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
||||
printTaskName(l, t)
|
||||
printTaskDescribingText(t, l)
|
||||
printTaskDependencies(l, t)
|
||||
printTaskCommands(l, t)
|
||||
}
|
||||
|
||||
func printTaskDescribingText(t *taskfile.Task, l *logger.Logger) {
|
||||
if hasSummary(t) {
|
||||
printTaskSummary(l, t)
|
||||
} else if hasDescription(t) {
|
||||
printTaskDescription(l, t)
|
||||
} else {
|
||||
printNoDescriptionOrSummary(l)
|
||||
}
|
||||
}
|
||||
|
||||
func hasSummary(t *taskfile.Task) bool {
|
||||
return t.Summary != ""
|
||||
}
|
||||
|
||||
func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
|
||||
lines := strings.Split(t.Summary, "\n")
|
||||
for i, line := range lines {
|
||||
notLastLine := i+1 < len(lines)
|
||||
if notLastLine || line != "" {
|
||||
l.Outf(logger.Default, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, "task: %s", t.Task)
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func hasDescription(t *taskfile.Task) bool {
|
||||
return t.Desc != ""
|
||||
}
|
||||
|
||||
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, t.Desc)
|
||||
}
|
||||
|
||||
func printNoDescriptionOrSummary(l *logger.Logger) {
|
||||
l.Outf(logger.Default, "(task does not have description or summary)")
|
||||
}
|
||||
|
||||
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Deps) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "dependencies:")
|
||||
|
||||
for _, d := range t.Deps {
|
||||
l.Outf(logger.Default, " - %s", d.Task)
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Cmds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "commands:")
|
||||
for _, c := range t.Cmds {
|
||||
isCommand := c.Cmd != ""
|
||||
if isCommand {
|
||||
l.Outf(logger.Default, " - %s", c.Cmd)
|
||||
} else {
|
||||
l.Outf(logger.Default, " - Task: %s", c.Task)
|
||||
}
|
||||
}
|
||||
}
|
||||
173
internal/summary/summary_test.go
Normal file
173
internal/summary/summary_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package summary_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPrintsDependenciesIfPresent(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Deps: []*taskfile.Dep{
|
||||
{Task: "dep1"},
|
||||
{Task: "dep2"},
|
||||
{Task: "dep3"},
|
||||
},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.Contains(t, buffer.String(), "\ndependencies:\n - dep1\n - dep2\n - dep3\n")
|
||||
}
|
||||
|
||||
func createDummyLogger() (*bytes.Buffer, logger.Logger) {
|
||||
buffer := &bytes.Buffer{}
|
||||
l := logger.Logger{
|
||||
Stderr: buffer,
|
||||
Stdout: buffer,
|
||||
Verbose: false,
|
||||
}
|
||||
return buffer, l
|
||||
}
|
||||
|
||||
func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Deps: []*taskfile.Dep{},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.NotContains(t, buffer.String(), "dependencies:")
|
||||
}
|
||||
|
||||
func TestPrintTaskName(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Task: "my-task-name",
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.Contains(t, buffer.String(), "task: my-task-name\n")
|
||||
}
|
||||
|
||||
func TestPrintTaskCommandsIfPresent(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Cmds: []*taskfile.Cmd{
|
||||
{Cmd: "command-1"},
|
||||
{Cmd: "command-2"},
|
||||
{Task: "task-1"},
|
||||
},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.Contains(t, buffer.String(), "\ncommands:\n")
|
||||
assert.Contains(t, buffer.String(), "\n - command-1\n")
|
||||
assert.Contains(t, buffer.String(), "\n - command-2\n")
|
||||
assert.Contains(t, buffer.String(), "\n - Task: task-1\n")
|
||||
}
|
||||
|
||||
func TestDoesNotPrintCommandIfMissing(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Cmds: []*taskfile.Cmd{},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.NotContains(t, buffer.String(), "commands")
|
||||
}
|
||||
|
||||
func TestLayout(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Task: "sample-task",
|
||||
Summary: "line1\nline2\nline3\n",
|
||||
Deps: []*taskfile.Dep{
|
||||
{Task: "dependency"},
|
||||
},
|
||||
Cmds: []*taskfile.Cmd{
|
||||
{Cmd: "command"},
|
||||
},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.Equal(t, expectedOutput(), buffer.String())
|
||||
}
|
||||
|
||||
func expectedOutput() string {
|
||||
expected := `task: sample-task
|
||||
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
|
||||
dependencies:
|
||||
- dependency
|
||||
|
||||
commands:
|
||||
- command
|
||||
`
|
||||
return expected
|
||||
}
|
||||
|
||||
func TestPrintDescriptionAsFallback(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
taskWithoutSummary := &taskfile.Task{
|
||||
Desc: "description",
|
||||
}
|
||||
|
||||
taskWithSummary := &taskfile.Task{
|
||||
Desc: "description",
|
||||
Summary: "summary",
|
||||
}
|
||||
taskWithoutSummaryOrDescription := &taskfile.Task{}
|
||||
|
||||
summary.PrintTask(&l, taskWithoutSummary)
|
||||
|
||||
assert.Contains(t, buffer.String(), "description")
|
||||
|
||||
buffer.Reset()
|
||||
summary.PrintTask(&l, taskWithSummary)
|
||||
|
||||
assert.NotContains(t, buffer.String(), "description")
|
||||
|
||||
buffer.Reset()
|
||||
summary.PrintTask(&l, taskWithoutSummaryOrDescription)
|
||||
|
||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n")
|
||||
|
||||
}
|
||||
|
||||
func TestPrintAllWithSpaces(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
|
||||
t1 := &taskfile.Task{Task: "t1"}
|
||||
t2 := &taskfile.Task{Task: "t2"}
|
||||
t3 := &taskfile.Task{Task: "t3"}
|
||||
|
||||
tasks := make(taskfile.Tasks, 3)
|
||||
tasks["t1"] = t1
|
||||
tasks["t2"] = t2
|
||||
tasks["t3"] = t3
|
||||
|
||||
summary.PrintTasks(&l,
|
||||
&taskfile.Taskfile{Tasks: tasks},
|
||||
[]taskfile.Call{{Task: "t1"}, {Task: "t2"}, {Task: "t3"}})
|
||||
|
||||
assert.True(t, strings.HasPrefix(buffer.String(), "task: t1"))
|
||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t2")
|
||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t3")
|
||||
|
||||
}
|
||||
@@ -3,5 +3,5 @@ package taskfile
|
||||
// Call is the parameters to a task call
|
||||
type Call struct {
|
||||
Task string
|
||||
Vars Vars
|
||||
Vars *Vars
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
40
internal/taskfile/included_taskfile.go
Normal file
40
internal/taskfile/included_taskfile.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
45
internal/taskfile/precondition.go
Normal file
45
internal/taskfile/precondition.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
|
||||
ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
|
||||
)
|
||||
|
||||
// Precondition represents a precondition necessary for a task to run
|
||||
type Precondition struct {
|
||||
Sh string
|
||||
Msg string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (p *Precondition) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var cmd string
|
||||
|
||||
if err := unmarshal(&cmd); err == nil {
|
||||
p.Sh = cmd
|
||||
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
var sh struct {
|
||||
Sh string
|
||||
Msg string
|
||||
}
|
||||
|
||||
if err := unmarshal(&sh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Sh = sh.Sh
|
||||
p.Msg = sh.Msg
|
||||
if p.Msg == "" {
|
||||
p.Msg = fmt.Sprintf("%s failed", sh.Sh)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
48
internal/taskfile/precondition_test.go
Normal file
48
internal/taskfile/precondition_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package taskfile_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestPreconditionParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
content string
|
||||
v interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
"test -f foo.txt",
|
||||
&taskfile.Precondition{},
|
||||
&taskfile.Precondition{Sh: `test -f foo.txt`, Msg: "`test -f foo.txt` failed"},
|
||||
},
|
||||
{
|
||||
"sh: '[ 1 = 0 ]'",
|
||||
&taskfile.Precondition{},
|
||||
&taskfile.Precondition{Sh: "[ 1 = 0 ]", Msg: "[ 1 = 0 ] failed"},
|
||||
},
|
||||
{`
|
||||
sh: "[ 1 = 2 ]"
|
||||
msg: "1 is not 2"
|
||||
`,
|
||||
&taskfile.Precondition{},
|
||||
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
||||
},
|
||||
{`
|
||||
sh: "[ 1 = 2 ]"
|
||||
msg: "1 is not 2"
|
||||
`,
|
||||
&taskfile.Precondition{},
|
||||
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := yaml.Unmarshal([]byte(test.content), test.v)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, test.v)
|
||||
}
|
||||
}
|
||||
@@ -8,31 +8,51 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v2/internal/templater"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"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")
|
||||
|
||||
// ErrNoTaskfileFound is returned when Taskfile.yml is not found
|
||||
ErrNoTaskfileFound = errors.New(`task: No Taskfile.yml found. Use "task --init" to create a new one`)
|
||||
)
|
||||
|
||||
// Taskfile reads a Taskfile for a given directory
|
||||
func Taskfile(dir string) (*taskfile.Taskfile, error) {
|
||||
path := filepath.Join(dir, "Taskfile.yml")
|
||||
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
path := filepath.Join(dir, entrypoint)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return nil, ErrNoTaskfileFound
|
||||
return nil, fmt.Errorf(`task: No Taskfile found on "%s". Use "task --init" to create a new one`, path)
|
||||
}
|
||||
t, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for namespace, path := range t.Includes {
|
||||
path = filepath.Join(dir, path)
|
||||
v, err := t.ParsedVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
@@ -47,19 +67,30 @@ func Taskfile(dir string) (*taskfile.Taskfile, error) {
|
||||
if len(includedTaskfile.Includes) > 0 {
|
||||
return nil, ErrIncludedTaskfilesCantHaveIncludes
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
"github.com/go-task/task/v2/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)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,86 @@
|
||||
package taskfile
|
||||
|
||||
// Tasks representas a group of tasks
|
||||
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
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
Dir string
|
||||
Vars Vars
|
||||
Env Vars
|
||||
Silent bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
Task string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
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
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrCantUnmarshalTask is returned for invalid task YAML
|
||||
ErrCantUnmarshalTask = errors.New("task: can't unmarshal task value")
|
||||
)
|
||||
|
||||
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
|
||||
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.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
|
||||
}
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if err := unmarshal(&taskfile); err != nil {
|
||||
return err
|
||||
@@ -33,12 +37,29 @@ 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
|
||||
if tf.Expansions <= 0 {
|
||||
tf.Expansions = 2
|
||||
}
|
||||
if tf.Vars == nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/go-task/task/v2/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"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
var (
|
||||
v1 = mustVersion("1")
|
||||
v2 = mustVersion("2")
|
||||
v21 = mustVersion("2.1")
|
||||
v22 = mustVersion("2.2")
|
||||
v23 = mustVersion("2.3")
|
||||
)
|
||||
|
||||
// IsV1 returns if is a given Taskfile version is version 1
|
||||
func IsV1(v *semver.Constraints) bool {
|
||||
return v.Check(v1)
|
||||
}
|
||||
|
||||
// IsV2 returns if is a given Taskfile version is at least version 2
|
||||
func IsV2(v *semver.Constraints) bool {
|
||||
return v.Check(v2)
|
||||
}
|
||||
|
||||
// IsV21 returns if is a given Taskfile version is at least version 2.1
|
||||
func IsV21(v *semver.Constraints) bool {
|
||||
return v.Check(v21)
|
||||
}
|
||||
|
||||
// IsV22 returns if is a given Taskfile version is at least version 2.2
|
||||
func IsV22(v *semver.Constraints) bool {
|
||||
return v.Check(v22)
|
||||
}
|
||||
|
||||
// IsV23 returns if is a given Taskfile version is at least version 2.3
|
||||
func IsV23(v *semver.Constraints) bool {
|
||||
return v.Check(v23)
|
||||
}
|
||||
|
||||
func mustVersion(s string) *semver.Version {
|
||||
v, err := semver.NewVersion(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/go-task/slim-sprig"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -2,6 +2,7 @@ package templater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
@@ -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 {
|
||||
|
||||
32
precondition.go
Normal file
32
precondition.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPreconditionFailed is returned when a precondition fails
|
||||
ErrPreconditionFailed = errors.New("task: precondition not met")
|
||||
)
|
||||
|
||||
func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||
for _, p := range t.Preconditions {
|
||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: p.Sh,
|
||||
Dir: t.Dir,
|
||||
Env: getEnviron(t),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
e.Logger.Errf(logger.Magenta, "task: %s", p.Msg)
|
||||
return false, ErrPreconditionFailed
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
44
status.go
44
status.go
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/status"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
@@ -49,24 +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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +92,10 @@ func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (
|
||||
Env: getEnviron(t),
|
||||
})
|
||||
if err != nil {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err)
|
||||
return false, nil
|
||||
}
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
220
task.go
220
task.go
@@ -2,22 +2,23 @@ package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"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"
|
||||
compilerv3 "github.com/go-task/task/v2/internal/compiler/v3"
|
||||
"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/v2/internal/taskfile/version"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -30,12 +31,17 @@ const (
|
||||
// Executor executes a Taskfile
|
||||
type Executor struct {
|
||||
Taskfile *taskfile.Taskfile
|
||||
Dir string
|
||||
Force bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
Dry bool
|
||||
|
||||
Dir string
|
||||
Entrypoint string
|
||||
Force bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
Parallel bool
|
||||
Color bool
|
||||
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
@@ -46,9 +52,10 @@ type Executor struct {
|
||||
Output output.Output
|
||||
OutputStyle string
|
||||
|
||||
taskvars taskfile.Vars
|
||||
taskvars *taskfile.Vars
|
||||
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
}
|
||||
|
||||
// Run runs Task
|
||||
@@ -62,33 +69,58 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
}
|
||||
}
|
||||
|
||||
if e.Summary {
|
||||
for i, c := range calls {
|
||||
compiledTask, err := e.CompiledTask(c)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
summary.PrintSpaceBetweenSummaries(e.Logger, i)
|
||||
summary.PrintTask(e.Logger, compiledTask)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.Watch {
|
||||
return e.watchTasks(calls...)
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
for _, c := range calls {
|
||||
if err := e.RunTask(ctx, c); err != nil {
|
||||
return err
|
||||
c := c
|
||||
if e.Parallel {
|
||||
g.Go(func() error { return e.RunTask(ctx, c) })
|
||||
} else {
|
||||
if err := e.RunTask(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// Setup setups Executor's internal state
|
||||
func (e *Executor) Setup() error {
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
if e.Entrypoint == "" {
|
||||
e.Entrypoint = "Taskfile.yml"
|
||||
}
|
||||
e.taskvars, err = read.Taskvars(e.Dir)
|
||||
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := semver.NewConstraint(e.Taskfile.Version)
|
||||
v, err := e.Taskfile.ParsedVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf(`task: could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if v < 3.0 {
|
||||
e.taskvars, err = read.Taskvars(e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if e.Stdin == nil {
|
||||
@@ -104,15 +136,28 @@ func (e *Executor) Setup() error {
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
Verbose: e.Verbose,
|
||||
Color: e.Color,
|
||||
}
|
||||
switch {
|
||||
case version.IsV1(v):
|
||||
e.Compiler = &compilerv1.CompilerV1{
|
||||
Dir: e.Dir,
|
||||
Vars: e.taskvars,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
case version.IsV2(v), version.IsV21(v), version.IsV22(v):
|
||||
|
||||
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 > 3.0 {
|
||||
return fmt.Errorf(`task: Taskfile versions greater than v3.0 not implemented in the version of Task`)
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -120,16 +165,24 @@ func (e *Executor) Setup() error {
|
||||
Expansions: e.Taskfile.Expansions,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
case version.IsV23(v):
|
||||
return fmt.Errorf(`task: Taskfile versions greater than v2.3 not implemented in the version of Task`)
|
||||
} else {
|
||||
e.Compiler = &compilerv3.CompilerV3{
|
||||
Dir: e.Dir,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
if !version.IsV21(v) && e.Taskfile.Output != "" {
|
||||
if v < 2.1 && e.Taskfile.Output != "" {
|
||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||
}
|
||||
if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
|
||||
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
|
||||
}
|
||||
@@ -144,8 +197,16 @@ func (e *Executor) Setup() error {
|
||||
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
||||
}
|
||||
|
||||
if !version.IsV21(v) {
|
||||
err := fmt.Errorf(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
||||
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`)
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.IgnoreError {
|
||||
@@ -159,9 +220,27 @@ func (e *Executor) Setup() error {
|
||||
}
|
||||
}
|
||||
|
||||
if v < 2.6 {
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if len(task.Preconditions) > 0 {
|
||||
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
e.taskCallCount[k] = new(int32)
|
||||
e.mkdirMutexMap[k] = &sync.Mutex{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -181,26 +260,36 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
}
|
||||
|
||||
if !e.Force {
|
||||
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upToDate, err := e.isTaskUpToDate(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if upToDate {
|
||||
|
||||
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.Task)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.mkdir(t); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -210,6 +299,23 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) mkdir(t *taskfile.Task) error {
|
||||
if t.Dir == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
mutex := e.mkdirMutexMap[t.Task]
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if _, err := os.Stat(t.Dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(t.Dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
@@ -217,7 +323,11 @@ func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
||||
d := d
|
||||
|
||||
g.Go(func() error {
|
||||
return e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
||||
err := e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -229,10 +339,14 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
||||
|
||||
switch {
|
||||
case cmd.Task != "":
|
||||
return e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
||||
err := e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case cmd.Cmd != "":
|
||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
|
||||
e.Logger.Errf(cmd.Cmd)
|
||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
||||
e.Logger.Errf(logger.Green, "task: %s", cmd.Cmd)
|
||||
}
|
||||
|
||||
if e.Dry {
|
||||
@@ -241,8 +355,18 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
||||
|
||||
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
|
||||
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
|
||||
defer stdOut.Close()
|
||||
defer stdErr.Close()
|
||||
defer func() {
|
||||
if _, ok := stdOut.(*os.File); !ok {
|
||||
if closer, ok := stdOut.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
if _, ok := stdErr.(*os.File); !ok {
|
||||
if closer, ok := stdErr.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: cmd.Cmd,
|
||||
@@ -253,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
|
||||
@@ -268,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
|
||||
}
|
||||
|
||||
287
task_test.go
287
task_test.go
@@ -7,13 +7,13 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"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"
|
||||
)
|
||||
@@ -236,7 +214,7 @@ func TestDeps(t *testing.T) {
|
||||
for _, f := range files {
|
||||
f = filepath.Join(dir, f)
|
||||
if _, err := os.Stat(f); err != nil {
|
||||
t.Errorf("File %s should exists", f)
|
||||
t.Errorf("File %s should exist", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +226,7 @@ func TestStatus(t *testing.T) {
|
||||
_ = os.Remove(file)
|
||||
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Errorf("File should not exists: %v", err)
|
||||
t.Errorf("File should not exist: %v", err)
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
@@ -262,7 +240,7 @@ func TestStatus(t *testing.T) {
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
t.Errorf("File should exists: %v", err)
|
||||
t.Errorf("File should exist: %v", err)
|
||||
}
|
||||
|
||||
e.Silent = false
|
||||
@@ -273,6 +251,47 @@ func TestStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrecondition(t *testing.T) {
|
||||
const dir = "testdata/precondition"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
|
||||
// A precondition that has been met
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
||||
if buff.String() != "" {
|
||||
t.Errorf("Got Output when none was expected: %s", buff.String())
|
||||
}
|
||||
|
||||
// A precondition that was not met
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "impossible"}))
|
||||
|
||||
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||
t.Errorf("Wrong output message: %s", buff.String())
|
||||
}
|
||||
buff.Reset()
|
||||
|
||||
// Calling a task with a precondition in a dependency fails the task
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "depends_on_impossible"}))
|
||||
|
||||
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||
t.Errorf("Wrong output message: %s", buff.String())
|
||||
}
|
||||
buff.Reset()
|
||||
|
||||
// Calling a task with a precondition in a cmd fails the task
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "executes_failing_task_as_cmd"}))
|
||||
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||
t.Errorf("Wrong output message: %s", buff.String())
|
||||
}
|
||||
buff.Reset()
|
||||
}
|
||||
|
||||
func TestGenerates(t *testing.T) {
|
||||
const (
|
||||
srcTask = "sub/src.txt"
|
||||
@@ -290,7 +309,7 @@ func TestGenerates(t *testing.T) {
|
||||
path := filepath.Join(dir, task)
|
||||
_ = os.Remove(path)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
t.Errorf("File should not exists: %v", err)
|
||||
t.Errorf("File should not exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,10 +330,10 @@ func TestGenerates(t *testing.T) {
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
|
||||
|
||||
if _, err := os.Stat(srcFile); err != nil {
|
||||
t.Errorf("File should exists: %v", err)
|
||||
t.Errorf("File should exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(destFile); err != nil {
|
||||
t.Errorf("File should exists: %v", err)
|
||||
t.Errorf("File should exist: %v", err)
|
||||
}
|
||||
// Ensure task was not incorrectly found to be up-to-date on first run.
|
||||
if buff.String() == upToDate {
|
||||
@@ -365,13 +384,41 @@ func TestStatusChecksum(t *testing.T) {
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
_ = os.Remove(file)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Errorf("Taskfile.yml should not exists")
|
||||
t.Errorf("Taskfile.yml should not exist")
|
||||
}
|
||||
|
||||
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
|
||||
@@ -379,7 +426,7 @@ func TestInit(t *testing.T) {
|
||||
}
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
t.Errorf("Taskfile.yml should exists")
|
||||
t.Errorf("Taskfile.yml should exist")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +447,6 @@ func TestTaskVersion(t *testing.T) {
|
||||
Dir string
|
||||
Version string
|
||||
}{
|
||||
{"testdata/version/v1", "1"},
|
||||
{"testdata/version/v2", "2"},
|
||||
}
|
||||
|
||||
@@ -437,7 +483,7 @@ func TestTaskIgnoreErrors(t *testing.T) {
|
||||
func TestExpand(t *testing.T) {
|
||||
const dir = "testdata/expand"
|
||||
|
||||
home, err := homedir.Dir()
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't get $HOME: %v", err)
|
||||
}
|
||||
@@ -470,7 +516,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)
|
||||
}
|
||||
@@ -508,14 +554,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",
|
||||
@@ -553,3 +618,117 @@ func TestIncludesCallingRoot(t *testing.T) {
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestSummary(t *testing.T) {
|
||||
const dir = "testdata/summary"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Summary: true,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
|
||||
|
||||
data, err := ioutil.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedOutput := string(data)
|
||||
if runtime.GOOS == "windows" {
|
||||
expectedOutput = strings.Replace(expectedOutput, "\r\n", "\n", -1)
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedOutput, buff.String())
|
||||
}
|
||||
|
||||
func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
|
||||
const expected = "dir"
|
||||
const dir = "testdata/" + expected
|
||||
var out bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &out,
|
||||
Stderr: &out,
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
|
||||
|
||||
// got should be the "dir" part of "testdata/dir"
|
||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
}
|
||||
|
||||
func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
|
||||
const expected = "exists"
|
||||
const dir = "testdata/dir/explicit_exists"
|
||||
var out bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &out,
|
||||
Stderr: &out,
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
|
||||
|
||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
}
|
||||
|
||||
func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
|
||||
const expected = "createme"
|
||||
const dir = "testdata/dir/explicit_doesnt_exist/"
|
||||
const toBeCreated = dir + expected
|
||||
const target = "whereami"
|
||||
var out bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &out,
|
||||
Stderr: &out,
|
||||
}
|
||||
|
||||
// Ensure that the directory to be created doesn't actually exist.
|
||||
_ = os.RemoveAll(toBeCreated)
|
||||
if _, err := os.Stat(toBeCreated); err == nil {
|
||||
t.Errorf("Directory should not exist: %v", err)
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}))
|
||||
|
||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
|
||||
// Clean-up after ourselves only if no error.
|
||||
_ = 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())
|
||||
}
|
||||
|
||||
21
testdata/checksum/Taskfile.yml
vendored
21
testdata/checksum/Taskfile.yml
vendored
@@ -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
|
||||
|
||||
15
testdata/cyclic/Taskfile.yml
vendored
15
testdata/cyclic/Taskfile.yml
vendored
@@ -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
|
||||
|
||||
85
testdata/deps/Taskfile.yml
vendored
85
testdata/deps/Taskfile.yml
vendored
@@ -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
|
||||
|
||||
7
testdata/dir/Taskfile.yml
vendored
Normal file
7
testdata/dir/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
whereami:
|
||||
cmds:
|
||||
- pwd
|
||||
silent: true
|
||||
8
testdata/dir/explicit_doesnt_exist/Taskfile.yml
vendored
Normal file
8
testdata/dir/explicit_doesnt_exist/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
whereami:
|
||||
dir: createme
|
||||
cmds:
|
||||
- pwd
|
||||
silent: true
|
||||
8
testdata/dir/explicit_exists/Taskfile.yml
vendored
Normal file
8
testdata/dir/explicit_exists/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
whereami:
|
||||
dir: exists
|
||||
cmds:
|
||||
- pwd
|
||||
silent: true
|
||||
0
testdata/dir/explicit_exists/exists/.keep
vendored
Normal file
0
testdata/dir/explicit_exists/exists/.keep
vendored
Normal file
2
testdata/dry/Taskfile.yml
vendored
2
testdata/dry/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
|
||||
2
testdata/dry_checksum/Taskfile.yml
vendored
2
testdata/dry_checksum/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
|
||||
2
testdata/env/Taskfile.yml
vendored
2
testdata/env/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
BAZ:
|
||||
|
||||
2
testdata/expand/Taskfile.yml
vendored
2
testdata/expand/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
pwd:
|
||||
|
||||
86
testdata/generates/Taskfile.yml
vendored
86
testdata/generates/Taskfile.yml
vendored
@@ -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'
|
||||
|
||||
1
testdata/generates/Taskvars.yml
vendored
1
testdata/generates/Taskvars.yml
vendored
@@ -1 +0,0 @@
|
||||
BUILD_DIR: $pwd
|
||||
2
testdata/ignore_errors/Taskfile.yml
vendored
2
testdata/ignore_errors/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
task-should-pass:
|
||||
|
||||
18
testdata/includes/Taskfile.yml
vendored
18
testdata/includes/Taskfile.yml
vendored
@@ -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:
|
||||
|
||||
2
testdata/includes/Taskfile2.yml
vendored
2
testdata/includes/Taskfile2.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
gen:
|
||||
|
||||
4
testdata/includes/Taskfile_darwin.yml
vendored
Normal file
4
testdata/includes/Taskfile_darwin.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
gen: echo 'os' > os_include.txt
|
||||
4
testdata/includes/Taskfile_linux.yml
vendored
Normal file
4
testdata/includes/Taskfile_linux.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
gen: echo 'os' > os_include.txt
|
||||
4
testdata/includes/Taskfile_windows.yml
vendored
Normal file
4
testdata/includes/Taskfile_windows.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
gen: echo 'os' > os_include.txt
|
||||
2
testdata/includes/included/Taskfile.yml
vendored
2
testdata/includes/included/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
gen:
|
||||
|
||||
10
testdata/includes/module1/Taskfile.yml
vendored
Normal file
10
testdata/includes/module1/Taskfile.yml
vendored
Normal 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
10
testdata/includes/module2/Taskfile.yml
vendored
Normal 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
|
||||
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included: Taskfile2.yml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
call-root:
|
||||
|
||||
2
testdata/includes_deps/Taskfile.yml
vendored
2
testdata/includes_deps/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included: Taskfile2.yml
|
||||
|
||||
2
testdata/includes_deps/Taskfile2.yml
vendored
2
testdata/includes_deps/Taskfile2.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
|
||||
2
testdata/includes_empty/Taskfile.yml
vendored
2
testdata/includes_empty/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included: Taskfile2.yml
|
||||
|
||||
2
testdata/includes_empty/Taskfile2.yml
vendored
2
testdata/includes_empty/Taskfile2.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
FILE: file.txt
|
||||
|
||||
10
testdata/incorrect_includes/Taskfile.yml
vendored
Normal file
10
testdata/incorrect_includes/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: '2.6'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: ./included
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: gen
|
||||
6
testdata/incorrect_includes/included/Taskfile.yml
vendored
Normal file
6
testdata/incorrect_includes/included/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '2.6'
|
||||
|
||||
tasks:
|
||||
gen:
|
||||
cmds:
|
||||
- echo incorrect includes test
|
||||
77
testdata/params/Taskfile.yml
vendored
77
testdata/params/Taskfile.yml
vendored
@@ -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}
|
||||
|
||||
2
testdata/params/Taskvars.yml
vendored
2
testdata/params/Taskvars.yml
vendored
@@ -1,2 +0,0 @@
|
||||
PORTUGUESE_HELLO_WORLD: Olá, mundo!
|
||||
GERMAN: "Hello"
|
||||
19
testdata/precondition/Taskfile.yml
vendored
Normal file
19
testdata/precondition/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
preconditions:
|
||||
- test -f foo.txt
|
||||
|
||||
impossible:
|
||||
preconditions:
|
||||
- sh: "[ 1 = 0 ]"
|
||||
msg: "1 != 0 obviously!"
|
||||
|
||||
depends_on_impossible:
|
||||
deps:
|
||||
- impossible
|
||||
|
||||
executes_failing_task_as_cmd:
|
||||
cmds:
|
||||
- task: impossible
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user