mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
1034 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2abc13ce2 | ||
|
|
0f4621fb02 | ||
|
|
c6ff641f6d | ||
|
|
350f74a53d | ||
|
|
41cd7acc87 | ||
|
|
c6eea26660 | ||
|
|
61c5718663 | ||
|
|
9897f4b527 | ||
|
|
978a6e5ecb | ||
|
|
a018997ddc | ||
|
|
de09843467 | ||
|
|
78a57fdb4b | ||
|
|
0bc2fd72f0 | ||
|
|
dda5164efd | ||
|
|
3df2396b63 | ||
|
|
d3da84e724 | ||
|
|
eb61015477 | ||
|
|
40c644f006 | ||
|
|
c9aa0180a8 | ||
|
|
a06e46885d | ||
|
|
60fa6e6c0a | ||
|
|
2f18f7927d | ||
|
|
292cf75836 | ||
|
|
fc95061f4c | ||
|
|
1f1275255c | ||
|
|
d8555e5a5d | ||
|
|
b323531dd5 | ||
|
|
cfb665310e | ||
|
|
51c6ebcd4d | ||
|
|
e94d1b6b9f | ||
|
|
ca7b32105d | ||
|
|
264db2737b | ||
|
|
5f2c9a6e45 | ||
|
|
19be1f1bf0 | ||
|
|
7cdf0000d9 | ||
|
|
13606e5e00 | ||
|
|
35af240faa | ||
|
|
0ac56f8973 | ||
|
|
6e5f8b1fb0 | ||
|
|
15e831c0b0 | ||
|
|
248952bc8f | ||
|
|
2373743eac | ||
|
|
f119596be6 | ||
|
|
b7cb41b388 | ||
|
|
a65ee26446 | ||
|
|
d3e2fbf1e2 | ||
|
|
66748ab5e5 | ||
|
|
c73a2c8f84 | ||
|
|
4bbcd99b8b | ||
|
|
02e7ff27c7 | ||
|
|
7ed3cea40b | ||
|
|
74f5cf8f29 | ||
|
|
086d13ca2f | ||
|
|
2780e96179 | ||
|
|
191678f9d6 | ||
|
|
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 | ||
|
|
5c420f3a34 | ||
|
|
393712ead2 | ||
|
|
d3060b0060 | ||
|
|
14d7f04a81 | ||
|
|
1a28e5e0d4 | ||
|
|
884cd0d636 | ||
|
|
6a7a3c0ae8 | ||
|
|
948e6bd57c | ||
|
|
78595fba0b | ||
|
|
8020284b12 | ||
|
|
d6a49da870 | ||
|
|
84da80356d | ||
|
|
bcbb85eac3 | ||
|
|
0e1d8a72e6 | ||
|
|
bbdd698869 | ||
|
|
bae1e1ee9f | ||
|
|
7138785500 | ||
|
|
8f684ffa6d | ||
|
|
9be3666fe7 | ||
|
|
b7785678f4 | ||
|
|
d8005b4cf6 | ||
|
|
52028fc3bc | ||
|
|
5285ec23ae | ||
|
|
3c882e5c57 | ||
|
|
1a33f9168b | ||
|
|
ccae3d7383 | ||
|
|
847651a90a | ||
|
|
1b8998e7a2 | ||
|
|
dc8fb79759 | ||
|
|
6b0935d6cf | ||
|
|
d1183ce272 | ||
|
|
a1aec8178a | ||
|
|
ad569a8a36 | ||
|
|
0d9fdbaac1 | ||
|
|
f5cd3eab9e | ||
|
|
cb6fe4bb59 | ||
|
|
db36bc67f1 | ||
|
|
e0f72a6193 | ||
|
|
1ee684b7c0 | ||
|
|
93005512b4 | ||
|
|
8987cd64a0 | ||
|
|
fac51dcf03 | ||
|
|
01101a4c9b | ||
|
|
d561e40817 | ||
|
|
b8094fd771 | ||
|
|
af5d9c952d | ||
|
|
ce4e187cbc | ||
|
|
821c80b61e | ||
|
|
5a6fb7c973 | ||
|
|
0cb298ebdf | ||
|
|
0f385f9f4e | ||
|
|
a149368725 | ||
|
|
afeefe8259 | ||
|
|
690d3c27a2 | ||
|
|
3d56ea5ce5 | ||
|
|
fdff7f80a3 | ||
|
|
fe6978b107 | ||
|
|
57db6865d2 | ||
|
|
d235d5ab28 | ||
|
|
c47c15ee47 | ||
|
|
613dfe06d3 | ||
|
|
6803ad2e59 | ||
|
|
d5a791b470 | ||
|
|
a312d61d68 | ||
|
|
e414c1f7b0 | ||
|
|
955359b073 | ||
|
|
26e0c0887a | ||
|
|
4c295b564a | ||
|
|
2eb52da0db | ||
|
|
d8bfb3ab13 | ||
|
|
d970e93507 | ||
|
|
e6255081a8 | ||
|
|
623db0ed94 | ||
|
|
0e575e9c25 | ||
|
|
fb23ba9878 | ||
|
|
4e09fc7f43 | ||
|
|
64cfdd815f | ||
|
|
f6f31e0a8d | ||
|
|
bd5fb9be03 | ||
|
|
762714de68 | ||
|
|
82a3651a18 | ||
|
|
dd9cdb0ec9 | ||
|
|
7f082a821d | ||
|
|
abe0352de9 | ||
|
|
4cee4aa5a8 | ||
|
|
9c68c7c50b | ||
|
|
0608782cfa | ||
|
|
edeaf3794a | ||
|
|
fe2b8c8afa | ||
|
|
b66bf58064 | ||
|
|
957dfa9cdf | ||
|
|
cc9264854e | ||
|
|
d1463b3e24 | ||
|
|
f1082520e1 | ||
|
|
733c563194 | ||
|
|
0200d043c3 | ||
|
|
9c475c36e7 | ||
|
|
c663c5c507 | ||
|
|
1e93c38307 | ||
|
|
81baf808c9 | ||
|
|
74537689dc | ||
|
|
12ab01d5e6 | ||
|
|
044d3a0ff9 | ||
|
|
659cae6a4c | ||
|
|
8efc38ad82 | ||
|
|
bd5882f0f0 | ||
|
|
6ff9ba9df9 | ||
|
|
b2df398a12 | ||
|
|
83d618e1eb | ||
|
|
f0768b3af1 | ||
|
|
0233ce52ed | ||
|
|
6e6f337509 | ||
|
|
1546415b8f | ||
|
|
20725c69bf | ||
|
|
90613220c6 | ||
|
|
659fd2ae93 | ||
|
|
29d899f7da | ||
|
|
902a0a01a9 | ||
|
|
8001fb3915 | ||
|
|
e81e2802f0 | ||
|
|
1ee066ec42 | ||
|
|
53d54d1c4a | ||
|
|
10082b60b8 | ||
|
|
c5b9773922 | ||
|
|
de11323d28 | ||
|
|
9f269e1a95 | ||
|
|
e4204168a0 | ||
|
|
9c350f8ef1 | ||
|
|
db19fdac29 | ||
|
|
d516b238b1 | ||
|
|
f9330f6cd9 | ||
|
|
360da29e1f | ||
|
|
9cfac1642a | ||
|
|
db90e87d10 | ||
|
|
b7564080bc | ||
|
|
1d783bf6c7 | ||
|
|
1025c2e3a1 | ||
|
|
4fd82ab222 | ||
|
|
8eadfc1bf6 | ||
|
|
f66edbad50 | ||
|
|
c7f17b5319 | ||
|
|
23c4adcef6 | ||
|
|
808542bed0 | ||
|
|
93bfd57856 | ||
|
|
7e7e1bccba | ||
|
|
34f6da86c3 | ||
|
|
15c0381c3c | ||
|
|
c2f4a57e02 | ||
|
|
f945cf2343 | ||
|
|
5bca3cfd71 | ||
|
|
26ce4e6886 | ||
|
|
f5f0e0c376 | ||
|
|
9dea1e7f3e | ||
|
|
c2e0f8c81f | ||
|
|
d341bc25ce | ||
|
|
0379e2b51b | ||
|
|
e79026b840 | ||
|
|
fc34d6b56f | ||
|
|
2a1571a99e | ||
|
|
c158608255 | ||
|
|
3ca590b185 | ||
|
|
3f8ee21849 | ||
|
|
845b88a193 | ||
|
|
e252972c7f | ||
|
|
a9012ebfc5 | ||
|
|
5cfd9bbbbd | ||
|
|
c82a7240bb | ||
|
|
a4a20d92a4 | ||
|
|
890996f595 | ||
|
|
474f27c6d3 | ||
|
|
33f3894372 | ||
|
|
24436ac76e | ||
|
|
3ee66ef705 | ||
|
|
a1765e1d33 | ||
|
|
765e3dbf72 | ||
|
|
80f5cee599 | ||
|
|
4dcb124693 | ||
|
|
31ecf167cc | ||
|
|
3999480d64 | ||
|
|
9dbb503c23 | ||
|
|
a98f803d87 | ||
|
|
9e9ffeb5d5 | ||
|
|
33d4ad4d84 | ||
|
|
d05d418c4c | ||
|
|
06d0af7a1d | ||
|
|
9a3b726068 | ||
|
|
2676ab9a59 | ||
|
|
a1837d553e | ||
|
|
fdbc130d8d | ||
|
|
4b3cea3812 | ||
|
|
1c3082ffa6 | ||
|
|
0446cfdba0 | ||
|
|
db1d3183b6 | ||
|
|
fb666394fc | ||
|
|
1054c89a9d | ||
|
|
8dd87dc482 | ||
|
|
b2edbf05a1 | ||
|
|
6fb53a406b | ||
|
|
b05fa0821d | ||
|
|
0a808b1212 | ||
|
|
f1d83e92a7 | ||
|
|
31b60f7f60 | ||
|
|
c0f9af5daa | ||
|
|
b25a9e8884 | ||
|
|
3c0cf3cd55 | ||
|
|
1ac6f17e6a | ||
|
|
399a2b38f3 | ||
|
|
b97221cdb2 | ||
|
|
0164bc21ea | ||
|
|
5a23250d32 | ||
|
|
80d88d9789 | ||
|
|
31ead854c7 | ||
|
|
4b64fcb8a4 | ||
|
|
a951f2403d | ||
|
|
f9adeba7f1 | ||
|
|
5c823d51d0 | ||
|
|
9be7521b83 | ||
|
|
c73ddc3552 | ||
|
|
4b7f058f41 | ||
|
|
07221a1b20 | ||
|
|
13614fb3c4 | ||
|
|
4fa983bde7 | ||
|
|
9cb1db8c0a | ||
|
|
5738436d55 | ||
|
|
5e49b38c33 | ||
|
|
0c94adaff9 | ||
|
|
f8a6c5d06c | ||
|
|
21e66c7c02 | ||
|
|
902f0d3ac4 | ||
|
|
713ecd35f6 | ||
|
|
27b35157cd | ||
|
|
f8fb639870 | ||
|
|
14f41ae619 | ||
|
|
a026d72924 | ||
|
|
2cb070f5b3 | ||
|
|
1dec956e99 | ||
|
|
310394aa60 | ||
|
|
468ff18243 | ||
|
|
44a63580f0 | ||
|
|
4ac1fa43aa | ||
|
|
6f992a3cf7 | ||
|
|
fd4ce656d5 | ||
|
|
9ed2dca427 | ||
|
|
dfb804fe3f | ||
|
|
4f2a84b426 | ||
|
|
14a127b6b3 | ||
|
|
06000533fb | ||
|
|
7722aba403 | ||
|
|
4817d8c67f | ||
|
|
9a062d90d1 | ||
|
|
959eb45373 | ||
|
|
a42f2af9eb | ||
|
|
4ddad68212 | ||
|
|
aac6c5a1c7 | ||
|
|
5572e31fd4 | ||
|
|
233b8bf81a | ||
|
|
2ae3810f80 | ||
|
|
736165876c | ||
|
|
61b3fca9a3 | ||
|
|
469863b7b3 | ||
|
|
5238bc55fd | ||
|
|
57a01aa6ff | ||
|
|
9361dbc39e | ||
|
|
11d257cb26 | ||
|
|
a928ab75e3 | ||
|
|
55a240c82e | ||
|
|
f8aedf438b | ||
|
|
9f1bb9a42e | ||
|
|
0ed7274610 | ||
|
|
df032b09a7 | ||
|
|
7dba742e4c | ||
|
|
83dbe78965 | ||
|
|
95b75c5330 | ||
|
|
780bd08490 | ||
|
|
a9b1f38a7c | ||
|
|
4ed4ad9852 | ||
|
|
81f172315c | ||
|
|
54666221a4 | ||
|
|
5327d702f8 | ||
|
|
a701ea3007 | ||
|
|
f0fb7d9661 | ||
|
|
3cbc89769d | ||
|
|
cb95200be3 | ||
|
|
a52f6c0acf | ||
|
|
77f9e3dd41 | ||
|
|
259d3e2df1 | ||
|
|
6eee5421b0 | ||
|
|
8004e9c943 | ||
|
|
2ab8511f45 | ||
|
|
7514ff53c9 | ||
|
|
309cfb1499 | ||
|
|
a567f7ed20 | ||
|
|
5720936247 | ||
|
|
5d9de14ca3 | ||
|
|
f519f56078 | ||
|
|
5eb1a1f7f5 | ||
|
|
5a28560177 | ||
|
|
db280adf55 | ||
|
|
b77fcd6c8a | ||
|
|
b5b2649283 | ||
|
|
318f9b216d | ||
|
|
61247a0b2a | ||
|
|
849a418273 | ||
|
|
2e63a62e08 | ||
|
|
6ccf1f2a3c | ||
|
|
e298256b82 | ||
|
|
787e5b2e29 | ||
|
|
4aa1e8b093 | ||
|
|
9ee224c36b | ||
|
|
08263c0597 | ||
|
|
347fe87229 | ||
|
|
b65a0a3a8d | ||
|
|
9a5a1e2253 | ||
|
|
687b4ec837 | ||
|
|
8bdf5c554d | ||
|
|
f4a18e531f | ||
|
|
df951a0c7c | ||
|
|
a6cac2691b | ||
|
|
a9f5179066 | ||
|
|
687e2699cf | ||
|
|
491da0ceb9 | ||
|
|
1bac40bc58 | ||
|
|
fb9061480d | ||
|
|
a04cf100b4 | ||
|
|
76253bf516 | ||
|
|
feaf70922d | ||
|
|
c70343a5bc | ||
|
|
550c116aea | ||
|
|
a5f31a4280 | ||
|
|
27fc4c4ca8 | ||
|
|
90a5f17f58 | ||
|
|
108cb91d95 | ||
|
|
00a0755ff3 | ||
|
|
3f7e8c88eb | ||
|
|
1c7ca94d49 | ||
|
|
31273cd6ff | ||
|
|
14e39dd745 | ||
|
|
cc6f7b6088 | ||
|
|
da1b0c9558 | ||
|
|
9f294b4d10 | ||
|
|
13f60bae41 | ||
|
|
67105b332f | ||
|
|
18961e3d07 | ||
|
|
fe31f5050d | ||
|
|
ab8549adea | ||
|
|
05600601ff | ||
|
|
c541356289 | ||
|
|
467c4360ca | ||
|
|
3b152a38b0 | ||
|
|
f4d3855528 | ||
|
|
09eab770a7 | ||
|
|
102f8ab74e | ||
|
|
a830dba5da | ||
|
|
3f13a50ca3 | ||
|
|
7c456f2ab9 | ||
|
|
bae95cd6f6 | ||
|
|
bbd6e443b0 | ||
|
|
db0d847e03 | ||
|
|
0afb453fed | ||
|
|
dbc79b4311 | ||
|
|
ac6008c33c | ||
|
|
e540e752f2 | ||
|
|
cdbe821eb8 | ||
|
|
6be994f1ca | ||
|
|
a407b0a8eb | ||
|
|
051ff35878 | ||
|
|
8b3c34c308 | ||
|
|
2cb2668803 | ||
|
|
4dccdb95b9 | ||
|
|
96db9a9410 | ||
|
|
0cd34bbebc | ||
|
|
15f50c0e58 | ||
|
|
7fca9732e7 | ||
|
|
0ea8c3ed28 | ||
|
|
0af9600e92 | ||
|
|
328e3725e5 | ||
|
|
2183e1e9f5 | ||
|
|
120d0be84c | ||
|
|
5649f75a8d | ||
|
|
a209f7d6be | ||
|
|
d48a2f3ccf | ||
|
|
c1ae36866e | ||
|
|
4f368923a5 | ||
|
|
7c02097d93 | ||
|
|
51998f706f | ||
|
|
1a3df08aca | ||
|
|
975f262ac0 | ||
|
|
1cb4a3b8d5 | ||
|
|
35f4b2f686 | ||
|
|
407ec91ca7 | ||
|
|
12c0d18932 | ||
|
|
2d4ca37226 | ||
|
|
afe6744e97 | ||
|
|
19d4b8b7f7 | ||
|
|
3556942516 | ||
|
|
87a200e42c | ||
|
|
152fc0ad38 | ||
|
|
3212ae4713 | ||
|
|
040cef1479 | ||
|
|
f5f70d7a75 | ||
|
|
42509cf2f5 | ||
|
|
6f74c2d823 | ||
|
|
e23a6dc9f1 | ||
|
|
134c6b79c4 | ||
|
|
00ff1447ee | ||
|
|
78f6cb08d8 | ||
|
|
dfd890c8a6 | ||
|
|
7457b3668b | ||
|
|
71e7cd5808 | ||
|
|
57e42af238 | ||
|
|
e065dcb816 | ||
|
|
9619c7f54d | ||
|
|
2508bed363 | ||
|
|
c469632ee0 | ||
|
|
44a52359dc | ||
|
|
2022551b26 | ||
|
|
baac067a1a | ||
|
|
f4216dd67f | ||
|
|
60186bdcd5 | ||
|
|
2c2eb1684b | ||
|
|
33b167215d | ||
|
|
c53db134c6 | ||
|
|
0513a21e25 | ||
|
|
2fc32414f5 | ||
|
|
309bc4ee4c | ||
|
|
7977e6fb16 | ||
|
|
abb19dfbf8 | ||
|
|
14676dc3f8 | ||
|
|
c16f8a4d46 | ||
|
|
48bf09da21 | ||
|
|
c295a1998a | ||
|
|
95f7b9443f | ||
|
|
0160f5dd30 | ||
|
|
f3097845b4 | ||
|
|
5e72de4ba2 | ||
|
|
7a64530e83 | ||
|
|
36f3be9979 | ||
|
|
451b965fb0 | ||
|
|
2b2852aad7 | ||
|
|
72bfd94329 | ||
|
|
300376b0b1 | ||
|
|
e67792177d | ||
|
|
0f24fd593e | ||
|
|
23ec6c721d | ||
|
|
26761e5445 | ||
|
|
e78e4e6a2e | ||
|
|
e765b7a9c4 | ||
|
|
c210e34ce3 | ||
|
|
ddd063f29e | ||
|
|
a2c96e9cdd | ||
|
|
f2416d68b8 | ||
|
|
1eccb61d44 | ||
|
|
cf2b189fbd | ||
|
|
394b69676a | ||
|
|
9704dc5734 | ||
|
|
f54dde4f78 | ||
|
|
70ef9fbcfe | ||
|
|
bb1aff84cf | ||
|
|
dc1ec77da5 | ||
|
|
7077b20a54 | ||
|
|
09e84c2583 | ||
|
|
e3ac6f9e01 | ||
|
|
f91bbe9397 | ||
|
|
31faf05c3a | ||
|
|
55672410cd | ||
|
|
8a66857fb9 | ||
|
|
d0b37df615 | ||
|
|
dc6cb68327 | ||
|
|
72250b32d3 | ||
|
|
726130be09 | ||
|
|
968a29d869 | ||
|
|
ce27e973be | ||
|
|
2607866c49 | ||
|
|
e8e914b11c | ||
|
|
d22b3b0d88 | ||
|
|
5ece1d74f6 | ||
|
|
ac7ab42d94 | ||
|
|
38a3f538f5 | ||
|
|
998935ea55 | ||
|
|
e781ac2512 | ||
|
|
ef2695974d | ||
|
|
b552cc2b12 | ||
|
|
4ae9c2445d | ||
|
|
769e25f080 | ||
|
|
86a23849e0 | ||
|
|
a586fef414 | ||
|
|
a548c63a92 | ||
|
|
5268df6bfd | ||
|
|
82e1c0f810 | ||
|
|
81b0ffb7f4 | ||
|
|
0da130ee2c | ||
|
|
6e880c9027 | ||
|
|
082fa321cb | ||
|
|
ff1c49f111 | ||
|
|
50f592c540 | ||
|
|
7a7f66dfdc | ||
|
|
a1140aa62f | ||
|
|
2dd3564da1 | ||
|
|
fb4b0a187e | ||
|
|
ac48ee066e | ||
|
|
06031efc09 | ||
|
|
9bea80b862 | ||
|
|
92ecb1c7ec | ||
|
|
645f77b849 | ||
|
|
2f9381065d | ||
|
|
774ef61c2f | ||
|
|
dd2c66d2e1 | ||
|
|
0deb2d78fb | ||
|
|
fdd7e7f2a8 | ||
|
|
ad1a440576 | ||
|
|
222b5cb587 | ||
|
|
a1d1f73fe7 | ||
|
|
e7f9ace559 | ||
|
|
cb72c404f5 | ||
|
|
01b9bf5289 | ||
|
|
a52a66ec1c | ||
|
|
313d7089da | ||
|
|
06d80e92eb | ||
|
|
e1fc3aa4fb | ||
|
|
b8fe8d465e | ||
|
|
196d3cb13d | ||
|
|
a3bfa13670 | ||
|
|
2ace0defd0 | ||
|
|
023a902f61 | ||
|
|
789a4c03df | ||
|
|
ecfd8e8a62 | ||
|
|
9ba44f3e6e | ||
|
|
03fd5c84ec | ||
|
|
81e0f170ef | ||
|
|
7e06ba1728 | ||
|
|
f8a5825083 | ||
|
|
a14f7f215c | ||
|
|
8393e8c52f | ||
|
|
81d221667b | ||
|
|
4f928e7570 | ||
|
|
08622ba8cb | ||
|
|
685b9ae293 | ||
|
|
e97fd65cd3 | ||
|
|
ba494702ed | ||
|
|
ad3f439cb5 | ||
|
|
067d6e6a02 | ||
|
|
540e458b16 | ||
|
|
b530cba0d5 | ||
|
|
09e6d5269d | ||
|
|
f98bf6c4b1 | ||
|
|
c40148a52e | ||
|
|
460297e43a | ||
|
|
561349c820 | ||
|
|
398a2c519c | ||
|
|
2615000609 | ||
|
|
83f1b213fa | ||
|
|
c9a64fedd7 | ||
|
|
504723bc19 | ||
|
|
b590e74ce6 | ||
|
|
353e4c4f48 | ||
|
|
2a2dfce137 | ||
|
|
86e0496555 | ||
|
|
bb84b067c5 | ||
|
|
b269c6e162 | ||
|
|
8b76911675 | ||
|
|
7e5cfefede | ||
|
|
9beef8b99b | ||
|
|
1386018c1c | ||
|
|
70cb1af80e | ||
|
|
3c63a2b3cc | ||
|
|
7077b0ce65 | ||
|
|
ede2ffab60 | ||
|
|
70fa93d0ff | ||
|
|
25134279f4 | ||
|
|
6bc27baa96 | ||
|
|
e327dab695 | ||
|
|
35f89afdf5 | ||
|
|
22c314f2cc | ||
|
|
011f96bd6a | ||
|
|
add59989c5 | ||
|
|
0d84549b2a | ||
|
|
c1f9f73184 | ||
|
|
c105294f61 | ||
|
|
0b1a89d456 | ||
|
|
c591ea4185 | ||
|
|
2ec6b03022 | ||
|
|
109f20f193 | ||
|
|
822f7f83ee | ||
|
|
947b3de0c4 | ||
|
|
18e668f4ac | ||
|
|
24e7aa01c7 | ||
|
|
72a6727e31 | ||
|
|
36614dccf8 | ||
|
|
59306cda38 | ||
|
|
0db3e9a05d | ||
|
|
c2773a7287 | ||
|
|
e28b0bc646 | ||
|
|
edd338097e | ||
|
|
16a11a60a5 | ||
|
|
1cabfc636f | ||
|
|
2054a1bc34 | ||
|
|
e4c1cc3e77 | ||
|
|
06633e2f99 | ||
|
|
81f0e93bdb | ||
|
|
1296d39421 | ||
|
|
ce823ad510 | ||
|
|
419a74b018 | ||
|
|
608d65f2b7 | ||
|
|
94c33668bd | ||
|
|
40b96ced43 | ||
|
|
8c5e7e89cd | ||
|
|
678ea86350 | ||
|
|
ff2a442f93 | ||
|
|
12babf4204 |
@@ -7,8 +7,7 @@ insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[*.{md,yml,yaml,json,toml}]
|
||||
[*.{md,yml,yaml,json,toml,htm,html}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
46
.github/CODE_OF_CONDUCT.md
vendored
Normal file
46
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at andrey.nering@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
6
.github/CONTRIBUTING.md
vendored
Normal file
6
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
* Bug reports and feature requests are welcome in [the issues][issues]
|
||||
* Pull Requests are welcome. For more complex changes and features it's
|
||||
recommended to open an issue with the feature request first
|
||||
* Documentation contributions are as important as code contributions
|
||||
|
||||
[issues]: https://github.com/go-task/task/issues
|
||||
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
open_collective: task
|
||||
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=USD&source=url'
|
||||
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.18.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
30
.github/workflows/test.yml
vendored
Normal file
30
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Test
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.17.x, 1.18.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
- name: Set up Go ${{matrix.go-version}}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
|
||||
- name: Build
|
||||
run: go build -o ./bin/task -v ./cmd/task
|
||||
|
||||
- name: Test
|
||||
run: ./bin/task test --output=group --output-group-begin='::group::{{.TASK}}' --output-group-end='::endgroup::'
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -14,4 +14,18 @@
|
||||
.glide/
|
||||
|
||||
./task
|
||||
.task
|
||||
dist/
|
||||
|
||||
.DS_Store
|
||||
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# exuberant ctags
|
||||
tags
|
||||
|
||||
/bin
|
||||
/testdata/vars/v1
|
||||
/tmp
|
||||
|
||||
80
.goreleaser.yml
Normal file
80
.goreleaser.yml
Normal file
@@ -0,0 +1,80 @@
|
||||
build:
|
||||
binary: task
|
||||
main: ./cmd/task
|
||||
goos:
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
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
|
||||
|
||||
release:
|
||||
draft: true
|
||||
|
||||
snapshot:
|
||||
name_template: "{{.Tag}}"
|
||||
|
||||
checksum:
|
||||
name_template: "task_checksums.txt"
|
||||
|
||||
nfpms:
|
||||
- vendor: Task
|
||||
homepage: https://github.com/go-task/task
|
||||
maintainer: Andrey Nering <andrey.nering@gmail.com>
|
||||
description: Simple task runner written in Go
|
||||
license: MIT
|
||||
conflicts:
|
||||
- taskwarrior
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||
contents:
|
||||
- src: completion/bash/task.bash
|
||||
dst: /etc/bash_completion.d/task
|
||||
- src: completion/fish/task.fish
|
||||
dst: /usr/share/fish/completions/task.fish
|
||||
- src: completion/zsh/_task
|
||||
dst: /usr/local/share/zsh/site-functions/_task
|
||||
|
||||
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"
|
||||
497
CHANGELOG.md
Normal file
497
CHANGELOG.md
Normal file
@@ -0,0 +1,497 @@
|
||||
# Changelog
|
||||
|
||||
## v3.12.0 - 2022-03-31
|
||||
|
||||
- The `--list` and `--list-all` flags can now be combined with the `--silent`
|
||||
flag to print the task names only, without their description
|
||||
([#691](https://github.com/go-task/task/pull/691)).
|
||||
- Added support for multi-level inclusion of Taskfiles. This means that
|
||||
included Taskfiles can also include other Taskfiles. Before this was limited
|
||||
to one level
|
||||
([#390](https://github.com/go-task/task/issues/390), [#623](https://github.com/go-task/task/discussions/623), [#656](https://github.com/go-task/task/pull/656)).
|
||||
- Add ability to specify vars when including a Taskfile.
|
||||
[Check out the documentation](https://taskfile.dev/#/usage?id=vars-of-included-taskfiles)
|
||||
for more information.
|
||||
([#677](https://github.com/go-task/task/pull/677)).
|
||||
|
||||
## v3.11.0 - 2022-02-19
|
||||
|
||||
- Task now supports printing begin and end messages when using the `group`
|
||||
output mode, useful for grouping tasks in CI systems.
|
||||
[Check out the documentation](http://taskfile.dev/#/usage?id=output-syntax) for more information
|
||||
([#647](https://github.com/go-task/task/issues/647), [#651](https://github.com/go-task/task/pull/651)).
|
||||
- Add `Taskfile.dist.yml` and `Taskfile.dist.yaml` to the supported file
|
||||
name list. [Check out the documentation](https://taskfile.dev/#/usage?id=supported-file-names) for more information
|
||||
([#498](https://github.com/go-task/task/issues/498), [#666](https://github.com/go-task/task/pull/666)).
|
||||
|
||||
## 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
|
||||
([#229](https://github.com/go-task/task/issues/229), [#243](https://github.com/go-task/task/issues/234)).
|
||||
- A task with `method: checksum` will now re-run if generated files are deleted
|
||||
([#228](https://github.com/go-task/task/pull/228), [#238](https://github.com/go-task/task/issues/238)).
|
||||
|
||||
## v2.6.0 - 2019-07-21
|
||||
|
||||
- Fixed some bugs regarding minor version checks on `version:`.
|
||||
- Add `preconditions:` to task
|
||||
([#205](https://github.com/go-task/task/pull/205)).
|
||||
- Create directory informed on `dir:` if it doesn't exist
|
||||
([#209](https://github.com/go-task/task/issues/209), [#211](https://github.com/go-task/task/pull/211)).
|
||||
- We now have a `--taskfile` flag (alias `-t`), which can be used to run
|
||||
another Taskfile (other than the default `Taskfile.yml`)
|
||||
([#221](https://github.com/go-task/task/pull/221)).
|
||||
- It's now possible to install Task using Homebrew on Linux
|
||||
([go-task/homebrew-tap#1](https://github.com/go-task/homebrew-tap/pull/1)).
|
||||
|
||||
## v2.5.2 - 2019-05-11
|
||||
|
||||
- Reverted YAML upgrade due issues with CRLF on Windows
|
||||
([#201](https://github.com/go-task/task/issues/201), [go-yaml/yaml#450](https://github.com/go-yaml/yaml/issues/450)).
|
||||
- Allow setting global variables through the CLI
|
||||
([#192](https://github.com/go-task/task/issues/192)).
|
||||
|
||||
## 2.5.1 - 2019-04-27
|
||||
|
||||
- Fixed some issues with interactive command line tools, where sometimes
|
||||
the output were not being shown, and similar issues
|
||||
([#114](https://github.com/go-task/task/issues/114), [#190](https://github.com/go-task/task/issues/190), [#200](https://github.com/go-task/task/pull/200)).
|
||||
- Upgraded [go-yaml/yaml](https://github.com/go-yaml/yaml) from v2 to v3.
|
||||
|
||||
## v2.5.0 - 2019-03-16
|
||||
|
||||
- We moved from the taskfile.org domain to the new fancy taskfile.dev domain.
|
||||
While stuff is being redirected, we strongly recommend to everyone that use
|
||||
[this install script](https://taskfile.dev/#/installation?id=install-script)
|
||||
to use the new taskfile.dev domain on scripts from now on.
|
||||
- Fixed to the ZSH completion
|
||||
([#182](https://github.com/go-task/task/pull/182)).
|
||||
- Add [`--summary` flag along with `summary:` task attribute](https://taskfile.org/#/usage?id=display-summary-of-task)
|
||||
([#180](https://github.com/go-task/task/pull/180)).
|
||||
|
||||
## v2.4.0 - 2019-02-21
|
||||
|
||||
- Allow calling a task of the root Taskfile from an included Taskfile
|
||||
by prefixing it with `:`
|
||||
([#161](https://github.com/go-task/task/issues/161), [#172](https://github.com/go-task/task/issues/172)),
|
||||
- Add flag to override the `output` option
|
||||
([#173](https://github.com/go-task/task/pull/173));
|
||||
- Fix bug where Task was persisting the new checksum on the disk when the Dry
|
||||
Mode is enabled
|
||||
([#166](https://github.com/go-task/task/issues/166));
|
||||
- Fix file timestamp issue when the file name has spaces
|
||||
([#176](https://github.com/go-task/task/issues/176));
|
||||
- Mitigating path expanding issues on Windows
|
||||
([#170](https://github.com/go-task/task/pull/170)).
|
||||
|
||||
## v2.3.0 - 2019-01-02
|
||||
|
||||
- On Windows, Task can now be installed using [Scoop](https://scoop.sh/)
|
||||
([#152](https://github.com/go-task/task/pull/152));
|
||||
- Fixed issue with file/directory globing
|
||||
([#153](https://github.com/go-task/task/issues/153));
|
||||
- Added ability to globally set environment variables
|
||||
(
|
||||
[#138](https://github.com/go-task/task/pull/138),
|
||||
[#159](https://github.com/go-task/task/pull/159)
|
||||
).
|
||||
|
||||
## v2.2.1 - 2018-12-09
|
||||
|
||||
- This repository now uses Go Modules (#143). We'll still keep the `vendor` directory in sync for some time, though;
|
||||
- Fixing a bug when the Taskfile has no tasks but includes another Taskfile (#150);
|
||||
- Fix a bug when calling another task or a dependency in an included Taskfile (#151).
|
||||
|
||||
## v2.2.0 - 2018-10-25
|
||||
|
||||
- Added support for [including other Taskfiles](https://taskfile.org/#/usage?id=including-other-taskfiles) (#98)
|
||||
- This should be considered experimental. For now, only including local files is supported, but support for including remote Taskfiles is being discussed. If you have any feedback, please comment on #98.
|
||||
- Task now have a dedicated documentation site: https://taskfile.org
|
||||
- Thanks to [Docsify](https://docsify.js.org/) for making this pretty easy. To check the source code, just take a look at the [docs](https://github.com/go-task/task/tree/master/docs) directory of this repository. Contributions to the documentation is really appreciated.
|
||||
|
||||
## v2.1.1 - 2018-09-17
|
||||
|
||||
- Fix suggestion to use `task --init` not being shown anymore (when a `Taskfile.yml` is not found)
|
||||
- Fix error when using checksum method and no file exists for a source glob (#131)
|
||||
- Fix signal handling when the `--watch` flag is given (#132)
|
||||
|
||||
## v2.1.0 - 2018-08-19
|
||||
|
||||
- Add a `ignore_error` option to task and command (#123)
|
||||
- Add a dry run mode (`--dry` flag) (#126)
|
||||
|
||||
## v2.0.3 - 2018-06-24
|
||||
|
||||
- Expand environment variables on "dir", "sources" and "generates" (#116)
|
||||
- Fix YAML merging syntax (#112)
|
||||
- Add ZSH completion (#111)
|
||||
- Implement new `output` option. Please check out the [documentation](https://github.com/go-task/task#output-syntax)
|
||||
|
||||
## v2.0.2 - 2018-05-01
|
||||
|
||||
- Fix merging of YAML anchors (#112)
|
||||
|
||||
## v2.0.1 - 2018-03-11
|
||||
|
||||
- Fixes panic on `task --list`
|
||||
|
||||
## v2.0.0 - 2018-03-08
|
||||
|
||||
Version 2.0.0 is here, with a new Taskfile format.
|
||||
|
||||
Please, make sure to read the [Taskfile versions](https://github.com/go-task/task/blob/master/TASKFILE_VERSIONS.md) document, since it describes in depth what changed for this version.
|
||||
|
||||
* New Taskfile version 2 (https://github.com/go-task/task/issues/77)
|
||||
* Possibility to have global variables in the `Taskfile.yml` instead of `Taskvars.yml` (https://github.com/go-task/task/issues/66)
|
||||
* Small improvements and fixes
|
||||
|
||||
## v1.4.4 - 2017-11-19
|
||||
|
||||
- Handle SIGINT and SIGTERM (#75);
|
||||
- List: print message with there's no task with description;
|
||||
- Expand home dir ("~" symbol) on paths (#74);
|
||||
- Add Snap as an installation method;
|
||||
- Move examples to its own repo;
|
||||
- Watch: also walk on tasks called on on "cmds", and not only on "deps";
|
||||
- Print logs to stderr instead of stdout (#68);
|
||||
- Remove deprecated `set` keyword;
|
||||
- Add checksum based status check, alternative to timestamp based.
|
||||
|
||||
## v1.4.3 - 2017-09-07
|
||||
|
||||
- Allow assigning variables to tasks at run time via CLI (#33)
|
||||
- Added suport for multiline variables from sh (#64)
|
||||
- Fixes env: remove square braces and evaluate shell (#62)
|
||||
- Watch: change watch library and few fixes and improvements
|
||||
- When use watching, cancel and restart long running process on file change (#59 and #60)
|
||||
|
||||
## v1.4.2 - 2017-07-30
|
||||
|
||||
- Flag to set directory of execution
|
||||
- Always echo command if is verbose mode
|
||||
- Add silent mode to disable echoing of commands
|
||||
- Fixes and improvements of variables (#56)
|
||||
|
||||
## v1.4.1 - 2017-07-15
|
||||
|
||||
- Allow use of YAML for dynamic variables instead of $ prefix
|
||||
- `VAR: {sh: echo Hello}` instead of `VAR: $echo Hello`
|
||||
- Add `--list` (or `-l`) flag to print existing tasks
|
||||
- OS specific Taskvars file (e.g. `Taskvars_windows.yml`, `Taskvars_linux.yml`, etc)
|
||||
- Consider task up-to-date on equal timestamps (#49)
|
||||
- Allow absolute path in generates section (#48)
|
||||
- Bugfix: allow templating when calling deps (#42)
|
||||
- Fix panic for invalid task in cyclic dep detection
|
||||
- Better error output for dynamic variables in Taskvars.yml (#41)
|
||||
- Allow template evaluation in parameters
|
||||
|
||||
## v1.4.0 - 2017-07-06
|
||||
|
||||
- Cache dynamic variables
|
||||
- Add verbose mode (`-v` flag)
|
||||
- Support to task parameters (overriding vars) (#31) (#32)
|
||||
- Print command, also when "set:" is specified (#35)
|
||||
- Improve task command help text (#35)
|
||||
|
||||
## v1.3.1 - 2017-06-14
|
||||
|
||||
- Fix glob not working on commands (#28)
|
||||
- Add ExeExt template function
|
||||
- Add `--init` flag to create a new Taskfile
|
||||
- Add status option to prevent task from running (#27)
|
||||
- Allow interpolation on `generates` and `sources` attributes (#26)
|
||||
|
||||
## v1.3.0 - 2017-04-24
|
||||
|
||||
- Migrate from os/exec.Cmd to a native Go sh/bash interpreter
|
||||
- This is a potentially breaking change if you use Windows.
|
||||
- Now, `cmd` is not used anymore on Windows. Always use Bash-like syntax for your commands, even on Windows.
|
||||
- Add "ToSlash" and "FromSlash" to template functions
|
||||
- Use functions defined on github.com/Masterminds/sprig
|
||||
- Do not redirect stdin while running variables commands
|
||||
- Using `context` and `errgroup` packages (this will make other tasks to be cancelled, if one returned an error)
|
||||
|
||||
## v1.2.0 - 2017-04-02
|
||||
|
||||
- More tests and Travis integration
|
||||
- Watch a task (experimental)
|
||||
- Possibility to call another task
|
||||
- Fix "=" not being reconized in variables/environment variables
|
||||
- Tasks can now have a description, and help will print them (#10)
|
||||
- Task dependencies now run concurrently
|
||||
- Support for a default task (#16)
|
||||
|
||||
## v1.1.0 - 2017-03-08
|
||||
|
||||
- Support for YAML, TOML and JSON (#1)
|
||||
- Support running command in another directory (#4)
|
||||
- `--force` or `-f` flag to force execution of task even when it's up-to-date
|
||||
- Detection of cyclic dependencies (#5)
|
||||
- Support for variables (#6, #9, #14)
|
||||
- Operation System specific commands and variables (#13)
|
||||
|
||||
## v1.0.0 - 2017-02-28
|
||||
|
||||
- Add LICENSE file
|
||||
290
README.md
290
README.md
@@ -1,281 +1,15 @@
|
||||
[](https://gitter.im/go-task/task?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
<div align="center">
|
||||
<a href="https://taskfile.dev">
|
||||
<img src="docs/Logo.png" width="200px" height="200px" />
|
||||
</a>
|
||||
|
||||
# Task - Simple "Make" alternative
|
||||
<h1>Task</h1>
|
||||
|
||||
Task is a simple tool that allows you to easily run development and build
|
||||
tasks. Task is written in Go, but can be used to develop any language.
|
||||
It aims to be simpler and easier to use then [GNU Make][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>
|
||||
|
||||
## Installation
|
||||
|
||||
If you have a [Go][golang] environment setup, you can simply run:
|
||||
|
||||
```bash
|
||||
go get -u -v github.com/go-task/task/cmd/task
|
||||
```
|
||||
|
||||
Or you can download from the [releases][releases] page and add to your `PATH`.
|
||||
|
||||
## Usage
|
||||
|
||||
Create a file called `Taskfile.yml` in the root of the project.
|
||||
(`Taskfile.toml` and `Taskfile.json` are also supported, but YAML is used in
|
||||
the documentation). The `cmds` attribute should contains the commands of a
|
||||
task:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
assets:
|
||||
cmds:
|
||||
- gulp
|
||||
```
|
||||
|
||||
Running the tasks is as simple as running:
|
||||
|
||||
```bash
|
||||
task assets build
|
||||
```
|
||||
|
||||
If Bash is available (Linux and Windows while on Git Bash), the commands will
|
||||
run in Bash, otherwise will fallback to `cmd` (on Windows).
|
||||
|
||||
|
||||
### Environment
|
||||
You can specify environment variables that are added when running a command:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo $hallo
|
||||
env:
|
||||
hallo: welt
|
||||
```
|
||||
|
||||
### OS specific task support
|
||||
|
||||
If you add a `Taskfile_{{GOOS}}` you can override or amend your taskfile based on the op;erating system.
|
||||
|
||||
Example:
|
||||
|
||||
Taskfile.yml:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo "default"
|
||||
```
|
||||
|
||||
Taskfile_linux.yml:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo "linux"
|
||||
```
|
||||
|
||||
Will print out `linux` and not default
|
||||
|
||||
### Running task in another dir
|
||||
|
||||
By default, tasks will be executed in the directory where the Taskfile is
|
||||
located. But you can easily make the task run in another folder informing
|
||||
`dir`:
|
||||
|
||||
```yml
|
||||
js:
|
||||
dir: www/public/js
|
||||
cmds:
|
||||
- gulp
|
||||
```
|
||||
|
||||
### Task dependencies
|
||||
|
||||
You may have tasks that depends on others. Just pointing them on `deps` will
|
||||
make them run automatically before running the parent task:
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [assets]
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
assets:
|
||||
cmds:
|
||||
- gulp
|
||||
```
|
||||
|
||||
In the above example, `assets` will always run right before `build` if you run
|
||||
`task build`.
|
||||
|
||||
A task can have only dependencies and no commands to group tasks together:
|
||||
|
||||
```yml
|
||||
assets:
|
||||
deps: [js, css]
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- npm run buildjs
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- npm run buildcss
|
||||
```
|
||||
|
||||
Each task can only be run once. If it is included from another dependend task causing
|
||||
a cyclomatic dependency, execution will be stopped.
|
||||
|
||||
```yml
|
||||
task1:
|
||||
deps: [task2]
|
||||
|
||||
task2:
|
||||
deps: [task1]
|
||||
```
|
||||
|
||||
Will stop at the moment the dependencies of `task2` are executed.
|
||||
|
||||
### Prevent task from running when not necessary
|
||||
|
||||
If a task generates something, you can inform Task the source and generated
|
||||
files, so Task will prevent to run them if not necessary.
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [js, css]
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- npm run buildjs
|
||||
sources:
|
||||
- js/src/**/*.js
|
||||
generates:
|
||||
- public/bundle.js
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- npm run buildcss
|
||||
sources:
|
||||
- css/src/*.css
|
||||
generates:
|
||||
- public/bundle.css
|
||||
```
|
||||
|
||||
`sources` and `generates` should be file patterns. When both are given, Task
|
||||
will compare the modification date/time of the files to determine if it's
|
||||
necessary to run the task. If not, it will just print
|
||||
`Task "js" is up to date`.
|
||||
|
||||
You can use `--force` or `-f` if you want to force a task to run even when
|
||||
up-to-date.
|
||||
|
||||
### Variables
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [setvar]
|
||||
cmds:
|
||||
- echo "{{.PREFIX}} {{.THEVAR}}"
|
||||
vars:
|
||||
PREFIX: "Path:"
|
||||
|
||||
setvar:
|
||||
cmds:
|
||||
- echo "{{.PATH}}"
|
||||
set: THEVAR
|
||||
```
|
||||
|
||||
The above sample saves the path into a new variable which is then again echoed.
|
||||
|
||||
You can use environment variables, task level variables and a file called
|
||||
`Taskvars.yml` (or `Taskvars.toml` or `Taskvars.json`) as source of variables.
|
||||
|
||||
They are evaluated in the following order:
|
||||
|
||||
Task local variables are overwritten by variables found in `Taskvars` file.
|
||||
Variables found in `Taskvars` file are overwritten with variables from the
|
||||
environment. The output of the last command is stored in the environment. So
|
||||
you can do something like this:
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [setvar]
|
||||
cmds:
|
||||
- echo "{{.PREFIX}} '{{.THEVAR}}'"
|
||||
vars:
|
||||
PREFIX: "Result: "
|
||||
|
||||
setvar:
|
||||
cmds:
|
||||
- echo -n "a"
|
||||
- echo -n "{{.THEVAR}}b"
|
||||
- echo -n "{{.THEVAR}}c"
|
||||
set: THEVAR
|
||||
```
|
||||
|
||||
The result of a run of build would be:
|
||||
|
||||
```
|
||||
a
|
||||
ab
|
||||
abc
|
||||
Result: 'abc'
|
||||
```
|
||||
|
||||
#### Dynamic variables
|
||||
|
||||
If you prefix a variable with `$`, then the variable is considered a dynamic
|
||||
variable. The value after the $-symbol will be treated as a command and the
|
||||
output assigned.
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- go build -ldflags="-X main.Version={{.LAST_GIT_COMMIT}}" main.go
|
||||
vars:
|
||||
LAST_GIT_COMMIT: $git log -n 1 --format=%h
|
||||
```
|
||||
|
||||
### Go's template engine
|
||||
|
||||
Task parse commands as [Go's template engine][gotemplate] before executing
|
||||
them. Variables are acessible through dot syntax (`.VARNAME`). The following
|
||||
functions are available:
|
||||
|
||||
- `OS`: return operating system. Possible values are "windows", "linux",
|
||||
"darwin" (macOS) and "freebsd".
|
||||
- `ARCH`: return the architecture Task was compiled to: "386", "amd64", "arm"
|
||||
or "s390x".
|
||||
- `IsSH`: on unix systems this should always return `true`. On Windows, will
|
||||
return `true` if `sh` command was found (Git Bash). In this case commands
|
||||
will run on `sh`. Otherwise, this function will return `false` meaning
|
||||
commands will run on `cmd`.
|
||||
|
||||
Example:
|
||||
|
||||
```yml
|
||||
printos:
|
||||
cmds:
|
||||
- echo '{{OS}} {{ARCH}}'
|
||||
- "echo '{{if eq OS \"windows\"}}windows-command{{else}}unix-command{{end}}'"
|
||||
- echo 'Is SH? {{IsSH}}'
|
||||
```
|
||||
|
||||
## Alternatives
|
||||
|
||||
Similar software:
|
||||
|
||||
- [tj/robo][robo]
|
||||
- [dogtools/dog][dog]
|
||||
|
||||
[make]: https://www.gnu.org/software/make/
|
||||
[releases]: https://github.com/go-task/task/releases
|
||||
[golang]: https://golang.org/
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[robo]: https://github.com/tj/robo
|
||||
[dog]: https://github.com/dogtools/dog
|
||||
<p>
|
||||
See <a href="https://taskfile.dev">taskfile.dev</a> for the documentation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
97
Taskfile.yml
97
Taskfile.yml
@@ -1,20 +1,83 @@
|
||||
# compiles current source code and make "task" executable available on
|
||||
# $GOPATH/bin/task{.exe}
|
||||
install:
|
||||
cmds:
|
||||
- go install -v ./...
|
||||
version: '3'
|
||||
|
||||
lint:
|
||||
cmds:
|
||||
- golint .
|
||||
- golint ./cmd/task
|
||||
includes:
|
||||
docs:
|
||||
taskfile: ./docs
|
||||
dir: ./docs
|
||||
|
||||
# TODO: have tests
|
||||
test:
|
||||
cmds:
|
||||
- go test -v
|
||||
vars:
|
||||
GIT_COMMIT:
|
||||
sh: git log -n 1 --format=%h
|
||||
|
||||
# https://github.com/goreleaser/goreleaser
|
||||
release:
|
||||
cmds:
|
||||
- goreleaser
|
||||
GO_PACKAGES:
|
||||
sh: go list ./...
|
||||
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: test
|
||||
|
||||
install:
|
||||
desc: Installs Task
|
||||
cmds:
|
||||
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
|
||||
|
||||
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}
|
||||
- task: go-get
|
||||
vars: {REPO: github.com/goreleaser/goreleaser}
|
||||
- task: go-get
|
||||
vars: {REPO: github.com/goreleaser/godownloader}
|
||||
|
||||
clean:
|
||||
desc: Cleans temp files and folders
|
||||
cmds:
|
||||
- rm -rf dist/
|
||||
|
||||
lint:
|
||||
desc: Runs golint
|
||||
cmds:
|
||||
- golint {{catLines .GO_PACKAGES}}
|
||||
silent: true
|
||||
|
||||
test:
|
||||
desc: Runs test suite
|
||||
deps: [install]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}}
|
||||
|
||||
test-release:
|
||||
desc: Tests release process without publishing
|
||||
cmds:
|
||||
- goreleaser --snapshot --rm-dist
|
||||
|
||||
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:
|
||||
- task: go-get
|
||||
vars: {REPO: golang.org/x/lint/golint}
|
||||
- task: lint
|
||||
- task: test
|
||||
|
||||
go-get: go get -u {{.REPO}}
|
||||
|
||||
packages:
|
||||
cmds:
|
||||
- echo '{{.GO_PACKAGES}}'
|
||||
silent: true
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
273
cmd/task/task.go
273
cmd/task/task.go
@@ -1,31 +1,272 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-task/task"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pflag.Usage = func() {
|
||||
fmt.Println(`task [target1 target2 ...]: Runs commands under targets like make.
|
||||
var (
|
||||
version = ""
|
||||
)
|
||||
|
||||
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]
|
||||
|
||||
Runs the specified task(s). Falls back to the "default" task if no task name
|
||||
was specified, or lists all tasks if an unknown task name was specified.
|
||||
|
||||
Example: 'task hello' with the following 'Taskfile.yml' file will generate an
|
||||
'output.txt' file with the content "hello".
|
||||
|
||||
Example: 'task hello' with the following 'Taskfile.yml' file will generate
|
||||
an 'output.txt' file.
|
||||
'''
|
||||
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:
|
||||
`
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(os.Stderr)
|
||||
|
||||
pflag.Usage = func() {
|
||||
log.Print(usage)
|
||||
pflag.PrintDefaults()
|
||||
}
|
||||
pflag.BoolVarP(&task.Force, "force", "f", false, "forces execution even when the task is up-to-date")
|
||||
|
||||
var (
|
||||
versionFlag bool
|
||||
helpFlag bool
|
||||
init bool
|
||||
list bool
|
||||
listAll bool
|
||||
status bool
|
||||
force bool
|
||||
watch bool
|
||||
verbose bool
|
||||
silent bool
|
||||
dry bool
|
||||
summary bool
|
||||
parallel bool
|
||||
concurrency int
|
||||
dir string
|
||||
entrypoint string
|
||||
output taskfile.Output
|
||||
color bool
|
||||
)
|
||||
|
||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||
pflag.BoolVarP(&helpFlag, "help", "h", false, "shows Task usage")
|
||||
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.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.Name, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
|
||||
pflag.StringVar(&output.Group.Begin, "output-group-begin", "", "message template to print before a task's grouped output")
|
||||
pflag.StringVar(&output.Group.End, "output-group-end", "", "message template to print after a task's grouped output")
|
||||
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()
|
||||
task.Run()
|
||||
|
||||
if versionFlag {
|
||||
fmt.Printf("Task version: %s\n", getVersion())
|
||||
return
|
||||
}
|
||||
|
||||
if helpFlag {
|
||||
pflag.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
if init {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := task.InitTaskfile(os.Stdout, wd); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if dir != "" && entrypoint != "" {
|
||||
log.Fatal("task: You can't set both --dir and --taskfile")
|
||||
return
|
||||
}
|
||||
if entrypoint != "" {
|
||||
dir = filepath.Dir(entrypoint)
|
||||
entrypoint = filepath.Base(entrypoint)
|
||||
}
|
||||
|
||||
if output.Name != "group" {
|
||||
if output.Group.Begin != "" {
|
||||
log.Fatal("task: You can't set --output-group-begin without --output=group")
|
||||
return
|
||||
}
|
||||
if output.Group.End != "" {
|
||||
log.Fatal("task: You can't set --output-group-end without --output=group")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
e := task.Executor{
|
||||
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,
|
||||
Stderr: os.Stderr,
|
||||
|
||||
OutputStyle: output,
|
||||
}
|
||||
|
||||
if (list || listAll) && silent {
|
||||
e.ListTaskNames(listAll)
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.Setup(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
v, err := e.Taskfile.ParsedVersion()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if list {
|
||||
e.ListTasksWithDesc()
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
if status {
|
||||
if err := e.Status(ctx, calls...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.Run(ctx, calls...); err != nil {
|
||||
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, syscall.SIGTERM)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
sig := <-ch
|
||||
log.Printf("task: signal received: %s", sig)
|
||||
cancel()
|
||||
}()
|
||||
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
|
||||
}
|
||||
|
||||
22
completion/bash/task.bash
Normal file
22
completion/bash/task.bash
Normal file
@@ -0,0 +1,22 @@
|
||||
# /bin/bash
|
||||
|
||||
_task_completion()
|
||||
{
|
||||
local cur
|
||||
_get_comp_words_by_ref -n : cur
|
||||
|
||||
case "$cur" in
|
||||
--*)
|
||||
local options="$(_parse_help task)"
|
||||
COMPREPLY=( $(compgen -W "$options" -- "$cur") )
|
||||
;;
|
||||
*)
|
||||
local tasks="$(task --list-all | awk 'NR>1 { sub(/:$/,"",$2); print $2 }')"
|
||||
COMPREPLY=( $(compgen -W "$tasks" -- "$cur") )
|
||||
;;
|
||||
esac
|
||||
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
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
|
||||
60
completion/zsh/_task
Executable file
60
completion/zsh/_task
Executable file
@@ -0,0 +1,60 @@
|
||||
#compdef task
|
||||
|
||||
local context state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
# Listing commands from Taskfile.yml
|
||||
function __task_list() {
|
||||
local -a scripts cmd
|
||||
local -i enabled=0
|
||||
local taskfile item task desc
|
||||
|
||||
cmd=(task)
|
||||
taskfile="${(v)opt_args[(i)-t|--taskfile]}"
|
||||
|
||||
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
cmd+=(--taskfile "$taskfile")
|
||||
else
|
||||
for taskfile in Taskfile{,.dist}.{yaml,yml}; do
|
||||
if [[ -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
(( enabled )) || return 0
|
||||
|
||||
scripts=()
|
||||
for item in "${(@)${(f)$("${cmd[@]}" --list)}[2,-1]#\* }"; do
|
||||
task="${item%%:[[:space:]]*}"
|
||||
desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
done
|
||||
_describe 'Task to run' scripts
|
||||
}
|
||||
|
||||
_arguments \
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
|
||||
'(-c --color)'{-c,--color}'[colored output]' \
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]' \
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]' \
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]' \
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
|
||||
+ '(operation)' \
|
||||
{-l,--list}'[list describable tasks]' \
|
||||
{-a,--list-all}'[list all tasks]' \
|
||||
{-i,--init}'[create new Taskfile.yaml]' \
|
||||
'(-*)'{-h,--help}'[show help]' \
|
||||
'(-*)--version[show version and exit]' \
|
||||
'*: :__task_list'
|
||||
25
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() {}
|
||||
0
docs/.nojekyll
Normal file
0
docs/.nojekyll
Normal file
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
taskfile.dev
|
||||
BIN
docs/Logo.png
Normal file
BIN
docs/Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
51
docs/README.md
Normal file
51
docs/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 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].
|
||||
|
||||
Since it's written in [Go][go], Task is just a single binary and has no other
|
||||
dependencies, which means you don't need to mess with any complicated install
|
||||
setups just to use a build tool.
|
||||
|
||||
Once [installed](installation.md), you just need to describe your build tasks
|
||||
using a simple [YAML][yaml] schema in a file called `Taskfile.yml`:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
hello:
|
||||
cmds:
|
||||
- echo 'Hello World from Task!'
|
||||
silent: true
|
||||
```
|
||||
|
||||
And call it by running `task hello` from you terminal.
|
||||
|
||||
The above example is just the start, you can take a look at the [usage](usage.md)
|
||||
guide to check the full schema documentation and Task features.
|
||||
|
||||
## Features
|
||||
|
||||
- [Easy installation](installation.md): just download a single binary, add to
|
||||
$PATH and you're done! Or you can also install using [Homebrew][homebrew],
|
||||
[Snapcraft][snapcraft], or [Scoop][scoop] if you want;
|
||||
- Available on CIs: by adding [this simple command](installation.md#install-script)
|
||||
to install on your CI script and you're done to use Task as part of your CI pipeline;
|
||||
- Truly cross-platform: while most build tools only work well on Linux or macOS,
|
||||
Task also supports Windows thanks to [this awesome shell interpreter for Go][sh];
|
||||
- Great for code generation: you can easily [prevent a task from running](usage.md#prevent-unnecessary-work)
|
||||
if a given set of files haven't changed since last run (based either on its
|
||||
timestamp or content).
|
||||
|
||||
[make]: https://www.gnu.org/software/make/
|
||||
[go]: https://golang.org/
|
||||
[yaml]: http://yaml.org/
|
||||
[homebrew]: https://brew.sh/
|
||||
[snapcraft]: https://snapcraft.io/
|
||||
[scoop]: https://scoop.sh/
|
||||
[sh]: https://mvdan.cc/sh
|
||||
17
docs/Taskfile.yml
Normal file
17
docs/Taskfile.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
install:
|
||||
desc: Installs docsify to work the on the documentation site
|
||||
cmds:
|
||||
- npm install docsify-cli -g
|
||||
|
||||
serve:
|
||||
desc: Serves the documentation site locally
|
||||
cmds:
|
||||
- docsify serve .
|
||||
|
||||
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"
|
||||
8
docs/_sidebar.md
Normal file
8
docs/_sidebar.md
Normal file
@@ -0,0 +1,8 @@
|
||||
- [Installation](installation.md)
|
||||
- [Usage](usage.md)
|
||||
- [Styleguide](styleguide.md)
|
||||
- [Taskfile Versions](taskfile_versions.md)
|
||||
- [Community](community.md)
|
||||
- [Releasing Task](releasing_task.md)
|
||||
- [Donate](donate.md)
|
||||
- [GitHub](https://github.com/go-task/task)
|
||||
56
docs/community.md
Normal file
56
docs/community.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 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).
|
||||
|
||||
### IntelliJ plugin
|
||||
|
||||
There's a JetBrains IntelliJ plugin done by
|
||||
[@lechuckroh](https://github.com/lechuckroh), which has its code [here](https://github.com/lechuckroh/task-intellij-plugin)
|
||||
and is published [here](https://plugins.jetbrains.com/plugin/17058-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>.
|
||||
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
50
docs/index.html
Normal file
50
docs/index.html
Normal file
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Task</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="A task runner / simpler Make alternative written in Go">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">
|
||||
<meta name="google-site-verification" content="VGAYkbdmuaciIDGkBe-eAg9yfZg0C6ostgonbGxxOa0" />
|
||||
<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>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'Task',
|
||||
repo: 'go-task/task',
|
||||
logo: 'Logo.png',
|
||||
themeColor: '#29beb0',
|
||||
loadSidebar: true,
|
||||
auto2top: true,
|
||||
maxLevel: 3,
|
||||
subMaxLevel: 3
|
||||
}
|
||||
</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>
|
||||
383
docs/install.sh
Executable file
383
docs/install.sh
Executable file
@@ -0,0 +1,383 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2021-01-12T13:40:40Z. 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?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))
|
||||
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=$(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}")
|
||||
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}"
|
||||
}
|
||||
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
|
||||
}
|
||||
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 OS
|
||||
case ${OS} 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
|
||||
cygwin_nt*) os="windows" ;;
|
||||
mingw*) os="windows" ;;
|
||||
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="armv5" ;;
|
||||
armv6*) arch="armv6" ;;
|
||||
armv7*) arch="armv7" ;;
|
||||
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 --no-same-owner -xzf "${tarball}" ;;
|
||||
*.tar) tar --no-same-owner -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
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 "$@"
|
||||
|
||||
get_binaries
|
||||
|
||||
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
|
||||
166
docs/installation.md
Normal file
166
docs/installation.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# Installation
|
||||
|
||||
Task offers many installation methods. Check out the available methods below.
|
||||
|
||||
## Package Managers
|
||||
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **Homebrew**
|
||||
|
||||
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
|
||||
Task is as simple as running:
|
||||
|
||||
```bash
|
||||
brew install go-task/tap/go-task
|
||||
```
|
||||
|
||||
#### **Snap**
|
||||
|
||||
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 --classic
|
||||
```
|
||||
|
||||
#### **Chocolatey**
|
||||
|
||||
If you're on Windows and have [Chocolatey][choco] installed, getting
|
||||
Task is as simple as running:
|
||||
|
||||
```bash
|
||||
choco install go-task
|
||||
```
|
||||
|
||||
This installation method is community owned.
|
||||
|
||||
|
||||
#### **Scoop**
|
||||
|
||||
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
|
||||
to install Task like:
|
||||
|
||||
```cmd
|
||||
scoop bucket add extras
|
||||
scoop install task
|
||||
```
|
||||
|
||||
This installation method is community owned. After a new release of Task, it
|
||||
may take some time until it's available on Scoop.
|
||||
|
||||
#### **AUR**
|
||||
|
||||
If you're on Arch Linux you can install Task from
|
||||
[AUR](https://aur.archlinux.org/packages/taskfile-git) using your favorite
|
||||
package manager such as `yay`, `pacaur` or `yaourt`:
|
||||
|
||||
```cmd
|
||||
yay -S taskfile-git
|
||||
```
|
||||
|
||||
This installation method is community owned, but since it's `-git` version of
|
||||
the package, it's always latest available version based on the Git repository.
|
||||
|
||||
|
||||
#### **Nix**
|
||||
|
||||
If you're on NixOS or have Nix installed
|
||||
you can install Task from [nixpkgs](https://github.com/NixOS/nixpkgs):
|
||||
|
||||
```cmd
|
||||
nix-env -iA nixpkgs.go-task
|
||||
```
|
||||
|
||||
This installation method is community owned. After a new release of Task, it
|
||||
may take some time until it's available in [nixpkgs](https://github.com/NixOS/nixpkgs).
|
||||
|
||||
<!-- 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
|
||||
# For Default Installation to ./bin with debug logging
|
||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
|
||||
|
||||
# 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
|
||||
|
||||
```
|
||||
|
||||
> 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/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
|
||||
[godownloader]: https://github.com/goreleaser/godownloader
|
||||
[choco]: https://chocolatey.org/
|
||||
[scoop]: https://scoop.sh/
|
||||
BIN
docs/pix.png
Normal file
BIN
docs/pix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
46
docs/releasing_task.md
Normal file
46
docs/releasing_task.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Releasing Task
|
||||
|
||||
The release process of Task is done with the help of
|
||||
[GoReleaser][goreleaser]. You can test the release process locally by calling
|
||||
the `test-release` task of the Taskfile.
|
||||
|
||||
[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
|
||||
|
||||
To release a new version on the [Homebrew tap][homebrewtap] edit the
|
||||
[Formula/go-task.rb][gotaskrb] file, updating with the new version, download
|
||||
URL and sha256.
|
||||
|
||||
# Snapcraft
|
||||
|
||||
The exception is the publishing of a new version of the
|
||||
[snap package][snappackage]. This current require two steps after publishing
|
||||
the binaries:
|
||||
|
||||
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
|
||||
* Moving both `i386` and `amd64` new artifacts to the stable channel on
|
||||
the [Snapcraft dashboard][snapcraftdashboard]
|
||||
|
||||
# Scoop
|
||||
|
||||
Scoop is a community owned installation method. Scoop owners usually take care
|
||||
of updating versions there by editing
|
||||
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
||||
If you think its Task version is outdated, open an issue to let us know.
|
||||
|
||||
# Nix
|
||||
|
||||
Nix is a community owned installation method. Nix package maintainers usually take care
|
||||
of updating versions there by editing
|
||||
[this file](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/development/tools/go-task/default.nix).
|
||||
If you think its Task version is outdated, open an issue to let us know.
|
||||
|
||||
[goreleaser]: https://goreleaser.com/#continuous_integration
|
||||
[homebrewtap]: https://github.com/go-task/homebrew-tap
|
||||
[gotaskrb]: https://github.com/go-task/homebrew-tap/blob/master/Formula/go-task.rb
|
||||
[snappackage]: https://github.com/go-task/snap
|
||||
[snapcraftyaml]: https://github.com/go-task/snap/blob/master/snap/snapcraft.yaml#L2
|
||||
[snapcraftdashboard]: https://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.
|
||||
225
docs/taskfile_versions.md
Normal file
225
docs/taskfile_versions.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Taskfile Versions
|
||||
|
||||
The Taskfile syntax and features changed with time. This document explains what
|
||||
changed on each version and how to upgrade your Taskfile.
|
||||
|
||||
## What the Taskfile version mean
|
||||
|
||||
The Taskfile version follows the Task version. E.g. the change to Taskfile
|
||||
version `2` means that Task `v2.0.0` should be release to support it.
|
||||
|
||||
The `version:` key on Taskfile accepts a semver string, so either `2`, `2.0` or
|
||||
`2.0.0` is accepted. 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:
|
||||
|
||||
```yaml
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
```
|
||||
|
||||
The variable priority order was also different:
|
||||
|
||||
1. Call variables
|
||||
2. Environment
|
||||
3. Task variables
|
||||
4. `Taskvars.yml` variables
|
||||
|
||||
## Version 2.0
|
||||
|
||||
At version 2, we introduced the `version:` key, to allow us to evolve Task
|
||||
with new features without breaking existing Taskfiles. The new syntax is as
|
||||
follows:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
echo:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
```
|
||||
|
||||
Version 2 allows you to write global variables directly in the Taskfile,
|
||||
if you don't want to create a `Taskvars.yml`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
cmds:
|
||||
- echo "{{.GREETING}}"
|
||||
```
|
||||
|
||||
The variable priority order changed to the following:
|
||||
|
||||
1. Task variables
|
||||
2. Call variables
|
||||
3. Taskfile variables
|
||||
4. Taskvars file variables
|
||||
5. Environment variables
|
||||
|
||||
A new global option was added to configure the number of variables expansions
|
||||
(which default to 2):
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
expansions: 3
|
||||
|
||||
vars:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BAZ: baz
|
||||
FOOBAR: "{{.FOO}}{{.BAR}}"
|
||||
FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "{{.FOOBARBAZ}}"
|
||||
```
|
||||
|
||||
## Version 2.1
|
||||
|
||||
Version 2.1 includes a global `output` option, to allow having more control
|
||||
over how commands output are printed to the console
|
||||
(see [documentation][output] for more info):
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
output: prefixed
|
||||
|
||||
tasks:
|
||||
server:
|
||||
cmds:
|
||||
- go run main.go
|
||||
prefix: server
|
||||
```
|
||||
|
||||
From this version it's also possible to ignore errors of a command or task
|
||||
(check documentation [here][ignore_errors]):
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
example-1:
|
||||
cmds:
|
||||
- cmd: exit 1
|
||||
ignore_error: true
|
||||
- echo "This will be print"
|
||||
|
||||
example-2:
|
||||
cmds:
|
||||
- exit 1
|
||||
- echo "This will be print"
|
||||
ignore_error: true
|
||||
```
|
||||
|
||||
## Version 2.2
|
||||
|
||||
Version 2.2 comes with a global `includes` options to include other
|
||||
Taskfiles:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
includes:
|
||||
docs: ./documentation # will look for ./documentation/Taskfile.yml
|
||||
docker: ./DockerTasks.yml
|
||||
```
|
||||
|
||||
## Version 2.6
|
||||
|
||||
Version 2.6 comes with `preconditions` stanza in tasks.
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
tasks:
|
||||
upload_environment:
|
||||
preconditions:
|
||||
- test -f .env
|
||||
cmds:
|
||||
- aws s3 cp .env s3://myenvironment
|
||||
```
|
||||
|
||||
Please check the [documentation][includes]
|
||||
|
||||
[output]: usage.md#output-syntax
|
||||
[ignore_errors]: usage.md#ignore-errors
|
||||
[includes]: usage.md#including-other-taskfiles
|
||||
|
||||
## 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
|
||||
1107
docs/usage.md
Normal file
1107
docs/usage.md
Normal file
File diff suppressed because it is too large
Load Diff
27
errors.go
27
errors.go
@@ -1,16 +1,14 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type taskFileNotFound struct {
|
||||
taskFile string
|
||||
}
|
||||
|
||||
func (err taskFileNotFound) Error() string {
|
||||
return fmt.Sprintf(`task: No task file found (is it named "%s"?)`, err.taskFile)
|
||||
}
|
||||
var (
|
||||
// ErrTaskfileAlreadyExists is returned on creating a Taskfile if one already exists
|
||||
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
|
||||
)
|
||||
|
||||
type taskNotFoundError struct {
|
||||
taskName string
|
||||
@@ -29,10 +27,17 @@ func (err *taskRunError) Error() string {
|
||||
return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
|
||||
}
|
||||
|
||||
type cyclicDepError struct {
|
||||
taskName string
|
||||
// MaximumTaskCallExceededError is returned when a task is called too
|
||||
// many times. In this case you probably have a cyclic dependendy or
|
||||
// infinite loop
|
||||
type MaximumTaskCallExceededError struct {
|
||||
task string
|
||||
}
|
||||
|
||||
func (err *cyclicDepError) Error() string {
|
||||
return fmt.Sprintf(`task: Cyclic dependency of task "%s" detected`, err.taskName)
|
||||
func (e *MaximumTaskCallExceededError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: maximum task call exceeded (%d) for task "%s": probably an cyclic dep or infinite loop`,
|
||||
MaximumTaskCall,
|
||||
e.task,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"hello": {
|
||||
"cmds": [
|
||||
"echo \"I am going to write a file named 'output.txt' now.\"",
|
||||
"echo \"hello\" > output.txt"
|
||||
],
|
||||
"generates": [
|
||||
"output.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[hello]
|
||||
cmds = [
|
||||
"echo \"I am going to write a file named 'output.txt' now.\"",
|
||||
"echo \"hello\" > output.txt"
|
||||
]
|
||||
generates = ["output.txt"]
|
||||
@@ -1,6 +0,0 @@
|
||||
hello:
|
||||
cmds:
|
||||
- echo "I am going to write a file named 'output.txt' now."
|
||||
- echo "hello" > output.txt
|
||||
generates:
|
||||
- output.txt
|
||||
82
file.go
82
file.go
@@ -1,82 +0,0 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-zglob"
|
||||
)
|
||||
|
||||
func minTime(a, b time.Time) time.Time {
|
||||
if !a.IsZero() && a.Before(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
func maxTime(a, b time.Time) time.Time {
|
||||
if a.After(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func getPatternsMinTime(patterns []string) (m time.Time, err error) {
|
||||
for _, p := range patterns {
|
||||
mp, err := getPatternMinTime(p)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
m = minTime(m, mp)
|
||||
}
|
||||
return
|
||||
}
|
||||
func getPatternsMaxTime(patterns []string) (m time.Time, err error) {
|
||||
for _, p := range patterns {
|
||||
mp, err := getPatternMaxTime(p)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
m = maxTime(m, mp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getPatternMinTime(pattern string) (minTime time.Time, err error) {
|
||||
files, err := zglob.Glob(pattern)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
modTime := info.ModTime()
|
||||
if minTime.IsZero() || modTime.Before(minTime) {
|
||||
minTime = modTime
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getPatternMaxTime(pattern string) (maxTime time.Time, err error) {
|
||||
files, err := zglob.Glob(pattern)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
modTime := info.ModTime()
|
||||
if modTime.After(maxTime) {
|
||||
maxTime = modTime
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
26
go.mod
Normal file
26
go.mod
Normal file
@@ -0,0 +1,26 @@
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
require (
|
||||
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.1
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
mvdan.cc/sh/v3 v3.4.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 // indirect
|
||||
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf // indirect
|
||||
)
|
||||
|
||||
go 1.17
|
||||
72
go.sum
Normal file
72
go.sum
Normal file
@@ -0,0 +1,72 @@
|
||||
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/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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.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.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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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.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.3 h1:zbuKH7YH9cqU6PGajhFFXZY7dhPXcDr55iN/cUAqpuw=
|
||||
mvdan.cc/sh/v3 v3.4.3/go.mod h1:p/tqPPI4Epfk2rICAe2RoaNd8HBSJ8t9Y2DA9yQlbzY=
|
||||
@@ -1,10 +0,0 @@
|
||||
build:
|
||||
binary_name: task
|
||||
main: cmd/task/task.go
|
||||
goos:
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
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)
|
||||
}
|
||||
106
help.go
Normal file
106
help.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
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(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.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)
|
||||
}
|
||||
}
|
||||
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
|
||||
return
|
||||
}
|
||||
|
||||
// PrintTaskNames prints only the task names in a Taskfile.
|
||||
// Only tasks with a non-empty description are printed if allTasks is false.
|
||||
// Otherwise, all task names are printed.
|
||||
func (e *Executor) ListTaskNames(allTasks bool) {
|
||||
// if called from cmd/task.go, e.Taskfile has not yet been parsed
|
||||
if e.Taskfile == nil {
|
||||
if err := e.readTaskfile(); err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// use stdout if no output defined
|
||||
var w io.Writer = os.Stdout
|
||||
if e.Stdout != nil {
|
||||
w = e.Stdout
|
||||
}
|
||||
// create a string slice from all map values (*taskfile.Task)
|
||||
s := make([]string, 0, len(e.Taskfile.Tasks))
|
||||
for _, t := range e.Taskfile.Tasks {
|
||||
if allTasks || t.Desc != "" {
|
||||
s = append(s, strings.TrimRight(t.Task, ":"))
|
||||
}
|
||||
}
|
||||
// sort and print all task names
|
||||
sort.Strings(s)
|
||||
for _, t := range s {
|
||||
fmt.Fprintln(w, t)
|
||||
}
|
||||
}
|
||||
37
init.go
Normal file
37
init.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const defaultTaskfile = `# https://taskfile.dev
|
||||
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "{{.GREETING}}"
|
||||
silent: true
|
||||
`
|
||||
|
||||
// InitTaskfile Taskfile creates a new Taskfile
|
||||
func InitTaskfile(w io.Writer, dir string) error {
|
||||
f := filepath.Join(dir, "Taskfile.yaml")
|
||||
|
||||
if _, err := os.Stat(f); err == nil {
|
||||
return ErrTaskfileAlreadyExists
|
||||
}
|
||||
|
||||
if err := os.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(w, "Taskfile.yaml created in the current directory\n")
|
||||
return nil
|
||||
}
|
||||
383
install-task.sh
Executable file
383
install-task.sh
Executable file
@@ -0,0 +1,383 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2021-01-12T13:40:40Z. 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?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))
|
||||
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=$(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}")
|
||||
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}"
|
||||
}
|
||||
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
|
||||
}
|
||||
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 OS
|
||||
case ${OS} 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
|
||||
cygwin_nt*) os="windows" ;;
|
||||
mingw*) os="windows" ;;
|
||||
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="armv5" ;;
|
||||
armv6*) arch="armv6" ;;
|
||||
armv7*) arch="armv7" ;;
|
||||
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 --no-same-owner -xzf "${tarball}" ;;
|
||||
*.tar) tar --no-same-owner -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
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 "$@"
|
||||
|
||||
get_binaries
|
||||
|
||||
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
|
||||
15
internal/compiler/compiler.go
Normal file
15
internal/compiler/compiler.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"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 {
|
||||
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()
|
||||
}
|
||||
20
internal/compiler/env.go
Normal file
20
internal/compiler/env.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// GetEnviron the all return all environment variables encapsulated on a
|
||||
// taskfile.Vars
|
||||
func GetEnviron() *taskfile.Vars {
|
||||
m := &taskfile.Vars{}
|
||||
for _, e := range os.Environ() {
|
||||
keyVal := strings.SplitN(e, "=", 2)
|
||||
key, val := keyVal[0], keyVal[1]
|
||||
m.Set(key, taskfile.Var{Static: val})
|
||||
}
|
||||
return m
|
||||
}
|
||||
131
internal/compiler/v2/compiler_v2.go
Normal file
131
internal/compiler/v2/compiler_v2.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
var _ compiler.Compiler = &CompilerV2{}
|
||||
|
||||
type CompilerV2 struct {
|
||||
Dir string
|
||||
|
||||
Taskvars *taskfile.Vars
|
||||
TaskfileVars *taskfile.Vars
|
||||
|
||||
Expansions int
|
||||
|
||||
Logger *logger.Logger
|
||||
|
||||
dynamicCache map[string]string
|
||||
muDynamicCache sync.Mutex
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
return vr.vars, vr.err
|
||||
}
|
||||
|
||||
type varResolver struct {
|
||||
c *CompilerV2
|
||||
vars *taskfile.Vars
|
||||
err error
|
||||
}
|
||||
|
||||
func (vr *varResolver) merge(vars *taskfile.Vars) {
|
||||
if vr.err != nil {
|
||||
return
|
||||
}
|
||||
tr := templater.Templater{Vars: vr.vars}
|
||||
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, "")
|
||||
if err != nil {
|
||||
vr.err = err
|
||||
return err
|
||||
}
|
||||
vr.vars.Set(k, taskfile.Var{Static: static})
|
||||
return nil
|
||||
})
|
||||
vr.err = tr.Err()
|
||||
}
|
||||
|
||||
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ 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
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
opts := &execext.RunCommandOptions{
|
||||
Command: v.Sh,
|
||||
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 *CompilerV2) ResetCache() {
|
||||
c.muDynamicCache.Lock()
|
||||
defer c.muDynamicCache.Unlock()
|
||||
|
||||
c.dynamicCache = nil
|
||||
}
|
||||
168
internal/compiler/v3/compiler_v3.go
Normal file
168
internal/compiler/v3/compiler_v3.go
Normal file
@@ -0,0 +1,168 @@
|
||||
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)
|
||||
|
||||
var taskRangeFunc func(k string, v taskfile.Var) error
|
||||
if t != nil {
|
||||
// NOTE(@andreynering): We're manually joining these paths here because
|
||||
// this is the raw task, not the compiled one.
|
||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
||||
dir := tr.Replace(t.Dir)
|
||||
if err := tr.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !filepath.IsAbs(dir) {
|
||||
dir = filepath.Join(c.Dir, dir)
|
||||
}
|
||||
taskRangeFunc = getRangeFunc(dir)
|
||||
}
|
||||
|
||||
if err := c.TaskfileEnv.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t != nil {
|
||||
if err := t.IncludedTaskfileVars.Range(taskRangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.IncludeVars.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if t == nil || call == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if err := call.Vars.Range(rangeFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 }
|
||||
110
internal/execext/exec.go
Normal file
110
internal/execext/exec.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package execext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
// RunCommandOptions is the options for the RunCommand func
|
||||
type RunCommandOptions struct {
|
||||
Command string
|
||||
Dir string
|
||||
Env []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNilOptions is returned when a nil options is given
|
||||
ErrNilOptions = errors.New("execext: nil options given")
|
||||
)
|
||||
|
||||
// RunCommand runs a shell command
|
||||
func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
if opts == nil {
|
||||
return ErrNilOptions
|
||||
}
|
||||
|
||||
p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
environ := opts.Env
|
||||
if len(environ) == 0 {
|
||||
environ = os.Environ()
|
||||
}
|
||||
|
||||
r, err := interp.New(
|
||||
interp.Params("-e"),
|
||||
interp.Env(expand.ListEnviron(environ...)),
|
||||
interp.OpenHandler(openHandler),
|
||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||
dirOption(opts.Dir),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Run(ctx, p)
|
||||
}
|
||||
|
||||
// IsExitError returns true the given error is an exis status error
|
||||
func IsExitError(err error) bool {
|
||||
if _, ok := interp.IsExitStatus(err); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
||||
// if available.
|
||||
func Expand(s string) (string, error) {
|
||||
s = filepath.ToSlash(s)
|
||||
s = strings.Replace(s, " ", `\ `, -1)
|
||||
fields, err := shell.Fields(s, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(fields) > 0 {
|
||||
return fields[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
65
internal/logger/logger.go
Normal file
65
internal/logger/logger.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"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
|
||||
}
|
||||
|
||||
// Outf prints stuff to STDOUT.
|
||||
func (l *Logger) Outf(color Color, s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
if !l.Color {
|
||||
color = Default
|
||||
}
|
||||
print := color()
|
||||
print(l.Stdout, s+"\n", args...)
|
||||
}
|
||||
|
||||
// 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(color, s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Errf prints stuff to STDERR.
|
||||
func (l *Logger) Errf(color Color, s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
if !l.Color {
|
||||
color = Default
|
||||
}
|
||||
print := color()
|
||||
print(l.Stderr, s+"\n", args...)
|
||||
}
|
||||
|
||||
// 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(color, s, args...)
|
||||
}
|
||||
}
|
||||
44
internal/output/group.go
Normal file
44
internal/output/group.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Group struct{
|
||||
Begin, End string
|
||||
}
|
||||
|
||||
func (g Group) WrapWriter(w io.Writer, _ string, tmpl Templater) io.Writer {
|
||||
gw := &groupWriter{writer: w}
|
||||
if g.Begin != "" {
|
||||
gw.begin = tmpl.Replace(g.Begin) + "\n"
|
||||
}
|
||||
if g.End != "" {
|
||||
gw.end = tmpl.Replace(g.End) + "\n"
|
||||
}
|
||||
return gw
|
||||
}
|
||||
|
||||
type groupWriter struct {
|
||||
writer io.Writer
|
||||
buff bytes.Buffer
|
||||
begin, end string
|
||||
}
|
||||
|
||||
func (gw *groupWriter) Write(p []byte) (int, error) {
|
||||
return gw.buff.Write(p)
|
||||
}
|
||||
|
||||
func (gw *groupWriter) Close() error {
|
||||
if gw.buff.Len() == 0 {
|
||||
// don't print begin/end messages if there's no buffered entries
|
||||
return nil
|
||||
}
|
||||
if _, err := io.WriteString(gw.writer, gw.begin); err != nil {
|
||||
return err
|
||||
}
|
||||
gw.buff.WriteString(gw.end)
|
||||
_, err := io.Copy(gw.writer, &gw.buff)
|
||||
return err
|
||||
}
|
||||
11
internal/output/interleaved.go
Normal file
11
internal/output/interleaved.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Interleaved struct{}
|
||||
|
||||
func (Interleaved) WrapWriter(w io.Writer, _ string, _ Templater) io.Writer {
|
||||
return w
|
||||
}
|
||||
49
internal/output/output.go
Normal file
49
internal/output/output.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// Templater executes a template engine.
|
||||
// It is provided by the templater.Templater package.
|
||||
type Templater interface {
|
||||
// Replace replaces the provided template string with a rendered string.
|
||||
Replace(tmpl string) string
|
||||
}
|
||||
|
||||
type Output interface {
|
||||
WrapWriter(w io.Writer, prefix string, tmpl Templater) io.Writer
|
||||
}
|
||||
|
||||
// Build the Output for the requested taskfile.Output.
|
||||
func BuildFor(o *taskfile.Output) (Output, error) {
|
||||
switch o.Name {
|
||||
case "interleaved", "":
|
||||
if err := checkOutputGroupUnset(o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Interleaved{}, nil
|
||||
case "group":
|
||||
return Group{
|
||||
Begin: o.Group.Begin,
|
||||
End: o.Group.End,
|
||||
}, nil
|
||||
case "prefixed":
|
||||
if err := checkOutputGroupUnset(o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Prefixed{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func checkOutputGroupUnset(o *taskfile.Output) error {
|
||||
if o.Group.IsSet() {
|
||||
return fmt.Errorf("task: output style %q does not support the group begin/end parameter", o.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
98
internal/output/output_test.go
Normal file
98
internal/output/output_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package output_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
)
|
||||
|
||||
func TestInterleaved(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Interleaved{}
|
||||
var w = o.WrapWriter(&b, "", nil)
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "foo\nbar\n", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
||||
}
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Group{}
|
||||
var w = o.WrapWriter(&b, "", nil).(io.WriteCloser)
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "", b.String())
|
||||
assert.NoError(t, w.Close())
|
||||
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
||||
}
|
||||
|
||||
func TestGroupWithBeginEnd(t *testing.T) {
|
||||
tmpl := templater.Templater{
|
||||
Vars: &taskfile.Vars{
|
||||
Keys: []string{"VAR1"},
|
||||
Mapping: map[string]taskfile.Var{
|
||||
"VAR1": {Static: "example-value"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var o output.Output = output.Group{
|
||||
Begin: "::group::{{ .VAR1 }}",
|
||||
End: "::endgroup::",
|
||||
}
|
||||
t.Run("simple", func(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var w = o.WrapWriter(&b, "", &tmpl).(io.WriteCloser)
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "", b.String())
|
||||
assert.NoError(t, w.Close())
|
||||
assert.Equal(t, "::group::example-value\nfoo\nbar\nbaz\n::endgroup::\n", b.String())
|
||||
})
|
||||
t.Run("no output", func(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var w = o.WrapWriter(&b, "", &tmpl).(io.WriteCloser)
|
||||
assert.NoError(t, w.Close())
|
||||
assert.Equal(t, "", b.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrefixed(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Prefixed{}
|
||||
var w = o.WrapWriter(&b, "prefix", nil).(io.WriteCloser)
|
||||
|
||||
t.Run("simple use cases", func(t *testing.T) {
|
||||
b.Reset()
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n[prefix] baz\n", b.String())
|
||||
})
|
||||
|
||||
t.Run("multiple writes for a single line", func(t *testing.T) {
|
||||
b.Reset()
|
||||
|
||||
for _, char := range []string{"T", "e", "s", "t", "!"} {
|
||||
fmt.Fprint(w, char)
|
||||
assert.Equal(t, "", b.String())
|
||||
}
|
||||
|
||||
assert.NoError(t, w.Close())
|
||||
assert.Equal(t, "[prefix] Test!\n", b.String())
|
||||
})
|
||||
}
|
||||
65
internal/output/prefixed.go
Normal file
65
internal/output/prefixed.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Prefixed struct{}
|
||||
|
||||
func (Prefixed) WrapWriter(w io.Writer, prefix string, _ Templater) io.Writer {
|
||||
return &prefixWriter{writer: w, prefix: prefix}
|
||||
}
|
||||
|
||||
type prefixWriter struct {
|
||||
writer io.Writer
|
||||
prefix string
|
||||
buff bytes.Buffer
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Write(p []byte) (int, error) {
|
||||
n, err := pw.buff.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, pw.writeOutputLines(false)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Close() error {
|
||||
return pw.writeOutputLines(true)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) writeOutputLines(force bool) error {
|
||||
for {
|
||||
switch line, err := pw.buff.ReadString('\n'); err {
|
||||
case nil:
|
||||
if err = pw.writeLine(line); err != nil {
|
||||
return err
|
||||
}
|
||||
case io.EOF:
|
||||
// if this line was not a complete line, re-add to the buffer
|
||||
if !force && !strings.HasSuffix(line, "\n") {
|
||||
_, err = pw.buff.WriteString(line)
|
||||
return err
|
||||
}
|
||||
|
||||
return pw.writeLine(line)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) writeLine(line string) error {
|
||||
if line == "" {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(line, "\n") {
|
||||
line += "\n"
|
||||
}
|
||||
_, err := fmt.Fprintf(pw.writer, "[%s] %s", pw.prefix, line)
|
||||
return err
|
||||
}
|
||||
118
internal/status/checksum.go
Normal file
118
internal/status/checksum.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Checksum validades if a task is up to date by calculating its source
|
||||
// files checksum
|
||||
type Checksum struct {
|
||||
BaseDir string
|
||||
TaskDir string
|
||||
Task string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Dry bool
|
||||
}
|
||||
|
||||
// IsUpToDate implements the Checker interface
|
||||
func (c *Checksum) IsUpToDate() (bool, error) {
|
||||
if len(c.Sources) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
checksumFile := c.checksumFilePath()
|
||||
|
||||
data, _ := os.ReadFile(checksumFile)
|
||||
oldMd5 := strings.TrimSpace(string(data))
|
||||
|
||||
sources, err := globs(c.TaskDir, c.Sources)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
newMd5, err := c.checksum(sources...)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !c.Dry {
|
||||
_ = os.MkdirAll(filepath.Join(c.BaseDir, ".task", "checksum"), 0755)
|
||||
if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Generates) > 0 {
|
||||
// For each specified 'generates' field, check whether the files actually exist
|
||||
for _, g := range c.Generates {
|
||||
generates, err := Glob(c.TaskDir, g)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(generates) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return oldMd5 == newMd5, nil
|
||||
}
|
||||
|
||||
func (c *Checksum) checksum(files ...string) (string, error) {
|
||||
h := md5.New()
|
||||
|
||||
for _, f := range files {
|
||||
// also sum the filename, so checksum changes for renaming a file
|
||||
if _, err := io.Copy(h, strings.NewReader(filepath.Base(f))); err != nil {
|
||||
return "", err
|
||||
}
|
||||
f, err := os.Open(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
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.BaseDir, ".task", "checksum", c.normalizeFilename(c.Task))
|
||||
}
|
||||
|
||||
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
|
||||
|
||||
// replaces invalid caracters on filenames with "-"
|
||||
func (*Checksum) normalizeFilename(f string) string {
|
||||
return checksumFilenameRegexp.ReplaceAllString(f, "-")
|
||||
}
|
||||
21
internal/status/checksum_test.go
Normal file
21
internal/status/checksum_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNormalizeFilename(t *testing.T) {
|
||||
tests := []struct {
|
||||
In, Out string
|
||||
}{
|
||||
{"foobarbaz", "foobarbaz"},
|
||||
{"foo/bar/baz", "foo-bar-baz"},
|
||||
{"foo@bar/baz", "foo-bar-baz"},
|
||||
{"foo1bar2baz3", "foo1bar2baz3"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.Out, (&Checksum{}).normalizeFilename(test.In))
|
||||
}
|
||||
}
|
||||
50
internal/status/glob.go
Normal file
50
internal/status/glob.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"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)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
files = append(files, f...)
|
||||
}
|
||||
sort.Strings(files)
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func Glob(dir string, g string) ([]string, error) {
|
||||
files := make([]string, 0)
|
||||
if !filepath.IsAbs(g) {
|
||||
g = filepath.Join(dir, g)
|
||||
}
|
||||
g, err := execext.Expand(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fs, err := zglob.Glob(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range fs {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.IsDir() {
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
23
internal/status/none.go
Normal file
23
internal/status/none.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package status
|
||||
|
||||
// None is a no-op Checker
|
||||
type None struct{}
|
||||
|
||||
// IsUpToDate implements the Checker interface
|
||||
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
|
||||
}
|
||||
15
internal/status/status.go
Normal file
15
internal/status/status.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package status
|
||||
|
||||
var (
|
||||
_ Checker = &Timestamp{}
|
||||
_ Checker = &Checksum{}
|
||||
_ Checker = None{}
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
108
internal/status/timestamp.go
Normal file
108
internal/status/timestamp.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timestamp checks if any source change compared with the generated files,
|
||||
// using file modifications timestamps.
|
||||
type Timestamp struct {
|
||||
Dir string
|
||||
Sources []string
|
||||
Generates []string
|
||||
}
|
||||
|
||||
// IsUpToDate implements the Checker interface
|
||||
func (t *Timestamp) IsUpToDate() (bool, error) {
|
||||
if len(t.Sources) == 0 || len(t.Generates) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
sources, err := globs(t.Dir, t.Sources)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
generates, err := globs(t.Dir, t.Generates)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
sourcesMaxTime, err := getMaxTime(sources...)
|
||||
if err != nil || sourcesMaxTime.IsZero() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
generatesMinTime, err := getMinTime(generates...)
|
||||
if err != nil || generatesMinTime.IsZero() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
t = minTime(t, info.ModTime())
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func getMaxTime(files ...string) (time.Time, error) {
|
||||
var t time.Time
|
||||
for _, f := range files {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
t = maxTime(t, info.ModTime())
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func minTime(a, b time.Time) time.Time {
|
||||
if !a.IsZero() && a.Before(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func maxTime(a, b time.Time) time.Time {
|
||||
if a.After(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// OnError implements the Checker interface
|
||||
func (*Timestamp) OnError() error {
|
||||
return nil
|
||||
}
|
||||
103
internal/summary/summary.go
Normal file
103
internal/summary/summary.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package summary
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/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)
|
||||
PrintTask(l, t.Tasks[call.Task])
|
||||
}
|
||||
}
|
||||
|
||||
func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
|
||||
spaceRequired := i > 0
|
||||
if !spaceRequired {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
||||
printTaskName(l, t)
|
||||
printTaskDescribingText(t, l)
|
||||
printTaskDependencies(l, t)
|
||||
printTaskCommands(l, t)
|
||||
}
|
||||
|
||||
func printTaskDescribingText(t *taskfile.Task, l *logger.Logger) {
|
||||
if hasSummary(t) {
|
||||
printTaskSummary(l, t)
|
||||
} else if hasDescription(t) {
|
||||
printTaskDescription(l, t)
|
||||
} else {
|
||||
printNoDescriptionOrSummary(l)
|
||||
}
|
||||
}
|
||||
|
||||
func hasSummary(t *taskfile.Task) bool {
|
||||
return t.Summary != ""
|
||||
}
|
||||
|
||||
func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
|
||||
lines := strings.Split(t.Summary, "\n")
|
||||
for i, line := range lines {
|
||||
notLastLine := i+1 < len(lines)
|
||||
if notLastLine || line != "" {
|
||||
l.Outf(logger.Default, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, "task: %s", t.Name())
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func hasDescription(t *taskfile.Task) bool {
|
||||
return t.Desc != ""
|
||||
}
|
||||
|
||||
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, t.Desc)
|
||||
}
|
||||
|
||||
func printNoDescriptionOrSummary(l *logger.Logger) {
|
||||
l.Outf(logger.Default, "(task does not have description or summary)")
|
||||
}
|
||||
|
||||
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Deps) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "dependencies:")
|
||||
|
||||
for _, d := range t.Deps {
|
||||
l.Outf(logger.Default, " - %s", d.Task)
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Cmds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "commands:")
|
||||
for _, c := range t.Cmds {
|
||||
isCommand := c.Cmd != ""
|
||||
if isCommand {
|
||||
l.Outf(logger.Default, " - %s", c.Cmd)
|
||||
} else {
|
||||
l.Outf(logger.Default, " - Task: %s", c.Task)
|
||||
}
|
||||
}
|
||||
}
|
||||
173
internal/summary/summary_test.go
Normal file
173
internal/summary/summary_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package summary_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/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) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Deps: []*taskfile.Dep{
|
||||
{Task: "dep1"},
|
||||
{Task: "dep2"},
|
||||
{Task: "dep3"},
|
||||
},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.Contains(t, buffer.String(), "\ndependencies:\n - dep1\n - dep2\n - dep3\n")
|
||||
}
|
||||
|
||||
func createDummyLogger() (*bytes.Buffer, logger.Logger) {
|
||||
buffer := &bytes.Buffer{}
|
||||
l := logger.Logger{
|
||||
Stderr: buffer,
|
||||
Stdout: buffer,
|
||||
Verbose: false,
|
||||
}
|
||||
return buffer, l
|
||||
}
|
||||
|
||||
func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Deps: []*taskfile.Dep{},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.NotContains(t, buffer.String(), "dependencies:")
|
||||
}
|
||||
|
||||
func TestPrintTaskName(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Task: "my-task-name",
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.Contains(t, buffer.String(), "task: my-task-name\n")
|
||||
}
|
||||
|
||||
func TestPrintTaskCommandsIfPresent(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Cmds: []*taskfile.Cmd{
|
||||
{Cmd: "command-1"},
|
||||
{Cmd: "command-2"},
|
||||
{Task: "task-1"},
|
||||
},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.Contains(t, buffer.String(), "\ncommands:\n")
|
||||
assert.Contains(t, buffer.String(), "\n - command-1\n")
|
||||
assert.Contains(t, buffer.String(), "\n - command-2\n")
|
||||
assert.Contains(t, buffer.String(), "\n - Task: task-1\n")
|
||||
}
|
||||
|
||||
func TestDoesNotPrintCommandIfMissing(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Cmds: []*taskfile.Cmd{},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.NotContains(t, buffer.String(), "commands")
|
||||
}
|
||||
|
||||
func TestLayout(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
task := &taskfile.Task{
|
||||
Task: "sample-task",
|
||||
Summary: "line1\nline2\nline3\n",
|
||||
Deps: []*taskfile.Dep{
|
||||
{Task: "dependency"},
|
||||
},
|
||||
Cmds: []*taskfile.Cmd{
|
||||
{Cmd: "command"},
|
||||
},
|
||||
}
|
||||
|
||||
summary.PrintTask(&l, task)
|
||||
|
||||
assert.Equal(t, expectedOutput(), buffer.String())
|
||||
}
|
||||
|
||||
func expectedOutput() string {
|
||||
expected := `task: sample-task
|
||||
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
|
||||
dependencies:
|
||||
- dependency
|
||||
|
||||
commands:
|
||||
- command
|
||||
`
|
||||
return expected
|
||||
}
|
||||
|
||||
func TestPrintDescriptionAsFallback(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
taskWithoutSummary := &taskfile.Task{
|
||||
Desc: "description",
|
||||
}
|
||||
|
||||
taskWithSummary := &taskfile.Task{
|
||||
Desc: "description",
|
||||
Summary: "summary",
|
||||
}
|
||||
taskWithoutSummaryOrDescription := &taskfile.Task{}
|
||||
|
||||
summary.PrintTask(&l, taskWithoutSummary)
|
||||
|
||||
assert.Contains(t, buffer.String(), "description")
|
||||
|
||||
buffer.Reset()
|
||||
summary.PrintTask(&l, taskWithSummary)
|
||||
|
||||
assert.NotContains(t, buffer.String(), "description")
|
||||
|
||||
buffer.Reset()
|
||||
summary.PrintTask(&l, taskWithoutSummaryOrDescription)
|
||||
|
||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n")
|
||||
|
||||
}
|
||||
|
||||
func TestPrintAllWithSpaces(t *testing.T) {
|
||||
buffer, l := createDummyLogger()
|
||||
|
||||
t1 := &taskfile.Task{Task: "t1"}
|
||||
t2 := &taskfile.Task{Task: "t2"}
|
||||
t3 := &taskfile.Task{Task: "t3"}
|
||||
|
||||
tasks := make(taskfile.Tasks, 3)
|
||||
tasks["t1"] = t1
|
||||
tasks["t2"] = t2
|
||||
tasks["t3"] = t3
|
||||
|
||||
summary.PrintTasks(&l,
|
||||
&taskfile.Taskfile{Tasks: tasks},
|
||||
[]taskfile.Call{{Task: "t1"}, {Task: "t2"}, {Task: "t3"}})
|
||||
|
||||
assert.True(t, strings.HasPrefix(buffer.String(), "task: t1"))
|
||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t2")
|
||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t3")
|
||||
|
||||
}
|
||||
56
internal/templater/funcs.go
Normal file
56
internal/templater/funcs.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package templater
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
sprig "github.com/go-task/slim-sprig"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
var (
|
||||
templateFuncs template.FuncMap
|
||||
)
|
||||
|
||||
func init() {
|
||||
taskFuncs := template.FuncMap{
|
||||
"OS": func() string { return runtime.GOOS },
|
||||
"ARCH": func() string { return runtime.GOARCH },
|
||||
"catLines": func(s string) string {
|
||||
s = strings.Replace(s, "\r\n", " ", -1)
|
||||
return strings.Replace(s, "\n", " ", -1)
|
||||
},
|
||||
"splitLines": func(s string) []string {
|
||||
s = strings.Replace(s, "\r\n", "\n", -1)
|
||||
return strings.Split(s, "\n")
|
||||
},
|
||||
"fromSlash": func(path string) string {
|
||||
return filepath.FromSlash(path)
|
||||
},
|
||||
"toSlash": func(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
},
|
||||
"exeExt": func() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return ".exe"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"shellQuote": func(str string) (string, error) {
|
||||
return syntax.Quote(str, syntax.LangBash)
|
||||
},
|
||||
// IsSH is deprecated.
|
||||
"IsSH": func() bool { return true },
|
||||
}
|
||||
// Deprecated aliases for renamed functions.
|
||||
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
|
||||
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
||||
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
||||
|
||||
templateFuncs = sprig.TxtFuncMap()
|
||||
for k, v := range taskFuncs {
|
||||
templateFuncs[k] = v
|
||||
}
|
||||
}
|
||||
85
internal/templater/templater.go
Normal file
85
internal/templater/templater.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package templater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// Templater is a help struct that allow us to call "replaceX" funcs multiple
|
||||
// times, without having to check for error each time. The first error that
|
||||
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
||||
// return the zero value.
|
||||
type Templater struct {
|
||||
Vars *taskfile.Vars
|
||||
RemoveNoValue bool
|
||||
|
||||
cacheMap map[string]interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *Templater) ResetCache() {
|
||||
r.cacheMap = r.Vars.ToCacheMap()
|
||||
}
|
||||
|
||||
func (r *Templater) Replace(str string) string {
|
||||
if r.err != nil || str == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
templ, err := template.New("").Funcs(templateFuncs).Parse(str)
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return ""
|
||||
}
|
||||
|
||||
if r.cacheMap == nil {
|
||||
r.cacheMap = r.Vars.ToCacheMap()
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
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()
|
||||
}
|
||||
|
||||
func (r *Templater) ReplaceSlice(strs []string) []string {
|
||||
if r.err != nil || len(strs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
new := make([]string, len(strs))
|
||||
for i, str := range strs {
|
||||
new[i] = r.Replace(str)
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
func (r *Templater) ReplaceVars(vars *taskfile.Vars) *taskfile.Vars {
|
||||
if r.err != nil || vars.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 nil
|
||||
})
|
||||
|
||||
return &new
|
||||
}
|
||||
|
||||
func (r *Templater) Err() error {
|
||||
return r.err
|
||||
}
|
||||
32
precondition.go
Normal file
32
precondition.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPreconditionFailed is returned when a precondition fails
|
||||
ErrPreconditionFailed = errors.New("task: precondition not met")
|
||||
)
|
||||
|
||||
func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||
for _, p := range t.Preconditions {
|
||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: p.Sh,
|
||||
Dir: t.Dir,
|
||||
Env: getEnviron(t),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
e.Logger.Errf(logger.Magenta, "task: %s", p.Msg)
|
||||
return false, ErrPreconditionFailed
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"runtime"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/imdario/mergo"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func readTaskfile() (map[string]*Task, error) {
|
||||
initialTasks, err := readTaskfileData(TaskFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergeTasks, err := readTaskfileData(fmt.Sprintf("%s_%s", TaskFilePath, runtime.GOOS))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
default:
|
||||
return nil, err
|
||||
case taskFileNotFound:
|
||||
return initialTasks, nil
|
||||
}
|
||||
}
|
||||
if err := mergo.MapWithOverwrite(&initialTasks, mergeTasks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return initialTasks, nil
|
||||
}
|
||||
|
||||
func readTaskfileData(path string) (tasks map[string]*Task, err error) {
|
||||
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
|
||||
return tasks, yaml.Unmarshal(b, &tasks)
|
||||
}
|
||||
if b, err := ioutil.ReadFile(path + ".json"); err == nil {
|
||||
return tasks, json.Unmarshal(b, &tasks)
|
||||
}
|
||||
if b, err := ioutil.ReadFile(path + ".toml"); err == nil {
|
||||
return tasks, toml.Unmarshal(b, &tasks)
|
||||
}
|
||||
return nil, taskFileNotFound{path}
|
||||
}
|
||||
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
121
status.go
Normal file
121
status.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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
|
||||
func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
|
||||
for _, call := range calls {
|
||||
t, err := e.CompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isUpToDate, err := e.isTaskUpToDate(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isUpToDate {
|
||||
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.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 {
|
||||
isUpToDate, err := e.isTaskUpToDateStatus(ctx, t)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isUpToDate {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
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 true, nil
|
||||
}
|
||||
|
||||
func (e *Executor) statusOnError(t *taskfile.Task) error {
|
||||
checker, err := e.getStatusChecker(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checker.OnError()
|
||||
}
|
||||
|
||||
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
||||
method := t.Method
|
||||
if method == "" {
|
||||
method = e.Taskfile.Method
|
||||
}
|
||||
switch method {
|
||||
case "timestamp":
|
||||
return e.timestampChecker(t), nil
|
||||
case "checksum":
|
||||
return e.checksumChecker(t), nil
|
||||
case "none":
|
||||
return status.None{}, nil
|
||||
default:
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||
for _, s := range t.Status {
|
||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: s,
|
||||
Dir: t.Dir,
|
||||
Env: getEnviron(t),
|
||||
})
|
||||
if err != nil {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err)
|
||||
return false, nil
|
||||
}
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
646
task.go
646
task.go
@@ -1,182 +1,536 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
|
||||
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/summary"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/read"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var (
|
||||
// TaskFilePath is the default Taskfile
|
||||
TaskFilePath = "Taskfile"
|
||||
// ShExists is true if Bash was found
|
||||
ShExists bool
|
||||
// ShPath constains the Bash path if found
|
||||
ShPath string
|
||||
|
||||
// Force (--force or -f flag) forces a task to run even when it's up-to-date
|
||||
Force bool
|
||||
|
||||
// Tasks constains the tasks parsed from Taskfile
|
||||
Tasks = make(map[string]*Task)
|
||||
|
||||
runnedTasks = make(map[string]struct{})
|
||||
const (
|
||||
// MaximumTaskCall is the max number of times a task can be called.
|
||||
// This exists to prevent infinite loops on cyclic dependencies
|
||||
MaximumTaskCall = 100
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
ShPath, err = exec.LookPath("sh")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ShExists = true
|
||||
}
|
||||
// Executor executes a Taskfile
|
||||
type Executor struct {
|
||||
Taskfile *taskfile.Taskfile
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
Cmds []string
|
||||
Deps []string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Dir string
|
||||
Vars map[string]string
|
||||
Set string
|
||||
Env map[string]string
|
||||
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
|
||||
Stderr io.Writer
|
||||
|
||||
Logger *logger.Logger
|
||||
Compiler compiler.Compiler
|
||||
Output output.Output
|
||||
OutputStyle taskfile.Output
|
||||
|
||||
taskvars *taskfile.Vars
|
||||
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
executionHashes map[string]context.Context
|
||||
executionHashesMutex sync.Mutex
|
||||
}
|
||||
|
||||
// Run runs Task
|
||||
func Run() {
|
||||
log.SetFlags(0)
|
||||
|
||||
args := pflag.Args()
|
||||
if len(args) == 0 {
|
||||
log.Fatal("task: No argument given")
|
||||
}
|
||||
|
||||
var err error
|
||||
Tasks, err = readTaskfile()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, a := range args {
|
||||
if err = RunTask(a); err != nil {
|
||||
log.Fatal(err)
|
||||
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
// check if given tasks exist
|
||||
for _, c := range calls {
|
||||
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
||||
// FIXME: move to the main package
|
||||
e.ListTasksWithDesc()
|
||||
return &taskNotFoundError{taskName: c.Task}
|
||||
}
|
||||
}
|
||||
|
||||
if e.Summary {
|
||||
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
|
||||
}
|
||||
|
||||
if e.Watch {
|
||||
return e.watchTasks(calls...)
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
for _, c := range calls {
|
||||
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 g.Wait()
|
||||
}
|
||||
|
||||
// readTaskfile selects and parses the entrypoint.
|
||||
func (e *Executor) readTaskfile() error {
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(&read.ReaderNode{
|
||||
Dir: e.Dir,
|
||||
Entrypoint: e.Entrypoint,
|
||||
Parent: nil,
|
||||
Optional: false,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup setups Executor's internal state
|
||||
func (e *Executor) Setup() error {
|
||||
err := e.readTaskfile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if e.Stdout == nil {
|
||||
e.Stdout = os.Stdout
|
||||
}
|
||||
if e.Stderr == nil {
|
||||
e.Stderr = os.Stderr
|
||||
}
|
||||
e.Logger = &logger.Logger{
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
Verbose: e.Verbose,
|
||||
Color: e.Color,
|
||||
}
|
||||
|
||||
if v < 2 {
|
||||
return fmt.Errorf(`task: Taskfile versions prior to v2 are not supported anymore`)
|
||||
}
|
||||
|
||||
// consider as equal to the greater version if round
|
||||
if v == 2.0 {
|
||||
v = 2.6
|
||||
}
|
||||
if v == 3.0 {
|
||||
v = 3.8
|
||||
}
|
||||
|
||||
if v > 3.8 {
|
||||
return fmt.Errorf(`task: Taskfile versions greater than v3.8 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,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
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.IsSet() {
|
||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||
}
|
||||
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 v < 3.8 && e.Taskfile.Output.Group.IsSet() {
|
||||
return fmt.Errorf(`task: Taskfile option "output.group" is only available starting on Taskfile version v3.8`)
|
||||
}
|
||||
|
||||
if !e.OutputStyle.IsSet() {
|
||||
e.OutputStyle = e.Taskfile.Output
|
||||
}
|
||||
e.Output, err = output.BuildFor(&e.OutputStyle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.Taskfile.Method == "" {
|
||||
if v >= 3 {
|
||||
e.Taskfile.Method = "checksum"
|
||||
} else {
|
||||
e.Taskfile.Method = "timestamp"
|
||||
}
|
||||
}
|
||||
|
||||
if v <= 2.1 {
|
||||
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.IgnoreError {
|
||||
return err
|
||||
}
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd.IgnoreError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v < 2.6 {
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if len(task.Preconditions) > 0 {
|
||||
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// RunTask runs a task by its name
|
||||
func RunTask(name string) error {
|
||||
if _, found := runnedTasks[name]; found {
|
||||
return &cyclicDepError{name}
|
||||
}
|
||||
runnedTasks[name] = struct{}{}
|
||||
|
||||
t, ok := Tasks[name]
|
||||
if !ok {
|
||||
return &taskNotFoundError{name}
|
||||
}
|
||||
|
||||
vars, err := t.handleVariables()
|
||||
func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
t, err := e.CompiledTask(call)
|
||||
if err != nil {
|
||||
return &taskRunError{name, err}
|
||||
return err
|
||||
}
|
||||
if !e.Watch && atomic.AddInt32(e.taskCallCount[call.Task], 1) >= MaximumTaskCall {
|
||||
return &MaximumTaskCallExceededError{task: call.Task}
|
||||
}
|
||||
|
||||
release := e.acquireConcurrencyLimit()
|
||||
defer release()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if !e.Force {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return 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
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
}
|
||||
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" finished`, call.Task)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Executor) mkdir(t *taskfile.Task) error {
|
||||
if t.Dir == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
mutex := e.mkdirMutexMap[t.Task]
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if _, err := os.Stat(t.Dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(t.Dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
defer reacquire()
|
||||
|
||||
for _, d := range t.Deps {
|
||||
d, err = ReplaceVariables(d, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = RunTask(d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
d := d
|
||||
|
||||
if !Force && t.isUpToDate() {
|
||||
log.Printf(`task: Task "%s" is up to date`, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range t.Cmds {
|
||||
if err = t.runCommand(i); err != nil {
|
||||
return &taskRunError{name, err}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) isUpToDate() bool {
|
||||
if len(t.Sources) == 0 || len(t.Generates) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
sourcesMaxTime, err := getPatternsMaxTime(t.Sources)
|
||||
if err != nil || sourcesMaxTime.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
generatesMinTime, err := getPatternsMinTime(t.Generates)
|
||||
if err != nil || generatesMinTime.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
return generatesMinTime.After(sourcesMaxTime)
|
||||
}
|
||||
|
||||
func (t *Task) runCommand(i int) error {
|
||||
vars, err := t.handleVariables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := ReplaceVariables(t.Cmds[i], vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir, err := ReplaceVariables(t.Dir, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cmd *exec.Cmd
|
||||
if ShExists {
|
||||
cmd = exec.Command(ShPath, "-c", c)
|
||||
} else {
|
||||
cmd = exec.Command("cmd", "/C", c)
|
||||
}
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
if t.Env != nil {
|
||||
cmd.Env = os.Environ()
|
||||
for key, value := range t.Env {
|
||||
replacedValue, err := ReplaceVariables(value, vars)
|
||||
g.Go(func() error {
|
||||
err := e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
replacedKey, err := ReplaceVariables(key, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", replacedKey, replacedValue))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
if t.Set != "" {
|
||||
bytes, err := cmd.Output()
|
||||
|
||||
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
|
||||
}
|
||||
os.Setenv(t.Set, strings.TrimSpace(string(bytes)))
|
||||
return nil
|
||||
case 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
|
||||
}
|
||||
|
||||
outputWrapper := e.Output
|
||||
if t.Interactive {
|
||||
outputWrapper = output.Interleaved{}
|
||||
}
|
||||
vars, err := e.Compiler.FastGetVariables(t, call)
|
||||
outputTemplater := &templater.Templater{Vars: vars, RemoveNoValue: true}
|
||||
if err != nil {
|
||||
return fmt.Errorf("task: failed to get variables: %w", err)
|
||||
}
|
||||
stdOut := outputWrapper.WrapWriter(e.Stdout, t.Prefix, outputTemplater)
|
||||
stdErr := outputWrapper.WrapWriter(e.Stderr, t.Prefix, outputTemplater)
|
||||
|
||||
defer func() {
|
||||
if _, ok := stdOut.(*os.File); !ok {
|
||||
if closer, ok := stdOut.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
if _, ok := stdErr.(*os.File); !ok {
|
||||
if closer, ok := stdErr.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: cmd.Cmd,
|
||||
Dir: t.Dir,
|
||||
Env: getEnviron(t),
|
||||
Stdin: e.Stdin,
|
||||
Stdout: stdOut,
|
||||
Stderr: stdErr,
|
||||
})
|
||||
if execext.IsExitError(err) && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v", t.Name(), err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
if err = cmd.Run(); err != nil {
|
||||
}
|
||||
|
||||
func getEnviron(t *taskfile.Task) []string {
|
||||
if t.Env == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
environ := os.Environ()
|
||||
|
||||
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
|
||||
}
|
||||
return nil
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
1278
task_test.go
Normal file
1278
task_test.go
Normal file
File diff suppressed because it is too large
Load Diff
7
taskfile/call.go
Normal file
7
taskfile/call.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package taskfile
|
||||
|
||||
// Call is the parameters to a task call
|
||||
type Call struct {
|
||||
Task string
|
||||
Vars *Vars
|
||||
}
|
||||
83
taskfile/cmd.go
Normal file
83
taskfile/cmd.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package taskfile
|
||||
|
||||
// Cmd is a task command
|
||||
type Cmd struct {
|
||||
Cmd string
|
||||
Silent bool
|
||||
Task string
|
||||
Vars *Vars
|
||||
IgnoreError bool
|
||||
Defer bool
|
||||
}
|
||||
|
||||
// Dep is a task dependency
|
||||
type Dep struct {
|
||||
Task string
|
||||
Vars *Vars
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var cmd string
|
||||
if err := unmarshal(&cmd); err == nil {
|
||||
c.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
var cmdStruct struct {
|
||||
Cmd string
|
||||
Silent bool
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
}
|
||||
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
|
||||
c.Cmd = cmdStruct.Cmd
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.IgnoreError = cmdStruct.IgnoreError
|
||||
return nil
|
||||
}
|
||||
var deferredCmd struct {
|
||||
Defer string
|
||||
}
|
||||
if err := unmarshal(&deferredCmd); err == nil && deferredCmd.Defer != "" {
|
||||
c.Defer = true
|
||||
c.Cmd = deferredCmd.Defer
|
||||
return nil
|
||||
}
|
||||
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
|
||||
func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var task string
|
||||
if err := unmarshal(&task); err == nil {
|
||||
d.Task = task
|
||||
return nil
|
||||
}
|
||||
var taskCall struct {
|
||||
Task string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := unmarshal(&taskCall); err != nil {
|
||||
return err
|
||||
}
|
||||
d.Task = taskCall.Task
|
||||
d.Vars = taskCall.Vars
|
||||
return nil
|
||||
}
|
||||
109
taskfile/included_taskfile.go
Normal file
109
taskfile/included_taskfile.go
Normal file
@@ -0,0 +1,109 @@
|
||||
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
|
||||
Vars *Vars
|
||||
}
|
||||
|
||||
// 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
|
||||
Vars *Vars
|
||||
}
|
||||
if err := unmarshal(&includedTaskfile); err != nil {
|
||||
return err
|
||||
}
|
||||
it.Taskfile = includedTaskfile.Taskfile
|
||||
it.Dir = includedTaskfile.Dir
|
||||
it.Optional = includedTaskfile.Optional
|
||||
it.AdvancedImport = true
|
||||
it.Vars = includedTaskfile.Vars
|
||||
return nil
|
||||
}
|
||||
66
taskfile/merge.go
Normal file
66
taskfile/merge.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NamespaceSeparator contains the character that separates namescapes
|
||||
const NamespaceSeparator = ":"
|
||||
|
||||
// Merge merges the second Taskfile into the first
|
||||
func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
||||
if t1.Version != t2.Version {
|
||||
return fmt.Errorf(`Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
||||
}
|
||||
|
||||
if t2.Expansions != 0 && t2.Expansions != 2 {
|
||||
t1.Expansions = t2.Expansions
|
||||
}
|
||||
if t2.Output.IsSet() {
|
||||
t1.Output = t2.Output
|
||||
}
|
||||
|
||||
if t1.Includes == nil {
|
||||
t1.Includes = &IncludedTaskfiles{}
|
||||
}
|
||||
t1.Includes.Merge(t2.Includes)
|
||||
|
||||
if t1.Vars == nil {
|
||||
t1.Vars = &Vars{}
|
||||
}
|
||||
if t1.Env == nil {
|
||||
t1.Env = &Vars{}
|
||||
}
|
||||
t1.Vars.Merge(t2.Vars)
|
||||
t1.Env.Merge(t2.Env)
|
||||
|
||||
if t1.Tasks == nil {
|
||||
t1.Tasks = make(Tasks)
|
||||
}
|
||||
for k, v := range t2.Tasks {
|
||||
// FIXME(@andreynering): Refactor this block, otherwise we can
|
||||
// have serious side-effects in the future, since we're editing
|
||||
// the original references instead of deep copying them.
|
||||
|
||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = v
|
||||
|
||||
for _, dep := range v.Deps {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, namespaces...)
|
||||
}
|
||||
for _, cmd := range v.Cmds {
|
||||
if cmd != nil && cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskNameWithNamespace(taskName string, namespaces ...string) string {
|
||||
if strings.HasPrefix(taskName, ":") {
|
||||
return strings.TrimPrefix(taskName, ":")
|
||||
}
|
||||
return strings.Join(append(namespaces, taskName), NamespaceSeparator)
|
||||
}
|
||||
55
taskfile/output.go
Normal file
55
taskfile/output.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Output of the Task output
|
||||
type Output struct {
|
||||
// Name of the Output.
|
||||
Name string `yaml:"-"`
|
||||
// Group specific style
|
||||
Group OutputGroup
|
||||
}
|
||||
|
||||
// IsSet returns true if and only if a custom output style is set.
|
||||
func (s *Output) IsSet() bool {
|
||||
return s.Name != ""
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler
|
||||
// It accepts a scalar node representing the Output.Name or a mapping node representing the OutputGroup.
|
||||
func (s *Output) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var name string
|
||||
if err := unmarshal(&name); err == nil {
|
||||
s.Name = name
|
||||
return nil
|
||||
}
|
||||
var tmp struct {
|
||||
Group *OutputGroup
|
||||
}
|
||||
if err := unmarshal(&tmp); err != nil {
|
||||
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
|
||||
}
|
||||
if tmp.Group == nil {
|
||||
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
|
||||
}
|
||||
*s = Output{
|
||||
Name: "group",
|
||||
Group: *tmp.Group,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutputGroup is the style options specific to the Group style.
|
||||
type OutputGroup struct {
|
||||
Begin, End string
|
||||
}
|
||||
|
||||
// IsSet returns true if and only if a custom output style is set.
|
||||
func (g *OutputGroup) IsSet() bool {
|
||||
if g == nil {
|
||||
return false
|
||||
}
|
||||
return g.Begin != "" || g.End != ""
|
||||
}
|
||||
45
taskfile/precondition.go
Normal file
45
taskfile/precondition.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
|
||||
ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
|
||||
)
|
||||
|
||||
// Precondition represents a precondition necessary for a task to run
|
||||
type Precondition struct {
|
||||
Sh string
|
||||
Msg string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (p *Precondition) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var cmd string
|
||||
|
||||
if err := unmarshal(&cmd); err == nil {
|
||||
p.Sh = cmd
|
||||
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
var sh struct {
|
||||
Sh string
|
||||
Msg string
|
||||
}
|
||||
|
||||
if err := unmarshal(&sh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Sh = sh.Sh
|
||||
p.Msg = sh.Msg
|
||||
if p.Msg == "" {
|
||||
p.Msg = fmt.Sprintf("%s failed", sh.Sh)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
48
taskfile/precondition_test.go
Normal file
48
taskfile/precondition_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package taskfile_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func TestPreconditionParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
content string
|
||||
v interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
"test -f foo.txt",
|
||||
&taskfile.Precondition{},
|
||||
&taskfile.Precondition{Sh: `test -f foo.txt`, Msg: "`test -f foo.txt` failed"},
|
||||
},
|
||||
{
|
||||
"sh: '[ 1 = 0 ]'",
|
||||
&taskfile.Precondition{},
|
||||
&taskfile.Precondition{Sh: "[ 1 = 0 ]", Msg: "[ 1 = 0 ] failed"},
|
||||
},
|
||||
{`
|
||||
sh: "[ 1 = 2 ]"
|
||||
msg: "1 is not 2"
|
||||
`,
|
||||
&taskfile.Precondition{},
|
||||
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
||||
},
|
||||
{`
|
||||
sh: "[ 1 = 2 ]"
|
||||
msg: "1 is not 2"
|
||||
`,
|
||||
&taskfile.Precondition{},
|
||||
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := yaml.Unmarshal([]byte(test.content), test.v)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, test.v)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
218
taskfile/read/taskfile.go
Normal file
218
taskfile/read/taskfile.go
Normal file
@@ -0,0 +1,218 @@
|
||||
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 (
|
||||
// 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.dist.yml",
|
||||
"Taskfile.dist.yaml",
|
||||
}
|
||||
)
|
||||
|
||||
type ReaderNode struct {
|
||||
Dir string
|
||||
Entrypoint string
|
||||
Optional bool
|
||||
Parent *ReaderNode
|
||||
}
|
||||
|
||||
// Taskfile reads a Taskfile for a given directory
|
||||
// Uses current dir when dir is left empty. Uses Taskfile.yml
|
||||
// or Taskfile.yaml when entrypoint is left empty
|
||||
func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
if readerNode.Dir == "" {
|
||||
d, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readerNode.Dir = d
|
||||
}
|
||||
path, err := exists(filepath.Join(readerNode.Dir, readerNode.Entrypoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readerNode.Entrypoint = filepath.Base(path)
|
||||
|
||||
t, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
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,
|
||||
Vars: includedTask.Vars,
|
||||
}
|
||||
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(readerNode.Dir, path)
|
||||
}
|
||||
path, err = exists(path)
|
||||
if err != nil {
|
||||
if includedTask.Optional {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
includeReaderNode := &ReaderNode{
|
||||
Dir: filepath.Dir(path),
|
||||
Entrypoint: filepath.Base(path),
|
||||
Parent: readerNode,
|
||||
Optional: includedTask.Optional,
|
||||
}
|
||||
|
||||
if err := checkCircularIncludes(includeReaderNode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
includedTaskfile, err := Taskfile(includeReaderNode)
|
||||
if err != nil {
|
||||
if includedTask.Optional {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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(readerNode.Dir, includedTask.Dir)
|
||||
includedTaskfile.Vars.Mapping[k] = o
|
||||
}
|
||||
for k, v := range includedTaskfile.Env.Mapping {
|
||||
o := v
|
||||
o.Dir = filepath.Join(readerNode.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)
|
||||
}
|
||||
task.IncludeVars = includedTask.Vars
|
||||
task.IncludedTaskfileVars = includedTaskfile.Vars
|
||||
}
|
||||
}
|
||||
|
||||
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(readerNode.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)
|
||||
}
|
||||
|
||||
func checkCircularIncludes(node *ReaderNode) error {
|
||||
if node == nil {
|
||||
return errors.New("task: failed to check for include cycle: node was nil")
|
||||
}
|
||||
if node.Parent == nil {
|
||||
return errors.New("task: failed to check for include cycle: node.Parent was nil")
|
||||
}
|
||||
var curNode = node
|
||||
var basePath = filepath.Join(node.Dir, node.Entrypoint)
|
||||
for curNode.Parent != nil {
|
||||
curNode = curNode.Parent
|
||||
curPath := filepath.Join(curNode.Dir, curNode.Entrypoint)
|
||||
if curPath == basePath {
|
||||
return fmt.Errorf("task: include cycle detected between %s <--> %s",
|
||||
curPath,
|
||||
filepath.Join(node.Parent.Dir, node.Parent.Entrypoint),
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
45
taskfile/read/taskvars.go
Normal file
45
taskfile/read/taskvars.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package read
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// Taskvars reads a Taskvars for a given directory
|
||||
func Taskvars(dir string) (*taskfile.Vars, error) {
|
||||
vars := &taskfile.Vars{}
|
||||
|
||||
path := filepath.Join(dir, "Taskvars.yml")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
vars, err = readTaskvars(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
path = filepath.Join(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
osVars, err := readTaskvars(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vars.Merge(osVars)
|
||||
}
|
||||
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
func readTaskvars(file string) (*taskfile.Vars, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vars taskfile.Vars
|
||||
return &vars, yaml.NewDecoder(f).Decode(&vars)
|
||||
}
|
||||
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
|
||||
}
|
||||
93
taskfile/task.go
Normal file
93
taskfile/task.go
Normal file
@@ -0,0 +1,93 @@
|
||||
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
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
}
|
||||
|
||||
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 Output
|
||||
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 Output
|
||||
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
|
||||
}
|
||||
2
testdata/checksum/.gitignore
vendored
Normal file
2
testdata/checksum/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.task/
|
||||
generated.txt
|
||||
12
testdata/checksum/Taskfile.yml
vendored
Normal file
12
testdata/checksum/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- cp ./source.txt ./generated.txt
|
||||
sources:
|
||||
- ./**/glob-with-inexistent-file.txt
|
||||
- ./source.txt
|
||||
generates:
|
||||
- ./generated.txt
|
||||
method: checksum
|
||||
1
testdata/checksum/source.txt
vendored
Normal file
1
testdata/checksum/source.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello, World!
|
||||
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
|
||||
10
testdata/cyclic/Taskfile.yml
vendored
Normal file
10
testdata/cyclic/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
task-1:
|
||||
deps:
|
||||
- task: task-2
|
||||
|
||||
task-2:
|
||||
deps:
|
||||
- task: task-1
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user