mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
415 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79f595d8d1 | ||
|
|
db2865fb17 | ||
|
|
f945fa60d9 | ||
|
|
454988f657 | ||
|
|
7e0346d6eb | ||
|
|
00a90d1fe6 | ||
|
|
d6c185580a | ||
|
|
fd9132c15d | ||
|
|
42702e81b3 | ||
|
|
09c9d55695 | ||
|
|
69e9effc88 | ||
|
|
1c782c599f | ||
|
|
ed37071fd6 | ||
|
|
d73cf106b1 | ||
|
|
1d7982e80a | ||
|
|
d5d1984116 | ||
|
|
9eda1629bb | ||
|
|
9a5d49774e | ||
|
|
b2efebce96 | ||
|
|
85232bd704 | ||
|
|
df4e3aea79 | ||
|
|
290d45fd05 | ||
|
|
168e8c925c | ||
|
|
d9859b18fe | ||
|
|
784847f35b | ||
|
|
97287377d1 | ||
|
|
a15b66e003 | ||
|
|
a441b4b90d | ||
|
|
0dcc1390a6 | ||
|
|
01c86636e9 | ||
|
|
846c27d579 | ||
|
|
db05059b42 | ||
|
|
b824328850 | ||
|
|
a8767a2b1a | ||
|
|
5e14e7fb70 | ||
|
|
fbaa7be52e | ||
|
|
b6016b244e | ||
|
|
e339a64261 | ||
|
|
17e18442ab | ||
|
|
e8aa3a17a6 | ||
|
|
bdb97eab86 | ||
|
|
690000254c | ||
|
|
6a0b778978 | ||
|
|
549d141053 | ||
|
|
c31ecdb8de | ||
|
|
8a09d044c7 | ||
|
|
a3b5b89930 | ||
|
|
ad6f100f6a | ||
|
|
3cfe21af58 | ||
|
|
b70a660975 | ||
|
|
04c1d1389f | ||
|
|
f12156bf81 | ||
|
|
0177ac660b | ||
|
|
361b9b4ce4 | ||
|
|
78792bd11c | ||
|
|
8b38ddfcd9 | ||
|
|
78ddf50d2d | ||
|
|
93dcb20e12 | ||
|
|
41a71e1dee | ||
|
|
a5ed8ad58c | ||
|
|
e45ed85b55 | ||
|
|
52474f9103 | ||
|
|
c2587da27d | ||
|
|
26036877b2 | ||
|
|
906cdd9050 | ||
|
|
762662d056 | ||
|
|
1de4b38766 | ||
|
|
6c73ab823b | ||
|
|
5ef1651151 | ||
|
|
8d695bc8d7 | ||
|
|
c892d055ed | ||
|
|
b327e54be1 | ||
|
|
989045489c | ||
|
|
888338c60e | ||
|
|
e6c6cc7811 | ||
|
|
fa0e72bd69 | ||
|
|
18decac44d | ||
|
|
1012a0cf2b | ||
|
|
7e4de945cf | ||
|
|
a468272726 | ||
|
|
039d8f000d | ||
|
|
2dc181c75e | ||
|
|
d35f960a8a | ||
|
|
634f8ed574 | ||
|
|
d369451308 | ||
|
|
8f1202424d | ||
|
|
0a6833e9d8 | ||
|
|
8f80fc4e2c | ||
|
|
50e5813222 | ||
|
|
7bc268aeaa | ||
|
|
537b5b1e25 | ||
|
|
aae38f8ce7 | ||
|
|
ad05432bcf | ||
|
|
8aa983257d | ||
|
|
046a97d1e5 | ||
|
|
d28649b13d | ||
|
|
3e16ca37bc | ||
|
|
2da38a5bdc | ||
|
|
bbe1d8b52e | ||
|
|
97c85e39c3 | ||
|
|
a7b59e5b12 | ||
|
|
9eb1252ce9 | ||
|
|
0e01e13670 | ||
|
|
239e61e718 | ||
|
|
22549e9fd8 | ||
|
|
1f9fd24064 | ||
|
|
a7594740e3 | ||
|
|
945c72cf6c | ||
|
|
824b0c0132 | ||
|
|
51e9f2f579 | ||
|
|
e8ec33d9d0 | ||
|
|
3bbbaf12fd | ||
|
|
30ffacd879 | ||
|
|
75e9b7791c | ||
|
|
4b665ab19a | ||
|
|
08265ed1d7 | ||
|
|
cded9af90f | ||
|
|
4e1f2ad017 | ||
|
|
7f92b7072d | ||
|
|
4a589ba6a4 | ||
|
|
7f16325fcc | ||
|
|
b62e5bf34c | ||
|
|
bd6b348cc7 | ||
|
|
62f35fe8c8 | ||
|
|
cb2cb4659c | ||
|
|
a2c58415cf | ||
|
|
2cb9987c99 | ||
|
|
36584cfb7c | ||
|
|
b825ad6a12 | ||
|
|
f8545d4c61 | ||
|
|
9b42ef5d46 | ||
|
|
05ddfc0495 | ||
|
|
53b2cebb66 | ||
|
|
58c69e36a1 | ||
|
|
837fb71a24 | ||
|
|
2e13cf5f74 | ||
|
|
0ae1681d9c | ||
|
|
ebb66ba8fb | ||
|
|
e79354a039 | ||
|
|
a57beb1de4 | ||
|
|
1648c44ee2 | ||
|
|
efe47a149e | ||
|
|
2d66a2f0f3 | ||
|
|
43a1f1314e | ||
|
|
4f4b282d7c | ||
|
|
d3d4da18e5 | ||
|
|
b8da583986 | ||
|
|
73f6b42715 | ||
|
|
0e2a4efdaa | ||
|
|
6798e16aaf | ||
|
|
c9cc64ecfc | ||
|
|
761f9045ac | ||
|
|
dfae979287 | ||
|
|
fe917affd2 | ||
|
|
d44207dd7f | ||
|
|
ec8b1403bd | ||
|
|
6f3d108c1e | ||
|
|
c34ee9c1f9 | ||
|
|
2a3f049336 | ||
|
|
0c91011e88 | ||
|
|
8bcd8719aa | ||
|
|
29a8af509b | ||
|
|
d3cd9f17f9 | ||
|
|
b9aea8c5ec | ||
|
|
897619a961 | ||
|
|
e6c4706b73 | ||
|
|
8994c50d34 | ||
|
|
55b62e47eb | ||
|
|
c6ecf70377 | ||
|
|
f0cd7d27fb | ||
|
|
f923bb499b | ||
|
|
aa3a29fed2 | ||
|
|
47d3011c85 | ||
|
|
cec713a47a | ||
|
|
bf6d0c0a74 | ||
|
|
c11672fca3 | ||
|
|
e086b654aa | ||
|
|
1107f691ea | ||
|
|
b095ca5756 | ||
|
|
4afc0e8ed0 | ||
|
|
141b377b4e | ||
|
|
402a478785 | ||
|
|
73680584f3 | ||
|
|
45dbbcd179 | ||
|
|
83d25bfa00 | ||
|
|
299e27af15 | ||
|
|
ec4cd5ed48 | ||
|
|
59d2733b88 | ||
|
|
cbdd088188 | ||
|
|
2d52485d7b | ||
|
|
d830178ef8 | ||
|
|
049984b4cc | ||
|
|
d261a986ab | ||
|
|
9b2e25735b | ||
|
|
e09e75b0ba | ||
|
|
6630113fef | ||
|
|
b2f08c9c20 | ||
|
|
6a4315b7e7 | ||
|
|
8b3e62ff6d | ||
|
|
f1d3f6740d | ||
|
|
9ccd1d920c | ||
|
|
9674d75ff6 | ||
|
|
22fd74846d | ||
|
|
777645888a | ||
|
|
ac8e344173 | ||
|
|
16fad60833 | ||
|
|
cb96a39b46 | ||
|
|
a540634b5b | ||
|
|
e15576bc47 | ||
|
|
95359760ae | ||
|
|
be209cb7b6 | ||
|
|
f5eb80759b | ||
|
|
9f125502f8 | ||
|
|
3f856c4b1c | ||
|
|
f55fb1e3a5 | ||
|
|
bf88bd5da5 | ||
|
|
b7112e02db | ||
|
|
347c796662 | ||
|
|
9bed7f7a9b | ||
|
|
b136166fc9 | ||
|
|
75727c3d68 | ||
|
|
6c625b3359 | ||
|
|
60759a4e3b | ||
|
|
582a66bb2f | ||
|
|
d78f78bb5c | ||
|
|
71b7d062d5 | ||
|
|
c6138a0660 | ||
|
|
ce4ac97269 | ||
|
|
2088a86512 | ||
|
|
e296fe2b98 | ||
|
|
96b8890ecc | ||
|
|
db6fae2f5b | ||
|
|
6743cdbb65 | ||
|
|
71466c9a27 | ||
|
|
1bdf7e3192 | ||
|
|
7285f3c844 | ||
|
|
eb257d3aa7 | ||
|
|
87f11491d9 | ||
|
|
5735a02473 | ||
|
|
47dd9b5a03 | ||
|
|
7652d7889b | ||
|
|
dd2116c897 | ||
|
|
c5566b3e94 | ||
|
|
30cbf02bff | ||
|
|
9e4e9b4f1a | ||
|
|
6f290f28b6 | ||
|
|
6ff3c9015b | ||
|
|
e28b82b2b7 | ||
|
|
3edf124f96 | ||
|
|
fb72b46a3c | ||
|
|
49bf395f61 | ||
|
|
eab14b6c49 | ||
|
|
8b962fb8e8 | ||
|
|
e5a3c861cb | ||
|
|
c7bb3d63b0 | ||
|
|
e6b543c15e | ||
|
|
8137517d93 | ||
|
|
572f6a7fab | ||
|
|
c6d9201680 | ||
|
|
7dcb3af944 | ||
|
|
4bc183a8a1 | ||
|
|
9f83311931 | ||
|
|
10986d3a7c | ||
|
|
f4f6efa547 | ||
|
|
bf64259af3 | ||
|
|
6141ba84ce | ||
|
|
329902f0db | ||
|
|
bfcaa7a443 | ||
|
|
45915bf0ed | ||
|
|
ee7f2a541f | ||
|
|
59a00eae98 | ||
|
|
df8293bee6 | ||
|
|
935216f179 | ||
|
|
f56bbd46fd | ||
|
|
9f0f18c5c4 | ||
|
|
191c34c9c4 | ||
|
|
6a604b3002 | ||
|
|
5a435b533e | ||
|
|
4b027722b1 | ||
|
|
68ce8642b1 | ||
|
|
4913b6a0f1 | ||
|
|
aee0ab05f4 | ||
|
|
b44432f24a | ||
|
|
86be13ff1f | ||
|
|
ee95df0e57 | ||
|
|
442c29f020 | ||
|
|
739037fc37 | ||
|
|
f8252020aa | ||
|
|
38b87439fe | ||
|
|
116879f7ea | ||
|
|
2eafb2f067 | ||
|
|
c36f0f6f7f | ||
|
|
bfc033959b | ||
|
|
814f350b6d | ||
|
|
05db8ce582 | ||
|
|
3fd36a0c72 | ||
|
|
cbb12b29bd | ||
|
|
6ed30f1add | ||
|
|
a044c41c66 | ||
|
|
fb78e53a14 | ||
|
|
acfbbaa549 | ||
|
|
d52d74c64c | ||
|
|
d36f73de01 | ||
|
|
628c4a7b5f | ||
|
|
ca07a663e1 | ||
|
|
66d008391e | ||
|
|
eef84bda26 | ||
|
|
e0defe71aa | ||
|
|
069257151e | ||
|
|
3f80a3b39e | ||
|
|
b2a56161bb | ||
|
|
5e75639244 | ||
|
|
cb2cd3e10f | ||
|
|
0acb911d6a | ||
|
|
17ad7060b3 | ||
|
|
f38ba7fcd3 | ||
|
|
a3464068bd | ||
|
|
347ecc028f | ||
|
|
94ac60fa09 | ||
|
|
d567e23e50 | ||
|
|
8ff81562d2 | ||
|
|
7a8142ed92 | ||
|
|
eaba1b9cc8 | ||
|
|
b08b3546d2 | ||
|
|
7453e688fd | ||
|
|
32b097b3f2 | ||
|
|
22394def78 | ||
|
|
68ecb7fbdd | ||
|
|
de98a53b43 | ||
|
|
1c9fbf92c6 | ||
|
|
c068b05232 | ||
|
|
15338ecb18 | ||
|
|
01e9a8f720 | ||
|
|
4bdfe64afb | ||
|
|
b7b752b92f | ||
|
|
b7bcd204b4 | ||
|
|
ec934ba3c0 | ||
|
|
7373639f57 | ||
|
|
d718527a1f | ||
|
|
48add0f293 | ||
|
|
a4685229c9 | ||
|
|
f0bc4d26a0 | ||
|
|
1d3b93d88d | ||
|
|
62752ba7e1 | ||
|
|
6a4f420187 | ||
|
|
6640632683 | ||
|
|
09d5d802d0 | ||
|
|
fea23ed6d4 | ||
|
|
10a6c4dc7a | ||
|
|
4cdaa72224 | ||
|
|
27bc1ca5d1 | ||
|
|
1ea49188c9 | ||
|
|
3084ef129c | ||
|
|
c0d112f858 | ||
|
|
2265dda84c | ||
|
|
263b094cab | ||
|
|
fbd13614a5 | ||
|
|
9eab74b595 | ||
|
|
5acdb041a9 | ||
|
|
0494d7ebe3 | ||
|
|
9a8442c946 | ||
|
|
e1dcd0b441 | ||
|
|
a152db7054 | ||
|
|
b9e092674e | ||
|
|
4162b5f41d | ||
|
|
67ae6f210f | ||
|
|
f6c5a46626 | ||
|
|
d6f7e01c53 | ||
|
|
46463e4e24 | ||
|
|
bc99509395 | ||
|
|
393712ead2 | ||
|
|
d3060b0060 | ||
|
|
14d7f04a81 | ||
|
|
1a28e5e0d4 | ||
|
|
884cd0d636 | ||
|
|
6a7a3c0ae8 | ||
|
|
948e6bd57c | ||
|
|
78595fba0b | ||
|
|
8020284b12 | ||
|
|
d6a49da870 | ||
|
|
bae1e1ee9f | ||
|
|
7138785500 | ||
|
|
1a33f9168b | ||
|
|
ccae3d7383 | ||
|
|
847651a90a | ||
|
|
1b8998e7a2 | ||
|
|
dc8fb79759 | ||
|
|
6b0935d6cf | ||
|
|
d1183ce272 | ||
|
|
a1aec8178a | ||
|
|
cb6fe4bb59 | ||
|
|
db36bc67f1 | ||
|
|
e0f72a6193 | ||
|
|
1ee684b7c0 | ||
|
|
93005512b4 | ||
|
|
b8094fd771 | ||
|
|
af5d9c952d | ||
|
|
ce4e187cbc | ||
|
|
821c80b61e | ||
|
|
5a6fb7c973 | ||
|
|
0f385f9f4e | ||
|
|
c47c15ee47 | ||
|
|
6803ad2e59 | ||
|
|
d5a791b470 | ||
|
|
e6255081a8 | ||
|
|
623db0ed94 | ||
|
|
0e575e9c25 | ||
|
|
fb23ba9878 | ||
|
|
4e09fc7f43 | ||
|
|
64cfdd815f | ||
|
|
f6f31e0a8d | ||
|
|
bd5fb9be03 | ||
|
|
dd9cdb0ec9 | ||
|
|
7f082a821d | ||
|
|
8efc38ad82 |
@@ -7,7 +7,6 @@ insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[*.{md,yml,yaml,json,toml,htm,html}]
|
||||
indent_style = space
|
||||
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +1,2 @@
|
||||
open_collective: task
|
||||
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=USD&source=url'
|
||||
|
||||
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,6 +0,0 @@
|
||||
<!--
|
||||
If relevant, include the following information:
|
||||
- Task version
|
||||
- OS
|
||||
- Example Taskfile showing the issue
|
||||
-->
|
||||
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Use the template to report bugs and issues
|
||||
---
|
||||
|
||||
- Task version:
|
||||
- Operating System:
|
||||
|
||||
### Example Taskfile showing the issue
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions, Ideas and General Discussions
|
||||
url: https://github.com/go-task/task/discussions
|
||||
about: Ask questions and discuss general ideas with the community
|
||||
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Use the template to make feature requests
|
||||
---
|
||||
|
||||
Describe in detail what feature do you want to see in Task.
|
||||
Give examples if possible.
|
||||
|
||||
Please, search if this wasn't proposed before, and if this is more like an idea
|
||||
than a strong feature request, consider opening a
|
||||
[discussion](https://github.com/go-task/task/discussions) instead.
|
||||
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: '08:00'
|
||||
timezone: America/Sao_Paulo
|
||||
26
.github/workflows/release.yml
vendored
Normal file
26
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
67
.github/workflows/test.yml
vendored
67
.github/workflows/test.yml
vendored
@@ -1,59 +1,30 @@
|
||||
name: Test
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
test:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.16.x, 1.17.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
- name: Set up Go ${{matrix.go-version}}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.13
|
||||
go-version: ${{matrix.go-version}}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
|
||||
- name: Build
|
||||
run: go build -o ./task -v ./cmd/task
|
||||
run: go build -o ./bin/task -v ./cmd/task
|
||||
|
||||
- name: Test
|
||||
run: ./task test
|
||||
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.13
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Build
|
||||
run: go install -v ./cmd/task
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
macos:
|
||||
name: MacOS
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.13
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Build
|
||||
run: go build -o ./task -v ./cmd/task
|
||||
|
||||
- name: Test
|
||||
run: ./task test
|
||||
run: ./bin/task test
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -19,10 +19,13 @@ dist/
|
||||
|
||||
.DS_Store
|
||||
|
||||
# intellij idea/goland
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# exuberant ctags
|
||||
tags
|
||||
|
||||
/bin
|
||||
/testdata/vars/v1
|
||||
/tmp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
build:
|
||||
binary: task
|
||||
main: cmd/task/task.go
|
||||
main: ./cmd/task
|
||||
goos:
|
||||
- windows
|
||||
- darwin
|
||||
@@ -8,14 +8,27 @@ build:
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
- 6
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w # Don't set main.version.
|
||||
|
||||
gomod:
|
||||
proxy: true
|
||||
|
||||
archives:
|
||||
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completion/**/*
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
@@ -40,4 +53,21 @@ nfpms:
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||
|
||||
brews:
|
||||
- name: go-task
|
||||
description: Task runner / simpler Make alternative written in Go
|
||||
license: MIT
|
||||
homepage: https://taskfile.dev
|
||||
folder: Formula
|
||||
tap:
|
||||
owner: go-task
|
||||
name: homebrew-tap
|
||||
test:
|
||||
system "#{bin}/task", "--help"
|
||||
install: |-
|
||||
bin.install "task"
|
||||
bash_completion.install "completion/bash/task.bash" => "task"
|
||||
zsh_completion.install "completion/zsh/_task" => "_task"
|
||||
fish_completion.install "completion/fish/task.fish"
|
||||
|
||||
22
.travis.yml
22
.travis.yml
@@ -1,22 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- rpm
|
||||
|
||||
script:
|
||||
- go install -v ./cmd/task
|
||||
- task ci
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: curl -sL http://git.io/goreleaser | bash
|
||||
on:
|
||||
tags: true
|
||||
condition: $TRAVIS_OS_NAME = linux
|
||||
262
CHANGELOG.md
262
CHANGELOG.md
@@ -1,5 +1,267 @@
|
||||
# Changelog
|
||||
|
||||
## v3.10.0 - 2022-01-04
|
||||
|
||||
- A new `--list-all` (alias `-a`) flag is now available. It's similar to the
|
||||
exiting `--list` (`-l`) but prints all tasks, even those without a
|
||||
description
|
||||
([#383](https://github.com/go-task/task/issues/383), [#401](https://github.com/go-task/task/pull/401)).
|
||||
- It's now possible to schedule cleanup commands to run once a task finishes
|
||||
with the `defer:` keyword
|
||||
([Documentation](https://taskfile.dev/#/usage?id=doing-task-cleanup-with-defer), [#475](https://github.com/go-task/task/issues/475), [#626](https://github.com/go-task/task/pull/626)).
|
||||
- Remove long deprecated and undocumented `$` variable prefix and `^` command
|
||||
prefix
|
||||
([#642](https://github.com/go-task/task/issues/642), [#644](https://github.com/go-task/task/issues/644), [#645](https://github.com/go-task/task/pull/645)).
|
||||
- Add support for `.yaml` extension (as an alternative to `.yml`).
|
||||
This was requested multiple times throughout the years. Enjoy!
|
||||
([#183](https://github.com/go-task/task/issues/183), [#184](https://github.com/go-task/task/pull/184), [#369](https://github.com/go-task/task/issues/369), [#584](https://github.com/go-task/task/issues/584), [#621](https://github.com/go-task/task/pull/621)).
|
||||
- Fixed error when computing a variable when the task directory do not exist
|
||||
yet
|
||||
([#481](https://github.com/go-task/task/issues/481), [#579](https://github.com/go-task/task/pull/579)).
|
||||
|
||||
## v3.9.2 - 2021-12-02
|
||||
|
||||
- Upgrade [mvdan/sh](https://github.com/mvdan/sh) which contains a fix a for
|
||||
a important regression on Windows
|
||||
([#619](https://github.com/go-task/task/issues/619), [mvdan/sh#768](https://github.com/mvdan/sh/issues/768), [mvdan/sh#769](https://github.com/mvdan/sh/pull/769)).
|
||||
|
||||
## v3.9.1 - 2021-11-28
|
||||
|
||||
- Add logging in verbose mode for when a task starts and finishes
|
||||
([#533](https://github.com/go-task/task/issues/533), [#588](https://github.com/go-task/task/pull/588)).
|
||||
- Fix an issue with preconditions and context errors
|
||||
([#597](https://github.com/go-task/task/issues/597), [#598](https://github.com/go-task/task/pull/598)).
|
||||
- Quote each `{{.CLI_ARGS}}` argument to prevent one with spaces to become many
|
||||
([#613](https://github.com/go-task/task/pull/613)).
|
||||
- Fix nil pointer when `cmd:` was left empty
|
||||
([#612](https://github.com/go-task/task/issues/612), [#614](https://github.com/go-task/task/pull/614)).
|
||||
- Upgrade [mvdan/sh](https://github.com/mvdan/sh) which contains two
|
||||
relevant fixes:
|
||||
- Fix quote of empty strings in `shellQuote`
|
||||
([#609](https://github.com/go-task/task/issues/609), [mvdan/sh#763](https://github.com/mvdan/sh/issues/763)).
|
||||
- Fix issue of wrong environment variable being picked when there's another
|
||||
very similar one
|
||||
([#586](https://github.com/go-task/task/issues/586), [mvdan/sh#745](https://github.com/mvdan/sh/pull/745)).
|
||||
- Install shell completions automatically when installing via Homebrew
|
||||
([#264](https://github.com/go-task/task/issues/264), [#592](https://github.com/go-task/task/pull/592), [go-task/homebrew-tap#2](https://github.com/go-task/homebrew-tap/pull/2)).
|
||||
|
||||
## v3.9.0 - 2021-10-02
|
||||
|
||||
- A new `shellQuote` function was added to the template system
|
||||
(`{{shellQuote "a string"}}`) to ensure a string is safe for use in shell
|
||||
([mvdan/sh#727](https://github.com/mvdan/sh/pull/727), [mvdan/sh#737](https://github.com/mvdan/sh/pull/737), [Documentation](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote))
|
||||
- In this version [mvdan.cc/sh](https://github.com/mvdan/sh) was upgraded
|
||||
with some small fixes and features
|
||||
- The `read -p` flag is now supported
|
||||
([#314](https://github.com/go-task/task/issues/314), [mvdan/sh#551](https://github.com/mvdan/sh/issues/551), [mvdan/sh#772](https://github.com/mvdan/sh/pull/722))
|
||||
- The `pwd -P` and `pwd -L` flags are now supported
|
||||
([#553](https://github.com/go-task/task/issues/553), [mvdan/sh#724](https://github.com/mvdan/sh/issues/724), [mvdan/sh#728](https://github.com/mvdan/sh/pull/728))
|
||||
- The `$GID` environment variable is now correctly being set
|
||||
([#561](https://github.com/go-task/task/issues/561), [mvdan/sh#723](https://github.com/mvdan/sh/pull/723))
|
||||
|
||||
## v3.8.0 - 2021-09-26
|
||||
|
||||
- Add `interactive: true` setting to improve support for interactive CLI apps
|
||||
([#217](https://github.com/go-task/task/issues/217), [#563](https://github.com/go-task/task/pull/563)).
|
||||
- Fix some `nil` errors
|
||||
([#534](https://github.com/go-task/task/issues/534), [#573](https://github.com/go-task/task/pull/573)).
|
||||
- Add ability to declare an included Taskfile as optional
|
||||
([#519](https://github.com/go-task/task/issues/519), [#552](https://github.com/go-task/task/pull/552)).
|
||||
- Add support for including Taskfiles in the home directory by using `~`
|
||||
([#539](https://github.com/go-task/task/issues/539), [#557](https://github.com/go-task/task/pull/557)).
|
||||
|
||||
## v3.7.3 - 2021-09-04
|
||||
|
||||
- Add official support to Apple M1 ([#564](https://github.com/go-task/task/pull/564), [#567](https://github.com/go-task/task/pull/567)).
|
||||
- Our [official Homebrew tap](https://github.com/go-task/homebrew-tap) will
|
||||
support more platforms, including Apple M1
|
||||
|
||||
## v3.7.0 - 2021-07-31
|
||||
|
||||
- Add `run:` setting to control if tasks should run multiple times or not.
|
||||
Available options are `always` (the default), `when_changed` (if a variable
|
||||
modified the task) and `once` (run only once no matter what).
|
||||
This is a long time requested feature. Enjoy!
|
||||
([#53](https://github.com/go-task/task/issues/53), [#359](https://github.com/go-task/task/pull/359)).
|
||||
|
||||
## v3.6.0 - 2021-07-10
|
||||
|
||||
- Allow using both `sources:` and `status:` in the same task
|
||||
([#411](https://github.com/go-task/task/issues/411), [#427](https://github.com/go-task/task/issues/427), [#477](https://github.com/go-task/task/pull/477)).
|
||||
- Small optimization and bug fix: don't compute variables if not needed for
|
||||
`dotenv:` ([#517](https://github.com/go-task/task/issues/517)).
|
||||
|
||||
## v3.5.0 - 2021-07-04
|
||||
|
||||
- Add support for interpolation in `dotenv:`
|
||||
([#433](https://github.com/go-task/task/discussions/433), [#434](https://github.com/go-task/task/issues/434), [#453](https://github.com/go-task/task/pull/453)).
|
||||
|
||||
## v3.4.3 - 2021-05-30
|
||||
|
||||
- Add support for the `NO_COLOR` environment variable.
|
||||
([#459](https://github.com/go-task/task/issues/459), [fatih/color#137](https://github.com/fatih/color/pull/137)).
|
||||
- Fix bug where sources were not considering the right directory
|
||||
in `--watch` mode
|
||||
([#484](https://github.com/go-task/task/issues/484), [#485](https://github.com/go-task/task/pull/485)).
|
||||
|
||||
## v3.4.2 - 2021-04-23
|
||||
|
||||
- On watch, report which file failed to read
|
||||
([#472](https://github.com/go-task/task/pull/472)).
|
||||
- Do not try to catch SIGKILL signal, which are not actually possible
|
||||
([#476](https://github.com/go-task/task/pull/476)).
|
||||
- Improve version reporting when building Task from source using Go Modules
|
||||
([#462](https://github.com/go-task/task/pull/462), [#473](https://github.com/go-task/task/pull/473)).
|
||||
|
||||
## v3.4.1 - 2021-04-17
|
||||
|
||||
- Improve error reporting when parsing YAML: in some situations where you
|
||||
would just see an generic error, you'll now see the actual error with
|
||||
more detail: the YAML line the failed to parse, for example
|
||||
([#467](https://github.com/go-task/task/issues/467)).
|
||||
- A JSON Schema was published [here](https://json.schemastore.org/taskfile.json)
|
||||
and is automatically being used by some editors like Visual Studio Code
|
||||
([#135](https://github.com/go-task/task/issues/135)).
|
||||
- Print task name before the command in the log output
|
||||
([#398](https://github.com/go-task/task/pull/398)).
|
||||
|
||||
## v3.3.0 - 2021-03-20
|
||||
|
||||
- Add support for delegating CLI arguments to commands with `--` and a
|
||||
special `CLI_ARGS` variable
|
||||
([#327](https://github.com/go-task/task/issues/327)).
|
||||
- Add a `--concurrency` (alias `-C`) flag, to limit the number of tasks that
|
||||
run concurrently. This is useful for heavy workloads.
|
||||
([#345](https://github.com/go-task/task/pull/345)).
|
||||
|
||||
## v3.2.2 - 2021-01-12
|
||||
|
||||
- Improve performance of `--list` and `--summary` by skipping running shell
|
||||
variables for these flags
|
||||
([#332](https://github.com/go-task/task/issues/332)).
|
||||
- Fixed a bug where an environment in a Taskfile was not always overridable
|
||||
by the system environment
|
||||
([#425](https://github.com/go-task/task/issues/425)).
|
||||
- Fixed environment from .env files not being available as variables
|
||||
([#379](https://github.com/go-task/task/issues/379)).
|
||||
- The install script is now working for ARM platforms
|
||||
([#428](https://github.com/go-task/task/pull/428)).
|
||||
|
||||
## v3.2.1 - 2021-01-09
|
||||
|
||||
- Fixed some bugs and regressions regarding dynamic variables and directories
|
||||
([#426](https://github.com/go-task/task/issues/426)).
|
||||
- The [slim-sprig](https://github.com/go-task/slim-sprig) package was updated
|
||||
with the upstream [sprig](https://github.com/Masterminds/sprig).
|
||||
|
||||
## v3.2.0 - 2021-01-07
|
||||
|
||||
- Fix the `.task` directory being created in the task directory instead of the
|
||||
Taskfile directory
|
||||
([#247](https://github.com/go-task/task/issues/247)).
|
||||
- Fix a bug where dynamic variables (those declared with `sh:`) were not
|
||||
running in the task directory when the task has a custom dir or it was
|
||||
in an included Taskfile
|
||||
([#384](https://github.com/go-task/task/issues/384)).
|
||||
- The watch feature (via the `--watch` flag) got a few different bug fixes and
|
||||
should be more stable now
|
||||
([#423](https://github.com/go-task/task/pull/423), [#365](https://github.com/go-task/task/issues/365)).
|
||||
|
||||
## v3.1.0 - 2021-01-03
|
||||
|
||||
- Fix a bug when the checksum up-to-date resolution is used by a task
|
||||
with a custom `label:` attribute
|
||||
([#412](https://github.com/go-task/task/issues/412)).
|
||||
- Starting from this release, we're releasing official ARMv6 and ARM64 binaries
|
||||
for Linux
|
||||
([#375](https://github.com/go-task/task/issues/375), [#418](https://github.com/go-task/task/issues/418)).
|
||||
- Task now respects the order of declaration of included Taskfiles when
|
||||
evaluating variables declaring by them
|
||||
([#393](https://github.com/go-task/task/issues/393)).
|
||||
- `set -e` is now automatically set on every command. This was done to fix an
|
||||
issue where multiline string commands wouldn't really fail unless the
|
||||
sentence was in the last line
|
||||
([#403](https://github.com/go-task/task/issues/403)).
|
||||
|
||||
## v3.0.1 - 2020-12-26
|
||||
|
||||
- Allow use as a library by moving the required packages out of the `internal`
|
||||
directory
|
||||
([#358](https://github.com/go-task/task/pull/358)).
|
||||
- Do not error if a specified dotenv file does not exist
|
||||
([#378](https://github.com/go-task/task/issues/378), [#385](https://github.com/go-task/task/pull/385)).
|
||||
- Fix panic when you have empty tasks in your Taskfile
|
||||
([#338](https://github.com/go-task/task/issues/338), [#362](https://github.com/go-task/task/pull/362)).
|
||||
|
||||
## v3.0.0 - 2020-08-16
|
||||
|
||||
- On `v3`, all CLI variables will be considered global variables
|
||||
([#336](https://github.com/go-task/task/issues/336), [#341](https://github.com/go-task/task/pull/341))
|
||||
- Add support to `.env` like files
|
||||
([#324](https://github.com/go-task/task/issues/324), [#356](https://github.com/go-task/task/pull/356)).
|
||||
- Add `label:` to task so you can override the task name in the logs
|
||||
([#321](https://github.com/go-task/task/issues/321]), [#337](https://github.com/go-task/task/pull/337)).
|
||||
- Refactor how variables work on version 3
|
||||
([#311](https://github.com/go-task/task/pull/311)).
|
||||
- Disallow `expansions` on v3 since it has no effect.
|
||||
- `Taskvars.yml` is not automatically included anymore.
|
||||
- `Taskfile_{{OS}}.yml` is not automatically included anymore.
|
||||
- Allow interpolation on `includes`, so you can manually include a Taskfile
|
||||
based on operation system, for example.
|
||||
- Expose `.TASK` variable in templates with the task name
|
||||
([#252](https://github.com/go-task/task/issues/252)).
|
||||
- Implement short task syntax
|
||||
([#194](https://github.com/go-task/task/issues/194), [#240](https://github.com/go-task/task/pull/240)).
|
||||
- Added option to make included Taskfile run commands on its own directory
|
||||
([#260](https://github.com/go-task/task/issues/260), [#144](https://github.com/go-task/task/issues/144))
|
||||
- Taskfiles in version 1 are not supported anymore
|
||||
([#237](https://github.com/go-task/task/pull/237)).
|
||||
- Added global `method:` option. With this option, you can set a default
|
||||
method to all tasks in a Taskfile
|
||||
([#246](https://github.com/go-task/task/issues/246)).
|
||||
- Changed default method from `timestamp` to `checksum`
|
||||
([#246](https://github.com/go-task/task/issues/246)).
|
||||
- New magic variables are now available when using `status:`:
|
||||
`.TIMESTAMP` which contains the greatest modification date
|
||||
from the files listed in `sources:`, and `.CHECKSUM`, which
|
||||
contains a checksum of all files listed in `status:`.
|
||||
This is useful for manual checking when using external, or even remote,
|
||||
artifacts when using `status:`
|
||||
([#216](https://github.com/go-task/task/pull/216)).
|
||||
- We're now using [slim-sprig](https://github.com/go-task/slim-sprig) instead of
|
||||
[sprig](https://github.com/Masterminds/sprig), which allowed a file size
|
||||
reduction of about 22%
|
||||
([#219](https://github.com/go-task/task/pull/219)).
|
||||
- We now use some colors on Task output to better distinguish message types -
|
||||
commands are green, errors are red, etc
|
||||
([#207](https://github.com/go-task/task/pull/207)).
|
||||
|
||||
## v2.8.1 - 2020-05-20
|
||||
|
||||
- Fix error code for the `--help` flag
|
||||
([#300](https://github.com/go-task/task/issues/300), [#330](https://github.com/go-task/task/pull/330)).
|
||||
- Print version to stdout instead of stderr
|
||||
([#299](https://github.com/go-task/task/issues/299), [#329](https://github.com/go-task/task/pull/329)).
|
||||
- Supress `context` errors when using the `--watch` flag
|
||||
([#313](https://github.com/go-task/task/issues/313), [#317](https://github.com/go-task/task/pull/317)).
|
||||
- Support templating on description
|
||||
([#276](https://github.com/go-task/task/issues/276), [#283](https://github.com/go-task/task/pull/283)).
|
||||
|
||||
## v2.8.0 - 2019-12-07
|
||||
|
||||
- Add `--parallel` flag (alias `-p`) to run tasks given by the command line in
|
||||
parallel
|
||||
([#266](https://github.com/go-task/task/pull/266)).
|
||||
- Fixed bug where calling the `task` CLI only informing global vars would not
|
||||
execute the `default` task.
|
||||
- Add hability to silent all tasks by adding `silent: true` a the root of the
|
||||
Taskfile.
|
||||
|
||||
## v2.7.1 - 2019-11-10
|
||||
|
||||
- Fix error being raised when `exit 0` was called
|
||||
([#251](https://github.com/go-task/task/issues/251)).
|
||||
|
||||
## v2.7.0 - 2019-09-22
|
||||
|
||||
- Fixed panic bug when assigning a global variable
|
||||
|
||||
31
README.md
31
README.md
@@ -1,22 +1,15 @@
|
||||
[](https://travis-ci.org/go-task/task)
|
||||
<div align="center">
|
||||
<a href="https://taskfile.dev">
|
||||
<img src="docs/Logo.png" width="200px" height="200px" />
|
||||
</a>
|
||||
|
||||
# Task
|
||||
<h1>Task</h1>
|
||||
|
||||
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/).
|
||||
<p>
|
||||
Task is a task runner / build tool that aims to be simpler and easier to use than, for example, <a href="https://www.gnu.org/software/make/">GNU Make<a>.
|
||||
</p>
|
||||
|
||||
See [taskfile.dev](https://taskfile.dev) for documentation.
|
||||
|
||||
---
|
||||
|
||||
## Sponsors
|
||||
|
||||
[](https://opencollective.com/task)
|
||||
|
||||
## Backers
|
||||
|
||||
[](https://opencollective.com/task)
|
||||
|
||||
## Contributors
|
||||
|
||||
[](https://github.com/go-task/task/graphs/contributors)
|
||||
<p>
|
||||
See <a href="https://taskfile.dev">taskfile.dev</a> for the documentation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
47
Taskfile.yml
47
Taskfile.yml
@@ -1,7 +1,9 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
docs: ./docs
|
||||
docs:
|
||||
taskfile: ./docs
|
||||
dir: ./docs
|
||||
|
||||
vars:
|
||||
GIT_COMMIT:
|
||||
@@ -10,6 +12,9 @@ vars:
|
||||
GO_PACKAGES:
|
||||
sh: go list ./...
|
||||
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
@@ -19,11 +24,15 @@ tasks:
|
||||
desc: Installs Task
|
||||
cmds:
|
||||
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
|
||||
dl-deps:
|
||||
desc: Downloads cli dependencies
|
||||
mod:
|
||||
desc: Downloads and tidy Go modules
|
||||
cmds:
|
||||
- go mod download
|
||||
- go mod tidy
|
||||
|
||||
cli-deps:
|
||||
desc: Downloads CLI dependencies
|
||||
cmds:
|
||||
- task: go-get
|
||||
vars: {REPO: golang.org/x/lint/golint}
|
||||
@@ -32,17 +41,6 @@ tasks:
|
||||
- task: go-get
|
||||
vars: {REPO: github.com/goreleaser/godownloader}
|
||||
|
||||
vendor:
|
||||
desc: Sync vendor/ directory according to go.mod file
|
||||
cmds:
|
||||
- go mod vendor
|
||||
|
||||
update-deps:
|
||||
desc: Updates dependencies
|
||||
cmds:
|
||||
- dep ensure
|
||||
- dep ensure -update
|
||||
|
||||
clean:
|
||||
desc: Cleans temp files and folders
|
||||
cmds:
|
||||
@@ -65,22 +63,19 @@ tasks:
|
||||
cmds:
|
||||
- goreleaser --snapshot --rm-dist
|
||||
|
||||
generate-install-script:
|
||||
gen-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
|
||||
- task: go-get
|
||||
vars: {REPO: golang.org/x/lint/golint}
|
||||
- task: lint
|
||||
- task: test
|
||||
|
||||
go-get:
|
||||
cmds:
|
||||
- go get -u {{.REPO}}
|
||||
go-get: go get -u {{.REPO}}
|
||||
|
||||
packages:
|
||||
cmds:
|
||||
|
||||
64
args/args.go
Normal file
64
args/args.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package args
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// ParseV3 parses command line argument: tasks and global variables
|
||||
func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
||||
var calls []taskfile.Call
|
||||
var globals = &taskfile.Vars{}
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.Contains(arg, "=") {
|
||||
calls = append(calls, taskfile.Call{Task: arg})
|
||||
continue
|
||||
}
|
||||
|
||||
name, value := splitVar(arg)
|
||||
globals.Set(name, taskfile.Var{Static: value})
|
||||
}
|
||||
|
||||
if len(calls) == 0 {
|
||||
calls = append(calls, taskfile.Call{Task: "default"})
|
||||
}
|
||||
|
||||
return calls, globals
|
||||
}
|
||||
|
||||
// ParseV2 parses command line argument: tasks and vars of each task
|
||||
func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
||||
var calls []taskfile.Call
|
||||
var globals = &taskfile.Vars{}
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.Contains(arg, "=") {
|
||||
calls = append(calls, taskfile.Call{Task: arg})
|
||||
continue
|
||||
}
|
||||
|
||||
if len(calls) < 1 {
|
||||
name, value := splitVar(arg)
|
||||
globals.Set(name, taskfile.Var{Static: value})
|
||||
} else {
|
||||
if calls[len(calls)-1].Vars == nil {
|
||||
calls[len(calls)-1].Vars = &taskfile.Vars{}
|
||||
}
|
||||
name, value := splitVar(arg)
|
||||
calls[len(calls)-1].Vars.Set(name, taskfile.Var{Static: value})
|
||||
}
|
||||
}
|
||||
|
||||
if len(calls) == 0 {
|
||||
calls = append(calls, taskfile.Call{Task: "default"})
|
||||
}
|
||||
|
||||
return calls, globals
|
||||
}
|
||||
|
||||
func splitVar(s string) (string, string) {
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
return pair[0], pair[1]
|
||||
}
|
||||
209
args/args_test.go
Normal file
209
args/args_test.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package args_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3/args"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func TestArgsV3(t *testing.T) {
|
||||
tests := []struct {
|
||||
Args []string
|
||||
ExpectedCalls []taskfile.Call
|
||||
ExpectedGlobals *taskfile.Vars
|
||||
}{
|
||||
{
|
||||
Args: []string{"task-a", "task-b", "task-c"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
},
|
||||
ExpectedGlobals: &taskfile.Vars{
|
||||
Keys: []string{"FOO", "BAR", "BAZ"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"FOO": taskfile.Var{Static: "bar"},
|
||||
"BAR": taskfile.Var{Static: "baz"},
|
||||
"BAZ": taskfile.Var{Static: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
},
|
||||
ExpectedGlobals: &taskfile.Vars{
|
||||
Keys: []string{"CONTENT"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"CONTENT": taskfile.Var{Static: "with some spaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"FOO=bar", "task-a", "task-b"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
},
|
||||
ExpectedGlobals: &taskfile.Vars{
|
||||
Keys: []string{"FOO"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"FOO": {Static: "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: nil,
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "default"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "default"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"FOO=bar", "BAR=baz"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "default"},
|
||||
},
|
||||
ExpectedGlobals: &taskfile.Vars{
|
||||
Keys: []string{"FOO", "BAR"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"FOO": {Static: "bar"},
|
||||
"BAR": {Static: "baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
||||
calls, globals := args.ParseV3(test.Args...)
|
||||
assert.Equal(t, test.ExpectedCalls, calls)
|
||||
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgsV2(t *testing.T) {
|
||||
tests := []struct {
|
||||
Args []string
|
||||
ExpectedCalls []taskfile.Call
|
||||
ExpectedGlobals *taskfile.Vars
|
||||
}{
|
||||
{
|
||||
Args: []string{"task-a", "task-b", "task-c"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{
|
||||
Task: "task-a",
|
||||
Vars: &taskfile.Vars{
|
||||
Keys: []string{"FOO"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"FOO": taskfile.Var{Static: "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Task: "task-b"},
|
||||
{
|
||||
Task: "task-c",
|
||||
Vars: &taskfile.Vars{
|
||||
Keys: []string{"BAR", "BAZ"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"BAR": taskfile.Var{Static: "baz"},
|
||||
"BAZ": taskfile.Var{Static: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{
|
||||
Task: "task-a",
|
||||
Vars: &taskfile.Vars{
|
||||
Keys: []string{"CONTENT"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"CONTENT": taskfile.Var{Static: "with some spaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"FOO=bar", "task-a", "task-b"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
},
|
||||
ExpectedGlobals: &taskfile.Vars{
|
||||
Keys: []string{"FOO"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"FOO": {Static: "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: nil,
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "default"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "default"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"FOO=bar", "BAR=baz"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "default"},
|
||||
},
|
||||
ExpectedGlobals: &taskfile.Vars{
|
||||
Keys: []string{"FOO", "BAR"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"FOO": {Static: "bar"},
|
||||
"BAR": {Static: "baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
||||
calls, globals := args.ParseV2(test.Args...)
|
||||
assert.Equal(t, test.ExpectedCalls, calls)
|
||||
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,421 +0,0 @@
|
||||
// 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
|
||||
`)
|
||||
150
cmd/task/task.go
150
cmd/task/task.go
@@ -2,20 +2,26 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-task/task/v2"
|
||||
"github.com/go-task/task/v2/internal/args"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/args"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "master"
|
||||
version = ""
|
||||
)
|
||||
|
||||
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]
|
||||
@@ -27,12 +33,14 @@ Example: 'task hello' with the following 'Taskfile.yml' file will generate an
|
||||
'output.txt' file with the content "hello".
|
||||
|
||||
'''
|
||||
hello:
|
||||
cmds:
|
||||
- echo "I am going to write a file named 'output.txt' now."
|
||||
- echo "hello" > output.txt
|
||||
generates:
|
||||
- output.txt
|
||||
version: '3'
|
||||
tasks:
|
||||
hello:
|
||||
cmds:
|
||||
- echo "I am going to write a file named 'output.txt' now."
|
||||
- echo "hello" > output.txt
|
||||
generates:
|
||||
- output.txt
|
||||
'''
|
||||
|
||||
Options:
|
||||
@@ -49,8 +57,10 @@ func main() {
|
||||
|
||||
var (
|
||||
versionFlag bool
|
||||
helpFlag bool
|
||||
init bool
|
||||
list bool
|
||||
listAll bool
|
||||
status bool
|
||||
force bool
|
||||
watch bool
|
||||
@@ -58,28 +68,41 @@ func main() {
|
||||
silent bool
|
||||
dry bool
|
||||
summary bool
|
||||
parallel bool
|
||||
concurrency int
|
||||
dir string
|
||||
entrypoint string
|
||||
output string
|
||||
color bool
|
||||
)
|
||||
|
||||
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(&helpFlag, "help", "h", false, "shows Task usage")
|
||||
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yaml in the current folder")
|
||||
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
|
||||
pflag.BoolVarP(&listAll, "list-all", "a", false, "lists tasks with or without a description")
|
||||
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.BoolVarP(¶llel, "parallel", "p", false, "executes tasks provided on command line in parallel")
|
||||
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
|
||||
pflag.BoolVar(&summary, "summary", false, "show summary about a task")
|
||||
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
|
||||
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
|
||||
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
|
||||
pflag.BoolVarP(&color, "color", "c", true, "colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable")
|
||||
pflag.IntVarP(&concurrency, "concurrency", "C", 0, "limit number tasks to run concurrently")
|
||||
pflag.Parse()
|
||||
|
||||
if versionFlag {
|
||||
log.Printf("Task version: %s\n", version)
|
||||
fmt.Printf("Task version: %s\n", getVersion())
|
||||
return
|
||||
}
|
||||
|
||||
if helpFlag {
|
||||
pflag.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -101,19 +124,20 @@ func main() {
|
||||
if entrypoint != "" {
|
||||
dir = filepath.Dir(entrypoint)
|
||||
entrypoint = filepath.Base(entrypoint)
|
||||
} else {
|
||||
entrypoint = "Taskfile.yml"
|
||||
}
|
||||
|
||||
e := task.Executor{
|
||||
Force: force,
|
||||
Watch: watch,
|
||||
Verbose: verbose,
|
||||
Silent: silent,
|
||||
Dir: dir,
|
||||
Dry: dry,
|
||||
Entrypoint: entrypoint,
|
||||
Summary: summary,
|
||||
Force: force,
|
||||
Watch: watch,
|
||||
Verbose: verbose,
|
||||
Silent: silent,
|
||||
Dir: dir,
|
||||
Dry: dry,
|
||||
Entrypoint: entrypoint,
|
||||
Summary: summary,
|
||||
Parallel: parallel,
|
||||
Color: color,
|
||||
Concurrency: concurrency,
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
@@ -124,23 +148,41 @@ func main() {
|
||||
if err := e.Setup(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if list {
|
||||
e.PrintTasksHelp()
|
||||
v, err := e.Taskfile.ParsedVersion()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
arguments := pflag.Args()
|
||||
if len(arguments) == 0 {
|
||||
log.Println("task: No argument given, trying default task")
|
||||
arguments = []string{"default"}
|
||||
if list {
|
||||
e.ListTasksWithDesc()
|
||||
return
|
||||
}
|
||||
|
||||
calls, globals := args.Parse(arguments...)
|
||||
for name, value := range globals {
|
||||
e.Taskfile.Vars[name] = value
|
||||
if listAll {
|
||||
e.ListAllTasks()
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
calls []taskfile.Call
|
||||
globals *taskfile.Vars
|
||||
)
|
||||
|
||||
tasksAndVars, cliArgs, err := getArgs()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if v >= 3.0 {
|
||||
calls, globals = args.ParseV3(tasksAndVars...)
|
||||
} else {
|
||||
calls, globals = args.ParseV2(tasksAndVars...)
|
||||
}
|
||||
|
||||
globals.Set("CLI_ARGS", taskfile.Var{Static: cliArgs})
|
||||
e.Taskfile.Vars.Merge(globals)
|
||||
|
||||
ctx := context.Background()
|
||||
if !watch {
|
||||
ctx = getSignalContext()
|
||||
@@ -154,13 +196,35 @@ func main() {
|
||||
}
|
||||
|
||||
if err := e.Run(ctx, calls...); err != nil {
|
||||
log.Fatal(err)
|
||||
e.Logger.Errf(logger.Red, "%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getArgs() ([]string, string, error) {
|
||||
var (
|
||||
args = pflag.Args()
|
||||
doubleDashPos = pflag.CommandLine.ArgsLenAtDash()
|
||||
)
|
||||
|
||||
if doubleDashPos == -1 {
|
||||
return args, "", nil
|
||||
}
|
||||
|
||||
var quotedCliArgs []string
|
||||
for _, arg := range args[doubleDashPos:] {
|
||||
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
|
||||
}
|
||||
return args[:doubleDashPos], strings.Join(quotedCliArgs, " "), nil
|
||||
}
|
||||
|
||||
func getSignalContext() context.Context {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
sig := <-ch
|
||||
@@ -169,3 +233,21 @@ func getSignalContext() context.Context {
|
||||
}()
|
||||
return ctx
|
||||
}
|
||||
|
||||
func getVersion() string {
|
||||
if version != "" {
|
||||
return version
|
||||
}
|
||||
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok || info.Main.Version == "" {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
version = info.Main.Version
|
||||
if info.Main.Sum != "" {
|
||||
version += fmt.Sprintf(" (%s)", info.Main.Sum)
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
21
completion/bash/task.bash
Normal file
21
completion/bash/task.bash
Normal file
@@ -0,0 +1,21 @@
|
||||
_task_completion()
|
||||
{
|
||||
local scripts;
|
||||
local curr_arg;
|
||||
|
||||
# Remove colon from word breaks
|
||||
COMP_WORDBREAKS=${COMP_WORDBREAKS//:}
|
||||
|
||||
scripts=$(task -l | sed '1d' | awk '{ print $2 }' | sed 's/:$//');
|
||||
|
||||
curr_arg="${COMP_WORDS[COMP_CWORD]:-"."}"
|
||||
|
||||
# Do not accept more than 1 argument
|
||||
if [ "${#COMP_WORDS[@]}" != "2" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -c | echo "$scripts" | grep -- $curr_arg));
|
||||
}
|
||||
|
||||
complete -F _task_completion task
|
||||
24
completion/fish/task.fish
Normal file
24
completion/fish/task.fish
Normal file
@@ -0,0 +1,24 @@
|
||||
function __task_get_tasks --description "Prints all available tasks with their description"
|
||||
task -l | sed '1d' | awk '{ $1=""; print $0 }' | sed 's/:\ /\t/g' | string trim
|
||||
end
|
||||
|
||||
complete -c task -d '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.' -xa "(__task_get_tasks)"
|
||||
|
||||
|
||||
complete -c task -s c -l color -d 'colored output (default true)'
|
||||
complete -c task -s d -l dir -d 'sets directory of execution'
|
||||
complete -c task -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them'
|
||||
complete -c task -s f -l force -d 'forces execution even when the task is up-to-date'
|
||||
complete -c task -s h -l help -d 'shows Task usage'
|
||||
complete -c task -s i -l init -d 'creates a new Taskfile.yml in the current folder'
|
||||
complete -c task -s l -l list -d 'lists tasks with description of current Taskfile'
|
||||
complete -c task -s o -l output -d 'sets output style: [interleaved|group|prefixed]' -xa "interleaved group prefixed"
|
||||
complete -c task -s p -l parallel -d 'executes tasks provided on command line in parallel'
|
||||
complete -c task -s s -l silent -d 'disables echoing'
|
||||
complete -c task -l status -d 'exits with non-zero exit code if any of the given tasks is not up-to-date'
|
||||
complete -c task -l summary -d 'show summary about a task'
|
||||
complete -c task -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml"'
|
||||
complete -c task -s v -l verbose -d 'enables verbose mode'
|
||||
complete -c task -l version -d 'show Task version'
|
||||
complete -c task -s w -l watch -d 'enables watch of the given task'
|
||||
10
completion/ps/task.ps1
Normal file
10
completion/ps/task.ps1
Normal file
@@ -0,0 +1,10 @@
|
||||
$scriptBlock = {
|
||||
param($commandName, $wordToComplete, $cursorPosition)
|
||||
$curReg = "task{.exe}? (.*?)$"
|
||||
$startsWith = $wordToComplete | Select-String $curReg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||
$reg = "\* ($startsWith.+?):"
|
||||
$listOutput = $(task -l)
|
||||
$listOutput | Select-String $reg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value + " " }
|
||||
}
|
||||
|
||||
Register-ArgumentCompleter -Native -CommandName task -ScriptBlock $scriptBlock
|
||||
@@ -4,8 +4,8 @@
|
||||
function __list() {
|
||||
local -a scripts
|
||||
|
||||
if [ -f Taskfile.yml ]; then
|
||||
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/'))
|
||||
if [ -f Taskfile.yml ] || [ -f Taskfile.yaml ]; then
|
||||
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/g'))
|
||||
_describe 'script' scripts
|
||||
fi
|
||||
}
|
||||
|
||||
25
concurrency.go
Normal file
25
concurrency.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package task
|
||||
|
||||
func (e *Executor) acquireConcurrencyLimit() func() {
|
||||
if e.concurrencySemaphore == nil {
|
||||
return emptyFunc
|
||||
}
|
||||
|
||||
e.concurrencySemaphore <- struct{}{}
|
||||
return func() {
|
||||
<-e.concurrencySemaphore
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) releaseConcurrencyLimit() func() {
|
||||
if e.concurrencySemaphore == nil {
|
||||
return emptyFunc
|
||||
}
|
||||
|
||||
<-e.concurrencySemaphore
|
||||
return func() {
|
||||
e.concurrencySemaphore <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func emptyFunc() {}
|
||||
BIN
docs/Logo.png
Normal file
BIN
docs/Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -1,5 +1,9 @@
|
||||
# Task
|
||||
|
||||
<div align="center">
|
||||
<img id="logo" src="/Logo.png" height="250px" width="250px" />
|
||||
</div>
|
||||
|
||||
Task is a task runner / build tool that aims to be simpler and easier to use
|
||||
than, for example, [GNU Make][make].
|
||||
|
||||
@@ -11,7 +15,7 @@ Once [installed](installation.md), you just need to describe your build tasks
|
||||
using a simple [YAML][yaml] schema in a file called `Taskfile.yml`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
hello:
|
||||
@@ -38,18 +42,6 @@ guide to check the full schema documentation and Task features.
|
||||
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/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
install:
|
||||
@@ -9,4 +9,9 @@ tasks:
|
||||
serve:
|
||||
desc: Serves the documentation site locally
|
||||
cmds:
|
||||
- docsify serve docs
|
||||
- docsify serve .
|
||||
|
||||
ico:
|
||||
desc: Generate favicon.ico from Logo.png
|
||||
cmds:
|
||||
- convert -background transparent "Logo.png" -define icon:auto-resize=16,24,32,48,64,72,96,128,256 "favicon.ico"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
- [Installation](installation.md)
|
||||
- [Usage](usage.md)
|
||||
- [Styleguide](styleguide.md)
|
||||
- [Taskfile Versions](taskfile_versions.md)
|
||||
- [Examples](examples.md)
|
||||
- [Community](community.md)
|
||||
- [Releasing Task](releasing_task.md)
|
||||
- [Alternative Task Runners](alternative_task_runners.md)
|
||||
- [Github](https://github.com/go-task/task)
|
||||
- [Donate](donate.md)
|
||||
- [GitHub](https://github.com/go-task/task)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# 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
|
||||
50
docs/community.md
Normal file
50
docs/community.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Community
|
||||
|
||||
Some of the work to improve the Task ecosystem is done by the community, be
|
||||
it installation methods or integrations with code editor. I (the author) am
|
||||
thankful for everyone that helps me to improve the overall experience.
|
||||
|
||||
## Editor Integrations
|
||||
|
||||
### JSON Schema
|
||||
|
||||
[@KROSF](https://github.com/KROSF) worked on a JSON Schema [into this Gist](https://gist.github.com/KROSF/c5435acf590acd632f71bb720f685895),
|
||||
which later was made officially available by [@Crandel](https://github.com/Crandel)
|
||||
at [https://json.schemastore.org/taskfile.json](https://json.schemastore.org/taskfile.json).
|
||||
Further improvements are possible by opening pull requests changing
|
||||
[this file](https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/taskfile.json).
|
||||
Some code editors, like Visual Studio Code, make use of Schema Store
|
||||
automatically.
|
||||
|
||||
### Visual Studio Code extension
|
||||
|
||||
Additionally, there's also some work done by
|
||||
[@paulvarache](https://github.com/paulvarache) in making an Visual Studio Code
|
||||
extension, which has its code [here](https://github.com/paulvarache/vscode-taskfile)
|
||||
and is published [here](https://marketplace.visualstudio.com/items?itemName=paulvarache.vscode-taskfile).
|
||||
|
||||
### Sublime Text 4 package
|
||||
|
||||
There is a convenience wrapper for initializing and running tasks from Sublime Text's command palette. The package is
|
||||
developed by [@biozz](https://github.com/biozz), the source code is available [here](https://github.com/biozz/sublime-taskfile)
|
||||
and it is published on Package Control [here](https://packagecontrol.io/packages/Taskfile).
|
||||
|
||||
## Installation methods
|
||||
|
||||
Some installation methods are maintained by third party:
|
||||
|
||||
- [GitHub Actions](https://github.com/arduino/setup-task)
|
||||
by [@arduino](https://github.com/arduino)
|
||||
- [AUR](https://aur.archlinux.org/packages/taskfile-git)
|
||||
by [@kovetskiy](https://github.com/kovetskiy)
|
||||
- [Scoop](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json)
|
||||
|
||||
## More
|
||||
|
||||
Also, thanks for all the [code contributors](https://github.com/go-task/task/graphs/contributors),
|
||||
[financial contributors](https://opencollective.com/task), all those who
|
||||
[reported bugs](https://github.com/go-task/task/issues?q=is%3Aissue) and
|
||||
[answered questions](https://github.com/go-task/task/discussions).
|
||||
|
||||
If you know something that is missing in this document, please submit a
|
||||
pull request.
|
||||
27
docs/donate.md
Normal file
27
docs/donate.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Donate
|
||||
|
||||
If you find this project useful, you can consider donating by using one of the
|
||||
channels listed below.
|
||||
|
||||
This is just a way of saying "thank you", it won't give you any benefits like
|
||||
higher priority on issues or something similar.
|
||||
|
||||
## Open Collective
|
||||
|
||||
Task is on [Open Collective](https://opencollective.com/task) and you have
|
||||
these options to donate:
|
||||
|
||||
- [$2 per month](https://opencollective.com/task/contribute/backer-4034/checkout)
|
||||
- [$5 per month](https://opencollective.com/task/contribute/supporter-8404/checkout)
|
||||
- [$20 per month](https://opencollective.com/task/contribute/sponsor-4035/checkout)
|
||||
- [$50 per month](https://opencollective.com/task/contribute/sponsor-28775/checkout)
|
||||
- [Custom value - One-time donation option supported](https://opencollective.com/task/donate)
|
||||
|
||||
## PayPal
|
||||
|
||||
- [Any value - One-time donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=USD&source=url)
|
||||
|
||||
## PIX (Brazil only)
|
||||
|
||||
If you're Brazilian, you can donate any value by
|
||||
<a href="/pix.png" target="_blank">using this QR Code</a>.
|
||||
@@ -1,7 +0,0 @@
|
||||
# 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
BIN
docs/favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 170 KiB |
@@ -6,25 +6,25 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/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>
|
||||
<style>
|
||||
#logo {
|
||||
transition: all 0.7s ease;
|
||||
}
|
||||
#logo:hover {
|
||||
-webkit-transform: rotateZ(360deg);
|
||||
-ms-transform: rotateZ(360deg);
|
||||
transform: rotateZ(360deg);
|
||||
}
|
||||
.app-name-link img {
|
||||
width: 125px;
|
||||
}
|
||||
:root {
|
||||
--base-font-size: 14px;
|
||||
--theme-color: #29beb0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
@@ -32,18 +32,19 @@
|
||||
window.$docsify = {
|
||||
name: 'Task',
|
||||
repo: 'go-task/task',
|
||||
ga: 'UA-126286662-1',
|
||||
themeColor: '#00add8',
|
||||
logo: 'Logo.png',
|
||||
themeColor: '#29beb0',
|
||||
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>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify-themeable/dist/js/docsify-themeable.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify-tabs"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-yaml.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT.
|
||||
# Code generated by godownloader on 2021-01-12T13:40:40Z. DO NOT EDIT.
|
||||
#
|
||||
|
||||
usage() {
|
||||
@@ -27,11 +27,12 @@ parse_args() {
|
||||
# over-ridden by flag below
|
||||
|
||||
BINDIR=${BINDIR:-./bin}
|
||||
while getopts "b:dh?" arg; do
|
||||
while getopts "b:dh?x" arg; do
|
||||
case "$arg" in
|
||||
b) BINDIR="$OPTARG" ;;
|
||||
d) log_set_priority 10 ;;
|
||||
h | \?) usage "$0" ;;
|
||||
x) set -x ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
@@ -42,46 +43,41 @@ parse_args() {
|
||||
# network, either nothing will happen or will syntax error
|
||||
# out preventing half-done work
|
||||
execute() {
|
||||
tmpdir=$(mktmpdir)
|
||||
tmpdir=$(mktemp -d)
|
||||
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
|
||||
test ! -d "${BINDIR}" && install -d "${BINDIR}"
|
||||
for binexe in $BINARIES; do
|
||||
if [ "$OS" = "windows" ]; then
|
||||
binexe="${binexe}.exe"
|
||||
fi
|
||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
||||
log_info "installed ${BINDIR}/${binexe}"
|
||||
done
|
||||
rm -rf "${tmpdir}"
|
||||
}
|
||||
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 ;;
|
||||
get_binaries() {
|
||||
case "$PLATFORM" in
|
||||
darwin/amd64) BINARIES="task" ;;
|
||||
darwin/arm64) BINARIES="task" ;;
|
||||
darwin/armv6) BINARIES="task" ;;
|
||||
linux/386) BINARIES="task" ;;
|
||||
linux/amd64) BINARIES="task" ;;
|
||||
linux/arm64) BINARIES="task" ;;
|
||||
linux/armv6) BINARIES="task" ;;
|
||||
windows/386) BINARIES="task" ;;
|
||||
windows/amd64) BINARIES="task" ;;
|
||||
windows/arm64) BINARIES="task" ;;
|
||||
windows/armv6) BINARIES="task" ;;
|
||||
*)
|
||||
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
|
||||
;;
|
||||
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
|
||||
@@ -99,8 +95,8 @@ tag_to_version() {
|
||||
VERSION=${TAG#v}
|
||||
}
|
||||
adjust_format() {
|
||||
# change format (tar.gz or zip) based on ARCH
|
||||
case ${ARCH} in
|
||||
# change format (tar.gz or zip) based on OS
|
||||
case ${OS} in
|
||||
windows) FORMAT=zip ;;
|
||||
esac
|
||||
true
|
||||
@@ -174,7 +170,9 @@ log_crit() {
|
||||
uname_os() {
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
case "$os" in
|
||||
msys_nt) os="windows" ;;
|
||||
cygwin_nt*) os="windows" ;;
|
||||
mingw*) os="windows" ;;
|
||||
msys_nt*) os="windows" ;;
|
||||
esac
|
||||
echo "$os"
|
||||
}
|
||||
@@ -186,9 +184,9 @@ uname_arch() {
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="arm5" ;;
|
||||
armv6*) arch="arm6" ;;
|
||||
armv7*) arch="arm7" ;;
|
||||
armv5*) arch="armv5" ;;
|
||||
armv6*) arch="armv6" ;;
|
||||
armv7*) arch="armv7" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
@@ -234,8 +232,8 @@ uname_arch_check() {
|
||||
untar() {
|
||||
tarball=$1
|
||||
case "${tarball}" in
|
||||
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
|
||||
*.tar) tar -xf "${tarball}" ;;
|
||||
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
|
||||
*.tar) tar --no-same-owner -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
@@ -243,11 +241,6 @@ untar() {
|
||||
;;
|
||||
esac
|
||||
}
|
||||
mktmpdir() {
|
||||
test -z "$TMPDIR" && TMPDIR="$(mktemp -d)"
|
||||
mkdir -p "${TMPDIR}"
|
||||
echo "${TMPDIR}"
|
||||
}
|
||||
http_download_curl() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
@@ -368,7 +361,7 @@ uname_arch_check "$ARCH"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
check_platform
|
||||
get_binaries
|
||||
|
||||
tag_to_version
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Installation
|
||||
|
||||
## Binary
|
||||
Task offers many installation methods. Check out the available methods below.
|
||||
|
||||
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.
|
||||
## Package Managers
|
||||
|
||||
## Homebrew
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **Homebrew**
|
||||
|
||||
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
|
||||
Task is as simple as running:
|
||||
@@ -15,19 +15,17 @@ Task is as simple as running:
|
||||
brew install go-task/tap/go-task
|
||||
```
|
||||
|
||||
> This installation method is only currently supported on amd64 architectures.
|
||||
#### **Snap**
|
||||
|
||||
## Snap
|
||||
|
||||
Task is available for [Snapcraft][snapcraft], but keep in mind that your
|
||||
Task is available in [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
|
||||
sudo snap install task --classic
|
||||
```
|
||||
|
||||
## Scoop
|
||||
#### **Scoop**
|
||||
|
||||
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
|
||||
to install Task like:
|
||||
@@ -40,51 +38,101 @@ 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
|
||||
#### **AUR**
|
||||
|
||||
Task now uses [Go Modules](https://github.com/golang/go/wiki/Modules), which
|
||||
means you may have trouble compiling it on older Go versions.
|
||||
If you're on Arch Linux you can install Task from
|
||||
[AUR](https://aur.archlinux.org/packages/taskfile-git) using your favorite
|
||||
package manager such as `yay`, `pacaur` or `yaourt`:
|
||||
|
||||
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
|
||||
```cmd
|
||||
yay -S taskfile-git
|
||||
```
|
||||
|
||||
Installing in another directory:
|
||||
This installation method is community owned, but since it's `-git` version of
|
||||
the package, it's always latest available version based on the Git repository.
|
||||
|
||||
<!-- tabs:end -->
|
||||
|
||||
## Get The Binary
|
||||
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **Binary**
|
||||
|
||||
You can download the binary from the [releases page on GitHub][releases] and
|
||||
add to your `$PATH`.
|
||||
|
||||
DEB and RPM packages are also available.
|
||||
|
||||
The `task_checksums.txt` file contains the SHA-256 checksum for each file.
|
||||
|
||||
#### **Install Script**
|
||||
|
||||
We also have an [install script][installscript] which is very useful in
|
||||
scenarios like CI. Many thanks to [GoDownloader][godownloader] for enabling the
|
||||
easy generation of this script.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/go-task/task
|
||||
cd task
|
||||
# For Default Installation to ./bin with debug logging
|
||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
|
||||
|
||||
# compiling binary to $GOPATH/bin
|
||||
go install -v
|
||||
# For Installation To /usr/local/bin for userwide access with debug logging
|
||||
# May require sudo sh
|
||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
|
||||
|
||||
# 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.
|
||||
|
||||
#### **GitHub Actions**
|
||||
|
||||
If you want to install Task in GitHub Actions you can try using
|
||||
[this action](https://github.com/arduino/setup-task)
|
||||
by the Arduino team:
|
||||
|
||||
```yaml
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
```
|
||||
|
||||
This installation method is community owned.
|
||||
|
||||
<!-- tabs:end -->
|
||||
|
||||
## Build From Source
|
||||
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **Go Modules**
|
||||
|
||||
First, make sure you have [Go][go] properly installed and setup.
|
||||
|
||||
You can easily install the latest release globally by running:
|
||||
|
||||
```bash
|
||||
go install github.com/go-task/task/v3/cmd/task@latest
|
||||
```
|
||||
|
||||
Or you can install into another directory:
|
||||
|
||||
```bash
|
||||
env GOBIN=/bin go install github.com/go-task/task/v3/cmd/task@latest
|
||||
```
|
||||
|
||||
If using Go 1.15 or earlier, instead use:
|
||||
|
||||
```bash
|
||||
env GO111MODULE=on go get -u github.com/go-task/task/v3/cmd/task@latest
|
||||
```
|
||||
|
||||
> For CI environments we recommend using the [Install Script](#get-the-binary)
|
||||
> instead, which is faster and more stable, since it'll just download the latest
|
||||
> released binary.
|
||||
|
||||
<!-- tabs:end -->
|
||||
|
||||
[go]: https://golang.org/
|
||||
[snapcraft]: https://snapcraft.io/
|
||||
[snapcraft]: https://snapcraft.io/task
|
||||
[homebrew]: https://brew.sh/
|
||||
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
|
||||
[releases]: https://github.com/go-task/task/releases
|
||||
|
||||
BIN
docs/pix.png
Normal file
BIN
docs/pix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -4,9 +4,9 @@ The release process of Task is done with the help of
|
||||
[GoReleaser][goreleaser]. You can test the release process locally by calling
|
||||
the `test-release` task of the Taskfile.
|
||||
|
||||
The Travis CI should release automatically when a new
|
||||
Git tag is pushed to master, either for the artifact uploading (raw executables
|
||||
and DEB and RPM packages)
|
||||
[GitHub Actions](https://github.com/go-task/task/actions) should release
|
||||
artifacts automatically when a new Git tag is pushed to master
|
||||
(raw executables and DEB and RPM packages).
|
||||
|
||||
# Homebrew
|
||||
|
||||
@@ -36,4 +36,4 @@ If you think its Task version is outdated, open an issue to let us know.
|
||||
[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/
|
||||
[snapcraftdashboard]: https://snapcraft.io/task/releases
|
||||
|
||||
211
docs/styleguide.md
Normal file
211
docs/styleguide.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# Styleguide
|
||||
|
||||
This is the official Task styleguide for `Taskfile.yml` files. This guide
|
||||
contains some basic instructions to keep your Taskfile clean and familiar to
|
||||
other users.
|
||||
|
||||
This contains general guidelines, but don't necessarely need to be strictly
|
||||
followed. Feel free to disagree and proceed differently in some point if you
|
||||
need or want to. Also, feel free to open issues or pull requests with
|
||||
improvements to this guide.
|
||||
|
||||
## Use `Taskfile.yml` and not `taskfile.yml`
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
taskfile.yml
|
||||
|
||||
|
||||
# good
|
||||
Taskfile.yml
|
||||
```
|
||||
|
||||
This is important especially for Linux users. Windows and macOS have case
|
||||
insensitive filesystems, so `taskfile.yml` will end up working, even that not
|
||||
officially supported. On Linux, only `Taskfile.yml` will work, though.
|
||||
|
||||
## Use the correct order of keywords
|
||||
|
||||
- `version:`
|
||||
- `includes:`
|
||||
- Configuration ones, like `output:` or `silent:`
|
||||
- `vars:`
|
||||
- `env:`
|
||||
- `tasks:`
|
||||
|
||||
## Use 2 spaces for indentation
|
||||
|
||||
This is the most common convention for YAML files, and Task follows it.
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
tasks:
|
||||
foo:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
|
||||
|
||||
# good
|
||||
tasks:
|
||||
foo:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
```
|
||||
|
||||
## Separate with spaces the mains sections
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: '3'
|
||||
includes:
|
||||
docker: ./docker/Taskfile.yml
|
||||
output: prefixed
|
||||
vars:
|
||||
FOO: bar
|
||||
env:
|
||||
BAR: baz
|
||||
tasks:
|
||||
# ...
|
||||
|
||||
|
||||
# good
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
docker: ./docker/Taskfile.yml
|
||||
|
||||
output: prefixed
|
||||
|
||||
vars:
|
||||
FOO: bar
|
||||
|
||||
env:
|
||||
BAR: baz
|
||||
|
||||
tasks:
|
||||
# ...
|
||||
```
|
||||
|
||||
## Add spaces between tasks
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
bar:
|
||||
cmds:
|
||||
- echo 'bar'
|
||||
baz:
|
||||
cmds:
|
||||
- echo 'baz'
|
||||
|
||||
|
||||
# good
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
|
||||
bar:
|
||||
cmds:
|
||||
- echo 'bar'
|
||||
|
||||
baz:
|
||||
cmds:
|
||||
- echo 'baz'
|
||||
```
|
||||
|
||||
## Use upper-case variable names
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
binary_name: myapp
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build -o {{.binary_name}} .
|
||||
|
||||
|
||||
# good
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
BINARY_NAME: myapp
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build -o {{.BINARY_NAME}} .
|
||||
```
|
||||
|
||||
## Don't wrap vars in spaces when templating
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo '{{ .MESSAGE }}'
|
||||
|
||||
|
||||
# good
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo '{{.MESSAGE}}'
|
||||
```
|
||||
|
||||
This convention is also used by most people for any Go templating.
|
||||
|
||||
## Separate task name words with a dash
|
||||
|
||||
```yaml
|
||||
# bad
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
do_something_fancy:
|
||||
cmds:
|
||||
- echo 'Do something'
|
||||
|
||||
|
||||
# good
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
do-something-fancy:
|
||||
cmds:
|
||||
- echo 'Do something'
|
||||
```
|
||||
|
||||
## Use colon for task namespacing
|
||||
|
||||
```yaml
|
||||
# good
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
docker:build:
|
||||
cmds:
|
||||
- docker ...
|
||||
|
||||
docker:run:
|
||||
cmds:
|
||||
- docker-compose ...
|
||||
```
|
||||
|
||||
This is also done automatically when using included Taskfiles.
|
||||
@@ -9,12 +9,14 @@ 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
|
||||
`2.0.0` is accepted. If you choose to use `2.0` Task will not enable future
|
||||
`2.1` features, but if you choose to use `2`, then any `2.x.x` features will be
|
||||
available, but not `3.0.0+`.
|
||||
|
||||
## Version 1
|
||||
|
||||
> NOTE: Taskfiles in version 1 are not supported on Task >= v3.0.0 anymore.
|
||||
|
||||
In the first version of the `Taskfile`, the `version:` key was not available,
|
||||
because the tasks was in the root of the YAML document. Like this:
|
||||
|
||||
@@ -33,7 +35,7 @@ The variable priority order was also different:
|
||||
|
||||
## Version 2.0
|
||||
|
||||
At version 2, we introduced the `version:` key, to allow us to envolve Task
|
||||
At version 2, we introduced the `version:` key, to allow us to evolve Task
|
||||
with new features without breaking existing Taskfiles. The new syntax is as
|
||||
follows:
|
||||
|
||||
@@ -108,7 +110,7 @@ tasks:
|
||||
prefix: server
|
||||
```
|
||||
|
||||
From this version it's not also possible to ignore errors of a command or task
|
||||
From this version it's also possible to ignore errors of a command or task
|
||||
(check documentation [here][ignore_errors]):
|
||||
|
||||
```yaml
|
||||
@@ -161,3 +163,63 @@ Please check the [documentation][includes]
|
||||
[output]: usage.md#output-syntax
|
||||
[ignore_errors]: usage.md#ignore-errors
|
||||
[includes]: usage.md#including-other-taskfiles
|
||||
|
||||
## Version 3
|
||||
|
||||
These are some major changes done on `v3`:
|
||||
|
||||
- Task's output will now be colored
|
||||
- Added support for `.env` like files
|
||||
- Added `label:` setting to task so one can override how the task name
|
||||
appear in the logs
|
||||
- A global `method:` was added to allow setting the default method,
|
||||
and Task's default changed to `checksum`
|
||||
- Two magic variables were added when using `status:`: `CHECKSUM` and
|
||||
`TIMESTAMP` which contains, respectively, the md5 checksum and greatest
|
||||
modification timestamp of the files listed on `sources:`
|
||||
- Also, the `TASK` variable is always available with the current task name
|
||||
- CLI variables are always treated as global variables
|
||||
- Added `dir:` option to `includes` to allow choosing on which directory an
|
||||
included Taskfile will run:
|
||||
|
||||
```yaml
|
||||
includes:
|
||||
docs:
|
||||
taskfile: ./docs
|
||||
dir: ./docs
|
||||
```
|
||||
|
||||
- Implemented short task syntax. All below syntaxes are equivalent:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
print:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
```
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
print:
|
||||
- echo "Hello, World!"
|
||||
```
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
print: echo "Hello, World!"
|
||||
```
|
||||
|
||||
- There was a major refactor on how variables are handled. They're now easier
|
||||
to understand. The `expansions:` setting was removed as it became unncessary.
|
||||
This is the order in which Task will process variables, each level can see
|
||||
the variables set by the previous one and override those.
|
||||
- Environment variables
|
||||
- Global + CLI variables
|
||||
- Call variables
|
||||
- Task variables
|
||||
|
||||
488
docs/usage.md
488
docs/usage.md
@@ -8,7 +8,7 @@ The example below allows compiling a Go app and uses [Minify][minify] to concat
|
||||
and minify multiple CSS files into a single one.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
@@ -33,12 +33,14 @@ executable called must be available by the OS or in PATH.
|
||||
|
||||
If you omit a task name, "default" will be assumed.
|
||||
|
||||
## Environment
|
||||
## Environment variables
|
||||
|
||||
### Task
|
||||
|
||||
You can use `env` to set custom environment variables for a specific task:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
@@ -52,7 +54,7 @@ Additionally, you can set globally environment variables, that'll be available
|
||||
to all tasks:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
env:
|
||||
GREETING: Hey, there!
|
||||
@@ -63,56 +65,47 @@ tasks:
|
||||
- echo $GREETING
|
||||
```
|
||||
|
||||
> NOTE: `env` supports expansion and and retrieving output from a shell command
|
||||
> NOTE: `env` supports expansion and retrieving output from a shell command
|
||||
> just like variables, as you can see on the [Variables](#variables) section.
|
||||
|
||||
## Operating System specific tasks
|
||||
### .env files
|
||||
|
||||
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
|
||||
based on the operating system.
|
||||
You can also ask Task to include `.env` like files by using the `dotenv:`
|
||||
setting:
|
||||
|
||||
Example:
|
||||
|
||||
Taskfile.yml:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- echo "default"
|
||||
```
|
||||
# .env
|
||||
KEYNAME=VALUE
|
||||
```
|
||||
|
||||
Taskfile_linux.yml:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- echo "linux"
|
||||
```
|
||||
# testing/.env
|
||||
ENDPOINT=testing.com
|
||||
```
|
||||
|
||||
Will print out `linux` and not `default`.
|
||||
```yaml
|
||||
# Taskfile.yml
|
||||
|
||||
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.
|
||||
version: '3'
|
||||
|
||||
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.
|
||||
env:
|
||||
ENV: testing
|
||||
|
||||
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||
```
|
||||
|
||||
## 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'
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
docs: ./documentation # will look for ./documentation/Taskfile.yml
|
||||
@@ -124,6 +117,36 @@ 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.
|
||||
|
||||
### OS-specific Taskfiles
|
||||
|
||||
With `version: '2'`, task automatically includes any `Taskfile_{{OS}}.yml`
|
||||
if it exists (for example: `Taskfile_windows.yml`, `Taskfile_linux.yml` or
|
||||
`Taskfile_darwin.yml`). Since this behavior was a bit too implicit, it
|
||||
was removed on version 3, but you still can have a similar behavior by
|
||||
explicitly importing these files:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
build: ./Taskfile_{{OS}}.yml
|
||||
```
|
||||
|
||||
### Directory of included Taskfile
|
||||
|
||||
By default, included Taskfile's tasks are ran in the current directory, even
|
||||
if the Taskfile is in another directory, but you can force its tasks to run
|
||||
in another directory by using this alternative syntax:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
docs:
|
||||
taskfile: ./docs/Taskfile.yml
|
||||
dir: ./docs
|
||||
```
|
||||
|
||||
> The included Taskfiles must be using the same schema version the main
|
||||
> Taskfile uses.
|
||||
|
||||
@@ -131,6 +154,25 @@ from the `DockerTasks.yml` file.
|
||||
> This was a deliberate decision to keep use and implementation simple.
|
||||
> If you disagree, open an GitHub issue and explain your use case. =)
|
||||
|
||||
### Optional includes
|
||||
|
||||
Includes marked as optional will allow Task to continue execution as normal if
|
||||
the included file is missing.
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
tests:
|
||||
taskfile: ./tests/Taskfile.yml
|
||||
optional: true
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo "This command can still be successfully executed if ./tests/Taskfile.yml does not exist"
|
||||
```
|
||||
|
||||
## Task directory
|
||||
|
||||
By default, tasks will be executed in the directory where the Taskfile is
|
||||
@@ -138,7 +180,7 @@ located. But you can easily make the task run in another folder informing
|
||||
`dir`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
serve:
|
||||
@@ -152,11 +194,15 @@ If the directory doesn't exist, `task` creates it.
|
||||
|
||||
## Task dependencies
|
||||
|
||||
> Dependencies run in parallel, so dependencies of a task shouldn't depend one
|
||||
> another. If you want to force tasks to run serially take a look at the
|
||||
> [Calling Another Task](#calling-another-task) section below.
|
||||
|
||||
You may have tasks that depend on others. Just pointing them on `deps` will
|
||||
make them run automatically before running the parent task:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
@@ -175,7 +221,7 @@ In the above example, `assets` will always run right before `build` if you run
|
||||
A task can have only dependencies and no commands to group tasks together:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
assets:
|
||||
@@ -193,11 +239,14 @@ tasks:
|
||||
If there is more than one dependency, they always run in parallel for better
|
||||
performance.
|
||||
|
||||
> You can also make the tasks given by the command line run in parallel by
|
||||
> using the `--parallel` flag (alias `-p`). Example: `task --parallel js css`.
|
||||
|
||||
If you want to pass information to dependencies, you can do that the same
|
||||
manner as you would to [call another task](#calling-another-task):
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
@@ -221,7 +270,7 @@ often result in a faster build pipeline. But in some situations you may need
|
||||
to call other tasks serially. In this case, just use the following syntax:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
main-task:
|
||||
@@ -243,19 +292,19 @@ Overriding variables in the called task is as simple as informing `vars`
|
||||
attribute:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
main-task:
|
||||
greet:
|
||||
vars:
|
||||
RECIPIENT: '{{default "World" .RECIPIENT}}'
|
||||
cmds:
|
||||
- task: write-file
|
||||
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
|
||||
- task: write-file
|
||||
vars: {FILE: "world.txt", CONTENT: "World!"}
|
||||
- echo "Hello, {{.RECIPIENT}}!"
|
||||
|
||||
write-file:
|
||||
greet-pessimistically:
|
||||
cmds:
|
||||
- echo "{{.CONTENT}}" > {{.FILE}}
|
||||
- task: greet
|
||||
vars: {RECIPIENT: "Cruel World"}
|
||||
```
|
||||
|
||||
The above syntax is also supported in `deps`.
|
||||
@@ -266,11 +315,13 @@ The above syntax is also supported in `deps`.
|
||||
|
||||
## Prevent unnecessary work
|
||||
|
||||
### By fingerprinting locally generated files and their sources
|
||||
|
||||
If a task generates something, you can inform Task the source and generated
|
||||
files, so Task will prevent to run them if not necessary.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
@@ -295,18 +346,18 @@ tasks:
|
||||
- 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
|
||||
`sources` and `generates` can be files or file patterns. When given,
|
||||
Task will compare the checksum of the source files to determine if it's
|
||||
necessary to run the task. If not, it will just print a message like
|
||||
`Task "js" is up to date`.
|
||||
|
||||
If you prefer this check to be made by the content of the files, instead of
|
||||
its timestamp, just set the `method` property to `checksum`.
|
||||
You will probably want to ignore the `.task` folder in your `.gitignore` file
|
||||
(It's there that Task stores the last checksum).
|
||||
|
||||
If you prefer this check to be made by the modification timestamp of the files,
|
||||
instead of its checksum (content), just set the `method` property to `timestamp`.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
@@ -321,11 +372,17 @@ tasks:
|
||||
|
||||
> TIP: method `none` skips any validation and always run the task.
|
||||
|
||||
> NOTE: for the `checksum` (default) method to work, it's only necessary to
|
||||
> inform the source files, but if you want to use the `timestamp` method, you
|
||||
> also need to inform the generated files with `generates`.
|
||||
|
||||
### Using programmatic checks to indicate a task is up to date.
|
||||
|
||||
Alternatively, you can inform a sequence of tests as `status`. If no error
|
||||
is returned (exit status 0), the task is considered up-to-date:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
generate-files:
|
||||
@@ -340,18 +397,37 @@ tasks:
|
||||
- test -f directory/file2.txt
|
||||
```
|
||||
|
||||
Normally, you would use `sources` in combination with
|
||||
`generates` - but for tasks that generate remote artifacts (Docker images,
|
||||
deploys, CD releases) the checksum source and timestamps require either
|
||||
access to the artifact or for an out-of-band refresh of the `.checksum`
|
||||
fingerprint file.
|
||||
|
||||
Two special variables `{{.CHECKSUM}}` and `{{.TIMESTAMP}}` are available
|
||||
for interpolation within `status` commands, depending on the method assigned
|
||||
to fingerprint the sources. Only `source` globs are fingerprinted.
|
||||
|
||||
Note that the `{{.TIMESTAMP}}` variable is a "live" Go `time.Time` struct, and
|
||||
can be formatted using any of the methods that `time.Time` responds to.
|
||||
|
||||
See [the Go Time documentation](https://golang.org/pkg/time/) for more information.
|
||||
|
||||
You can use `--force` or `-f` if you want to force a task to run even when
|
||||
up-to-date.
|
||||
|
||||
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
||||
the tasks are not up-to-date.
|
||||
|
||||
If you need a certain set of conditions to be _true_ you can use the
|
||||
`preconditions` stanza. `preconditions` are very similar to `status`
|
||||
lines except they support `sh` expansion and they SHOULD all return 0.
|
||||
### Using programmatic checks to cancel execution of an task and it's dependencies
|
||||
|
||||
In addition to `status` checks, there are also `preconditions` checks, which are
|
||||
the logical inverse of `status` checks. That is, if you need a certain set of
|
||||
conditions to be _true_ you can use the `preconditions` stanza.
|
||||
`preconditions` are similar to `status` lines except they support `sh`
|
||||
expansion and they SHOULD all return 0.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
generate-files:
|
||||
@@ -379,32 +455,73 @@ executing tasks that depend on it, a `precondition` will fail a task, along
|
||||
with any other tasks that depend on it.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
task_will_fail:
|
||||
task-will-fail:
|
||||
preconditions:
|
||||
- sh: "exit 1"
|
||||
|
||||
task_will_also_fail:
|
||||
task-will-also-fail:
|
||||
deps:
|
||||
- task_will_fail
|
||||
- task-will-fail
|
||||
|
||||
task_will_still_fail:
|
||||
task-will-still-fail:
|
||||
cmds:
|
||||
- task: task_will_fail
|
||||
- task: task-will-fail
|
||||
- echo "I will not run"
|
||||
```
|
||||
|
||||
### Limiting when tasks run
|
||||
|
||||
If a task executed by multiple `cmds` or multiple `deps` you can control
|
||||
when it is executed using `run`. `run` can also be set at the root
|
||||
of the Taskfile to change the behavior of all the tasks unless explicitly
|
||||
overridden.
|
||||
|
||||
Supported values for `run`:
|
||||
|
||||
* `always` (default) always attempt to invoke the task regardless of the
|
||||
number of previous executions
|
||||
* `once` only invoke this task once regardless of the number of references
|
||||
* `when_changed` only invokes the task once for each unique set of variables
|
||||
passed into the task
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: generate-file
|
||||
vars: { CONTENT: '1' }
|
||||
- task: generate-file
|
||||
vars: { CONTENT: '2' }
|
||||
- task: generate-file
|
||||
vars: { CONTENT: '2' }
|
||||
|
||||
generate-file:
|
||||
run: when_changed
|
||||
deps:
|
||||
- install-deps
|
||||
cmds:
|
||||
- echo {{.CONTENT}}
|
||||
|
||||
install-deps:
|
||||
run: once
|
||||
cmds:
|
||||
- sleep 5 # long operation like installing packages
|
||||
```
|
||||
|
||||
## 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 in the task definition
|
||||
- Variables given while calling a task from another.
|
||||
- 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
|
||||
- Global variables (those declared in the `vars:` option in the Taskfile)
|
||||
- Environment variables
|
||||
|
||||
Example of sending parameters with environment variables:
|
||||
@@ -413,25 +530,20 @@ Example of sending parameters with environment variables:
|
||||
$ TASK_VARIABLE=a-value task do-something
|
||||
```
|
||||
|
||||
> TIP: A special variable `.TASK` is always available containing the task name.
|
||||
|
||||
Since some shells don't support above syntax to set environment variables
|
||||
(Windows) tasks also accepts a similar style when not in the beginning of
|
||||
the command. Variables given in this form are only visible to the task called
|
||||
right before.
|
||||
the command.
|
||||
|
||||
```bash
|
||||
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
||||
```
|
||||
|
||||
If you want to set global variables using this syntax, give it before any task:
|
||||
|
||||
```bash
|
||||
$ task OUTPUT=file.txt generate-file
|
||||
```
|
||||
|
||||
Example of locally declared vars:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
print-var:
|
||||
@@ -444,7 +556,7 @@ tasks:
|
||||
Example of global vars in a `Taskfile.yml`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
GREETING: Hello from Taskfile!
|
||||
@@ -455,38 +567,6 @@ tasks:
|
||||
- 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.
|
||||
@@ -494,7 +574,7 @@ The value will be treated as a command and the output assigned. If there is one
|
||||
or more trailing newlines, the last newline will be trimmed.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
@@ -507,16 +587,76 @@ tasks:
|
||||
|
||||
This works for all types of variables.
|
||||
|
||||
## Forwarding CLI arguments to commands
|
||||
|
||||
If `--` is given in the CLI, all following parameters are added to a
|
||||
special `.CLI_ARGS` variable. This is useful to forward arguments to another
|
||||
command.
|
||||
|
||||
The below example will run `yarn install`.
|
||||
|
||||
```bash
|
||||
$ task yarn -- install
|
||||
```
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
yarn:
|
||||
cmds:
|
||||
- yarn {{.CLI_ARGS}}
|
||||
```
|
||||
|
||||
## Doing task cleanup with `defer`
|
||||
|
||||
With the `defer` keyword, it's possible to schedule cleanup to be run once
|
||||
the task finishes. The difference with just putting it as the last command is
|
||||
that this command will run even when the task fails.
|
||||
|
||||
In the example below `rm -rf tmpdir/` will run even if the third command fails:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- mkdir -p tmpdir/
|
||||
- defer: rm -rf tmpdir/
|
||||
- echo 'Do work on tmpdir/'
|
||||
```
|
||||
|
||||
If you want to move the cleanup command into another task, that's possible as
|
||||
well:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- mkdir -p tmpdir/
|
||||
- defer: { task: cleanup }
|
||||
- echo 'Do work on tmpdir/'
|
||||
|
||||
cleanup: rm -rf tmpdir/
|
||||
```
|
||||
|
||||
> NOTE: Due to the nature of how the
|
||||
[Go's own `defer` work](https://go.dev/tour/flowcontrol/13), the deferred
|
||||
commands are executed in the reverse order if you schedule multiple of them.
|
||||
|
||||
## 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/)
|
||||
All functions by the Go's [slim-sprig lib](https://go-task.github.io/slim-sprig/)
|
||||
are available. The following example gets the current date in a given format:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
print-date:
|
||||
@@ -535,14 +675,17 @@ Task also adds the following functions:
|
||||
- `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 `/`.
|
||||
converts a string from `/` path format to `\`.
|
||||
- `exeExt`: Returns the right executable extension for the current OS
|
||||
(`".exe"` for Windows, `""` for others).
|
||||
- `shellQuote`: Quotes a string to make it safe for use in shell scripts.
|
||||
Task uses [this Go function](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote)
|
||||
for this. The Bash dialect is assumed.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
print-os:
|
||||
@@ -570,7 +713,7 @@ Running `task --list` (or `task -l`) lists all tasks with a description.
|
||||
The following Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
@@ -599,13 +742,15 @@ would print the following output:
|
||||
* test: Run all the go tests.
|
||||
```
|
||||
|
||||
If you want to see all tasks, there's a `--list-all` (alias `-a`) flag as well.
|
||||
|
||||
## Display summary of task
|
||||
|
||||
Running `task --summary task-name` will show a summary of a task
|
||||
Running `task --summary task-name` will show a summary of a task.
|
||||
The following Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
release:
|
||||
@@ -613,7 +758,7 @@ tasks:
|
||||
summary: |
|
||||
Release your project to github
|
||||
|
||||
It will build your project before starting the release it.
|
||||
It will build your project before starting the release.
|
||||
Please make sure that you have set GITHUB_TOKEN before starting.
|
||||
cmds:
|
||||
- your-release-tool
|
||||
@@ -630,7 +775,7 @@ task: release
|
||||
|
||||
Release your project to github
|
||||
|
||||
It will build your project before starting the release it.
|
||||
It will build your project before starting the release.
|
||||
Please make sure that you have set GITHUB_TOKEN before starting.
|
||||
|
||||
dependencies:
|
||||
@@ -644,13 +789,37 @@ If the task does not have a summary or a description, a warning is printed.
|
||||
|
||||
Please note: *showing the summary will not execute the command*.
|
||||
|
||||
## Overriding task name
|
||||
|
||||
Sometimes you may want to override the task name print on summary, up-to-date
|
||||
messages to STDOUT, etc. In this case you can just set `label:`, which can also
|
||||
be interpolated with variables:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
- task: print
|
||||
vars:
|
||||
MESSAGE: hello
|
||||
- task: print
|
||||
vars:
|
||||
MESSAGE: world
|
||||
|
||||
print:
|
||||
label: 'print-{{.MESSAGE}}'
|
||||
cmds:
|
||||
- echo "{{.MESSAGE}}"
|
||||
```
|
||||
|
||||
## Silent mode
|
||||
|
||||
Silent mode disables echoing of commands before Task runs it.
|
||||
For the following Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
@@ -671,12 +840,12 @@ With silent mode on, the below will be print instead:
|
||||
Print something
|
||||
```
|
||||
|
||||
There's three ways to enable silent mode:
|
||||
There are four ways to enable silent mode:
|
||||
|
||||
* At command level:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
@@ -688,7 +857,7 @@ tasks:
|
||||
* At task level:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
@@ -697,12 +866,25 @@ tasks:
|
||||
silent: true
|
||||
```
|
||||
|
||||
* Globally at Taskfile level:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
silent: true
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Print something"
|
||||
```
|
||||
|
||||
* Or globally with `--silent` or `-s` flag
|
||||
|
||||
If you want to suppress STDOUT instead, just redirect a command to `/dev/null`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
@@ -721,7 +903,7 @@ You have the option to ignore errors during command execution.
|
||||
Given the following Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
@@ -734,7 +916,7 @@ Task will abort the execution after running `exit 1` because the status code `1`
|
||||
However it is possible to continue with execution using `ignore_error`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
@@ -765,7 +947,7 @@ options you can choose:
|
||||
To choose another one, just set it to root in the Taskfile:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
output: 'group'
|
||||
|
||||
@@ -782,7 +964,7 @@ tasks:
|
||||
with the `prefix:` attribute:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
output: prefixed
|
||||
|
||||
@@ -812,11 +994,49 @@ $ task default
|
||||
|
||||
> The `output` option can also be specified by the `--output` or `-o` flags.
|
||||
|
||||
## Interactive CLI application
|
||||
|
||||
When running interactive CLI applications inside Task they can sometimes behave
|
||||
weirdly, specially when the [output mode](#output-syntax) is set to something
|
||||
other than `interleaved` (the default), or when interactive apps are ran in
|
||||
parallel with other tasks.
|
||||
|
||||
The `interactive: true` tells Task this is an interactive application, and Task
|
||||
will try to optimize for it:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
cmds:
|
||||
- vim my-file.txt
|
||||
interactive: true
|
||||
```
|
||||
|
||||
If you still have problem running an interactive app through Task, please open
|
||||
an issue about it.
|
||||
|
||||
## Short task syntax
|
||||
|
||||
Starting on Task v3, you can now write tasks with a shorter syntax if they
|
||||
have the default settings (e.g. no custom `env:`, `vars:`, `desc:`, `silent:` , etc):
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build: go build -v -o ./app{{exeExt}} .
|
||||
|
||||
run:
|
||||
- task: build
|
||||
- ./app{{exeExt}} -h localhost -p 8080
|
||||
```
|
||||
|
||||
## Watch tasks
|
||||
|
||||
If you give a `--watch` or `-w` argument, task will watch for file changes
|
||||
With the flags `--watch` or `-w` 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.
|
||||
so task knows which files to watch.
|
||||
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify
|
||||
|
||||
29
go.mod
29
go.mod
@@ -1,20 +1,17 @@
|
||||
module github.com/go-task/task/v2
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.4.2 // indirect
|
||||
github.com/Masterminds/sprig v2.16.0+incompatible
|
||||
github.com/aokoli/goutils v1.0.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/mattn/go-zglob v0.0.1
|
||||
github.com/radovskyb/watcher v1.0.5
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/stretchr/testify v1.3.0
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
mvdan.cc/sh/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/mattn/go-zglob v0.0.3
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/radovskyb/watcher v1.0.7
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
mvdan.cc/sh/v3 v3.4.2
|
||||
)
|
||||
|
||||
go 1.13
|
||||
go 1.16
|
||||
|
||||
98
go.sum
98
go.sum
@@ -1,52 +1,72 @@
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY=
|
||||
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
|
||||
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
|
||||
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/huandu/xstrings v1.1.0 h1:9oZY6Z/H3A1gytJxzuicbmV5QoR8M1TAPcn9WTg7vqg=
|
||||
github.com/huandu/xstrings v1.1.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/frankban/quicktest v1.13.1 h1:xVm/f9seEhZFL9+n5kv5XLrGwy6elc4V9v/XFY2vmd8=
|
||||
github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To=
|
||||
github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/radovskyb/watcher v1.0.5 h1:wqt7gb+HjGacvFoLTKeT44C+XVPxu7bvHvKT1IvZ7rw=
|
||||
github.com/radovskyb/watcher v1.0.5/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
|
||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 h1:QOQNt6vCjMpXE7JSK5VvAzJC1byuN3FgTNSBwf+CJgI=
|
||||
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI=
|
||||
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
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/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b h1:kzTXBacNrjp7n8ncNC894X9NUeL8yeKVS5MS0bigJwE=
|
||||
mvdan.cc/sh/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b/go.mod h1:6Cd5lQRZZIHeKAdYoLvhhse0oDOIrS5gBoGacWnuiUE=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
|
||||
mvdan.cc/sh/v3 v3.4.2 h1:d3TKODXfZ1bjWU/StENN+GDg5xOzNu5+C8AEu405E5U=
|
||||
mvdan.cc/sh/v3 v3.4.2/go.mod h1:p/tqPPI4Epfk2rICAe2RoaNd8HBSJ8t9Y2DA9yQlbzY=
|
||||
|
||||
28
hash.go
Normal file
28
hash.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-task/task/v3/internal/hash"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func (e *Executor) GetHash(t *taskfile.Task) (string, error) {
|
||||
r := t.Run
|
||||
if r == "" {
|
||||
r = e.Taskfile.Run
|
||||
}
|
||||
|
||||
var h hash.HashFunc
|
||||
switch r {
|
||||
case "always":
|
||||
h = hash.Empty
|
||||
case "once":
|
||||
h = hash.Name
|
||||
case "when_changed":
|
||||
h = hash.Hash
|
||||
default:
|
||||
return "", fmt.Errorf(`task: invalid run "%s"`, r)
|
||||
}
|
||||
return h(t)
|
||||
}
|
||||
49
help.go
49
help.go
@@ -5,30 +5,65 @@ import (
|
||||
"sort"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// PrintTasksHelp prints help os tasks that have a description
|
||||
func (e *Executor) PrintTasksHelp() {
|
||||
tasks := e.tasksWithDesc()
|
||||
// ListTasksWithDesc reports tasks that have a description spec.
|
||||
func (e *Executor) ListTasksWithDesc() {
|
||||
e.printTasks(false)
|
||||
return
|
||||
}
|
||||
|
||||
// ListAllTasks reports all tasks, with or without a description spec.
|
||||
func (e *Executor) ListAllTasks() {
|
||||
e.printTasks(true)
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Executor) printTasks(listAll bool) {
|
||||
var tasks []*taskfile.Task
|
||||
if listAll {
|
||||
tasks = e.allTaskNames()
|
||||
} else {
|
||||
tasks = e.tasksWithDesc()
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
e.Logger.Outf("task: No tasks with description available")
|
||||
if listAll {
|
||||
e.Logger.Outf(logger.Yellow, "task: No tasks available")
|
||||
} else {
|
||||
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks")
|
||||
}
|
||||
return
|
||||
}
|
||||
e.Logger.Outf("task: Available tasks for this project:")
|
||||
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
|
||||
for _, task := range tasks {
|
||||
fmt.Fprintf(w, "* %s: \t%s\n", task.Task, task.Desc)
|
||||
fmt.Fprintf(w, "* %s: \t%s\n", task.Name(), task.Desc)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func (e *Executor) allTaskNames() (tasks []*taskfile.Task) {
|
||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
|
||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.Desc != "" {
|
||||
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
|
||||
if err == nil {
|
||||
task = compiledTask
|
||||
}
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
}
|
||||
|
||||
9
init.go
9
init.go
@@ -3,14 +3,13 @@ package task
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const defaultTaskfile = `# https://taskfile.dev
|
||||
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
@@ -24,15 +23,15 @@ tasks:
|
||||
|
||||
// InitTaskfile Taskfile creates a new Taskfile
|
||||
func InitTaskfile(w io.Writer, dir string) error {
|
||||
f := filepath.Join(dir, "Taskfile.yml")
|
||||
f := filepath.Join(dir, "Taskfile.yaml")
|
||||
|
||||
if _, err := os.Stat(f); err == nil {
|
||||
return ErrTaskfileAlreadyExists
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
|
||||
if err := os.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(w, "Taskfile.yml created in the current directory\n")
|
||||
fmt.Fprintf(w, "Taskfile.yaml created in the current directory\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT.
|
||||
# Code generated by godownloader on 2021-01-12T13:40:40Z. DO NOT EDIT.
|
||||
#
|
||||
|
||||
usage() {
|
||||
@@ -27,11 +27,12 @@ parse_args() {
|
||||
# over-ridden by flag below
|
||||
|
||||
BINDIR=${BINDIR:-./bin}
|
||||
while getopts "b:dh?" arg; do
|
||||
while getopts "b:dh?x" arg; do
|
||||
case "$arg" in
|
||||
b) BINDIR="$OPTARG" ;;
|
||||
d) log_set_priority 10 ;;
|
||||
h | \?) usage "$0" ;;
|
||||
x) set -x ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
@@ -42,46 +43,41 @@ parse_args() {
|
||||
# network, either nothing will happen or will syntax error
|
||||
# out preventing half-done work
|
||||
execute() {
|
||||
tmpdir=$(mktmpdir)
|
||||
tmpdir=$(mktemp -d)
|
||||
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
|
||||
test ! -d "${BINDIR}" && install -d "${BINDIR}"
|
||||
for binexe in $BINARIES; do
|
||||
if [ "$OS" = "windows" ]; then
|
||||
binexe="${binexe}.exe"
|
||||
fi
|
||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
||||
log_info "installed ${BINDIR}/${binexe}"
|
||||
done
|
||||
rm -rf "${tmpdir}"
|
||||
}
|
||||
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 ;;
|
||||
get_binaries() {
|
||||
case "$PLATFORM" in
|
||||
darwin/amd64) BINARIES="task" ;;
|
||||
darwin/arm64) BINARIES="task" ;;
|
||||
darwin/armv6) BINARIES="task" ;;
|
||||
linux/386) BINARIES="task" ;;
|
||||
linux/amd64) BINARIES="task" ;;
|
||||
linux/arm64) BINARIES="task" ;;
|
||||
linux/armv6) BINARIES="task" ;;
|
||||
windows/386) BINARIES="task" ;;
|
||||
windows/amd64) BINARIES="task" ;;
|
||||
windows/arm64) BINARIES="task" ;;
|
||||
windows/armv6) BINARIES="task" ;;
|
||||
*)
|
||||
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
|
||||
;;
|
||||
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
|
||||
@@ -99,8 +95,8 @@ tag_to_version() {
|
||||
VERSION=${TAG#v}
|
||||
}
|
||||
adjust_format() {
|
||||
# change format (tar.gz or zip) based on ARCH
|
||||
case ${ARCH} in
|
||||
# change format (tar.gz or zip) based on OS
|
||||
case ${OS} in
|
||||
windows) FORMAT=zip ;;
|
||||
esac
|
||||
true
|
||||
@@ -174,7 +170,9 @@ log_crit() {
|
||||
uname_os() {
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
case "$os" in
|
||||
msys_nt) os="windows" ;;
|
||||
cygwin_nt*) os="windows" ;;
|
||||
mingw*) os="windows" ;;
|
||||
msys_nt*) os="windows" ;;
|
||||
esac
|
||||
echo "$os"
|
||||
}
|
||||
@@ -186,9 +184,9 @@ uname_arch() {
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="arm5" ;;
|
||||
armv6*) arch="arm6" ;;
|
||||
armv7*) arch="arm7" ;;
|
||||
armv5*) arch="armv5" ;;
|
||||
armv6*) arch="armv6" ;;
|
||||
armv7*) arch="armv7" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
@@ -234,8 +232,8 @@ uname_arch_check() {
|
||||
untar() {
|
||||
tarball=$1
|
||||
case "${tarball}" in
|
||||
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
|
||||
*.tar) tar -xf "${tarball}" ;;
|
||||
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
|
||||
*.tar) tar --no-same-owner -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
@@ -243,11 +241,6 @@ untar() {
|
||||
;;
|
||||
esac
|
||||
}
|
||||
mktmpdir() {
|
||||
test -z "$TMPDIR" && TMPDIR="$(mktemp -d)"
|
||||
mkdir -p "${TMPDIR}"
|
||||
echo "${TMPDIR}"
|
||||
}
|
||||
http_download_curl() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
@@ -368,7 +361,7 @@ uname_arch_check "$ARCH"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
check_platform
|
||||
get_binaries
|
||||
|
||||
tag_to_version
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package args
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
)
|
||||
|
||||
// Parse parses command line argument: tasks and vars of each task
|
||||
func Parse(args ...string) ([]taskfile.Call, taskfile.Vars) {
|
||||
var calls []taskfile.Call
|
||||
var globals taskfile.Vars
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.Contains(arg, "=") {
|
||||
calls = append(calls, taskfile.Call{Task: arg})
|
||||
continue
|
||||
}
|
||||
|
||||
if len(calls) < 1 {
|
||||
if globals == nil {
|
||||
globals = taskfile.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)
|
||||
}
|
||||
|
||||
name, value := splitVar((arg))
|
||||
calls[len(calls)-1].Vars[name] = taskfile.Var{Static: value}
|
||||
}
|
||||
}
|
||||
|
||||
return calls, globals
|
||||
}
|
||||
|
||||
func splitVar(s string) (string, string) {
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
return pair[0], pair[1]
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package args_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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
|
||||
ExpectedCalls []taskfile.Call
|
||||
ExpectedGlobals taskfile.Vars
|
||||
}{
|
||||
{
|
||||
Args: []string{"task-a", "task-b", "task-c"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{
|
||||
Task: "task-a",
|
||||
Vars: taskfile.Vars{
|
||||
"FOO": taskfile.Var{Static: "bar"},
|
||||
},
|
||||
},
|
||||
{Task: "task-b"},
|
||||
{
|
||||
Task: "task-c",
|
||||
Vars: taskfile.Vars{
|
||||
"BAR": taskfile.Var{Static: "baz"},
|
||||
"BAZ": taskfile.Var{Static: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||
ExpectedCalls: []taskfile.Call{
|
||||
{
|
||||
Task: "task-a",
|
||||
Vars: taskfile.Vars{
|
||||
"CONTENT": taskfile.Var{Static: "with some spaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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, globals := args.Parse(test.Args...)
|
||||
assert.Equal(t, test.ExpectedCalls, calls)
|
||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v3/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)
|
||||
GetTaskfileVariables() (*taskfile.Vars, error)
|
||||
GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
||||
FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
||||
HandleDynamicVar(v taskfile.Var, dir string) (string, error)
|
||||
ResetCache()
|
||||
}
|
||||
|
||||
@@ -4,21 +4,17 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// GetEnviron the all return all environment variables encapsulated on a
|
||||
// taskfile.Vars
|
||||
func GetEnviron() taskfile.Vars {
|
||||
var (
|
||||
env = os.Environ()
|
||||
m = make(taskfile.Vars, len(env))
|
||||
)
|
||||
|
||||
for _, e := range env {
|
||||
func GetEnviron() *taskfile.Vars {
|
||||
m := &taskfile.Vars{}
|
||||
for _, e := range os.Environ() {
|
||||
keyVal := strings.SplitN(e, "=", 2)
|
||||
key, val := keyVal[0], keyVal[1]
|
||||
m[key] = taskfile.Var{Static: val}
|
||||
m.Set(key, taskfile.Var{Static: val})
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-task/task/v2/internal/compiler"
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v2/internal/templater"
|
||||
)
|
||||
|
||||
var _ compiler.Compiler = &CompilerV1{}
|
||||
|
||||
type CompilerV1 struct {
|
||||
Dir string
|
||||
Vars taskfile.Vars
|
||||
|
||||
Logger *logger.Logger
|
||||
|
||||
dynamicCache map[string]string
|
||||
muDynamicCache sync.Mutex
|
||||
}
|
||||
|
||||
// GetVariables returns fully resolved variables following the priority order:
|
||||
// 1. Call variables (should already have been resolved)
|
||||
// 2. Environment (should not need to be resolved)
|
||||
// 3. Task variables, resolved with access to:
|
||||
// - call, taskvars and environment variables
|
||||
// 4. Taskvars variables, resolved with access to:
|
||||
// - environment variables
|
||||
func (c *CompilerV1) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
|
||||
merge := func(dest taskfile.Vars, srcs ...taskfile.Vars) {
|
||||
for _, src := range srcs {
|
||||
for k, v := range src {
|
||||
dest[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
varsKeys := func(srcs ...taskfile.Vars) []string {
|
||||
m := make(map[string]struct{})
|
||||
for _, src := range srcs {
|
||||
for k := range src {
|
||||
m[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
lst := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
lst = append(lst, k)
|
||||
}
|
||||
return lst
|
||||
}
|
||||
replaceVars := func(dest taskfile.Vars, keys []string) error {
|
||||
r := templater.Templater{Vars: dest}
|
||||
for _, k := range keys {
|
||||
v := dest[k]
|
||||
dest[k] = taskfile.Var{
|
||||
Static: r.Replace(v.Static),
|
||||
Sh: r.Replace(v.Sh),
|
||||
}
|
||||
}
|
||||
return r.Err()
|
||||
}
|
||||
resolveShell := func(dest taskfile.Vars, keys []string) error {
|
||||
for _, k := range keys {
|
||||
v := dest[k]
|
||||
static, err := c.HandleDynamicVar(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest[k] = taskfile.Var{Static: static}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
update := func(dest taskfile.Vars, srcs ...taskfile.Vars) error {
|
||||
merge(dest, srcs...)
|
||||
// updatedKeys ensures template evaluation is run only once.
|
||||
updatedKeys := varsKeys(srcs...)
|
||||
if err := replaceVars(dest, updatedKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
return resolveShell(dest, updatedKeys)
|
||||
}
|
||||
|
||||
// Resolve taskvars variables to "result" with environment override variables.
|
||||
override := compiler.GetEnviron()
|
||||
result := make(taskfile.Vars, len(c.Vars)+len(t.Vars)+len(override))
|
||||
if err := update(result, c.Vars, override); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Resolve task variables to "result" with environment and call override variables.
|
||||
merge(override, call.Vars)
|
||||
if err := update(result, t.Vars, override); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *CompilerV1) HandleDynamicVar(v taskfile.Var) (string, error) {
|
||||
if v.Static != "" || v.Sh == "" {
|
||||
return v.Static, nil
|
||||
}
|
||||
|
||||
c.muDynamicCache.Lock()
|
||||
defer c.muDynamicCache.Unlock()
|
||||
|
||||
if c.dynamicCache == nil {
|
||||
c.dynamicCache = make(map[string]string, 30)
|
||||
}
|
||||
if result, ok := c.dynamicCache[v.Sh]; ok {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
opts := &execext.RunCommandOptions{
|
||||
Command: v.Sh,
|
||||
Dir: c.Dir,
|
||||
Stdout: &stdout,
|
||||
Stderr: c.Logger.Stderr,
|
||||
}
|
||||
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
||||
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
|
||||
}
|
||||
|
||||
// Trim a single trailing newline from the result to make most command
|
||||
// output easier to use in shell commands.
|
||||
result := strings.TrimSuffix(stdout.String(), "\n")
|
||||
|
||||
c.dynamicCache[v.Sh] = result
|
||||
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-task/task/v2/internal/compiler"
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v2/internal/templater"
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
var _ compiler.Compiler = &CompilerV2{}
|
||||
@@ -19,8 +19,8 @@ var _ compiler.Compiler = &CompilerV2{}
|
||||
type CompilerV2 struct {
|
||||
Dir string
|
||||
|
||||
Taskvars taskfile.Vars
|
||||
TaskfileVars taskfile.Vars
|
||||
Taskvars *taskfile.Vars
|
||||
TaskfileVars *taskfile.Vars
|
||||
|
||||
Expansions int
|
||||
|
||||
@@ -30,15 +30,29 @@ type CompilerV2 struct {
|
||||
muDynamicCache sync.Mutex
|
||||
}
|
||||
|
||||
func (c *CompilerV2) GetTaskfileVariables() (*taskfile.Vars, error) {
|
||||
return &taskfile.Vars{}, nil
|
||||
}
|
||||
|
||||
// FastGetVariables is a no-op on v2
|
||||
func (c *CompilerV2) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||
return c.GetVariables(t, call)
|
||||
}
|
||||
|
||||
// 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} {
|
||||
func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||
vr := varResolver{
|
||||
c: c,
|
||||
vars: compiler.GetEnviron(),
|
||||
}
|
||||
vr.vars.Set("TASK", taskfile.Var{Static: t.Task})
|
||||
|
||||
for _, vars := range []*taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} {
|
||||
for i := 0; i < c.Expansions; i++ {
|
||||
vr.merge(vars)
|
||||
}
|
||||
@@ -48,31 +62,32 @@ func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfil
|
||||
|
||||
type varResolver struct {
|
||||
c *CompilerV2
|
||||
vars taskfile.Vars
|
||||
vars *taskfile.Vars
|
||||
err error
|
||||
}
|
||||
|
||||
func (vr *varResolver) merge(vars taskfile.Vars) {
|
||||
func (vr *varResolver) merge(vars *taskfile.Vars) {
|
||||
if vr.err != nil {
|
||||
return
|
||||
}
|
||||
tr := templater.Templater{Vars: vr.vars}
|
||||
for k, v := range vars {
|
||||
vars.Range(func(k string, v taskfile.Var) error {
|
||||
v = taskfile.Var{
|
||||
Static: tr.Replace(v.Static),
|
||||
Sh: tr.Replace(v.Sh),
|
||||
}
|
||||
static, err := vr.c.HandleDynamicVar(v)
|
||||
static, err := vr.c.HandleDynamicVar(v, "")
|
||||
if err != nil {
|
||||
vr.err = err
|
||||
return
|
||||
return err
|
||||
}
|
||||
vr.vars[k] = taskfile.Var{Static: static}
|
||||
}
|
||||
vr.vars.Set(k, taskfile.Var{Static: static})
|
||||
return nil
|
||||
})
|
||||
vr.err = tr.Err()
|
||||
}
|
||||
|
||||
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
|
||||
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ string) (string, error) {
|
||||
if v.Static != "" || v.Sh == "" {
|
||||
return v.Static, nil
|
||||
}
|
||||
@@ -90,12 +105,11 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
|
||||
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)
|
||||
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
|
||||
}
|
||||
|
||||
// Trim a single trailing newline from the result to make most command
|
||||
@@ -103,7 +117,15 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
|
||||
result := strings.TrimSuffix(stdout.String(), "\n")
|
||||
|
||||
c.dynamicCache[v.Sh] = result
|
||||
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
||||
c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResetCache clear the dymanic variables cache
|
||||
func (c *CompilerV2) ResetCache() {
|
||||
c.muDynamicCache.Lock()
|
||||
defer c.muDynamicCache.Unlock()
|
||||
|
||||
c.dynamicCache = nil
|
||||
}
|
||||
|
||||
158
internal/compiler/v3/compiler_v3.go
Normal file
158
internal/compiler/v3/compiler_v3.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
var _ compiler.Compiler = &CompilerV3{}
|
||||
|
||||
type CompilerV3 struct {
|
||||
Dir string
|
||||
|
||||
TaskfileEnv *taskfile.Vars
|
||||
TaskfileVars *taskfile.Vars
|
||||
|
||||
Logger *logger.Logger
|
||||
|
||||
dynamicCache map[string]string
|
||||
muDynamicCache sync.Mutex
|
||||
}
|
||||
|
||||
func (c *CompilerV3) GetTaskfileVariables() (*taskfile.Vars, error) {
|
||||
return c.getVariables(nil, nil, true)
|
||||
}
|
||||
|
||||
func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||
return c.getVariables(t, &call, true)
|
||||
}
|
||||
|
||||
func (c *CompilerV3) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||
return c.getVariables(t, &call, false)
|
||||
}
|
||||
|
||||
func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
|
||||
result := compiler.GetEnviron()
|
||||
if t != nil {
|
||||
result.Set("TASK", taskfile.Var{Static: t.Task})
|
||||
}
|
||||
|
||||
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
|
||||
return func(k string, v taskfile.Var) error {
|
||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
||||
|
||||
if !evaluateShVars {
|
||||
result.Set(k, taskfile.Var{Static: tr.Replace(v.Static)})
|
||||
return nil
|
||||
}
|
||||
|
||||
v = taskfile.Var{
|
||||
Static: tr.Replace(v.Static),
|
||||
Sh: tr.Replace(v.Sh),
|
||||
Dir: v.Dir,
|
||||
}
|
||||
if err := tr.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
static, err := c.HandleDynamicVar(v, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Set(k, taskfile.Var{Static: static})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
rangeFunc := getRangeFunc(c.Dir)
|
||||
|
||||
if err := c.TaskfileEnv.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t == nil || call == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if err := call.Vars.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// NOTE(@andreynering): We're manually joining these paths here because
|
||||
// this is the raw task, not the compiled one.
|
||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
||||
dir := tr.Replace(t.Dir)
|
||||
if err := tr.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !filepath.IsAbs(dir) {
|
||||
dir = filepath.Join(c.Dir, dir)
|
||||
}
|
||||
taskRangeFunc := getRangeFunc(dir)
|
||||
|
||||
if err := t.Vars.Range(taskRangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *CompilerV3) HandleDynamicVar(v taskfile.Var, dir string) (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
|
||||
}
|
||||
|
||||
// NOTE(@andreynering): If a var have a specific dir, use this instead
|
||||
if v.Dir != "" {
|
||||
dir = v.Dir
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
opts := &execext.RunCommandOptions{
|
||||
Command: v.Sh,
|
||||
Dir: dir,
|
||||
Stdout: &stdout,
|
||||
Stderr: c.Logger.Stderr,
|
||||
}
|
||||
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
||||
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
|
||||
}
|
||||
|
||||
// Trim a single trailing newline from the result to make most command
|
||||
// output easier to use in shell commands.
|
||||
result := strings.TrimSuffix(stdout.String(), "\n")
|
||||
|
||||
c.dynamicCache[v.Sh] = result
|
||||
c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResetCache clear the dymanic variables cache
|
||||
func (c *CompilerV3) ResetCache() {
|
||||
c.muDynamicCache.Lock()
|
||||
defer c.muDynamicCache.Unlock()
|
||||
|
||||
c.dynamicCache = nil
|
||||
}
|
||||
13
internal/execext/devnull.go
Normal file
13
internal/execext/devnull.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package execext
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var _ io.ReadWriteCloser = devNull{}
|
||||
|
||||
type devNull struct{}
|
||||
|
||||
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
|
||||
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
|
||||
func (devNull) Close() error { return nil }
|
||||
@@ -46,12 +46,11 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
}
|
||||
|
||||
r, err := interp.New(
|
||||
interp.Dir(opts.Dir),
|
||||
interp.Params("-e"),
|
||||
interp.Env(expand.ListEnviron(environ...)),
|
||||
|
||||
interp.WithOpenModules(interp.OpenDevImpls),
|
||||
|
||||
interp.OpenHandler(openHandler),
|
||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||
dirOption(opts.Dir),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -61,12 +60,10 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
|
||||
// IsExitError returns true the given error is an exis status error
|
||||
func IsExitError(err error) bool {
|
||||
switch err.(type) {
|
||||
case interp.ExitStatus, interp.ShellExitStatus:
|
||||
if _, ok := interp.IsExitStatus(err); ok {
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
||||
@@ -83,3 +80,31 @@ func Expand(s string) (string, error) {
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
if path == "/dev/null" {
|
||||
return devNull{}, nil
|
||||
}
|
||||
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
|
||||
}
|
||||
|
||||
func dirOption(path string) interp.RunnerOption {
|
||||
return func(r *interp.Runner) error {
|
||||
err := interp.Dir(path)(r)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the specified directory doesn't exist, it will be created later.
|
||||
// Therefore, even if `interp.Dir` method returns an error, the
|
||||
// directory path should be set only when the directory cannot be found.
|
||||
if absPath, _ := filepath.Abs(path); absPath != "" {
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
r.Dir = absPath
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
24
internal/hash/hash.go
Normal file
24
internal/hash/hash.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package hash
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
type HashFunc func(*taskfile.Task) (string, error)
|
||||
|
||||
func Empty(*taskfile.Task) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func Name(t *taskfile.Task) (string, error) {
|
||||
return t.Task, nil
|
||||
}
|
||||
|
||||
func Hash(t *taskfile.Task) (string, error) {
|
||||
h, err := hashstructure.Hash(t, hashstructure.FormatV2, nil)
|
||||
return fmt.Sprintf("%s:%d", t.Task, h), err
|
||||
}
|
||||
@@ -1,38 +1,65 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
type Color func() PrintFunc
|
||||
type PrintFunc func(io.Writer, string, ...interface{})
|
||||
|
||||
func Default() PrintFunc { return color.New(color.Reset).FprintfFunc() }
|
||||
func Blue() PrintFunc { return color.New(color.FgBlue).FprintfFunc() }
|
||||
func Green() PrintFunc { return color.New(color.FgGreen).FprintfFunc() }
|
||||
func Cyan() PrintFunc { return color.New(color.FgCyan).FprintfFunc() }
|
||||
func Yellow() PrintFunc { return color.New(color.FgYellow).FprintfFunc() }
|
||||
func Magenta() PrintFunc { return color.New(color.FgMagenta).FprintfFunc() }
|
||||
func Red() PrintFunc { return color.New(color.FgRed).FprintfFunc() }
|
||||
|
||||
// Logger is just a wrapper that prints stuff to STDOUT or STDERR,
|
||||
// with optional color.
|
||||
type Logger struct {
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Verbose bool
|
||||
Color bool
|
||||
}
|
||||
|
||||
func (l *Logger) Outf(s string, args ...interface{}) {
|
||||
// Outf prints stuff to STDOUT.
|
||||
func (l *Logger) Outf(color Color, s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
fmt.Fprintf(l.Stdout, s+"\n", args...)
|
||||
if !l.Color {
|
||||
color = Default
|
||||
}
|
||||
print := color()
|
||||
print(l.Stdout, s+"\n", args...)
|
||||
}
|
||||
|
||||
func (l *Logger) VerboseOutf(s string, args ...interface{}) {
|
||||
// VerboseOutf prints stuff to STDOUT if verbose mode is enabled.
|
||||
func (l *Logger) VerboseOutf(color Color, s string, args ...interface{}) {
|
||||
if l.Verbose {
|
||||
l.Outf(s, args...)
|
||||
l.Outf(color, s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Errf(s string, args ...interface{}) {
|
||||
// Errf prints stuff to STDERR.
|
||||
func (l *Logger) Errf(color Color, s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
fmt.Fprintf(l.Stderr, s+"\n", args...)
|
||||
if !l.Color {
|
||||
color = Default
|
||||
}
|
||||
print := color()
|
||||
print(l.Stderr, s+"\n", args...)
|
||||
}
|
||||
|
||||
func (l *Logger) VerboseErrf(s string, args ...interface{}) {
|
||||
// VerboseErrf prints stuff to STDERR if verbose mode is enabled.
|
||||
func (l *Logger) VerboseErrf(color Color, s string, args ...interface{}) {
|
||||
if l.Verbose {
|
||||
l.Errf(s, args...)
|
||||
l.Errf(color, s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2/internal/output"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
)
|
||||
|
||||
func TestInterleaved(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -14,7 +13,8 @@ import (
|
||||
// Checksum validades if a task is up to date by calculating its source
|
||||
// files checksum
|
||||
type Checksum struct {
|
||||
Dir string
|
||||
BaseDir string
|
||||
TaskDir string
|
||||
Task string
|
||||
Sources []string
|
||||
Generates []string
|
||||
@@ -23,12 +23,16 @@ type Checksum struct {
|
||||
|
||||
// IsUpToDate implements the Checker interface
|
||||
func (c *Checksum) IsUpToDate() (bool, error) {
|
||||
if len(c.Sources) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
checksumFile := c.checksumFilePath()
|
||||
|
||||
data, _ := ioutil.ReadFile(checksumFile)
|
||||
data, _ := os.ReadFile(checksumFile)
|
||||
oldMd5 := strings.TrimSpace(string(data))
|
||||
|
||||
sources, err := globs(c.Dir, c.Sources)
|
||||
sources, err := globs(c.TaskDir, c.Sources)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -39,8 +43,8 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
||||
}
|
||||
|
||||
if !c.Dry {
|
||||
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
|
||||
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
||||
_ = os.MkdirAll(filepath.Join(c.BaseDir, ".task", "checksum"), 0755)
|
||||
if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
@@ -48,7 +52,7 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
||||
if len(c.Generates) > 0 {
|
||||
// For each specified 'generates' field, check whether the files actually exist
|
||||
for _, g := range c.Generates {
|
||||
generates, err := glob(c.Dir, g)
|
||||
generates, err := Glob(c.TaskDir, g)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
@@ -84,13 +88,26 @@ func (c *Checksum) checksum(files ...string) (string, error) {
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// Value implements the Checker Interface
|
||||
func (c *Checksum) Value() (interface{}, error) {
|
||||
return c.checksum()
|
||||
}
|
||||
|
||||
// OnError implements the Checker interface
|
||||
func (c *Checksum) OnError() error {
|
||||
if len(c.Sources) == 0 {
|
||||
return nil
|
||||
}
|
||||
return os.Remove(c.checksumFilePath())
|
||||
}
|
||||
|
||||
// Kind implements the Checker Interface
|
||||
func (*Checksum) Kind() string {
|
||||
return "checksum"
|
||||
}
|
||||
|
||||
func (c *Checksum) checksumFilePath() string {
|
||||
return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task))
|
||||
return filepath.Join(c.BaseDir, ".task", "checksum", c.normalizeFilename(c.Task))
|
||||
}
|
||||
|
||||
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
|
||||
|
||||
@@ -5,15 +5,15 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
|
||||
"github.com/mattn/go-zglob"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
)
|
||||
|
||||
func globs(dir string, globs []string) ([]string, error) {
|
||||
files := make([]string, 0)
|
||||
for _, g := range globs {
|
||||
f, err := glob(dir, g)
|
||||
f, err := Glob(dir, g)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func globs(dir string, globs []string) ([]string, error) {
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func glob(dir string, g string) ([]string, error) {
|
||||
func Glob(dir string, g string) ([]string, error) {
|
||||
files := make([]string, 0)
|
||||
if !filepath.IsAbs(g) {
|
||||
g = filepath.Join(dir, g)
|
||||
|
||||
@@ -8,6 +8,15 @@ func (None) IsUpToDate() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Value implements the Checker interface
|
||||
func (None) Value() (interface{}, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (None) Kind() string {
|
||||
return "none"
|
||||
}
|
||||
|
||||
// OnError implements the Checker interface
|
||||
func (None) OnError() error {
|
||||
return nil
|
||||
|
||||
@@ -9,5 +9,7 @@ var (
|
||||
// Checker is an interface that checks if the status is up-to-date
|
||||
type Checker interface {
|
||||
IsUpToDate() (bool, error)
|
||||
Value() (interface{}, error)
|
||||
OnError() error
|
||||
Kind() string
|
||||
}
|
||||
|
||||
@@ -41,6 +41,29 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
|
||||
return !generatesMinTime.Before(sourcesMaxTime), nil
|
||||
}
|
||||
|
||||
func (t *Timestamp) Kind() string {
|
||||
return "timestamp"
|
||||
}
|
||||
|
||||
// Value implements the Checker Interface
|
||||
func (t *Timestamp) Value() (interface{}, error) {
|
||||
sources, err := globs(t.Dir, t.Sources)
|
||||
if err != nil {
|
||||
return time.Now(), err
|
||||
}
|
||||
|
||||
sourcesMaxTime, err := getMaxTime(sources...)
|
||||
if err != nil {
|
||||
return time.Now(), err
|
||||
}
|
||||
|
||||
if sourcesMaxTime.IsZero() {
|
||||
return time.Unix(0, 0), nil
|
||||
}
|
||||
|
||||
return sourcesMaxTime, nil
|
||||
}
|
||||
|
||||
func getMinTime(files ...string) (time.Time, error) {
|
||||
var t time.Time
|
||||
for _, f := range files {
|
||||
|
||||
@@ -3,25 +3,25 @@ package summary
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
|
||||
for i, call := range c {
|
||||
printSpaceBetweenSummaries(l, i)
|
||||
PrintSpaceBetweenSummaries(l, i)
|
||||
PrintTask(l, t.Tasks[call.Task])
|
||||
}
|
||||
}
|
||||
|
||||
func printSpaceBetweenSummaries(l *logger.Logger, i int) {
|
||||
func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
|
||||
spaceRequired := i > 0
|
||||
if !spaceRequired {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf("")
|
||||
l.Outf("")
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
||||
@@ -50,14 +50,14 @@ func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
|
||||
for i, line := range lines {
|
||||
notLastLine := i+1 < len(lines)
|
||||
if notLastLine || line != "" {
|
||||
l.Outf(line)
|
||||
l.Outf(logger.Default, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf("task: %s", t.Task)
|
||||
l.Outf("")
|
||||
l.Outf(logger.Default, "task: %s", t.Name())
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func hasDescription(t *taskfile.Task) bool {
|
||||
@@ -65,11 +65,11 @@ func hasDescription(t *taskfile.Task) bool {
|
||||
}
|
||||
|
||||
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(t.Desc)
|
||||
l.Outf(logger.Default, t.Desc)
|
||||
}
|
||||
|
||||
func printNoDescriptionOrSummary(l *logger.Logger) {
|
||||
l.Outf("(task does not have description or summary)")
|
||||
l.Outf(logger.Default, "(task does not have description or summary)")
|
||||
}
|
||||
|
||||
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
|
||||
@@ -77,11 +77,11 @@ func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf("")
|
||||
l.Outf("dependencies:")
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "dependencies:")
|
||||
|
||||
for _, d := range t.Deps {
|
||||
l.Outf(" - %s", d.Task)
|
||||
l.Outf(logger.Default, " - %s", d.Task)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,14 +90,14 @@ func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf("")
|
||||
l.Outf("commands:")
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "commands:")
|
||||
for _, c := range t.Cmds {
|
||||
isCommand := c.Cmd != ""
|
||||
if isCommand {
|
||||
l.Outf(" - %s", c.Cmd)
|
||||
l.Outf(logger.Default, " - %s", c.Cmd)
|
||||
} else {
|
||||
l.Outf(" - Task: %s", c.Task)
|
||||
l.Outf(logger.Default, " - Task: %s", c.Task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/summary"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/summary"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func TestPrintsDependenciesIfPresent(t *testing.T) {
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
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
|
||||
}
|
||||
if tf.Vars == nil {
|
||||
tf.Vars = make(Vars)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package taskfile_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestCmdParse(t *testing.T) {
|
||||
const (
|
||||
yamlCmd = `echo "a string command"`
|
||||
yamlDep = `"task-name"`
|
||||
yamlTaskCall = `
|
||||
task: another-task
|
||||
vars:
|
||||
PARAM1: VALUE1
|
||||
PARAM2: VALUE2
|
||||
`
|
||||
)
|
||||
tests := []struct {
|
||||
content string
|
||||
v interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
yamlCmd,
|
||||
&taskfile.Cmd{},
|
||||
&taskfile.Cmd{Cmd: `echo "a string command"`},
|
||||
},
|
||||
{
|
||||
yamlTaskCall,
|
||||
&taskfile.Cmd{},
|
||||
&taskfile.Cmd{Task: "another-task", Vars: taskfile.Vars{
|
||||
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
||||
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
yamlDep,
|
||||
&taskfile.Dep{},
|
||||
&taskfile.Dep{Task: "task-name"},
|
||||
},
|
||||
{
|
||||
yamlTaskCall,
|
||||
&taskfile.Dep{},
|
||||
&taskfile.Dep{Task: "another-task", Vars: taskfile.Vars{
|
||||
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
||||
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := yaml.Unmarshal([]byte(test.content), test.v)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, test.v)
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
sprig "github.com/go-task/slim-sprig"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -37,6 +38,9 @@ func init() {
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"shellQuote": func(str string) (string, error) {
|
||||
return syntax.Quote(str, syntax.LangBash)
|
||||
},
|
||||
// IsSH is deprecated.
|
||||
"IsSH": func() bool { return true },
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package templater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// Templater is a help struct that allow us to call "replaceX" funcs multiple
|
||||
@@ -12,10 +13,15 @@ import (
|
||||
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
||||
// return the zero value.
|
||||
type Templater struct {
|
||||
Vars taskfile.Vars
|
||||
Vars *taskfile.Vars
|
||||
RemoveNoValue bool
|
||||
|
||||
strMap map[string]string
|
||||
err error
|
||||
cacheMap map[string]interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *Templater) ResetCache() {
|
||||
r.cacheMap = r.Vars.ToCacheMap()
|
||||
}
|
||||
|
||||
func (r *Templater) Replace(str string) string {
|
||||
@@ -29,15 +35,18 @@ func (r *Templater) Replace(str string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
if r.strMap == nil {
|
||||
r.strMap = r.Vars.ToStringMap()
|
||||
if r.cacheMap == nil {
|
||||
r.cacheMap = r.Vars.ToCacheMap()
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if err = templ.Execute(&b, r.strMap); err != nil {
|
||||
if err = templ.Execute(&b, r.cacheMap); err != nil {
|
||||
r.err = err
|
||||
return ""
|
||||
}
|
||||
if r.RemoveNoValue {
|
||||
return strings.ReplaceAll(b.String(), "<no value>", "")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -53,19 +62,22 @@ func (r *Templater) ReplaceSlice(strs []string) []string {
|
||||
return new
|
||||
}
|
||||
|
||||
func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars {
|
||||
if r.err != nil || len(vars) == 0 {
|
||||
func (r *Templater) ReplaceVars(vars *taskfile.Vars) *taskfile.Vars {
|
||||
if r.err != nil || vars.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
new := make(taskfile.Vars, len(vars))
|
||||
for k, v := range vars {
|
||||
new[k] = taskfile.Var{
|
||||
var new taskfile.Vars
|
||||
vars.Range(func(k string, v taskfile.Var) error {
|
||||
new.Set(k, taskfile.Var{
|
||||
Static: r.Replace(v.Static),
|
||||
Live: v.Live,
|
||||
Sh: r.Replace(v.Sh),
|
||||
}
|
||||
}
|
||||
return new
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
return &new
|
||||
}
|
||||
|
||||
func (r *Templater) Err() error {
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,7 +23,7 @@ func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
e.Logger.Errf("task: %s", p.Msg)
|
||||
e.Logger.Errf(logger.Magenta, "task: %s", p.Msg)
|
||||
return false, ErrPreconditionFailed
|
||||
}
|
||||
}
|
||||
|
||||
85
status.go
85
status.go
@@ -4,9 +4,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/status"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/status"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// Status returns an error if any the of given tasks is not up-to-date
|
||||
@@ -21,23 +22,42 @@ func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
|
||||
return err
|
||||
}
|
||||
if !isUpToDate {
|
||||
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Task)
|
||||
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Name())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||
if len(t.Status) == 0 && len(t.Sources) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(t.Status) > 0 {
|
||||
return e.isTaskUpToDateStatus(ctx, t)
|
||||
isUpToDate, err := e.isTaskUpToDateStatus(ctx, t)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isUpToDate {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
checker, err := e.getStatusChecker(t)
|
||||
if err != nil {
|
||||
return false, err
|
||||
if len(t.Sources) > 0 {
|
||||
checker, err := e.getStatusChecker(t)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isUpToDate, err := checker.IsUpToDate()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isUpToDate {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return checker.IsUpToDate()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (e *Executor) statusOnError(t *taskfile.Task) error {
|
||||
@@ -49,25 +69,38 @@ func (e *Executor) statusOnError(t *taskfile.Task) error {
|
||||
}
|
||||
|
||||
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
||||
switch t.Method {
|
||||
case "", "timestamp":
|
||||
return &status.Timestamp{
|
||||
Dir: t.Dir,
|
||||
Sources: t.Sources,
|
||||
Generates: t.Generates,
|
||||
}, nil
|
||||
method := t.Method
|
||||
if method == "" {
|
||||
method = e.Taskfile.Method
|
||||
}
|
||||
switch method {
|
||||
case "timestamp":
|
||||
return e.timestampChecker(t), nil
|
||||
case "checksum":
|
||||
return &status.Checksum{
|
||||
Dir: t.Dir,
|
||||
Task: t.Task,
|
||||
Sources: t.Sources,
|
||||
Generates: t.Generates,
|
||||
Dry: e.Dry,
|
||||
}, nil
|
||||
return e.checksumChecker(t), nil
|
||||
case "none":
|
||||
return status.None{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`task: invalid method "%s"`, t.Method)
|
||||
return nil, fmt.Errorf(`task: invalid method "%s"`, method)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
|
||||
return &status.Timestamp{
|
||||
Dir: t.Dir,
|
||||
Sources: t.Sources,
|
||||
Generates: t.Generates,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) checksumChecker(t *taskfile.Task) status.Checker {
|
||||
return &status.Checksum{
|
||||
BaseDir: e.Dir,
|
||||
TaskDir: t.Dir,
|
||||
Task: t.Name(),
|
||||
Sources: t.Sources,
|
||||
Generates: t.Generates,
|
||||
Dry: e.Dry,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +112,10 @@ func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (
|
||||
Env: getEnviron(t),
|
||||
})
|
||||
if err != nil {
|
||||
e.Logger.VerboseOutf("task: status command %s exited non-zero: %s", s, err)
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err)
|
||||
return false, nil
|
||||
}
|
||||
e.Logger.VerboseOutf("task: status command %s exited zero", s)
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
341
task.go
341
task.go
@@ -6,19 +6,18 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-task/task/v2/internal/compiler"
|
||||
compilerv1 "github.com/go-task/task/v2/internal/compiler/v1"
|
||||
compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
|
||||
"github.com/go-task/task/v2/internal/execext"
|
||||
"github.com/go-task/task/v2/internal/logger"
|
||||
"github.com/go-task/task/v2/internal/output"
|
||||
"github.com/go-task/task/v2/internal/summary"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"github.com/go-task/task/v2/internal/taskfile/read"
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
|
||||
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/summary"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/read"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -33,14 +32,17 @@ const (
|
||||
type Executor struct {
|
||||
Taskfile *taskfile.Taskfile
|
||||
|
||||
Dir string
|
||||
Entrypoint string
|
||||
Force bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
Dir string
|
||||
Entrypoint string
|
||||
Force bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
Parallel bool
|
||||
Color bool
|
||||
Concurrency int
|
||||
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
@@ -51,10 +53,13 @@ type Executor struct {
|
||||
Output output.Output
|
||||
OutputStyle string
|
||||
|
||||
taskvars taskfile.Vars
|
||||
taskvars *taskfile.Vars
|
||||
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
executionHashes map[string]context.Context
|
||||
executionHashesMutex sync.Mutex
|
||||
}
|
||||
|
||||
// Run runs Task
|
||||
@@ -63,13 +68,20 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
for _, c := range calls {
|
||||
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
||||
// FIXME: move to the main package
|
||||
e.PrintTasksHelp()
|
||||
e.ListTasksWithDesc()
|
||||
return &taskNotFoundError{taskName: c.Task}
|
||||
}
|
||||
}
|
||||
|
||||
if e.Summary {
|
||||
summary.PrintTasks(e.Logger, e.Taskfile, calls)
|
||||
for i, c := range calls {
|
||||
compiledTask, err := e.FastCompiledTask(c)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
summary.PrintSpaceBetweenSummaries(e.Logger, i)
|
||||
summary.PrintTask(e.Logger, compiledTask)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -77,30 +89,40 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
return e.watchTasks(calls...)
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
for _, c := range calls {
|
||||
if err := e.RunTask(ctx, c); err != nil {
|
||||
return err
|
||||
c := c
|
||||
if e.Parallel {
|
||||
g.Go(func() error { return e.RunTask(ctx, c) })
|
||||
} else {
|
||||
if err := e.RunTask(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// Setup setups Executor's internal state
|
||||
func (e *Executor) Setup() error {
|
||||
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)
|
||||
|
||||
v, err := e.Taskfile.ParsedVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v < 3.0 {
|
||||
e.taskvars, err = read.Taskvars(e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if e.Stdin == nil {
|
||||
e.Stdin = os.Stdin
|
||||
}
|
||||
@@ -114,31 +136,31 @@ func (e *Executor) Setup() error {
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
Verbose: e.Verbose,
|
||||
Color: e.Color,
|
||||
}
|
||||
|
||||
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
||||
if v < 2 {
|
||||
return fmt.Errorf(`task: Taskfile versions prior to v2 are not supported anymore`)
|
||||
}
|
||||
|
||||
// consider as equal to the greater version if round
|
||||
if v == 2.0 {
|
||||
v = 2.6
|
||||
}
|
||||
|
||||
if v < 1 {
|
||||
return fmt.Errorf(`task: Taskfile version should be greater or equal to v1`)
|
||||
}
|
||||
if v > 2.6 {
|
||||
return fmt.Errorf(`task: Taskfile versions greater than v2.6 not implemented in the version of Task`)
|
||||
if v == 3.0 {
|
||||
v = 3.7
|
||||
}
|
||||
|
||||
if v < 2 {
|
||||
e.Compiler = &compilerv1.CompilerV1{
|
||||
Dir: e.Dir,
|
||||
Vars: e.taskvars,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
} else { // v >= 2
|
||||
if v > 3.7 {
|
||||
return fmt.Errorf(`task: Taskfile versions greater than v3.7 not implemented in the version of Task`)
|
||||
}
|
||||
|
||||
// Color available only on v3
|
||||
if v < 3 {
|
||||
e.Logger.Color = false
|
||||
}
|
||||
|
||||
if v < 3 {
|
||||
e.Compiler = &compilerv2.CompilerV2{
|
||||
Dir: e.Dir,
|
||||
Taskvars: e.taskvars,
|
||||
@@ -146,14 +168,41 @@ func (e *Executor) Setup() error {
|
||||
Expansions: e.Taskfile.Expansions,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
} else {
|
||||
e.Compiler = &compilerv3.CompilerV3{
|
||||
Dir: e.Dir,
|
||||
TaskfileEnv: e.Taskfile.Env,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
if v >= 3.0 {
|
||||
env, err := read.Dotenv(e.Compiler, e.Taskfile, e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = env.Range(func(key string, value taskfile.Var) error {
|
||||
if _, ok := e.Taskfile.Env.Mapping[key]; !ok {
|
||||
e.Taskfile.Env.Set(key, value)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if v < 2.2 && e.Taskfile.Includes.Len() > 0 {
|
||||
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
||||
}
|
||||
if v >= 3.0 && e.Taskfile.Expansions > 2 {
|
||||
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
|
||||
}
|
||||
|
||||
if e.OutputStyle != "" {
|
||||
e.Taskfile.Output = e.OutputStyle
|
||||
@@ -169,6 +218,14 @@ func (e *Executor) Setup() error {
|
||||
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
||||
}
|
||||
|
||||
if e.Taskfile.Method == "" {
|
||||
if v >= 3 {
|
||||
e.Taskfile.Method = "checksum"
|
||||
} else {
|
||||
e.Taskfile.Method = "timestamp"
|
||||
}
|
||||
}
|
||||
|
||||
if v <= 2.1 {
|
||||
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
||||
|
||||
@@ -192,12 +249,46 @@ func (e *Executor) Setup() error {
|
||||
}
|
||||
}
|
||||
|
||||
if v < 3 {
|
||||
err := e.Taskfile.Includes.Range(func(_ string, taskfile taskfile.IncludedTaskfile) error {
|
||||
if taskfile.AdvancedImport {
|
||||
return errors.New(`task: Import with additional parameters is only available starting on Taskfile version v3`)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v < 3.7 {
|
||||
if e.Taskfile.Run != "" {
|
||||
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
|
||||
}
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.Run != "" {
|
||||
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if e.Taskfile.Run == "" {
|
||||
e.Taskfile.Run = "always"
|
||||
}
|
||||
|
||||
e.executionHashes = make(map[string]context.Context)
|
||||
|
||||
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
|
||||
e.mkdirMutexMap = make(map[string]*sync.Mutex, len(e.Taskfile.Tasks))
|
||||
for k := range e.Taskfile.Tasks {
|
||||
e.taskCallCount[k] = new(int32)
|
||||
e.mkdirMutexMap[k] = &sync.Mutex{}
|
||||
}
|
||||
|
||||
if e.Concurrency > 0 {
|
||||
e.concurrencySemaphore = make(chan struct{}, e.Concurrency)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -211,48 +302,64 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
return &MaximumTaskCallExceededError{task: call.Task}
|
||||
}
|
||||
|
||||
if err := e.runDeps(ctx, t); err != nil {
|
||||
return err
|
||||
}
|
||||
release := e.acquireConcurrencyLimit()
|
||||
defer release()
|
||||
|
||||
if !e.Force {
|
||||
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
|
||||
if err != nil {
|
||||
return e.startExecution(ctx, t, func(ctx context.Context) error {
|
||||
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" started`, call.Task)
|
||||
if err := e.runDeps(ctx, t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upToDate, err := e.isTaskUpToDate(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if upToDate && preCondMet {
|
||||
if !e.Silent {
|
||||
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 := e.statusOnError(t); err2 != nil {
|
||||
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
|
||||
if !e.Force {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if execext.IsExitError(err) && t.IgnoreError {
|
||||
e.Logger.VerboseErrf("task: task error ignored: %v", err)
|
||||
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upToDate, err := e.isTaskUpToDate(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if upToDate && preCondMet {
|
||||
if !e.Silent {
|
||||
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Name())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.mkdir(t); err != nil {
|
||||
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v", t.Dir, err)
|
||||
}
|
||||
|
||||
for i := range t.Cmds {
|
||||
if t.Cmds[i].Defer {
|
||||
defer e.runDeferred(t, call, i)
|
||||
continue
|
||||
}
|
||||
|
||||
return &taskRunError{t.Task, err}
|
||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||
if err2 := e.statusOnError(t); err2 != nil {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v", err2)
|
||||
}
|
||||
|
||||
if execext.IsExitError(err) && t.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
return &taskRunError{t.Task, err}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" finished`, call.Task)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Executor) mkdir(t *taskfile.Task) error {
|
||||
@@ -275,6 +382,9 @@ func (e *Executor) mkdir(t *taskfile.Task) error {
|
||||
func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
defer reacquire()
|
||||
|
||||
for _, d := range t.Deps {
|
||||
d := d
|
||||
|
||||
@@ -290,27 +400,44 @@ func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (e *Executor) runDeferred(t *taskfile.Task, call taskfile.Call, i int) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||
e.Logger.VerboseErrf(logger.Yellow, `task: ignored error in deferred cmd: %s`, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error {
|
||||
cmd := t.Cmds[i]
|
||||
|
||||
switch {
|
||||
case cmd.Task != "":
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
defer reacquire()
|
||||
|
||||
err := e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case cmd.Cmd != "":
|
||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
|
||||
e.Logger.Errf(cmd.Cmd)
|
||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
||||
e.Logger.Errf(logger.Green, "task: [%s] %s", t.Name(), cmd.Cmd)
|
||||
}
|
||||
|
||||
if e.Dry {
|
||||
return nil
|
||||
}
|
||||
|
||||
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
|
||||
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
|
||||
outputWrapper := e.Output
|
||||
if t.Interactive {
|
||||
outputWrapper = output.Interleaved{}
|
||||
}
|
||||
stdOut := outputWrapper.WrapWriter(e.Stdout, t.Prefix)
|
||||
stdErr := outputWrapper.WrapWriter(e.Stderr, t.Prefix)
|
||||
|
||||
defer func() {
|
||||
if _, ok := stdOut.(*os.File); !ok {
|
||||
if closer, ok := stdOut.(io.Closer); ok {
|
||||
@@ -333,7 +460,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
||||
Stderr: stdErr,
|
||||
})
|
||||
if execext.IsExitError(err) && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf("task: command error ignored: %v", err)
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v", t.Name(), err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@@ -348,8 +475,48 @@ func getEnviron(t *taskfile.Task) []string {
|
||||
}
|
||||
|
||||
environ := os.Environ()
|
||||
for k, v := range t.Env.ToStringMap() {
|
||||
environ = append(environ, fmt.Sprintf("%s=%s", k, v))
|
||||
|
||||
for k, v := range t.Env.ToCacheMap() {
|
||||
str, isString := v.(string)
|
||||
if !isString {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
||||
continue
|
||||
}
|
||||
|
||||
environ = append(environ, fmt.Sprintf("%s=%s", k, str))
|
||||
}
|
||||
|
||||
return environ
|
||||
}
|
||||
|
||||
func (e *Executor) startExecution(ctx context.Context, t *taskfile.Task, execute func(ctx context.Context) error) error {
|
||||
h, err := e.GetHash(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if h == "" {
|
||||
return execute(ctx)
|
||||
}
|
||||
|
||||
e.executionHashesMutex.Lock()
|
||||
otherExecutionCtx, ok := e.executionHashes[h]
|
||||
|
||||
if ok {
|
||||
e.executionHashesMutex.Unlock()
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: skipping execution of task: %s", h)
|
||||
<-otherExecutionCtx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
e.executionHashes[h] = ctx
|
||||
e.executionHashesMutex.Unlock()
|
||||
|
||||
return execute(ctx)
|
||||
}
|
||||
|
||||
666
task_test.go
666
task_test.go
@@ -4,26 +4,31 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2"
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func init() {
|
||||
_ = os.Setenv("NO_COLOR", "1")
|
||||
}
|
||||
|
||||
// fileContentTest provides a basic reusable test-case for running a Taskfile
|
||||
// and inspect generated files.
|
||||
type fileContentTest struct {
|
||||
Dir string
|
||||
Target string
|
||||
TrimSpace bool
|
||||
Files map[string]string
|
||||
Dir string
|
||||
Entrypoint string
|
||||
Target string
|
||||
TrimSpace bool
|
||||
Files map[string]string
|
||||
}
|
||||
|
||||
func (fct fileContentTest) name(file string) string {
|
||||
@@ -36,16 +41,17 @@ func (fct fileContentTest) Run(t *testing.T) {
|
||||
}
|
||||
|
||||
e := &task.Executor{
|
||||
Dir: fct.Dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Dir: fct.Dir,
|
||||
Entrypoint: fct.Entrypoint,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
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) {
|
||||
b, err := ioutil.ReadFile(filepath.Join(fct.Dir, name))
|
||||
b, err := os.ReadFile(filepath.Join(fct.Dir, name))
|
||||
assert.NoError(t, err, "Error reading file")
|
||||
s := string(b)
|
||||
if fct.TrimSpace {
|
||||
@@ -56,6 +62,16 @@ func (fct fileContentTest) Run(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyTask(t *testing.T) {
|
||||
e := &task.Executor{
|
||||
Dir: "testdata/empty_task",
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/env",
|
||||
@@ -69,43 +85,6 @@ func TestEnv(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestVarsV1(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/vars/v1",
|
||||
Target: "default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
// hello task:
|
||||
"foo.txt": "foo",
|
||||
"bar.txt": "bar",
|
||||
"baz.txt": "baz",
|
||||
"tmpl_foo.txt": "foo",
|
||||
"tmpl_bar.txt": "<no value>",
|
||||
"tmpl_foo2.txt": "foo2",
|
||||
"tmpl_bar2.txt": "bar2",
|
||||
"shtmpl_foo.txt": "foo",
|
||||
"shtmpl_foo2.txt": "foo2",
|
||||
"nestedtmpl_foo.txt": "{{.FOO}}",
|
||||
"nestedtmpl_foo2.txt": "foo2",
|
||||
"foo2.txt": "foo2",
|
||||
"bar2.txt": "bar2",
|
||||
"baz2.txt": "baz2",
|
||||
"tmpl2_foo.txt": "<no value>",
|
||||
"tmpl2_foo2.txt": "foo2",
|
||||
"tmpl2_bar.txt": "<no value>",
|
||||
"tmpl2_bar2.txt": "<no value>",
|
||||
"shtmpl2_foo.txt": "<no value>",
|
||||
"shtmpl2_foo2.txt": "foo2",
|
||||
"nestedtmpl2_foo2.txt": "{{.FOO2}}",
|
||||
"override.txt": "bar",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
// Ensure identical results when running hello task directly.
|
||||
tt.Target = "hello"
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestVarsV2(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/vars/v2",
|
||||
@@ -135,6 +114,7 @@ func TestVarsV2(t *testing.T) {
|
||||
"nestedtmpl2_foo2.txt": "<no value>",
|
||||
"override.txt": "bar",
|
||||
"nested.txt": "Taskvars-TaskfileVars-TaskVars",
|
||||
"task_name.txt": "hello",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
@@ -143,8 +123,23 @@ func TestVarsV2(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestVarsV3(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/vars/v3",
|
||||
Target: "default",
|
||||
Files: map[string]string{
|
||||
"missing-var.txt": "\n",
|
||||
"var-order.txt": "ABCDEF\n",
|
||||
"dependent-sh.txt": "123456\n",
|
||||
"with-call.txt": "Hi, ABC123!\n",
|
||||
"from-dot-env.txt": "From .env file\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestMultilineVars(t *testing.T) {
|
||||
for _, dir := range []string{"testdata/vars/v1/multiline", "testdata/vars/v2/multiline"} {
|
||||
for _, dir := range []string{"testdata/vars/v2/multiline"} {
|
||||
tt := fileContentTest{
|
||||
Dir: dir,
|
||||
Target: "default",
|
||||
@@ -168,20 +163,36 @@ func TestMultilineVars(t *testing.T) {
|
||||
|
||||
func TestVarsInvalidTmpl(t *testing.T) {
|
||||
const (
|
||||
dir = "testdata/vars/v1"
|
||||
dir = "testdata/vars/v2"
|
||||
target = "invalid-var-tmpl"
|
||||
expectError = "template: :1: unexpected EOF"
|
||||
)
|
||||
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||
assert.EqualError(t, e.Run(context.Background(), taskfile.Call{Task: target}), expectError, "e.Run(target)")
|
||||
}
|
||||
|
||||
func TestConcurrency(t *testing.T) {
|
||||
const (
|
||||
dir = "testdata/concurrency"
|
||||
target = "default"
|
||||
)
|
||||
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
Concurrency: 1,
|
||||
}
|
||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}), "e.Run(target)")
|
||||
}
|
||||
|
||||
func TestParams(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/params",
|
||||
@@ -227,8 +238,8 @@ func TestDeps(t *testing.T) {
|
||||
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
@@ -243,12 +254,18 @@ func TestDeps(t *testing.T) {
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
const dir = "testdata/status"
|
||||
var file = filepath.Join(dir, "foo.txt")
|
||||
|
||||
_ = os.Remove(file)
|
||||
files := []string{
|
||||
"foo.txt",
|
||||
"bar.txt",
|
||||
}
|
||||
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Errorf("File should not exist: %v", err)
|
||||
for _, f := range files {
|
||||
path := filepath.Join(dir, f)
|
||||
_ = os.Remove(path)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
t.Errorf("File should not exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
@@ -260,17 +277,33 @@ func TestStatus(t *testing.T) {
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-bar"}))
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
t.Errorf("File should exist: %v", err)
|
||||
for _, f := range files {
|
||||
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
|
||||
t.Errorf("File should exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
// all: not up-to-date
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||
assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
// status: not up-to-date
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||
assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
|
||||
// sources: not up-to-date
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-bar"}))
|
||||
assert.Equal(t, "task: [gen-bar] touch bar.txt", strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
// all: up-to-date
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-bar"}))
|
||||
assert.Equal(t, `task: Task "gen-bar" is up to date`, strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
}
|
||||
|
||||
func TestPrecondition(t *testing.T) {
|
||||
@@ -315,6 +348,8 @@ func TestPrecondition(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerates(t *testing.T) {
|
||||
const dir = "testdata/generates"
|
||||
|
||||
const (
|
||||
srcTask = "sub/src.txt"
|
||||
relTask = "rel.txt"
|
||||
@@ -322,9 +357,6 @@ func TestGenerates(t *testing.T) {
|
||||
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, fileWithSpaces} {
|
||||
@@ -406,22 +438,183 @@ func TestStatusChecksum(t *testing.T) {
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
}
|
||||
|
||||
func TestLabelUpToDate(t *testing.T) {
|
||||
const dir = "testdata/label_uptodate"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
||||
assert.Contains(t, buff.String(), "foobar")
|
||||
}
|
||||
|
||||
func TestLabelSummary(t *testing.T) {
|
||||
const dir = "testdata/label_summary"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Summary: true,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
||||
assert.Contains(t, buff.String(), "foobar")
|
||||
}
|
||||
|
||||
func TestLabelInStatus(t *testing.T) {
|
||||
const dir = "testdata/label_status"
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
err := e.Status(context.Background(), taskfile.Call{Task: "foo"})
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "foobar")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelWithVariableExpansion(t *testing.T) {
|
||||
const dir = "testdata/label_var"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
||||
assert.Contains(t, buff.String(), "foobaz")
|
||||
}
|
||||
|
||||
func TestLabelInSummary(t *testing.T) {
|
||||
const dir = "testdata/label_summary"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
||||
assert.Contains(t, buff.String(), "foobar")
|
||||
}
|
||||
|
||||
func TestLabelInList(t *testing.T) {
|
||||
const dir = "testdata/label_list"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
e.ListTasksWithDesc()
|
||||
assert.Contains(t, buff.String(), "foobar")
|
||||
}
|
||||
|
||||
// task -al case 1: listAll list all tasks
|
||||
func TestListAllShowsNoDesc(t *testing.T) {
|
||||
const dir = "testdata/list_mixed_desc"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
var title string
|
||||
e.ListAllTasks()
|
||||
for _, title = range []string{
|
||||
"foo",
|
||||
"voo",
|
||||
"doo",
|
||||
} {
|
||||
assert.Contains(t, buff.String(), title)
|
||||
}
|
||||
}
|
||||
|
||||
// task -al case 2: !listAll list some tasks (only those with desc)
|
||||
func TestListCanListDescOnly(t *testing.T) {
|
||||
const dir = "testdata/list_mixed_desc"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Setup())
|
||||
e.ListTasksWithDesc()
|
||||
|
||||
var title string
|
||||
assert.Contains(t, buff.String(), "foo")
|
||||
for _, title = range []string{
|
||||
"voo",
|
||||
"doo",
|
||||
} {
|
||||
assert.NotContains(t, buff.String(), title)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusVariables(t *testing.T) {
|
||||
const dir = "testdata/status_vars"
|
||||
|
||||
_ = os.RemoveAll(filepath.Join(dir, ".task"))
|
||||
_ = os.Remove(filepath.Join(dir, "generated.txt"))
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: false,
|
||||
Verbose: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||
|
||||
assert.Contains(t, buff.String(), "d41d8cd98f00b204e9800998ecf8427e")
|
||||
|
||||
inf, err := os.Stat(filepath.Join(dir, "source.txt"))
|
||||
assert.NoError(t, err)
|
||||
ts := fmt.Sprintf("%d", inf.ModTime().Unix())
|
||||
tf := fmt.Sprintf("%s", inf.ModTime())
|
||||
|
||||
assert.Contains(t, buff.String(), ts)
|
||||
assert.Contains(t, buff.String(), tf)
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
const dir = "testdata/init"
|
||||
var file = filepath.Join(dir, "Taskfile.yml")
|
||||
var file = filepath.Join(dir, "Taskfile.yaml")
|
||||
|
||||
_ = os.Remove(file)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Errorf("Taskfile.yml should not exist")
|
||||
t.Errorf("Taskfile.yaml should not exist")
|
||||
}
|
||||
|
||||
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
|
||||
if err := task.InitTaskfile(io.Discard, dir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
t.Errorf("Taskfile.yml should exist")
|
||||
t.Errorf("Taskfile.yaml should exist")
|
||||
}
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
|
||||
func TestCyclicDep(t *testing.T) {
|
||||
@@ -429,8 +622,8 @@ func TestCyclicDep(t *testing.T) {
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(context.Background(), taskfile.Call{Task: "task-1"}))
|
||||
@@ -441,7 +634,6 @@ func TestTaskVersion(t *testing.T) {
|
||||
Dir string
|
||||
Version string
|
||||
}{
|
||||
{"testdata/version/v1", "1"},
|
||||
{"testdata/version/v2", "2"},
|
||||
}
|
||||
|
||||
@@ -449,8 +641,8 @@ func TestTaskVersion(t *testing.T) {
|
||||
t.Run(test.Dir, func(t *testing.T) {
|
||||
e := task.Executor{
|
||||
Dir: test.Dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.Equal(t, test.Version, e.Taskfile.Version)
|
||||
@@ -464,8 +656,8 @@ func TestTaskIgnoreErrors(t *testing.T) {
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
@@ -511,7 +703,7 @@ func TestDry(t *testing.T) {
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||
|
||||
assert.Equal(t, "touch file.txt", strings.TrimSpace(buff.String()))
|
||||
assert.Equal(t, "task: [build] touch file.txt", strings.TrimSpace(buff.String()))
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Errorf("File should not exist %s", file)
|
||||
}
|
||||
@@ -527,8 +719,8 @@ func TestDryChecksum(t *testing.T) {
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
Dry: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
@@ -549,14 +741,33 @@ func TestIncludes(t *testing.T) {
|
||||
Target: "default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"main.txt": "main",
|
||||
"included_directory.txt": "included_directory",
|
||||
"included_taskfile.txt": "included_taskfile",
|
||||
"main.txt": "main",
|
||||
"included_directory.txt": "included_directory",
|
||||
"included_directory_without_dir.txt": "included_directory_without_dir",
|
||||
"included_taskfile_without_dir.txt": "included_taskfile_without_dir",
|
||||
"./module2/included_directory_with_dir.txt": "included_directory_with_dir",
|
||||
"./module2/included_taskfile_with_dir.txt": "included_taskfile_with_dir",
|
||||
"os_include.txt": "os",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncorrectVersionIncludes(t *testing.T) {
|
||||
const dir = "testdata/incorrect_includes"
|
||||
expectedError := "task: Import with additional parameters is only available starting on Taskfile version v3"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
assert.EqualError(t, e.Setup(), expectedError)
|
||||
}
|
||||
|
||||
func TestIncludesEmptyMain(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_empty",
|
||||
@@ -595,6 +806,56 @@ func TestIncludesCallingRoot(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludesOptional(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_optional",
|
||||
Target: "default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"called_dep.txt": "called_dep",
|
||||
}}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludesOptionalImplicitFalse(t *testing.T) {
|
||||
e := task.Executor{
|
||||
Dir: "testdata/includes_optional_implicit_false",
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "stat testdata/includes_optional_implicit_false/TaskfileOptional.yml: no such file or directory", err.Error())
|
||||
}
|
||||
|
||||
func TestIncludesOptionalExplicitFalse(t *testing.T) {
|
||||
e := task.Executor{
|
||||
Dir: "testdata/includes_optional_explicit_false",
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "stat testdata/includes_optional_explicit_false/TaskfileOptional.yml: no such file or directory", err.Error())
|
||||
}
|
||||
|
||||
func TestIncludesFromCustomTaskfile(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_yaml",
|
||||
Entrypoint: "Custom.ext",
|
||||
Target: "default",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"main.txt": "main",
|
||||
"included_with_yaml_extension.txt": "included_with_yaml_extension",
|
||||
"included_with_custom_file.txt": "included_with_custom_file",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestSummary(t *testing.T) {
|
||||
const dir = "testdata/summary"
|
||||
|
||||
@@ -609,7 +870,7 @@ func TestSummary(t *testing.T) {
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
|
||||
|
||||
data, err := ioutil.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
|
||||
data, err := os.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedOutput := string(data)
|
||||
@@ -668,7 +929,7 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
|
||||
}
|
||||
|
||||
// Ensure that the directory to be created doesn't actually exist.
|
||||
_ = os.Remove(toBeCreated)
|
||||
_ = os.RemoveAll(toBeCreated)
|
||||
if _, err := os.Stat(toBeCreated); err == nil {
|
||||
t.Errorf("Directory should not exist: %v", err)
|
||||
}
|
||||
@@ -679,5 +940,234 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
|
||||
// Clean-up after ourselves only if no error.
|
||||
_ = os.Remove(toBeCreated)
|
||||
_ = os.RemoveAll(toBeCreated)
|
||||
}
|
||||
|
||||
func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
|
||||
const expected = "created"
|
||||
const dir = "testdata/dir/dynamic_var_on_created_dir/"
|
||||
const toBeCreated = dir + expected
|
||||
const target = "default"
|
||||
var out bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &out,
|
||||
Stderr: &out,
|
||||
}
|
||||
|
||||
// Ensure that the directory to be created doesn't actually exist.
|
||||
_ = os.RemoveAll(toBeCreated)
|
||||
if _, err := os.Stat(toBeCreated); err == nil {
|
||||
t.Errorf("Directory should not exist: %v", err)
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}))
|
||||
|
||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
|
||||
// Clean-up after ourselves only if no error.
|
||||
_ = os.RemoveAll(toBeCreated)
|
||||
}
|
||||
|
||||
func TestDynamicVariablesShouldRunOnTheTaskDir(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dir/dynamic_var",
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
"subdirectory/from_root_taskfile.txt": "subdirectory\n",
|
||||
"subdirectory/from_included_taskfile.txt": "subdirectory\n",
|
||||
"subdirectory/from_included_taskfile_task.txt": "subdirectory\n",
|
||||
"subdirectory/from_interpolated_dir.txt": "subdirectory\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestDisplaysErrorOnUnsupportedVersion(t *testing.T) {
|
||||
e := task.Executor{
|
||||
Dir: "testdata/version/v1",
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "task: Taskfile versions prior to v2 are not supported anymore", err.Error())
|
||||
|
||||
}
|
||||
|
||||
func TestShortTaskNotation(t *testing.T) {
|
||||
const dir = "testdata/short_task_notation"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
|
||||
}
|
||||
|
||||
func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv/default",
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
"include.txt": "INCLUDE1='from_include1' INCLUDE2='from_include2'\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
|
||||
const dir = "testdata/dotenv/error_included_envs"
|
||||
const entry = "Taskfile.yml"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Entrypoint: entry,
|
||||
Summary: true,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "move the dotenv")
|
||||
}
|
||||
|
||||
func TestDotenvShouldAllowMissingEnv(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv/missing_env",
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
"include.txt": "INCLUDE1='' INCLUDE2=''\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestDotenvHasLocalEnvInPath(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv/local_env_in_path",
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
"var.txt": "VAR='var_in_dot_env_1'\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestDotenvHasLocalVarInPath(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv/local_var_in_path",
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
"var.txt": "VAR='var_in_dot_env_3'\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestDotenvHasEnvVarInPath(t *testing.T) {
|
||||
os.Setenv("ENV_VAR", "testing")
|
||||
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv/env_var_in_path",
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
"var.txt": "VAR='var_in_dot_env_2'\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestExitImmediately(t *testing.T) {
|
||||
const dir = "testdata/exit_immediately"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
|
||||
}
|
||||
|
||||
func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/run",
|
||||
Target: "generate-hash",
|
||||
Files: map[string]string{
|
||||
"hash.txt": "starting 1\n1\n2\n",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestDeferredCmds(t *testing.T) {
|
||||
const dir = "testdata/deferred"
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
expectedOutputOrder := strings.TrimSpace(`
|
||||
task: [task-2] echo 'cmd ran'
|
||||
cmd ran
|
||||
task: [task-2] exit 1
|
||||
task: [task-2] echo 'failing' && exit 2
|
||||
failing
|
||||
task: [task-2] echo 'echo ran'
|
||||
echo ran
|
||||
task: [task-1] echo 'task-1 ran successfully'
|
||||
task-1 ran successfully
|
||||
`)
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "task-2"}))
|
||||
fmt.Println(buff.String())
|
||||
assert.Contains(t, buff.String(), expectedOutputOrder)
|
||||
}
|
||||
|
||||
func TestIgnoreNilElements(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
}{
|
||||
{"nil cmd", "testdata/ignore_nil_elements/cmds"},
|
||||
{"nil dep", "testdata/ignore_nil_elements/deps"},
|
||||
{"nil include", "testdata/ignore_nil_elements/includes"},
|
||||
{"nil precondition", "testdata/ignore_nil_elements/preconditions"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: test.dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
assert.Equal(t, "string-slice-1\n", buff.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ package taskfile
|
||||
// Call is the parameters to a task call
|
||||
type Call struct {
|
||||
Task string
|
||||
Vars Vars
|
||||
Vars *Vars
|
||||
}
|
||||
@@ -1,41 +1,26 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Cmd is a task command
|
||||
type Cmd struct {
|
||||
Cmd string
|
||||
Silent bool
|
||||
Task string
|
||||
Vars Vars
|
||||
Vars *Vars
|
||||
IgnoreError bool
|
||||
Defer bool
|
||||
}
|
||||
|
||||
// Dep is a task dependency
|
||||
type Dep struct {
|
||||
Task string
|
||||
Vars Vars
|
||||
Vars *Vars
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrCantUnmarshalCmd is returned for invalid command YAML
|
||||
ErrCantUnmarshalCmd = errors.New("task: can't unmarshal cmd value")
|
||||
// ErrCantUnmarshalDep is returned for invalid dependency YAML
|
||||
ErrCantUnmarshalDep = errors.New("task: can't unmarshal dep value")
|
||||
)
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var cmd string
|
||||
if err := unmarshal(&cmd); err == nil {
|
||||
if strings.HasPrefix(cmd, "^") {
|
||||
c.Task = strings.TrimPrefix(cmd, "^")
|
||||
} else {
|
||||
c.Cmd = cmd
|
||||
}
|
||||
c.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
var cmdStruct struct {
|
||||
@@ -49,16 +34,33 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
c.IgnoreError = cmdStruct.IgnoreError
|
||||
return nil
|
||||
}
|
||||
var taskCall struct {
|
||||
Task string
|
||||
Vars Vars
|
||||
var deferredCmd struct {
|
||||
Defer string
|
||||
}
|
||||
if err := unmarshal(&taskCall); err == nil {
|
||||
c.Task = taskCall.Task
|
||||
c.Vars = taskCall.Vars
|
||||
if err := unmarshal(&deferredCmd); err == nil && deferredCmd.Defer != "" {
|
||||
c.Defer = true
|
||||
c.Cmd = deferredCmd.Defer
|
||||
return nil
|
||||
}
|
||||
return ErrCantUnmarshalCmd
|
||||
var deferredCall struct {
|
||||
Defer Call
|
||||
}
|
||||
if err := unmarshal(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
|
||||
c.Defer = true
|
||||
c.Task = deferredCall.Defer.Task
|
||||
c.Vars = deferredCall.Defer.Vars
|
||||
return nil
|
||||
}
|
||||
var taskCall struct {
|
||||
Task string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := unmarshal(&taskCall); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Task = taskCall.Task
|
||||
c.Vars = taskCall.Vars
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
@@ -70,12 +72,12 @@ func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
}
|
||||
var taskCall struct {
|
||||
Task string
|
||||
Vars Vars
|
||||
Vars *Vars
|
||||
}
|
||||
if err := unmarshal(&taskCall); err == nil {
|
||||
d.Task = taskCall.Task
|
||||
d.Vars = taskCall.Vars
|
||||
return nil
|
||||
if err := unmarshal(&taskCall); err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrCantUnmarshalDep
|
||||
d.Task = taskCall.Task
|
||||
d.Vars = taskCall.Vars
|
||||
return nil
|
||||
}
|
||||
106
taskfile/included_taskfile.go
Normal file
106
taskfile/included_taskfile.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// IncludedTaskfile represents information about included tasksfile
|
||||
type IncludedTaskfile struct {
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
AdvancedImport bool
|
||||
}
|
||||
|
||||
// IncludedTaskfiles represents information about included tasksfiles
|
||||
type IncludedTaskfiles struct {
|
||||
Keys []string
|
||||
Mapping map[string]IncludedTaskfile
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (tfs *IncludedTaskfiles) UnmarshalYAML(node *yaml.Node) error {
|
||||
if node.Kind != yaml.MappingNode {
|
||||
return errors.New("task: includes is not a map")
|
||||
}
|
||||
|
||||
// NOTE(@andreynering): on this style of custom unmarsheling,
|
||||
// even number contains the keys, while odd numbers contains
|
||||
// the values.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
|
||||
var v IncludedTaskfile
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
tfs.Set(keyNode.Value, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len returns the length of the map
|
||||
func (tfs *IncludedTaskfiles) Len() int {
|
||||
if tfs == nil {
|
||||
return 0
|
||||
}
|
||||
return len(tfs.Keys)
|
||||
}
|
||||
|
||||
// Merge merges the given IncludedTaskfiles into the caller one
|
||||
func (tfs *IncludedTaskfiles) Merge(other *IncludedTaskfiles) {
|
||||
other.Range(func(key string, value IncludedTaskfile) error {
|
||||
tfs.Set(key, value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Set sets a value to a given key
|
||||
func (tfs *IncludedTaskfiles) Set(key string, includedTaskfile IncludedTaskfile) {
|
||||
if tfs.Mapping == nil {
|
||||
tfs.Mapping = make(map[string]IncludedTaskfile, 1)
|
||||
}
|
||||
if !stringSliceContains(tfs.Keys, key) {
|
||||
tfs.Keys = append(tfs.Keys, key)
|
||||
}
|
||||
tfs.Mapping[key] = includedTaskfile
|
||||
}
|
||||
|
||||
// Range allows you to loop into the included taskfiles in its right order
|
||||
func (tfs *IncludedTaskfiles) Range(yield func(key string, includedTaskfile IncludedTaskfile) error) error {
|
||||
if tfs == nil {
|
||||
return nil
|
||||
}
|
||||
for _, k := range tfs.Keys {
|
||||
if err := yield(k, tfs.Mapping[k]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var str string
|
||||
if err := unmarshal(&str); err == nil {
|
||||
it.Taskfile = str
|
||||
return nil
|
||||
}
|
||||
|
||||
var includedTaskfile struct {
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
}
|
||||
if err := unmarshal(&includedTaskfile); err != nil {
|
||||
return err
|
||||
}
|
||||
it.Taskfile = includedTaskfile.Taskfile
|
||||
it.Dir = includedTaskfile.Dir
|
||||
it.Optional = includedTaskfile.Optional
|
||||
it.AdvancedImport = true
|
||||
return nil
|
||||
}
|
||||
@@ -22,25 +22,18 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
||||
}
|
||||
|
||||
if t1.Includes == nil {
|
||||
t1.Includes = make(map[string]string)
|
||||
}
|
||||
for k, v := range t2.Includes {
|
||||
t1.Includes[k] = v
|
||||
t1.Includes = &IncludedTaskfiles{}
|
||||
}
|
||||
t1.Includes.Merge(t2.Includes)
|
||||
|
||||
if t1.Vars == nil {
|
||||
t1.Vars = make(Vars)
|
||||
t1.Vars = &Vars{}
|
||||
}
|
||||
for k, v := range t2.Vars {
|
||||
t1.Vars[k] = v
|
||||
}
|
||||
|
||||
if t1.Env == nil {
|
||||
t1.Env = make(Vars)
|
||||
}
|
||||
for k, v := range t2.Env {
|
||||
t1.Env[k] = v
|
||||
t1.Env = &Vars{}
|
||||
}
|
||||
t1.Vars.Merge(t2.Vars)
|
||||
t1.Env.Merge(t2.Env)
|
||||
|
||||
if t1.Tasks == nil {
|
||||
t1.Tasks = make(Tasks)
|
||||
@@ -56,7 +49,7 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, namespaces...)
|
||||
}
|
||||
for _, cmd := range v.Cmds {
|
||||
if cmd.Task != "" {
|
||||
if cmd != nil && cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...)
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ package taskfile_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func TestPreconditionParse(t *testing.T) {
|
||||
50
taskfile/read/dotenv.go
Normal file
50
taskfile/read/dotenv.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package read
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func Dotenv(c compiler.Compiler, tf *taskfile.Taskfile, dir string) (*taskfile.Vars, error) {
|
||||
if len(tf.Dotenv) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
vars, err := c.GetTaskfileVariables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env := &taskfile.Vars{}
|
||||
|
||||
tr := templater.Templater{Vars: vars, RemoveNoValue: true}
|
||||
|
||||
for _, dotEnvPath := range tf.Dotenv {
|
||||
dotEnvPath = tr.Replace(dotEnvPath)
|
||||
|
||||
if !filepath.IsAbs(dotEnvPath) {
|
||||
dotEnvPath = filepath.Join(dir, dotEnvPath)
|
||||
}
|
||||
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
envs, err := godotenv.Read(dotEnvPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range envs {
|
||||
if _, ok := env.Mapping[key]; !ok {
|
||||
env.Set(key, taskfile.Var{Static: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
172
taskfile/read/taskfile.go
Normal file
172
taskfile/read/taskfile.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package read
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
|
||||
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
|
||||
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
||||
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
||||
|
||||
defaultTaskfiles = []string{"Taskfile.yml", "Taskfile.yaml"}
|
||||
)
|
||||
|
||||
// Taskfile reads a Taskfile for a given directory
|
||||
// Uses current dir when dir is left empty. Uses Taskfile.yml
|
||||
// or Taskfile.yaml when entrypoint is left empty
|
||||
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||
if dir == "" {
|
||||
d, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir = d
|
||||
}
|
||||
path, err := exists(filepath.Join(dir, entrypoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := t.ParsedVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
|
||||
if v >= 3.0 {
|
||||
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
|
||||
includedTask = taskfile.IncludedTaskfile{
|
||||
Taskfile: tr.Replace(includedTask.Taskfile),
|
||||
Dir: tr.Replace(includedTask.Dir),
|
||||
Optional: includedTask.Optional,
|
||||
AdvancedImport: includedTask.AdvancedImport,
|
||||
}
|
||||
if err := tr.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
path, err := execext.Expand(includedTask.Taskfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(dir, path)
|
||||
}
|
||||
|
||||
path, err = exists(path)
|
||||
if err != nil {
|
||||
if includedTask.Optional {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
includedTaskfile, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if includedTaskfile.Includes.Len() > 0 {
|
||||
return ErrIncludedTaskfilesCantHaveIncludes
|
||||
}
|
||||
|
||||
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
|
||||
return ErrIncludedTaskfilesCantHaveDotenvs
|
||||
}
|
||||
|
||||
if includedTask.AdvancedImport {
|
||||
for k, v := range includedTaskfile.Vars.Mapping {
|
||||
o := v
|
||||
o.Dir = filepath.Join(dir, includedTask.Dir)
|
||||
includedTaskfile.Vars.Mapping[k] = o
|
||||
}
|
||||
for k, v := range includedTaskfile.Env.Mapping {
|
||||
o := v
|
||||
o.Dir = filepath.Join(dir, includedTask.Dir)
|
||||
includedTaskfile.Env.Mapping[k] = o
|
||||
}
|
||||
|
||||
for _, task := range includedTaskfile.Tasks {
|
||||
if !filepath.IsAbs(task.Dir) {
|
||||
task.Dir = filepath.Join(includedTask.Dir, task.Dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v < 3.0 {
|
||||
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
|
||||
if _, err = os.Stat(path); err == nil {
|
||||
osTaskfile, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = taskfile.Merge(t, osTaskfile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name, task := range t.Tasks {
|
||||
if task == nil {
|
||||
task = &taskfile.Task{}
|
||||
t.Tasks[name] = task
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func exists(path string) (string, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if fi.Mode().IsRegular() {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
for _, n := range defaultTaskfiles {
|
||||
fpath := filepath.Join(path, n)
|
||||
if _, err := os.Stat(fpath); err == nil {
|
||||
return fpath, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf(`task: No Taskfile found in "%s". Use "task --init" to create a new one`, path)
|
||||
}
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-task/task/v2/internal/taskfile"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// Taskvars reads a Taskvars for a given directory
|
||||
func Taskvars(dir string) (taskfile.Vars, error) {
|
||||
vars := make(taskfile.Vars)
|
||||
func Taskvars(dir string) (*taskfile.Vars, error) {
|
||||
vars := &taskfile.Vars{}
|
||||
|
||||
path := filepath.Join(dir, "Taskvars.yml")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
@@ -29,24 +29,17 @@ func Taskvars(dir string) (taskfile.Vars, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
vars = osVars
|
||||
} else {
|
||||
for k, v := range osVars {
|
||||
vars[k] = v
|
||||
}
|
||||
}
|
||||
vars.Merge(osVars)
|
||||
}
|
||||
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
func readTaskvars(file string) (taskfile.Vars, error) {
|
||||
func readTaskvars(file string) (*taskfile.Vars, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vars taskfile.Vars
|
||||
return vars, yaml.NewDecoder(f).Decode(&vars)
|
||||
return &vars, yaml.NewDecoder(f).Decode(&vars)
|
||||
}
|
||||
10
taskfile/slice.go
Normal file
10
taskfile/slice.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package taskfile
|
||||
|
||||
func stringSliceContains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
91
taskfile/task.go
Normal file
91
taskfile/task.go
Normal file
@@ -0,0 +1,91 @@
|
||||
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
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool
|
||||
Run string
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
if t.Label != "" {
|
||||
return t.Label
|
||||
}
|
||||
return t.Task
|
||||
}
|
||||
|
||||
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var cmd Cmd
|
||||
if err := unmarshal(&cmd); err == nil && cmd.Cmd != "" {
|
||||
t.Cmds = append(t.Cmds, &cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cmds []*Cmd
|
||||
if err := unmarshal(&cmds); err == nil && len(cmds) > 0 {
|
||||
t.Cmds = cmds
|
||||
return nil
|
||||
}
|
||||
|
||||
var task struct {
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
Run string
|
||||
}
|
||||
if err := unmarshal(&task); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Cmds = task.Cmds
|
||||
t.Deps = task.Deps
|
||||
t.Label = task.Label
|
||||
t.Desc = task.Desc
|
||||
t.Summary = task.Summary
|
||||
t.Sources = task.Sources
|
||||
t.Generates = task.Generates
|
||||
t.Status = task.Status
|
||||
t.Preconditions = task.Preconditions
|
||||
t.Dir = task.Dir
|
||||
t.Vars = task.Vars
|
||||
t.Env = task.Env
|
||||
t.Silent = task.Silent
|
||||
t.Interactive = task.Interactive
|
||||
t.Method = task.Method
|
||||
t.Prefix = task.Prefix
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.Run = task.Run
|
||||
return nil
|
||||
}
|
||||
71
taskfile/taskfile.go
Normal file
71
taskfile/taskfile.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Taskfile represents a Taskfile.yml
|
||||
type Taskfile struct {
|
||||
Version string
|
||||
Expansions int
|
||||
Output string
|
||||
Method string
|
||||
Includes *IncludedTaskfiles
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var taskfile struct {
|
||||
Version string
|
||||
Expansions int
|
||||
Output string
|
||||
Method string
|
||||
Includes *IncludedTaskfiles
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
}
|
||||
if err := unmarshal(&taskfile); err != nil {
|
||||
return err
|
||||
}
|
||||
tf.Version = taskfile.Version
|
||||
tf.Expansions = taskfile.Expansions
|
||||
tf.Output = taskfile.Output
|
||||
tf.Method = taskfile.Method
|
||||
tf.Includes = taskfile.Includes
|
||||
tf.Vars = taskfile.Vars
|
||||
tf.Env = taskfile.Env
|
||||
tf.Tasks = taskfile.Tasks
|
||||
tf.Silent = taskfile.Silent
|
||||
tf.Dotenv = taskfile.Dotenv
|
||||
tf.Run = taskfile.Run
|
||||
if tf.Expansions <= 0 {
|
||||
tf.Expansions = 2
|
||||
}
|
||||
if tf.Vars == nil {
|
||||
tf.Vars = &Vars{}
|
||||
}
|
||||
if tf.Env == nil {
|
||||
tf.Env = &Vars{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParsedVersion returns the version as a float64
|
||||
func (tf *Taskfile) ParsedVersion() (float64, error) {
|
||||
v, err := strconv.ParseFloat(tf.Version, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, tf.Version, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
83
taskfile/taskfile_test.go
Normal file
83
taskfile/taskfile_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package taskfile_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func TestCmdParse(t *testing.T) {
|
||||
const (
|
||||
yamlCmd = `echo "a string command"`
|
||||
yamlDep = `"task-name"`
|
||||
yamlTaskCall = `
|
||||
task: another-task
|
||||
vars:
|
||||
PARAM1: VALUE1
|
||||
PARAM2: VALUE2
|
||||
`
|
||||
yamlDeferredCall = `defer: { task: some_task, vars: { PARAM1: "var" } }`
|
||||
yamlDeferredCmd = `defer: echo 'test'`
|
||||
)
|
||||
tests := []struct {
|
||||
content string
|
||||
v interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
yamlCmd,
|
||||
&taskfile.Cmd{},
|
||||
&taskfile.Cmd{Cmd: `echo "a string command"`},
|
||||
},
|
||||
{
|
||||
yamlTaskCall,
|
||||
&taskfile.Cmd{},
|
||||
&taskfile.Cmd{Task: "another-task", Vars: &taskfile.Vars{
|
||||
Keys: []string{"PARAM1", "PARAM2"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
||||
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
yamlDeferredCmd,
|
||||
&taskfile.Cmd{},
|
||||
&taskfile.Cmd{Cmd: "echo 'test'", Defer: true},
|
||||
},
|
||||
{
|
||||
yamlDeferredCall,
|
||||
&taskfile.Cmd{},
|
||||
&taskfile.Cmd{Task: "some_task", Vars: &taskfile.Vars{
|
||||
Keys: []string{"PARAM1"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"PARAM1": taskfile.Var{Static: "var"},
|
||||
},
|
||||
}, Defer: true},
|
||||
},
|
||||
{
|
||||
yamlDep,
|
||||
&taskfile.Dep{},
|
||||
&taskfile.Dep{Task: "task-name"},
|
||||
},
|
||||
{
|
||||
yamlTaskCall,
|
||||
&taskfile.Dep{},
|
||||
&taskfile.Dep{Task: "another-task", Vars: &taskfile.Vars{
|
||||
Keys: []string{"PARAM1", "PARAM2"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
||||
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := yaml.Unmarshal([]byte(test.content), test.v)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, test.v)
|
||||
}
|
||||
}
|
||||
122
taskfile/var.go
Normal file
122
taskfile/var.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Vars is a string[string] variables map.
|
||||
type Vars struct {
|
||||
Keys []string
|
||||
Mapping map[string]Var
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
|
||||
if node.Kind != yaml.MappingNode {
|
||||
return errors.New("task: vars is not a map")
|
||||
}
|
||||
|
||||
// NOTE(@andreynering): on this style of custom unmarsheling,
|
||||
// even number contains the keys, while odd numbers contains
|
||||
// the values.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
|
||||
var v Var
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
vs.Set(keyNode.Value, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge merges the given Vars into the caller one
|
||||
func (vs *Vars) Merge(other *Vars) {
|
||||
other.Range(func(key string, value Var) error {
|
||||
vs.Set(key, value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Set sets a value to a given key
|
||||
func (vs *Vars) Set(key string, value Var) {
|
||||
if vs.Mapping == nil {
|
||||
vs.Mapping = make(map[string]Var, 1)
|
||||
}
|
||||
if !stringSliceContains(vs.Keys, key) {
|
||||
vs.Keys = append(vs.Keys, key)
|
||||
}
|
||||
vs.Mapping[key] = value
|
||||
}
|
||||
|
||||
// Range allows you to loop into the vars in its right order
|
||||
func (vs *Vars) Range(yield func(key string, value Var) error) error {
|
||||
if vs == nil {
|
||||
return nil
|
||||
}
|
||||
for _, k := range vs.Keys {
|
||||
if err := yield(k, vs.Mapping[k]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToCacheMap converts Vars to a map containing only the static
|
||||
// variables
|
||||
func (vs *Vars) ToCacheMap() (m map[string]interface{}) {
|
||||
m = make(map[string]interface{}, vs.Len())
|
||||
vs.Range(func(k string, v Var) error {
|
||||
if v.Sh != "" {
|
||||
// Dynamic variable is not yet resolved; trigger
|
||||
// <no value> to be used in templates.
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Live != nil {
|
||||
m[k] = v.Live
|
||||
} else {
|
||||
m[k] = v.Static
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns the size of the map
|
||||
func (vs *Vars) Len() int {
|
||||
if vs == nil {
|
||||
return 0
|
||||
}
|
||||
return len(vs.Keys)
|
||||
}
|
||||
|
||||
// Var represents either a static or dynamic variable.
|
||||
type Var struct {
|
||||
Static string
|
||||
Live interface{}
|
||||
Sh string
|
||||
Dir string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var str string
|
||||
if err := unmarshal(&str); err == nil {
|
||||
v.Static = str
|
||||
return nil
|
||||
}
|
||||
|
||||
var sh struct {
|
||||
Sh string
|
||||
}
|
||||
if err := unmarshal(&sh); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Sh = sh.Sh
|
||||
return nil
|
||||
}
|
||||
21
testdata/checksum/Taskfile.yml
vendored
21
testdata/checksum/Taskfile.yml
vendored
@@ -1,9 +1,12 @@
|
||||
build:
|
||||
cmds:
|
||||
- cp ./source.txt ./generated.txt
|
||||
sources:
|
||||
- ./**/glob-with-inexistent-file.txt
|
||||
- ./source.txt
|
||||
generates:
|
||||
- ./generated.txt
|
||||
method: checksum
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- cp ./source.txt ./generated.txt
|
||||
sources:
|
||||
- ./**/glob-with-inexistent-file.txt
|
||||
- ./source.txt
|
||||
generates:
|
||||
- ./generated.txt
|
||||
method: checksum
|
||||
|
||||
32
testdata/concurrency/Taskfile.yml
vendored
Normal file
32
testdata/concurrency/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps:
|
||||
- t1
|
||||
|
||||
t1:
|
||||
deps:
|
||||
- t3
|
||||
- t4
|
||||
cmds:
|
||||
- task: t2
|
||||
- echo done 1
|
||||
t2:
|
||||
deps:
|
||||
- t5
|
||||
- t6
|
||||
cmds:
|
||||
- echo done 2
|
||||
t3:
|
||||
cmds:
|
||||
- echo done 3
|
||||
t4:
|
||||
cmds:
|
||||
- echo done 4
|
||||
t5:
|
||||
cmds:
|
||||
- echo done 5
|
||||
t6:
|
||||
cmds:
|
||||
- echo done 6
|
||||
15
testdata/cyclic/Taskfile.yml
vendored
15
testdata/cyclic/Taskfile.yml
vendored
@@ -1,7 +1,10 @@
|
||||
task-1:
|
||||
deps:
|
||||
- task: task-2
|
||||
version: '3'
|
||||
|
||||
task-2:
|
||||
deps:
|
||||
- task: task-1
|
||||
tasks:
|
||||
task-1:
|
||||
deps:
|
||||
- task: task-2
|
||||
|
||||
task-2:
|
||||
deps:
|
||||
- task: task-1
|
||||
|
||||
12
testdata/deferred/Taskfile.yml
vendored
Normal file
12
testdata/deferred/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
task-1:
|
||||
- echo 'task-1 ran {{.PARAM}}'
|
||||
|
||||
task-2:
|
||||
- defer: { task: "task-1", vars: { PARAM: "successfully" } }
|
||||
- defer: echo 'echo ran'
|
||||
- defer: echo 'failing' && exit 2
|
||||
- echo 'cmd ran'
|
||||
- exit 1
|
||||
85
testdata/deps/Taskfile.yml
vendored
85
testdata/deps/Taskfile.yml
vendored
@@ -1,53 +1,56 @@
|
||||
default:
|
||||
deps: [d1, d2, d3]
|
||||
version: '3'
|
||||
|
||||
d1:
|
||||
deps: [d11, d12, d13]
|
||||
cmds:
|
||||
- echo 'Text' > d1.txt
|
||||
tasks:
|
||||
default:
|
||||
deps: [d1, d2, d3]
|
||||
|
||||
d2:
|
||||
deps: [d21, d22, d23]
|
||||
cmds:
|
||||
- echo 'Text' > d2.txt
|
||||
d1:
|
||||
deps: [d11, d12, d13]
|
||||
cmds:
|
||||
- echo 'Text' > d1.txt
|
||||
|
||||
d3:
|
||||
deps: [d31, d32, d33]
|
||||
cmds:
|
||||
- echo 'Text' > d3.txt
|
||||
d2:
|
||||
deps: [d21, d22, d23]
|
||||
cmds:
|
||||
- echo 'Text' > d2.txt
|
||||
|
||||
d11:
|
||||
cmds:
|
||||
- echo 'Text' > d11.txt
|
||||
d3:
|
||||
deps: [d31, d32, d33]
|
||||
cmds:
|
||||
- echo 'Text' > d3.txt
|
||||
|
||||
d12:
|
||||
cmds:
|
||||
- echo 'Text' > d12.txt
|
||||
d11:
|
||||
cmds:
|
||||
- echo 'Text' > d11.txt
|
||||
|
||||
d13:
|
||||
cmds:
|
||||
- echo 'Text' > d13.txt
|
||||
d12:
|
||||
cmds:
|
||||
- echo 'Text' > d12.txt
|
||||
|
||||
d21:
|
||||
cmds:
|
||||
- echo 'Text' > d21.txt
|
||||
d13:
|
||||
cmds:
|
||||
- echo 'Text' > d13.txt
|
||||
|
||||
d22:
|
||||
cmds:
|
||||
- echo 'Text' > d22.txt
|
||||
d21:
|
||||
cmds:
|
||||
- echo 'Text' > d21.txt
|
||||
|
||||
d23:
|
||||
cmds:
|
||||
- echo 'Text' > d23.txt
|
||||
d22:
|
||||
cmds:
|
||||
- echo 'Text' > d22.txt
|
||||
|
||||
d31:
|
||||
cmds:
|
||||
- echo 'Text' > d31.txt
|
||||
d23:
|
||||
cmds:
|
||||
- echo 'Text' > d23.txt
|
||||
|
||||
d32:
|
||||
cmds:
|
||||
- echo 'Text' > d32.txt
|
||||
d31:
|
||||
cmds:
|
||||
- echo 'Text' > d31.txt
|
||||
|
||||
d33:
|
||||
cmds:
|
||||
- echo 'Text' > d33.txt
|
||||
d32:
|
||||
cmds:
|
||||
- echo 'Text' > d32.txt
|
||||
|
||||
d33:
|
||||
cmds:
|
||||
- echo 'Text' > d33.txt
|
||||
|
||||
2
testdata/dir/Taskfile.yml
vendored
2
testdata/dir/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
whereami:
|
||||
|
||||
33
testdata/dir/dynamic_var/Taskfile.yml
vendored
Normal file
33
testdata/dir/dynamic_var/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
sub:
|
||||
taskfile: subdirectory
|
||||
dir: subdirectory
|
||||
|
||||
vars:
|
||||
DIRECTORY: subdirectory
|
||||
|
||||
tasks:
|
||||
default:
|
||||
- task: from-root-taskfile
|
||||
- task: sub:from-included-taskfile
|
||||
- task: sub:from-included-taskfile-task
|
||||
- task: from-interpolated-dir
|
||||
|
||||
from-root-taskfile:
|
||||
cmds:
|
||||
- echo '{{.TASK_DIR}}' > from_root_taskfile.txt
|
||||
dir: subdirectory
|
||||
vars:
|
||||
TASK_DIR:
|
||||
sh: basename $(pwd)
|
||||
silent: true
|
||||
|
||||
from-interpolated-dir:
|
||||
cmds:
|
||||
- echo '{{.INTERPOLATED_DIR}}' > from_interpolated_dir.txt
|
||||
dir: '{{.DIRECTORY}}'
|
||||
vars:
|
||||
INTERPOLATED_DIR:
|
||||
sh: basename $(pwd)
|
||||
19
testdata/dir/dynamic_var/subdirectory/Taskfile.yml
vendored
Normal file
19
testdata/dir/dynamic_var/subdirectory/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
TASKFILE_DIR:
|
||||
sh: basename $(pwd)
|
||||
|
||||
tasks:
|
||||
from-included-taskfile:
|
||||
cmds:
|
||||
- echo '{{.TASKFILE_DIR}}' > from_included_taskfile.txt
|
||||
silent: true
|
||||
|
||||
from-included-taskfile-task:
|
||||
cmds:
|
||||
- echo '{{.TASKFILE_TASK_DIR}}' > from_included_taskfile_task.txt
|
||||
silent: true
|
||||
vars:
|
||||
TASKFILE_TASK_DIR:
|
||||
sh: basename $(pwd)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user