mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
327 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eb52da0db | ||
|
|
d8bfb3ab13 | ||
|
|
d970e93507 | ||
|
|
762714de68 | ||
|
|
82a3651a18 | ||
|
|
abe0352de9 | ||
|
|
4cee4aa5a8 | ||
|
|
9c68c7c50b | ||
|
|
0608782cfa | ||
|
|
edeaf3794a | ||
|
|
fe2b8c8afa | ||
|
|
b66bf58064 | ||
|
|
957dfa9cdf | ||
|
|
cc9264854e | ||
|
|
d1463b3e24 | ||
|
|
f1082520e1 | ||
|
|
733c563194 | ||
|
|
0200d043c3 | ||
|
|
9c475c36e7 | ||
|
|
c663c5c507 | ||
|
|
1e93c38307 | ||
|
|
81baf808c9 | ||
|
|
74537689dc | ||
|
|
12ab01d5e6 | ||
|
|
044d3a0ff9 | ||
|
|
659cae6a4c | ||
|
|
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 | ||
|
|
5738436d55 | ||
|
|
5e49b38c33 | ||
|
|
0c94adaff9 | ||
|
|
f8a6c5d06c | ||
|
|
21e66c7c02 | ||
|
|
902f0d3ac4 | ||
|
|
713ecd35f6 | ||
|
|
27b35157cd | ||
|
|
f8fb639870 | ||
|
|
14f41ae619 | ||
|
|
a026d72924 | ||
|
|
2cb070f5b3 | ||
|
|
1dec956e99 | ||
|
|
310394aa60 | ||
|
|
468ff18243 | ||
|
|
44a63580f0 | ||
|
|
4ac1fa43aa | ||
|
|
6f992a3cf7 | ||
|
|
fd4ce656d5 | ||
|
|
9ed2dca427 | ||
|
|
dfb804fe3f | ||
|
|
4f2a84b426 | ||
|
|
14a127b6b3 | ||
|
|
06000533fb | ||
|
|
7722aba403 | ||
|
|
4817d8c67f | ||
|
|
9a062d90d1 | ||
|
|
959eb45373 | ||
|
|
a42f2af9eb | ||
|
|
4ddad68212 | ||
|
|
aac6c5a1c7 | ||
|
|
5572e31fd4 | ||
|
|
233b8bf81a | ||
|
|
2ae3810f80 | ||
|
|
736165876c | ||
|
|
61b3fca9a3 | ||
|
|
469863b7b3 | ||
|
|
5238bc55fd | ||
|
|
57a01aa6ff | ||
|
|
9361dbc39e | ||
|
|
11d257cb26 | ||
|
|
a928ab75e3 | ||
|
|
55a240c82e | ||
|
|
f8aedf438b | ||
|
|
9f1bb9a42e | ||
|
|
0ed7274610 | ||
|
|
df032b09a7 | ||
|
|
7dba742e4c | ||
|
|
83dbe78965 | ||
|
|
95b75c5330 | ||
|
|
780bd08490 | ||
|
|
a9b1f38a7c | ||
|
|
4ed4ad9852 | ||
|
|
81f172315c | ||
|
|
54666221a4 | ||
|
|
5327d702f8 | ||
|
|
a701ea3007 | ||
|
|
f0fb7d9661 | ||
|
|
3cbc89769d | ||
|
|
cb95200be3 | ||
|
|
a52f6c0acf | ||
|
|
77f9e3dd41 | ||
|
|
259d3e2df1 | ||
|
|
6eee5421b0 | ||
|
|
8004e9c943 | ||
|
|
2ab8511f45 | ||
|
|
7514ff53c9 | ||
|
|
309cfb1499 | ||
|
|
a567f7ed20 | ||
|
|
5720936247 | ||
|
|
5d9de14ca3 | ||
|
|
f519f56078 | ||
|
|
5eb1a1f7f5 | ||
|
|
5a28560177 | ||
|
|
db280adf55 | ||
|
|
b77fcd6c8a | ||
|
|
b5b2649283 | ||
|
|
318f9b216d | ||
|
|
61247a0b2a | ||
|
|
849a418273 | ||
|
|
2e63a62e08 | ||
|
|
6ccf1f2a3c | ||
|
|
e298256b82 | ||
|
|
787e5b2e29 | ||
|
|
4aa1e8b093 | ||
|
|
9ee224c36b | ||
|
|
08263c0597 | ||
|
|
347fe87229 | ||
|
|
b65a0a3a8d | ||
|
|
9a5a1e2253 | ||
|
|
687b4ec837 | ||
|
|
8bdf5c554d | ||
|
|
f4a18e531f | ||
|
|
df951a0c7c | ||
|
|
a6cac2691b | ||
|
|
a9f5179066 | ||
|
|
687e2699cf | ||
|
|
491da0ceb9 | ||
|
|
1bac40bc58 | ||
|
|
fb9061480d | ||
|
|
a04cf100b4 | ||
|
|
76253bf516 | ||
|
|
feaf70922d | ||
|
|
c70343a5bc | ||
|
|
550c116aea | ||
|
|
a5f31a4280 | ||
|
|
27fc4c4ca8 | ||
|
|
90a5f17f58 | ||
|
|
108cb91d95 | ||
|
|
00a0755ff3 | ||
|
|
3f7e8c88eb | ||
|
|
1c7ca94d49 | ||
|
|
31273cd6ff | ||
|
|
14e39dd745 | ||
|
|
cc6f7b6088 | ||
|
|
da1b0c9558 | ||
|
|
9f294b4d10 | ||
|
|
13f60bae41 | ||
|
|
67105b332f | ||
|
|
18961e3d07 | ||
|
|
fe31f5050d | ||
|
|
ab8549adea | ||
|
|
05600601ff | ||
|
|
c541356289 | ||
|
|
467c4360ca | ||
|
|
3b152a38b0 | ||
|
|
f4d3855528 | ||
|
|
09eab770a7 | ||
|
|
102f8ab74e | ||
|
|
a830dba5da | ||
|
|
3f13a50ca3 | ||
|
|
7c456f2ab9 | ||
|
|
bae95cd6f6 | ||
|
|
bbd6e443b0 | ||
|
|
db0d847e03 | ||
|
|
0afb453fed | ||
|
|
dbc79b4311 | ||
|
|
ac6008c33c | ||
|
|
e540e752f2 | ||
|
|
cdbe821eb8 | ||
|
|
6be994f1ca | ||
|
|
a407b0a8eb | ||
|
|
051ff35878 | ||
|
|
8b3c34c308 | ||
|
|
2cb2668803 | ||
|
|
4dccdb95b9 | ||
|
|
96db9a9410 | ||
|
|
0cd34bbebc | ||
|
|
15f50c0e58 | ||
|
|
7fca9732e7 | ||
|
|
0ea8c3ed28 | ||
|
|
0af9600e92 | ||
|
|
328e3725e5 | ||
|
|
2183e1e9f5 | ||
|
|
120d0be84c | ||
|
|
5649f75a8d | ||
|
|
a209f7d6be | ||
|
|
d48a2f3ccf | ||
|
|
c1ae36866e | ||
|
|
4f368923a5 | ||
|
|
7c02097d93 | ||
|
|
51998f706f | ||
|
|
1a3df08aca | ||
|
|
975f262ac0 | ||
|
|
1cb4a3b8d5 | ||
|
|
35f4b2f686 | ||
|
|
407ec91ca7 | ||
|
|
12c0d18932 | ||
|
|
2d4ca37226 | ||
|
|
afe6744e97 | ||
|
|
19d4b8b7f7 | ||
|
|
3556942516 | ||
|
|
87a200e42c | ||
|
|
152fc0ad38 | ||
|
|
3212ae4713 | ||
|
|
040cef1479 | ||
|
|
f5f70d7a75 | ||
|
|
42509cf2f5 | ||
|
|
6f74c2d823 | ||
|
|
e23a6dc9f1 | ||
|
|
134c6b79c4 | ||
|
|
00ff1447ee | ||
|
|
78f6cb08d8 |
@@ -9,6 +9,6 @@ trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[*.{md,yml,yaml,json,toml}]
|
||||
[*.{md,yml,yaml,json,toml,htm,html}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
8
.github/CONTRIBUTING.md
vendored
8
.github/CONTRIBUTING.md
vendored
@@ -1,12 +1,6 @@
|
||||
* Bug reports and feature requests are welcome in [the issues][issues]
|
||||
* For questions and discussion there's the [Slack room][slack] ([invititation here][slackinvite])
|
||||
* Pull Requests are welcome. For more complex changes and features it's
|
||||
recommended to open an issue first
|
||||
* About 3 or 4 pull requests accepted one gets write access to the repo.
|
||||
Even then, possible backward incompatible changes should be discussed first
|
||||
in an issue or pull request
|
||||
recommended to open an issue with the feature request first
|
||||
* Documentation contributions are as important as code contributions
|
||||
|
||||
[issues]: https://github.com/go-task/task/issues
|
||||
[slack]: https://gophers.slack.com/messages/task
|
||||
[slackinvite]: https://invite.slack.golangbridge.org/
|
||||
|
||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
open_collective: task
|
||||
3
.github/ISSUE_TEMPLATE.md
vendored
3
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,7 +1,4 @@
|
||||
<!--
|
||||
For questions and general talk there's the Slack room: https://gophers.slack.com/messages/task
|
||||
Invite to the Slack is available in this link: https://invite.slack.golangbridge.org/
|
||||
|
||||
If relevant, include the following information:
|
||||
- Task version
|
||||
- OS
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -14,6 +14,13 @@
|
||||
.glide/
|
||||
|
||||
./task
|
||||
.task
|
||||
dist/
|
||||
|
||||
vendor/**/*_test.go
|
||||
.DS_Store
|
||||
|
||||
# intellij idea/goland
|
||||
.idea/
|
||||
|
||||
# exuberant ctags
|
||||
tags
|
||||
|
||||
@@ -11,6 +11,8 @@ build:
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
|
||||
archive:
|
||||
name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||
@@ -22,12 +24,21 @@ archive:
|
||||
release:
|
||||
draft: true
|
||||
|
||||
fpm:
|
||||
snapshot:
|
||||
name_template: "{{.Tag}}"
|
||||
|
||||
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}}"
|
||||
|
||||
23
.travis.yml
23
.travis.yml
@@ -1,9 +1,22 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8
|
||||
- 1.9
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- rpm
|
||||
|
||||
script:
|
||||
- go install github.com/go-task/task/cmd/task
|
||||
- task dl-deps
|
||||
- task lint
|
||||
- task test
|
||||
- task ci
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: curl -sL http://git.io/goreleaser | bash
|
||||
on:
|
||||
tags: true
|
||||
condition: $TRAVIS_OS_NAME = linux
|
||||
|
||||
204
CHANGELOG.md
Normal file
204
CHANGELOG.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- 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
|
||||
by prefixing it with `:`
|
||||
([#161](https://github.com/go-task/task/issues/161), [#172](https://github.com/go-task/task/issues/172)),
|
||||
- Add flag to override the `output` option
|
||||
([#173](https://github.com/go-task/task/pull/173));
|
||||
- Fix bug where Task was persisting the new checksum on the disk when the Dry
|
||||
Mode is enabled
|
||||
([#166](https://github.com/go-task/task/issues/166));
|
||||
- Fix file timestamp issue when the file name has spaces
|
||||
([#176](https://github.com/go-task/task/issues/176));
|
||||
- Mitigating path expanding issues on Windows
|
||||
([#170](https://github.com/go-task/task/pull/170)).
|
||||
|
||||
## v2.3.0 - 2019-01-02
|
||||
|
||||
- On Windows, Task can now be installed using [Scoop](https://scoop.sh/)
|
||||
([#152](https://github.com/go-task/task/pull/152));
|
||||
- Fixed issue with file/directory globing
|
||||
([#153](https://github.com/go-task/task/issues/153));
|
||||
- Added ability to globally set environment variables
|
||||
(
|
||||
[#138](https://github.com/go-task/task/pull/138),
|
||||
[#159](https://github.com/go-task/task/pull/159)
|
||||
).
|
||||
|
||||
## v2.2.1 - 2018-12-09
|
||||
|
||||
- This repository now uses Go Modules (#143). We'll still keep the `vendor` directory in sync for some time, though;
|
||||
- Fixing a bug when the Taskfile has no tasks but includes another Taskfile (#150);
|
||||
- Fix a bug when calling another task or a dependency in an included Taskfile (#151).
|
||||
|
||||
## v2.2.0 - 2018-10-25
|
||||
|
||||
- Added support for [including other Taskfiles](https://taskfile.org/#/usage?id=including-other-taskfiles) (#98)
|
||||
- This should be considered experimental. For now, only including local files is supported, but support for including remote Taskfiles is being discussed. If you have any feedback, please comment on #98.
|
||||
- Task now have a dedicated documentation site: https://taskfile.org
|
||||
- Thanks to [Docsify](https://docsify.js.org/) for making this pretty easy. To check the source code, just take a look at the [docs](https://github.com/go-task/task/tree/master/docs) directory of this repository. Contributions to the documentation is really appreciated.
|
||||
|
||||
## v2.1.1 - 2018-09-17
|
||||
|
||||
- Fix suggestion to use `task --init` not being shown anymore (when a `Taskfile.yml` is not found)
|
||||
- Fix error when using checksum method and no file exists for a source glob (#131)
|
||||
- Fix signal handling when the `--watch` flag is given (#132)
|
||||
|
||||
## v2.1.0 - 2018-08-19
|
||||
|
||||
- Add a `ignore_error` option to task and command (#123)
|
||||
- Add a dry run mode (`--dry` flag) (#126)
|
||||
|
||||
## v2.0.3 - 2018-06-24
|
||||
|
||||
- Expand environment variables on "dir", "sources" and "generates" (#116)
|
||||
- Fix YAML merging syntax (#112)
|
||||
- Add ZSH completion (#111)
|
||||
- Implement new `output` option. Please check out the [documentation](https://github.com/go-task/task#output-syntax)
|
||||
|
||||
## v2.0.2 - 2018-05-01
|
||||
|
||||
- Fix merging of YAML anchors (#112)
|
||||
|
||||
## v2.0.1 - 2018-03-11
|
||||
|
||||
- Fixes panic on `task --list`
|
||||
|
||||
## v2.0.0 - 2018-03-08
|
||||
|
||||
Version 2.0.0 is here, with a new Taskfile format.
|
||||
|
||||
Please, make sure to read the [Taskfile versions](https://github.com/go-task/task/blob/master/TASKFILE_VERSIONS.md) document, since it describes in depth what changed for this version.
|
||||
|
||||
* New Taskfile version 2 (https://github.com/go-task/task/issues/77)
|
||||
* Possibility to have global variables in the `Taskfile.yml` instead of `Taskvars.yml` (https://github.com/go-task/task/issues/66)
|
||||
* Small improvements and fixes
|
||||
|
||||
## v1.4.4 - 2017-11-19
|
||||
|
||||
- Handle SIGINT and SIGTERM (#75);
|
||||
- List: print message with there's no task with description;
|
||||
- Expand home dir ("~" symbol) on paths (#74);
|
||||
- Add Snap as an installation method;
|
||||
- Move examples to its own repo;
|
||||
- Watch: also walk on tasks called on on "cmds", and not only on "deps";
|
||||
- Print logs to stderr instead of stdout (#68);
|
||||
- Remove deprecated `set` keyword;
|
||||
- Add checksum based status check, alternative to timestamp based.
|
||||
|
||||
## v1.4.3 - 2017-09-07
|
||||
|
||||
- Allow assigning variables to tasks at run time via CLI (#33)
|
||||
- Added suport for multiline variables from sh (#64)
|
||||
- Fixes env: remove square braces and evaluate shell (#62)
|
||||
- Watch: change watch library and few fixes and improvements
|
||||
- When use watching, cancel and restart long running process on file change (#59 and #60)
|
||||
|
||||
## v1.4.2 - 2017-07-30
|
||||
|
||||
- Flag to set directory of execution
|
||||
- Always echo command if is verbose mode
|
||||
- Add silent mode to disable echoing of commands
|
||||
- Fixes and improvements of variables (#56)
|
||||
|
||||
## v1.4.1 - 2017-07-15
|
||||
|
||||
- Allow use of YAML for dynamic variables instead of $ prefix
|
||||
- `VAR: {sh: echo Hello}` instead of `VAR: $echo Hello`
|
||||
- Add `--list` (or `-l`) flag to print existing tasks
|
||||
- OS specific Taskvars file (e.g. `Taskvars_windows.yml`, `Taskvars_linux.yml`, etc)
|
||||
- Consider task up-to-date on equal timestamps (#49)
|
||||
- Allow absolute path in generates section (#48)
|
||||
- Bugfix: allow templating when calling deps (#42)
|
||||
- Fix panic for invalid task in cyclic dep detection
|
||||
- Better error output for dynamic variables in Taskvars.yml (#41)
|
||||
- Allow template evaluation in parameters
|
||||
|
||||
## v1.4.0 - 2017-07-06
|
||||
|
||||
- Cache dynamic variables
|
||||
- Add verbose mode (`-v` flag)
|
||||
- Support to task parameters (overriding vars) (#31) (#32)
|
||||
- Print command, also when "set:" is specified (#35)
|
||||
- Improve task command help text (#35)
|
||||
|
||||
## v1.3.1 - 2017-06-14
|
||||
|
||||
- Fix glob not working on commands (#28)
|
||||
- Add ExeExt template function
|
||||
- Add `--init` flag to create a new Taskfile
|
||||
- Add status option to prevent task from running (#27)
|
||||
- Allow interpolation on `generates` and `sources` attributes (#26)
|
||||
|
||||
## v1.3.0 - 2017-04-24
|
||||
|
||||
- Migrate from os/exec.Cmd to a native Go sh/bash interpreter
|
||||
- This is a potentially breaking change if you use Windows.
|
||||
- Now, `cmd` is not used anymore on Windows. Always use Bash-like syntax for your commands, even on Windows.
|
||||
- Add "ToSlash" and "FromSlash" to template functions
|
||||
- Use functions defined on github.com/Masterminds/sprig
|
||||
- Do not redirect stdin while running variables commands
|
||||
- Using `context` and `errgroup` packages (this will make other tasks to be cancelled, if one returned an error)
|
||||
|
||||
## v1.2.0 - 2017-04-02
|
||||
|
||||
- More tests and Travis integration
|
||||
- Watch a task (experimental)
|
||||
- Possibility to call another task
|
||||
- Fix "=" not being reconized in variables/environment variables
|
||||
- Tasks can now have a description, and help will print them (#10)
|
||||
- Task dependencies now run concurrently
|
||||
- Support for a default task (#16)
|
||||
|
||||
## v1.1.0 - 2017-03-08
|
||||
|
||||
- Support for YAML, TOML and JSON (#1)
|
||||
- Support running command in another directory (#4)
|
||||
- `--force` or `-f` flag to force execution of task even when it's up-to-date
|
||||
- Detection of cyclic dependencies (#5)
|
||||
- Support for variables (#6, #9, #14)
|
||||
- Operation System specific commands and variables (#13)
|
||||
|
||||
## v1.0.0 - 2017-02-28
|
||||
|
||||
- Add LICENSE file
|
||||
123
Gopkg.lock
generated
123
Gopkg.lock
generated
@@ -1,123 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Masterminds/semver"
|
||||
packages = ["."]
|
||||
revision = "15d8430ab86497c5c0da827b748823945e1cf1e1"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Masterminds/sprig"
|
||||
packages = ["."]
|
||||
revision = "82f6f19d47b416d27ae039939b44afaa0575860e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/aokoli/goutils"
|
||||
packages = ["."]
|
||||
revision = "3391d3790d23d03408670993e957e8f408993c34"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/huandu/xstrings"
|
||||
packages = ["."]
|
||||
revision = "d6590c0c31d16526217fa60fbd2067f7afcd78c5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/imdario/mergo"
|
||||
packages = ["."]
|
||||
revision = "7fe0c75c13abdee74b09fcacef5ea1c6bba6a874"
|
||||
version = "0.2.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-zglob"
|
||||
packages = [".","fastwalk"]
|
||||
revision = "4b74c24375b3b1ee226867156e01996f4e19a8d6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/radovskyb/watcher"
|
||||
packages = ["."]
|
||||
revision = "6145e1439b9de93806925353403f91d2abbad8a5"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/satori/go.uuid"
|
||||
packages = ["."]
|
||||
revision = "879c5887cd475cd7864858769793b2ceb0d44feb"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
revision = "4c012f6dcd9546820e378d0bdda4d8fc772cdfea"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
|
||||
version = "v1.1.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["pbkdf2","scrypt","ssh/terminal"]
|
||||
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context"]
|
||||
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["errgroup"]
|
||||
revision = "fd80eb99c8f653c847d294a001bdf2a3a6f768f5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix","windows"]
|
||||
revision = "0ac51a24ef1c37380f98ba8b98f56e3bffd59850"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "287cf08546ab5e7e37d55a84f7ed3fd1db036de5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "mvdan.cc/sh"
|
||||
packages = ["interp","syntax"]
|
||||
revision = "5758e57655f2f2242603195aaaad08d1cb9f8f85"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "da52cb2c602c1362c303cf241aa18dfd6199f30484bb12684adb0b6927391cbf"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
103
Gopkg.toml
103
Gopkg.toml
@@ -1,103 +0,0 @@
|
||||
|
||||
## Gopkg.toml example (these lines may be deleted)
|
||||
|
||||
## "metadata" defines metadata about the project that could be used by other independent
|
||||
## systems. The metadata defined here will be ignored by dep.
|
||||
# [metadata]
|
||||
# key1 = "value that convey data to other systems"
|
||||
# system1-data = "value that is used by a system"
|
||||
# system2-data = "value that is used by another system"
|
||||
|
||||
## "required" lists a set of packages (not projects) that must be included in
|
||||
## Gopkg.lock. This list is merged with the set of packages imported by the current
|
||||
## project. Use it when your project needs a package it doesn't explicitly import -
|
||||
## including "main" packages.
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
|
||||
## "ignored" lists a set of packages (not projects) that are ignored when
|
||||
## dep statically analyzes source code. Ignored packages can be in this project,
|
||||
## or in a dependency.
|
||||
# ignored = ["github.com/user/project/badpkg"]
|
||||
|
||||
## Constraints are rules for how directly imported projects
|
||||
## may be incorporated into the depgraph. They are respected by
|
||||
## dep whether coming from the Gopkg.toml of the current project or a dependency.
|
||||
# [[constraint]]
|
||||
## Required: the root import path of the project being constrained.
|
||||
# name = "github.com/user/project"
|
||||
#
|
||||
## Recommended: the version constraint to enforce for the project.
|
||||
## Only one of "branch", "version" or "revision" can be specified.
|
||||
# version = "1.0.0"
|
||||
# branch = "master"
|
||||
# revision = "abc123"
|
||||
#
|
||||
## Optional: an alternate location (URL or import path) for the project's source.
|
||||
# source = "https://github.com/myfork/package.git"
|
||||
#
|
||||
## "metadata" defines metadata about the dependency or override that could be used
|
||||
## by other independent systems. The metadata defined here will be ignored by dep.
|
||||
# [metadata]
|
||||
# key1 = "value that convey data to other systems"
|
||||
# system1-data = "value that is used by a system"
|
||||
# system2-data = "value that is used by another system"
|
||||
|
||||
## Overrides have the same structure as [[constraint]], but supersede all
|
||||
## [[constraint]] declarations from all projects. Only [[override]] from
|
||||
## the current project's are applied.
|
||||
##
|
||||
## Overrides are a sledgehammer. Use them only as a last resort.
|
||||
# [[override]]
|
||||
## Required: the root import path of the project being constrained.
|
||||
# name = "github.com/user/project"
|
||||
#
|
||||
## Optional: specifying a version constraint override will cause all other
|
||||
## constraints on this project to be ignored; only the overridden constraint
|
||||
## need be satisfied.
|
||||
## Again, only one of "branch", "version" or "revision" can be specified.
|
||||
# version = "1.0.0"
|
||||
# branch = "master"
|
||||
# revision = "abc123"
|
||||
#
|
||||
## Optional: specifying an alternate source location as an override will
|
||||
## enforce that the alternate location is used for that project, regardless of
|
||||
## what source location any dependent projects specify.
|
||||
# source = "https://github.com/myfork/package.git"
|
||||
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/Masterminds/sprig"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/imdario/mergo"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-zglob"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "mvdan.cc/sh"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/spf13/pflag"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sync"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/radovskyb/watcher"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
568
README.md
568
README.md
@@ -1,568 +1,22 @@
|
||||
[](https://gophers.slack.com/messages/task)
|
||||
[](https://travis-ci.org/go-task/task)
|
||||
|
||||
# Task - A task runner / simpler Make alternative written in Go
|
||||
# Task
|
||||
|
||||
Task is a simple tool that allows you to easily run development and build
|
||||
tasks. Task is written in Golang, but can be used to develop any language.
|
||||
It aims to be simpler and easier to use then [GNU Make][make].
|
||||
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/).
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Go](#go)
|
||||
- [Snap](#snap)
|
||||
- [Binary](#binary)
|
||||
- [Usage](#usage)
|
||||
- [Environment](#environment)
|
||||
- [OS specific task](#os-specific-task)
|
||||
- [Task directory](#task-directory)
|
||||
- [Task dependencies](#task-dependencies)
|
||||
- [Calling another task](#calling-another-task)
|
||||
- [Prevent unnecessary work](#prevent-unnecessary-work)
|
||||
- [Variables](#variables)
|
||||
- [Dynamic variables](#dynamic-variables)
|
||||
- [Go's template engine](#gos-template-engine)
|
||||
- [Help](#help)
|
||||
- [Silent mode](#silent-mode)
|
||||
- [Watch tasks](#watch-tasks-experimental)
|
||||
- [Examples](#examples)
|
||||
- [Task in the wild](#task-in-the-wild)
|
||||
- [Alternative task runners](#alternative-task-runners)
|
||||
See [taskfile.dev](https://taskfile.dev) for documentation.
|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
### Go
|
||||
## Sponsors
|
||||
|
||||
If you have a [Golang][golang] environment setup, you can simply run:
|
||||
[](https://opencollective.com/task)
|
||||
|
||||
```bash
|
||||
go get -u -v github.com/go-task/task/cmd/task
|
||||
```
|
||||
## Backers
|
||||
|
||||
### Snap
|
||||
[](https://opencollective.com/task)
|
||||
|
||||
Task is available for [Snapcraft][snapcraft], but keep in mind that your
|
||||
Linux distribution should allow classic confinement for Snaps to Task work
|
||||
right:
|
||||
## Contributors
|
||||
|
||||
```bash
|
||||
sudo snap install task
|
||||
```
|
||||
|
||||
### Binary
|
||||
|
||||
Or you can download the binary from the [releases][releases] page and add to
|
||||
your `PATH`. DEB and RPM packages are also available.
|
||||
The `task_checksums.txt` file contains the sha256 checksum for each file.
|
||||
|
||||
## Usage
|
||||
|
||||
Create a file called `Taskfile.yml` in the root of the project.
|
||||
The `cmds` attribute should contains the commands of a task.
|
||||
The example below allows compile a Go app and uses [Minify][minify] to concat
|
||||
and minify multiple CSS files into a single one.
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
assets:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
```
|
||||
|
||||
Running the tasks is as simple as running:
|
||||
|
||||
```bash
|
||||
task assets build
|
||||
```
|
||||
|
||||
Task uses [github.com/mvdan/sh](https://github.com/mvdan/sh), a native Go sh
|
||||
interpreter. So you can write sh/bash commands and it will work even on
|
||||
Windows, where `sh` or `bash` is usually not available. Just remember any
|
||||
executable called must be available by the OS or in PATH.
|
||||
|
||||
If you ommit a task name, "default" will be assumed.
|
||||
|
||||
### Environment
|
||||
|
||||
You can specify environment variables that are added when running a command:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo $hallo
|
||||
env:
|
||||
hallo: welt
|
||||
```
|
||||
|
||||
### OS specific task
|
||||
|
||||
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your taskfile
|
||||
based on the operating system.
|
||||
|
||||
Example:
|
||||
|
||||
Taskfile.yml:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo "default"
|
||||
```
|
||||
|
||||
Taskfile_linux.yml:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo "linux"
|
||||
```
|
||||
|
||||
Will print out `linux` and not default.
|
||||
|
||||
It's also possible to have OS specific `Taskvars.yml` file, like
|
||||
`Taskvars_windows.yml`, `Taskfile_linux.yml` or `Taskvars_darwin.yml`. See the
|
||||
[variables section](#variables) below.
|
||||
|
||||
### Task directory
|
||||
|
||||
By default, tasks will be executed in the directory where the Taskfile is
|
||||
located. But you can easily make the task run in another folder informing
|
||||
`dir`:
|
||||
|
||||
```yml
|
||||
serve:
|
||||
dir: public/www
|
||||
cmds:
|
||||
# run http server
|
||||
- caddy
|
||||
```
|
||||
|
||||
### Task dependencies
|
||||
|
||||
You may have tasks that depends on others. Just pointing them on `deps` will
|
||||
make them run automatically before running the parent task:
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [assets]
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
assets:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
```
|
||||
|
||||
In the above example, `assets` will always run right before `build` if you run
|
||||
`task build`.
|
||||
|
||||
A task can have only dependencies and no commands to group tasks together:
|
||||
|
||||
```yml
|
||||
assets:
|
||||
deps: [js, css]
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- minify -o public/script.js src/js
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
```
|
||||
|
||||
If there are more than one dependency, they always run in parallel for better
|
||||
performance.
|
||||
|
||||
### Calling another task
|
||||
|
||||
When a task has many dependencies, they are executed concurrently. This will
|
||||
often result in a faster build pipeline. But in some situations you may need
|
||||
to call other tasks serially. In this case, just use the following syntax:
|
||||
|
||||
```yml
|
||||
main-task:
|
||||
cmds:
|
||||
- task: task-to-be-called
|
||||
- task: another-task
|
||||
- echo "Both done"
|
||||
|
||||
task-to-be-called:
|
||||
cmds:
|
||||
- echo "Task to be called"
|
||||
|
||||
another-task:
|
||||
cmds:
|
||||
- echo "Another task"
|
||||
```
|
||||
|
||||
Overriding variables in the called task is as simple as informing `vars`
|
||||
attribute:
|
||||
|
||||
```yml
|
||||
main-task:
|
||||
cmds:
|
||||
- task: write-file
|
||||
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
|
||||
- task: write-file
|
||||
vars: {FILE: "world.txt", CONTENT: "World!"}
|
||||
|
||||
write-file:
|
||||
cmds:
|
||||
- echo "{{.CONTENT}}" > {{.FILE}}
|
||||
```
|
||||
|
||||
The above syntax is also supported in `deps`.
|
||||
|
||||
> NOTE: It's also possible to call a task without any param prefixing it
|
||||
with `^`, but this syntax is deprecated:
|
||||
|
||||
```yml
|
||||
a-task:
|
||||
cmds:
|
||||
- ^another-task
|
||||
|
||||
another-task:
|
||||
cmds:
|
||||
- echo "Another task"
|
||||
```
|
||||
|
||||
### Prevent unnecessary work
|
||||
|
||||
If a task generates something, you can inform Task the source and generated
|
||||
files, so Task will prevent to run them if not necessary.
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [js, css]
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- minify -o public/script.js src/js
|
||||
sources:
|
||||
- src/js/**/*.js
|
||||
generates:
|
||||
- public/script.js
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
sources:
|
||||
- src/css/**/*.css
|
||||
generates:
|
||||
- public/style.css
|
||||
```
|
||||
|
||||
`sources` and `generates` can be files or file patterns. When both are given,
|
||||
Task will compare the modification date/time of the files to determine if it's
|
||||
necessary to run the task. If not, it will just print a message like
|
||||
`Task "js" is up to date`.
|
||||
|
||||
If you prefer this check to be made by the content of the files, instead of
|
||||
its timestamp, just set the `method` property to `checksum`.
|
||||
You will probably want to ignore the `.task` folder in your `.gitignore` file
|
||||
(It's there that Task stores the last checksum).
|
||||
This feature is still experimental and can change until it's stable.
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- go build .
|
||||
sources:
|
||||
- ./*.go
|
||||
generates:
|
||||
- app{{exeExt}}
|
||||
method: checksum
|
||||
```
|
||||
|
||||
> TIP: method `none` skips any validation and always run the task.
|
||||
|
||||
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:
|
||||
|
||||
```yml
|
||||
generate-files:
|
||||
cmds:
|
||||
- mkdir directory
|
||||
- touch directory/file1.txt
|
||||
- touch directory/file2.txt
|
||||
# test existence of files
|
||||
status:
|
||||
- test -d directory
|
||||
- test -f directory/file1.txt
|
||||
- test -f directory/file2.txt
|
||||
```
|
||||
|
||||
You can use `--force` or `-f` if you want to force a task to run even when
|
||||
up-to-date.
|
||||
|
||||
### 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 given while calling a task from another.
|
||||
(See [Calling another task](#calling-another-task) above)
|
||||
- Environment variables
|
||||
- Variables declared locally in the task
|
||||
- Variables available in the `Taskvars.yml` file
|
||||
|
||||
Example of overriding with environment variables:
|
||||
|
||||
```bash
|
||||
$ TASK_VARIABLE=a-value task do-something
|
||||
```
|
||||
|
||||
Since some shells don't support above syntax to set environment variables
|
||||
(Windows) tasks also accepts a similar style when not in the beginning of
|
||||
the command. Variables given in this form are only visible to the task called
|
||||
right before.
|
||||
|
||||
```bash
|
||||
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
||||
```
|
||||
|
||||
Example of locally declared vars:
|
||||
|
||||
```yml
|
||||
print-var:
|
||||
cmds:
|
||||
echo "{{.VAR}}"
|
||||
vars:
|
||||
VAR: Hello!
|
||||
```
|
||||
|
||||
Example of `Taskvars.yml` file:
|
||||
|
||||
```yml
|
||||
PROJECT_NAME: My Project
|
||||
DEV_MODE: production
|
||||
GIT_COMMIT: {sh: git log -n 1 --format=%h}
|
||||
```
|
||||
|
||||
#### Dynamic variables
|
||||
|
||||
The below syntax (`sh:` prop in a variable) is considered a dynamic variable.
|
||||
The value will be treated as a command and the output assigned. If there is one
|
||||
or more trailing newlines, the last newline will be trimmed.
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go
|
||||
vars:
|
||||
GIT_COMMIT:
|
||||
sh: git log -n 1 --format=%h
|
||||
```
|
||||
|
||||
This works for all types of variables.
|
||||
|
||||
> It's also possible to prefix the variable with `$` to have a dynamic
|
||||
variable, but this is now considered deprecated:
|
||||
|
||||
```yml
|
||||
# Taskvars.yml
|
||||
|
||||
# recommended
|
||||
GIT_COMMIT:
|
||||
sh: git log -n 1 --format=%h
|
||||
|
||||
# deprecated
|
||||
GIT_COMMIT: $git log -n 1 --format=%h
|
||||
```
|
||||
|
||||
### Go's template engine
|
||||
|
||||
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/)
|
||||
are available. The following example gets the current date in a given format:
|
||||
|
||||
```yml
|
||||
print-date:
|
||||
cmds:
|
||||
- echo {{now | date "2006-01-02"}}
|
||||
```
|
||||
|
||||
Task also adds the following functions:
|
||||
|
||||
- `OS`: Returns operating system. Possible values are "windows", "linux",
|
||||
"darwin" (macOS) and "freebsd".
|
||||
- `ARCH`: return the architecture Task was compiled to: "386", "amd64", "arm"
|
||||
or "s390x".
|
||||
- `splitLines`: Splits Unix (\n) and Windows (\r\n) styled newlines.
|
||||
- `catLines`: Replaces Unix (\n) and Windows (\r\n) styled newlines with a space.
|
||||
- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\`
|
||||
path format to `/`.
|
||||
- `fromSlash`: Oposite of `toSlash`. Does nothing on Unix, but on Windows
|
||||
converts a string from `\` path format to `/`.
|
||||
- `exeExt`: Returns the right executable extension for the current OS
|
||||
(`".exe"` for Windows, `""` for others).
|
||||
|
||||
Example:
|
||||
|
||||
```yml
|
||||
print-os:
|
||||
cmds:
|
||||
- echo '{{OS}} {{ARCH}}'
|
||||
- echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}'
|
||||
# This will be path/to/file on Unix but path\to\file on Windows
|
||||
- echo '{{fromSlash "path/to/file"}}'
|
||||
enumerated-file:
|
||||
vars:
|
||||
CONTENT: |
|
||||
foo
|
||||
bar
|
||||
cmds:
|
||||
- |
|
||||
cat << EOF > output.txt
|
||||
{{range $i, $line := .CONTENT | splitLines -}}
|
||||
{{printf "%3d" $i}}: {{$line}}
|
||||
{{end}}EOF
|
||||
```
|
||||
|
||||
> NOTE: There are some deprecated function names still available: `ToSlash`,
|
||||
`FromSlash` and `ExeExt`. These where changed for consistency with sprig lib.
|
||||
|
||||
### Help
|
||||
|
||||
Running `task --list` (or `task -l`) lists all tasks with a description.
|
||||
The following taskfile:
|
||||
|
||||
```yml
|
||||
build:
|
||||
desc: Build the go binary.
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
test:
|
||||
desc: Run all the go tests.
|
||||
cmds:
|
||||
- go test -race ./...
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- minify -o public/script.js src/js
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
```
|
||||
|
||||
would print the following output:
|
||||
|
||||
```bash
|
||||
* build: Build the go binary.
|
||||
* test: Run all the go tests.
|
||||
```
|
||||
|
||||
## Silent mode
|
||||
|
||||
Silent mode disables echoing of commands before Task runs it.
|
||||
For the following Taskfile:
|
||||
|
||||
```yml
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Print something"
|
||||
```
|
||||
|
||||
Normally this will be print:
|
||||
|
||||
```sh
|
||||
echo "Print something"
|
||||
Print something
|
||||
```
|
||||
|
||||
With silent mode on, the below will be print instead:
|
||||
|
||||
```sh
|
||||
Print something
|
||||
```
|
||||
|
||||
There's three ways to enable silent mode:
|
||||
|
||||
* At command level:
|
||||
|
||||
```yml
|
||||
echo:
|
||||
cmds:
|
||||
- cmd: echo "Print something"
|
||||
silent: true
|
||||
```
|
||||
|
||||
* At task level:
|
||||
|
||||
```yml
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Print something"
|
||||
silent: true
|
||||
```
|
||||
|
||||
* Or globally with `--silent` or `-s` flag
|
||||
|
||||
If you want to suppress stdout instead, just redirect a command to `/dev/null`:
|
||||
|
||||
```yml
|
||||
echo:
|
||||
cmds:
|
||||
- echo "This will print nothing" > /dev/null
|
||||
```
|
||||
|
||||
## Watch tasks (experimental)
|
||||
|
||||
If you give a `--watch` or `-w` argument, task will watch for files changes
|
||||
and run the task again. This requires the `sources` attribute to be given,
|
||||
so task know which files to watch.
|
||||
|
||||
## Examples
|
||||
|
||||
The [go-task/examples][examples] intends to be a collection of Taskfiles for
|
||||
various use cases.
|
||||
(It still lacks many examples, though. Contributions are welcome).
|
||||
|
||||
## Task in the wild
|
||||
|
||||
- [How I Build My Static Assets for Hugo][post-hugo]
|
||||
|
||||
## Alternative task runners
|
||||
|
||||
- YAML based:
|
||||
- [tj/robo][robo]
|
||||
- [dogtools/dog][dog]
|
||||
- [goeuro/myke][myke]
|
||||
- [dreadl0ck/zeus][zeus]
|
||||
- [rliebz/tusk][tusk]
|
||||
- Go based:
|
||||
- [go-godo/godo][godo]
|
||||
- [markbates/grift][grift]
|
||||
- [nstratos/make.go][make.go]
|
||||
- [magefile/mage][mage]
|
||||
- Make based:
|
||||
- [tj/mmake][mmake]
|
||||
|
||||
[make]: https://www.gnu.org/software/make/
|
||||
[releases]: https://github.com/go-task/task/releases
|
||||
[golang]: https://golang.org/
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[robo]: https://github.com/tj/robo
|
||||
[dog]: https://github.com/dogtools/dog
|
||||
[myke]: https://github.com/goeuro/myke
|
||||
[zeus]: https://github.com/dreadl0ck/zeus
|
||||
[tusk]: https://github.com/rliebz/tusk
|
||||
[godo]: https://github.com/go-godo/godo
|
||||
[grift]: https://github.com/markbates/grift
|
||||
[make.go]: https://github.com/nstratos/make.go
|
||||
[mage]: https://github.com/magefile/mage
|
||||
[mmake]: https://github.com/tj/mmake
|
||||
[sh]: https://github.com/mvdan/sh
|
||||
[post-hugo]: https://blog.carlmjohnson.net/post/2017/hugo-asset-pipeline/
|
||||
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify
|
||||
[examples]: https://github.com/go-task/examples
|
||||
[snapcraft]: https://snapcraft.io/
|
||||
[](https://github.com/go-task/task/graphs/contributors)
|
||||
|
||||
127
Taskfile.yml
127
Taskfile.yml
@@ -1,55 +1,88 @@
|
||||
# compiles current source code and make "task" executable available on
|
||||
# $GOPATH/bin/task{.exe}
|
||||
install:
|
||||
desc: Installs Task
|
||||
cmds:
|
||||
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
|
||||
version: '2'
|
||||
|
||||
dl-deps:
|
||||
desc: Downloads cli dependencies
|
||||
cmds:
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- go get -u github.com/goreleaser/goreleaser
|
||||
- go get -u github.com/asticode/go-astitodo/astitodo
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
includes:
|
||||
docs: ./docs
|
||||
|
||||
update-deps:
|
||||
desc: Updates dependencies
|
||||
cmds:
|
||||
- dep ensure
|
||||
- dep ensure -update
|
||||
- dep prune
|
||||
vars:
|
||||
GIT_COMMIT:
|
||||
sh: git log -n 1 --format=%h
|
||||
|
||||
clean:
|
||||
desc: Cleans temp files and folders
|
||||
cmds:
|
||||
- rm -rf dist/
|
||||
GO_PACKAGES:
|
||||
sh: go list ./...
|
||||
|
||||
lint:
|
||||
desc: Runs golint
|
||||
cmds:
|
||||
- golint {{.GO_PACKAGES}}
|
||||
silent: true
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: test
|
||||
|
||||
test:
|
||||
desc: Runs test suite
|
||||
deps: [install]
|
||||
cmds:
|
||||
- go test {{.GO_PACKAGES}}
|
||||
install:
|
||||
desc: Installs Task
|
||||
cmds:
|
||||
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
|
||||
# https://github.com/goreleaser/goreleaser
|
||||
release:
|
||||
desc: Release Task
|
||||
cmds:
|
||||
- goreleaser
|
||||
dl-deps:
|
||||
desc: Downloads cli dependencies
|
||||
cmds:
|
||||
- task: go-get
|
||||
vars: {REPO: golang.org/x/lint/golint}
|
||||
- task: go-get
|
||||
vars: {REPO: github.com/goreleaser/goreleaser}
|
||||
- task: go-get
|
||||
vars: {REPO: github.com/goreleaser/godownloader}
|
||||
|
||||
test-release:
|
||||
desc: Tests release process without publishing
|
||||
cmds:
|
||||
- goreleaser --skip-validate --skip-publish
|
||||
vendor:
|
||||
desc: Sync vendor/ directory according to go.mod file
|
||||
cmds:
|
||||
- go mod vendor
|
||||
|
||||
todo:
|
||||
desc: Prints TODO comments present in the code
|
||||
cmds:
|
||||
- astitodo {{.GO_PACKAGES}}
|
||||
silent: true
|
||||
update-deps:
|
||||
desc: Updates dependencies
|
||||
cmds:
|
||||
- dep ensure
|
||||
- dep ensure -update
|
||||
|
||||
clean:
|
||||
desc: Cleans temp files and folders
|
||||
cmds:
|
||||
- rm -rf dist/
|
||||
|
||||
lint:
|
||||
desc: Runs golint
|
||||
cmds:
|
||||
- golint {{catLines .GO_PACKAGES}}
|
||||
silent: true
|
||||
|
||||
test:
|
||||
desc: Runs test suite
|
||||
deps: [install]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}}
|
||||
|
||||
test-release:
|
||||
desc: Tests release process without publishing
|
||||
cmds:
|
||||
- goreleaser --snapshot --rm-dist
|
||||
|
||||
generate-install-script:
|
||||
desc: Generate install script using https://github.com/goreleaser/godownloader
|
||||
cmds:
|
||||
- godownloader --repo go-task/task -o install-task.sh
|
||||
- cp ./install-task.sh ./docs/install.sh
|
||||
|
||||
ci:
|
||||
cmds:
|
||||
- task: go-get
|
||||
vars: {REPO: golang.org/x/lint/golint}
|
||||
- task: lint
|
||||
- task: test
|
||||
|
||||
go-get:
|
||||
cmds:
|
||||
- go get -u {{.REPO}}
|
||||
|
||||
packages:
|
||||
cmds:
|
||||
- echo '{{.GO_PACKAGES}}'
|
||||
silent: true
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
GIT_COMMIT:
|
||||
sh: git log -n 1 --format=%h
|
||||
|
||||
GO_PACKAGES:
|
||||
.
|
||||
./cmd/task
|
||||
./internal/args
|
||||
./internal/execext
|
||||
./internal/status
|
||||
421
cmd/redirector/redirector.go
Normal file
421
cmd/redirector/redirector.go
Normal file
@@ -0,0 +1,421 @@
|
||||
// This small web app is used to redirect from the old taskfile.org domain
|
||||
// to the new taskfile.dev without breaking CIs that uses cURL to download
|
||||
// "/install.sh" without the -L flag (which follow redirects).
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/install.sh" {
|
||||
println("Dumping install.sh")
|
||||
|
||||
w.Write(installShContent)
|
||||
return
|
||||
}
|
||||
|
||||
println("Redirecting to https://taskfile.dev" + r.URL.Path)
|
||||
|
||||
w.Header().Set("Location", "https://taskfile.dev"+r.URL.Path)
|
||||
w.WriteHeader(301)
|
||||
})
|
||||
|
||||
println("Listening :8080")
|
||||
|
||||
panic(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
var installShContent = []byte(`#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT.
|
||||
#
|
||||
|
||||
usage() {
|
||||
this=$1
|
||||
cat <<EOF
|
||||
$this: download go binaries for go-task/task
|
||||
|
||||
Usage: $this [-b] bindir [-d] [tag]
|
||||
-b sets bindir or installation directory, Defaults to ./bin
|
||||
-d turns on debug logging
|
||||
[tag] is a tag from
|
||||
https://github.com/go-task/task/releases
|
||||
If tag is missing, then the latest will be used.
|
||||
|
||||
Generated by godownloader
|
||||
https://github.com/goreleaser/godownloader
|
||||
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
#BINDIR is ./bin unless set be ENV
|
||||
# over-ridden by flag below
|
||||
|
||||
BINDIR=${BINDIR:-./bin}
|
||||
while getopts "b:dh?" arg; do
|
||||
case "$arg" in
|
||||
b) BINDIR="$OPTARG" ;;
|
||||
d) log_set_priority 10 ;;
|
||||
h | \?) usage "$0" ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
TAG=$1
|
||||
}
|
||||
# this function wraps all the destructive operations
|
||||
# if a curl|bash cuts off the end of the script due to
|
||||
# network, either nothing will happen or will syntax error
|
||||
# out preventing half-done work
|
||||
execute() {
|
||||
tmpdir=$(mktmpdir)
|
||||
log_debug "downloading files into ${tmpdir}"
|
||||
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
||||
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
||||
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
||||
srcdir="${tmpdir}"
|
||||
(cd "${tmpdir}" && untar "${TARBALL}")
|
||||
install -d "${BINDIR}"
|
||||
for binexe in "task" ; do
|
||||
if [ "$OS" = "windows" ]; then
|
||||
binexe="${binexe}.exe"
|
||||
fi
|
||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
||||
log_info "installed ${BINDIR}/${binexe}"
|
||||
done
|
||||
}
|
||||
is_supported_platform() {
|
||||
platform=$1
|
||||
found=1
|
||||
case "$platform" in
|
||||
windows/386) found=0 ;;
|
||||
windows/amd64) found=0 ;;
|
||||
darwin/386) found=0 ;;
|
||||
darwin/amd64) found=0 ;;
|
||||
linux/386) found=0 ;;
|
||||
linux/amd64) found=0 ;;
|
||||
esac
|
||||
case "$platform" in
|
||||
darwin/386) found=1 ;;
|
||||
esac
|
||||
return $found
|
||||
}
|
||||
check_platform() {
|
||||
if is_supported_platform "$PLATFORM"; then
|
||||
# optional logging goes here
|
||||
true
|
||||
else
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
tag_to_version() {
|
||||
if [ -z "${TAG}" ]; then
|
||||
log_info "checking GitHub for latest tag"
|
||||
else
|
||||
log_info "checking GitHub for tag '${TAG}'"
|
||||
fi
|
||||
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
|
||||
if test -z "$REALTAG"; then
|
||||
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
|
||||
exit 1
|
||||
fi
|
||||
# if version starts with 'v', remove it
|
||||
TAG="$REALTAG"
|
||||
VERSION=${TAG#v}
|
||||
}
|
||||
adjust_format() {
|
||||
# change format (tar.gz or zip) based on ARCH
|
||||
case ${ARCH} in
|
||||
windows) FORMAT=zip ;;
|
||||
esac
|
||||
true
|
||||
}
|
||||
adjust_os() {
|
||||
# adjust archive name based on OS
|
||||
true
|
||||
}
|
||||
adjust_arch() {
|
||||
# adjust archive name based on ARCH
|
||||
true
|
||||
}
|
||||
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
https://github.com/client9/shlib - portable posix shell functions
|
||||
Public domain - http://unlicense.org
|
||||
https://github.com/client9/shlib/blob/master/LICENSE.md
|
||||
but credit (and pull requests) appreciated.
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
is_command() {
|
||||
command -v "$1" >/dev/null
|
||||
}
|
||||
echoerr() {
|
||||
echo "$@" 1>&2
|
||||
}
|
||||
log_prefix() {
|
||||
echo "$0"
|
||||
}
|
||||
_logp=6
|
||||
log_set_priority() {
|
||||
_logp="$1"
|
||||
}
|
||||
log_priority() {
|
||||
if test -z "$1"; then
|
||||
echo "$_logp"
|
||||
return
|
||||
fi
|
||||
[ "$1" -le "$_logp" ]
|
||||
}
|
||||
log_tag() {
|
||||
case $1 in
|
||||
0) echo "emerg" ;;
|
||||
1) echo "alert" ;;
|
||||
2) echo "crit" ;;
|
||||
3) echo "err" ;;
|
||||
4) echo "warning" ;;
|
||||
5) echo "notice" ;;
|
||||
6) echo "info" ;;
|
||||
7) echo "debug" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
log_debug() {
|
||||
log_priority 7 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
|
||||
}
|
||||
log_info() {
|
||||
log_priority 6 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
|
||||
}
|
||||
log_err() {
|
||||
log_priority 3 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
|
||||
}
|
||||
log_crit() {
|
||||
log_priority 2 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
|
||||
}
|
||||
uname_os() {
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
case "$os" in
|
||||
msys_nt) os="windows" ;;
|
||||
esac
|
||||
echo "$os"
|
||||
}
|
||||
uname_arch() {
|
||||
arch=$(uname -m)
|
||||
case $arch in
|
||||
x86_64) arch="amd64" ;;
|
||||
x86) arch="386" ;;
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="arm5" ;;
|
||||
armv6*) arch="arm6" ;;
|
||||
armv7*) arch="arm7" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
uname_os_check() {
|
||||
os=$(uname_os)
|
||||
case "$os" in
|
||||
darwin) return 0 ;;
|
||||
dragonfly) return 0 ;;
|
||||
freebsd) return 0 ;;
|
||||
linux) return 0 ;;
|
||||
android) return 0 ;;
|
||||
nacl) return 0 ;;
|
||||
netbsd) return 0 ;;
|
||||
openbsd) return 0 ;;
|
||||
plan9) return 0 ;;
|
||||
solaris) return 0 ;;
|
||||
windows) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
uname_arch_check() {
|
||||
arch=$(uname_arch)
|
||||
case "$arch" in
|
||||
386) return 0 ;;
|
||||
amd64) return 0 ;;
|
||||
arm64) return 0 ;;
|
||||
armv5) return 0 ;;
|
||||
armv6) return 0 ;;
|
||||
armv7) return 0 ;;
|
||||
ppc64) return 0 ;;
|
||||
ppc64le) return 0 ;;
|
||||
mips) return 0 ;;
|
||||
mipsle) return 0 ;;
|
||||
mips64) return 0 ;;
|
||||
mips64le) return 0 ;;
|
||||
s390x) return 0 ;;
|
||||
amd64p32) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
untar() {
|
||||
tarball=$1
|
||||
case "${tarball}" in
|
||||
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
|
||||
*.tar) tar -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
mktmpdir() {
|
||||
test -z "$TMPDIR" && TMPDIR="$(mktemp -d)"
|
||||
mkdir -p "${TMPDIR}"
|
||||
echo "${TMPDIR}"
|
||||
}
|
||||
http_download_curl() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
||||
else
|
||||
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
||||
fi
|
||||
if [ "$code" != "200" ]; then
|
||||
log_debug "http_download_curl received HTTP status $code"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
http_download_wget() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
wget -q -O "$local_file" "$source_url"
|
||||
else
|
||||
wget -q --header "$header" -O "$local_file" "$source_url"
|
||||
fi
|
||||
}
|
||||
http_download() {
|
||||
log_debug "http_download $2"
|
||||
if is_command curl; then
|
||||
http_download_curl "$@"
|
||||
return
|
||||
elif is_command wget; then
|
||||
http_download_wget "$@"
|
||||
return
|
||||
fi
|
||||
log_crit "http_download unable to find wget or curl"
|
||||
return 1
|
||||
}
|
||||
http_copy() {
|
||||
tmp=$(mktemp)
|
||||
http_download "${tmp}" "$1" "$2" || return 1
|
||||
body=$(cat "$tmp")
|
||||
rm -f "${tmp}"
|
||||
echo "$body"
|
||||
}
|
||||
github_release() {
|
||||
owner_repo=$1
|
||||
version=$2
|
||||
test -z "$version" && version="latest"
|
||||
giturl="https://github.com/${owner_repo}/releases/${version}"
|
||||
json=$(http_copy "$giturl" "Accept:application/json")
|
||||
test -z "$json" && return 1
|
||||
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
|
||||
test -z "$version" && return 1
|
||||
echo "$version"
|
||||
}
|
||||
hash_sha256() {
|
||||
TARGET=${1:-/dev/stdin}
|
||||
if is_command gsha256sum; then
|
||||
hash=$(gsha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command sha256sum; then
|
||||
hash=$(sha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command shasum; then
|
||||
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command openssl; then
|
||||
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f a
|
||||
else
|
||||
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
hash_sha256_verify() {
|
||||
TARGET=$1
|
||||
checksums=$2
|
||||
if [ -z "$checksums" ]; then
|
||||
log_err "hash_sha256_verify checksum file not specified in arg2"
|
||||
return 1
|
||||
fi
|
||||
BASENAME=${TARGET##*/}
|
||||
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
||||
if [ -z "$want" ]; then
|
||||
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
||||
return 1
|
||||
fi
|
||||
got=$(hash_sha256 "$TARGET")
|
||||
if [ "$want" != "$got" ]; then
|
||||
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
End of functions from https://github.com/client9/shlib
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
|
||||
PROJECT_NAME="task"
|
||||
OWNER=go-task
|
||||
REPO="task"
|
||||
BINARY=task
|
||||
FORMAT=tar.gz
|
||||
OS=$(uname_os)
|
||||
ARCH=$(uname_arch)
|
||||
PREFIX="$OWNER/$REPO"
|
||||
|
||||
# use in logging routines
|
||||
log_prefix() {
|
||||
echo "$PREFIX"
|
||||
}
|
||||
PLATFORM="${OS}/${ARCH}"
|
||||
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
|
||||
|
||||
uname_os_check "$OS"
|
||||
uname_arch_check "$ARCH"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
check_platform
|
||||
|
||||
tag_to_version
|
||||
|
||||
adjust_format
|
||||
|
||||
adjust_os
|
||||
|
||||
adjust_arch
|
||||
|
||||
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
||||
|
||||
NAME=${BINARY}_${OS}_${ARCH}
|
||||
TARBALL=${NAME}.${FORMAT}
|
||||
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
||||
CHECKSUM=task_checksums.txt
|
||||
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
||||
|
||||
|
||||
execute
|
||||
`)
|
||||
@@ -5,10 +5,11 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-task/task"
|
||||
"github.com/go-task/task/internal/args"
|
||||
"github.com/go-task/task/v2"
|
||||
"github.com/go-task/task/v2/internal/args"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -17,7 +18,7 @@ var (
|
||||
version = "master"
|
||||
)
|
||||
|
||||
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [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.
|
||||
@@ -50,21 +51,31 @@ func main() {
|
||||
versionFlag bool
|
||||
init bool
|
||||
list bool
|
||||
status bool
|
||||
force bool
|
||||
watch bool
|
||||
verbose bool
|
||||
silent bool
|
||||
dry bool
|
||||
summary bool
|
||||
dir string
|
||||
entrypoint string
|
||||
output string
|
||||
)
|
||||
|
||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||
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")
|
||||
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
|
||||
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.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.Parse()
|
||||
|
||||
if versionFlag {
|
||||
@@ -83,20 +94,34 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
e := task.Executor{
|
||||
Force: force,
|
||||
Watch: watch,
|
||||
Verbose: verbose,
|
||||
Silent: silent,
|
||||
Dir: dir,
|
||||
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"
|
||||
}
|
||||
|
||||
Context: getSignalContext(),
|
||||
e := task.Executor{
|
||||
Force: force,
|
||||
Watch: watch,
|
||||
Verbose: verbose,
|
||||
Silent: silent,
|
||||
Dir: dir,
|
||||
Dry: dry,
|
||||
Entrypoint: entrypoint,
|
||||
Summary: summary,
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
|
||||
OutputStyle: output,
|
||||
}
|
||||
if err := e.ReadTaskfile(); err != nil {
|
||||
if err := e.Setup(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -111,12 +136,24 @@ func main() {
|
||||
arguments = []string{"default"}
|
||||
}
|
||||
|
||||
calls, err := args.Parse(arguments...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
calls, globals := args.Parse(arguments...)
|
||||
for name, value := range globals {
|
||||
e.Taskfile.Vars[name] = value
|
||||
}
|
||||
|
||||
if err := e.Run(calls...); err != nil {
|
||||
ctx := context.Background()
|
||||
if !watch {
|
||||
ctx = getSignalContext()
|
||||
}
|
||||
|
||||
if status {
|
||||
if err := e.Status(ctx, calls...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.Run(ctx, calls...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
25
completion/zsh/_task
Executable file
25
completion/zsh/_task
Executable file
@@ -0,0 +1,25 @@
|
||||
#compdef task
|
||||
|
||||
# Listing commands from Taskfile.yml
|
||||
function __list() {
|
||||
local -a scripts
|
||||
|
||||
if [ -f Taskfile.yml ]; then
|
||||
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/'))
|
||||
_describe 'script' scripts
|
||||
fi
|
||||
}
|
||||
|
||||
_arguments \
|
||||
'(-d --dir)'{-d,--dir}': :_files' \
|
||||
'(--dry)'--dry \
|
||||
'(-f --force)'{-f,--force} \
|
||||
'(-i --init)'{-i,--init} \
|
||||
'(-l --list)'{-l,--list} \
|
||||
'(-s --silent)'{-s,--silent} \
|
||||
'(--status)'--status \
|
||||
'(-v --verbose)'{-v,--verbose} \
|
||||
'(--version)'--version \
|
||||
'(-w --watch)'{-w,--watch} \
|
||||
'(- *)'{-h,--help} \
|
||||
'*: :__list' \
|
||||
0
docs/.nojekyll
Normal file
0
docs/.nojekyll
Normal file
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
taskfile.dev
|
||||
59
docs/README.md
Normal file
59
docs/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Task
|
||||
|
||||
Task is a task runner / build tool that aims to be simpler and easier to use
|
||||
than, for example, [GNU Make][make].
|
||||
|
||||
Since it's written in [Go][go], Task is just a single binary and has no other
|
||||
dependencies, which means you don't need to mess with any complicated install
|
||||
setups just to use a build tool.
|
||||
|
||||
Once [installed](installation.md), you just need to describe your build tasks
|
||||
using a simple [YAML][yaml] schema in a file called `Taskfile.yml`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
hello:
|
||||
cmds:
|
||||
- echo 'Hello World from Task!'
|
||||
silent: true
|
||||
```
|
||||
|
||||
And call it by running `task hello` from you terminal.
|
||||
|
||||
The above example is just the start, you can take a look at the [usage](usage.md)
|
||||
guide to check the full schema documentation and Task features.
|
||||
|
||||
## Features
|
||||
|
||||
- [Easy installation](installation.md): just download a single binary, add to
|
||||
$PATH and you're done! Or you can also install using [Homebrew][homebrew],
|
||||
[Snapcraft][snapcraft], or [Scoop][scoop] if you want;
|
||||
- Available on CIs: by adding [this simple command](installation.md#install-script)
|
||||
to install on your CI script and you're done to use Task as part of your CI pipeline;
|
||||
- Truly cross-platform: while most build tools only work well on Linux or macOS,
|
||||
Task also supports Windows thanks to [this awesome shell interpreter for Go][sh];
|
||||
- Great for code generation: you can easily [prevent a task from running](usage.md#prevent-unnecessary-work)
|
||||
if a given set of files haven't changed since last run (based either on its
|
||||
timestamp or content).
|
||||
|
||||
## Sponsors
|
||||
|
||||
[](https://opencollective.com/task)
|
||||
|
||||
## Backers
|
||||
|
||||
[](https://opencollective.com/task)
|
||||
|
||||
## Contributors
|
||||
|
||||
[](https://github.com/go-task/task/graphs/contributors)
|
||||
|
||||
[make]: https://www.gnu.org/software/make/
|
||||
[go]: https://golang.org/
|
||||
[yaml]: http://yaml.org/
|
||||
[homebrew]: https://brew.sh/
|
||||
[snapcraft]: https://snapcraft.io/
|
||||
[scoop]: https://scoop.sh/
|
||||
[sh]: https://mvdan.cc/sh
|
||||
12
docs/Taskfile.yml
Normal file
12
docs/Taskfile.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
install:
|
||||
desc: Installs docsify to work the on the documentation site
|
||||
cmds:
|
||||
- npm install docsify-cli -g
|
||||
|
||||
serve:
|
||||
desc: Serves the documentation site locally
|
||||
cmds:
|
||||
- docsify serve docs
|
||||
7
docs/_sidebar.md
Normal file
7
docs/_sidebar.md
Normal file
@@ -0,0 +1,7 @@
|
||||
- [Installation](installation.md)
|
||||
- [Usage](usage.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)
|
||||
17
docs/alternative_task_runners.md
Normal file
17
docs/alternative_task_runners.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Alternative task runners
|
||||
|
||||
## YAML based
|
||||
|
||||
- [rliebz/tusk][tusk]
|
||||
|
||||
## Go based
|
||||
|
||||
- [magefile/mage][mage]
|
||||
|
||||
## Make similar
|
||||
|
||||
- [casey/just][just]
|
||||
|
||||
[tusk]: https://github.com/rliebz/tusk
|
||||
[mage]: https://github.com/magefile/mage
|
||||
[just]: https://github.com/casey/just
|
||||
7
docs/examples.md
Normal file
7
docs/examples.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Examples
|
||||
|
||||
The [go-task/examples][examples] intends to be a collection of Taskfiles for
|
||||
various use cases.
|
||||
(It still lacks many examples, though. Contributions are welcome).
|
||||
|
||||
[examples]: https://github.com/go-task/examples
|
||||
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
49
docs/index.html
Normal file
49
docs/index.html
Normal file
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Task</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="A task runner / simpler Make alternative written in Go">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify-themeable/dist/css/theme-simple.css">
|
||||
<meta name="google-site-verification" content="VGAYkbdmuaciIDGkBe-eAg9yfZg0C6ostgonbGxxOa0" />
|
||||
<script>
|
||||
var SeedAndDewConfig = {};
|
||||
(function() {
|
||||
SeedAndDewConfig['adClass'] = "snd-ad";
|
||||
/* * * DON'T EDIT BELOW THIS LINE * * */
|
||||
SeedAndDewConfig['projectId'] = '16e0aed0-b265-48c9-9eae-0aad56147553';
|
||||
SeedAndDewConfig['loadStartTime'] = performance.now();
|
||||
SeedAndDewConfig['apiVersion'] = '2018-05-28'
|
||||
SeedAndDewConfig['sessionId'] = Math.random().toString(36).substring(2, 15);
|
||||
var snd = document.createElement('script');
|
||||
snd.type = 'text/javascript';
|
||||
snd.async = true;
|
||||
snd.src = 'https://www.seedanddew.com/static/embed.min.js';
|
||||
(document.getElementsByTagName('head')[0] ||
|
||||
document.getElementsByTagName('body')[0]).appendChild(snd);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'Task',
|
||||
repo: 'go-task/task',
|
||||
ga: 'UA-126286662-1',
|
||||
themeColor: '#00add8',
|
||||
loadSidebar: true,
|
||||
auto2top: true,
|
||||
maxLevel: 3,
|
||||
subMaxLevel: 3
|
||||
}
|
||||
</script>
|
||||
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/ga.min.js"></script>
|
||||
<script src="//unpkg.com/docsify-themeable"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-yaml.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
390
docs/install.sh
Executable file
390
docs/install.sh
Executable file
@@ -0,0 +1,390 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT.
|
||||
#
|
||||
|
||||
usage() {
|
||||
this=$1
|
||||
cat <<EOF
|
||||
$this: download go binaries for go-task/task
|
||||
|
||||
Usage: $this [-b] bindir [-d] [tag]
|
||||
-b sets bindir or installation directory, Defaults to ./bin
|
||||
-d turns on debug logging
|
||||
[tag] is a tag from
|
||||
https://github.com/go-task/task/releases
|
||||
If tag is missing, then the latest will be used.
|
||||
|
||||
Generated by godownloader
|
||||
https://github.com/goreleaser/godownloader
|
||||
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
#BINDIR is ./bin unless set be ENV
|
||||
# over-ridden by flag below
|
||||
|
||||
BINDIR=${BINDIR:-./bin}
|
||||
while getopts "b:dh?" arg; do
|
||||
case "$arg" in
|
||||
b) BINDIR="$OPTARG" ;;
|
||||
d) log_set_priority 10 ;;
|
||||
h | \?) usage "$0" ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
TAG=$1
|
||||
}
|
||||
# this function wraps all the destructive operations
|
||||
# if a curl|bash cuts off the end of the script due to
|
||||
# network, either nothing will happen or will syntax error
|
||||
# out preventing half-done work
|
||||
execute() {
|
||||
tmpdir=$(mktmpdir)
|
||||
log_debug "downloading files into ${tmpdir}"
|
||||
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
||||
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
||||
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
||||
srcdir="${tmpdir}"
|
||||
(cd "${tmpdir}" && untar "${TARBALL}")
|
||||
install -d "${BINDIR}"
|
||||
for binexe in "task" ; do
|
||||
if [ "$OS" = "windows" ]; then
|
||||
binexe="${binexe}.exe"
|
||||
fi
|
||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
||||
log_info "installed ${BINDIR}/${binexe}"
|
||||
done
|
||||
}
|
||||
is_supported_platform() {
|
||||
platform=$1
|
||||
found=1
|
||||
case "$platform" in
|
||||
windows/386) found=0 ;;
|
||||
windows/amd64) found=0 ;;
|
||||
darwin/386) found=0 ;;
|
||||
darwin/amd64) found=0 ;;
|
||||
linux/386) found=0 ;;
|
||||
linux/amd64) found=0 ;;
|
||||
esac
|
||||
case "$platform" in
|
||||
darwin/386) found=1 ;;
|
||||
esac
|
||||
return $found
|
||||
}
|
||||
check_platform() {
|
||||
if is_supported_platform "$PLATFORM"; then
|
||||
# optional logging goes here
|
||||
true
|
||||
else
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
tag_to_version() {
|
||||
if [ -z "${TAG}" ]; then
|
||||
log_info "checking GitHub for latest tag"
|
||||
else
|
||||
log_info "checking GitHub for tag '${TAG}'"
|
||||
fi
|
||||
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
|
||||
if test -z "$REALTAG"; then
|
||||
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
|
||||
exit 1
|
||||
fi
|
||||
# if version starts with 'v', remove it
|
||||
TAG="$REALTAG"
|
||||
VERSION=${TAG#v}
|
||||
}
|
||||
adjust_format() {
|
||||
# change format (tar.gz or zip) based on ARCH
|
||||
case ${ARCH} in
|
||||
windows) FORMAT=zip ;;
|
||||
esac
|
||||
true
|
||||
}
|
||||
adjust_os() {
|
||||
# adjust archive name based on OS
|
||||
true
|
||||
}
|
||||
adjust_arch() {
|
||||
# adjust archive name based on ARCH
|
||||
true
|
||||
}
|
||||
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
https://github.com/client9/shlib - portable posix shell functions
|
||||
Public domain - http://unlicense.org
|
||||
https://github.com/client9/shlib/blob/master/LICENSE.md
|
||||
but credit (and pull requests) appreciated.
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
is_command() {
|
||||
command -v "$1" >/dev/null
|
||||
}
|
||||
echoerr() {
|
||||
echo "$@" 1>&2
|
||||
}
|
||||
log_prefix() {
|
||||
echo "$0"
|
||||
}
|
||||
_logp=6
|
||||
log_set_priority() {
|
||||
_logp="$1"
|
||||
}
|
||||
log_priority() {
|
||||
if test -z "$1"; then
|
||||
echo "$_logp"
|
||||
return
|
||||
fi
|
||||
[ "$1" -le "$_logp" ]
|
||||
}
|
||||
log_tag() {
|
||||
case $1 in
|
||||
0) echo "emerg" ;;
|
||||
1) echo "alert" ;;
|
||||
2) echo "crit" ;;
|
||||
3) echo "err" ;;
|
||||
4) echo "warning" ;;
|
||||
5) echo "notice" ;;
|
||||
6) echo "info" ;;
|
||||
7) echo "debug" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
log_debug() {
|
||||
log_priority 7 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
|
||||
}
|
||||
log_info() {
|
||||
log_priority 6 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
|
||||
}
|
||||
log_err() {
|
||||
log_priority 3 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
|
||||
}
|
||||
log_crit() {
|
||||
log_priority 2 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
|
||||
}
|
||||
uname_os() {
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
case "$os" in
|
||||
msys_nt) os="windows" ;;
|
||||
esac
|
||||
echo "$os"
|
||||
}
|
||||
uname_arch() {
|
||||
arch=$(uname -m)
|
||||
case $arch in
|
||||
x86_64) arch="amd64" ;;
|
||||
x86) arch="386" ;;
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="arm5" ;;
|
||||
armv6*) arch="arm6" ;;
|
||||
armv7*) arch="arm7" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
uname_os_check() {
|
||||
os=$(uname_os)
|
||||
case "$os" in
|
||||
darwin) return 0 ;;
|
||||
dragonfly) return 0 ;;
|
||||
freebsd) return 0 ;;
|
||||
linux) return 0 ;;
|
||||
android) return 0 ;;
|
||||
nacl) return 0 ;;
|
||||
netbsd) return 0 ;;
|
||||
openbsd) return 0 ;;
|
||||
plan9) return 0 ;;
|
||||
solaris) return 0 ;;
|
||||
windows) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
uname_arch_check() {
|
||||
arch=$(uname_arch)
|
||||
case "$arch" in
|
||||
386) return 0 ;;
|
||||
amd64) return 0 ;;
|
||||
arm64) return 0 ;;
|
||||
armv5) return 0 ;;
|
||||
armv6) return 0 ;;
|
||||
armv7) return 0 ;;
|
||||
ppc64) return 0 ;;
|
||||
ppc64le) return 0 ;;
|
||||
mips) return 0 ;;
|
||||
mipsle) return 0 ;;
|
||||
mips64) return 0 ;;
|
||||
mips64le) return 0 ;;
|
||||
s390x) return 0 ;;
|
||||
amd64p32) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
untar() {
|
||||
tarball=$1
|
||||
case "${tarball}" in
|
||||
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
|
||||
*.tar) tar -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
mktmpdir() {
|
||||
test -z "$TMPDIR" && TMPDIR="$(mktemp -d)"
|
||||
mkdir -p "${TMPDIR}"
|
||||
echo "${TMPDIR}"
|
||||
}
|
||||
http_download_curl() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
||||
else
|
||||
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
||||
fi
|
||||
if [ "$code" != "200" ]; then
|
||||
log_debug "http_download_curl received HTTP status $code"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
http_download_wget() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
wget -q -O "$local_file" "$source_url"
|
||||
else
|
||||
wget -q --header "$header" -O "$local_file" "$source_url"
|
||||
fi
|
||||
}
|
||||
http_download() {
|
||||
log_debug "http_download $2"
|
||||
if is_command curl; then
|
||||
http_download_curl "$@"
|
||||
return
|
||||
elif is_command wget; then
|
||||
http_download_wget "$@"
|
||||
return
|
||||
fi
|
||||
log_crit "http_download unable to find wget or curl"
|
||||
return 1
|
||||
}
|
||||
http_copy() {
|
||||
tmp=$(mktemp)
|
||||
http_download "${tmp}" "$1" "$2" || return 1
|
||||
body=$(cat "$tmp")
|
||||
rm -f "${tmp}"
|
||||
echo "$body"
|
||||
}
|
||||
github_release() {
|
||||
owner_repo=$1
|
||||
version=$2
|
||||
test -z "$version" && version="latest"
|
||||
giturl="https://github.com/${owner_repo}/releases/${version}"
|
||||
json=$(http_copy "$giturl" "Accept:application/json")
|
||||
test -z "$json" && return 1
|
||||
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
|
||||
test -z "$version" && return 1
|
||||
echo "$version"
|
||||
}
|
||||
hash_sha256() {
|
||||
TARGET=${1:-/dev/stdin}
|
||||
if is_command gsha256sum; then
|
||||
hash=$(gsha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command sha256sum; then
|
||||
hash=$(sha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command shasum; then
|
||||
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command openssl; then
|
||||
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f a
|
||||
else
|
||||
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
hash_sha256_verify() {
|
||||
TARGET=$1
|
||||
checksums=$2
|
||||
if [ -z "$checksums" ]; then
|
||||
log_err "hash_sha256_verify checksum file not specified in arg2"
|
||||
return 1
|
||||
fi
|
||||
BASENAME=${TARGET##*/}
|
||||
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
||||
if [ -z "$want" ]; then
|
||||
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
||||
return 1
|
||||
fi
|
||||
got=$(hash_sha256 "$TARGET")
|
||||
if [ "$want" != "$got" ]; then
|
||||
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
End of functions from https://github.com/client9/shlib
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
|
||||
PROJECT_NAME="task"
|
||||
OWNER=go-task
|
||||
REPO="task"
|
||||
BINARY=task
|
||||
FORMAT=tar.gz
|
||||
OS=$(uname_os)
|
||||
ARCH=$(uname_arch)
|
||||
PREFIX="$OWNER/$REPO"
|
||||
|
||||
# use in logging routines
|
||||
log_prefix() {
|
||||
echo "$PREFIX"
|
||||
}
|
||||
PLATFORM="${OS}/${ARCH}"
|
||||
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
|
||||
|
||||
uname_os_check "$OS"
|
||||
uname_arch_check "$ARCH"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
check_platform
|
||||
|
||||
tag_to_version
|
||||
|
||||
adjust_format
|
||||
|
||||
adjust_os
|
||||
|
||||
adjust_arch
|
||||
|
||||
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
||||
|
||||
NAME=${BINARY}_${OS}_${ARCH}
|
||||
TARBALL=${NAME}.${FORMAT}
|
||||
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
||||
CHECKSUM=task_checksums.txt
|
||||
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
||||
|
||||
|
||||
execute
|
||||
92
docs/installation.md
Normal file
92
docs/installation.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Installation
|
||||
|
||||
## Binary
|
||||
|
||||
Or you can download the binary from the [releases][releases] page and add to
|
||||
your $PATH. DEB and RPM packages are also available.
|
||||
The `task_checksums.txt` file contains the sha256 checksum for each file.
|
||||
|
||||
## Homebrew
|
||||
|
||||
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
|
||||
Linux distribution should allow classic confinement for Snaps to Task work
|
||||
right:
|
||||
|
||||
```bash
|
||||
sudo snap install task
|
||||
```
|
||||
|
||||
## Scoop
|
||||
|
||||
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
|
||||
to install Task like:
|
||||
|
||||
```cmd
|
||||
scoop bucket add extras
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
git clone https://github.com/go-task/task
|
||||
cd task
|
||||
|
||||
# compiling binary to $GOPATH/bin
|
||||
go install -v
|
||||
|
||||
# compiling it to another location
|
||||
# use -o ./task.exe on Windows
|
||||
go build -v -o ./task ./cmd/task
|
||||
```
|
||||
|
||||
Both methods requires having the [Go][go] environment properly setup locally.
|
||||
|
||||
## Install script
|
||||
|
||||
We also have a [install script][installscript], which is very useful on
|
||||
scenarios like CIs. Many thanks to [godownloader][godownloader] for allowing
|
||||
easily generating this script.
|
||||
|
||||
```bash
|
||||
curl -sL https://taskfile.dev/install.sh | sh
|
||||
```
|
||||
|
||||
> This method will download the binary on the local `./bin` directory by default.
|
||||
|
||||
[go]: https://golang.org/
|
||||
[snapcraft]: https://snapcraft.io/
|
||||
[homebrew]: https://brew.sh/
|
||||
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
|
||||
[releases]: https://github.com/go-task/task/releases
|
||||
[godownloader]: https://github.com/goreleaser/godownloader
|
||||
[scoop]: https://scoop.sh/
|
||||
39
docs/releasing_task.md
Normal file
39
docs/releasing_task.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Releasing Task
|
||||
|
||||
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)
|
||||
|
||||
# Homebrew
|
||||
|
||||
To release a new version on the [Homebrew tap][homebrewtap] edit the
|
||||
[Formula/go-task.rb][gotaskrb] file, updating with the new version, download
|
||||
URL and sha256.
|
||||
|
||||
# Snapcraft
|
||||
|
||||
The exception is the publishing of a new version of the
|
||||
[snap package][snappackage]. This current require two steps after publishing
|
||||
the binaries:
|
||||
|
||||
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
|
||||
* Moving both `i386` and `amd64` new artifacts to the stable channel on
|
||||
the [Snapcraft dashboard][snapcraftdashboard]
|
||||
|
||||
# Scoop
|
||||
|
||||
Scoop is a community owned installation method. Scoop owners usually take care
|
||||
of updating versions there by editing
|
||||
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
||||
If you think its Task version is outdated, open an issue to let us know.
|
||||
|
||||
[goreleaser]: https://goreleaser.com/#continuous_integration
|
||||
[homebrewtap]: https://github.com/go-task/homebrew-tap
|
||||
[gotaskrb]: https://github.com/go-task/homebrew-tap/blob/master/Formula/go-task.rb
|
||||
[snappackage]: https://github.com/go-task/snap
|
||||
[snapcraftyaml]: https://github.com/go-task/snap/blob/master/snap/snapcraft.yaml#L2
|
||||
[snapcraftdashboard]: https://dashboard.snapcraft.io/
|
||||
163
docs/taskfile_versions.md
Normal file
163
docs/taskfile_versions.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Taskfile Versions
|
||||
|
||||
The Taskfile syntax and features changed with time. This document explains what
|
||||
changed on each version and how to upgrade your Taskfile.
|
||||
|
||||
## What the Taskfile version mean
|
||||
|
||||
The Taskfile version follows the Task version. E.g. the change to Taskfile
|
||||
version `2` means that Task `v2.0.0` should be release to support it.
|
||||
|
||||
The `version:` key on Taskfile accepts a semver string, so either `2`, `2.0` or
|
||||
`2.0.0` is accepted. You you choose to use `2.0` Task will not enable future
|
||||
`2.1` features, but if you choose to use `2`, than any `2.x.x` features will be
|
||||
available, but not `3.0.0+`.
|
||||
|
||||
## Version 1
|
||||
|
||||
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:
|
||||
|
||||
```yaml
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
```
|
||||
|
||||
The variable priority order was also different:
|
||||
|
||||
1. Call variables
|
||||
2. Environment
|
||||
3. Task variables
|
||||
4. `Taskvars.yml` variables
|
||||
|
||||
## Version 2.0
|
||||
|
||||
At version 2, we introduced the `version:` key, to allow us to envolve Task
|
||||
with new features without breaking existing Taskfiles. The new syntax is as
|
||||
follows:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
```
|
||||
|
||||
Version 2 allows you to write global variables directly in the Taskfile,
|
||||
if you don't want to create a `Taskvars.yml`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo "{{.GREETING}}"
|
||||
```
|
||||
|
||||
The variable priority order changed to the following:
|
||||
|
||||
1. Task variables
|
||||
2. Call variables
|
||||
3. Taskfile variables
|
||||
4. Taskvars file variables
|
||||
5. Environment variables
|
||||
|
||||
A new global option was added to configure the number of variables expansions
|
||||
(which default to 2):
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
expansions: 3
|
||||
|
||||
vars:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BAZ: baz
|
||||
FOOBAR: "{{.FOO}}{{.BAR}}"
|
||||
FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "{{.FOOBARBAZ}}"
|
||||
```
|
||||
|
||||
## Version 2.1
|
||||
|
||||
Version 2.1 includes a global `output` option, to allow having more control
|
||||
over how commands output are printed to the console
|
||||
(see [documentation][output] for more info):
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
output: prefixed
|
||||
|
||||
tasks:
|
||||
server:
|
||||
cmds:
|
||||
- go run main.go
|
||||
prefix: server
|
||||
```
|
||||
|
||||
From this version it's not also possible to ignore errors of a command or task
|
||||
(check documentation [here][ignore_errors]):
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
example-1:
|
||||
cmds:
|
||||
- cmd: exit 1
|
||||
ignore_error: true
|
||||
- echo "This will be print"
|
||||
|
||||
example-2:
|
||||
cmds:
|
||||
- exit 1
|
||||
- echo "This will be print"
|
||||
ignore_error: true
|
||||
```
|
||||
|
||||
## Version 2.2
|
||||
|
||||
Version 2.2 comes with a global `includes` options to include other
|
||||
Taskfiles:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
includes:
|
||||
docs: ./documentation # will look for ./documentation/Taskfile.yml
|
||||
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
|
||||
[ignore_errors]: usage.md#ignore-errors
|
||||
[includes]: usage.md#including-other-taskfiles
|
||||
822
docs/usage.md
Normal file
822
docs/usage.md
Normal file
@@ -0,0 +1,822 @@
|
||||
# Usage
|
||||
|
||||
## Getting started
|
||||
|
||||
Create a file called `Taskfile.yml` in the root of your project.
|
||||
The `cmds` attribute should contain the commands of a task.
|
||||
The example below allows compiling a Go app and uses [Minify][minify] to concat
|
||||
and minify multiple CSS files into a single one.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
assets:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
```
|
||||
|
||||
Running the tasks is as simple as running:
|
||||
|
||||
```bash
|
||||
task assets build
|
||||
```
|
||||
|
||||
Task uses [github.com/mvdan/sh](https://github.com/mvdan/sh), a native Go sh
|
||||
interpreter. So you can write sh/bash commands and it will work even on
|
||||
Windows, where `sh` or `bash` are usually not available. Just remember any
|
||||
executable called must be available by the OS or in PATH.
|
||||
|
||||
If you omit a task name, "default" will be assumed.
|
||||
|
||||
## Environment
|
||||
|
||||
You can use `env` to set custom environment variables for a specific task:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo $GREETING
|
||||
env:
|
||||
GREETING: Hey, there!
|
||||
```
|
||||
|
||||
Additionally, you can set globally environment variables, that'll be available
|
||||
to all tasks:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
env:
|
||||
GREETING: Hey, there!
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo $GREETING
|
||||
```
|
||||
|
||||
> NOTE: `env` supports expansion and and retrieving output from a shell command
|
||||
> just like variables, as you can see on the [Variables](#variables) section.
|
||||
|
||||
## Operating System specific tasks
|
||||
|
||||
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
|
||||
based on the operating system.
|
||||
|
||||
Example:
|
||||
|
||||
Taskfile.yml:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- echo "default"
|
||||
```
|
||||
|
||||
Taskfile_linux.yml:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- echo "linux"
|
||||
```
|
||||
|
||||
Will print out `linux` and not `default`.
|
||||
|
||||
Keep in mind that the version of the files should match. Also, when redefining
|
||||
a task the whole task is replaced, properties of the task are not merged.
|
||||
|
||||
It's also possible to have an OS specific `Taskvars.yml` file, like
|
||||
`Taskvars_windows.yml`, `Taskvars_linux.yml`, or `Taskvars_darwin.yml`. See the
|
||||
[variables section](#variables) below.
|
||||
|
||||
## Including other Taskfiles
|
||||
|
||||
> This feature is still experimental and may have bugs.
|
||||
|
||||
If you want to share tasks between different projects (Taskfiles), you can use
|
||||
the importing mechanism to include other Taskfiles using the `includes` keyword:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
includes:
|
||||
docs: ./documentation # will look for ./documentation/Taskfile.yml
|
||||
docker: ./DockerTasks.yml
|
||||
```
|
||||
|
||||
The tasks described in the given Taskfiles will be available with the informed
|
||||
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.
|
||||
|
||||
> The included Taskfiles must be using the same schema version the main
|
||||
> Taskfile uses.
|
||||
|
||||
> Also, for now included Taskfiles can't include other Taskfiles.
|
||||
> This was a deliberate decision to keep use and implementation simple.
|
||||
> If you disagree, open an GitHub issue and explain your use case. =)
|
||||
|
||||
## Task directory
|
||||
|
||||
By default, tasks will be executed in the directory where the Taskfile is
|
||||
located. But you can easily make the task run in another folder informing
|
||||
`dir`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
serve:
|
||||
dir: public/www
|
||||
cmds:
|
||||
# run http server
|
||||
- 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
|
||||
make them run automatically before running the parent task:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
deps: [assets]
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
assets:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
```
|
||||
|
||||
In the above example, `assets` will always run right before `build` if you run
|
||||
`task build`.
|
||||
|
||||
A task can have only dependencies and no commands to group tasks together:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
assets:
|
||||
deps: [js, css]
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- minify -o public/script.js src/js
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
```
|
||||
|
||||
If there is more than one dependency, they always run in parallel for better
|
||||
performance.
|
||||
|
||||
If you want to pass information to dependencies, you can do that the same
|
||||
manner as you would to [call another task](#calling-another-task):
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps:
|
||||
- task: echo_sth
|
||||
vars: {TEXT: "before 1"}
|
||||
- task: echo_sth
|
||||
vars: {TEXT: "before 2"}
|
||||
cmds:
|
||||
- echo "after"
|
||||
|
||||
echo_sth:
|
||||
cmds:
|
||||
- echo {{.TEXT}}
|
||||
```
|
||||
|
||||
## Calling another task
|
||||
|
||||
When a task has many dependencies, they are executed concurrently. This will
|
||||
often result in a faster build pipeline. But in some situations you may need
|
||||
to call other tasks serially. In this case, just use the following syntax:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
main-task:
|
||||
cmds:
|
||||
- task: task-to-be-called
|
||||
- task: another-task
|
||||
- echo "Both done"
|
||||
|
||||
task-to-be-called:
|
||||
cmds:
|
||||
- echo "Task to be called"
|
||||
|
||||
another-task:
|
||||
cmds:
|
||||
- echo "Another task"
|
||||
```
|
||||
|
||||
Overriding variables in the called task is as simple as informing `vars`
|
||||
attribute:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
main-task:
|
||||
cmds:
|
||||
- task: write-file
|
||||
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
|
||||
- task: write-file
|
||||
vars: {FILE: "world.txt", CONTENT: "World!"}
|
||||
|
||||
write-file:
|
||||
cmds:
|
||||
- echo "{{.CONTENT}}" > {{.FILE}}
|
||||
```
|
||||
|
||||
The above syntax is also supported in `deps`.
|
||||
|
||||
> NOTE: If you want to call a task declared in the root Taskfile from within an
|
||||
> [included Taskfile](#including-other-taskfiles), add a leading `:` like this:
|
||||
> `task: :task-name`.
|
||||
|
||||
## Prevent unnecessary work
|
||||
|
||||
If a task generates something, you can inform Task the source and generated
|
||||
files, so Task will prevent to run them if not necessary.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
deps: [js, css]
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- minify -o public/script.js src/js
|
||||
sources:
|
||||
- src/js/**/*.js
|
||||
generates:
|
||||
- public/script.js
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
sources:
|
||||
- src/css/**/*.css
|
||||
generates:
|
||||
- public/style.css
|
||||
```
|
||||
|
||||
`sources` and `generates` can be files or file patterns. When both are given,
|
||||
Task will compare the modification date/time of the files to determine if it's
|
||||
necessary to run the task. If not, it will just print a message like
|
||||
`Task "js" is up to date`.
|
||||
|
||||
If you prefer this check to be made by the content of the files, instead of
|
||||
its timestamp, just set the `method` property to `checksum`.
|
||||
You will probably want to ignore the `.task` folder in your `.gitignore` file
|
||||
(It's there that Task stores the last checksum).
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build .
|
||||
sources:
|
||||
- ./*.go
|
||||
generates:
|
||||
- app{{exeExt}}
|
||||
method: checksum
|
||||
```
|
||||
|
||||
> TIP: method `none` skips any validation and always run the task.
|
||||
|
||||
Alternatively, you can inform a sequence of tests as `status`. If no error
|
||||
is returned (exit status 0), the task is considered up-to-date:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
generate-files:
|
||||
cmds:
|
||||
- mkdir directory
|
||||
- touch directory/file1.txt
|
||||
- touch directory/file2.txt
|
||||
# test existence of files
|
||||
status:
|
||||
- test -d directory
|
||||
- test -f directory/file1.txt
|
||||
- test -f directory/file2.txt
|
||||
```
|
||||
|
||||
You can use `--force` or `-f` if you want to force a task to run even when
|
||||
up-to-date.
|
||||
|
||||
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
||||
the tasks are not up-to-date.
|
||||
|
||||
If you need a certain set of conditions to be _true_ you can use the
|
||||
`preconditions` stanza. `preconditions` are very similar to `status`
|
||||
lines except they support `sh` expansion and they SHOULD all return 0.
|
||||
|
||||
```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 given while calling a task from another.
|
||||
(See [Calling another task](#calling-another-task) above)
|
||||
- Variables declared in the `vars:` option in the `Taskfile`
|
||||
- Variables available in the `Taskvars.yml` file
|
||||
- Environment variables
|
||||
|
||||
Example of sending parameters with environment variables:
|
||||
|
||||
```bash
|
||||
$ TASK_VARIABLE=a-value task do-something
|
||||
```
|
||||
|
||||
Since some shells don't support above syntax to set environment variables
|
||||
(Windows) tasks also accepts a similar style when not in the beginning of
|
||||
the command. Variables given in this form are only visible to the task called
|
||||
right before.
|
||||
|
||||
```bash
|
||||
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
||||
```
|
||||
|
||||
If you want to set global variables using this syntax, give it before any task:
|
||||
|
||||
```bash
|
||||
$ task OUTPUT=file.txt generate-file
|
||||
```
|
||||
|
||||
Example of locally declared vars:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
print-var:
|
||||
cmds:
|
||||
- echo "{{.VAR}}"
|
||||
vars:
|
||||
VAR: Hello!
|
||||
```
|
||||
|
||||
Example of global vars in a `Taskfile.yml`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
vars:
|
||||
GREETING: Hello from Taskfile!
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo "{{.GREETING}}"
|
||||
```
|
||||
|
||||
Example of `Taskvars.yml` file:
|
||||
|
||||
```yaml
|
||||
PROJECT_NAME: My Project
|
||||
DEV_MODE: production
|
||||
GIT_COMMIT: {sh: git log -n 1 --format=%h}
|
||||
```
|
||||
|
||||
### Variables expansion
|
||||
|
||||
Variables are expanded 2 times by default. You can change that by setting the
|
||||
`expansions:` option. Change that will be necessary if you compose many
|
||||
variables together:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
expansions: 3
|
||||
|
||||
vars:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BAZ: baz
|
||||
FOOBAR: "{{.FOO}}{{.BAR}}"
|
||||
FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "{{.FOOBARBAZ}}"
|
||||
```
|
||||
|
||||
### Dynamic variables
|
||||
|
||||
The below syntax (`sh:` prop in a variable) is considered a dynamic variable.
|
||||
The value will be treated as a command and the output assigned. If there is one
|
||||
or more trailing newlines, the last newline will be trimmed.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go
|
||||
vars:
|
||||
GIT_COMMIT:
|
||||
sh: git log -n 1 --format=%h
|
||||
```
|
||||
|
||||
This works for all types of variables.
|
||||
|
||||
## Go's template engine
|
||||
|
||||
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/)
|
||||
are available. The following example gets the current date in a given format:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
print-date:
|
||||
cmds:
|
||||
- echo {{now | date "2006-01-02"}}
|
||||
```
|
||||
|
||||
Task also adds the following functions:
|
||||
|
||||
- `OS`: Returns operating system. Possible values are "windows", "linux",
|
||||
"darwin" (macOS) and "freebsd".
|
||||
- `ARCH`: return the architecture Task was compiled to: "386", "amd64", "arm"
|
||||
or "s390x".
|
||||
- `splitLines`: Splits Unix (\n) and Windows (\r\n) styled newlines.
|
||||
- `catLines`: Replaces Unix (\n) and Windows (\r\n) styled newlines with a space.
|
||||
- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\`
|
||||
path format to `/`.
|
||||
- `fromSlash`: Opposite of `toSlash`. Does nothing on Unix, but on Windows
|
||||
converts a string from `\` path format to `/`.
|
||||
- `exeExt`: Returns the right executable extension for the current OS
|
||||
(`".exe"` for Windows, `""` for others).
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
print-os:
|
||||
cmds:
|
||||
- echo '{{OS}} {{ARCH}}'
|
||||
- echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}'
|
||||
# This will be path/to/file on Unix but path\to\file on Windows
|
||||
- echo '{{fromSlash "path/to/file"}}'
|
||||
enumerated-file:
|
||||
vars:
|
||||
CONTENT: |
|
||||
foo
|
||||
bar
|
||||
cmds:
|
||||
- |
|
||||
cat << EOF > output.txt
|
||||
{{range $i, $line := .CONTENT | splitLines -}}
|
||||
{{printf "%3d" $i}}: {{$line}}
|
||||
{{end}}EOF
|
||||
```
|
||||
|
||||
## Help
|
||||
|
||||
Running `task --list` (or `task -l`) lists all tasks with a description.
|
||||
The following Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
desc: Build the go binary.
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
test:
|
||||
desc: Run all the go tests.
|
||||
cmds:
|
||||
- go test -race ./...
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- minify -o public/script.js src/js
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- minify -o public/style.css src/css
|
||||
```
|
||||
|
||||
would print the following output:
|
||||
|
||||
```bash
|
||||
* build: Build the go binary.
|
||||
* 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.
|
||||
For the following Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Print something"
|
||||
```
|
||||
|
||||
Normally this will be print:
|
||||
|
||||
```sh
|
||||
echo "Print something"
|
||||
Print something
|
||||
```
|
||||
|
||||
With silent mode on, the below will be print instead:
|
||||
|
||||
```sh
|
||||
Print something
|
||||
```
|
||||
|
||||
There's three ways to enable silent mode:
|
||||
|
||||
* At command level:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- cmd: echo "Print something"
|
||||
silent: true
|
||||
```
|
||||
|
||||
* At task level:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Print something"
|
||||
silent: true
|
||||
```
|
||||
|
||||
* Or globally with `--silent` or `-s` flag
|
||||
|
||||
If you want to suppress STDOUT instead, just redirect a command to `/dev/null`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- echo "This will print nothing" > /dev/null
|
||||
```
|
||||
|
||||
## Dry run mode
|
||||
|
||||
Dry run mode (`--dry`) compiles and steps through each task, printing the commands
|
||||
that would be run without executing them. This is useful for debugging your Taskfiles.
|
||||
|
||||
## Ignore errors
|
||||
|
||||
You have the option to ignore errors during command execution.
|
||||
Given the following Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- exit 1
|
||||
- echo "Hello World"
|
||||
```
|
||||
|
||||
Task will abort the execution after running `exit 1` because the status code `1` stands for `EXIT_FAILURE`.
|
||||
However it is possible to continue with execution using `ignore_error`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- cmd: exit 1
|
||||
ignore_error: true
|
||||
- echo "Hello World"
|
||||
```
|
||||
|
||||
`ignore_error` can also be set for a task, which mean errors will be suppressed
|
||||
for all commands. But keep in mind this option won't propagate to other tasks
|
||||
called either by `deps` or `cmds`!
|
||||
|
||||
## Output syntax
|
||||
|
||||
By default, Task just redirect the STDOUT and STDERR of the running commands
|
||||
to the shell in real time. This is good for having live feedback for log
|
||||
printed by commands, but the output can become messy if you have multiple
|
||||
commands running at the same time and printing lots of stuff.
|
||||
|
||||
To make this more customizable, there are currently three different output
|
||||
options you can choose:
|
||||
|
||||
- `interleaved` (default)
|
||||
- `group`
|
||||
- `prefixed`
|
||||
|
||||
To choose another one, just set it to root in the Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
output: 'group'
|
||||
|
||||
tasks:
|
||||
# ...
|
||||
```
|
||||
|
||||
The `group` output will print the entire output of a command once, after it
|
||||
finishes, so you won't have live feedback for commands that take a long time
|
||||
to run.
|
||||
|
||||
The `prefix` output will prefix every line printed by a command with
|
||||
`[task-name] ` as the prefix, but you can customize the prefix for a command
|
||||
with the `prefix:` attribute:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
output: prefixed
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps:
|
||||
- task: print
|
||||
vars: {TEXT: foo}
|
||||
- task: print
|
||||
vars: {TEXT: bar}
|
||||
- task: print
|
||||
vars: {TEXT: baz}
|
||||
|
||||
print:
|
||||
cmds:
|
||||
- echo "{{.TEXT}}"
|
||||
prefix: "print-{{.TEXT}}"
|
||||
silent: true
|
||||
```
|
||||
|
||||
```bash
|
||||
$ task default
|
||||
[print-foo] foo
|
||||
[print-bar] bar
|
||||
[print-baz] baz
|
||||
```
|
||||
|
||||
> The `output` option can also be specified by the `--output` or `-o` flags.
|
||||
|
||||
## Watch tasks
|
||||
|
||||
If you give a `--watch` or `-w` argument, task will watch for file changes
|
||||
and run the task again. This requires the `sources` attribute to be given,
|
||||
so task know which files to watch.
|
||||
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify
|
||||
33
errors.go
33
errors.go
@@ -10,14 +10,6 @@ var (
|
||||
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
|
||||
)
|
||||
|
||||
type taskFileNotFound struct {
|
||||
taskFile string
|
||||
}
|
||||
|
||||
func (err taskFileNotFound) Error() string {
|
||||
return fmt.Sprintf(`task: No task file found (is it named "%s"?). Use "task --init" to create a new one`, err.taskFile)
|
||||
}
|
||||
|
||||
type taskNotFoundError struct {
|
||||
taskName string
|
||||
}
|
||||
@@ -35,31 +27,6 @@ func (err *taskRunError) Error() string {
|
||||
return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
|
||||
}
|
||||
|
||||
type cyclicDepError struct {
|
||||
taskName string
|
||||
}
|
||||
|
||||
func (err *cyclicDepError) Error() string {
|
||||
return fmt.Sprintf(`task: Cyclic dependency of task "%s" detected`, err.taskName)
|
||||
}
|
||||
|
||||
type cantWatchNoSourcesError struct {
|
||||
taskName string
|
||||
}
|
||||
|
||||
func (err *cantWatchNoSourcesError) Error() string {
|
||||
return fmt.Sprintf(`task: Can't watch task "%s" because it has no specified sources`, err.taskName)
|
||||
}
|
||||
|
||||
type dynamicVarError struct {
|
||||
cause error
|
||||
cmd string
|
||||
}
|
||||
|
||||
func (err *dynamicVarError) Error() string {
|
||||
return fmt.Sprintf(`task: Command "%s" in taskvars file failed: %s`, err.cmd, err.cause)
|
||||
}
|
||||
|
||||
// MaximumTaskCallExceededError is returned when a task is called too
|
||||
// many times. In this case you probably have a cyclic dependendy or
|
||||
// infinite loop
|
||||
|
||||
23
go.mod
Normal file
23
go.mod
Normal file
@@ -0,0 +1,23 @@
|
||||
module github.com/go-task/task/v2
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.4.2 // indirect
|
||||
github.com/Masterminds/sprig v2.16.0+incompatible
|
||||
github.com/aokoli/goutils v1.0.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/kr/pretty v0.1.0 // 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.4+incompatible
|
||||
)
|
||||
47
go.sum
Normal file
47
go.sum
Normal file
@@ -0,0 +1,47 @@
|
||||
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/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/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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
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/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/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/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=
|
||||
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.4+incompatible h1:eD6tDeh0pw+/TOTI1BBEryZ02rD2nMcFsgcvde7jffM=
|
||||
mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
|
||||
17
help.go
17
help.go
@@ -4,31 +4,34 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
// PrintTasksHelp prints help os tasks that have a description
|
||||
func (e *Executor) PrintTasksHelp() {
|
||||
tasks := e.tasksWithDesc()
|
||||
if len(tasks) == 0 {
|
||||
e.outf("task: No tasks with description available")
|
||||
e.Logger.Outf("task: No tasks with description available")
|
||||
return
|
||||
}
|
||||
e.outf("task: Available tasks for this project:")
|
||||
e.Logger.Outf("task: Available tasks for this project:")
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
|
||||
for _, task := range tasks {
|
||||
fmt.Fprintln(w, fmt.Sprintf("* %s: \t%s", task, e.Tasks[task].Desc))
|
||||
fmt.Fprintf(w, "* %s: \t%s\n", task.Task, task.Desc)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func (e *Executor) tasksWithDesc() (tasks []string) {
|
||||
for name, task := range e.Tasks {
|
||||
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 != "" {
|
||||
tasks = append(tasks, name)
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
}
|
||||
sort.Strings(tasks)
|
||||
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
|
||||
return
|
||||
}
|
||||
|
||||
29
init.go
29
init.go
@@ -8,24 +8,29 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const defaultTaskfile = `# github.com/go-task/task
|
||||
const defaultTaskfile = `# https://taskfile.dev
|
||||
|
||||
default:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
version: '2'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "{{.GREETING}}"
|
||||
silent: true
|
||||
`
|
||||
|
||||
// InitTaskfile Taskfile creates a new Taskfile
|
||||
func InitTaskfile(w io.Writer, path string) error {
|
||||
for _, f := range []string{"Taskfile.yml", "Taskfile.toml", "Taskfile.json"} {
|
||||
f = filepath.Join(path, f)
|
||||
if _, err := os.Stat(f); err == nil {
|
||||
return ErrTaskfileAlreadyExists
|
||||
}
|
||||
func InitTaskfile(w io.Writer, dir string) error {
|
||||
f := filepath.Join(dir, "Taskfile.yml")
|
||||
|
||||
if _, err := os.Stat(f); err == nil {
|
||||
return ErrTaskfileAlreadyExists
|
||||
}
|
||||
|
||||
f := filepath.Join(path, "Taskfile.yml")
|
||||
if err := ioutil.WriteFile(f, []byte(defaultTaskfile), 0666); err != nil {
|
||||
if err := ioutil.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(w, "Taskfile.yml created in the current directory\n")
|
||||
|
||||
390
install-task.sh
Executable file
390
install-task.sh
Executable file
@@ -0,0 +1,390 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT.
|
||||
#
|
||||
|
||||
usage() {
|
||||
this=$1
|
||||
cat <<EOF
|
||||
$this: download go binaries for go-task/task
|
||||
|
||||
Usage: $this [-b] bindir [-d] [tag]
|
||||
-b sets bindir or installation directory, Defaults to ./bin
|
||||
-d turns on debug logging
|
||||
[tag] is a tag from
|
||||
https://github.com/go-task/task/releases
|
||||
If tag is missing, then the latest will be used.
|
||||
|
||||
Generated by godownloader
|
||||
https://github.com/goreleaser/godownloader
|
||||
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
#BINDIR is ./bin unless set be ENV
|
||||
# over-ridden by flag below
|
||||
|
||||
BINDIR=${BINDIR:-./bin}
|
||||
while getopts "b:dh?" arg; do
|
||||
case "$arg" in
|
||||
b) BINDIR="$OPTARG" ;;
|
||||
d) log_set_priority 10 ;;
|
||||
h | \?) usage "$0" ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
TAG=$1
|
||||
}
|
||||
# this function wraps all the destructive operations
|
||||
# if a curl|bash cuts off the end of the script due to
|
||||
# network, either nothing will happen or will syntax error
|
||||
# out preventing half-done work
|
||||
execute() {
|
||||
tmpdir=$(mktmpdir)
|
||||
log_debug "downloading files into ${tmpdir}"
|
||||
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
||||
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
||||
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
||||
srcdir="${tmpdir}"
|
||||
(cd "${tmpdir}" && untar "${TARBALL}")
|
||||
install -d "${BINDIR}"
|
||||
for binexe in "task" ; do
|
||||
if [ "$OS" = "windows" ]; then
|
||||
binexe="${binexe}.exe"
|
||||
fi
|
||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
||||
log_info "installed ${BINDIR}/${binexe}"
|
||||
done
|
||||
}
|
||||
is_supported_platform() {
|
||||
platform=$1
|
||||
found=1
|
||||
case "$platform" in
|
||||
windows/386) found=0 ;;
|
||||
windows/amd64) found=0 ;;
|
||||
darwin/386) found=0 ;;
|
||||
darwin/amd64) found=0 ;;
|
||||
linux/386) found=0 ;;
|
||||
linux/amd64) found=0 ;;
|
||||
esac
|
||||
case "$platform" in
|
||||
darwin/386) found=1 ;;
|
||||
esac
|
||||
return $found
|
||||
}
|
||||
check_platform() {
|
||||
if is_supported_platform "$PLATFORM"; then
|
||||
# optional logging goes here
|
||||
true
|
||||
else
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
tag_to_version() {
|
||||
if [ -z "${TAG}" ]; then
|
||||
log_info "checking GitHub for latest tag"
|
||||
else
|
||||
log_info "checking GitHub for tag '${TAG}'"
|
||||
fi
|
||||
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
|
||||
if test -z "$REALTAG"; then
|
||||
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
|
||||
exit 1
|
||||
fi
|
||||
# if version starts with 'v', remove it
|
||||
TAG="$REALTAG"
|
||||
VERSION=${TAG#v}
|
||||
}
|
||||
adjust_format() {
|
||||
# change format (tar.gz or zip) based on ARCH
|
||||
case ${ARCH} in
|
||||
windows) FORMAT=zip ;;
|
||||
esac
|
||||
true
|
||||
}
|
||||
adjust_os() {
|
||||
# adjust archive name based on OS
|
||||
true
|
||||
}
|
||||
adjust_arch() {
|
||||
# adjust archive name based on ARCH
|
||||
true
|
||||
}
|
||||
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
https://github.com/client9/shlib - portable posix shell functions
|
||||
Public domain - http://unlicense.org
|
||||
https://github.com/client9/shlib/blob/master/LICENSE.md
|
||||
but credit (and pull requests) appreciated.
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
is_command() {
|
||||
command -v "$1" >/dev/null
|
||||
}
|
||||
echoerr() {
|
||||
echo "$@" 1>&2
|
||||
}
|
||||
log_prefix() {
|
||||
echo "$0"
|
||||
}
|
||||
_logp=6
|
||||
log_set_priority() {
|
||||
_logp="$1"
|
||||
}
|
||||
log_priority() {
|
||||
if test -z "$1"; then
|
||||
echo "$_logp"
|
||||
return
|
||||
fi
|
||||
[ "$1" -le "$_logp" ]
|
||||
}
|
||||
log_tag() {
|
||||
case $1 in
|
||||
0) echo "emerg" ;;
|
||||
1) echo "alert" ;;
|
||||
2) echo "crit" ;;
|
||||
3) echo "err" ;;
|
||||
4) echo "warning" ;;
|
||||
5) echo "notice" ;;
|
||||
6) echo "info" ;;
|
||||
7) echo "debug" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
log_debug() {
|
||||
log_priority 7 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
|
||||
}
|
||||
log_info() {
|
||||
log_priority 6 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
|
||||
}
|
||||
log_err() {
|
||||
log_priority 3 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
|
||||
}
|
||||
log_crit() {
|
||||
log_priority 2 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
|
||||
}
|
||||
uname_os() {
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
case "$os" in
|
||||
msys_nt) os="windows" ;;
|
||||
esac
|
||||
echo "$os"
|
||||
}
|
||||
uname_arch() {
|
||||
arch=$(uname -m)
|
||||
case $arch in
|
||||
x86_64) arch="amd64" ;;
|
||||
x86) arch="386" ;;
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="arm5" ;;
|
||||
armv6*) arch="arm6" ;;
|
||||
armv7*) arch="arm7" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
uname_os_check() {
|
||||
os=$(uname_os)
|
||||
case "$os" in
|
||||
darwin) return 0 ;;
|
||||
dragonfly) return 0 ;;
|
||||
freebsd) return 0 ;;
|
||||
linux) return 0 ;;
|
||||
android) return 0 ;;
|
||||
nacl) return 0 ;;
|
||||
netbsd) return 0 ;;
|
||||
openbsd) return 0 ;;
|
||||
plan9) return 0 ;;
|
||||
solaris) return 0 ;;
|
||||
windows) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
uname_arch_check() {
|
||||
arch=$(uname_arch)
|
||||
case "$arch" in
|
||||
386) return 0 ;;
|
||||
amd64) return 0 ;;
|
||||
arm64) return 0 ;;
|
||||
armv5) return 0 ;;
|
||||
armv6) return 0 ;;
|
||||
armv7) return 0 ;;
|
||||
ppc64) return 0 ;;
|
||||
ppc64le) return 0 ;;
|
||||
mips) return 0 ;;
|
||||
mipsle) return 0 ;;
|
||||
mips64) return 0 ;;
|
||||
mips64le) return 0 ;;
|
||||
s390x) return 0 ;;
|
||||
amd64p32) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
untar() {
|
||||
tarball=$1
|
||||
case "${tarball}" in
|
||||
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
|
||||
*.tar) tar -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
mktmpdir() {
|
||||
test -z "$TMPDIR" && TMPDIR="$(mktemp -d)"
|
||||
mkdir -p "${TMPDIR}"
|
||||
echo "${TMPDIR}"
|
||||
}
|
||||
http_download_curl() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
||||
else
|
||||
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
||||
fi
|
||||
if [ "$code" != "200" ]; then
|
||||
log_debug "http_download_curl received HTTP status $code"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
http_download_wget() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
wget -q -O "$local_file" "$source_url"
|
||||
else
|
||||
wget -q --header "$header" -O "$local_file" "$source_url"
|
||||
fi
|
||||
}
|
||||
http_download() {
|
||||
log_debug "http_download $2"
|
||||
if is_command curl; then
|
||||
http_download_curl "$@"
|
||||
return
|
||||
elif is_command wget; then
|
||||
http_download_wget "$@"
|
||||
return
|
||||
fi
|
||||
log_crit "http_download unable to find wget or curl"
|
||||
return 1
|
||||
}
|
||||
http_copy() {
|
||||
tmp=$(mktemp)
|
||||
http_download "${tmp}" "$1" "$2" || return 1
|
||||
body=$(cat "$tmp")
|
||||
rm -f "${tmp}"
|
||||
echo "$body"
|
||||
}
|
||||
github_release() {
|
||||
owner_repo=$1
|
||||
version=$2
|
||||
test -z "$version" && version="latest"
|
||||
giturl="https://github.com/${owner_repo}/releases/${version}"
|
||||
json=$(http_copy "$giturl" "Accept:application/json")
|
||||
test -z "$json" && return 1
|
||||
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
|
||||
test -z "$version" && return 1
|
||||
echo "$version"
|
||||
}
|
||||
hash_sha256() {
|
||||
TARGET=${1:-/dev/stdin}
|
||||
if is_command gsha256sum; then
|
||||
hash=$(gsha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command sha256sum; then
|
||||
hash=$(sha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command shasum; then
|
||||
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command openssl; then
|
||||
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f a
|
||||
else
|
||||
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
hash_sha256_verify() {
|
||||
TARGET=$1
|
||||
checksums=$2
|
||||
if [ -z "$checksums" ]; then
|
||||
log_err "hash_sha256_verify checksum file not specified in arg2"
|
||||
return 1
|
||||
fi
|
||||
BASENAME=${TARGET##*/}
|
||||
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
||||
if [ -z "$want" ]; then
|
||||
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
||||
return 1
|
||||
fi
|
||||
got=$(hash_sha256 "$TARGET")
|
||||
if [ "$want" != "$got" ]; then
|
||||
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
End of functions from https://github.com/client9/shlib
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
|
||||
PROJECT_NAME="task"
|
||||
OWNER=go-task
|
||||
REPO="task"
|
||||
BINARY=task
|
||||
FORMAT=tar.gz
|
||||
OS=$(uname_os)
|
||||
ARCH=$(uname_arch)
|
||||
PREFIX="$OWNER/$REPO"
|
||||
|
||||
# use in logging routines
|
||||
log_prefix() {
|
||||
echo "$PREFIX"
|
||||
}
|
||||
PLATFORM="${OS}/${ARCH}"
|
||||
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
|
||||
|
||||
uname_os_check "$OS"
|
||||
uname_arch_check "$ARCH"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
check_platform
|
||||
|
||||
tag_to_version
|
||||
|
||||
adjust_format
|
||||
|
||||
adjust_os
|
||||
|
||||
adjust_arch
|
||||
|
||||
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
||||
|
||||
NAME=${BINARY}_${OS}_${ARCH}
|
||||
TARBALL=${NAME}.${FORMAT}
|
||||
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
||||
CHECKSUM=task_checksums.txt
|
||||
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
||||
|
||||
|
||||
execute
|
||||
@@ -1,36 +1,43 @@
|
||||
package args
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrVariableWithoutTask is returned when variables are given before any task
|
||||
ErrVariableWithoutTask = errors.New("task: variable given before any task")
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
// Parse parses command line argument: tasks and vars of each task
|
||||
func Parse(args ...string) ([]task.Call, error) {
|
||||
var calls []task.Call
|
||||
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, task.Call{Task: arg})
|
||||
calls = append(calls, taskfile.Call{Task: arg})
|
||||
continue
|
||||
}
|
||||
|
||||
if len(calls) < 1 {
|
||||
return nil, ErrVariableWithoutTask
|
||||
}
|
||||
if globals == nil {
|
||||
globals = taskfile.Vars{}
|
||||
}
|
||||
|
||||
if calls[len(calls)-1].Vars == nil {
|
||||
calls[len(calls)-1].Vars = make(task.Vars)
|
||||
}
|
||||
name, value := splitVar(arg)
|
||||
globals[name] = taskfile.Var{Static: value}
|
||||
} else {
|
||||
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]] = task.Var{Static: pair[1]}
|
||||
name, value := splitVar((arg))
|
||||
calls[len(calls)-1].Vars[name] = taskfile.Var{Static: value}
|
||||
}
|
||||
}
|
||||
return calls, nil
|
||||
|
||||
return calls, globals
|
||||
}
|
||||
|
||||
func splitVar(s string) (string, string) {
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
return pair[0], pair[1]
|
||||
}
|
||||
|
||||
@@ -4,21 +4,21 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task"
|
||||
"github.com/go-task/task/internal/args"
|
||||
"github.com/go-task/task/v2/internal/args"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
Args []string
|
||||
Expected []task.Call
|
||||
Err error
|
||||
Args []string
|
||||
ExpectedCalls []taskfile.Call
|
||||
ExpectedGlobals taskfile.Vars
|
||||
}{
|
||||
{
|
||||
Args: []string{"task-a", "task-b", "task-c"},
|
||||
Expected: []task.Call{
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
@@ -26,45 +26,51 @@ func TestArgs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||
Expected: []task.Call{
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{
|
||||
Task: "task-a",
|
||||
Vars: task.Vars{
|
||||
"FOO": task.Var{Static: "bar"},
|
||||
Vars: taskfile.Vars{
|
||||
"FOO": taskfile.Var{Static: "bar"},
|
||||
},
|
||||
},
|
||||
{Task: "task-b"},
|
||||
{
|
||||
Task: "task-c",
|
||||
Vars: task.Vars{
|
||||
"BAR": task.Var{Static: "baz"},
|
||||
"BAZ": task.Var{Static: "foo"},
|
||||
Vars: taskfile.Vars{
|
||||
"BAR": taskfile.Var{Static: "baz"},
|
||||
"BAZ": taskfile.Var{Static: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||
Expected: []task.Call{
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{
|
||||
Task: "task-a",
|
||||
Vars: task.Vars{
|
||||
"CONTENT": task.Var{Static: "with some spaces"},
|
||||
Vars: taskfile.Vars{
|
||||
"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{
|
||||
"FOO": {Static: "bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
12
internal/compiler/compiler.go
Normal file
12
internal/compiler/compiler.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
// Compiler handles compilation of a task before its execution.
|
||||
// E.g. variable merger, template processing, etc.
|
||||
type Compiler interface {
|
||||
GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error)
|
||||
HandleDynamicVar(v taskfile.Var) (string, error)
|
||||
}
|
||||
24
internal/compiler/env.go
Normal file
24
internal/compiler/env.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
// GetEnviron the all return all environment variables encapsulated on a
|
||||
// taskfile.Vars
|
||||
func GetEnviron() taskfile.Vars {
|
||||
var (
|
||||
env = os.Environ()
|
||||
m = make(taskfile.Vars, len(env))
|
||||
)
|
||||
|
||||
for _, e := range env {
|
||||
keyVal := strings.SplitN(e, "=", 2)
|
||||
key, val := keyVal[0], keyVal[1]
|
||||
m[key] = taskfile.Var{Static: val}
|
||||
}
|
||||
return m
|
||||
}
|
||||
137
internal/compiler/v1/compiler_v1.go
Normal file
137
internal/compiler/v1/compiler_v1.go
Normal file
@@ -0,0 +1,137 @@
|
||||
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
|
||||
}
|
||||
109
internal/compiler/v2/compiler_v2.go
Normal file
109
internal/compiler/v2/compiler_v2.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package v2
|
||||
|
||||
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 = &CompilerV2{}
|
||||
|
||||
type CompilerV2 struct {
|
||||
Dir string
|
||||
|
||||
Taskvars taskfile.Vars
|
||||
TaskfileVars taskfile.Vars
|
||||
|
||||
Expansions int
|
||||
|
||||
Logger *logger.Logger
|
||||
|
||||
dynamicCache map[string]string
|
||||
muDynamicCache sync.Mutex
|
||||
}
|
||||
|
||||
// GetVariables returns fully resolved variables following the priority order:
|
||||
// 1. Task variables
|
||||
// 2. Call variables
|
||||
// 3. Taskfile variables
|
||||
// 4. Taskvars file variables
|
||||
// 5. Environment variables
|
||||
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} {
|
||||
for i := 0; i < c.Expansions; i++ {
|
||||
vr.merge(vars)
|
||||
}
|
||||
}
|
||||
return vr.vars, vr.err
|
||||
}
|
||||
|
||||
type varResolver struct {
|
||||
c *CompilerV2
|
||||
vars taskfile.Vars
|
||||
err error
|
||||
}
|
||||
|
||||
func (vr *varResolver) merge(vars taskfile.Vars) {
|
||||
if vr.err != nil {
|
||||
return
|
||||
}
|
||||
tr := templater.Templater{Vars: vr.vars}
|
||||
for k, v := range vars {
|
||||
v = taskfile.Var{
|
||||
Static: tr.Replace(v.Static),
|
||||
Sh: tr.Replace(v.Sh),
|
||||
}
|
||||
static, err := vr.c.HandleDynamicVar(v)
|
||||
if err != nil {
|
||||
vr.err = err
|
||||
return
|
||||
}
|
||||
vr.vars[k] = taskfile.Var{Static: static}
|
||||
}
|
||||
vr.err = tr.Err()
|
||||
}
|
||||
|
||||
func (c *CompilerV2) 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
|
||||
}
|
||||
@@ -4,15 +4,18 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/sh/expand"
|
||||
"mvdan.cc/sh/interp"
|
||||
"mvdan.cc/sh/shell"
|
||||
"mvdan.cc/sh/syntax"
|
||||
)
|
||||
|
||||
// RunCommandOptions is the options for the RunCommand func
|
||||
type RunCommandOptions struct {
|
||||
Context context.Context
|
||||
Command string
|
||||
Dir string
|
||||
Env []string
|
||||
@@ -27,7 +30,7 @@ var (
|
||||
)
|
||||
|
||||
// RunCommand runs a shell command
|
||||
func RunCommand(opts *RunCommandOptions) error {
|
||||
func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
if opts == nil {
|
||||
return ErrNilOptions
|
||||
}
|
||||
@@ -37,20 +40,47 @@ func RunCommand(opts *RunCommandOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
r := interp.Runner{
|
||||
Context: opts.Context,
|
||||
Dir: opts.Dir,
|
||||
Env: opts.Env,
|
||||
|
||||
Exec: interp.DefaultExec,
|
||||
Open: interp.OpenDevImpls(interp.DefaultOpen),
|
||||
|
||||
Stdin: opts.Stdin,
|
||||
Stdout: opts.Stdout,
|
||||
Stderr: opts.Stderr,
|
||||
environ := opts.Env
|
||||
if len(environ) == 0 {
|
||||
environ = os.Environ()
|
||||
}
|
||||
if err = r.Reset(); err != nil {
|
||||
|
||||
r, err := interp.New(
|
||||
interp.Dir(opts.Dir),
|
||||
interp.Env(expand.ListEnviron(environ...)),
|
||||
|
||||
interp.Module(interp.DefaultExec),
|
||||
interp.Module(interp.OpenDevImpls(interp.DefaultOpen)),
|
||||
|
||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Run(p)
|
||||
return r.Run(ctx, p)
|
||||
}
|
||||
|
||||
// IsExitError returns true the given error is an exis status error
|
||||
func IsExitError(err error) bool {
|
||||
switch err.(type) {
|
||||
case interp.ExitStatus, interp.ShellExitStatus:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
||||
// if available.
|
||||
func Expand(s string) (string, error) {
|
||||
s = filepath.ToSlash(s)
|
||||
s = strings.Replace(s, " ", `\ `, -1)
|
||||
fields, err := shell.Fields(s, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(fields) > 0 {
|
||||
return fields[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
38
internal/logger/logger.go
Normal file
38
internal/logger/logger.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func (l *Logger) Outf(s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
fmt.Fprintf(l.Stdout, s+"\n", args...)
|
||||
}
|
||||
|
||||
func (l *Logger) VerboseOutf(s string, args ...interface{}) {
|
||||
if l.Verbose {
|
||||
l.Outf(s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Errf(s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
fmt.Fprintf(l.Stderr, s+"\n", args...)
|
||||
}
|
||||
|
||||
func (l *Logger) VerboseErrf(s string, args ...interface{}) {
|
||||
if l.Verbose {
|
||||
l.Errf(s, args...)
|
||||
}
|
||||
}
|
||||
26
internal/output/group.go
Normal file
26
internal/output/group.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Group struct{}
|
||||
|
||||
func (Group) WrapWriter(w io.Writer, _ string) io.Writer {
|
||||
return &groupWriter{writer: w}
|
||||
}
|
||||
|
||||
type groupWriter struct {
|
||||
writer io.Writer
|
||||
buff bytes.Buffer
|
||||
}
|
||||
|
||||
func (gw *groupWriter) Write(p []byte) (int, error) {
|
||||
return gw.buff.Write(p)
|
||||
}
|
||||
|
||||
func (gw *groupWriter) Close() error {
|
||||
_, err := io.Copy(gw.writer, &gw.buff)
|
||||
return err
|
||||
}
|
||||
11
internal/output/interleaved.go
Normal file
11
internal/output/interleaved.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Interleaved struct{}
|
||||
|
||||
func (Interleaved) WrapWriter(w io.Writer, _ string) io.Writer {
|
||||
return w
|
||||
}
|
||||
9
internal/output/output.go
Normal file
9
internal/output/output.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Output interface {
|
||||
WrapWriter(w io.Writer, prefix string) io.Writer
|
||||
}
|
||||
63
internal/output/output_test.go
Normal file
63
internal/output/output_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package output_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2/internal/output"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInterleaved(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Interleaved{}
|
||||
var w = o.WrapWriter(&b, "")
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "foo\nbar\n", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
||||
}
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Group{}
|
||||
var w = o.WrapWriter(&b, "").(io.WriteCloser)
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "", b.String())
|
||||
assert.NoError(t, w.Close())
|
||||
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
||||
}
|
||||
|
||||
func TestPrefixed(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Prefixed{}
|
||||
var w = o.WrapWriter(&b, "prefix").(io.WriteCloser)
|
||||
|
||||
t.Run("simple use cases", func(t *testing.T) {
|
||||
b.Reset()
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n[prefix] baz\n", b.String())
|
||||
})
|
||||
|
||||
t.Run("multiple writes for a single line", func(t *testing.T) {
|
||||
b.Reset()
|
||||
|
||||
for _, char := range []string{"T", "e", "s", "t", "!"} {
|
||||
fmt.Fprint(w, char)
|
||||
assert.Equal(t, "", b.String())
|
||||
}
|
||||
|
||||
assert.NoError(t, w.Close())
|
||||
assert.Equal(t, "[prefix] Test!\n", b.String())
|
||||
})
|
||||
}
|
||||
65
internal/output/prefixed.go
Normal file
65
internal/output/prefixed.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Prefixed struct{}
|
||||
|
||||
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.Writer {
|
||||
return &prefixWriter{writer: w, prefix: prefix}
|
||||
}
|
||||
|
||||
type prefixWriter struct {
|
||||
writer io.Writer
|
||||
prefix string
|
||||
buff bytes.Buffer
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Write(p []byte) (int, error) {
|
||||
n, err := pw.buff.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, pw.writeOutputLines(false)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Close() error {
|
||||
return pw.writeOutputLines(true)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) writeOutputLines(force bool) error {
|
||||
for {
|
||||
switch line, err := pw.buff.ReadString('\n'); err {
|
||||
case nil:
|
||||
if err = pw.writeLine(line); err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
||||
return pw.writeLine(line)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) writeLine(line string) error {
|
||||
if line == "" {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(line, "\n") {
|
||||
line += "\n"
|
||||
}
|
||||
_, err := fmt.Fprintf(pw.writer, "[%s] %s", pw.prefix, line)
|
||||
return err
|
||||
}
|
||||
@@ -17,6 +17,7 @@ type Checksum struct {
|
||||
Dir string
|
||||
Task string
|
||||
Sources []string
|
||||
Dry bool
|
||||
}
|
||||
|
||||
// IsUpToDate implements the Checker interface
|
||||
@@ -36,9 +37,11 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
|
||||
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
||||
return false, err
|
||||
if !c.Dry {
|
||||
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
|
||||
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return oldMd5 == newMd5, nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
|
||||
"github.com/mattn/go-zglob"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func glob(dir string, globs []string) (files []string, err error) {
|
||||
@@ -13,13 +14,13 @@ func glob(dir string, globs []string) (files []string, err error) {
|
||||
if !filepath.IsAbs(g) {
|
||||
g = filepath.Join(dir, g)
|
||||
}
|
||||
g, err = homedir.Expand(g)
|
||||
g, err = execext.Expand(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := zglob.Glob(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
continue
|
||||
}
|
||||
files = append(files, f...)
|
||||
}
|
||||
|
||||
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("")
|
||||
l.Outf("")
|
||||
}
|
||||
|
||||
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(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf("task: %s", t.Task)
|
||||
l.Outf("")
|
||||
}
|
||||
|
||||
func hasDescription(t *taskfile.Task) bool {
|
||||
return t.Desc != ""
|
||||
}
|
||||
|
||||
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(t.Desc)
|
||||
}
|
||||
|
||||
func printNoDescriptionOrSummary(l *logger.Logger) {
|
||||
l.Outf("(task does not have description or summary)")
|
||||
}
|
||||
|
||||
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Deps) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf("")
|
||||
l.Outf("dependencies:")
|
||||
|
||||
for _, d := range t.Deps {
|
||||
l.Outf(" - %s", d.Task)
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Cmds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf("")
|
||||
l.Outf("commands:")
|
||||
for _, c := range t.Cmds {
|
||||
isCommand := c.Cmd != ""
|
||||
if isCommand {
|
||||
l.Outf(" - %s", c.Cmd)
|
||||
} else {
|
||||
l.Outf(" - 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")
|
||||
|
||||
}
|
||||
7
internal/taskfile/call.go
Normal file
7
internal/taskfile/call.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package taskfile
|
||||
|
||||
// Call is the parameters to a task call
|
||||
type Call struct {
|
||||
Task string
|
||||
Vars Vars
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package task
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -7,10 +7,11 @@ import (
|
||||
|
||||
// Cmd is a task command
|
||||
type Cmd struct {
|
||||
Cmd string
|
||||
Silent bool
|
||||
Task string
|
||||
Vars Vars
|
||||
Cmd string
|
||||
Silent bool
|
||||
Task string
|
||||
Vars Vars
|
||||
IgnoreError bool
|
||||
}
|
||||
|
||||
// Dep is a task dependency
|
||||
@@ -38,12 +39,14 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
return nil
|
||||
}
|
||||
var cmdStruct struct {
|
||||
Cmd string
|
||||
Silent bool
|
||||
Cmd string
|
||||
Silent bool
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
}
|
||||
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
|
||||
c.Cmd = cmdStruct.Cmd
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.IgnoreError = cmdStruct.IgnoreError
|
||||
return nil
|
||||
}
|
||||
var taskCall struct {
|
||||
@@ -76,9 +79,3 @@ func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
}
|
||||
return ErrCantUnmarshalDep
|
||||
}
|
||||
|
||||
// Call is the parameters to a task call
|
||||
type Call struct {
|
||||
Task string
|
||||
Vars Vars
|
||||
}
|
||||
73
internal/taskfile/merge.go
Normal file
73
internal/taskfile/merge.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NamespaceSeparator contains the character that separates namescapes
|
||||
const NamespaceSeparator = ":"
|
||||
|
||||
// Merge merges the second Taskfile into the first
|
||||
func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
||||
if t1.Version != t2.Version {
|
||||
return fmt.Errorf(`Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
||||
}
|
||||
|
||||
if t2.Expansions != 0 && t2.Expansions != 2 {
|
||||
t1.Expansions = t2.Expansions
|
||||
}
|
||||
if t2.Output != "" {
|
||||
t1.Output = t2.Output
|
||||
}
|
||||
|
||||
if t1.Includes == nil {
|
||||
t1.Includes = make(map[string]string)
|
||||
}
|
||||
for k, v := range t2.Includes {
|
||||
t1.Includes[k] = v
|
||||
}
|
||||
|
||||
if t1.Vars == nil {
|
||||
t1.Vars = make(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
|
||||
}
|
||||
|
||||
if t1.Tasks == nil {
|
||||
t1.Tasks = make(Tasks)
|
||||
}
|
||||
for k, v := range t2.Tasks {
|
||||
// FIXME(@andreynering): Refactor this block, otherwise we can
|
||||
// have serious side-effects in the future, since we're editing
|
||||
// the original references instead of deep copying them.
|
||||
|
||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = v
|
||||
|
||||
for _, dep := range v.Deps {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, namespaces...)
|
||||
}
|
||||
for _, cmd := range v.Cmds {
|
||||
if cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskNameWithNamespace(taskName string, namespaces ...string) string {
|
||||
if strings.HasPrefix(taskName, ":") {
|
||||
return strings.TrimPrefix(taskName, ":")
|
||||
}
|
||||
return strings.Join(append(namespaces, taskName), NamespaceSeparator)
|
||||
}
|
||||
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.v2"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
77
internal/taskfile/read/taskfile.go
Normal file
77
internal/taskfile/read/taskfile.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package read
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
// Taskfile reads a Taskfile for a given directory
|
||||
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
path := filepath.Join(dir, entrypoint)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
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)
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.IsDir() {
|
||||
path = filepath.Join(path, "Taskfile.yml")
|
||||
}
|
||||
includedTaskfile, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(includedTaskfile.Includes) > 0 {
|
||||
return nil, ErrIncludedTaskfilesCantHaveIncludes
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
for name, task := range t.Tasks {
|
||||
task.Task = name
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func readTaskfile(file string) (*taskfile.Taskfile, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var t taskfile.Taskfile
|
||||
return &t, yaml.NewDecoder(f).Decode(&t)
|
||||
}
|
||||
52
internal/taskfile/read/taskvars.go
Normal file
52
internal/taskfile/read/taskvars.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package read
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Taskvars reads a Taskvars for a given directory
|
||||
func Taskvars(dir string) (taskfile.Vars, error) {
|
||||
vars := make(taskfile.Vars)
|
||||
|
||||
path := filepath.Join(dir, "Taskvars.yml")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
vars, err = readTaskvars(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
path = filepath.Join(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
osVars, err := readTaskvars(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
vars = osVars
|
||||
} else {
|
||||
for k, v := range osVars {
|
||||
vars[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
24
internal/taskfile/task.go
Normal file
24
internal/taskfile/task.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package taskfile
|
||||
|
||||
// Tasks represents a group of tasks
|
||||
type Tasks map[string]*Task
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
Task string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Desc string
|
||||
Summary string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Vars Vars
|
||||
Env Vars
|
||||
Silent bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
}
|
||||
44
internal/taskfile/taskfile.go
Normal file
44
internal/taskfile/taskfile.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package taskfile
|
||||
|
||||
// Taskfile represents a Taskfile.yml
|
||||
type Taskfile struct {
|
||||
Version string
|
||||
Expansions int
|
||||
Output string
|
||||
Includes map[string]string
|
||||
Vars Vars
|
||||
Env Vars
|
||||
Tasks Tasks
|
||||
}
|
||||
|
||||
// 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
|
||||
Tasks Tasks
|
||||
}
|
||||
if err := unmarshal(&taskfile); err != nil {
|
||||
return err
|
||||
}
|
||||
tf.Version = taskfile.Version
|
||||
tf.Expansions = taskfile.Expansions
|
||||
tf.Output = taskfile.Output
|
||||
tf.Includes = taskfile.Includes
|
||||
tf.Vars = taskfile.Vars
|
||||
tf.Env = taskfile.Env
|
||||
tf.Tasks = taskfile.Tasks
|
||||
if tf.Expansions <= 0 {
|
||||
tf.Expansions = 2
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package task_test
|
||||
package taskfile_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v2"
|
||||
@@ -27,28 +27,28 @@ vars:
|
||||
}{
|
||||
{
|
||||
yamlCmd,
|
||||
&task.Cmd{},
|
||||
&task.Cmd{Cmd: `echo "a string command"`},
|
||||
&taskfile.Cmd{},
|
||||
&taskfile.Cmd{Cmd: `echo "a string command"`},
|
||||
},
|
||||
{
|
||||
yamlTaskCall,
|
||||
&task.Cmd{},
|
||||
&task.Cmd{Task: "another-task", Vars: task.Vars{
|
||||
"PARAM1": task.Var{Static: "VALUE1"},
|
||||
"PARAM2": task.Var{Static: "VALUE2"},
|
||||
&taskfile.Cmd{},
|
||||
&taskfile.Cmd{Task: "another-task", Vars: taskfile.Vars{
|
||||
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
||||
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
yamlDep,
|
||||
&task.Dep{},
|
||||
&task.Dep{Task: "task-name"},
|
||||
&taskfile.Dep{},
|
||||
&taskfile.Dep{Task: "task-name"},
|
||||
},
|
||||
{
|
||||
yamlTaskCall,
|
||||
&task.Dep{},
|
||||
&task.Dep{Task: "another-task", Vars: task.Vars{
|
||||
"PARAM1": task.Var{Static: "VALUE1"},
|
||||
"PARAM2": task.Var{Static: "VALUE2"},
|
||||
&taskfile.Dep{},
|
||||
&taskfile.Dep{Task: "another-task", Vars: taskfile.Vars{
|
||||
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
||||
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
58
internal/taskfile/var.go
Normal file
58
internal/taskfile/var.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCantUnmarshalVar is returned for invalid var YAML.
|
||||
ErrCantUnmarshalVar = errors.New("task: can't unmarshal var value")
|
||||
)
|
||||
|
||||
// Vars is a string[string] variables map.
|
||||
type Vars map[string]Var
|
||||
|
||||
// ToStringMap converts Vars to a string 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 {
|
||||
if v.Sh != "" {
|
||||
// Dynamic variable is not yet resolved; trigger
|
||||
// <no value> to be used in templates.
|
||||
continue
|
||||
}
|
||||
m[k] = v.Static
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Var represents either a static or dynamic variable.
|
||||
type Var struct {
|
||||
Static string
|
||||
Sh string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var str string
|
||||
if err := unmarshal(&str); err == nil {
|
||||
if strings.HasPrefix(str, "$") {
|
||||
v.Sh = strings.TrimPrefix(str, "$")
|
||||
} else {
|
||||
v.Static = str
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var sh struct {
|
||||
Sh string
|
||||
}
|
||||
if err := unmarshal(&sh); err == nil {
|
||||
v.Sh = sh.Sh
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrCantUnmarshalVar
|
||||
}
|
||||
52
internal/templater/funcs.go
Normal file
52
internal/templater/funcs.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package templater
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
)
|
||||
|
||||
var (
|
||||
templateFuncs template.FuncMap
|
||||
)
|
||||
|
||||
func init() {
|
||||
taskFuncs := template.FuncMap{
|
||||
"OS": func() string { return runtime.GOOS },
|
||||
"ARCH": func() string { return runtime.GOARCH },
|
||||
"catLines": func(s string) string {
|
||||
s = strings.Replace(s, "\r\n", " ", -1)
|
||||
return strings.Replace(s, "\n", " ", -1)
|
||||
},
|
||||
"splitLines": func(s string) []string {
|
||||
s = strings.Replace(s, "\r\n", "\n", -1)
|
||||
return strings.Split(s, "\n")
|
||||
},
|
||||
"fromSlash": func(path string) string {
|
||||
return filepath.FromSlash(path)
|
||||
},
|
||||
"toSlash": func(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
},
|
||||
"exeExt": func() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return ".exe"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
// IsSH is deprecated.
|
||||
"IsSH": func() bool { return true },
|
||||
}
|
||||
// Deprecated aliases for renamed functions.
|
||||
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
|
||||
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
||||
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
||||
|
||||
templateFuncs = sprig.TxtFuncMap()
|
||||
for k, v := range taskFuncs {
|
||||
templateFuncs[k] = v
|
||||
}
|
||||
}
|
||||
73
internal/templater/templater.go
Normal file
73
internal/templater/templater.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package templater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
// Templater is a help struct that allow us to call "replaceX" funcs multiple
|
||||
// times, without having to check for error each time. The first error that
|
||||
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
||||
// return the zero value.
|
||||
type Templater struct {
|
||||
Vars taskfile.Vars
|
||||
|
||||
strMap map[string]string
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *Templater) Replace(str string) string {
|
||||
if r.err != nil || str == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
templ, err := template.New("").Funcs(templateFuncs).Parse(str)
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return ""
|
||||
}
|
||||
|
||||
if r.strMap == nil {
|
||||
r.strMap = r.Vars.ToStringMap()
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if err = templ.Execute(&b, r.strMap); err != nil {
|
||||
r.err = err
|
||||
return ""
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (r *Templater) ReplaceSlice(strs []string) []string {
|
||||
if r.err != nil || len(strs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
new := make([]string, len(strs))
|
||||
for i, str := range strs {
|
||||
new[i] = r.Replace(str)
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars {
|
||||
if r.err != nil || len(vars) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
new := make(taskfile.Vars, len(vars))
|
||||
for k, v := range vars {
|
||||
new[k] = taskfile.Var{
|
||||
Static: r.Replace(v.Static),
|
||||
Sh: r.Replace(v.Sh),
|
||||
}
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
func (r *Templater) Err() error {
|
||||
return r.err
|
||||
}
|
||||
31
log.go
31
log.go
@@ -1,31 +0,0 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (e *Executor) outf(s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
fmt.Fprintf(e.Stdout, s+"\n", args...)
|
||||
}
|
||||
|
||||
func (e *Executor) verboseOutf(s string, args ...interface{}) {
|
||||
if e.Verbose {
|
||||
e.outf(s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) errf(s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
fmt.Fprintf(e.Stderr, s+"\n", args...)
|
||||
}
|
||||
|
||||
func (e *Executor) verboseErrf(s string, args ...interface{}) {
|
||||
if e.Verbose {
|
||||
e.errf(s, args...)
|
||||
}
|
||||
}
|
||||
31
precondition.go
Normal file
31
precondition.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"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("task: %s", p.Msg)
|
||||
return false, ErrPreconditionFailed
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
45
status.go
45
status.go
@@ -4,16 +4,35 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-task/task/internal/execext"
|
||||
"github.com/go-task/task/internal/status"
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/status"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
func (t *Task) isUpToDate(ctx context.Context) (bool, error) {
|
||||
// Status returns an error if any the of given tasks is not up-to-date
|
||||
func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
|
||||
for _, call := range calls {
|
||||
t, err := e.CompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isUpToDate, err := e.isTaskUpToDate(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isUpToDate {
|
||||
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Task)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||
if len(t.Status) > 0 {
|
||||
return t.isUpToDateStatus(ctx)
|
||||
return e.isTaskUpToDateStatus(ctx, t)
|
||||
}
|
||||
|
||||
checker, err := t.getStatusChecker()
|
||||
checker, err := e.getStatusChecker(t)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -21,15 +40,15 @@ func (t *Task) isUpToDate(ctx context.Context) (bool, error) {
|
||||
return checker.IsUpToDate()
|
||||
}
|
||||
|
||||
func (t *Task) statusOnError() error {
|
||||
checker, err := t.getStatusChecker()
|
||||
func (e *Executor) statusOnError(t *taskfile.Task) error {
|
||||
checker, err := e.getStatusChecker(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checker.OnError()
|
||||
}
|
||||
|
||||
func (t *Task) getStatusChecker() (status.Checker, error) {
|
||||
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
||||
switch t.Method {
|
||||
case "", "timestamp":
|
||||
return &status.Timestamp{
|
||||
@@ -42,6 +61,7 @@ func (t *Task) getStatusChecker() (status.Checker, error) {
|
||||
Dir: t.Dir,
|
||||
Task: t.Task,
|
||||
Sources: t.Sources,
|
||||
Dry: e.Dry,
|
||||
}, nil
|
||||
case "none":
|
||||
return status.None{}, nil
|
||||
@@ -50,17 +70,18 @@ func (t *Task) getStatusChecker() (status.Checker, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Task) isUpToDateStatus(ctx context.Context) (bool, error) {
|
||||
func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||
for _, s := range t.Status {
|
||||
err := execext.RunCommand(&execext.RunCommandOptions{
|
||||
Context: ctx,
|
||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: s,
|
||||
Dir: t.Dir,
|
||||
Env: t.getEnviron(),
|
||||
Env: getEnviron(t),
|
||||
})
|
||||
if err != nil {
|
||||
e.Logger.VerboseOutf("task: status command %s exited non-zero: %s", s, err)
|
||||
return false, nil
|
||||
}
|
||||
e.Logger.VerboseOutf("task: status command %s exited zero", s)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
340
task.go
340
task.go
@@ -2,20 +2,28 @@ package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-task/task/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/compiler"
|
||||
compilerv1 "github.com/go-task/task/v2/internal/compiler/v1"
|
||||
compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/output"
|
||||
"github.com/go-task/task/v2/internal/summary"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v2/internal/taskfile/read"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
// TaskFilePath is the default Taskfile
|
||||
TaskFilePath = "Taskfile"
|
||||
// MaximumTaskCall is the max number of times a task can be called.
|
||||
// This exists to prevent infinite loops on cyclic dependencies
|
||||
MaximumTaskCall = 100
|
||||
@@ -23,51 +31,76 @@ const (
|
||||
|
||||
// Executor executes a Taskfile
|
||||
type Executor struct {
|
||||
Tasks Tasks
|
||||
Dir string
|
||||
Force bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
Taskfile *taskfile.Taskfile
|
||||
|
||||
Context context.Context
|
||||
Dir string
|
||||
Entrypoint string
|
||||
Force bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
taskvars Vars
|
||||
Logger *logger.Logger
|
||||
Compiler compiler.Compiler
|
||||
Output output.Output
|
||||
OutputStyle string
|
||||
|
||||
taskvars taskfile.Vars
|
||||
|
||||
taskCallCount map[string]*int32
|
||||
|
||||
dynamicCache map[string]string
|
||||
muDynamicCache sync.Mutex
|
||||
}
|
||||
|
||||
// Tasks representas 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
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
}
|
||||
|
||||
// Run runs Task
|
||||
func (e *Executor) Run(calls ...Call) error {
|
||||
if e.Context == nil {
|
||||
e.Context = context.Background()
|
||||
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
// check if given tasks exist
|
||||
for _, c := range calls {
|
||||
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
||||
// FIXME: move to the main package
|
||||
e.PrintTasksHelp()
|
||||
return &taskNotFoundError{taskName: c.Task}
|
||||
}
|
||||
}
|
||||
|
||||
if e.Summary {
|
||||
summary.PrintTasks(e.Logger, e.Taskfile, calls)
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.Watch {
|
||||
return e.watchTasks(calls...)
|
||||
}
|
||||
|
||||
for _, c := range calls {
|
||||
if err := e.RunTask(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup setups Executor's internal state
|
||||
func (e *Executor) Setup() error {
|
||||
if e.Entrypoint == "" {
|
||||
e.Entrypoint = "Taskfile.yml"
|
||||
}
|
||||
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.taskvars, err = read.Taskvars(e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.Stdin == nil {
|
||||
e.Stdin = os.Stdin
|
||||
}
|
||||
@@ -77,39 +110,99 @@ func (e *Executor) Run(calls ...Call) error {
|
||||
if e.Stderr == nil {
|
||||
e.Stderr = os.Stderr
|
||||
}
|
||||
e.Logger = &logger.Logger{
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
Verbose: e.Verbose,
|
||||
}
|
||||
|
||||
e.taskCallCount = make(map[string]*int32, len(e.Tasks))
|
||||
for k := range e.Tasks {
|
||||
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
||||
}
|
||||
// consider as equal to the greater version if round
|
||||
if v == 2.0 {
|
||||
v = 2.6
|
||||
}
|
||||
|
||||
if v < 1 {
|
||||
return fmt.Errorf(`task: Taskfile version should be greater or equal to v1`)
|
||||
}
|
||||
if v > 2.6 {
|
||||
return fmt.Errorf(`task: Taskfile versions greater than v2.6 not implemented in the version of Task`)
|
||||
}
|
||||
|
||||
if v < 2 {
|
||||
e.Compiler = &compilerv1.CompilerV1{
|
||||
Dir: e.Dir,
|
||||
Vars: e.taskvars,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
} else { // v >= 2
|
||||
e.Compiler = &compilerv2.CompilerV2{
|
||||
Dir: e.Dir,
|
||||
Taskvars: e.taskvars,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Expansions: e.Taskfile.Expansions,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
if v < 2.1 && e.Taskfile.Output != "" {
|
||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||
}
|
||||
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 e.OutputStyle != "" {
|
||||
e.Taskfile.Output = e.OutputStyle
|
||||
}
|
||||
switch e.Taskfile.Output {
|
||||
case "", "interleaved":
|
||||
e.Output = output.Interleaved{}
|
||||
case "group":
|
||||
e.Output = output.Group{}
|
||||
case "prefixed":
|
||||
e.Output = output.Prefixed{}
|
||||
default:
|
||||
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd.IgnoreError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if e.dynamicCache == nil {
|
||||
e.dynamicCache = make(map[string]string, 10)
|
||||
}
|
||||
|
||||
// check if given tasks exist
|
||||
for _, c := range calls {
|
||||
if _, ok := e.Tasks[c.Task]; !ok {
|
||||
// FIXME: move to the main package
|
||||
e.PrintTasksHelp()
|
||||
return &taskNotFoundError{taskName: c.Task}
|
||||
}
|
||||
}
|
||||
|
||||
if e.Watch {
|
||||
return e.watchTasks(calls...)
|
||||
}
|
||||
|
||||
for _, c := range calls {
|
||||
if err := e.RunTask(e.Context, c); err != nil {
|
||||
return err
|
||||
}
|
||||
e.mkdirMutexMap[k] = &sync.Mutex{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunTask runs a task by its name
|
||||
func (e *Executor) RunTask(ctx context.Context, call Call) error {
|
||||
func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
t, err := e.CompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -123,73 +216,140 @@ func (e *Executor) RunTask(ctx context.Context, call Call) error {
|
||||
}
|
||||
|
||||
if !e.Force {
|
||||
upToDate, err := t.isUpToDate(ctx)
|
||||
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if upToDate {
|
||||
|
||||
upToDate, err := e.isTaskUpToDate(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if upToDate && preCondMet {
|
||||
if !e.Silent {
|
||||
e.errf(`task: Task "%s" is up to date`, t.Task)
|
||||
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.mkdir(t); err != nil {
|
||||
e.Logger.Errf("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 := t.statusOnError(); err2 != nil {
|
||||
e.verboseErrf("task: error cleaning status on error: %v", err2)
|
||||
if err2 := e.statusOnError(t); err2 != nil {
|
||||
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
|
||||
}
|
||||
|
||||
if execext.IsExitError(err) && t.IgnoreError {
|
||||
e.Logger.VerboseErrf("task: task error ignored: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
return &taskRunError{t.Task, err}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) runDeps(ctx context.Context, t *Task) error {
|
||||
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)
|
||||
|
||||
for _, d := range t.Deps {
|
||||
d := d
|
||||
|
||||
g.Go(func() error {
|
||||
return e.RunTask(ctx, 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
|
||||
})
|
||||
}
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (e *Executor) runCommand(ctx context.Context, t *Task, call Call, i int) error {
|
||||
func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error {
|
||||
cmd := t.Cmds[i]
|
||||
|
||||
if cmd.Cmd == "" {
|
||||
return e.RunTask(ctx, Call{Task: cmd.Task, Vars: cmd.Vars})
|
||||
}
|
||||
switch {
|
||||
case cmd.Task != "":
|
||||
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.Silent) {
|
||||
e.errf(cmd.Cmd)
|
||||
}
|
||||
if e.Dry {
|
||||
return nil
|
||||
}
|
||||
|
||||
return execext.RunCommand(&execext.RunCommandOptions{
|
||||
Context: ctx,
|
||||
Command: cmd.Cmd,
|
||||
Dir: t.Dir,
|
||||
Env: t.getEnviron(),
|
||||
Stdin: e.Stdin,
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
})
|
||||
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
|
||||
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
|
||||
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,
|
||||
Dir: t.Dir,
|
||||
Env: getEnviron(t),
|
||||
Stdin: e.Stdin,
|
||||
Stdout: stdOut,
|
||||
Stderr: stdErr,
|
||||
})
|
||||
if execext.IsExitError(err) && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf("task: command error ignored: %v", err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Task) getEnviron() []string {
|
||||
func getEnviron(t *taskfile.Task) []string {
|
||||
if t.Env == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
envs := os.Environ()
|
||||
for k, v := range t.Env.toStringMap() {
|
||||
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
||||
environ := os.Environ()
|
||||
for k, v := range t.Env.ToStringMap() {
|
||||
environ = append(environ, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return envs
|
||||
return environ
|
||||
}
|
||||
|
||||
437
task_test.go
437
task_test.go
@@ -2,6 +2,7 @@ package task_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -9,8 +10,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task"
|
||||
"github.com/go-task/task/v2"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -37,8 +40,8 @@ func (fct fileContentTest) Run(t *testing.T) {
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
}
|
||||
assert.NoError(t, e.ReadTaskfile(), "e.ReadTaskfile()")
|
||||
assert.NoError(t, e.Run(task.Call{Task: fct.Target}), "e.Run(target)")
|
||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: fct.Target}), "e.Run(target)")
|
||||
|
||||
for name, expectContent := range fct.Files {
|
||||
t.Run(fct.name(name), func(t *testing.T) {
|
||||
@@ -51,7 +54,6 @@ func (fct fileContentTest) Run(t *testing.T) {
|
||||
assert.Equal(t, expectContent, s, "unexpected file content")
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
@@ -60,15 +62,16 @@ func TestEnv(t *testing.T) {
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
"env.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
||||
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
||||
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestVars(t *testing.T) {
|
||||
func TestVarsV1(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/vars",
|
||||
Dir: "testdata/vars/v1",
|
||||
Target: "default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
@@ -102,30 +105,70 @@ func TestVars(t *testing.T) {
|
||||
tt.Target = "hello"
|
||||
tt.Run(t)
|
||||
}
|
||||
func TestMultilineVars(t *testing.T) {
|
||||
|
||||
func TestVarsV2(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/vars/multiline",
|
||||
Dir: "testdata/vars/v2",
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
// Note:
|
||||
// - task does not strip a trailing newline from var entries
|
||||
// - task strips one trailing newline from shell output
|
||||
// - the cat command adds a trailing newline
|
||||
"echo_foobar.txt": "foo\nbar\n",
|
||||
"echo_n_foobar.txt": "foo\nbar\n",
|
||||
"echo_n_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n",
|
||||
"var_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n\n",
|
||||
"var_catlines.txt": " foo bar foobar baz \n",
|
||||
"var_enumfile.txt": "0:\n1:\n2:foo\n3: bar\n4:foobar\n5:\n6:baz\n7:\n8:\n",
|
||||
"foo.txt": "foo",
|
||||
"bar.txt": "bar",
|
||||
"baz.txt": "baz",
|
||||
"tmpl_foo.txt": "foo",
|
||||
"tmpl_bar.txt": "bar",
|
||||
"tmpl_foo2.txt": "foo2",
|
||||
"tmpl_bar2.txt": "bar2",
|
||||
"shtmpl_foo.txt": "foo",
|
||||
"shtmpl_foo2.txt": "foo2",
|
||||
"nestedtmpl_foo.txt": "<no value>",
|
||||
"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": "bar2",
|
||||
"shtmpl2_foo.txt": "<no value>",
|
||||
"shtmpl2_foo2.txt": "foo2",
|
||||
"nestedtmpl2_foo2.txt": "<no value>",
|
||||
"override.txt": "bar",
|
||||
"nested.txt": "Taskvars-TaskfileVars-TaskVars",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
// Ensure identical results when running hello task directly.
|
||||
tt.Target = "hello"
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestMultilineVars(t *testing.T) {
|
||||
for _, dir := range []string{"testdata/vars/v1/multiline", "testdata/vars/v2/multiline"} {
|
||||
tt := fileContentTest{
|
||||
Dir: dir,
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
// Note:
|
||||
// - task does not strip a trailing newline from var entries
|
||||
// - task strips one trailing newline from shell output
|
||||
// - the cat command adds a trailing newline
|
||||
"echo_foobar.txt": "foo\nbar\n",
|
||||
"echo_n_foobar.txt": "foo\nbar\n",
|
||||
"echo_n_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n",
|
||||
"var_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n\n",
|
||||
"var_catlines.txt": " foo bar foobar baz \n",
|
||||
"var_enumfile.txt": "0:\n1:\n2:foo\n3: bar\n4:foobar\n5:\n6:baz\n7:\n8:\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVarsInvalidTmpl(t *testing.T) {
|
||||
const (
|
||||
dir = "testdata/vars"
|
||||
dir = "testdata/vars/v1"
|
||||
target = "invalid-var-tmpl"
|
||||
expectError = "template: :1: unexpected EOF"
|
||||
)
|
||||
@@ -135,8 +178,8 @@ func TestVarsInvalidTmpl(t *testing.T) {
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
}
|
||||
assert.NoError(t, e.ReadTaskfile(), "e.ReadTaskfile()")
|
||||
assert.EqualError(t, e.Run(task.Call{Task: target}), expectError, "e.Run(target)")
|
||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||
assert.EqualError(t, e.Run(context.Background(), taskfile.Call{Task: target}), expectError, "e.Run(target)")
|
||||
}
|
||||
|
||||
func TestParams(t *testing.T) {
|
||||
@@ -187,13 +230,13 @@ func TestDeps(t *testing.T) {
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
}
|
||||
assert.NoError(t, e.ReadTaskfile())
|
||||
assert.NoError(t, e.Run(task.Call{Task: "default"}))
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,45 +248,90 @@ 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
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.ReadTaskfile())
|
||||
assert.NoError(t, e.Run(task.Call{Task: "gen-foo"}))
|
||||
assert.NoError(t, e.Setup())
|
||||
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)
|
||||
}
|
||||
|
||||
buff := bytes.NewBuffer(nil)
|
||||
e.Stdout, e.Stderr = buff, buff
|
||||
assert.NoError(t, e.Run(task.Call{Task: "gen-foo"}))
|
||||
e.Silent = false
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||
|
||||
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
|
||||
t.Errorf("Wrong output message: %s", buff.String())
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
var srcTask = "sub/src.txt"
|
||||
var relTask = "rel.txt"
|
||||
var absTask = "abs.txt"
|
||||
const (
|
||||
srcTask = "sub/src.txt"
|
||||
relTask = "rel.txt"
|
||||
absTask = "abs.txt"
|
||||
fileWithSpaces = "my text file.txt"
|
||||
)
|
||||
|
||||
// This test does not work with a relative dir.
|
||||
dir, err := filepath.Abs("testdata/generates")
|
||||
assert.NoError(t, err)
|
||||
var srcFile = filepath.Join(dir, srcTask)
|
||||
|
||||
for _, task := range []string{srcTask, relTask, absTask} {
|
||||
for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,21 +341,21 @@ func TestGenerates(t *testing.T) {
|
||||
Stdout: buff,
|
||||
Stderr: buff,
|
||||
}
|
||||
assert.NoError(t, e.ReadTaskfile())
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
for _, theTask := range []string{relTask, absTask} {
|
||||
for _, theTask := range []string{relTask, absTask, fileWithSpaces} {
|
||||
var destFile = filepath.Join(dir, theTask)
|
||||
var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
|
||||
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
|
||||
|
||||
// Run task for the first time.
|
||||
assert.NoError(t, e.Run(task.Call{Task: theTask}))
|
||||
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 {
|
||||
@@ -276,7 +364,7 @@ func TestGenerates(t *testing.T) {
|
||||
buff.Reset()
|
||||
|
||||
// Re-run task to ensure it's now found to be up-to-date.
|
||||
assert.NoError(t, e.Run(task.Call{Task: theTask}))
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
|
||||
if buff.String() != upToDate {
|
||||
t.Errorf("Wrong output message: %s", buff.String())
|
||||
}
|
||||
@@ -305,16 +393,16 @@ func TestStatusChecksum(t *testing.T) {
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.ReadTaskfile())
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
assert.NoError(t, e.Run(task.Call{Task: "build"}))
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||
for _, f := range files {
|
||||
_, err := os.Stat(filepath.Join(dir, f))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
buff.Reset()
|
||||
assert.NoError(t, e.Run(task.Call{Task: "build"}))
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
}
|
||||
|
||||
@@ -324,7 +412,7 @@ func TestInit(t *testing.T) {
|
||||
|
||||
_ = 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 {
|
||||
@@ -332,7 +420,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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +432,249 @@ func TestCyclicDep(t *testing.T) {
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
}
|
||||
assert.NoError(t, e.ReadTaskfile())
|
||||
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(task.Call{Task: "task-1"}))
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(context.Background(), taskfile.Call{Task: "task-1"}))
|
||||
}
|
||||
|
||||
func TestTaskVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
Dir string
|
||||
Version string
|
||||
}{
|
||||
{"testdata/version/v1", "1"},
|
||||
{"testdata/version/v2", "2"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Dir, func(t *testing.T) {
|
||||
e := task.Executor{
|
||||
Dir: test.Dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.Equal(t, test.Version, e.Taskfile.Version)
|
||||
assert.Equal(t, 2, len(e.Taskfile.Tasks))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskIgnoreErrors(t *testing.T) {
|
||||
const dir = "testdata/ignore_errors"
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-should-pass"}))
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "task-should-fail"}))
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "cmd-should-pass"}))
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "cmd-should-fail"}))
|
||||
}
|
||||
|
||||
func TestExpand(t *testing.T) {
|
||||
const dir = "testdata/expand"
|
||||
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't get $HOME: %v", err)
|
||||
}
|
||||
var buff bytes.Buffer
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "pwd"}))
|
||||
assert.Equal(t, home, strings.TrimSpace(buff.String()))
|
||||
}
|
||||
|
||||
func TestDry(t *testing.T) {
|
||||
const dir = "testdata/dry"
|
||||
|
||||
file := filepath.Join(dir, "file.txt")
|
||||
_ = os.Remove(file)
|
||||
|
||||
var buff bytes.Buffer
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Dry: true,
|
||||
}
|
||||
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()))
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Errorf("File should not exist %s", file)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDryChecksum tests if the checksum file is not being written to disk
|
||||
// if the dry mode is enabled.
|
||||
func TestDryChecksum(t *testing.T) {
|
||||
const dir = "testdata/dry_checksum"
|
||||
|
||||
checksumFile := filepath.Join(dir, ".task/checksum/default")
|
||||
_ = os.Remove(checksumFile)
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Dry: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
|
||||
_, err := os.Stat(checksumFile)
|
||||
assert.Error(t, err, "checksum file should not exist")
|
||||
|
||||
e.Dry = false
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
_, err = os.Stat(checksumFile)
|
||||
assert.NoError(t, err, "checksum file should exist")
|
||||
}
|
||||
|
||||
func TestIncludes(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes",
|
||||
Target: "default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"main.txt": "main",
|
||||
"included_directory.txt": "included_directory",
|
||||
"included_taskfile.txt": "included_taskfile",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludesEmptyMain(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_empty",
|
||||
Target: "included:default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"file.txt": "default",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludesDependencies(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_deps",
|
||||
Target: "default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"default.txt": "default",
|
||||
"called_dep.txt": "called_dep",
|
||||
"called_task.txt": "called_task",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludesCallingRoot(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_call_root_task",
|
||||
Target: "included:call-root",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"root_task.txt": "root task",
|
||||
},
|
||||
}
|
||||
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"}))
|
||||
assert.Equal(t, readTestFixture(t, dir, "task-with-summary.txt"), buff.String())
|
||||
}
|
||||
|
||||
func readTestFixture(t *testing.T, dir string, file string) string {
|
||||
b, err := ioutil.ReadFile(dir + "/" + file)
|
||||
assert.NoError(t, err, "error reading text fixture")
|
||||
return string(b)
|
||||
}
|
||||
|
||||
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.Remove(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.Remove(toBeCreated)
|
||||
}
|
||||
|
||||
70
taskfile.go
70
taskfile.go
@@ -1,70 +0,0 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// ReadTaskfile parses Taskfile from the disk
|
||||
func (e *Executor) ReadTaskfile() error {
|
||||
path := filepath.Join(e.Dir, TaskFilePath)
|
||||
|
||||
var err error
|
||||
e.Tasks, err = e.readTaskfileData(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
osTasks, err := e.readTaskfileData(fmt.Sprintf("%s_%s", path, runtime.GOOS))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case taskFileNotFound:
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := mergo.MapWithOverwrite(&e.Tasks, osTasks); err != nil {
|
||||
return err
|
||||
}
|
||||
for name, task := range e.Tasks {
|
||||
task.Task = name
|
||||
}
|
||||
|
||||
return e.readTaskvars()
|
||||
}
|
||||
|
||||
func (e *Executor) readTaskfileData(path string) (tasks map[string]*Task, err error) {
|
||||
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
|
||||
return tasks, yaml.UnmarshalStrict(b, &tasks)
|
||||
}
|
||||
return nil, taskFileNotFound{path}
|
||||
}
|
||||
|
||||
func (e *Executor) readTaskvars() error {
|
||||
var (
|
||||
file = filepath.Join(e.Dir, TaskvarsFilePath)
|
||||
osSpecificFile = fmt.Sprintf("%s_%s", file, runtime.GOOS)
|
||||
)
|
||||
|
||||
if b, err := ioutil.ReadFile(file + ".yml"); err == nil {
|
||||
if err := yaml.UnmarshalStrict(b, &e.taskvars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if b, err := ioutil.ReadFile(osSpecificFile + ".yml"); err == nil {
|
||||
osTaskvars := make(Vars, 10)
|
||||
if err := yaml.UnmarshalStrict(b, &osTaskvars); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range osTaskvars {
|
||||
e.taskvars[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1
testdata/checksum/Taskfile.yml
vendored
1
testdata/checksum/Taskfile.yml
vendored
@@ -2,6 +2,7 @@ build:
|
||||
cmds:
|
||||
- cp ./source.txt ./generated.txt
|
||||
sources:
|
||||
- ./**/glob-with-inexistent-file.txt
|
||||
- ./source.txt
|
||||
generates:
|
||||
- ./generated.txt
|
||||
|
||||
7
testdata/dir/Taskfile.yml
vendored
Normal file
7
testdata/dir/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '2'
|
||||
|
||||
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: '2'
|
||||
|
||||
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: '2'
|
||||
|
||||
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
6
testdata/dry/Taskfile.yml
vendored
Normal file
6
testdata/dry/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- touch file.txt
|
||||
9
testdata/dry_checksum/Taskfile.yml
vendored
Normal file
9
testdata/dry_checksum/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "Working..."
|
||||
sources:
|
||||
- source.txt
|
||||
method: checksum
|
||||
1
testdata/dry_checksum/source.txt
vendored
Normal file
1
testdata/dry_checksum/source.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Something...
|
||||
2
testdata/env/.gitignore
vendored
2
testdata/env/.gitignore
vendored
@@ -1 +1 @@
|
||||
env.txt
|
||||
*.txt
|
||||
|
||||
43
testdata/env/Taskfile.yml
vendored
43
testdata/env/Taskfile.yml
vendored
@@ -1,10 +1,33 @@
|
||||
default:
|
||||
vars:
|
||||
AMD64: amd64
|
||||
env:
|
||||
GOOS: linux
|
||||
GOARCH: "{{.AMD64}}"
|
||||
CGO_ENABLED:
|
||||
sh: echo '0'
|
||||
cmds:
|
||||
- echo "GOOS='$GOOS' GOARCH='$GOARCH' CGO_ENABLED='$CGO_ENABLED'" > env.txt
|
||||
version: '2'
|
||||
|
||||
vars:
|
||||
BAZ:
|
||||
sh: echo baz
|
||||
|
||||
env:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BAZ: "{{.BAZ}}"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: local
|
||||
- task: global
|
||||
|
||||
local:
|
||||
vars:
|
||||
AMD64: amd64
|
||||
env:
|
||||
GOOS: linux
|
||||
GOARCH: "{{.AMD64}}"
|
||||
CGO_ENABLED:
|
||||
sh: echo '0'
|
||||
cmds:
|
||||
- echo "GOOS='$GOOS' GOARCH='$GOARCH' CGO_ENABLED='$CGO_ENABLED'" > local.txt
|
||||
|
||||
global:
|
||||
env:
|
||||
BAR: overriden
|
||||
cmds:
|
||||
- echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > global.txt
|
||||
|
||||
8
testdata/expand/Taskfile.yml
vendored
Normal file
8
testdata/expand/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
pwd:
|
||||
cmds:
|
||||
- pwd
|
||||
dir: '~'
|
||||
silent: true
|
||||
10
testdata/generates/Taskfile.yml
vendored
10
testdata/generates/Taskfile.yml
vendored
@@ -29,3 +29,13 @@ sub/src.txt:
|
||||
- echo "hello world" > sub/src.txt
|
||||
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'
|
||||
sources:
|
||||
- sub/src.txt
|
||||
generates:
|
||||
- 'my text file.txt'
|
||||
|
||||
20
testdata/ignore_errors/Taskfile.yml
vendored
Normal file
20
testdata/ignore_errors/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
task-should-pass:
|
||||
cmds:
|
||||
- exit 1
|
||||
ignore_error: true
|
||||
|
||||
task-should-fail:
|
||||
cmds:
|
||||
- exit 1
|
||||
|
||||
cmd-should-pass:
|
||||
cmds:
|
||||
- cmd: exit 1
|
||||
ignore_error: true
|
||||
|
||||
cmd-should-fail:
|
||||
cmds:
|
||||
- cmd: exit 1
|
||||
16
testdata/includes/Taskfile.yml
vendored
Normal file
16
testdata/includes/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
version: '2'
|
||||
|
||||
includes:
|
||||
included: ./included
|
||||
included_taskfile: ./Taskfile2.yml
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: gen
|
||||
- task: included:gen
|
||||
- task: included_taskfile:gen
|
||||
|
||||
gen:
|
||||
cmds:
|
||||
- echo main > main.txt
|
||||
6
testdata/includes/Taskfile2.yml
vendored
Normal file
6
testdata/includes/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
gen:
|
||||
cmds:
|
||||
- echo included_taskfile > included_taskfile.txt
|
||||
6
testdata/includes/included/Taskfile.yml
vendored
Normal file
6
testdata/includes/included/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
gen:
|
||||
cmds:
|
||||
- echo included_directory > included_directory.txt
|
||||
1
testdata/includes_call_root_task/.gitignore
vendored
Normal file
1
testdata/includes_call_root_task/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.txt
|
||||
9
testdata/includes_call_root_task/Taskfile.yml
vendored
Normal file
9
testdata/includes_call_root_task/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '2'
|
||||
|
||||
includes:
|
||||
included: Taskfile2.yml
|
||||
|
||||
tasks:
|
||||
root-task:
|
||||
cmds:
|
||||
- echo "root task" > root_task.txt
|
||||
6
testdata/includes_call_root_task/Taskfile2.yml
vendored
Normal file
6
testdata/includes_call_root_task/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
call-root:
|
||||
cmds:
|
||||
- task: :root-task
|
||||
1
testdata/includes_deps/.gitignore
vendored
Normal file
1
testdata/includes_deps/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.txt
|
||||
9
testdata/includes_deps/Taskfile.yml
vendored
Normal file
9
testdata/includes_deps/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '2'
|
||||
|
||||
includes:
|
||||
included: Taskfile2.yml
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: included:default
|
||||
16
testdata/includes_deps/Taskfile2.yml
vendored
Normal file
16
testdata/includes_deps/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps: [called_dep]
|
||||
cmds:
|
||||
- echo "default" > default.txt
|
||||
- task: called_task
|
||||
|
||||
called_dep:
|
||||
cmds:
|
||||
- echo "called_dep" > called_dep.txt
|
||||
|
||||
called_task:
|
||||
cmds:
|
||||
- echo "called_task" > called_task.txt
|
||||
1
testdata/includes_empty/.gitignore
vendored
Normal file
1
testdata/includes_empty/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
file.txt
|
||||
4
testdata/includes_empty/Taskfile.yml
vendored
Normal file
4
testdata/includes_empty/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '2'
|
||||
|
||||
includes:
|
||||
included: Taskfile2.yml
|
||||
10
testdata/includes_empty/Taskfile2.yml
vendored
Normal file
10
testdata/includes_empty/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: '2'
|
||||
|
||||
vars:
|
||||
FILE: file.txt
|
||||
CONTENT: default
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "{{.CONTENT}}" > {{.FILE}}
|
||||
19
testdata/precondition/Taskfile.yml
vendored
Normal file
19
testdata/precondition/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
version: '2'
|
||||
|
||||
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
|
||||
0
testdata/precondition/foo.txt
vendored
Normal file
0
testdata/precondition/foo.txt
vendored
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user