mirror of
https://github.com/go-task/task.git
synced 2026-06-23 20:55:52 +00:00
Compare commits
178 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c420f3a34 | ||
|
|
84da80356d | ||
|
|
bcbb85eac3 | ||
|
|
0e1d8a72e6 | ||
|
|
bbdd698869 | ||
|
|
8f684ffa6d | ||
|
|
9be3666fe7 | ||
|
|
b7785678f4 | ||
|
|
d8005b4cf6 | ||
|
|
52028fc3bc | ||
|
|
5285ec23ae | ||
|
|
3c882e5c57 | ||
|
|
ad569a8a36 | ||
|
|
0d9fdbaac1 | ||
|
|
f5cd3eab9e | ||
|
|
8987cd64a0 | ||
|
|
fac51dcf03 | ||
|
|
01101a4c9b | ||
|
|
d561e40817 | ||
|
|
0cb298ebdf | ||
|
|
a149368725 | ||
|
|
afeefe8259 | ||
|
|
690d3c27a2 | ||
|
|
3d56ea5ce5 | ||
|
|
fdff7f80a3 | ||
|
|
fe6978b107 | ||
|
|
57db6865d2 | ||
|
|
d235d5ab28 | ||
|
|
613dfe06d3 | ||
|
|
a312d61d68 | ||
|
|
e414c1f7b0 | ||
|
|
955359b073 | ||
|
|
26e0c0887a | ||
|
|
4c295b564a | ||
|
|
2eb52da0db | ||
|
|
d8bfb3ab13 | ||
|
|
d970e93507 | ||
|
|
762714de68 | ||
|
|
82a3651a18 | ||
|
|
abe0352de9 | ||
|
|
4cee4aa5a8 | ||
|
|
9c68c7c50b | ||
|
|
0608782cfa | ||
|
|
edeaf3794a | ||
|
|
fe2b8c8afa | ||
|
|
b66bf58064 | ||
|
|
957dfa9cdf | ||
|
|
cc9264854e | ||
|
|
d1463b3e24 | ||
|
|
f1082520e1 | ||
|
|
733c563194 | ||
|
|
0200d043c3 | ||
|
|
9c475c36e7 | ||
|
|
c663c5c507 | ||
|
|
1e93c38307 | ||
|
|
81baf808c9 | ||
|
|
74537689dc | ||
|
|
12ab01d5e6 | ||
|
|
044d3a0ff9 | ||
|
|
659cae6a4c | ||
|
|
bd5882f0f0 | ||
|
|
6ff9ba9df9 | ||
|
|
b2df398a12 | ||
|
|
83d618e1eb | ||
|
|
f0768b3af1 | ||
|
|
0233ce52ed | ||
|
|
6e6f337509 | ||
|
|
1546415b8f | ||
|
|
20725c69bf | ||
|
|
90613220c6 | ||
|
|
659fd2ae93 | ||
|
|
29d899f7da | ||
|
|
902a0a01a9 | ||
|
|
8001fb3915 | ||
|
|
e81e2802f0 | ||
|
|
1ee066ec42 | ||
|
|
53d54d1c4a | ||
|
|
10082b60b8 | ||
|
|
c5b9773922 | ||
|
|
de11323d28 | ||
|
|
9f269e1a95 | ||
|
|
e4204168a0 | ||
|
|
9c350f8ef1 | ||
|
|
db19fdac29 | ||
|
|
d516b238b1 | ||
|
|
f9330f6cd9 | ||
|
|
360da29e1f | ||
|
|
9cfac1642a | ||
|
|
db90e87d10 | ||
|
|
b7564080bc | ||
|
|
1d783bf6c7 | ||
|
|
1025c2e3a1 | ||
|
|
4fd82ab222 | ||
|
|
8eadfc1bf6 | ||
|
|
f66edbad50 | ||
|
|
c7f17b5319 | ||
|
|
23c4adcef6 | ||
|
|
808542bed0 | ||
|
|
93bfd57856 | ||
|
|
7e7e1bccba | ||
|
|
34f6da86c3 | ||
|
|
15c0381c3c | ||
|
|
c2f4a57e02 | ||
|
|
f945cf2343 | ||
|
|
5bca3cfd71 | ||
|
|
26ce4e6886 | ||
|
|
f5f0e0c376 | ||
|
|
9dea1e7f3e | ||
|
|
c2e0f8c81f | ||
|
|
d341bc25ce | ||
|
|
0379e2b51b | ||
|
|
e79026b840 | ||
|
|
fc34d6b56f | ||
|
|
2a1571a99e | ||
|
|
c158608255 | ||
|
|
3ca590b185 | ||
|
|
3f8ee21849 | ||
|
|
845b88a193 | ||
|
|
e252972c7f | ||
|
|
a9012ebfc5 | ||
|
|
5cfd9bbbbd | ||
|
|
c82a7240bb | ||
|
|
a4a20d92a4 | ||
|
|
890996f595 | ||
|
|
474f27c6d3 | ||
|
|
33f3894372 | ||
|
|
24436ac76e | ||
|
|
3ee66ef705 | ||
|
|
a1765e1d33 | ||
|
|
765e3dbf72 | ||
|
|
80f5cee599 | ||
|
|
4dcb124693 | ||
|
|
31ecf167cc | ||
|
|
3999480d64 | ||
|
|
9dbb503c23 | ||
|
|
a98f803d87 | ||
|
|
9e9ffeb5d5 | ||
|
|
33d4ad4d84 | ||
|
|
d05d418c4c | ||
|
|
06d0af7a1d | ||
|
|
9a3b726068 | ||
|
|
2676ab9a59 | ||
|
|
a1837d553e | ||
|
|
fdbc130d8d | ||
|
|
4b3cea3812 | ||
|
|
1c3082ffa6 | ||
|
|
0446cfdba0 | ||
|
|
db1d3183b6 | ||
|
|
fb666394fc | ||
|
|
1054c89a9d | ||
|
|
8dd87dc482 | ||
|
|
b2edbf05a1 | ||
|
|
6fb53a406b | ||
|
|
b05fa0821d | ||
|
|
0a808b1212 | ||
|
|
f1d83e92a7 | ||
|
|
31b60f7f60 | ||
|
|
c0f9af5daa | ||
|
|
b25a9e8884 | ||
|
|
3c0cf3cd55 | ||
|
|
1ac6f17e6a | ||
|
|
399a2b38f3 | ||
|
|
b97221cdb2 | ||
|
|
0164bc21ea | ||
|
|
5a23250d32 | ||
|
|
80d88d9789 | ||
|
|
31ead854c7 | ||
|
|
4b64fcb8a4 | ||
|
|
a951f2403d | ||
|
|
f9adeba7f1 | ||
|
|
5c823d51d0 | ||
|
|
9be7521b83 | ||
|
|
c73ddc3552 | ||
|
|
4b7f058f41 | ||
|
|
07221a1b20 | ||
|
|
13614fb3c4 | ||
|
|
4fa983bde7 | ||
|
|
9cb1db8c0a |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
open_collective: task
|
||||||
59
.github/workflows/test.yml
vendored
Normal file
59
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
name: Test
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
linux:
|
||||||
|
name: Linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build -o ./task -v ./cmd/task
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: ./task test
|
||||||
|
|
||||||
|
windows:
|
||||||
|
name: Windows
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go install -v ./cmd/task
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v ./...
|
||||||
|
|
||||||
|
macos:
|
||||||
|
name: MacOS
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build -o ./task -v ./cmd/task
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: ./task test
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -18,3 +18,11 @@
|
|||||||
dist/
|
dist/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# intellij idea/goland
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# exuberant ctags
|
||||||
|
tags
|
||||||
|
|
||||||
|
/bin
|
||||||
|
|||||||
@@ -14,12 +14,11 @@ build:
|
|||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
|
|
||||||
archive:
|
archives:
|
||||||
name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||||
|
format_overrides:
|
||||||
format_overrides:
|
- goos: windows
|
||||||
- goos: windows
|
format: zip
|
||||||
format: zip
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
draft: true
|
draft: true
|
||||||
@@ -30,15 +29,15 @@ snapshot:
|
|||||||
checksum:
|
checksum:
|
||||||
name_template: "task_checksums.txt"
|
name_template: "task_checksums.txt"
|
||||||
|
|
||||||
nfpm:
|
nfpms:
|
||||||
vendor: Task
|
- vendor: Task
|
||||||
homepage: https://github.com/go-task/task
|
homepage: https://github.com/go-task/task
|
||||||
maintainer: Andrey Nering <andrey.nering@gmail.com>
|
maintainer: Andrey Nering <andrey.nering@gmail.com>
|
||||||
description: Simple task runner written in Go
|
description: Simple task runner written in Go
|
||||||
license: MIT
|
license: MIT
|
||||||
conflicts:
|
conflicts:
|
||||||
- taskwarrior
|
- taskwarrior
|
||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.10.x
|
- 1.12.x
|
||||||
- 1.11.x
|
- 1.13.x
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
@@ -10,7 +10,7 @@ addons:
|
|||||||
- rpm
|
- rpm
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go install github.com/go-task/task/cmd/task
|
- go install -v ./cmd/task
|
||||||
- task ci
|
- task ci
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
|
|||||||
45
CHANGELOG.md
45
CHANGELOG.md
@@ -1,5 +1,50 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
## v2.4.0 - 2019-02-21
|
||||||
|
|
||||||
- Allow calling a task of the root Taskfile from an included Taskfile
|
- Allow calling a task of the root Taskfile from an included Taskfile
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
Task is a task runner / build tool that aims to be simpler and easier to use
|
Task is a task runner / build tool that aims to be simpler and easier to use
|
||||||
than, for example, [GNU Make](https://www.gnu.org/software/make/).
|
than, for example, [GNU Make](https://www.gnu.org/software/make/).
|
||||||
|
|
||||||
See [taskfile.org](https://taskfile.org) for documentation.
|
See [taskfile.dev](https://taskfile.dev) for documentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
421
cmd/redirector/redirector.go
Normal file
421
cmd/redirector/redirector.go
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
// This small web app is used to redirect from the old taskfile.org domain
|
||||||
|
// to the new taskfile.dev without breaking CIs that uses cURL to download
|
||||||
|
// "/install.sh" without the -L flag (which follow redirects).
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/install.sh" {
|
||||||
|
println("Dumping install.sh")
|
||||||
|
|
||||||
|
w.Write(installShContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Redirecting to https://taskfile.dev" + r.URL.Path)
|
||||||
|
|
||||||
|
w.Header().Set("Location", "https://taskfile.dev"+r.URL.Path)
|
||||||
|
w.WriteHeader(301)
|
||||||
|
})
|
||||||
|
|
||||||
|
println("Listening :8080")
|
||||||
|
|
||||||
|
panic(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
var installShContent = []byte(`#!/bin/sh
|
||||||
|
set -e
|
||||||
|
# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT.
|
||||||
|
#
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
this=$1
|
||||||
|
cat <<EOF
|
||||||
|
$this: download go binaries for go-task/task
|
||||||
|
|
||||||
|
Usage: $this [-b] bindir [-d] [tag]
|
||||||
|
-b sets bindir or installation directory, Defaults to ./bin
|
||||||
|
-d turns on debug logging
|
||||||
|
[tag] is a tag from
|
||||||
|
https://github.com/go-task/task/releases
|
||||||
|
If tag is missing, then the latest will be used.
|
||||||
|
|
||||||
|
Generated by godownloader
|
||||||
|
https://github.com/goreleaser/godownloader
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
#BINDIR is ./bin unless set be ENV
|
||||||
|
# over-ridden by flag below
|
||||||
|
|
||||||
|
BINDIR=${BINDIR:-./bin}
|
||||||
|
while getopts "b:dh?" arg; do
|
||||||
|
case "$arg" in
|
||||||
|
b) BINDIR="$OPTARG" ;;
|
||||||
|
d) log_set_priority 10 ;;
|
||||||
|
h | \?) usage "$0" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $((OPTIND - 1))
|
||||||
|
TAG=$1
|
||||||
|
}
|
||||||
|
# this function wraps all the destructive operations
|
||||||
|
# if a curl|bash cuts off the end of the script due to
|
||||||
|
# network, either nothing will happen or will syntax error
|
||||||
|
# out preventing half-done work
|
||||||
|
execute() {
|
||||||
|
tmpdir=$(mktmpdir)
|
||||||
|
log_debug "downloading files into ${tmpdir}"
|
||||||
|
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
||||||
|
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
||||||
|
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
||||||
|
srcdir="${tmpdir}"
|
||||||
|
(cd "${tmpdir}" && untar "${TARBALL}")
|
||||||
|
install -d "${BINDIR}"
|
||||||
|
for binexe in "task" ; do
|
||||||
|
if [ "$OS" = "windows" ]; then
|
||||||
|
binexe="${binexe}.exe"
|
||||||
|
fi
|
||||||
|
install "${srcdir}/${binexe}" "${BINDIR}/"
|
||||||
|
log_info "installed ${BINDIR}/${binexe}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
is_supported_platform() {
|
||||||
|
platform=$1
|
||||||
|
found=1
|
||||||
|
case "$platform" in
|
||||||
|
windows/386) found=0 ;;
|
||||||
|
windows/amd64) found=0 ;;
|
||||||
|
darwin/386) found=0 ;;
|
||||||
|
darwin/amd64) found=0 ;;
|
||||||
|
linux/386) found=0 ;;
|
||||||
|
linux/amd64) found=0 ;;
|
||||||
|
esac
|
||||||
|
case "$platform" in
|
||||||
|
darwin/386) found=1 ;;
|
||||||
|
esac
|
||||||
|
return $found
|
||||||
|
}
|
||||||
|
check_platform() {
|
||||||
|
if is_supported_platform "$PLATFORM"; then
|
||||||
|
# optional logging goes here
|
||||||
|
true
|
||||||
|
else
|
||||||
|
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
tag_to_version() {
|
||||||
|
if [ -z "${TAG}" ]; then
|
||||||
|
log_info "checking GitHub for latest tag"
|
||||||
|
else
|
||||||
|
log_info "checking GitHub for tag '${TAG}'"
|
||||||
|
fi
|
||||||
|
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
|
||||||
|
if test -z "$REALTAG"; then
|
||||||
|
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# if version starts with 'v', remove it
|
||||||
|
TAG="$REALTAG"
|
||||||
|
VERSION=${TAG#v}
|
||||||
|
}
|
||||||
|
adjust_format() {
|
||||||
|
# change format (tar.gz or zip) based on ARCH
|
||||||
|
case ${ARCH} in
|
||||||
|
windows) FORMAT=zip ;;
|
||||||
|
esac
|
||||||
|
true
|
||||||
|
}
|
||||||
|
adjust_os() {
|
||||||
|
# adjust archive name based on OS
|
||||||
|
true
|
||||||
|
}
|
||||||
|
adjust_arch() {
|
||||||
|
# adjust archive name based on ARCH
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
cat /dev/null <<EOF
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
https://github.com/client9/shlib - portable posix shell functions
|
||||||
|
Public domain - http://unlicense.org
|
||||||
|
https://github.com/client9/shlib/blob/master/LICENSE.md
|
||||||
|
but credit (and pull requests) appreciated.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
EOF
|
||||||
|
is_command() {
|
||||||
|
command -v "$1" >/dev/null
|
||||||
|
}
|
||||||
|
echoerr() {
|
||||||
|
echo "$@" 1>&2
|
||||||
|
}
|
||||||
|
log_prefix() {
|
||||||
|
echo "$0"
|
||||||
|
}
|
||||||
|
_logp=6
|
||||||
|
log_set_priority() {
|
||||||
|
_logp="$1"
|
||||||
|
}
|
||||||
|
log_priority() {
|
||||||
|
if test -z "$1"; then
|
||||||
|
echo "$_logp"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
[ "$1" -le "$_logp" ]
|
||||||
|
}
|
||||||
|
log_tag() {
|
||||||
|
case $1 in
|
||||||
|
0) echo "emerg" ;;
|
||||||
|
1) echo "alert" ;;
|
||||||
|
2) echo "crit" ;;
|
||||||
|
3) echo "err" ;;
|
||||||
|
4) echo "warning" ;;
|
||||||
|
5) echo "notice" ;;
|
||||||
|
6) echo "info" ;;
|
||||||
|
7) echo "debug" ;;
|
||||||
|
*) echo "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
log_debug() {
|
||||||
|
log_priority 7 || return 0
|
||||||
|
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
|
||||||
|
}
|
||||||
|
log_info() {
|
||||||
|
log_priority 6 || return 0
|
||||||
|
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
|
||||||
|
}
|
||||||
|
log_err() {
|
||||||
|
log_priority 3 || return 0
|
||||||
|
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
|
||||||
|
}
|
||||||
|
log_crit() {
|
||||||
|
log_priority 2 || return 0
|
||||||
|
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
|
||||||
|
}
|
||||||
|
uname_os() {
|
||||||
|
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||||
|
case "$os" in
|
||||||
|
msys_nt) os="windows" ;;
|
||||||
|
esac
|
||||||
|
echo "$os"
|
||||||
|
}
|
||||||
|
uname_arch() {
|
||||||
|
arch=$(uname -m)
|
||||||
|
case $arch in
|
||||||
|
x86_64) arch="amd64" ;;
|
||||||
|
x86) arch="386" ;;
|
||||||
|
i686) arch="386" ;;
|
||||||
|
i386) arch="386" ;;
|
||||||
|
aarch64) arch="arm64" ;;
|
||||||
|
armv5*) arch="arm5" ;;
|
||||||
|
armv6*) arch="arm6" ;;
|
||||||
|
armv7*) arch="arm7" ;;
|
||||||
|
esac
|
||||||
|
echo ${arch}
|
||||||
|
}
|
||||||
|
uname_os_check() {
|
||||||
|
os=$(uname_os)
|
||||||
|
case "$os" in
|
||||||
|
darwin) return 0 ;;
|
||||||
|
dragonfly) return 0 ;;
|
||||||
|
freebsd) return 0 ;;
|
||||||
|
linux) return 0 ;;
|
||||||
|
android) return 0 ;;
|
||||||
|
nacl) return 0 ;;
|
||||||
|
netbsd) return 0 ;;
|
||||||
|
openbsd) return 0 ;;
|
||||||
|
plan9) return 0 ;;
|
||||||
|
solaris) return 0 ;;
|
||||||
|
windows) return 0 ;;
|
||||||
|
esac
|
||||||
|
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
uname_arch_check() {
|
||||||
|
arch=$(uname_arch)
|
||||||
|
case "$arch" in
|
||||||
|
386) return 0 ;;
|
||||||
|
amd64) return 0 ;;
|
||||||
|
arm64) return 0 ;;
|
||||||
|
armv5) return 0 ;;
|
||||||
|
armv6) return 0 ;;
|
||||||
|
armv7) return 0 ;;
|
||||||
|
ppc64) return 0 ;;
|
||||||
|
ppc64le) return 0 ;;
|
||||||
|
mips) return 0 ;;
|
||||||
|
mipsle) return 0 ;;
|
||||||
|
mips64) return 0 ;;
|
||||||
|
mips64le) return 0 ;;
|
||||||
|
s390x) return 0 ;;
|
||||||
|
amd64p32) return 0 ;;
|
||||||
|
esac
|
||||||
|
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
untar() {
|
||||||
|
tarball=$1
|
||||||
|
case "${tarball}" in
|
||||||
|
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
|
||||||
|
*.tar) tar -xf "${tarball}" ;;
|
||||||
|
*.zip) unzip "${tarball}" ;;
|
||||||
|
*)
|
||||||
|
log_err "untar unknown archive format for ${tarball}"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
mktmpdir() {
|
||||||
|
test -z "$TMPDIR" && TMPDIR="$(mktemp -d)"
|
||||||
|
mkdir -p "${TMPDIR}"
|
||||||
|
echo "${TMPDIR}"
|
||||||
|
}
|
||||||
|
http_download_curl() {
|
||||||
|
local_file=$1
|
||||||
|
source_url=$2
|
||||||
|
header=$3
|
||||||
|
if [ -z "$header" ]; then
|
||||||
|
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
||||||
|
else
|
||||||
|
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
||||||
|
fi
|
||||||
|
if [ "$code" != "200" ]; then
|
||||||
|
log_debug "http_download_curl received HTTP status $code"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
http_download_wget() {
|
||||||
|
local_file=$1
|
||||||
|
source_url=$2
|
||||||
|
header=$3
|
||||||
|
if [ -z "$header" ]; then
|
||||||
|
wget -q -O "$local_file" "$source_url"
|
||||||
|
else
|
||||||
|
wget -q --header "$header" -O "$local_file" "$source_url"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
http_download() {
|
||||||
|
log_debug "http_download $2"
|
||||||
|
if is_command curl; then
|
||||||
|
http_download_curl "$@"
|
||||||
|
return
|
||||||
|
elif is_command wget; then
|
||||||
|
http_download_wget "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
log_crit "http_download unable to find wget or curl"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
http_copy() {
|
||||||
|
tmp=$(mktemp)
|
||||||
|
http_download "${tmp}" "$1" "$2" || return 1
|
||||||
|
body=$(cat "$tmp")
|
||||||
|
rm -f "${tmp}"
|
||||||
|
echo "$body"
|
||||||
|
}
|
||||||
|
github_release() {
|
||||||
|
owner_repo=$1
|
||||||
|
version=$2
|
||||||
|
test -z "$version" && version="latest"
|
||||||
|
giturl="https://github.com/${owner_repo}/releases/${version}"
|
||||||
|
json=$(http_copy "$giturl" "Accept:application/json")
|
||||||
|
test -z "$json" && return 1
|
||||||
|
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
|
||||||
|
test -z "$version" && return 1
|
||||||
|
echo "$version"
|
||||||
|
}
|
||||||
|
hash_sha256() {
|
||||||
|
TARGET=${1:-/dev/stdin}
|
||||||
|
if is_command gsha256sum; then
|
||||||
|
hash=$(gsha256sum "$TARGET") || return 1
|
||||||
|
echo "$hash" | cut -d ' ' -f 1
|
||||||
|
elif is_command sha256sum; then
|
||||||
|
hash=$(sha256sum "$TARGET") || return 1
|
||||||
|
echo "$hash" | cut -d ' ' -f 1
|
||||||
|
elif is_command shasum; then
|
||||||
|
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
||||||
|
echo "$hash" | cut -d ' ' -f 1
|
||||||
|
elif is_command openssl; then
|
||||||
|
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
||||||
|
echo "$hash" | cut -d ' ' -f a
|
||||||
|
else
|
||||||
|
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
hash_sha256_verify() {
|
||||||
|
TARGET=$1
|
||||||
|
checksums=$2
|
||||||
|
if [ -z "$checksums" ]; then
|
||||||
|
log_err "hash_sha256_verify checksum file not specified in arg2"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
BASENAME=${TARGET##*/}
|
||||||
|
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
||||||
|
if [ -z "$want" ]; then
|
||||||
|
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
got=$(hash_sha256 "$TARGET")
|
||||||
|
if [ "$want" != "$got" ]; then
|
||||||
|
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
cat /dev/null <<EOF
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
End of functions from https://github.com/client9/shlib
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
EOF
|
||||||
|
|
||||||
|
PROJECT_NAME="task"
|
||||||
|
OWNER=go-task
|
||||||
|
REPO="task"
|
||||||
|
BINARY=task
|
||||||
|
FORMAT=tar.gz
|
||||||
|
OS=$(uname_os)
|
||||||
|
ARCH=$(uname_arch)
|
||||||
|
PREFIX="$OWNER/$REPO"
|
||||||
|
|
||||||
|
# use in logging routines
|
||||||
|
log_prefix() {
|
||||||
|
echo "$PREFIX"
|
||||||
|
}
|
||||||
|
PLATFORM="${OS}/${ARCH}"
|
||||||
|
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
|
||||||
|
|
||||||
|
uname_os_check "$OS"
|
||||||
|
uname_arch_check "$ARCH"
|
||||||
|
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
check_platform
|
||||||
|
|
||||||
|
tag_to_version
|
||||||
|
|
||||||
|
adjust_format
|
||||||
|
|
||||||
|
adjust_os
|
||||||
|
|
||||||
|
adjust_arch
|
||||||
|
|
||||||
|
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
||||||
|
|
||||||
|
NAME=${BINARY}_${OS}_${ARCH}
|
||||||
|
TARBALL=${NAME}.${FORMAT}
|
||||||
|
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
||||||
|
CHECKSUM=task_checksums.txt
|
||||||
|
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
||||||
|
|
||||||
|
|
||||||
|
execute
|
||||||
|
`)
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/go-task/task/v2"
|
"github.com/go-task/task/v2"
|
||||||
@@ -17,7 +18,7 @@ var (
|
|||||||
version = "master"
|
version = "master"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--dry] [task...]
|
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]
|
||||||
|
|
||||||
Runs the specified task(s). Falls back to the "default" task if no task name
|
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.
|
was specified, or lists all tasks if an unknown task name was specified.
|
||||||
@@ -56,7 +57,9 @@ func main() {
|
|||||||
verbose bool
|
verbose bool
|
||||||
silent bool
|
silent bool
|
||||||
dry bool
|
dry bool
|
||||||
|
summary bool
|
||||||
dir string
|
dir string
|
||||||
|
entrypoint string
|
||||||
output string
|
output string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,7 +72,9 @@ func main() {
|
|||||||
pflag.BoolVarP(&verbose, "verbose", "v", false, "enables verbose mode")
|
pflag.BoolVarP(&verbose, "verbose", "v", false, "enables verbose mode")
|
||||||
pflag.BoolVarP(&silent, "silent", "s", false, "disables echoing")
|
pflag.BoolVarP(&silent, "silent", "s", false, "disables echoing")
|
||||||
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
|
pflag.BoolVar(&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(&dir, "dir", "d", "", "sets directory of execution")
|
||||||
|
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
|
||||||
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
|
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
@@ -89,13 +94,26 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dir != "" && entrypoint != "" {
|
||||||
|
log.Fatal("task: You can't set both --dir and --taskfile")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if entrypoint != "" {
|
||||||
|
dir = filepath.Dir(entrypoint)
|
||||||
|
entrypoint = filepath.Base(entrypoint)
|
||||||
|
} else {
|
||||||
|
entrypoint = "Taskfile.yml"
|
||||||
|
}
|
||||||
|
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
Force: force,
|
Force: force,
|
||||||
Watch: watch,
|
Watch: watch,
|
||||||
Verbose: verbose,
|
Verbose: verbose,
|
||||||
Silent: silent,
|
Silent: silent,
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
Dry: dry,
|
Dry: dry,
|
||||||
|
Entrypoint: entrypoint,
|
||||||
|
Summary: summary,
|
||||||
|
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
@@ -118,9 +136,9 @@ func main() {
|
|||||||
arguments = []string{"default"}
|
arguments = []string{"default"}
|
||||||
}
|
}
|
||||||
|
|
||||||
calls, err := args.Parse(arguments...)
|
calls, globals := args.Parse(arguments...)
|
||||||
if err != nil {
|
for name, value := range globals {
|
||||||
log.Fatal(err)
|
e.Taskfile.Vars[name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -129,7 +147,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status {
|
if status {
|
||||||
if err = e.Status(ctx, calls...); err != nil {
|
if err := e.Status(ctx, calls...); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
2
completion/zsh/_task
Normal file → Executable file
2
completion/zsh/_task
Normal file → Executable file
@@ -5,7 +5,7 @@ function __list() {
|
|||||||
local -a scripts
|
local -a scripts
|
||||||
|
|
||||||
if [ -f Taskfile.yml ]; then
|
if [ -f Taskfile.yml ]; then
|
||||||
scripts=($(task -l | sed '1d' | sed 's/://' | awk '{ print $2 }'))
|
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/'))
|
||||||
_describe 'script' scripts
|
_describe 'script' scripts
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
taskfile.org
|
taskfile.dev
|
||||||
@@ -8,13 +8,15 @@ The `task_checksums.txt` file contains the sha256 checksum for each file.
|
|||||||
|
|
||||||
## Homebrew
|
## Homebrew
|
||||||
|
|
||||||
If you're on macOS and have [Homebrew][homebrew] installed, getting Task is
|
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
|
||||||
as simple as running:
|
Task is as simple as running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install go-task/tap/go-task
|
brew install go-task/tap/go-task
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> This installation method is only currently supported on amd64 architectures.
|
||||||
|
|
||||||
## Snap
|
## Snap
|
||||||
|
|
||||||
Task is available for [Snapcraft][snapcraft], but keep in mind that your
|
Task is available for [Snapcraft][snapcraft], but keep in mind that your
|
||||||
@@ -76,7 +78,7 @@ scenarios like CIs. Many thanks to [godownloader][godownloader] for allowing
|
|||||||
easily generating this script.
|
easily generating this script.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -s https://taskfile.org/install.sh | sh
|
curl -sL https://taskfile.dev/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
> This method will download the binary on the local `./bin` directory by default.
|
> This method will download the binary on the local `./bin` directory by default.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ the [Snapcraft dashboard][snapcraftdashboard]
|
|||||||
|
|
||||||
Scoop is a community owned installation method. Scoop owners usually take care
|
Scoop is a community owned installation method. Scoop owners usually take care
|
||||||
of updating versions there by editing
|
of updating versions there by editing
|
||||||
[this file](https://github.com/lukesampson/scoop-extras/blob/master/task.json).
|
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
||||||
If you think its Task version is outdated, open an issue to let us know.
|
If you think its Task version is outdated, open an issue to let us know.
|
||||||
|
|
||||||
[goreleaser]: https://goreleaser.com/#continuous_integration
|
[goreleaser]: https://goreleaser.com/#continuous_integration
|
||||||
|
|||||||
@@ -141,6 +141,21 @@ includes:
|
|||||||
docker: ./DockerTasks.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]
|
Please check the [documentation][includes]
|
||||||
|
|
||||||
[output]: usage.md#output-syntax
|
[output]: usage.md#output-syntax
|
||||||
|
|||||||
106
docs/usage.md
106
docs/usage.md
@@ -148,6 +148,8 @@ tasks:
|
|||||||
- caddy
|
- caddy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If the directory doesn't exist, `task` creates it.
|
||||||
|
|
||||||
## Task dependencies
|
## Task dependencies
|
||||||
|
|
||||||
You may have tasks that depend on others. Just pointing them on `deps` will
|
You may have tasks that depend on others. Just pointing them on `deps` will
|
||||||
@@ -344,12 +346,61 @@ up-to-date.
|
|||||||
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
||||||
the tasks are not up-to-date.
|
the tasks are not up-to-date.
|
||||||
|
|
||||||
|
If you need a certain set of conditions to be _true_ you can use the
|
||||||
|
`preconditions` stanza. `preconditions` are very similar to `status`
|
||||||
|
lines except they support `sh` expansion and they SHOULD all return 0.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
generate-files:
|
||||||
|
cmds:
|
||||||
|
- mkdir directory
|
||||||
|
- touch directory/file1.txt
|
||||||
|
- touch directory/file2.txt
|
||||||
|
# test existence of files
|
||||||
|
preconditions:
|
||||||
|
- test -f .env
|
||||||
|
- sh: "[ 1 = 0 ]"
|
||||||
|
msg: "One doesn't equal Zero, Halting"
|
||||||
|
```
|
||||||
|
|
||||||
|
Preconditions can set specific failure messages that can tell
|
||||||
|
a user what steps to take using the `msg` field.
|
||||||
|
|
||||||
|
If a task has a dependency on a sub-task with a precondition, and that
|
||||||
|
precondition is not met - the calling task will fail. Note that a task
|
||||||
|
executed with a failing precondition will not run unless `--force` is
|
||||||
|
given.
|
||||||
|
|
||||||
|
Unlike `status` which will skip a task if it is up to date, and continue
|
||||||
|
executing tasks that depend on it, a `precondition` will fail a task, along
|
||||||
|
with any other tasks that depend on it.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
tasks:
|
||||||
|
task_will_fail:
|
||||||
|
preconditions:
|
||||||
|
- sh: "exit 1"
|
||||||
|
|
||||||
|
task_will_also_fail:
|
||||||
|
deps:
|
||||||
|
- task_will_fail
|
||||||
|
|
||||||
|
task_will_still_fail:
|
||||||
|
cmds:
|
||||||
|
- task: task_will_fail
|
||||||
|
- echo "I will not run"
|
||||||
|
```
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
When doing interpolation of variables, Task will look for the below.
|
When doing interpolation of variables, Task will look for the below.
|
||||||
They are listed below in order of importance (e.g. most important first):
|
They are listed below in order of importance (e.g. most important first):
|
||||||
|
|
||||||
- Variables declared locally in the task
|
- Variables declared in the task definition
|
||||||
- Variables given while calling a task from another.
|
- Variables given while calling a task from another.
|
||||||
(See [Calling another task](#calling-another-task) above)
|
(See [Calling another task](#calling-another-task) above)
|
||||||
- Variables declared in the `vars:` option in the `Taskfile`
|
- Variables declared in the `vars:` option in the `Taskfile`
|
||||||
@@ -371,6 +422,12 @@ right before.
|
|||||||
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you want to set global variables using this syntax, give it before any task:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ task OUTPUT=file.txt generate-file
|
||||||
|
```
|
||||||
|
|
||||||
Example of locally declared vars:
|
Example of locally declared vars:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -379,7 +436,7 @@ version: '2'
|
|||||||
tasks:
|
tasks:
|
||||||
print-var:
|
print-var:
|
||||||
cmds:
|
cmds:
|
||||||
echo "{{.VAR}}"
|
- echo "{{.VAR}}"
|
||||||
vars:
|
vars:
|
||||||
VAR: Hello!
|
VAR: Hello!
|
||||||
```
|
```
|
||||||
@@ -542,6 +599,51 @@ would print the following output:
|
|||||||
* test: Run all the go tests.
|
* test: Run all the go tests.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Display summary of task
|
||||||
|
|
||||||
|
Running `task --summary task-name` will show a summary of a task
|
||||||
|
The following Taskfile:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
release:
|
||||||
|
deps: [build]
|
||||||
|
summary: |
|
||||||
|
Release your project to github
|
||||||
|
|
||||||
|
It will build your project before starting the release it.
|
||||||
|
Please make sure that you have set GITHUB_TOKEN before starting.
|
||||||
|
cmds:
|
||||||
|
- your-release-tool
|
||||||
|
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- your-build-tool
|
||||||
|
```
|
||||||
|
|
||||||
|
with running ``task --summary release`` would print the following output:
|
||||||
|
|
||||||
|
```
|
||||||
|
task: release
|
||||||
|
|
||||||
|
Release your project to github
|
||||||
|
|
||||||
|
It will build your project before starting the release it.
|
||||||
|
Please make sure that you have set GITHUB_TOKEN before starting.
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- build
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- your-release-tool
|
||||||
|
```
|
||||||
|
If a summary is missing, the description will be printed.
|
||||||
|
If the task does not have a summary or a description, a warning is printed.
|
||||||
|
|
||||||
|
Please note: *showing the summary will not execute the command*.
|
||||||
|
|
||||||
## Silent mode
|
## Silent mode
|
||||||
|
|
||||||
Silent mode disables echoing of commands before Task runs it.
|
Silent mode disables echoing of commands before Task runs it.
|
||||||
|
|||||||
13
go.mod
13
go.mod
@@ -1,23 +1,20 @@
|
|||||||
module github.com/go-task/task/v2
|
module github.com/go-task/task/v2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver v1.4.2
|
github.com/Masterminds/semver v1.4.2 // indirect
|
||||||
github.com/Masterminds/sprig v2.16.0+incompatible
|
github.com/Masterminds/sprig v2.16.0+incompatible
|
||||||
github.com/aokoli/goutils v1.0.1 // indirect
|
github.com/aokoli/goutils v1.0.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/uuid v1.0.0 // indirect
|
github.com/google/uuid v1.0.0 // indirect
|
||||||
github.com/huandu/xstrings v1.1.0 // indirect
|
github.com/huandu/xstrings v1.1.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.6 // indirect
|
github.com/imdario/mergo v0.3.6 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
|
||||||
github.com/mattn/go-zglob v0.0.1
|
github.com/mattn/go-zglob v0.0.1
|
||||||
github.com/mitchellh/go-homedir v1.0.0
|
|
||||||
github.com/radovskyb/watcher v1.0.5
|
github.com/radovskyb/watcher v1.0.5
|
||||||
github.com/spf13/pflag v1.0.3
|
github.com/spf13/pflag v1.0.3
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 // indirect
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
|
||||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.2.1
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
mvdan.cc/sh v2.6.3+incompatible
|
mvdan.cc/sh/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|||||||
29
go.sum
29
go.sum
@@ -20,28 +20,33 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
|
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
|
||||||
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
||||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/radovskyb/watcher v1.0.5 h1:wqt7gb+HjGacvFoLTKeT44C+XVPxu7bvHvKT1IvZ7rw=
|
github.com/radovskyb/watcher v1.0.5 h1:wqt7gb+HjGacvFoLTKeT44C+XVPxu7bvHvKT1IvZ7rw=
|
||||||
github.com/radovskyb/watcher v1.0.5/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
github.com/radovskyb/watcher v1.0.5/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 h1:T8D7l6WB3tLu+VpKvw06ieD/OhBi1XpJmG1U/FtttZg=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
|
||||||
|
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
mvdan.cc/sh v2.6.3+incompatible h1:uXnnFNSBQbKUwwh2iBSkVjG+GbwoMuI+UmBVPnNiWhA=
|
mvdan.cc/sh/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b h1:kzTXBacNrjp7n8ncNC894X9NUeL8yeKVS5MS0bigJwE=
|
||||||
mvdan.cc/sh v2.6.3+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
|
mvdan.cc/sh/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b/go.mod h1:6Cd5lQRZZIHeKAdYoLvhhse0oDOIrS5gBoGacWnuiUE=
|
||||||
|
|||||||
2
init.go
2
init.go
@@ -8,7 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultTaskfile = `# https://taskfile.org
|
const defaultTaskfile = `# https://taskfile.dev
|
||||||
|
|
||||||
version: '2'
|
version: '2'
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,43 @@
|
|||||||
package args
|
package args
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-task/task/v2/internal/taskfile"
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrVariableWithoutTask is returned when variables are given before any task
|
|
||||||
ErrVariableWithoutTask = errors.New("task: variable given before any task")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parse parses command line argument: tasks and vars of each task
|
// Parse parses command line argument: tasks and vars of each task
|
||||||
func Parse(args ...string) ([]taskfile.Call, error) {
|
func Parse(args ...string) ([]taskfile.Call, taskfile.Vars) {
|
||||||
var calls []taskfile.Call
|
var calls []taskfile.Call
|
||||||
|
var globals taskfile.Vars
|
||||||
|
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if !strings.Contains(arg, "=") {
|
if !strings.Contains(arg, "=") {
|
||||||
calls = append(calls, taskfile.Call{Task: arg})
|
calls = append(calls, taskfile.Call{Task: arg})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(calls) < 1 {
|
if len(calls) < 1 {
|
||||||
return nil, ErrVariableWithoutTask
|
if globals == nil {
|
||||||
}
|
globals = taskfile.Vars{}
|
||||||
|
}
|
||||||
|
|
||||||
if calls[len(calls)-1].Vars == nil {
|
name, value := splitVar(arg)
|
||||||
calls[len(calls)-1].Vars = make(taskfile.Vars)
|
globals[name] = taskfile.Var{Static: value}
|
||||||
}
|
} else {
|
||||||
|
if calls[len(calls)-1].Vars == nil {
|
||||||
|
calls[len(calls)-1].Vars = make(taskfile.Vars)
|
||||||
|
}
|
||||||
|
|
||||||
pair := strings.SplitN(arg, "=", 2)
|
name, value := splitVar((arg))
|
||||||
calls[len(calls)-1].Vars[pair[0]] = taskfile.Var{Static: pair[1]}
|
calls[len(calls)-1].Vars[name] = taskfile.Var{Static: value}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return calls, nil
|
|
||||||
|
return calls, globals
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitVar(s string) (string, string) {
|
||||||
|
pair := strings.SplitN(s, "=", 2)
|
||||||
|
return pair[0], pair[1]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import (
|
|||||||
|
|
||||||
func TestArgs(t *testing.T) {
|
func TestArgs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Args []string
|
Args []string
|
||||||
Expected []taskfile.Call
|
ExpectedCalls []taskfile.Call
|
||||||
Err error
|
ExpectedGlobals taskfile.Vars
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Args: []string{"task-a", "task-b", "task-c"},
|
Args: []string{"task-a", "task-b", "task-c"},
|
||||||
Expected: []taskfile.Call{
|
ExpectedCalls: []taskfile.Call{
|
||||||
{Task: "task-a"},
|
{Task: "task-a"},
|
||||||
{Task: "task-b"},
|
{Task: "task-b"},
|
||||||
{Task: "task-c"},
|
{Task: "task-c"},
|
||||||
@@ -26,7 +26,7 @@ func TestArgs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||||
Expected: []taskfile.Call{
|
ExpectedCalls: []taskfile.Call{
|
||||||
{
|
{
|
||||||
Task: "task-a",
|
Task: "task-a",
|
||||||
Vars: taskfile.Vars{
|
Vars: taskfile.Vars{
|
||||||
@@ -45,7 +45,7 @@ func TestArgs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||||
Expected: []taskfile.Call{
|
ExpectedCalls: []taskfile.Call{
|
||||||
{
|
{
|
||||||
Task: "task-a",
|
Task: "task-a",
|
||||||
Vars: taskfile.Vars{
|
Vars: taskfile.Vars{
|
||||||
@@ -55,16 +55,22 @@ func TestArgs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Args: []string{"FOO=bar", "task-a"},
|
Args: []string{"FOO=bar", "task-a", "task-b"},
|
||||||
Err: args.ErrVariableWithoutTask,
|
ExpectedCalls: []taskfile.Call{
|
||||||
|
{Task: "task-a"},
|
||||||
|
{Task: "task-b"},
|
||||||
|
},
|
||||||
|
ExpectedGlobals: taskfile.Vars{
|
||||||
|
"FOO": {Static: "bar"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
||||||
calls, err := args.Parse(test.Args...)
|
calls, globals := args.Parse(test.Args...)
|
||||||
assert.Equal(t, test.Err, err)
|
assert.Equal(t, test.ExpectedCalls, calls)
|
||||||
assert.Equal(t, test.Expected, calls)
|
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"mvdan.cc/sh/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/shell"
|
"mvdan.cc/sh/v3/shell"
|
||||||
"mvdan.cc/sh/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunCommandOptions is the options for the RunCommand func
|
// RunCommandOptions is the options for the RunCommand func
|
||||||
@@ -49,8 +49,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
interp.Dir(opts.Dir),
|
interp.Dir(opts.Dir),
|
||||||
interp.Env(expand.ListEnviron(environ...)),
|
interp.Env(expand.ListEnviron(environ...)),
|
||||||
|
|
||||||
interp.Module(interp.DefaultExec),
|
interp.WithOpenModules(interp.OpenDevImpls),
|
||||||
interp.Module(interp.OpenDevImpls(interp.DefaultOpen)),
|
|
||||||
|
|
||||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
type Group struct{}
|
type Group struct{}
|
||||||
|
|
||||||
func (Group) WrapWriter(w io.Writer, _ string) io.WriteCloser {
|
func (Group) WrapWriter(w io.Writer, _ string) io.Writer {
|
||||||
return &groupWriter{writer: w}
|
return &groupWriter{writer: w}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,18 +6,6 @@ import (
|
|||||||
|
|
||||||
type Interleaved struct{}
|
type Interleaved struct{}
|
||||||
|
|
||||||
func (Interleaved) WrapWriter(w io.Writer, _ string) io.WriteCloser {
|
func (Interleaved) WrapWriter(w io.Writer, _ string) io.Writer {
|
||||||
return nopWriterCloser{w: w}
|
return w
|
||||||
}
|
|
||||||
|
|
||||||
type nopWriterCloser struct {
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wc nopWriterCloser) Write(p []byte) (int, error) {
|
|
||||||
return wc.w.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wc nopWriterCloser) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Output interface {
|
type Output interface {
|
||||||
WrapWriter(w io.Writer, prefix string) io.WriteCloser
|
WrapWriter(w io.Writer, prefix string) io.Writer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package output_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-task/task/v2/internal/output"
|
"github.com/go-task/task/v2/internal/output"
|
||||||
@@ -24,7 +25,7 @@ func TestInterleaved(t *testing.T) {
|
|||||||
func TestGroup(t *testing.T) {
|
func TestGroup(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var o output.Output = output.Group{}
|
var o output.Output = output.Group{}
|
||||||
var w = o.WrapWriter(&b, "")
|
var w = o.WrapWriter(&b, "").(io.WriteCloser)
|
||||||
|
|
||||||
fmt.Fprintln(w, "foo\nbar")
|
fmt.Fprintln(w, "foo\nbar")
|
||||||
assert.Equal(t, "", b.String())
|
assert.Equal(t, "", b.String())
|
||||||
@@ -37,7 +38,7 @@ func TestGroup(t *testing.T) {
|
|||||||
func TestPrefixed(t *testing.T) {
|
func TestPrefixed(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var o output.Output = output.Prefixed{}
|
var o output.Output = output.Prefixed{}
|
||||||
var w = o.WrapWriter(&b, "prefix")
|
var w = o.WrapWriter(&b, "prefix").(io.WriteCloser)
|
||||||
|
|
||||||
t.Run("simple use cases", func(t *testing.T) {
|
t.Run("simple use cases", func(t *testing.T) {
|
||||||
b.Reset()
|
b.Reset()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
type Prefixed struct{}
|
type Prefixed struct{}
|
||||||
|
|
||||||
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.WriteCloser {
|
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.Writer {
|
||||||
return &prefixWriter{writer: w, prefix: prefix}
|
return &prefixWriter{writer: w, prefix: prefix}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,12 +34,12 @@ func (pw *prefixWriter) Close() error {
|
|||||||
|
|
||||||
func (pw *prefixWriter) writeOutputLines(force bool) error {
|
func (pw *prefixWriter) writeOutputLines(force bool) error {
|
||||||
for {
|
for {
|
||||||
line, err := pw.buff.ReadString('\n')
|
switch line, err := pw.buff.ReadString('\n'); err {
|
||||||
if err == nil {
|
case nil:
|
||||||
if err = pw.writeLine(line); err != nil {
|
if err = pw.writeLine(line); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if err == io.EOF {
|
case io.EOF:
|
||||||
// if this line was not a complete line, re-add to the buffer
|
// if this line was not a complete line, re-add to the buffer
|
||||||
if !force && !strings.HasSuffix(line, "\n") {
|
if !force && !strings.HasSuffix(line, "\n") {
|
||||||
_, err = pw.buff.WriteString(line)
|
_, err = pw.buff.WriteString(line)
|
||||||
@@ -47,7 +47,7 @@ func (pw *prefixWriter) writeOutputLines(force bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pw.writeLine(line)
|
return pw.writeLine(line)
|
||||||
} else {
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import (
|
|||||||
// Checksum validades if a task is up to date by calculating its source
|
// Checksum validades if a task is up to date by calculating its source
|
||||||
// files checksum
|
// files checksum
|
||||||
type Checksum struct {
|
type Checksum struct {
|
||||||
Dir string
|
Dir string
|
||||||
Task string
|
Task string
|
||||||
Sources []string
|
Sources []string
|
||||||
Dry bool
|
Generates []string
|
||||||
|
Dry bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUpToDate implements the Checker interface
|
// IsUpToDate implements the Checker interface
|
||||||
@@ -27,7 +28,7 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
|||||||
data, _ := ioutil.ReadFile(checksumFile)
|
data, _ := ioutil.ReadFile(checksumFile)
|
||||||
oldMd5 := strings.TrimSpace(string(data))
|
oldMd5 := strings.TrimSpace(string(data))
|
||||||
|
|
||||||
sources, err := glob(c.Dir, c.Sources)
|
sources, err := globs(c.Dir, c.Sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -43,6 +44,23 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(c.Generates) > 0 {
|
||||||
|
// For each specified 'generates' field, check whether the files actually exist
|
||||||
|
for _, g := range c.Generates {
|
||||||
|
generates, err := glob(c.Dir, g)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(generates) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return oldMd5 == newMd5, nil
|
return oldMd5 == newMd5, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,21 +68,14 @@ func (c *Checksum) checksum(files ...string) (string, error) {
|
|||||||
h := md5.New()
|
h := md5.New()
|
||||||
|
|
||||||
for _, f := range files {
|
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)
|
f, err := os.Open(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
info, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// also sum the filename, so checksum changes for renaming a file
|
|
||||||
if _, err = io.Copy(h, strings.NewReader(info.Name())); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if _, err = io.Copy(h, f); err != nil {
|
if _, err = io.Copy(h, f); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
@@ -9,21 +10,41 @@ import (
|
|||||||
"github.com/mattn/go-zglob"
|
"github.com/mattn/go-zglob"
|
||||||
)
|
)
|
||||||
|
|
||||||
func glob(dir string, globs []string) (files []string, err error) {
|
func globs(dir string, globs []string) ([]string, error) {
|
||||||
|
files := make([]string, 0)
|
||||||
for _, g := range globs {
|
for _, g := range globs {
|
||||||
if !filepath.IsAbs(g) {
|
f, err := glob(dir, g)
|
||||||
g = filepath.Join(dir, g)
|
|
||||||
}
|
|
||||||
g, err = execext.Expand(g)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f, err := zglob.Glob(g)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
files = append(files, f...)
|
files = append(files, f...)
|
||||||
}
|
}
|
||||||
sort.Strings(files)
|
sort.Strings(files)
|
||||||
return
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func glob(dir string, g string) ([]string, error) {
|
||||||
|
files := make([]string, 0)
|
||||||
|
if !filepath.IsAbs(g) {
|
||||||
|
g = filepath.Join(dir, g)
|
||||||
|
}
|
||||||
|
g, err := execext.Expand(g)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fs, err := zglob.Glob(g)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, f := range fs {
|
||||||
|
info, err := os.Stat(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, f)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sources, err := glob(t.Dir, t.Sources)
|
sources, err := globs(t.Dir, t.Sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
generates, err := glob(t.Dir, t.Generates)
|
generates, err := globs(t.Dir, t.Generates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, 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/v2/internal/logger"
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
|
||||||
|
for i, call := range c {
|
||||||
|
printSpaceBetweenSummaries(l, i)
|
||||||
|
PrintTask(l, t.Tasks[call.Task])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printSpaceBetweenSummaries(l *logger.Logger, i int) {
|
||||||
|
spaceRequired := i > 0
|
||||||
|
if !spaceRequired {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Outf("")
|
||||||
|
l.Outf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
printTaskName(l, t)
|
||||||
|
printTaskDescribingText(t, l)
|
||||||
|
printTaskDependencies(l, t)
|
||||||
|
printTaskCommands(l, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskDescribingText(t *taskfile.Task, l *logger.Logger) {
|
||||||
|
if hasSummary(t) {
|
||||||
|
printTaskSummary(l, t)
|
||||||
|
} else if hasDescription(t) {
|
||||||
|
printTaskDescription(l, t)
|
||||||
|
} else {
|
||||||
|
printNoDescriptionOrSummary(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasSummary(t *taskfile.Task) bool {
|
||||||
|
return t.Summary != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
lines := strings.Split(t.Summary, "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
notLastLine := i+1 < len(lines)
|
||||||
|
if notLastLine || line != "" {
|
||||||
|
l.Outf(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
l.Outf("task: %s", t.Task)
|
||||||
|
l.Outf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDescription(t *taskfile.Task) bool {
|
||||||
|
return t.Desc != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
l.Outf(t.Desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printNoDescriptionOrSummary(l *logger.Logger) {
|
||||||
|
l.Outf("(task does not have description or summary)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
if len(t.Deps) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Outf("")
|
||||||
|
l.Outf("dependencies:")
|
||||||
|
|
||||||
|
for _, d := range t.Deps {
|
||||||
|
l.Outf(" - %s", d.Task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
if len(t.Cmds) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Outf("")
|
||||||
|
l.Outf("commands:")
|
||||||
|
for _, c := range t.Cmds {
|
||||||
|
isCommand := c.Cmd != ""
|
||||||
|
if isCommand {
|
||||||
|
l.Outf(" - %s", c.Cmd)
|
||||||
|
} else {
|
||||||
|
l.Outf(" - Task: %s", c.Task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
173
internal/summary/summary_test.go
Normal file
173
internal/summary/summary_test.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package summary_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/logger"
|
||||||
|
"github.com/go-task/task/v2/internal/summary"
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrintsDependenciesIfPresent(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Deps: []*taskfile.Dep{
|
||||||
|
{Task: "dep1"},
|
||||||
|
{Task: "dep2"},
|
||||||
|
{Task: "dep3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "\ndependencies:\n - dep1\n - dep2\n - dep3\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDummyLogger() (*bytes.Buffer, logger.Logger) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
l := logger.Logger{
|
||||||
|
Stderr: buffer,
|
||||||
|
Stdout: buffer,
|
||||||
|
Verbose: false,
|
||||||
|
}
|
||||||
|
return buffer, l
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Deps: []*taskfile.Dep{},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.NotContains(t, buffer.String(), "dependencies:")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintTaskName(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Task: "my-task-name",
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "task: my-task-name\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintTaskCommandsIfPresent(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Cmds: []*taskfile.Cmd{
|
||||||
|
{Cmd: "command-1"},
|
||||||
|
{Cmd: "command-2"},
|
||||||
|
{Task: "task-1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "\ncommands:\n")
|
||||||
|
assert.Contains(t, buffer.String(), "\n - command-1\n")
|
||||||
|
assert.Contains(t, buffer.String(), "\n - command-2\n")
|
||||||
|
assert.Contains(t, buffer.String(), "\n - Task: task-1\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoesNotPrintCommandIfMissing(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Cmds: []*taskfile.Cmd{},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.NotContains(t, buffer.String(), "commands")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLayout(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Task: "sample-task",
|
||||||
|
Summary: "line1\nline2\nline3\n",
|
||||||
|
Deps: []*taskfile.Dep{
|
||||||
|
{Task: "dependency"},
|
||||||
|
},
|
||||||
|
Cmds: []*taskfile.Cmd{
|
||||||
|
{Cmd: "command"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.Equal(t, expectedOutput(), buffer.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectedOutput() string {
|
||||||
|
expected := `task: sample-task
|
||||||
|
|
||||||
|
line1
|
||||||
|
line2
|
||||||
|
line3
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- dependency
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- command
|
||||||
|
`
|
||||||
|
return expected
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintDescriptionAsFallback(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
taskWithoutSummary := &taskfile.Task{
|
||||||
|
Desc: "description",
|
||||||
|
}
|
||||||
|
|
||||||
|
taskWithSummary := &taskfile.Task{
|
||||||
|
Desc: "description",
|
||||||
|
Summary: "summary",
|
||||||
|
}
|
||||||
|
taskWithoutSummaryOrDescription := &taskfile.Task{}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, taskWithoutSummary)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "description")
|
||||||
|
|
||||||
|
buffer.Reset()
|
||||||
|
summary.PrintTask(&l, taskWithSummary)
|
||||||
|
|
||||||
|
assert.NotContains(t, buffer.String(), "description")
|
||||||
|
|
||||||
|
buffer.Reset()
|
||||||
|
summary.PrintTask(&l, taskWithoutSummaryOrDescription)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintAllWithSpaces(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
|
||||||
|
t1 := &taskfile.Task{Task: "t1"}
|
||||||
|
t2 := &taskfile.Task{Task: "t2"}
|
||||||
|
t3 := &taskfile.Task{Task: "t3"}
|
||||||
|
|
||||||
|
tasks := make(taskfile.Tasks, 3)
|
||||||
|
tasks["t1"] = t1
|
||||||
|
tasks["t2"] = t2
|
||||||
|
tasks["t3"] = t3
|
||||||
|
|
||||||
|
summary.PrintTasks(&l,
|
||||||
|
&taskfile.Taskfile{Tasks: tasks},
|
||||||
|
[]taskfile.Call{{Task: "t1"}, {Task: "t2"}, {Task: "t3"}})
|
||||||
|
|
||||||
|
assert.True(t, strings.HasPrefix(buffer.String(), "task: t1"))
|
||||||
|
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t2")
|
||||||
|
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t3")
|
||||||
|
|
||||||
|
}
|
||||||
45
internal/taskfile/precondition.go
Normal file
45
internal/taskfile/precondition.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
|
||||||
|
ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Precondition represents a precondition necessary for a task to run
|
||||||
|
type Precondition struct {
|
||||||
|
Sh string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||||
|
func (p *Precondition) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var cmd string
|
||||||
|
|
||||||
|
if err := unmarshal(&cmd); err == nil {
|
||||||
|
p.Sh = cmd
|
||||||
|
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sh struct {
|
||||||
|
Sh string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unmarshal(&sh); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Sh = sh.Sh
|
||||||
|
p.Msg = sh.Msg
|
||||||
|
if p.Msg == "" {
|
||||||
|
p.Msg = fmt.Sprintf("%s failed", sh.Sh)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
48
internal/taskfile/precondition_test.go
Normal file
48
internal/taskfile/precondition_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package taskfile_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPreconditionParse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
content string
|
||||||
|
v interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"test -f foo.txt",
|
||||||
|
&taskfile.Precondition{},
|
||||||
|
&taskfile.Precondition{Sh: `test -f foo.txt`, Msg: "`test -f foo.txt` failed"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sh: '[ 1 = 0 ]'",
|
||||||
|
&taskfile.Precondition{},
|
||||||
|
&taskfile.Precondition{Sh: "[ 1 = 0 ]", Msg: "[ 1 = 0 ] failed"},
|
||||||
|
},
|
||||||
|
{`
|
||||||
|
sh: "[ 1 = 2 ]"
|
||||||
|
msg: "1 is not 2"
|
||||||
|
`,
|
||||||
|
&taskfile.Precondition{},
|
||||||
|
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
||||||
|
},
|
||||||
|
{`
|
||||||
|
sh: "[ 1 = 2 ]"
|
||||||
|
msg: "1 is not 2"
|
||||||
|
`,
|
||||||
|
&taskfile.Precondition{},
|
||||||
|
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
err := yaml.Unmarshal([]byte(test.content), test.v)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, test.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,16 +15,13 @@ import (
|
|||||||
var (
|
var (
|
||||||
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
|
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
|
||||||
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
|
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
|
||||||
|
|
||||||
// ErrNoTaskfileFound is returned when Taskfile.yml is not found
|
|
||||||
ErrNoTaskfileFound = errors.New(`task: No Taskfile.yml found. Use "task --init" to create a new one`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Taskfile reads a Taskfile for a given directory
|
// Taskfile reads a Taskfile for a given directory
|
||||||
func Taskfile(dir string) (*taskfile.Taskfile, error) {
|
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||||
path := filepath.Join(dir, "Taskfile.yml")
|
path := filepath.Join(dir, entrypoint)
|
||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
return nil, ErrNoTaskfileFound
|
return nil, fmt.Errorf(`task: No Taskfile found on "%s". Use "task --init" to create a new one`, path)
|
||||||
}
|
}
|
||||||
t, err := readTaskfile(path)
|
t, err := readTaskfile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
package taskfile
|
package taskfile
|
||||||
|
|
||||||
// Tasks representas a group of tasks
|
// Tasks represents a group of tasks
|
||||||
type Tasks map[string]*Task
|
type Tasks map[string]*Task
|
||||||
|
|
||||||
// Task represents a task
|
// Task represents a task
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Task string
|
Task string
|
||||||
Cmds []*Cmd
|
Cmds []*Cmd
|
||||||
Deps []*Dep
|
Deps []*Dep
|
||||||
Desc string
|
Desc string
|
||||||
Sources []string
|
Summary string
|
||||||
Generates []string
|
Sources []string
|
||||||
Status []string
|
Generates []string
|
||||||
Dir string
|
Status []string
|
||||||
Vars Vars
|
Preconditions []*Precondition
|
||||||
Env Vars
|
Dir string
|
||||||
Silent bool
|
Vars Vars
|
||||||
Method string
|
Env Vars
|
||||||
Prefix string
|
Silent bool
|
||||||
IgnoreError bool `yaml:"ignore_error"`
|
Method string
|
||||||
|
Prefix string
|
||||||
|
IgnoreError bool `yaml:"ignore_error"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,5 +40,8 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
if tf.Expansions <= 0 {
|
if tf.Expansions <= 0 {
|
||||||
tf.Expansions = 2
|
tf.Expansions = 2
|
||||||
}
|
}
|
||||||
|
if tf.Vars == nil {
|
||||||
|
tf.Vars = make(Vars)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Masterminds/semver"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
v1 = mustVersion("1")
|
|
||||||
v2 = mustVersion("2")
|
|
||||||
v21 = mustVersion("2.1")
|
|
||||||
v22 = mustVersion("2.2")
|
|
||||||
v23 = mustVersion("2.3")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsV1 returns if is a given Taskfile version is version 1
|
|
||||||
func IsV1(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsV2 returns if is a given Taskfile version is at least version 2
|
|
||||||
func IsV2(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsV21 returns if is a given Taskfile version is at least version 2.1
|
|
||||||
func IsV21(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v21)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsV22 returns if is a given Taskfile version is at least version 2.2
|
|
||||||
func IsV22(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v22)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsV23 returns if is a given Taskfile version is at least version 2.3
|
|
||||||
func IsV23(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v23)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustVersion(s string) *semver.Version {
|
|
||||||
v, err := semver.NewVersion(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
31
precondition.go
Normal file
31
precondition.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/execext"
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPreconditionFailed is returned when a precondition fails
|
||||||
|
ErrPreconditionFailed = errors.New("task: precondition not met")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||||
|
for _, p := range t.Preconditions {
|
||||||
|
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
|
Command: p.Sh,
|
||||||
|
Dir: t.Dir,
|
||||||
|
Env: getEnviron(t),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
e.Logger.Errf("task: %s", p.Msg)
|
||||||
|
return false, ErrPreconditionFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
11
status.go
11
status.go
@@ -58,10 +58,11 @@ func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
case "checksum":
|
case "checksum":
|
||||||
return &status.Checksum{
|
return &status.Checksum{
|
||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
Task: t.Task,
|
Task: t.Task,
|
||||||
Sources: t.Sources,
|
Sources: t.Sources,
|
||||||
Dry: e.Dry,
|
Generates: t.Generates,
|
||||||
|
Dry: e.Dry,
|
||||||
}, nil
|
}, nil
|
||||||
case "none":
|
case "none":
|
||||||
return status.None{}, nil
|
return status.None{}, nil
|
||||||
@@ -78,8 +79,10 @@ func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (
|
|||||||
Env: getEnviron(t),
|
Env: getEnviron(t),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
e.Logger.VerboseOutf("task: status command %s exited non-zero: %s", s, err)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
e.Logger.VerboseOutf("task: status command %s exited zero", s)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|||||||
136
task.go
136
task.go
@@ -2,9 +2,12 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/go-task/task/v2/internal/compiler"
|
"github.com/go-task/task/v2/internal/compiler"
|
||||||
@@ -13,11 +16,10 @@ import (
|
|||||||
"github.com/go-task/task/v2/internal/execext"
|
"github.com/go-task/task/v2/internal/execext"
|
||||||
"github.com/go-task/task/v2/internal/logger"
|
"github.com/go-task/task/v2/internal/logger"
|
||||||
"github.com/go-task/task/v2/internal/output"
|
"github.com/go-task/task/v2/internal/output"
|
||||||
|
"github.com/go-task/task/v2/internal/summary"
|
||||||
"github.com/go-task/task/v2/internal/taskfile"
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
"github.com/go-task/task/v2/internal/taskfile/read"
|
"github.com/go-task/task/v2/internal/taskfile/read"
|
||||||
"github.com/go-task/task/v2/internal/taskfile/version"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver"
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,12 +32,15 @@ const (
|
|||||||
// Executor executes a Taskfile
|
// Executor executes a Taskfile
|
||||||
type Executor struct {
|
type Executor struct {
|
||||||
Taskfile *taskfile.Taskfile
|
Taskfile *taskfile.Taskfile
|
||||||
Dir string
|
|
||||||
Force bool
|
Dir string
|
||||||
Watch bool
|
Entrypoint string
|
||||||
Verbose bool
|
Force bool
|
||||||
Silent bool
|
Watch bool
|
||||||
Dry bool
|
Verbose bool
|
||||||
|
Silent bool
|
||||||
|
Dry bool
|
||||||
|
Summary bool
|
||||||
|
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
@@ -49,6 +54,7 @@ type Executor struct {
|
|||||||
taskvars taskfile.Vars
|
taskvars taskfile.Vars
|
||||||
|
|
||||||
taskCallCount map[string]*int32
|
taskCallCount map[string]*int32
|
||||||
|
mkdirMutexMap map[string]*sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs Task
|
// Run runs Task
|
||||||
@@ -62,6 +68,11 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.Summary {
|
||||||
|
summary.PrintTasks(e.Logger, e.Taskfile, calls)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if e.Watch {
|
if e.Watch {
|
||||||
return e.watchTasks(calls...)
|
return e.watchTasks(calls...)
|
||||||
}
|
}
|
||||||
@@ -76,8 +87,12 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
|||||||
|
|
||||||
// Setup setups Executor's internal state
|
// Setup setups Executor's internal state
|
||||||
func (e *Executor) Setup() error {
|
func (e *Executor) Setup() error {
|
||||||
|
if e.Entrypoint == "" {
|
||||||
|
e.Entrypoint = "Taskfile.yml"
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
e.Taskfile, err = read.Taskfile(e.Dir)
|
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -86,11 +101,6 @@ func (e *Executor) Setup() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := semver.NewConstraint(e.Taskfile.Version)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`task: could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Stdin == nil {
|
if e.Stdin == nil {
|
||||||
e.Stdin = os.Stdin
|
e.Stdin = os.Stdin
|
||||||
}
|
}
|
||||||
@@ -105,14 +115,30 @@ func (e *Executor) Setup() error {
|
|||||||
Stderr: e.Stderr,
|
Stderr: e.Stderr,
|
||||||
Verbose: e.Verbose,
|
Verbose: e.Verbose,
|
||||||
}
|
}
|
||||||
switch {
|
|
||||||
case version.IsV1(v):
|
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
||||||
|
}
|
||||||
|
// consider as equal to the greater version if round
|
||||||
|
if v == 2.0 {
|
||||||
|
v = 2.6
|
||||||
|
}
|
||||||
|
|
||||||
|
if v < 1 {
|
||||||
|
return fmt.Errorf(`task: Taskfile version should be greater or equal to v1`)
|
||||||
|
}
|
||||||
|
if v > 2.6 {
|
||||||
|
return fmt.Errorf(`task: Taskfile versions greater than v2.6 not implemented in the version of Task`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v < 2 {
|
||||||
e.Compiler = &compilerv1.CompilerV1{
|
e.Compiler = &compilerv1.CompilerV1{
|
||||||
Dir: e.Dir,
|
Dir: e.Dir,
|
||||||
Vars: e.taskvars,
|
Vars: e.taskvars,
|
||||||
Logger: e.Logger,
|
Logger: e.Logger,
|
||||||
}
|
}
|
||||||
case version.IsV2(v), version.IsV21(v), version.IsV22(v):
|
} else { // v >= 2
|
||||||
e.Compiler = &compilerv2.CompilerV2{
|
e.Compiler = &compilerv2.CompilerV2{
|
||||||
Dir: e.Dir,
|
Dir: e.Dir,
|
||||||
Taskvars: e.taskvars,
|
Taskvars: e.taskvars,
|
||||||
@@ -120,16 +146,15 @@ func (e *Executor) Setup() error {
|
|||||||
Expansions: e.Taskfile.Expansions,
|
Expansions: e.Taskfile.Expansions,
|
||||||
Logger: e.Logger,
|
Logger: e.Logger,
|
||||||
}
|
}
|
||||||
case version.IsV23(v):
|
|
||||||
return fmt.Errorf(`task: Taskfile versions greater than v2.3 not implemented in the version of Task`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !version.IsV21(v) && e.Taskfile.Output != "" {
|
if v < 2.1 && e.Taskfile.Output != "" {
|
||||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||||
}
|
}
|
||||||
if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
|
if v < 2.2 && len(e.Taskfile.Includes) > 0 {
|
||||||
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.OutputStyle != "" {
|
if e.OutputStyle != "" {
|
||||||
e.Taskfile.Output = e.OutputStyle
|
e.Taskfile.Output = e.OutputStyle
|
||||||
}
|
}
|
||||||
@@ -144,8 +169,8 @@ func (e *Executor) Setup() error {
|
|||||||
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !version.IsV21(v) {
|
if v <= 2.1 {
|
||||||
err := fmt.Errorf(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
||||||
|
|
||||||
for _, task := range e.Taskfile.Tasks {
|
for _, task := range e.Taskfile.Tasks {
|
||||||
if task.IgnoreError {
|
if task.IgnoreError {
|
||||||
@@ -159,9 +184,19 @@ func (e *Executor) Setup() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v < 2.6 {
|
||||||
|
for _, task := range e.Taskfile.Tasks {
|
||||||
|
if len(task.Preconditions) > 0 {
|
||||||
|
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
|
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 {
|
for k := range e.Taskfile.Tasks {
|
||||||
e.taskCallCount[k] = new(int32)
|
e.taskCallCount[k] = new(int32)
|
||||||
|
e.mkdirMutexMap[k] = &sync.Mutex{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -181,11 +216,17 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !e.Force {
|
if !e.Force {
|
||||||
|
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
upToDate, err := e.isTaskUpToDate(ctx, t)
|
upToDate, err := e.isTaskUpToDate(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if upToDate {
|
|
||||||
|
if upToDate && preCondMet {
|
||||||
if !e.Silent {
|
if !e.Silent {
|
||||||
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
|
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
|
||||||
}
|
}
|
||||||
@@ -193,6 +234,10 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := e.mkdir(t); err != nil {
|
||||||
|
e.Logger.Errf("task: cannot make directory %q: %v", t.Dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
for i := range t.Cmds {
|
for i := range t.Cmds {
|
||||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||||
if err2 := e.statusOnError(t); err2 != nil {
|
if err2 := e.statusOnError(t); err2 != nil {
|
||||||
@@ -210,6 +255,23 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
return nil
|
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 {
|
func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
@@ -217,7 +279,11 @@ func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
|||||||
d := d
|
d := d
|
||||||
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
return e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
err := e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +295,11 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case cmd.Task != "":
|
case cmd.Task != "":
|
||||||
return e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
err := e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
case cmd.Cmd != "":
|
case cmd.Cmd != "":
|
||||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
|
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
|
||||||
e.Logger.Errf(cmd.Cmd)
|
e.Logger.Errf(cmd.Cmd)
|
||||||
@@ -241,8 +311,18 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
|
|
||||||
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
|
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
|
||||||
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
|
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
|
||||||
defer stdOut.Close()
|
defer func() {
|
||||||
defer stdErr.Close()
|
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{
|
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
Command: cmd.Cmd,
|
Command: cmd.Cmd,
|
||||||
|
|||||||
148
task_test.go
148
task_test.go
@@ -7,13 +7,13 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-task/task/v2"
|
"github.com/go-task/task/v2"
|
||||||
"github.com/go-task/task/v2/internal/taskfile"
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ func TestDeps(t *testing.T) {
|
|||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
f = filepath.Join(dir, f)
|
f = filepath.Join(dir, f)
|
||||||
if _, err := os.Stat(f); err != nil {
|
if _, err := os.Stat(f); err != nil {
|
||||||
t.Errorf("File %s should exists", f)
|
t.Errorf("File %s should exist", f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ func TestStatus(t *testing.T) {
|
|||||||
_ = os.Remove(file)
|
_ = os.Remove(file)
|
||||||
|
|
||||||
if _, err := os.Stat(file); err == nil {
|
if _, err := os.Stat(file); err == nil {
|
||||||
t.Errorf("File should not exists: %v", err)
|
t.Errorf("File should not exist: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
@@ -262,7 +262,7 @@ func TestStatus(t *testing.T) {
|
|||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||||
|
|
||||||
if _, err := os.Stat(file); err != nil {
|
if _, err := os.Stat(file); err != nil {
|
||||||
t.Errorf("File should exists: %v", err)
|
t.Errorf("File should exist: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Silent = false
|
e.Silent = false
|
||||||
@@ -273,6 +273,47 @@ func TestStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrecondition(t *testing.T) {
|
||||||
|
const dir = "testdata/precondition"
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precondition that has been met
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
||||||
|
if buff.String() != "" {
|
||||||
|
t.Errorf("Got Output when none was expected: %s", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precondition that was not met
|
||||||
|
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "impossible"}))
|
||||||
|
|
||||||
|
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||||
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
// Calling a task with a precondition in a dependency fails the task
|
||||||
|
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "depends_on_impossible"}))
|
||||||
|
|
||||||
|
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||||
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
// Calling a task with a precondition in a cmd fails the task
|
||||||
|
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "executes_failing_task_as_cmd"}))
|
||||||
|
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||||
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerates(t *testing.T) {
|
func TestGenerates(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
srcTask = "sub/src.txt"
|
srcTask = "sub/src.txt"
|
||||||
@@ -290,7 +331,7 @@ func TestGenerates(t *testing.T) {
|
|||||||
path := filepath.Join(dir, task)
|
path := filepath.Join(dir, task)
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
t.Errorf("File should not exists: %v", err)
|
t.Errorf("File should not exist: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,10 +352,10 @@ func TestGenerates(t *testing.T) {
|
|||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
|
||||||
|
|
||||||
if _, err := os.Stat(srcFile); err != nil {
|
if _, err := os.Stat(srcFile); err != nil {
|
||||||
t.Errorf("File should exists: %v", err)
|
t.Errorf("File should exist: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(destFile); err != nil {
|
if _, err := os.Stat(destFile); err != nil {
|
||||||
t.Errorf("File should exists: %v", err)
|
t.Errorf("File should exist: %v", err)
|
||||||
}
|
}
|
||||||
// Ensure task was not incorrectly found to be up-to-date on first run.
|
// Ensure task was not incorrectly found to be up-to-date on first run.
|
||||||
if buff.String() == upToDate {
|
if buff.String() == upToDate {
|
||||||
@@ -371,7 +412,7 @@ func TestInit(t *testing.T) {
|
|||||||
|
|
||||||
_ = os.Remove(file)
|
_ = os.Remove(file)
|
||||||
if _, err := os.Stat(file); err == nil {
|
if _, err := os.Stat(file); err == nil {
|
||||||
t.Errorf("Taskfile.yml should not exists")
|
t.Errorf("Taskfile.yml should not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
|
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
|
||||||
@@ -379,7 +420,7 @@ func TestInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(file); err != nil {
|
if _, err := os.Stat(file); err != nil {
|
||||||
t.Errorf("Taskfile.yml should exists")
|
t.Errorf("Taskfile.yml should exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +478,7 @@ func TestTaskIgnoreErrors(t *testing.T) {
|
|||||||
func TestExpand(t *testing.T) {
|
func TestExpand(t *testing.T) {
|
||||||
const dir = "testdata/expand"
|
const dir = "testdata/expand"
|
||||||
|
|
||||||
home, err := homedir.Dir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Couldn't get $HOME: %v", err)
|
t.Errorf("Couldn't get $HOME: %v", err)
|
||||||
}
|
}
|
||||||
@@ -553,3 +594,90 @@ func TestIncludesCallingRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSummary(t *testing.T) {
|
||||||
|
const dir = "testdata/summary"
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
Summary: true,
|
||||||
|
Silent: true,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
expectedOutput := string(data)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
expectedOutput = strings.Replace(expectedOutput, "\r\n", "\n", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expectedOutput, buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
|
||||||
|
const expected = "dir"
|
||||||
|
const dir = "testdata/" + expected
|
||||||
|
var out bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &out,
|
||||||
|
Stderr: &out,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
|
||||||
|
|
||||||
|
// got should be the "dir" part of "testdata/dir"
|
||||||
|
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||||
|
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
|
||||||
|
const expected = "exists"
|
||||||
|
const dir = "testdata/dir/explicit_exists"
|
||||||
|
var out bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &out,
|
||||||
|
Stderr: &out,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
|
||||||
|
|
||||||
|
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||||
|
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
|
||||||
|
const expected = "createme"
|
||||||
|
const dir = "testdata/dir/explicit_doesnt_exist/"
|
||||||
|
const toBeCreated = dir + expected
|
||||||
|
const target = "whereami"
|
||||||
|
var out bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &out,
|
||||||
|
Stderr: &out,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the directory to be created doesn't actually exist.
|
||||||
|
_ = os.Remove(toBeCreated)
|
||||||
|
if _, err := os.Stat(toBeCreated); err == nil {
|
||||||
|
t.Errorf("Directory should not exist: %v", err)
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}))
|
||||||
|
|
||||||
|
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||||
|
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||||
|
|
||||||
|
// Clean-up after ourselves only if no error.
|
||||||
|
_ = os.Remove(toBeCreated)
|
||||||
|
}
|
||||||
|
|||||||
7
testdata/dir/Taskfile.yml
vendored
Normal file
7
testdata/dir/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
whereami:
|
||||||
|
cmds:
|
||||||
|
- pwd
|
||||||
|
silent: true
|
||||||
8
testdata/dir/explicit_doesnt_exist/Taskfile.yml
vendored
Normal file
8
testdata/dir/explicit_doesnt_exist/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
whereami:
|
||||||
|
dir: createme
|
||||||
|
cmds:
|
||||||
|
- pwd
|
||||||
|
silent: true
|
||||||
8
testdata/dir/explicit_exists/Taskfile.yml
vendored
Normal file
8
testdata/dir/explicit_exists/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
whereami:
|
||||||
|
dir: exists
|
||||||
|
cmds:
|
||||||
|
- pwd
|
||||||
|
silent: true
|
||||||
0
testdata/dir/explicit_exists/exists/.keep
vendored
Normal file
0
testdata/dir/explicit_exists/exists/.keep
vendored
Normal file
19
testdata/precondition/Taskfile.yml
vendored
Normal file
19
testdata/precondition/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
preconditions:
|
||||||
|
- test -f foo.txt
|
||||||
|
|
||||||
|
impossible:
|
||||||
|
preconditions:
|
||||||
|
- sh: "[ 1 = 0 ]"
|
||||||
|
msg: "1 != 0 obviously!"
|
||||||
|
|
||||||
|
depends_on_impossible:
|
||||||
|
deps:
|
||||||
|
- impossible
|
||||||
|
|
||||||
|
executes_failing_task_as_cmd:
|
||||||
|
cmds:
|
||||||
|
- task: impossible
|
||||||
0
testdata/precondition/foo.txt
vendored
Normal file
0
testdata/precondition/foo.txt
vendored
Normal file
26
testdata/summary/Taskfile.yml
vendored
Normal file
26
testdata/summary/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
task-with-summary:
|
||||||
|
deps: [dependend-task-1, dependend-task-2]
|
||||||
|
summary: |
|
||||||
|
summary of task-with-summary - line 1
|
||||||
|
line 2
|
||||||
|
line 3
|
||||||
|
cmds:
|
||||||
|
- echo 'task-with-summary was executed'
|
||||||
|
- echo 'another command'
|
||||||
|
- exit 0
|
||||||
|
|
||||||
|
other-task-with-summary:
|
||||||
|
summary: summary of other-task-with-summary
|
||||||
|
cmds:
|
||||||
|
- echo 'other-task-with-summary was executed'
|
||||||
|
|
||||||
|
dependend-task-1:
|
||||||
|
cmds:
|
||||||
|
- echo 'dependend-task-1 was executed'
|
||||||
|
|
||||||
|
dependend-task-2:
|
||||||
|
cmds:
|
||||||
|
- echo 'dependend-task-2 was executed'
|
||||||
22
testdata/summary/task-with-summary.txt
vendored
Normal file
22
testdata/summary/task-with-summary.txt
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
task: task-with-summary
|
||||||
|
|
||||||
|
summary of task-with-summary - line 1
|
||||||
|
line 2
|
||||||
|
line 3
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- dependend-task-1
|
||||||
|
- dependend-task-2
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- echo 'task-with-summary was executed'
|
||||||
|
- echo 'another command'
|
||||||
|
- exit 0
|
||||||
|
|
||||||
|
|
||||||
|
task: other-task-with-summary
|
||||||
|
|
||||||
|
summary of other-task-with-summary
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- echo 'other-task-with-summary was executed'
|
||||||
10
variables.go
10
variables.go
@@ -84,5 +84,15 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(origTask.Preconditions) > 0 {
|
||||||
|
new.Preconditions = make([]*taskfile.Precondition, len(origTask.Preconditions))
|
||||||
|
for i, precond := range origTask.Preconditions {
|
||||||
|
new.Preconditions[i] = &taskfile.Precondition{
|
||||||
|
Sh: r.Replace(precond.Sh),
|
||||||
|
Msg: r.Replace(precond.Msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &new, r.Err()
|
return &new, r.Err()
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -38,7 +38,7 @@ golang.org/x/sys/unix
|
|||||||
golang.org/x/sys/windows
|
golang.org/x/sys/windows
|
||||||
# gopkg.in/yaml.v2 v2.2.1
|
# gopkg.in/yaml.v2 v2.2.1
|
||||||
gopkg.in/yaml.v2
|
gopkg.in/yaml.v2
|
||||||
# mvdan.cc/sh v2.6.3+incompatible
|
# mvdan.cc/sh v2.6.4+incompatible
|
||||||
mvdan.cc/sh/expand
|
mvdan.cc/sh/expand
|
||||||
mvdan.cc/sh/interp
|
mvdan.cc/sh/interp
|
||||||
mvdan.cc/sh/shell
|
mvdan.cc/sh/shell
|
||||||
|
|||||||
26
vendor/mvdan.cc/sh/expand/expand.go
vendored
26
vendor/mvdan.cc/sh/expand/expand.go
vendored
@@ -373,7 +373,11 @@ func (cfg *Config) wordField(wps []syntax.WordPart, ql quoteLevel) ([]fieldPart,
|
|||||||
case *syntax.Lit:
|
case *syntax.Lit:
|
||||||
s := x.Value
|
s := x.Value
|
||||||
if i == 0 && ql == quoteNone {
|
if i == 0 && ql == quoteNone {
|
||||||
s = cfg.expandUser(s)
|
if prefix, rest := cfg.expandUser(s); prefix != "" {
|
||||||
|
// TODO: return two separate fieldParts,
|
||||||
|
// like in wordFields?
|
||||||
|
s = prefix + rest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ql == quoteDouble && strings.Contains(s, "\\") {
|
if ql == quoteDouble && strings.Contains(s, "\\") {
|
||||||
buf := cfg.strBuilder()
|
buf := cfg.strBuilder()
|
||||||
@@ -468,7 +472,12 @@ func (cfg *Config) wordFields(wps []syntax.WordPart) ([][]fieldPart, error) {
|
|||||||
case *syntax.Lit:
|
case *syntax.Lit:
|
||||||
s := x.Value
|
s := x.Value
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
s = cfg.expandUser(s)
|
prefix, rest := cfg.expandUser(s)
|
||||||
|
curField = append(curField, fieldPart{
|
||||||
|
quote: quoteSingle,
|
||||||
|
val: prefix,
|
||||||
|
})
|
||||||
|
s = rest
|
||||||
}
|
}
|
||||||
if strings.Contains(s, "\\") {
|
if strings.Contains(s, "\\") {
|
||||||
buf := cfg.strBuilder()
|
buf := cfg.strBuilder()
|
||||||
@@ -562,28 +571,27 @@ func (cfg *Config) quotedElems(pe *syntax.ParamExp) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) expandUser(field string) string {
|
func (cfg *Config) expandUser(field string) (prefix, rest string) {
|
||||||
if len(field) == 0 || field[0] != '~' {
|
if len(field) == 0 || field[0] != '~' {
|
||||||
return field
|
return "", field
|
||||||
}
|
}
|
||||||
name := field[1:]
|
name := field[1:]
|
||||||
rest := ""
|
|
||||||
if i := strings.Index(name, "/"); i >= 0 {
|
if i := strings.Index(name, "/"); i >= 0 {
|
||||||
rest = name[i:]
|
rest = name[i:]
|
||||||
name = name[:i]
|
name = name[:i]
|
||||||
}
|
}
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return cfg.Env.Get("HOME").String() + rest
|
return cfg.Env.Get("HOME").String(), rest
|
||||||
}
|
}
|
||||||
if vr := cfg.Env.Get("HOME " + name); vr.IsSet() {
|
if vr := cfg.Env.Get("HOME " + name); vr.IsSet() {
|
||||||
return vr.String() + rest
|
return vr.String(), rest
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user.Lookup(name)
|
u, err := user.Lookup(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return field
|
return "", field
|
||||||
}
|
}
|
||||||
return u.HomeDir + rest
|
return u.HomeDir, rest
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAllIndex(pattern, name string, n int) [][]int {
|
func findAllIndex(pattern, name string, n int) [][]int {
|
||||||
|
|||||||
1
vendor/mvdan.cc/sh/interp/builtin.go
vendored
1
vendor/mvdan.cc/sh/interp/builtin.go
vendored
@@ -70,6 +70,7 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
|
|||||||
r.errf("set: %v\n", err)
|
r.errf("set: %v\n", err)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
r.updateExpandOpts()
|
||||||
case "shift":
|
case "shift":
|
||||||
n := 1
|
n := 1
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
|
|||||||
15
vendor/mvdan.cc/sh/interp/interp.go
vendored
15
vendor/mvdan.cc/sh/interp/interp.go
vendored
@@ -260,7 +260,6 @@ func Params(args ...string) func(*Runner) error {
|
|||||||
args = args[1:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
r.Params = args
|
r.Params = args
|
||||||
r.updateExpandOpts()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -479,6 +478,7 @@ func (r *Runner) Reset() {
|
|||||||
Exec: r.Exec,
|
Exec: r.Exec,
|
||||||
Open: r.Open,
|
Open: r.Open,
|
||||||
KillTimeout: r.KillTimeout,
|
KillTimeout: r.KillTimeout,
|
||||||
|
opts: r.opts,
|
||||||
|
|
||||||
// emptied below, to reuse the space
|
// emptied below, to reuse the space
|
||||||
Vars: r.Vars,
|
Vars: r.Vars,
|
||||||
@@ -791,7 +791,11 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) {
|
|||||||
switch y := x.Loop.(type) {
|
switch y := x.Loop.(type) {
|
||||||
case *syntax.WordIter:
|
case *syntax.WordIter:
|
||||||
name := y.Name.Value
|
name := y.Name.Value
|
||||||
for _, field := range r.fields(y.Items...) {
|
items := r.Params // for i; do ...
|
||||||
|
if y.InPos.IsValid() {
|
||||||
|
items = r.fields(y.Items...) // for i in ...; do ...
|
||||||
|
}
|
||||||
|
for _, field := range items {
|
||||||
r.setVarString(name, field)
|
r.setVarString(name, field)
|
||||||
if r.loopStmtsBroken(ctx, x.Do) {
|
if r.loopStmtsBroken(ctx, x.Do) {
|
||||||
break
|
break
|
||||||
@@ -1182,6 +1186,10 @@ func (r *Runner) findExecutable(file string, exts []string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func driveLetter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
// splitList is like filepath.SplitList, but always using the unix path
|
// splitList is like filepath.SplitList, but always using the unix path
|
||||||
// list separator ':'. On Windows, it also makes sure not to split
|
// list separator ':'. On Windows, it also makes sure not to split
|
||||||
// [A-Z]:[/\].
|
// [A-Z]:[/\].
|
||||||
@@ -1198,8 +1206,7 @@ func splitList(path string) []string {
|
|||||||
for i := 0; i < len(list); i++ {
|
for i := 0; i < len(list); i++ {
|
||||||
s := list[i]
|
s := list[i]
|
||||||
switch {
|
switch {
|
||||||
case len(s) != 1, s[0] < 'A', s[0] > 'Z':
|
case len(s) != 1, !driveLetter(s[0]):
|
||||||
// not a disk name
|
|
||||||
case i+1 >= len(list):
|
case i+1 >= len(list):
|
||||||
// last element
|
// last element
|
||||||
case strings.IndexAny(list[i+1], `/\`) != 0:
|
case strings.IndexAny(list[i+1], `/\`) != 0:
|
||||||
|
|||||||
2
vendor/mvdan.cc/sh/interp/test.go
vendored
2
vendor/mvdan.cc/sh/interp/test.go
vendored
@@ -19,7 +19,7 @@ import (
|
|||||||
func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic bool) string {
|
func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic bool) string {
|
||||||
switch x := expr.(type) {
|
switch x := expr.(type) {
|
||||||
case *syntax.Word:
|
case *syntax.Word:
|
||||||
return r.literal(x)
|
return r.document(x)
|
||||||
case *syntax.ParenTest:
|
case *syntax.ParenTest:
|
||||||
return r.bashTest(ctx, x.X, classic)
|
return r.bashTest(ctx, x.X, classic)
|
||||||
case *syntax.BinaryTest:
|
case *syntax.BinaryTest:
|
||||||
|
|||||||
8
vendor/mvdan.cc/sh/shell/doc.go
vendored
8
vendor/mvdan.cc/sh/shell/doc.go
vendored
@@ -3,4 +3,12 @@
|
|||||||
|
|
||||||
// Package shell contains high-level features that use the syntax, expand, and
|
// Package shell contains high-level features that use the syntax, expand, and
|
||||||
// interp packages under the hood.
|
// interp packages under the hood.
|
||||||
|
//
|
||||||
|
// Please note that this package uses POSIX Shell syntax. As such, path names on
|
||||||
|
// Windows need to use double backslashes or be within single quotes when given
|
||||||
|
// to functions like Fields. For example:
|
||||||
|
//
|
||||||
|
// shell.Fields("echo /foo/bar") // on Unix-like
|
||||||
|
// shell.Fields("echo C:\\foo\\bar") // on Windows
|
||||||
|
// shell.Fields("echo 'C:\foo\bar'") // on Windows, with quotes
|
||||||
package shell
|
package shell
|
||||||
|
|||||||
24
vendor/mvdan.cc/sh/syntax/nodes.go
vendored
24
vendor/mvdan.cc/sh/syntax/nodes.go
vendored
@@ -376,14 +376,21 @@ func (*WordIter) loopNode() {}
|
|||||||
func (*CStyleLoop) loopNode() {}
|
func (*CStyleLoop) loopNode() {}
|
||||||
|
|
||||||
// WordIter represents the iteration of a variable over a series of words in a
|
// WordIter represents the iteration of a variable over a series of words in a
|
||||||
// for clause.
|
// for clause. If InPos is an invalid position, the "in" token was missing, so
|
||||||
|
// the iteration is over the shell's positional parameters.
|
||||||
type WordIter struct {
|
type WordIter struct {
|
||||||
Name *Lit
|
Name *Lit
|
||||||
|
InPos Pos // position of "in"
|
||||||
Items []*Word
|
Items []*Word
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WordIter) Pos() Pos { return w.Name.Pos() }
|
func (w *WordIter) Pos() Pos { return w.Name.Pos() }
|
||||||
func (w *WordIter) End() Pos { return posMax(w.Name.End(), wordLastEnd(w.Items)) }
|
func (w *WordIter) End() Pos {
|
||||||
|
if len(w.Items) > 0 {
|
||||||
|
return wordLastEnd(w.Items)
|
||||||
|
}
|
||||||
|
return posMax(w.Name.End(), posAddCol(w.InPos, 2))
|
||||||
|
}
|
||||||
|
|
||||||
// CStyleLoop represents the behaviour of a for clause similar to the C
|
// CStyleLoop represents the behaviour of a for clause similar to the C
|
||||||
// language.
|
// language.
|
||||||
@@ -781,8 +788,12 @@ func (a *ArrayExpr) Pos() Pos { return a.Lparen }
|
|||||||
func (a *ArrayExpr) End() Pos { return posAddCol(a.Rparen, 1) }
|
func (a *ArrayExpr) End() Pos { return posAddCol(a.Rparen, 1) }
|
||||||
|
|
||||||
// ArrayElem represents a Bash array element.
|
// ArrayElem represents a Bash array element.
|
||||||
|
//
|
||||||
|
// Index can be nil; for example, declare -a x=(value).
|
||||||
|
// Value can be nil; for example, declare -A x=([index]=).
|
||||||
|
// Finally, neither can be nil; for example, declare -A x=([index]=value)
|
||||||
type ArrayElem struct {
|
type ArrayElem struct {
|
||||||
Index ArithmExpr // [i]=, ["k"]=
|
Index ArithmExpr
|
||||||
Value *Word
|
Value *Word
|
||||||
Comments []Comment
|
Comments []Comment
|
||||||
}
|
}
|
||||||
@@ -793,7 +804,12 @@ func (a *ArrayElem) Pos() Pos {
|
|||||||
}
|
}
|
||||||
return a.Value.Pos()
|
return a.Value.Pos()
|
||||||
}
|
}
|
||||||
func (a *ArrayElem) End() Pos { return a.Value.End() }
|
func (a *ArrayElem) End() Pos {
|
||||||
|
if a.Value != nil {
|
||||||
|
return a.Value.End()
|
||||||
|
}
|
||||||
|
return posAddCol(a.Index.Pos(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
// ExtGlob represents a Bash extended globbing expression. Note that these are
|
// ExtGlob represents a Bash extended globbing expression. Note that these are
|
||||||
// parsed independently of whether shopt has been called or not.
|
// parsed independently of whether shopt has been called or not.
|
||||||
|
|||||||
19
vendor/mvdan.cc/sh/syntax/parser.go
vendored
19
vendor/mvdan.cc/sh/syntax/parser.go
vendored
@@ -1003,6 +1003,11 @@ func (p *Parser) wordPart() WordPart {
|
|||||||
|
|
||||||
p.next()
|
p.next()
|
||||||
cs.StmtList = p.stmtList()
|
cs.StmtList = p.stmtList()
|
||||||
|
if p.tok == bckQuote && p.lastBquoteEsc < p.openBquotes-1 {
|
||||||
|
// e.g. found ` before the nested backquote \` was closed.
|
||||||
|
p.tok = _EOF
|
||||||
|
p.quoteErr(cs.Pos(), bckQuote)
|
||||||
|
}
|
||||||
p.postNested(old)
|
p.postNested(old)
|
||||||
p.openBquotes--
|
p.openBquotes--
|
||||||
cs.Right = p.pos
|
cs.Right = p.pos
|
||||||
@@ -1587,11 +1592,16 @@ func (p *Parser) getAssign(needEqual bool) *Assign {
|
|||||||
p.follow(left, `"[x]"`, assgn)
|
p.follow(left, `"[x]"`, assgn)
|
||||||
}
|
}
|
||||||
if ae.Value = p.getWord(); ae.Value == nil {
|
if ae.Value = p.getWord(); ae.Value == nil {
|
||||||
if p.tok == leftParen {
|
switch p.tok {
|
||||||
|
case leftParen:
|
||||||
p.curErr("arrays cannot be nested")
|
p.curErr("arrays cannot be nested")
|
||||||
|
return nil
|
||||||
|
case _Newl, rightParen, leftBrack:
|
||||||
|
// TODO: support [index]=[
|
||||||
|
default:
|
||||||
|
p.curErr("array element values must be words")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
p.curErr("array element values must be words")
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if len(p.accComs) > 0 {
|
if len(p.accComs) > 0 {
|
||||||
c := p.accComs[0]
|
c := p.accComs[0]
|
||||||
@@ -2012,7 +2022,8 @@ func (p *Parser) wordIter(ftok string, fpos Pos) *WordIter {
|
|||||||
return wi
|
return wi
|
||||||
}
|
}
|
||||||
p.got(_Newl)
|
p.got(_Newl)
|
||||||
if _, ok := p.gotRsrv("in"); ok {
|
if pos, ok := p.gotRsrv("in"); ok {
|
||||||
|
wi.InPos = pos
|
||||||
for !stopToken(p.tok) {
|
for !stopToken(p.tok) {
|
||||||
if w := p.getWord(); w == nil {
|
if w := p.getWord(); w == nil {
|
||||||
p.curErr("word list can only contain words")
|
p.curErr("word list can only contain words")
|
||||||
|
|||||||
6
vendor/mvdan.cc/sh/syntax/printer.go
vendored
6
vendor/mvdan.cc/sh/syntax/printer.go
vendored
@@ -620,7 +620,7 @@ func (p *Printer) loop(loop Loop) {
|
|||||||
switch x := loop.(type) {
|
switch x := loop.(type) {
|
||||||
case *WordIter:
|
case *WordIter:
|
||||||
p.WriteString(x.Name.Value)
|
p.WriteString(x.Name.Value)
|
||||||
if len(x.Items) > 0 {
|
if x.InPos.IsValid() {
|
||||||
p.spacedString(" in", Pos{})
|
p.spacedString(" in", Pos{})
|
||||||
p.wordJoin(x.Items)
|
p.wordJoin(x.Items)
|
||||||
}
|
}
|
||||||
@@ -788,7 +788,9 @@ func (p *Printer) elemJoin(elems []*ArrayElem, last []Comment) {
|
|||||||
if p.wroteIndex(el.Index) {
|
if p.wroteIndex(el.Index) {
|
||||||
p.WriteByte('=')
|
p.WriteByte('=')
|
||||||
}
|
}
|
||||||
p.word(el.Value)
|
if el.Value != nil {
|
||||||
|
p.word(el.Value)
|
||||||
|
}
|
||||||
p.comments(left...)
|
p.comments(left...)
|
||||||
}
|
}
|
||||||
if len(last) > 0 {
|
if len(last) > 0 {
|
||||||
|
|||||||
4
vendor/mvdan.cc/sh/syntax/walk.go
vendored
4
vendor/mvdan.cc/sh/syntax/walk.go
vendored
@@ -199,7 +199,9 @@ func Walk(node Node, f func(Node) bool) {
|
|||||||
if x.Index != nil {
|
if x.Index != nil {
|
||||||
Walk(x.Index, f)
|
Walk(x.Index, f)
|
||||||
}
|
}
|
||||||
Walk(x.Value, f)
|
if x.Value != nil {
|
||||||
|
Walk(x.Value, f)
|
||||||
|
}
|
||||||
case *ExtGlob:
|
case *ExtGlob:
|
||||||
Walk(x.Pattern, f)
|
Walk(x.Pattern, f)
|
||||||
case *ProcSubst:
|
case *ProcSubst:
|
||||||
|
|||||||
Reference in New Issue
Block a user