mirror of
https://github.com/go-task/task.git
synced 2026-06-23 20:55:52 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36317f8ddd | ||
|
|
115b4a308f | ||
|
|
62ba8dbabf |
@@ -7,7 +7,8 @@ insert_final_newline = true
|
|||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
indent_size = 8
|
||||||
|
|
||||||
[*.{md,yml,yaml,json,toml,htm,html}]
|
[*.{md,yml,yaml,json,toml}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|||||||
8
.github/CONTRIBUTING.md
vendored
8
.github/CONTRIBUTING.md
vendored
@@ -1,6 +1,12 @@
|
|||||||
* Bug reports and feature requests are welcome in [the issues][issues]
|
* Bug reports and feature requests are welcome in [the issues][issues]
|
||||||
|
* For questions and discussion there's the [Slack room][slack] ([invititation here][slackinvite])
|
||||||
* Pull Requests are welcome. For more complex changes and features it's
|
* Pull Requests are welcome. For more complex changes and features it's
|
||||||
recommended to open an issue with the feature request first
|
recommended to open an issue first
|
||||||
|
* About 3 or 4 pull requests accepted one gets write access to the repo.
|
||||||
|
Even then, possible backward incompatible changes should be discussed first
|
||||||
|
in an issue or pull request
|
||||||
* Documentation contributions are as important as code contributions
|
* Documentation contributions are as important as code contributions
|
||||||
|
|
||||||
[issues]: https://github.com/go-task/task/issues
|
[issues]: https://github.com/go-task/task/issues
|
||||||
|
[slack]: https://gophers.slack.com/messages/task
|
||||||
|
[slackinvite]: https://invite.slack.golangbridge.org/
|
||||||
|
|||||||
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
open_collective: task
|
|
||||||
patreon: andreynering
|
|
||||||
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=BRL&source=url'
|
|
||||||
9
.github/ISSUE_TEMPLATE.md
vendored
Normal file
9
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!--
|
||||||
|
For questions and general talk there's the Slack room: https://gophers.slack.com/messages/task
|
||||||
|
Invite to the Slack is available in this link: https://invite.slack.golangbridge.org/
|
||||||
|
|
||||||
|
If relevant, include the following information:
|
||||||
|
- Task version
|
||||||
|
- OS
|
||||||
|
- Example Taskfile showing the issue
|
||||||
|
-->
|
||||||
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug Report
|
|
||||||
about: Use the template to report bugs and issues
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
|
|
||||||
- Task version:
|
|
||||||
- Operating System:
|
|
||||||
|
|
||||||
### Example Taskfile showing the issue
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
|
|
||||||
```
|
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Questions, Ideas and General Discussions
|
|
||||||
url: https://github.com/go-task/task/discussions
|
|
||||||
about: Ask questions and discuss general ideas with the community
|
|
||||||
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature Request
|
|
||||||
about: Use the template to make feature requests
|
|
||||||
labels: feature
|
|
||||||
---
|
|
||||||
|
|
||||||
Describe in detail what feature do you want to see in Task.
|
|
||||||
Give examples if possible.
|
|
||||||
|
|
||||||
Please, search if this wasn't proposed before, and if this is more like an idea
|
|
||||||
than a strong feature request, consider opening a
|
|
||||||
[discussion](https://github.com/go-task/task/discussions) instead.
|
|
||||||
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
@@ -1,26 +0,0 @@
|
|||||||
name: goreleaser
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
goreleaser:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.15.x
|
|
||||||
|
|
||||||
- name: Run GoReleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v1
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: release --rm-dist
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
30
.github/workflows/test.yml
vendored
30
.github/workflows/test.yml
vendored
@@ -1,30 +0,0 @@
|
|||||||
name: Test
|
|
||||||
on: [push, pull_request]
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
name: Test
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go-version: [1.14.x, 1.15.x]
|
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
runs-on: ${{matrix.platform}}
|
|
||||||
steps:
|
|
||||||
- name: Set up Go ${{matrix.go-version}}
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: ${{matrix.go-version}}
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
|
|
||||||
- name: Download Go modules
|
|
||||||
run: go mod download
|
|
||||||
env:
|
|
||||||
GOPROXY: https://proxy.golang.org
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: go build -o ./bin/task -v ./cmd/task
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: ./bin/task test
|
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -14,17 +14,6 @@
|
|||||||
.glide/
|
.glide/
|
||||||
|
|
||||||
./task
|
./task
|
||||||
.task
|
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# intellij idea/goland
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# exuberant ctags
|
|
||||||
tags
|
|
||||||
|
|
||||||
/bin
|
|
||||||
/testdata/vars/v1
|
|
||||||
/tmp
|
|
||||||
|
|||||||
@@ -8,21 +8,16 @@ build:
|
|||||||
goarch:
|
goarch:
|
||||||
- 386
|
- 386
|
||||||
- amd64
|
- amd64
|
||||||
- arm
|
|
||||||
- arm64
|
|
||||||
goarm:
|
|
||||||
- 6
|
|
||||||
ignore:
|
ignore:
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
goarch: 386
|
goarch: 386
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
|
|
||||||
archives:
|
archive:
|
||||||
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||||
format_overrides:
|
|
||||||
- goos: windows
|
format_overrides:
|
||||||
format: zip
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
|
||||||
release:
|
release:
|
||||||
draft: true
|
draft: true
|
||||||
@@ -33,15 +28,15 @@ snapshot:
|
|||||||
checksum:
|
checksum:
|
||||||
name_template: "task_checksums.txt"
|
name_template: "task_checksums.txt"
|
||||||
|
|
||||||
nfpms:
|
nfpm:
|
||||||
- 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
|
||||||
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||||
|
|||||||
23
.travis.yml
Normal file
23
.travis.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- '1.8'
|
||||||
|
- '1.9'
|
||||||
|
- '1.10'
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- rpm
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go install github.com/go-task/task/cmd/task
|
||||||
|
- task ci
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
- provider: script
|
||||||
|
skip_cleanup: true
|
||||||
|
script: curl -sL http://git.io/goreleaser | bash
|
||||||
|
on:
|
||||||
|
tags: true
|
||||||
|
condition: $TRAVIS_OS_NAME = linux
|
||||||
348
CHANGELOG.md
348
CHANGELOG.md
@@ -1,348 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## v3.3.0 - 2021-03-20
|
|
||||||
|
|
||||||
- Add support for delegating CLI arguments to commands with `--` and a
|
|
||||||
special `CLI_ARGS` variable
|
|
||||||
([#327](https://github.com/go-task/task/issues/327)).
|
|
||||||
- Add a `--concurrency` (alias `-C`) flag, to limit the number of tasks that
|
|
||||||
run concurrently. This is useful for heavy workloads.
|
|
||||||
([#345](https://github.com/go-task/task/pull/345)).
|
|
||||||
|
|
||||||
## v3.2.2 - 2021-01-12
|
|
||||||
|
|
||||||
- Improve performance of `--list` and `--summary` by skipping running shell
|
|
||||||
variables for these flags
|
|
||||||
([#332](https://github.com/go-task/task/issues/332)).
|
|
||||||
- Fixed a bug where an environment in a Taskfile was not always overridable
|
|
||||||
by the system environment
|
|
||||||
([#425](https://github.com/go-task/task/issues/425)).
|
|
||||||
- Fixed environment from .env files not being available as variables
|
|
||||||
([#379](https://github.com/go-task/task/issues/379)).
|
|
||||||
- The install script is now working for ARM platforms
|
|
||||||
([#428](https://github.com/go-task/task/pull/428)).
|
|
||||||
|
|
||||||
## v3.2.1 - 2021-01-09
|
|
||||||
|
|
||||||
- Fixed some bugs and regressions regarding dynamic variables and directories
|
|
||||||
([#426](https://github.com/go-task/task/issues/426)).
|
|
||||||
- The [slim-sprig](https://github.com/go-task/slim-sprig) package was updated
|
|
||||||
with the upstream [sprig](https://github.com/Masterminds/sprig).
|
|
||||||
|
|
||||||
## v3.2.0 - 2021-01-07
|
|
||||||
|
|
||||||
- Fix the `.task` directory being created in the task directory instead of the
|
|
||||||
Taskfile directory
|
|
||||||
([#247](https://github.com/go-task/task/issues/247)).
|
|
||||||
- Fix a bug where dynamic variables (those declared with `sh:`) were not
|
|
||||||
running in the task directory when the task has a custom dir or it was
|
|
||||||
in an included Taskfile
|
|
||||||
([#384](https://github.com/go-task/task/issues/384)).
|
|
||||||
- The watch feature (via the `--watch` flag) got a few different bug fixes and
|
|
||||||
should be more stable now
|
|
||||||
([#423](https://github.com/go-task/task/pull/423), [#365](https://github.com/go-task/task/issues/365)).
|
|
||||||
|
|
||||||
## v3.1.0 - 2021-01-03
|
|
||||||
|
|
||||||
- Fix a bug when the checksum up-to-date resolution is used by a task
|
|
||||||
with a custom `label:` attribute
|
|
||||||
([#412](https://github.com/go-task/task/issues/412)).
|
|
||||||
- Starting from this release, we're releasing official ARMv6 and ARM64 binaries
|
|
||||||
for Linux
|
|
||||||
([#375](https://github.com/go-task/task/issues/375), [#418](https://github.com/go-task/task/issues/418)).
|
|
||||||
- Task now respects the order of declaration of included Taskfiles when
|
|
||||||
evaluating variables declaring by them
|
|
||||||
([#393](https://github.com/go-task/task/issues/393)).
|
|
||||||
- `set -e` is now automatically set on every command. This was done to fix an
|
|
||||||
issue where multiline string commands wouldn't really fail unless the
|
|
||||||
sentence was in the last line
|
|
||||||
([#403](https://github.com/go-task/task/issues/403)).
|
|
||||||
|
|
||||||
## v3.0.1 - 2020-12-26
|
|
||||||
|
|
||||||
- Allow use as a library by moving the required packages out of the `internal`
|
|
||||||
directory
|
|
||||||
([#358](https://github.com/go-task/task/pull/358)).
|
|
||||||
- Do not error if a specified dotenv file does not exist
|
|
||||||
([#378](https://github.com/go-task/task/issues/378), [#385](https://github.com/go-task/task/pull/385)).
|
|
||||||
- Fix panic when you have empty tasks in your Taskfile
|
|
||||||
([#338](https://github.com/go-task/task/issues/338), [#362](https://github.com/go-task/task/pull/362)).
|
|
||||||
|
|
||||||
## v3.0.0 - 2020-08-16
|
|
||||||
|
|
||||||
- On `v3`, all CLI variables will be considered global variables
|
|
||||||
([#336](https://github.com/go-task/task/issues/336), [#341](https://github.com/go-task/task/pull/341))
|
|
||||||
- Add support to `.env` like files
|
|
||||||
([#324](https://github.com/go-task/task/issues/324), [#356](https://github.com/go-task/task/pull/356)).
|
|
||||||
- Add `label:` to task so you can override the task name in the logs
|
|
||||||
([#321](https://github.com/go-task/task/issues/321]), [#337](https://github.com/go-task/task/pull/337)).
|
|
||||||
- Refactor how variables work on version 3
|
|
||||||
([#311](https://github.com/go-task/task/pull/311)).
|
|
||||||
- Disallow `expansions` on v3 since it has no effect.
|
|
||||||
- `Taskvars.yml` is not automatically included anymore.
|
|
||||||
- `Taskfile_{{OS}}.yml` is not automatically included anymore.
|
|
||||||
- Allow interpolation on `includes`, so you can manually include a Taskfile
|
|
||||||
based on operation system, for example.
|
|
||||||
- Expose `.TASK` variable in templates with the task name
|
|
||||||
([#252](https://github.com/go-task/task/issues/252)).
|
|
||||||
- Implement short task syntax
|
|
||||||
([#194](https://github.com/go-task/task/issues/194), [#240](https://github.com/go-task/task/pull/240)).
|
|
||||||
- Added option to make included Taskfile run commands on its own directory
|
|
||||||
([#260](https://github.com/go-task/task/issues/260), [#144](https://github.com/go-task/task/issues/144))
|
|
||||||
- Taskfiles in version 1 are not supported anymore
|
|
||||||
([#237](https://github.com/go-task/task/pull/237)).
|
|
||||||
- Added global `method:` option. With this option, you can set a default
|
|
||||||
method to all tasks in a Taskfile
|
|
||||||
([#246](https://github.com/go-task/task/issues/246)).
|
|
||||||
- Changed default method from `timestamp` to `checksum`
|
|
||||||
([#246](https://github.com/go-task/task/issues/246)).
|
|
||||||
- New magic variables are now available when using `status:`:
|
|
||||||
`.TIMESTAMP` which contains the greatest modification date
|
|
||||||
from the files listed in `sources:`, and `.CHECKSUM`, which
|
|
||||||
contains a checksum of all files listed in `status:`.
|
|
||||||
This is useful for manual checking when using external, or even remote,
|
|
||||||
artifacts when using `status:`
|
|
||||||
([#216](https://github.com/go-task/task/pull/216)).
|
|
||||||
- We're now using [slim-sprig](https://github.com/go-task/slim-sprig) instead of
|
|
||||||
[sprig](https://github.com/Masterminds/sprig), which allowed a file size
|
|
||||||
reduction of about 22%
|
|
||||||
([#219](https://github.com/go-task/task/pull/219)).
|
|
||||||
- We now use some colors on Task output to better distinguish message types -
|
|
||||||
commands are green, errors are red, etc
|
|
||||||
([#207](https://github.com/go-task/task/pull/207)).
|
|
||||||
|
|
||||||
## v2.8.1 - 2020-05-20
|
|
||||||
|
|
||||||
- Fix error code for the `--help` flag
|
|
||||||
([#300](https://github.com/go-task/task/issues/300), [#330](https://github.com/go-task/task/pull/330)).
|
|
||||||
- Print version to stdout instead of stderr
|
|
||||||
([#299](https://github.com/go-task/task/issues/299), [#329](https://github.com/go-task/task/pull/329)).
|
|
||||||
- Supress `context` errors when using the `--watch` flag
|
|
||||||
([#313](https://github.com/go-task/task/issues/313), [#317](https://github.com/go-task/task/pull/317)).
|
|
||||||
- Support templating on description
|
|
||||||
([#276](https://github.com/go-task/task/issues/276), [#283](https://github.com/go-task/task/pull/283)).
|
|
||||||
|
|
||||||
## v2.8.0 - 2019-12-07
|
|
||||||
|
|
||||||
- Add `--parallel` flag (alias `-p`) to run tasks given by the command line in
|
|
||||||
parallel
|
|
||||||
([#266](https://github.com/go-task/task/pull/266)).
|
|
||||||
- Fixed bug where calling the `task` CLI only informing global vars would not
|
|
||||||
execute the `default` task.
|
|
||||||
- Add hability to silent all tasks by adding `silent: true` a the root of the
|
|
||||||
Taskfile.
|
|
||||||
|
|
||||||
## v2.7.1 - 2019-11-10
|
|
||||||
|
|
||||||
- Fix error being raised when `exit 0` was called
|
|
||||||
([#251](https://github.com/go-task/task/issues/251)).
|
|
||||||
|
|
||||||
## v2.7.0 - 2019-09-22
|
|
||||||
|
|
||||||
- Fixed panic bug when assigning a global variable
|
|
||||||
([#229](https://github.com/go-task/task/issues/229), [#243](https://github.com/go-task/task/issues/234)).
|
|
||||||
- A task with `method: checksum` will now re-run if generated files are deleted
|
|
||||||
([#228](https://github.com/go-task/task/pull/228), [#238](https://github.com/go-task/task/issues/238)).
|
|
||||||
|
|
||||||
## v2.6.0 - 2019-07-21
|
|
||||||
|
|
||||||
- Fixed some bugs regarding minor version checks on `version:`.
|
|
||||||
- Add `preconditions:` to task
|
|
||||||
([#205](https://github.com/go-task/task/pull/205)).
|
|
||||||
- Create directory informed on `dir:` if it doesn't exist
|
|
||||||
([#209](https://github.com/go-task/task/issues/209), [#211](https://github.com/go-task/task/pull/211)).
|
|
||||||
- We now have a `--taskfile` flag (alias `-t`), which can be used to run
|
|
||||||
another Taskfile (other than the default `Taskfile.yml`)
|
|
||||||
([#221](https://github.com/go-task/task/pull/221)).
|
|
||||||
- It's now possible to install Task using Homebrew on Linux
|
|
||||||
([go-task/homebrew-tap#1](https://github.com/go-task/homebrew-tap/pull/1)).
|
|
||||||
|
|
||||||
## v2.5.2 - 2019-05-11
|
|
||||||
|
|
||||||
- Reverted YAML upgrade due issues with CRLF on Windows
|
|
||||||
([#201](https://github.com/go-task/task/issues/201), [go-yaml/yaml#450](https://github.com/go-yaml/yaml/issues/450)).
|
|
||||||
- Allow setting global variables through the CLI
|
|
||||||
([#192](https://github.com/go-task/task/issues/192)).
|
|
||||||
|
|
||||||
## 2.5.1 - 2019-04-27
|
|
||||||
|
|
||||||
- Fixed some issues with interactive command line tools, where sometimes
|
|
||||||
the output were not being shown, and similar issues
|
|
||||||
([#114](https://github.com/go-task/task/issues/114), [#190](https://github.com/go-task/task/issues/190), [#200](https://github.com/go-task/task/pull/200)).
|
|
||||||
- Upgraded [go-yaml/yaml](https://github.com/go-yaml/yaml) from v2 to v3.
|
|
||||||
|
|
||||||
## v2.5.0 - 2019-03-16
|
|
||||||
|
|
||||||
- We moved from the taskfile.org domain to the new fancy taskfile.dev domain.
|
|
||||||
While stuff is being redirected, we strongly recommend to everyone that use
|
|
||||||
[this install script](https://taskfile.dev/#/installation?id=install-script)
|
|
||||||
to use the new taskfile.dev domain on scripts from now on.
|
|
||||||
- Fixed to the ZSH completion
|
|
||||||
([#182](https://github.com/go-task/task/pull/182)).
|
|
||||||
- Add [`--summary` flag along with `summary:` task attribute](https://taskfile.org/#/usage?id=display-summary-of-task)
|
|
||||||
([#180](https://github.com/go-task/task/pull/180)).
|
|
||||||
|
|
||||||
## v2.4.0 - 2019-02-21
|
|
||||||
|
|
||||||
- Allow calling a task of the root Taskfile from an included Taskfile
|
|
||||||
by prefixing it with `:`
|
|
||||||
([#161](https://github.com/go-task/task/issues/161), [#172](https://github.com/go-task/task/issues/172)),
|
|
||||||
- Add flag to override the `output` option
|
|
||||||
([#173](https://github.com/go-task/task/pull/173));
|
|
||||||
- Fix bug where Task was persisting the new checksum on the disk when the Dry
|
|
||||||
Mode is enabled
|
|
||||||
([#166](https://github.com/go-task/task/issues/166));
|
|
||||||
- Fix file timestamp issue when the file name has spaces
|
|
||||||
([#176](https://github.com/go-task/task/issues/176));
|
|
||||||
- Mitigating path expanding issues on Windows
|
|
||||||
([#170](https://github.com/go-task/task/pull/170)).
|
|
||||||
|
|
||||||
## v2.3.0 - 2019-01-02
|
|
||||||
|
|
||||||
- On Windows, Task can now be installed using [Scoop](https://scoop.sh/)
|
|
||||||
([#152](https://github.com/go-task/task/pull/152));
|
|
||||||
- Fixed issue with file/directory globing
|
|
||||||
([#153](https://github.com/go-task/task/issues/153));
|
|
||||||
- Added ability to globally set environment variables
|
|
||||||
(
|
|
||||||
[#138](https://github.com/go-task/task/pull/138),
|
|
||||||
[#159](https://github.com/go-task/task/pull/159)
|
|
||||||
).
|
|
||||||
|
|
||||||
## v2.2.1 - 2018-12-09
|
|
||||||
|
|
||||||
- This repository now uses Go Modules (#143). We'll still keep the `vendor` directory in sync for some time, though;
|
|
||||||
- Fixing a bug when the Taskfile has no tasks but includes another Taskfile (#150);
|
|
||||||
- Fix a bug when calling another task or a dependency in an included Taskfile (#151).
|
|
||||||
|
|
||||||
## v2.2.0 - 2018-10-25
|
|
||||||
|
|
||||||
- Added support for [including other Taskfiles](https://taskfile.org/#/usage?id=including-other-taskfiles) (#98)
|
|
||||||
- This should be considered experimental. For now, only including local files is supported, but support for including remote Taskfiles is being discussed. If you have any feedback, please comment on #98.
|
|
||||||
- Task now have a dedicated documentation site: https://taskfile.org
|
|
||||||
- Thanks to [Docsify](https://docsify.js.org/) for making this pretty easy. To check the source code, just take a look at the [docs](https://github.com/go-task/task/tree/master/docs) directory of this repository. Contributions to the documentation is really appreciated.
|
|
||||||
|
|
||||||
## v2.1.1 - 2018-09-17
|
|
||||||
|
|
||||||
- Fix suggestion to use `task --init` not being shown anymore (when a `Taskfile.yml` is not found)
|
|
||||||
- Fix error when using checksum method and no file exists for a source glob (#131)
|
|
||||||
- Fix signal handling when the `--watch` flag is given (#132)
|
|
||||||
|
|
||||||
## v2.1.0 - 2018-08-19
|
|
||||||
|
|
||||||
- Add a `ignore_error` option to task and command (#123)
|
|
||||||
- Add a dry run mode (`--dry` flag) (#126)
|
|
||||||
|
|
||||||
## v2.0.3 - 2018-06-24
|
|
||||||
|
|
||||||
- Expand environment variables on "dir", "sources" and "generates" (#116)
|
|
||||||
- Fix YAML merging syntax (#112)
|
|
||||||
- Add ZSH completion (#111)
|
|
||||||
- Implement new `output` option. Please check out the [documentation](https://github.com/go-task/task#output-syntax)
|
|
||||||
|
|
||||||
## v2.0.2 - 2018-05-01
|
|
||||||
|
|
||||||
- Fix merging of YAML anchors (#112)
|
|
||||||
|
|
||||||
## v2.0.1 - 2018-03-11
|
|
||||||
|
|
||||||
- Fixes panic on `task --list`
|
|
||||||
|
|
||||||
## v2.0.0 - 2018-03-08
|
|
||||||
|
|
||||||
Version 2.0.0 is here, with a new Taskfile format.
|
|
||||||
|
|
||||||
Please, make sure to read the [Taskfile versions](https://github.com/go-task/task/blob/master/TASKFILE_VERSIONS.md) document, since it describes in depth what changed for this version.
|
|
||||||
|
|
||||||
* New Taskfile version 2 (https://github.com/go-task/task/issues/77)
|
|
||||||
* Possibility to have global variables in the `Taskfile.yml` instead of `Taskvars.yml` (https://github.com/go-task/task/issues/66)
|
|
||||||
* Small improvements and fixes
|
|
||||||
|
|
||||||
## v1.4.4 - 2017-11-19
|
|
||||||
|
|
||||||
- Handle SIGINT and SIGTERM (#75);
|
|
||||||
- List: print message with there's no task with description;
|
|
||||||
- Expand home dir ("~" symbol) on paths (#74);
|
|
||||||
- Add Snap as an installation method;
|
|
||||||
- Move examples to its own repo;
|
|
||||||
- Watch: also walk on tasks called on on "cmds", and not only on "deps";
|
|
||||||
- Print logs to stderr instead of stdout (#68);
|
|
||||||
- Remove deprecated `set` keyword;
|
|
||||||
- Add checksum based status check, alternative to timestamp based.
|
|
||||||
|
|
||||||
## v1.4.3 - 2017-09-07
|
|
||||||
|
|
||||||
- Allow assigning variables to tasks at run time via CLI (#33)
|
|
||||||
- Added suport for multiline variables from sh (#64)
|
|
||||||
- Fixes env: remove square braces and evaluate shell (#62)
|
|
||||||
- Watch: change watch library and few fixes and improvements
|
|
||||||
- When use watching, cancel and restart long running process on file change (#59 and #60)
|
|
||||||
|
|
||||||
## v1.4.2 - 2017-07-30
|
|
||||||
|
|
||||||
- Flag to set directory of execution
|
|
||||||
- Always echo command if is verbose mode
|
|
||||||
- Add silent mode to disable echoing of commands
|
|
||||||
- Fixes and improvements of variables (#56)
|
|
||||||
|
|
||||||
## v1.4.1 - 2017-07-15
|
|
||||||
|
|
||||||
- Allow use of YAML for dynamic variables instead of $ prefix
|
|
||||||
- `VAR: {sh: echo Hello}` instead of `VAR: $echo Hello`
|
|
||||||
- Add `--list` (or `-l`) flag to print existing tasks
|
|
||||||
- OS specific Taskvars file (e.g. `Taskvars_windows.yml`, `Taskvars_linux.yml`, etc)
|
|
||||||
- Consider task up-to-date on equal timestamps (#49)
|
|
||||||
- Allow absolute path in generates section (#48)
|
|
||||||
- Bugfix: allow templating when calling deps (#42)
|
|
||||||
- Fix panic for invalid task in cyclic dep detection
|
|
||||||
- Better error output for dynamic variables in Taskvars.yml (#41)
|
|
||||||
- Allow template evaluation in parameters
|
|
||||||
|
|
||||||
## v1.4.0 - 2017-07-06
|
|
||||||
|
|
||||||
- Cache dynamic variables
|
|
||||||
- Add verbose mode (`-v` flag)
|
|
||||||
- Support to task parameters (overriding vars) (#31) (#32)
|
|
||||||
- Print command, also when "set:" is specified (#35)
|
|
||||||
- Improve task command help text (#35)
|
|
||||||
|
|
||||||
## v1.3.1 - 2017-06-14
|
|
||||||
|
|
||||||
- Fix glob not working on commands (#28)
|
|
||||||
- Add ExeExt template function
|
|
||||||
- Add `--init` flag to create a new Taskfile
|
|
||||||
- Add status option to prevent task from running (#27)
|
|
||||||
- Allow interpolation on `generates` and `sources` attributes (#26)
|
|
||||||
|
|
||||||
## v1.3.0 - 2017-04-24
|
|
||||||
|
|
||||||
- Migrate from os/exec.Cmd to a native Go sh/bash interpreter
|
|
||||||
- This is a potentially breaking change if you use Windows.
|
|
||||||
- Now, `cmd` is not used anymore on Windows. Always use Bash-like syntax for your commands, even on Windows.
|
|
||||||
- Add "ToSlash" and "FromSlash" to template functions
|
|
||||||
- Use functions defined on github.com/Masterminds/sprig
|
|
||||||
- Do not redirect stdin while running variables commands
|
|
||||||
- Using `context` and `errgroup` packages (this will make other tasks to be cancelled, if one returned an error)
|
|
||||||
|
|
||||||
## v1.2.0 - 2017-04-02
|
|
||||||
|
|
||||||
- More tests and Travis integration
|
|
||||||
- Watch a task (experimental)
|
|
||||||
- Possibility to call another task
|
|
||||||
- Fix "=" not being reconized in variables/environment variables
|
|
||||||
- Tasks can now have a description, and help will print them (#10)
|
|
||||||
- Task dependencies now run concurrently
|
|
||||||
- Support for a default task (#16)
|
|
||||||
|
|
||||||
## v1.1.0 - 2017-03-08
|
|
||||||
|
|
||||||
- Support for YAML, TOML and JSON (#1)
|
|
||||||
- Support running command in another directory (#4)
|
|
||||||
- `--force` or `-f` flag to force execution of task even when it's up-to-date
|
|
||||||
- Detection of cyclic dependencies (#5)
|
|
||||||
- Support for variables (#6, #9, #14)
|
|
||||||
- Operation System specific commands and variables (#13)
|
|
||||||
|
|
||||||
## v1.0.0 - 2017-02-28
|
|
||||||
|
|
||||||
- Add LICENSE file
|
|
||||||
136
Gopkg.lock
generated
Normal file
136
Gopkg.lock
generated
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/Masterminds/semver"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "15d8430ab86497c5c0da827b748823945e1cf1e1"
|
||||||
|
version = "v1.4.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/Masterminds/sprig"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "9d9aa1f74c86fd9d36ecfe3f2a44a3093c3d4c15"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/aokoli/goutils"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "3391d3790d23d03408670993e957e8f408993c34"
|
||||||
|
version = "v1.0.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/davecgh/go-spew"
|
||||||
|
packages = ["spew"]
|
||||||
|
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
|
version = "v1.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/huandu/xstrings"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "2bf18b218c51864a87384c06996e40ff9dcff8e1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/imdario/mergo"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "0d4b488675fdec1dde48751b05ab530cf0b630e1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mattn/go-zglob"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"fastwalk"
|
||||||
|
]
|
||||||
|
revision = "4959821b481786922ac53e7ef25c61ae19fb7c36"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mitchellh/go-homedir"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pmezard/go-difflib"
|
||||||
|
packages = ["difflib"]
|
||||||
|
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/radovskyb/watcher"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "6145e1439b9de93806925353403f91d2abbad8a5"
|
||||||
|
version = "v1.0.2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/satori/go.uuid"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
|
||||||
|
version = "v1.2.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/spf13/pflag"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "ee5fd03fd6acfd43e44aea0b4135958546ed8e73"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/stretchr/testify"
|
||||||
|
packages = ["assert"]
|
||||||
|
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||||
|
version = "v1.2.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
packages = [
|
||||||
|
"pbkdf2",
|
||||||
|
"scrypt",
|
||||||
|
"ssh/terminal"
|
||||||
|
]
|
||||||
|
revision = "91a49db82a88618983a78a06c1cbd4e00ab749ab"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/net"
|
||||||
|
packages = ["context"]
|
||||||
|
revision = "22ae77b79946ea320088417e4d50825671d82d57"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/sync"
|
||||||
|
packages = ["errgroup"]
|
||||||
|
revision = "fd80eb99c8f653c847d294a001bdf2a3a6f768f5"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/sys"
|
||||||
|
packages = [
|
||||||
|
"unix",
|
||||||
|
"windows"
|
||||||
|
]
|
||||||
|
revision = "dd2ff4accc098aceecb86b36eaa7829b2a17b1c9"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "gopkg.in/yaml.v2"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5"
|
||||||
|
version = "v2.1.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "mvdan.cc/sh"
|
||||||
|
packages = [
|
||||||
|
"interp",
|
||||||
|
"syntax"
|
||||||
|
]
|
||||||
|
revision = "fb0bad77f8fa7a57e6f249f53074ec52b21558d1"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "976972e7291789a9d4904805cc7f49d733476868bf80f120309078b02a095a65"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
||||||
44
Gopkg.toml
Normal file
44
Gopkg.toml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[prune]
|
||||||
|
unused-packages = true
|
||||||
|
non-go = true
|
||||||
|
go-tests = true
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/Masterminds/sprig"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/imdario/mergo"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mattn/go-zglob"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "mvdan.cc/sh"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/spf13/pflag"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/sync"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "v2"
|
||||||
|
name = "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/radovskyb/watcher"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mitchellh/go-homedir"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/Masterminds/semver"
|
||||||
674
README.md
674
README.md
@@ -1,15 +1,665 @@
|
|||||||
<div align="center">
|
[](https://gophers.slack.com/messages/task)
|
||||||
<a href="https://taskfile.dev">
|
[](https://travis-ci.org/go-task/task)
|
||||||
<img src="docs/Logo.png" width="200px" height="200px" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<h1>Task</h1>
|
# Task - A task runner / simpler Make alternative written in Go
|
||||||
|
|
||||||
<p>
|
> We recently released version 2.0.0 of Task. The Taskfile changed a bit.
|
||||||
Task is a task runner / build tool that aims to be simpler and easier to use than, for example, <a href="https://www.gnu.org/software/make/">GNU Make<a>.
|
Please, check the [Taskfile versions](TASKFILE_VERSIONS.md) document to see
|
||||||
</p>
|
what changed and how to upgrade.
|
||||||
|
|
||||||
<p>
|
Task is a simple tool that allows you to easily run development and build
|
||||||
See <a href="https://taskfile.dev">taskfile.dev</a> for the documentation.
|
tasks. Task is written in Golang, but can be used to develop any language.
|
||||||
</p>
|
It aims to be simpler and easier to use then [GNU Make][make].
|
||||||
</div>
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Go](#go)
|
||||||
|
- [Homebrew](#homebrew)
|
||||||
|
- [Snap](#snap)
|
||||||
|
- [Binary](#binary)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Environment](#environment)
|
||||||
|
- [OS specific task](#os-specific-task)
|
||||||
|
- [Task directory](#task-directory)
|
||||||
|
- [Task dependencies](#task-dependencies)
|
||||||
|
- [Calling another task](#calling-another-task)
|
||||||
|
- [Prevent unnecessary work](#prevent-unnecessary-work)
|
||||||
|
- [Variables](#variables)
|
||||||
|
- [Dynamic variables](#dynamic-variables)
|
||||||
|
- [Go's template engine](#gos-template-engine)
|
||||||
|
- [Help](#help)
|
||||||
|
- [Silent mode](#silent-mode)
|
||||||
|
- [Watch tasks](#watch-tasks-experimental)
|
||||||
|
- [Examples](#examples)
|
||||||
|
- [Alternative task runners](#alternative-task-runners)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
If you have a [Golang][golang] environment setup, you can simply run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get -u -v github.com/go-task/task/cmd/task
|
||||||
|
```
|
||||||
|
|
||||||
|
### Homebrew
|
||||||
|
|
||||||
|
If you're on macOS and have [Homebrew][homebrew] installed, getting Task is
|
||||||
|
as simple as running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew update
|
||||||
|
brew tap go-task/tap
|
||||||
|
brew install go-task
|
||||||
|
```
|
||||||
|
|
||||||
|
### Snap
|
||||||
|
|
||||||
|
Task is available for [Snapcraft][snapcraft], but keep in mind that your
|
||||||
|
Linux distribution should allow classic confinement for Snaps to Task work
|
||||||
|
right:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo snap install task
|
||||||
|
```
|
||||||
|
|
||||||
|
### Binary
|
||||||
|
|
||||||
|
Or you can download the binary from the [releases][releases] page and add to
|
||||||
|
your `PATH`. DEB and RPM packages are also available.
|
||||||
|
The `task_checksums.txt` file contains the sha256 checksum for each file.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Create a file called `Taskfile.yml` in the root of the project.
|
||||||
|
The `cmds` attribute should contains the commands of a task.
|
||||||
|
The example below allows compile a Go app and uses [Minify][minify] to concat
|
||||||
|
and minify multiple CSS files into a single one.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- go build -v -i main.go
|
||||||
|
|
||||||
|
assets:
|
||||||
|
cmds:
|
||||||
|
- minify -o public/style.css src/css
|
||||||
|
```
|
||||||
|
|
||||||
|
Running the tasks is as simple as running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task assets build
|
||||||
|
```
|
||||||
|
|
||||||
|
Task uses [github.com/mvdan/sh](https://github.com/mvdan/sh), a native Go sh
|
||||||
|
interpreter. So you can write sh/bash commands and it will work even on
|
||||||
|
Windows, where `sh` or `bash` is usually not available. Just remember any
|
||||||
|
executable called must be available by the OS or in PATH.
|
||||||
|
|
||||||
|
If you ommit a task name, "default" will be assumed.
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
|
||||||
|
You can specify environment variables that are added when running a command:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- echo $hallo
|
||||||
|
env:
|
||||||
|
hallo: welt
|
||||||
|
```
|
||||||
|
|
||||||
|
### OS specific task
|
||||||
|
|
||||||
|
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your taskfile
|
||||||
|
based on the operating system.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Taskfile.yml:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- echo "default"
|
||||||
|
```
|
||||||
|
|
||||||
|
Taskfile_linux.yml:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- echo "linux"
|
||||||
|
```
|
||||||
|
|
||||||
|
Will print out `linux` and not default.
|
||||||
|
|
||||||
|
It's also possible to have OS specific `Taskvars.yml` file, like
|
||||||
|
`Taskvars_windows.yml`, `Taskfile_linux.yml` or `Taskvars_darwin.yml`. See the
|
||||||
|
[variables section](#variables) below.
|
||||||
|
|
||||||
|
### Task directory
|
||||||
|
|
||||||
|
By default, tasks will be executed in the directory where the Taskfile is
|
||||||
|
located. But you can easily make the task run in another folder informing
|
||||||
|
`dir`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
serve:
|
||||||
|
dir: public/www
|
||||||
|
cmds:
|
||||||
|
# run http server
|
||||||
|
- caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task dependencies
|
||||||
|
|
||||||
|
You may have tasks that depends on others. Just pointing them on `deps` will
|
||||||
|
make them run automatically before running the parent task:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
deps: [assets]
|
||||||
|
cmds:
|
||||||
|
- go build -v -i main.go
|
||||||
|
|
||||||
|
assets:
|
||||||
|
cmds:
|
||||||
|
- minify -o public/style.css src/css
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, `assets` will always run right before `build` if you run
|
||||||
|
`task build`.
|
||||||
|
|
||||||
|
A task can have only dependencies and no commands to group tasks together:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
assets:
|
||||||
|
deps: [js, css]
|
||||||
|
|
||||||
|
js:
|
||||||
|
cmds:
|
||||||
|
- minify -o public/script.js src/js
|
||||||
|
|
||||||
|
css:
|
||||||
|
cmds:
|
||||||
|
- minify -o public/style.css src/css
|
||||||
|
```
|
||||||
|
|
||||||
|
If there are more than one dependency, they always run in parallel for better
|
||||||
|
performance.
|
||||||
|
|
||||||
|
If you want to pass information to dependencies, you can do that the same
|
||||||
|
manner as you would to [call another task](#calling-another-task):
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
deps:
|
||||||
|
- task: echo_sth
|
||||||
|
vars: {TEXT: "before 1"}
|
||||||
|
- task: echo_sth
|
||||||
|
vars: {TEXT: "before 2"}
|
||||||
|
cmds:
|
||||||
|
- echo "after"
|
||||||
|
|
||||||
|
echo_sth:
|
||||||
|
cmds:
|
||||||
|
- echo {{.TEXT}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Calling another task
|
||||||
|
|
||||||
|
When a task has many dependencies, they are executed concurrently. This will
|
||||||
|
often result in a faster build pipeline. But in some situations you may need
|
||||||
|
to call other tasks serially. In this case, just use the following syntax:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
main-task:
|
||||||
|
cmds:
|
||||||
|
- task: task-to-be-called
|
||||||
|
- task: another-task
|
||||||
|
- echo "Both done"
|
||||||
|
|
||||||
|
task-to-be-called:
|
||||||
|
cmds:
|
||||||
|
- echo "Task to be called"
|
||||||
|
|
||||||
|
another-task:
|
||||||
|
cmds:
|
||||||
|
- echo "Another task"
|
||||||
|
```
|
||||||
|
|
||||||
|
Overriding variables in the called task is as simple as informing `vars`
|
||||||
|
attribute:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
main-task:
|
||||||
|
cmds:
|
||||||
|
- task: write-file
|
||||||
|
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
|
||||||
|
- task: write-file
|
||||||
|
vars: {FILE: "world.txt", CONTENT: "World!"}
|
||||||
|
|
||||||
|
write-file:
|
||||||
|
cmds:
|
||||||
|
- echo "{{.CONTENT}}" > {{.FILE}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above syntax is also supported in `deps`.
|
||||||
|
|
||||||
|
### Prevent unnecessary work
|
||||||
|
|
||||||
|
If a task generates something, you can inform Task the source and generated
|
||||||
|
files, so Task will prevent to run them if not necessary.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
deps: [js, css]
|
||||||
|
cmds:
|
||||||
|
- go build -v -i main.go
|
||||||
|
|
||||||
|
js:
|
||||||
|
cmds:
|
||||||
|
- minify -o public/script.js src/js
|
||||||
|
sources:
|
||||||
|
- src/js/**/*.js
|
||||||
|
generates:
|
||||||
|
- public/script.js
|
||||||
|
|
||||||
|
css:
|
||||||
|
cmds:
|
||||||
|
- minify -o public/style.css src/css
|
||||||
|
sources:
|
||||||
|
- src/css/**/*.css
|
||||||
|
generates:
|
||||||
|
- public/style.css
|
||||||
|
```
|
||||||
|
|
||||||
|
`sources` and `generates` can be files or file patterns. When both are given,
|
||||||
|
Task will compare the modification date/time of the files to determine if it's
|
||||||
|
necessary to run the task. If not, it will just print a message like
|
||||||
|
`Task "js" is up to date`.
|
||||||
|
|
||||||
|
If you prefer this check to be made by the content of the files, instead of
|
||||||
|
its timestamp, just set the `method` property to `checksum`.
|
||||||
|
You will probably want to ignore the `.task` folder in your `.gitignore` file
|
||||||
|
(It's there that Task stores the last checksum).
|
||||||
|
This feature is still experimental and can change until it's stable.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- go build .
|
||||||
|
sources:
|
||||||
|
- ./*.go
|
||||||
|
generates:
|
||||||
|
- app{{exeExt}}
|
||||||
|
method: checksum
|
||||||
|
```
|
||||||
|
|
||||||
|
> TIP: method `none` skips any validation and always run the task.
|
||||||
|
|
||||||
|
Alternatively, you can inform a sequence of tests as `status`. If no error
|
||||||
|
is returned (exit status 0), the task is considered up-to-date:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
generate-files:
|
||||||
|
cmds:
|
||||||
|
- mkdir directory
|
||||||
|
- touch directory/file1.txt
|
||||||
|
- touch directory/file2.txt
|
||||||
|
# test existence of files
|
||||||
|
status:
|
||||||
|
- test -d directory
|
||||||
|
- test -f directory/file1.txt
|
||||||
|
- test -f directory/file2.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use `--force` or `-f` if you want to force a task to run even when
|
||||||
|
up-to-date.
|
||||||
|
|
||||||
|
Also, `task --status [tasks]...` will exit with non-zero exit code if any of
|
||||||
|
the tasks is not up-to-date.
|
||||||
|
|
||||||
|
### Variables
|
||||||
|
|
||||||
|
When doing interpolation of variables, Task will look for the below.
|
||||||
|
They are listed below in order of importance (e.g. most important first):
|
||||||
|
|
||||||
|
- Variables declared locally in the task
|
||||||
|
- Variables given while calling a task from another.
|
||||||
|
(See [Calling another task](#calling-another-task) above)
|
||||||
|
- Variables declared in the `vars:` option in the `Taskfile`
|
||||||
|
- Variables available in the `Taskvars.yml` file
|
||||||
|
- Environment variables
|
||||||
|
|
||||||
|
Example of sending parameters with environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ TASK_VARIABLE=a-value task do-something
|
||||||
|
```
|
||||||
|
|
||||||
|
Since some shells don't support above syntax to set environment variables
|
||||||
|
(Windows) tasks also accepts a similar style when not in the beginning of
|
||||||
|
the command. Variables given in this form are only visible to the task called
|
||||||
|
right before.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of locally declared vars:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
print-var:
|
||||||
|
cmds:
|
||||||
|
echo "{{.VAR}}"
|
||||||
|
vars:
|
||||||
|
VAR: Hello!
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of global vars in a `Taskfile.yml`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
GREETING: Hello from Taskfile!
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
greet:
|
||||||
|
cmds:
|
||||||
|
- echo "{{.GREETING}}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of `Taskvars.yml` file:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
PROJECT_NAME: My Project
|
||||||
|
DEV_MODE: production
|
||||||
|
GIT_COMMIT: {sh: git log -n 1 --format=%h}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Variables expansion
|
||||||
|
|
||||||
|
Variables are expanded 2 times by default. You can change that by setting the
|
||||||
|
`expansions:` option. Change that will be necessary if you compose many
|
||||||
|
variables together:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
expansions: 3
|
||||||
|
|
||||||
|
vars:
|
||||||
|
FOO: foo
|
||||||
|
BAR: bar
|
||||||
|
BAZ: baz
|
||||||
|
FOOBAR: "{{.FOO}}{{.BAR}}"
|
||||||
|
FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo "{{.FOOBARBAZ}}"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Dynamic variables
|
||||||
|
|
||||||
|
The below syntax (`sh:` prop in a variable) is considered a dynamic variable.
|
||||||
|
The value will be treated as a command and the output assigned. If there is one
|
||||||
|
or more trailing newlines, the last newline will be trimmed.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go
|
||||||
|
vars:
|
||||||
|
GIT_COMMIT:
|
||||||
|
sh: git log -n 1 --format=%h
|
||||||
|
```
|
||||||
|
|
||||||
|
This works for all types of variables.
|
||||||
|
|
||||||
|
### Go's template engine
|
||||||
|
|
||||||
|
Task parse commands as [Go's template engine][gotemplate] before executing
|
||||||
|
them. Variables are accessible through dot syntax (`.VARNAME`).
|
||||||
|
|
||||||
|
All functions by the Go's [sprig lib](http://masterminds.github.io/sprig/)
|
||||||
|
are available. The following example gets the current date in a given format:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
print-date:
|
||||||
|
cmds:
|
||||||
|
- echo {{now | date "2006-01-02"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Task also adds the following functions:
|
||||||
|
|
||||||
|
- `OS`: Returns operating system. Possible values are "windows", "linux",
|
||||||
|
"darwin" (macOS) and "freebsd".
|
||||||
|
- `ARCH`: return the architecture Task was compiled to: "386", "amd64", "arm"
|
||||||
|
or "s390x".
|
||||||
|
- `splitLines`: Splits Unix (\n) and Windows (\r\n) styled newlines.
|
||||||
|
- `catLines`: Replaces Unix (\n) and Windows (\r\n) styled newlines with a space.
|
||||||
|
- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\`
|
||||||
|
path format to `/`.
|
||||||
|
- `fromSlash`: Oposite of `toSlash`. Does nothing on Unix, but on Windows
|
||||||
|
converts a string from `\` path format to `/`.
|
||||||
|
- `exeExt`: Returns the right executable extension for the current OS
|
||||||
|
(`".exe"` for Windows, `""` for others).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
print-os:
|
||||||
|
cmds:
|
||||||
|
- echo '{{OS}} {{ARCH}}'
|
||||||
|
- echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}'
|
||||||
|
# This will be path/to/file on Unix but path\to\file on Windows
|
||||||
|
- echo '{{fromSlash "path/to/file"}}'
|
||||||
|
enumerated-file:
|
||||||
|
vars:
|
||||||
|
CONTENT: |
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
cat << EOF > output.txt
|
||||||
|
{{range $i, $line := .CONTENT | splitLines -}}
|
||||||
|
{{printf "%3d" $i}}: {{$line}}
|
||||||
|
{{end}}EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### Help
|
||||||
|
|
||||||
|
Running `task --list` (or `task -l`) lists all tasks with a description.
|
||||||
|
The following taskfile:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
desc: Build the go binary.
|
||||||
|
cmds:
|
||||||
|
- go build -v -i main.go
|
||||||
|
|
||||||
|
test:
|
||||||
|
desc: Run all the go tests.
|
||||||
|
cmds:
|
||||||
|
- go test -race ./...
|
||||||
|
|
||||||
|
js:
|
||||||
|
cmds:
|
||||||
|
- minify -o public/script.js src/js
|
||||||
|
|
||||||
|
css:
|
||||||
|
cmds:
|
||||||
|
- minify -o public/style.css src/css
|
||||||
|
```
|
||||||
|
|
||||||
|
would print the following output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
* build: Build the go binary.
|
||||||
|
* test: Run all the go tests.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Silent mode
|
||||||
|
|
||||||
|
Silent mode disables echoing of commands before Task runs it.
|
||||||
|
For the following Taskfile:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
echo:
|
||||||
|
cmds:
|
||||||
|
- echo "Print something"
|
||||||
|
```
|
||||||
|
|
||||||
|
Normally this will be print:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo "Print something"
|
||||||
|
Print something
|
||||||
|
```
|
||||||
|
|
||||||
|
With silent mode on, the below will be print instead:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Print something
|
||||||
|
```
|
||||||
|
|
||||||
|
There's three ways to enable silent mode:
|
||||||
|
|
||||||
|
* At command level:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
echo:
|
||||||
|
cmds:
|
||||||
|
- cmd: echo "Print something"
|
||||||
|
silent: true
|
||||||
|
```
|
||||||
|
|
||||||
|
* At task level:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
echo:
|
||||||
|
cmds:
|
||||||
|
- echo "Print something"
|
||||||
|
silent: true
|
||||||
|
```
|
||||||
|
|
||||||
|
* Or globally with `--silent` or `-s` flag
|
||||||
|
|
||||||
|
If you want to suppress stdout instead, just redirect a command to `/dev/null`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
echo:
|
||||||
|
cmds:
|
||||||
|
- echo "This will print nothing" > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Watch tasks (experimental)
|
||||||
|
|
||||||
|
If you give a `--watch` or `-w` argument, task will watch for files changes
|
||||||
|
and run the task again. This requires the `sources` attribute to be given,
|
||||||
|
so task know which files to watch.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
The [go-task/examples][examples] intends to be a collection of Taskfiles for
|
||||||
|
various use cases.
|
||||||
|
(It still lacks many examples, though. Contributions are welcome).
|
||||||
|
|
||||||
|
## Alternative task runners
|
||||||
|
|
||||||
|
- YAML based:
|
||||||
|
- [goeuro/myke][myke]
|
||||||
|
- [dreadl0ck/zeus][zeus]
|
||||||
|
- [rliebz/tusk][tusk]
|
||||||
|
- Go based:
|
||||||
|
- [markbates/grift][grift]
|
||||||
|
- [magefile/mage][mage]
|
||||||
|
- Make based:
|
||||||
|
- [tj/mmake][mmake]
|
||||||
|
|
||||||
|
[make]: https://www.gnu.org/software/make/
|
||||||
|
[releases]: https://github.com/go-task/task/releases
|
||||||
|
[golang]: https://golang.org/
|
||||||
|
[gotemplate]: https://golang.org/pkg/text/template/
|
||||||
|
[myke]: https://github.com/goeuro/myke
|
||||||
|
[zeus]: https://github.com/dreadl0ck/zeus
|
||||||
|
[tusk]: https://github.com/rliebz/tusk
|
||||||
|
[grift]: https://github.com/markbates/grift
|
||||||
|
[mage]: https://github.com/magefile/mage
|
||||||
|
[mmake]: https://github.com/tj/mmake
|
||||||
|
[sh]: https://github.com/mvdan/sh
|
||||||
|
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify
|
||||||
|
[examples]: https://github.com/go-task/examples
|
||||||
|
[snapcraft]: https://snapcraft.io/
|
||||||
|
[homebrew]: https://brew.sh/
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Releasing Task
|
# Releasing Task
|
||||||
|
|
||||||
The release process of Task is done with the help of
|
The release process of Task is done is done with the help of
|
||||||
[GoReleaser][goreleaser]. You can test the release process locally by calling
|
[GoReleaser][goreleaser]. You can test the release process locally by calling
|
||||||
the `test-release` task of the Taskfile.
|
the `test-release` task of the Taskfile.
|
||||||
|
|
||||||
[GitHub Actions](https://github.com/go-task/task/actions) should release
|
The Travis CI should release automatically when a new
|
||||||
artifacts automatically when a new Git tag is pushed to master
|
Git tag is pushed to master, either for the artifact uploading (raw executables
|
||||||
(raw executables and DEB and RPM packages).
|
and DEB and RPM packages)
|
||||||
|
|
||||||
# Homebrew
|
# Homebrew
|
||||||
|
|
||||||
@@ -21,15 +21,8 @@ The exception is the publishing of a new version of the
|
|||||||
the binaries:
|
the binaries:
|
||||||
|
|
||||||
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
|
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
|
||||||
* Moving both `i386` and `amd64` new artifacts to the stable channel on
|
* Moving either the `i386` and `amd64` new artifacts to the stable channel on
|
||||||
the [Snapcraft dashboard][snapcraftdashboard]
|
the [Snapscraft dashboard][snapcraftdashboard]
|
||||||
|
|
||||||
# Scoop
|
|
||||||
|
|
||||||
Scoop is a community owned installation method. Scoop owners usually take care
|
|
||||||
of updating versions there by editing
|
|
||||||
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
|
||||||
If you think its Task version is outdated, open an issue to let us know.
|
|
||||||
|
|
||||||
[goreleaser]: https://goreleaser.com/#continuous_integration
|
[goreleaser]: https://goreleaser.com/#continuous_integration
|
||||||
[homebrewtap]: https://github.com/go-task/homebrew-tap
|
[homebrewtap]: https://github.com/go-task/homebrew-tap
|
||||||
91
TASKFILE_VERSIONS.md
Normal file
91
TASKFILE_VERSIONS.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Taskfile version
|
||||||
|
|
||||||
|
The Taskfile syntax and features changed with time. This document explains what
|
||||||
|
changed on each version and how to upgrade your Taskfile.
|
||||||
|
|
||||||
|
# What the Taskfile version mean
|
||||||
|
|
||||||
|
The Taskfile version follows the Task version. E.g. the change to Taskfile
|
||||||
|
version `2` means that Task `v2.0.0` should be release to support it.
|
||||||
|
|
||||||
|
The `version:` key on Taskfile accepts a semver string, so either `2`, `2.0` or
|
||||||
|
`2.0.0` is accepted. You you choose to use `2.0` Task will not enable future
|
||||||
|
`2.1` features, but if you choose to use `2`, than any `2.x.x` features will be
|
||||||
|
available, but not `3.0.0+`.
|
||||||
|
|
||||||
|
## Version 1
|
||||||
|
|
||||||
|
In the first version of the `Taskfile`, the `version:` key was not available,
|
||||||
|
because the tasks was in the root of the YAML document. Like this:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
echo:
|
||||||
|
cmds:
|
||||||
|
- echo "Hello, World!"
|
||||||
|
```
|
||||||
|
|
||||||
|
The variable priority order was also different:
|
||||||
|
|
||||||
|
1. Call variables
|
||||||
|
2. Environment
|
||||||
|
3. Task variables
|
||||||
|
4. `Taskvars.yml` variables
|
||||||
|
|
||||||
|
## Version 2.0
|
||||||
|
|
||||||
|
At version 2, we introduced the `version:` key, to allow us to envolve Task
|
||||||
|
with new features without breaking existing Taskfiles. The new syntax is as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
echo:
|
||||||
|
cmds:
|
||||||
|
- echo "Hello, World!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Version 2 allows you to write global variables directly in the Taskfile,
|
||||||
|
if you don't want to create a `Taskvars.yml`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
GREETING: Hello, World!
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
greet:
|
||||||
|
cmds:
|
||||||
|
- echo "{{.GREETING}}"
|
||||||
|
```
|
||||||
|
|
||||||
|
The variable priority order changed to the following:
|
||||||
|
|
||||||
|
1. Task variables
|
||||||
|
2. Call variables
|
||||||
|
3. Taskfile variables
|
||||||
|
4. Taskvars file variables
|
||||||
|
5. Environment variables
|
||||||
|
|
||||||
|
A new global option was added to configure the number of variables expansions
|
||||||
|
(which default to 2):
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
expansions: 3
|
||||||
|
|
||||||
|
vars:
|
||||||
|
FOO: foo
|
||||||
|
BAR: bar
|
||||||
|
BAZ: baz
|
||||||
|
FOOBAR: "{{.FOO}}{{.BAR}}"
|
||||||
|
FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo "{{.FOOBARBAZ}}"
|
||||||
|
```
|
||||||
75
Taskfile.yml
75
Taskfile.yml
@@ -1,46 +1,33 @@
|
|||||||
version: '3'
|
version: '2'
|
||||||
|
|
||||||
includes:
|
|
||||||
docs:
|
|
||||||
taskfile: ./docs
|
|
||||||
dir: ./docs
|
|
||||||
|
|
||||||
vars:
|
|
||||||
GIT_COMMIT:
|
|
||||||
sh: git log -n 1 --format=%h
|
|
||||||
|
|
||||||
GO_PACKAGES:
|
|
||||||
sh: go list ./...
|
|
||||||
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: '0'
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
default:
|
# compiles current source code and make "task" executable available on
|
||||||
cmds:
|
# $GOPATH/bin/task{.exe}
|
||||||
- task: test
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
desc: Installs Task
|
desc: Installs Task
|
||||||
cmds:
|
cmds:
|
||||||
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
|
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
|
||||||
|
|
||||||
mod:
|
dl-deps:
|
||||||
desc: Downloads and tidy Go modules
|
desc: Downloads cli dependencies
|
||||||
cmds:
|
|
||||||
- go mod download
|
|
||||||
- go mod tidy
|
|
||||||
|
|
||||||
cli-deps:
|
|
||||||
desc: Downloads CLI dependencies
|
|
||||||
cmds:
|
cmds:
|
||||||
- task: go-get
|
- task: go-get
|
||||||
vars: {REPO: golang.org/x/lint/golint}
|
vars: {REPO: github.com/golang/lint/golint}
|
||||||
|
- task: go-get
|
||||||
|
vars: {REPO: github.com/asticode/go-astitodo/astitodo}
|
||||||
|
- task: go-get
|
||||||
|
vars: {REPO: github.com/golang/dep/cmd/dep}
|
||||||
- task: go-get
|
- task: go-get
|
||||||
vars: {REPO: github.com/goreleaser/goreleaser}
|
vars: {REPO: github.com/goreleaser/goreleaser}
|
||||||
- task: go-get
|
- task: go-get
|
||||||
vars: {REPO: github.com/goreleaser/godownloader}
|
vars: {REPO: github.com/goreleaser/godownloader}
|
||||||
|
|
||||||
|
update-deps:
|
||||||
|
desc: Updates dependencies
|
||||||
|
cmds:
|
||||||
|
- dep ensure
|
||||||
|
- dep ensure -update
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
desc: Cleans temp files and folders
|
desc: Cleans temp files and folders
|
||||||
cmds:
|
cmds:
|
||||||
@@ -49,35 +36,33 @@ tasks:
|
|||||||
lint:
|
lint:
|
||||||
desc: Runs golint
|
desc: Runs golint
|
||||||
cmds:
|
cmds:
|
||||||
- golint {{catLines .GO_PACKAGES}}
|
- golint {{.GO_PACKAGES}}
|
||||||
silent: true
|
silent: true
|
||||||
|
|
||||||
test:
|
test:
|
||||||
desc: Runs test suite
|
desc: Runs test suite
|
||||||
deps: [install]
|
deps: [install]
|
||||||
cmds:
|
cmds:
|
||||||
- go test {{catLines .GO_PACKAGES}}
|
- go test {{.GO_PACKAGES}}
|
||||||
|
|
||||||
test-release:
|
test-release:
|
||||||
desc: Tests release process without publishing
|
desc: Tests release process without publishing
|
||||||
cmds:
|
cmds:
|
||||||
- goreleaser --snapshot --rm-dist
|
- goreleaser --snapshot --rm-dist
|
||||||
|
|
||||||
gen-install-script:
|
todo:
|
||||||
desc: Generate install script using https://github.com/goreleaser/godownloader
|
desc: Prints TODO comments present in the code
|
||||||
cmds:
|
cmds:
|
||||||
- godownloader --repo go-task/task -o install-task.sh
|
- astitodo {{.GO_PACKAGES}}
|
||||||
- cp ./install-task.sh ./docs/install.sh
|
silent: true
|
||||||
|
|
||||||
ci:
|
ci:
|
||||||
- task: go-get
|
|
||||||
vars: {REPO: golang.org/x/lint/golint}
|
|
||||||
- task: lint
|
|
||||||
- task: test
|
|
||||||
|
|
||||||
go-get: go get -u {{.REPO}}
|
|
||||||
|
|
||||||
packages:
|
|
||||||
cmds:
|
cmds:
|
||||||
- echo '{{.GO_PACKAGES}}'
|
- task: go-get
|
||||||
silent: true
|
vars: {REPO: github.com/golang/lint/golint}
|
||||||
|
- task: lint
|
||||||
|
- task: test
|
||||||
|
|
||||||
|
go-get:
|
||||||
|
cmds:
|
||||||
|
- go get -u {{.REPO}}
|
||||||
|
|||||||
15
Taskvars.yml
Normal file
15
Taskvars.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
GIT_COMMIT:
|
||||||
|
sh: git log -n 1 --format=%h
|
||||||
|
|
||||||
|
GO_PACKAGES:
|
||||||
|
.
|
||||||
|
./cmd/task
|
||||||
|
./internal/args
|
||||||
|
./internal/compiler
|
||||||
|
./internal/compiler/v1
|
||||||
|
./internal/compiler/v2
|
||||||
|
./internal/execext
|
||||||
|
./internal/logger
|
||||||
|
./internal/status
|
||||||
|
./internal/taskfile
|
||||||
|
./internal/templater
|
||||||
64
args/args.go
64
args/args.go
@@ -1,64 +0,0 @@
|
|||||||
package args
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseV3 parses command line argument: tasks and global variables
|
|
||||||
func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
|
||||||
var calls []taskfile.Call
|
|
||||||
var globals = &taskfile.Vars{}
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
if !strings.Contains(arg, "=") {
|
|
||||||
calls = append(calls, taskfile.Call{Task: arg})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
name, value := splitVar(arg)
|
|
||||||
globals.Set(name, taskfile.Var{Static: value})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(calls) == 0 {
|
|
||||||
calls = append(calls, taskfile.Call{Task: "default"})
|
|
||||||
}
|
|
||||||
|
|
||||||
return calls, globals
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseV2 parses command line argument: tasks and vars of each task
|
|
||||||
func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
|
||||||
var calls []taskfile.Call
|
|
||||||
var globals = &taskfile.Vars{}
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
if !strings.Contains(arg, "=") {
|
|
||||||
calls = append(calls, taskfile.Call{Task: arg})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(calls) < 1 {
|
|
||||||
name, value := splitVar(arg)
|
|
||||||
globals.Set(name, taskfile.Var{Static: value})
|
|
||||||
} else {
|
|
||||||
if calls[len(calls)-1].Vars == nil {
|
|
||||||
calls[len(calls)-1].Vars = &taskfile.Vars{}
|
|
||||||
}
|
|
||||||
name, value := splitVar(arg)
|
|
||||||
calls[len(calls)-1].Vars.Set(name, taskfile.Var{Static: value})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(calls) == 0 {
|
|
||||||
calls = append(calls, taskfile.Call{Task: "default"})
|
|
||||||
}
|
|
||||||
|
|
||||||
return calls, globals
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitVar(s string) (string, string) {
|
|
||||||
pair := strings.SplitN(s, "=", 2)
|
|
||||||
return pair[0], pair[1]
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
package args_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/args"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArgsV3(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
Args []string
|
|
||||||
ExpectedCalls []taskfile.Call
|
|
||||||
ExpectedGlobals *taskfile.Vars
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "task-b", "task-c"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "task-a"},
|
|
||||||
{Task: "task-b"},
|
|
||||||
{Task: "task-c"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "task-a"},
|
|
||||||
{Task: "task-b"},
|
|
||||||
{Task: "task-c"},
|
|
||||||
},
|
|
||||||
ExpectedGlobals: &taskfile.Vars{
|
|
||||||
Keys: []string{"FOO", "BAR", "BAZ"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"FOO": taskfile.Var{Static: "bar"},
|
|
||||||
"BAR": taskfile.Var{Static: "baz"},
|
|
||||||
"BAZ": taskfile.Var{Static: "foo"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "task-a"},
|
|
||||||
},
|
|
||||||
ExpectedGlobals: &taskfile.Vars{
|
|
||||||
Keys: []string{"CONTENT"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"CONTENT": taskfile.Var{Static: "with some spaces"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"FOO=bar", "task-a", "task-b"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "task-a"},
|
|
||||||
{Task: "task-b"},
|
|
||||||
},
|
|
||||||
ExpectedGlobals: &taskfile.Vars{
|
|
||||||
Keys: []string{"FOO"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"FOO": {Static: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: nil,
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "default"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "default"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"FOO=bar", "BAR=baz"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "default"},
|
|
||||||
},
|
|
||||||
ExpectedGlobals: &taskfile.Vars{
|
|
||||||
Keys: []string{"FOO", "BAR"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"FOO": {Static: "bar"},
|
|
||||||
"BAR": {Static: "baz"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
|
||||||
calls, globals := args.ParseV3(test.Args...)
|
|
||||||
assert.Equal(t, test.ExpectedCalls, calls)
|
|
||||||
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
|
||||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArgsV2(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
Args []string
|
|
||||||
ExpectedCalls []taskfile.Call
|
|
||||||
ExpectedGlobals *taskfile.Vars
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "task-b", "task-c"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "task-a"},
|
|
||||||
{Task: "task-b"},
|
|
||||||
{Task: "task-c"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{
|
|
||||||
Task: "task-a",
|
|
||||||
Vars: &taskfile.Vars{
|
|
||||||
Keys: []string{"FOO"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"FOO": taskfile.Var{Static: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{Task: "task-b"},
|
|
||||||
{
|
|
||||||
Task: "task-c",
|
|
||||||
Vars: &taskfile.Vars{
|
|
||||||
Keys: []string{"BAR", "BAZ"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"BAR": taskfile.Var{Static: "baz"},
|
|
||||||
"BAZ": taskfile.Var{Static: "foo"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{
|
|
||||||
Task: "task-a",
|
|
||||||
Vars: &taskfile.Vars{
|
|
||||||
Keys: []string{"CONTENT"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"CONTENT": taskfile.Var{Static: "with some spaces"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"FOO=bar", "task-a", "task-b"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "task-a"},
|
|
||||||
{Task: "task-b"},
|
|
||||||
},
|
|
||||||
ExpectedGlobals: &taskfile.Vars{
|
|
||||||
Keys: []string{"FOO"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"FOO": {Static: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: nil,
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "default"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "default"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"FOO=bar", "BAR=baz"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "default"},
|
|
||||||
},
|
|
||||||
ExpectedGlobals: &taskfile.Vars{
|
|
||||||
Keys: []string{"FOO", "BAR"},
|
|
||||||
Mapping: map[string]taskfile.Var{
|
|
||||||
"FOO": {Static: "bar"},
|
|
||||||
"BAR": {Static: "baz"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
|
||||||
calls, globals := args.ParseV2(test.Args...)
|
|
||||||
assert.Equal(t, test.ExpectedCalls, calls)
|
|
||||||
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
|
||||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
132
cmd/task/task.go
132
cmd/task/task.go
@@ -2,27 +2,22 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/go-task/task"
|
||||||
|
"github.com/go-task/task/internal/args"
|
||||||
|
|
||||||
"github.com/go-task/task/v3"
|
"github.com/spf13/pflag"
|
||||||
"github.com/go-task/task/v3/args"
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "master"
|
version = "master"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]
|
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [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.
|
||||||
@@ -31,14 +26,12 @@ Example: 'task hello' with the following 'Taskfile.yml' file will generate an
|
|||||||
'output.txt' file with the content "hello".
|
'output.txt' file with the content "hello".
|
||||||
|
|
||||||
'''
|
'''
|
||||||
version: '3'
|
hello:
|
||||||
tasks:
|
cmds:
|
||||||
hello:
|
- echo "I am going to write a file named 'output.txt' now."
|
||||||
cmds:
|
- echo "hello" > output.txt
|
||||||
- echo "I am going to write a file named 'output.txt' now."
|
generates:
|
||||||
- echo "hello" > output.txt
|
- output.txt
|
||||||
generates:
|
|
||||||
- output.txt
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@@ -55,7 +48,6 @@ func main() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
versionFlag bool
|
versionFlag bool
|
||||||
helpFlag bool
|
|
||||||
init bool
|
init bool
|
||||||
list bool
|
list bool
|
||||||
status bool
|
status bool
|
||||||
@@ -63,18 +55,10 @@ func main() {
|
|||||||
watch bool
|
watch bool
|
||||||
verbose bool
|
verbose bool
|
||||||
silent bool
|
silent bool
|
||||||
dry bool
|
|
||||||
summary bool
|
|
||||||
parallel bool
|
|
||||||
concurrency int
|
|
||||||
dir string
|
dir string
|
||||||
entrypoint string
|
|
||||||
output string
|
|
||||||
color bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||||
pflag.BoolVarP(&helpFlag, "help", "h", false, "shows Task usage")
|
|
||||||
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
|
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
|
||||||
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
|
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
|
||||||
pflag.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date")
|
pflag.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date")
|
||||||
@@ -82,23 +66,11 @@ func main() {
|
|||||||
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
|
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
|
||||||
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.BoolVarP(¶llel, "parallel", "p", false, "executes tasks provided on command line in parallel")
|
|
||||||
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
|
|
||||||
pflag.BoolVar(&summary, "summary", false, "show summary about a task")
|
|
||||||
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
|
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
|
||||||
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
|
|
||||||
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
|
|
||||||
pflag.BoolVarP(&color, "color", "c", true, "colored output")
|
|
||||||
pflag.IntVarP(&concurrency, "concurrency", "C", 0, "limit number tasks to run concurrently")
|
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
if versionFlag {
|
if versionFlag {
|
||||||
fmt.Printf("Task version: %s\n", version)
|
log.Printf("Task version: %s\n", version)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if helpFlag {
|
|
||||||
pflag.Usage()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,99 +85,51 @@ 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,
|
|
||||||
Entrypoint: entrypoint,
|
Context: getSignalContext(),
|
||||||
Summary: summary,
|
|
||||||
Parallel: parallel,
|
|
||||||
Color: color,
|
|
||||||
Concurrency: concurrency,
|
|
||||||
|
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
|
|
||||||
OutputStyle: output,
|
|
||||||
}
|
}
|
||||||
if err := e.Setup(); err != nil {
|
if err := e.Setup(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
v, err := e.Taskfile.ParsedVersion()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if list {
|
if list {
|
||||||
e.PrintTasksHelp()
|
e.PrintTasksHelp()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
arguments := pflag.Args()
|
||||||
calls []taskfile.Call
|
if len(arguments) == 0 {
|
||||||
globals *taskfile.Vars
|
log.Println("task: No argument given, trying default task")
|
||||||
tasksAndVars, cliArgs = getArgs()
|
arguments = []string{"default"}
|
||||||
)
|
|
||||||
|
|
||||||
if v >= 3.0 {
|
|
||||||
calls, globals = args.ParseV3(tasksAndVars...)
|
|
||||||
} else {
|
|
||||||
calls, globals = args.ParseV2(tasksAndVars...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
globals.Set("CLI_ARGS", taskfile.Var{Static: strings.Join(cliArgs, " ")})
|
calls, err := args.Parse(arguments...)
|
||||||
e.Taskfile.Vars.Merge(globals)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
ctx := context.Background()
|
|
||||||
if !watch {
|
|
||||||
ctx = getSignalContext()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if status {
|
if status {
|
||||||
if err := e.Status(ctx, calls...); err != nil {
|
if err = e.Status(calls...); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.Run(ctx, calls...); err != nil {
|
if err := e.Run(calls...); err != nil {
|
||||||
e.Logger.Errf(logger.Red, "%v", err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getArgs() (tasksAndVars, cliArgs []string) {
|
|
||||||
var (
|
|
||||||
args = pflag.Args()
|
|
||||||
doubleDashPos = pflag.CommandLine.ArgsLenAtDash()
|
|
||||||
)
|
|
||||||
|
|
||||||
if doubleDashPos != -1 {
|
|
||||||
tasksAndVars = args[:doubleDashPos]
|
|
||||||
cliArgs = args[doubleDashPos:]
|
|
||||||
} else {
|
|
||||||
tasksAndVars = args
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSignalContext() context.Context {
|
func getSignalContext() context.Context {
|
||||||
ch := make(chan os.Signal, 1)
|
ch := make(chan os.Signal, 1)
|
||||||
signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)
|
signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
_task_completion()
|
|
||||||
{
|
|
||||||
local scripts;
|
|
||||||
local curr_arg;
|
|
||||||
|
|
||||||
# Remove colon from word breaks
|
|
||||||
COMP_WORDBREAKS=${COMP_WORDBREAKS//:}
|
|
||||||
|
|
||||||
scripts=$(task -l | sed '1d' | awk '{ print $2 }' | sed 's/:$//');
|
|
||||||
|
|
||||||
curr_arg="${COMP_WORDS[COMP_CWORD]:-"."}"
|
|
||||||
|
|
||||||
# Do not accept more than 1 argument
|
|
||||||
if [ "${#COMP_WORDS[@]}" != "2" ]; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
COMPREPLY=($(compgen -c | echo "$scripts" | grep $curr_arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
complete -F _task_completion task
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
function __task_get_tasks --description "Prints all available tasks with their description"
|
|
||||||
task -l | sed '1d' | awk '{ $1=""; print $0 }' | tr ': ', '\t' | string trim
|
|
||||||
end
|
|
||||||
|
|
||||||
complete -c task -d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was
|
|
||||||
specified.' -xa "(__task_get_tasks)"
|
|
||||||
|
|
||||||
|
|
||||||
complete -c task -s c -l color -d 'colored output (default true)'
|
|
||||||
complete -c task -s d -l dir -d 'sets directory of execution'
|
|
||||||
complete -c task -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them'
|
|
||||||
complete -c task -s f -l force -d 'forces execution even when the task is up-to-date'
|
|
||||||
complete -c task -s h -l help -d 'shows Task usage'
|
|
||||||
complete -c task -s i -l init -d 'creates a new Taskfile.yml in the current folder'
|
|
||||||
complete -c task -s l -l list -d 'lists tasks with description of current Taskfile'
|
|
||||||
complete -c task -s o -l output -d 'sets output style: [interleaved|group|prefixed]' -xa "interleaved group prefixed"
|
|
||||||
complete -c task -s p -l parallel -d 'executes tasks provided on command line in parallel'
|
|
||||||
complete -c task -s s -l silent -d 'disables echoing'
|
|
||||||
complete -c task -l status -d 'exits with non-zero exit code if any of the given tasks is not up-to-date'
|
|
||||||
complete -c task -l summary -d 'show summary about a task'
|
|
||||||
complete -c task -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml"'
|
|
||||||
complete -c task -s v -l verbose -d 'enables verbose mode'
|
|
||||||
complete -c task -l version -d 'show Task version'
|
|
||||||
complete -c task -s w -l watch -d 'enables watch of the given task'
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
$scriptBlock = {
|
|
||||||
param($commandName, $wordToComplete, $cursorPosition)
|
|
||||||
$curReg = "task{.exe}? (.*?)$"
|
|
||||||
$startsWith = $wordToComplete | Select-String $curReg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value }
|
|
||||||
$reg = "\* ($startsWith.+?):"
|
|
||||||
$listOutput = $(task -l)
|
|
||||||
$listOutput | Select-String $reg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value + " " }
|
|
||||||
}
|
|
||||||
|
|
||||||
Register-ArgumentCompleter -Native -CommandName task -ScriptBlock $scriptBlock
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#compdef task
|
|
||||||
|
|
||||||
# Listing commands from Taskfile.yml
|
|
||||||
function __list() {
|
|
||||||
local -a scripts
|
|
||||||
|
|
||||||
if [ -f Taskfile.yml ]; then
|
|
||||||
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/g'))
|
|
||||||
_describe 'script' scripts
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
_arguments \
|
|
||||||
'(-d --dir)'{-d,--dir}': :_files' \
|
|
||||||
'(--dry)'--dry \
|
|
||||||
'(-f --force)'{-f,--force} \
|
|
||||||
'(-i --init)'{-i,--init} \
|
|
||||||
'(-l --list)'{-l,--list} \
|
|
||||||
'(-s --silent)'{-s,--silent} \
|
|
||||||
'(--status)'--status \
|
|
||||||
'(-v --verbose)'{-v,--verbose} \
|
|
||||||
'(--version)'--version \
|
|
||||||
'(-w --watch)'{-w,--watch} \
|
|
||||||
'(- *)'{-h,--help} \
|
|
||||||
'*: :__list' \
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package task
|
|
||||||
|
|
||||||
func (e *Executor) acquireConcurrencyLimit() func() {
|
|
||||||
if e.concurrencySemaphore == nil {
|
|
||||||
return emptyFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
e.concurrencySemaphore <- struct{}{}
|
|
||||||
return func() {
|
|
||||||
<-e.concurrencySemaphore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) releaseConcurrencyLimit() func() {
|
|
||||||
if e.concurrencySemaphore == nil {
|
|
||||||
return emptyFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
<-e.concurrencySemaphore
|
|
||||||
return func() {
|
|
||||||
e.concurrencySemaphore <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func emptyFunc() {}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
taskfile.dev
|
|
||||||
BIN
docs/Logo.png
BIN
docs/Logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,51 +0,0 @@
|
|||||||
# Task
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img id="logo" src="/Logo.png" height="250px" width="250px" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
Task is a task runner / build tool that aims to be simpler and easier to use
|
|
||||||
than, for example, [GNU Make][make].
|
|
||||||
|
|
||||||
Since it's written in [Go][go], Task is just a single binary and has no other
|
|
||||||
dependencies, which means you don't need to mess with any complicated install
|
|
||||||
setups just to use a build tool.
|
|
||||||
|
|
||||||
Once [installed](installation.md), you just need to describe your build tasks
|
|
||||||
using a simple [YAML][yaml] schema in a file called `Taskfile.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
hello:
|
|
||||||
cmds:
|
|
||||||
- echo 'Hello World from Task!'
|
|
||||||
silent: true
|
|
||||||
```
|
|
||||||
|
|
||||||
And call it by running `task hello` from you terminal.
|
|
||||||
|
|
||||||
The above example is just the start, you can take a look at the [usage](usage.md)
|
|
||||||
guide to check the full schema documentation and Task features.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- [Easy installation](installation.md): just download a single binary, add to
|
|
||||||
$PATH and you're done! Or you can also install using [Homebrew][homebrew],
|
|
||||||
[Snapcraft][snapcraft], or [Scoop][scoop] if you want;
|
|
||||||
- Available on CIs: by adding [this simple command](installation.md#install-script)
|
|
||||||
to install on your CI script and you're done to use Task as part of your CI pipeline;
|
|
||||||
- Truly cross-platform: while most build tools only work well on Linux or macOS,
|
|
||||||
Task also supports Windows thanks to [this awesome shell interpreter for Go][sh];
|
|
||||||
- Great for code generation: you can easily [prevent a task from running](usage.md#prevent-unnecessary-work)
|
|
||||||
if a given set of files haven't changed since last run (based either on its
|
|
||||||
timestamp or content).
|
|
||||||
|
|
||||||
[make]: https://www.gnu.org/software/make/
|
|
||||||
[go]: https://golang.org/
|
|
||||||
[yaml]: http://yaml.org/
|
|
||||||
[homebrew]: https://brew.sh/
|
|
||||||
[snapcraft]: https://snapcraft.io/
|
|
||||||
[scoop]: https://scoop.sh/
|
|
||||||
[sh]: https://mvdan.cc/sh
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
install:
|
|
||||||
desc: Installs docsify to work the on the documentation site
|
|
||||||
cmds:
|
|
||||||
- npm install docsify-cli -g
|
|
||||||
|
|
||||||
serve:
|
|
||||||
desc: Serves the documentation site locally
|
|
||||||
cmds:
|
|
||||||
- docsify serve .
|
|
||||||
|
|
||||||
ico:
|
|
||||||
desc: Generate favicon.ico from Logo.png
|
|
||||||
cmds:
|
|
||||||
- convert -background transparent "Logo.png" -define icon:auto-resize=16,24,32,48,64,72,96,128,256 "favicon.ico"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
- [Installation](installation.md)
|
|
||||||
- [Usage](usage.md)
|
|
||||||
- [Styleguide](styleguide.md)
|
|
||||||
- [Taskfile Versions](taskfile_versions.md)
|
|
||||||
- [Examples](examples.md)
|
|
||||||
- [Releasing Task](releasing_task.md)
|
|
||||||
- [Donate](donate.md)
|
|
||||||
- [GitHub](https://github.com/go-task/task)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Donate
|
|
||||||
|
|
||||||
If you find this project useful, you can consider donating by using one of the
|
|
||||||
channels listed below.
|
|
||||||
|
|
||||||
This is just a way of saying "thank you", it won't give you any benefits like
|
|
||||||
higher priority on issues or something similar.
|
|
||||||
|
|
||||||
## Open Collective
|
|
||||||
|
|
||||||
Task is on [Open Collective](https://opencollective.com/task) and you have
|
|
||||||
these options to donate:
|
|
||||||
|
|
||||||
- [$2 per month](https://opencollective.com/task/contribute/backer-4034/checkout)
|
|
||||||
- [$5 per month](https://opencollective.com/task/contribute/supporter-8404/checkout)
|
|
||||||
- [$20 per month](https://opencollective.com/task/contribute/sponsor-4035/checkout)
|
|
||||||
- [Custom value - One-time donation option supported](https://opencollective.com/task/donate)
|
|
||||||
|
|
||||||
## Patreon
|
|
||||||
|
|
||||||
I'm also on [Patreon](https://www.patreon.com/andreynering) if
|
|
||||||
you prefer:
|
|
||||||
|
|
||||||
- [$5 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229277)
|
|
||||||
- [$10 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229276)
|
|
||||||
- [$15 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229275)
|
|
||||||
|
|
||||||
You can choose a custom value on any of the links above.
|
|
||||||
|
|
||||||
Patreon does not support one-time donation. As a workaround you can fire a
|
|
||||||
subscription and cancel it once the donation was succeded.
|
|
||||||
|
|
||||||
## PayPal
|
|
||||||
|
|
||||||
- [Any value - One-time donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=BRL&source=url)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Examples
|
|
||||||
|
|
||||||
The [go-task/examples][examples] intends to be a collection of Taskfiles for
|
|
||||||
various use cases.
|
|
||||||
(It still lacks many examples, though. Contributions are welcome).
|
|
||||||
|
|
||||||
[examples]: https://github.com/go-task/examples
|
|
||||||
BIN
docs/favicon.ico
BIN
docs/favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 170 KiB |
@@ -1,45 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Task</title>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
||||||
<meta name="description" content="A task runner / simpler Make alternative written in Go">
|
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
|
||||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-themeable/dist/css/theme-simple.css">
|
|
||||||
<meta name="google-site-verification" content="VGAYkbdmuaciIDGkBe-eAg9yfZg0C6ostgonbGxxOa0" />
|
|
||||||
<style>
|
|
||||||
#logo {
|
|
||||||
transition: all 0.7s ease;
|
|
||||||
}
|
|
||||||
#logo:hover {
|
|
||||||
-webkit-transform: rotateZ(360deg);
|
|
||||||
-ms-transform: rotateZ(360deg);
|
|
||||||
transform: rotateZ(360deg);
|
|
||||||
}
|
|
||||||
.app-name-link img {
|
|
||||||
width: 125px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script>
|
|
||||||
window.$docsify = {
|
|
||||||
name: 'Task',
|
|
||||||
repo: 'go-task/task',
|
|
||||||
logo: 'Logo.png',
|
|
||||||
themeColor: '#29beb0',
|
|
||||||
loadSidebar: true,
|
|
||||||
auto2top: true,
|
|
||||||
maxLevel: 3,
|
|
||||||
subMaxLevel: 3
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
|
||||||
<script src="//cdn.jsdelivr.net/npm/docsify-themeable/dist/js/docsify-themeable.min.js"></script>
|
|
||||||
<script src="//cdn.jsdelivr.net/npm/docsify-tabs"></script>
|
|
||||||
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
|
|
||||||
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-yaml.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
383
docs/install.sh
383
docs/install.sh
@@ -1,383 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
# Code generated by godownloader on 2021-01-12T13:40:40Z. DO NOT EDIT.
|
|
||||||
#
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
this=$1
|
|
||||||
cat <<EOF
|
|
||||||
$this: download go binaries for go-task/task
|
|
||||||
|
|
||||||
Usage: $this [-b] bindir [-d] [tag]
|
|
||||||
-b sets bindir or installation directory, Defaults to ./bin
|
|
||||||
-d turns on debug logging
|
|
||||||
[tag] is a tag from
|
|
||||||
https://github.com/go-task/task/releases
|
|
||||||
If tag is missing, then the latest will be used.
|
|
||||||
|
|
||||||
Generated by godownloader
|
|
||||||
https://github.com/goreleaser/godownloader
|
|
||||||
|
|
||||||
EOF
|
|
||||||
exit 2
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_args() {
|
|
||||||
#BINDIR is ./bin unless set be ENV
|
|
||||||
# over-ridden by flag below
|
|
||||||
|
|
||||||
BINDIR=${BINDIR:-./bin}
|
|
||||||
while getopts "b:dh?x" arg; do
|
|
||||||
case "$arg" in
|
|
||||||
b) BINDIR="$OPTARG" ;;
|
|
||||||
d) log_set_priority 10 ;;
|
|
||||||
h | \?) usage "$0" ;;
|
|
||||||
x) set -x ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
shift $((OPTIND - 1))
|
|
||||||
TAG=$1
|
|
||||||
}
|
|
||||||
# this function wraps all the destructive operations
|
|
||||||
# if a curl|bash cuts off the end of the script due to
|
|
||||||
# network, either nothing will happen or will syntax error
|
|
||||||
# out preventing half-done work
|
|
||||||
execute() {
|
|
||||||
tmpdir=$(mktemp -d)
|
|
||||||
log_debug "downloading files into ${tmpdir}"
|
|
||||||
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
|
||||||
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
|
||||||
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
|
||||||
srcdir="${tmpdir}"
|
|
||||||
(cd "${tmpdir}" && untar "${TARBALL}")
|
|
||||||
test ! -d "${BINDIR}" && install -d "${BINDIR}"
|
|
||||||
for binexe in $BINARIES; do
|
|
||||||
if [ "$OS" = "windows" ]; then
|
|
||||||
binexe="${binexe}.exe"
|
|
||||||
fi
|
|
||||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
|
||||||
log_info "installed ${BINDIR}/${binexe}"
|
|
||||||
done
|
|
||||||
rm -rf "${tmpdir}"
|
|
||||||
}
|
|
||||||
get_binaries() {
|
|
||||||
case "$PLATFORM" in
|
|
||||||
darwin/amd64) BINARIES="task" ;;
|
|
||||||
darwin/arm64) BINARIES="task" ;;
|
|
||||||
darwin/armv6) BINARIES="task" ;;
|
|
||||||
linux/386) BINARIES="task" ;;
|
|
||||||
linux/amd64) BINARIES="task" ;;
|
|
||||||
linux/arm64) BINARIES="task" ;;
|
|
||||||
linux/armv6) BINARIES="task" ;;
|
|
||||||
windows/386) BINARIES="task" ;;
|
|
||||||
windows/amd64) BINARIES="task" ;;
|
|
||||||
windows/arm64) BINARIES="task" ;;
|
|
||||||
windows/armv6) BINARIES="task" ;;
|
|
||||||
*)
|
|
||||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
tag_to_version() {
|
|
||||||
if [ -z "${TAG}" ]; then
|
|
||||||
log_info "checking GitHub for latest tag"
|
|
||||||
else
|
|
||||||
log_info "checking GitHub for tag '${TAG}'"
|
|
||||||
fi
|
|
||||||
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
|
|
||||||
if test -z "$REALTAG"; then
|
|
||||||
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# if version starts with 'v', remove it
|
|
||||||
TAG="$REALTAG"
|
|
||||||
VERSION=${TAG#v}
|
|
||||||
}
|
|
||||||
adjust_format() {
|
|
||||||
# change format (tar.gz or zip) based on OS
|
|
||||||
case ${OS} in
|
|
||||||
windows) FORMAT=zip ;;
|
|
||||||
esac
|
|
||||||
true
|
|
||||||
}
|
|
||||||
adjust_os() {
|
|
||||||
# adjust archive name based on OS
|
|
||||||
true
|
|
||||||
}
|
|
||||||
adjust_arch() {
|
|
||||||
# adjust archive name based on ARCH
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
cat /dev/null <<EOF
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
https://github.com/client9/shlib - portable posix shell functions
|
|
||||||
Public domain - http://unlicense.org
|
|
||||||
https://github.com/client9/shlib/blob/master/LICENSE.md
|
|
||||||
but credit (and pull requests) appreciated.
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
EOF
|
|
||||||
is_command() {
|
|
||||||
command -v "$1" >/dev/null
|
|
||||||
}
|
|
||||||
echoerr() {
|
|
||||||
echo "$@" 1>&2
|
|
||||||
}
|
|
||||||
log_prefix() {
|
|
||||||
echo "$0"
|
|
||||||
}
|
|
||||||
_logp=6
|
|
||||||
log_set_priority() {
|
|
||||||
_logp="$1"
|
|
||||||
}
|
|
||||||
log_priority() {
|
|
||||||
if test -z "$1"; then
|
|
||||||
echo "$_logp"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
[ "$1" -le "$_logp" ]
|
|
||||||
}
|
|
||||||
log_tag() {
|
|
||||||
case $1 in
|
|
||||||
0) echo "emerg" ;;
|
|
||||||
1) echo "alert" ;;
|
|
||||||
2) echo "crit" ;;
|
|
||||||
3) echo "err" ;;
|
|
||||||
4) echo "warning" ;;
|
|
||||||
5) echo "notice" ;;
|
|
||||||
6) echo "info" ;;
|
|
||||||
7) echo "debug" ;;
|
|
||||||
*) echo "$1" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
log_debug() {
|
|
||||||
log_priority 7 || return 0
|
|
||||||
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
|
|
||||||
}
|
|
||||||
log_info() {
|
|
||||||
log_priority 6 || return 0
|
|
||||||
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
|
|
||||||
}
|
|
||||||
log_err() {
|
|
||||||
log_priority 3 || return 0
|
|
||||||
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
|
|
||||||
}
|
|
||||||
log_crit() {
|
|
||||||
log_priority 2 || return 0
|
|
||||||
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
|
|
||||||
}
|
|
||||||
uname_os() {
|
|
||||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
||||||
case "$os" in
|
|
||||||
cygwin_nt*) os="windows" ;;
|
|
||||||
mingw*) os="windows" ;;
|
|
||||||
msys_nt*) os="windows" ;;
|
|
||||||
esac
|
|
||||||
echo "$os"
|
|
||||||
}
|
|
||||||
uname_arch() {
|
|
||||||
arch=$(uname -m)
|
|
||||||
case $arch in
|
|
||||||
x86_64) arch="amd64" ;;
|
|
||||||
x86) arch="386" ;;
|
|
||||||
i686) arch="386" ;;
|
|
||||||
i386) arch="386" ;;
|
|
||||||
aarch64) arch="arm64" ;;
|
|
||||||
armv5*) arch="armv5" ;;
|
|
||||||
armv6*) arch="armv6" ;;
|
|
||||||
armv7*) arch="armv7" ;;
|
|
||||||
esac
|
|
||||||
echo ${arch}
|
|
||||||
}
|
|
||||||
uname_os_check() {
|
|
||||||
os=$(uname_os)
|
|
||||||
case "$os" in
|
|
||||||
darwin) return 0 ;;
|
|
||||||
dragonfly) return 0 ;;
|
|
||||||
freebsd) return 0 ;;
|
|
||||||
linux) return 0 ;;
|
|
||||||
android) return 0 ;;
|
|
||||||
nacl) return 0 ;;
|
|
||||||
netbsd) return 0 ;;
|
|
||||||
openbsd) return 0 ;;
|
|
||||||
plan9) return 0 ;;
|
|
||||||
solaris) return 0 ;;
|
|
||||||
windows) return 0 ;;
|
|
||||||
esac
|
|
||||||
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
uname_arch_check() {
|
|
||||||
arch=$(uname_arch)
|
|
||||||
case "$arch" in
|
|
||||||
386) return 0 ;;
|
|
||||||
amd64) return 0 ;;
|
|
||||||
arm64) return 0 ;;
|
|
||||||
armv5) return 0 ;;
|
|
||||||
armv6) return 0 ;;
|
|
||||||
armv7) return 0 ;;
|
|
||||||
ppc64) return 0 ;;
|
|
||||||
ppc64le) return 0 ;;
|
|
||||||
mips) return 0 ;;
|
|
||||||
mipsle) return 0 ;;
|
|
||||||
mips64) return 0 ;;
|
|
||||||
mips64le) return 0 ;;
|
|
||||||
s390x) return 0 ;;
|
|
||||||
amd64p32) return 0 ;;
|
|
||||||
esac
|
|
||||||
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
untar() {
|
|
||||||
tarball=$1
|
|
||||||
case "${tarball}" in
|
|
||||||
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
|
|
||||||
*.tar) tar --no-same-owner -xf "${tarball}" ;;
|
|
||||||
*.zip) unzip "${tarball}" ;;
|
|
||||||
*)
|
|
||||||
log_err "untar unknown archive format for ${tarball}"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
http_download_curl() {
|
|
||||||
local_file=$1
|
|
||||||
source_url=$2
|
|
||||||
header=$3
|
|
||||||
if [ -z "$header" ]; then
|
|
||||||
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
|
||||||
else
|
|
||||||
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
|
||||||
fi
|
|
||||||
if [ "$code" != "200" ]; then
|
|
||||||
log_debug "http_download_curl received HTTP status $code"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
http_download_wget() {
|
|
||||||
local_file=$1
|
|
||||||
source_url=$2
|
|
||||||
header=$3
|
|
||||||
if [ -z "$header" ]; then
|
|
||||||
wget -q -O "$local_file" "$source_url"
|
|
||||||
else
|
|
||||||
wget -q --header "$header" -O "$local_file" "$source_url"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
http_download() {
|
|
||||||
log_debug "http_download $2"
|
|
||||||
if is_command curl; then
|
|
||||||
http_download_curl "$@"
|
|
||||||
return
|
|
||||||
elif is_command wget; then
|
|
||||||
http_download_wget "$@"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
log_crit "http_download unable to find wget or curl"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
http_copy() {
|
|
||||||
tmp=$(mktemp)
|
|
||||||
http_download "${tmp}" "$1" "$2" || return 1
|
|
||||||
body=$(cat "$tmp")
|
|
||||||
rm -f "${tmp}"
|
|
||||||
echo "$body"
|
|
||||||
}
|
|
||||||
github_release() {
|
|
||||||
owner_repo=$1
|
|
||||||
version=$2
|
|
||||||
test -z "$version" && version="latest"
|
|
||||||
giturl="https://github.com/${owner_repo}/releases/${version}"
|
|
||||||
json=$(http_copy "$giturl" "Accept:application/json")
|
|
||||||
test -z "$json" && return 1
|
|
||||||
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
|
|
||||||
test -z "$version" && return 1
|
|
||||||
echo "$version"
|
|
||||||
}
|
|
||||||
hash_sha256() {
|
|
||||||
TARGET=${1:-/dev/stdin}
|
|
||||||
if is_command gsha256sum; then
|
|
||||||
hash=$(gsha256sum "$TARGET") || return 1
|
|
||||||
echo "$hash" | cut -d ' ' -f 1
|
|
||||||
elif is_command sha256sum; then
|
|
||||||
hash=$(sha256sum "$TARGET") || return 1
|
|
||||||
echo "$hash" | cut -d ' ' -f 1
|
|
||||||
elif is_command shasum; then
|
|
||||||
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
|
||||||
echo "$hash" | cut -d ' ' -f 1
|
|
||||||
elif is_command openssl; then
|
|
||||||
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
|
||||||
echo "$hash" | cut -d ' ' -f a
|
|
||||||
else
|
|
||||||
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
hash_sha256_verify() {
|
|
||||||
TARGET=$1
|
|
||||||
checksums=$2
|
|
||||||
if [ -z "$checksums" ]; then
|
|
||||||
log_err "hash_sha256_verify checksum file not specified in arg2"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
BASENAME=${TARGET##*/}
|
|
||||||
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
|
||||||
if [ -z "$want" ]; then
|
|
||||||
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(hash_sha256 "$TARGET")
|
|
||||||
if [ "$want" != "$got" ]; then
|
|
||||||
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
cat /dev/null <<EOF
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
End of functions from https://github.com/client9/shlib
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
EOF
|
|
||||||
|
|
||||||
PROJECT_NAME="task"
|
|
||||||
OWNER=go-task
|
|
||||||
REPO="task"
|
|
||||||
BINARY=task
|
|
||||||
FORMAT=tar.gz
|
|
||||||
OS=$(uname_os)
|
|
||||||
ARCH=$(uname_arch)
|
|
||||||
PREFIX="$OWNER/$REPO"
|
|
||||||
|
|
||||||
# use in logging routines
|
|
||||||
log_prefix() {
|
|
||||||
echo "$PREFIX"
|
|
||||||
}
|
|
||||||
PLATFORM="${OS}/${ARCH}"
|
|
||||||
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
|
|
||||||
|
|
||||||
uname_os_check "$OS"
|
|
||||||
uname_arch_check "$ARCH"
|
|
||||||
|
|
||||||
parse_args "$@"
|
|
||||||
|
|
||||||
get_binaries
|
|
||||||
|
|
||||||
tag_to_version
|
|
||||||
|
|
||||||
adjust_format
|
|
||||||
|
|
||||||
adjust_os
|
|
||||||
|
|
||||||
adjust_arch
|
|
||||||
|
|
||||||
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
|
||||||
|
|
||||||
NAME=${BINARY}_${OS}_${ARCH}
|
|
||||||
TARBALL=${NAME}.${FORMAT}
|
|
||||||
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
|
||||||
CHECKSUM=task_checksums.txt
|
|
||||||
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
|
||||||
|
|
||||||
|
|
||||||
execute
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
# Installation
|
|
||||||
|
|
||||||
Task offers many installation methods. Check out the available methods below.
|
|
||||||
|
|
||||||
## Package Managers
|
|
||||||
|
|
||||||
<!-- tabs:start -->
|
|
||||||
|
|
||||||
#### **Homebrew**
|
|
||||||
|
|
||||||
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
|
|
||||||
Task is as simple as running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brew install go-task/tap/go-task
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Snap**
|
|
||||||
|
|
||||||
Task is available in [Snapcraft][snapcraft], but keep in mind that your
|
|
||||||
Linux distribution should allow classic confinement for Snaps to Task work
|
|
||||||
right:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo snap install task --classic
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Scoop**
|
|
||||||
|
|
||||||
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
|
|
||||||
to install Task like:
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
scoop bucket add extras
|
|
||||||
scoop install task
|
|
||||||
```
|
|
||||||
|
|
||||||
This installation method is community owned. After a new release of Task, it
|
|
||||||
may take some time until it's available on Scoop.
|
|
||||||
|
|
||||||
#### **AUR**
|
|
||||||
|
|
||||||
If you're on Arch Linux you can install Task from
|
|
||||||
[AUR](https://aur.archlinux.org/packages/taskfile-git) using your favorite
|
|
||||||
package manager such as `yay`, `pacaur` or `yaourt`:
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
yay -S taskfile-git
|
|
||||||
```
|
|
||||||
|
|
||||||
This installation method is community owned, but since it's `-git` version of
|
|
||||||
the package, it's always latest available version based on the Git repository.
|
|
||||||
|
|
||||||
<!-- tabs:end -->
|
|
||||||
|
|
||||||
## Get The Binary
|
|
||||||
|
|
||||||
<!-- tabs:start -->
|
|
||||||
|
|
||||||
#### **Binary**
|
|
||||||
|
|
||||||
You can download the binary from the [releases page on GitHub][releases] and
|
|
||||||
add to your `$PATH`.
|
|
||||||
|
|
||||||
DEB and RPM packages are also available.
|
|
||||||
|
|
||||||
The `task_checksums.txt` file contains the SHA-256 checksum for each file.
|
|
||||||
|
|
||||||
#### **Install Script**
|
|
||||||
|
|
||||||
We also have a [install script][installscript], which is very useful on
|
|
||||||
scenarios like CIs. Many thanks to [GoDownloader][godownloader] for allowing
|
|
||||||
easily generating this script.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# For Default Installion to ./bin with debug logging
|
|
||||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
|
|
||||||
|
|
||||||
# For Installation To /usr/local/bin for userwide access with debug logging
|
|
||||||
# May require sudo sh
|
|
||||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
> This method will download the binary on the local `./bin` directory by default.
|
|
||||||
|
|
||||||
#### **GitHub Actions**
|
|
||||||
|
|
||||||
If you want to install Task in GitHub Actions you can try using
|
|
||||||
[this action](https://github.com/arduino/actions/tree/master/setup-taskfile)
|
|
||||||
by the Arduino team:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Install Task
|
|
||||||
uses: Arduino/actions/setup-taskfile@master
|
|
||||||
```
|
|
||||||
|
|
||||||
This installation method is community owned.
|
|
||||||
|
|
||||||
<!-- tabs:end -->
|
|
||||||
|
|
||||||
## Build From Source
|
|
||||||
|
|
||||||
<!-- tabs:start -->
|
|
||||||
|
|
||||||
#### **Go Modules**
|
|
||||||
|
|
||||||
First, make sure you have [Go][go] properly installed and setup.
|
|
||||||
|
|
||||||
You can easily install it globally by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get -u github.com/go-task/task/v3/cmd/task
|
|
||||||
```
|
|
||||||
|
|
||||||
Or you can install into another directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/go-task/task
|
|
||||||
cd task
|
|
||||||
|
|
||||||
# Compiling binary to $GOPATH/bin
|
|
||||||
go install -v ./cmd/task
|
|
||||||
|
|
||||||
# Compiling it to another location.
|
|
||||||
# Use -o ./task.exe on Windows.
|
|
||||||
go build -v -o ./task ./cmd/task
|
|
||||||
```
|
|
||||||
|
|
||||||
> For CI environments we recommend using the [Install Script](#get-the-binary)
|
|
||||||
> instead, which is faster and more stable, since it'll just download the latest
|
|
||||||
> released binary, instead of compiling the edge (master branch) version.
|
|
||||||
|
|
||||||
<!-- tabs:end -->
|
|
||||||
|
|
||||||
[go]: https://golang.org/
|
|
||||||
[snapcraft]: https://snapcraft.io/task
|
|
||||||
[homebrew]: https://brew.sh/
|
|
||||||
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
|
|
||||||
[releases]: https://github.com/go-task/task/releases
|
|
||||||
[godownloader]: https://github.com/goreleaser/godownloader
|
|
||||||
[scoop]: https://scoop.sh/
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
# Styleguide
|
|
||||||
|
|
||||||
This is the official Task styleguide for `Taskfile.yml` files. This guide
|
|
||||||
contains some basic instructions to keep your Taskfile clean and familiar to
|
|
||||||
other users.
|
|
||||||
|
|
||||||
This contains general guidelines, but don't necessarely need to be strictly
|
|
||||||
followed. Feel free to disagree and proceed differently in some point if you
|
|
||||||
need or want to. Also, feel free to open issues or pull requests with
|
|
||||||
improvements to this guide.
|
|
||||||
|
|
||||||
## Use `Taskfile.yml` and not `taskfile.yml`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# bad
|
|
||||||
taskfile.yml
|
|
||||||
|
|
||||||
|
|
||||||
# good
|
|
||||||
Taskfile.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
This is important especially for Linux users. Windows and macOS have case
|
|
||||||
insensitive filesystems, so `taskfile.yml` will end up working, even that not
|
|
||||||
officially supported. On Linux, only `Taskfile.yml` will work, though.
|
|
||||||
|
|
||||||
## Use the correct order of keywords
|
|
||||||
|
|
||||||
- `version:`
|
|
||||||
- `includes:`
|
|
||||||
- Configuration ones, like `output:`, `expansions:` or `silent:`
|
|
||||||
- `vars:`
|
|
||||||
- `env:`
|
|
||||||
- `tasks:`
|
|
||||||
|
|
||||||
## Use 2 spaces for indentation
|
|
||||||
|
|
||||||
This is the most common convention for YAML files, and Task follows it.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# bad
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
cmds:
|
|
||||||
- echo 'foo'
|
|
||||||
|
|
||||||
|
|
||||||
# good
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
cmds:
|
|
||||||
- echo 'foo'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Separate with spaces the mains sections
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# bad
|
|
||||||
version: '3'
|
|
||||||
includes:
|
|
||||||
docker: ./docker/Taskfile.yml
|
|
||||||
output: prefixed
|
|
||||||
expansions: 3
|
|
||||||
vars:
|
|
||||||
FOO: bar
|
|
||||||
env:
|
|
||||||
BAR: baz
|
|
||||||
tasks:
|
|
||||||
# ...
|
|
||||||
|
|
||||||
|
|
||||||
# good
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
docker: ./docker/Taskfile.yml
|
|
||||||
|
|
||||||
output: prefixed
|
|
||||||
expansions: 3
|
|
||||||
|
|
||||||
vars:
|
|
||||||
FOO: bar
|
|
||||||
|
|
||||||
env:
|
|
||||||
BAR: baz
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Add spaces between tasks
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# bad
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
cmds:
|
|
||||||
- echo 'foo'
|
|
||||||
bar:
|
|
||||||
cmds:
|
|
||||||
- echo 'bar'
|
|
||||||
baz:
|
|
||||||
cmds:
|
|
||||||
- echo 'baz'
|
|
||||||
|
|
||||||
|
|
||||||
# good
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
cmds:
|
|
||||||
- echo 'foo'
|
|
||||||
|
|
||||||
bar:
|
|
||||||
cmds:
|
|
||||||
- echo 'bar'
|
|
||||||
|
|
||||||
baz:
|
|
||||||
cmds:
|
|
||||||
- echo 'baz'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use upper-case variable names
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# bad
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
vars:
|
|
||||||
binary_name: myapp
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
cmds:
|
|
||||||
- go build -o {{.binary_name}} .
|
|
||||||
|
|
||||||
|
|
||||||
# good
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
vars:
|
|
||||||
BINARY_NAME: myapp
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
cmds:
|
|
||||||
- go build -o {{.BINARY_NAME}} .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Don't wrap vars in spaces when templating
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# bad
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
greet:
|
|
||||||
cmds:
|
|
||||||
- echo '{{ .MESSAGE }}'
|
|
||||||
|
|
||||||
|
|
||||||
# good
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
greet:
|
|
||||||
cmds:
|
|
||||||
- echo '{{.MESSAGE}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
This convention is also used by most people for any Go templating.
|
|
||||||
|
|
||||||
## Separate task name words with a dash
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# bad
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
do_something_fancy:
|
|
||||||
cmds:
|
|
||||||
- echo 'Do something'
|
|
||||||
|
|
||||||
|
|
||||||
# good
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
do-something-fancy:
|
|
||||||
cmds:
|
|
||||||
- echo 'Do something'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use colon for task namespacing
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# good
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
docker:build:
|
|
||||||
cmds:
|
|
||||||
- docker ...
|
|
||||||
|
|
||||||
docker:run:
|
|
||||||
cmds:
|
|
||||||
- docker-compose ...
|
|
||||||
```
|
|
||||||
|
|
||||||
This is also done automatically when using included Taskfiles.
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
# Taskfile Versions
|
|
||||||
|
|
||||||
The Taskfile syntax and features changed with time. This document explains what
|
|
||||||
changed on each version and how to upgrade your Taskfile.
|
|
||||||
|
|
||||||
## What the Taskfile version mean
|
|
||||||
|
|
||||||
The Taskfile version follows the Task version. E.g. the change to Taskfile
|
|
||||||
version `2` means that Task `v2.0.0` should be release to support it.
|
|
||||||
|
|
||||||
The `version:` key on Taskfile accepts a semver string, so either `2`, `2.0` or
|
|
||||||
`2.0.0` is accepted. If you choose to use `2.0` Task will not enable future
|
|
||||||
`2.1` features, but if you choose to use `2`, then any `2.x.x` features will be
|
|
||||||
available, but not `3.0.0+`.
|
|
||||||
|
|
||||||
## Version 1
|
|
||||||
|
|
||||||
> NOTE: Taskfiles in version 1 are not supported on Task >= v3.0.0 anymore.
|
|
||||||
|
|
||||||
In the first version of the `Taskfile`, the `version:` key was not available,
|
|
||||||
because the tasks was in the root of the YAML document. Like this:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- echo "Hello, World!"
|
|
||||||
```
|
|
||||||
|
|
||||||
The variable priority order was also different:
|
|
||||||
|
|
||||||
1. Call variables
|
|
||||||
2. Environment
|
|
||||||
3. Task variables
|
|
||||||
4. `Taskvars.yml` variables
|
|
||||||
|
|
||||||
## Version 2.0
|
|
||||||
|
|
||||||
At version 2, we introduced the `version:` key, to allow us to evolve Task
|
|
||||||
with new features without breaking existing Taskfiles. The new syntax is as
|
|
||||||
follows:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '2'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- echo "Hello, World!"
|
|
||||||
```
|
|
||||||
|
|
||||||
Version 2 allows you to write global variables directly in the Taskfile,
|
|
||||||
if you don't want to create a `Taskvars.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '2'
|
|
||||||
|
|
||||||
vars:
|
|
||||||
GREETING: Hello, World!
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
greet:
|
|
||||||
cmds:
|
|
||||||
- echo "{{.GREETING}}"
|
|
||||||
```
|
|
||||||
|
|
||||||
The variable priority order changed to the following:
|
|
||||||
|
|
||||||
1. Task variables
|
|
||||||
2. Call variables
|
|
||||||
3. Taskfile variables
|
|
||||||
4. Taskvars file variables
|
|
||||||
5. Environment variables
|
|
||||||
|
|
||||||
A new global option was added to configure the number of variables expansions
|
|
||||||
(which default to 2):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '2'
|
|
||||||
|
|
||||||
expansions: 3
|
|
||||||
|
|
||||||
vars:
|
|
||||||
FOO: foo
|
|
||||||
BAR: bar
|
|
||||||
BAZ: baz
|
|
||||||
FOOBAR: "{{.FOO}}{{.BAR}}"
|
|
||||||
FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}"
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
cmds:
|
|
||||||
- echo "{{.FOOBARBAZ}}"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Version 2.1
|
|
||||||
|
|
||||||
Version 2.1 includes a global `output` option, to allow having more control
|
|
||||||
over how commands output are printed to the console
|
|
||||||
(see [documentation][output] for more info):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '2'
|
|
||||||
|
|
||||||
output: prefixed
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
server:
|
|
||||||
cmds:
|
|
||||||
- go run main.go
|
|
||||||
prefix: server
|
|
||||||
```
|
|
||||||
|
|
||||||
From this version it's also possible to ignore errors of a command or task
|
|
||||||
(check documentation [here][ignore_errors]):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '2'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
example-1:
|
|
||||||
cmds:
|
|
||||||
- cmd: exit 1
|
|
||||||
ignore_error: true
|
|
||||||
- echo "This will be print"
|
|
||||||
|
|
||||||
example-2:
|
|
||||||
cmds:
|
|
||||||
- exit 1
|
|
||||||
- echo "This will be print"
|
|
||||||
ignore_error: true
|
|
||||||
```
|
|
||||||
|
|
||||||
## Version 2.2
|
|
||||||
|
|
||||||
Version 2.2 comes with a global `includes` options to include other
|
|
||||||
Taskfiles:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '2'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
docs: ./documentation # will look for ./documentation/Taskfile.yml
|
|
||||||
docker: ./DockerTasks.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Version 2.6
|
|
||||||
|
|
||||||
Version 2.6 comes with `preconditions` stanza in tasks.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '2'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
upload_environment:
|
|
||||||
preconditions:
|
|
||||||
- test -f .env
|
|
||||||
cmds:
|
|
||||||
- aws s3 cp .env s3://myenvironment
|
|
||||||
```
|
|
||||||
|
|
||||||
Please check the [documentation][includes]
|
|
||||||
|
|
||||||
[output]: usage.md#output-syntax
|
|
||||||
[ignore_errors]: usage.md#ignore-errors
|
|
||||||
[includes]: usage.md#including-other-taskfiles
|
|
||||||
|
|
||||||
## Version 3
|
|
||||||
|
|
||||||
These are some major changes done on `v3`:
|
|
||||||
|
|
||||||
- Task's output will now be colored
|
|
||||||
- Added support for `.env` like files
|
|
||||||
- Added `label:` setting to task so one can override how the task name
|
|
||||||
appear in the logs
|
|
||||||
- A global `method:` was added to allow setting the default method,
|
|
||||||
and Task's default changed to `checksum`
|
|
||||||
- Two magic variables were added when using `status:`: `CHECKSUM` and
|
|
||||||
`TIMESTAMP` which contains, respectively, the md5 checksum and greatest
|
|
||||||
modification timestamp of the files listed on `sources:`
|
|
||||||
- Also, the `TASK` variable is always available with the current task name
|
|
||||||
- CLI variables are always treated as global variables
|
|
||||||
- Added `dir:` option to `includes` to allow choosing on which directory an
|
|
||||||
included Taskfile will run:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
includes:
|
|
||||||
docs:
|
|
||||||
taskfile: ./docs
|
|
||||||
dir: ./docs
|
|
||||||
```
|
|
||||||
|
|
||||||
- Implemented short task syntax. All below syntaxes are equivalent:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
print:
|
|
||||||
cmds:
|
|
||||||
- echo "Hello, World!"
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
print:
|
|
||||||
- echo "Hello, World!"
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
print: echo "Hello, World!"
|
|
||||||
```
|
|
||||||
|
|
||||||
- There was a major refactor on how variables are handled. They're now easier
|
|
||||||
to understand. The `expansions:` setting was removed as it became unncessary.
|
|
||||||
This is the order in which Task will process variables, each level can see
|
|
||||||
the variables set by the previous one and override those.
|
|
||||||
- Environment variables
|
|
||||||
- Global + CLI variables
|
|
||||||
- Call variables
|
|
||||||
- Task variables
|
|
||||||
909
docs/usage.md
909
docs/usage.md
@@ -1,909 +0,0 @@
|
|||||||
# Usage
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
Create a file called `Taskfile.yml` in the root of your project.
|
|
||||||
The `cmds` attribute should contain the commands of a task.
|
|
||||||
The example below allows compiling a Go app and uses [Minify][minify] to concat
|
|
||||||
and minify multiple CSS files into a single one.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
cmds:
|
|
||||||
- go build -v -i main.go
|
|
||||||
|
|
||||||
assets:
|
|
||||||
cmds:
|
|
||||||
- minify -o public/style.css src/css
|
|
||||||
```
|
|
||||||
|
|
||||||
Running the tasks is as simple as running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
task assets build
|
|
||||||
```
|
|
||||||
|
|
||||||
Task uses [github.com/mvdan/sh](https://github.com/mvdan/sh), a native Go sh
|
|
||||||
interpreter. So you can write sh/bash commands and it will work even on
|
|
||||||
Windows, where `sh` or `bash` are usually not available. Just remember any
|
|
||||||
executable called must be available by the OS or in PATH.
|
|
||||||
|
|
||||||
If you omit a task name, "default" will be assumed.
|
|
||||||
|
|
||||||
## Environment variables
|
|
||||||
|
|
||||||
### Task
|
|
||||||
|
|
||||||
You can use `env` to set custom environment variables for a specific task:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
greet:
|
|
||||||
cmds:
|
|
||||||
- echo $GREETING
|
|
||||||
env:
|
|
||||||
GREETING: Hey, there!
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, you can set globally environment variables, that'll be available
|
|
||||||
to all tasks:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
env:
|
|
||||||
GREETING: Hey, there!
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
greet:
|
|
||||||
cmds:
|
|
||||||
- echo $GREETING
|
|
||||||
```
|
|
||||||
|
|
||||||
> NOTE: `env` supports expansion and retrieving output from a shell command
|
|
||||||
> just like variables, as you can see on the [Variables](#variables) section.
|
|
||||||
|
|
||||||
### .env files
|
|
||||||
|
|
||||||
You can also ask Task to include `.env` like files by using the `dotenv:`
|
|
||||||
setting:
|
|
||||||
|
|
||||||
```
|
|
||||||
# .env
|
|
||||||
KEYNAME=VALUE
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Taskfile.yml
|
|
||||||
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
dotenv: ['.env']
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
greet:
|
|
||||||
cmds:
|
|
||||||
- echo "Using $KEYNAME"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Including other Taskfiles
|
|
||||||
|
|
||||||
If you want to share tasks between different projects (Taskfiles), you can use
|
|
||||||
the importing mechanism to include other Taskfiles using the `includes` keyword:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
docs: ./documentation # will look for ./documentation/Taskfile.yml
|
|
||||||
docker: ./DockerTasks.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
The tasks described in the given Taskfiles will be available with the informed
|
|
||||||
namespace. So, you'd call `task docs:serve` to run the `serve` task from
|
|
||||||
`documentation/Taskfile.yml` or `task docker:build` to run the `build` task
|
|
||||||
from the `DockerTasks.yml` file.
|
|
||||||
|
|
||||||
### OS-specific Taskfiles
|
|
||||||
|
|
||||||
With `version: '2'`, task automatically includes any `Taskfile_{{OS}}.yml`
|
|
||||||
if it exists (for example: `Taskfile_windows.yml`, `Taskfile_linux.yml` or
|
|
||||||
`Taskfile_darwin.yml`). Since this behavior was a bit too implicit, it
|
|
||||||
was removed on version 3, but you still can have a similar behavior by
|
|
||||||
explicitly importing these files:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
build: ./Taskfile_{{OS}}.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Directory of included Taskfile
|
|
||||||
|
|
||||||
By default, included Taskfile's tasks are ran in the current directory, even
|
|
||||||
if the Taskfile is in another directory, but you can force its tasks to run
|
|
||||||
in another directory by using this alternative syntax:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
docs:
|
|
||||||
taskfile: ./docs/Taskfile.yml
|
|
||||||
dir: ./docs
|
|
||||||
```
|
|
||||||
|
|
||||||
> The included Taskfiles must be using the same schema version the main
|
|
||||||
> Taskfile uses.
|
|
||||||
|
|
||||||
> Also, for now included Taskfiles can't include other Taskfiles.
|
|
||||||
> This was a deliberate decision to keep use and implementation simple.
|
|
||||||
> If you disagree, open an GitHub issue and explain your use case. =)
|
|
||||||
|
|
||||||
## Task directory
|
|
||||||
|
|
||||||
By default, tasks will be executed in the directory where the Taskfile is
|
|
||||||
located. But you can easily make the task run in another folder informing
|
|
||||||
`dir`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
serve:
|
|
||||||
dir: public/www
|
|
||||||
cmds:
|
|
||||||
# run http server
|
|
||||||
- caddy
|
|
||||||
```
|
|
||||||
|
|
||||||
If the directory doesn't exist, `task` creates it.
|
|
||||||
|
|
||||||
## Task dependencies
|
|
||||||
|
|
||||||
> Dependencies run in parallel, so dependencies of a task shouldn't depend one
|
|
||||||
> another. If you want to force tasks to run serially take a look at the
|
|
||||||
> [Calling Another Task](#calling-another-task) section below.
|
|
||||||
|
|
||||||
You may have tasks that depend on others. Just pointing them on `deps` will
|
|
||||||
make them run automatically before running the parent task:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
deps: [assets]
|
|
||||||
cmds:
|
|
||||||
- go build -v -i main.go
|
|
||||||
|
|
||||||
assets:
|
|
||||||
cmds:
|
|
||||||
- minify -o public/style.css src/css
|
|
||||||
```
|
|
||||||
|
|
||||||
In the above example, `assets` will always run right before `build` if you run
|
|
||||||
`task build`.
|
|
||||||
|
|
||||||
A task can have only dependencies and no commands to group tasks together:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
assets:
|
|
||||||
deps: [js, css]
|
|
||||||
|
|
||||||
js:
|
|
||||||
cmds:
|
|
||||||
- minify -o public/script.js src/js
|
|
||||||
|
|
||||||
css:
|
|
||||||
cmds:
|
|
||||||
- minify -o public/style.css src/css
|
|
||||||
```
|
|
||||||
|
|
||||||
If there is more than one dependency, they always run in parallel for better
|
|
||||||
performance.
|
|
||||||
|
|
||||||
> You can also make the tasks given by the command line run in parallel by
|
|
||||||
> using the `--parallel` flag (alias `-p`). Example: `task --parallel js css`.
|
|
||||||
|
|
||||||
If you want to pass information to dependencies, you can do that the same
|
|
||||||
manner as you would to [call another task](#calling-another-task):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
deps:
|
|
||||||
- task: echo_sth
|
|
||||||
vars: {TEXT: "before 1"}
|
|
||||||
- task: echo_sth
|
|
||||||
vars: {TEXT: "before 2"}
|
|
||||||
cmds:
|
|
||||||
- echo "after"
|
|
||||||
|
|
||||||
echo_sth:
|
|
||||||
cmds:
|
|
||||||
- echo {{.TEXT}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Calling another task
|
|
||||||
|
|
||||||
When a task has many dependencies, they are executed concurrently. This will
|
|
||||||
often result in a faster build pipeline. But in some situations you may need
|
|
||||||
to call other tasks serially. In this case, just use the following syntax:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
main-task:
|
|
||||||
cmds:
|
|
||||||
- task: task-to-be-called
|
|
||||||
- task: another-task
|
|
||||||
- echo "Both done"
|
|
||||||
|
|
||||||
task-to-be-called:
|
|
||||||
cmds:
|
|
||||||
- echo "Task to be called"
|
|
||||||
|
|
||||||
another-task:
|
|
||||||
cmds:
|
|
||||||
- echo "Another task"
|
|
||||||
```
|
|
||||||
|
|
||||||
Overriding variables in the called task is as simple as informing `vars`
|
|
||||||
attribute:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
greet:
|
|
||||||
vars:
|
|
||||||
RECIPIENT: '{{default "World" .RECIPIENT}}'
|
|
||||||
cmds:
|
|
||||||
- echo "Hello, {{.RECIPIENT}}!"
|
|
||||||
|
|
||||||
greet-pessimistically:
|
|
||||||
cmds:
|
|
||||||
- task: greet
|
|
||||||
vars: {RECIPIENT: "Cruel World"}
|
|
||||||
```
|
|
||||||
|
|
||||||
The above syntax is also supported in `deps`.
|
|
||||||
|
|
||||||
> NOTE: If you want to call a task declared in the root Taskfile from within an
|
|
||||||
> [included Taskfile](#including-other-taskfiles), add a leading `:` like this:
|
|
||||||
> `task: :task-name`.
|
|
||||||
|
|
||||||
## Prevent unnecessary work
|
|
||||||
|
|
||||||
### By fingerprinting locally generated files and their sources
|
|
||||||
|
|
||||||
If a task generates something, you can inform Task the source and generated
|
|
||||||
files, so Task will prevent to run them if not necessary.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
deps: [js, css]
|
|
||||||
cmds:
|
|
||||||
- go build -v -i main.go
|
|
||||||
|
|
||||||
js:
|
|
||||||
cmds:
|
|
||||||
- minify -o public/script.js src/js
|
|
||||||
sources:
|
|
||||||
- src/js/**/*.js
|
|
||||||
generates:
|
|
||||||
- public/script.js
|
|
||||||
|
|
||||||
css:
|
|
||||||
cmds:
|
|
||||||
- minify -o public/style.css src/css
|
|
||||||
sources:
|
|
||||||
- src/css/**/*.css
|
|
||||||
generates:
|
|
||||||
- public/style.css
|
|
||||||
```
|
|
||||||
|
|
||||||
`sources` and `generates` can be files or file patterns. When given,
|
|
||||||
Task will compare the checksum of the source files to determine if it's
|
|
||||||
necessary to run the task. If not, it will just print a message like
|
|
||||||
`Task "js" is up to date`.
|
|
||||||
You will probably want to ignore the `.task` folder in your `.gitignore` file
|
|
||||||
(It's there that Task stores the last checksum).
|
|
||||||
|
|
||||||
If you prefer this check to be made by the modification timestamp of the files,
|
|
||||||
instead of its checksum (content), just set the `method` property to `timestamp`.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
cmds:
|
|
||||||
- go build .
|
|
||||||
sources:
|
|
||||||
- ./*.go
|
|
||||||
generates:
|
|
||||||
- app{{exeExt}}
|
|
||||||
method: checksum
|
|
||||||
```
|
|
||||||
|
|
||||||
> TIP: method `none` skips any validation and always run the task.
|
|
||||||
|
|
||||||
> NOTE: for the `checksum` (default) method to work, it's only necessary to
|
|
||||||
> inform the source files, but if you want to use the `timestamp` method, you
|
|
||||||
> also need to inform the generated files with `generates`.
|
|
||||||
|
|
||||||
### Using programmatic checks to indicate a task is up to date.
|
|
||||||
|
|
||||||
Alternatively, you can inform a sequence of tests as `status`. If no error
|
|
||||||
is returned (exit status 0), the task is considered up-to-date:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
generate-files:
|
|
||||||
cmds:
|
|
||||||
- mkdir directory
|
|
||||||
- touch directory/file1.txt
|
|
||||||
- touch directory/file2.txt
|
|
||||||
# test existence of files
|
|
||||||
status:
|
|
||||||
- test -d directory
|
|
||||||
- test -f directory/file1.txt
|
|
||||||
- test -f directory/file2.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Normally, you would use `sources` in combination with
|
|
||||||
`generates` - but for tasks that generate remote artifacts (Docker images,
|
|
||||||
deploys, CD releases) the checksum source and timestamps require either
|
|
||||||
access to the artifact or for an out-of-band refresh of the `.checksum`
|
|
||||||
fingerprint file.
|
|
||||||
|
|
||||||
Two special variables `{{.CHECKSUM}}` and `{{.TIMESTAMP}}` are available
|
|
||||||
for interpolation within `status` commands, depending on the method assigned
|
|
||||||
to fingerprint the sources. Only `source` globs are fingerprinted.
|
|
||||||
|
|
||||||
Note that the `{{.TIMESTAMP}}` variable is a "live" Go `time.Time` struct, and
|
|
||||||
can be formatted using any of the methods that `time.Time` responds to.
|
|
||||||
|
|
||||||
See [the Go Time documentation](https://golang.org/pkg/time/) for more information.
|
|
||||||
|
|
||||||
You can use `--force` or `-f` if you want to force a task to run even when
|
|
||||||
up-to-date.
|
|
||||||
|
|
||||||
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
|
||||||
the tasks are not up-to-date.
|
|
||||||
|
|
||||||
### Using programmatic checks to cancel execution of an task and it's dependencies
|
|
||||||
|
|
||||||
In addition to `status` checks, there are also `preconditions` checks, which are
|
|
||||||
the logical inverse of `status` checks. That is, if you need a certain set of
|
|
||||||
conditions to be _true_ you can use the `preconditions` stanza.
|
|
||||||
`preconditions` are similar to `status` lines except they support `sh`
|
|
||||||
expansion and they SHOULD all return 0.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
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: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
task-will-fail:
|
|
||||||
preconditions:
|
|
||||||
- sh: "exit 1"
|
|
||||||
|
|
||||||
task-will-also-fail:
|
|
||||||
deps:
|
|
||||||
- task-will-fail
|
|
||||||
|
|
||||||
task-will-still-fail:
|
|
||||||
cmds:
|
|
||||||
- task: task-will-fail
|
|
||||||
- echo "I will not run"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Variables
|
|
||||||
|
|
||||||
When doing interpolation of variables, Task will look for the below.
|
|
||||||
They are listed below in order of importance (e.g. most important first):
|
|
||||||
|
|
||||||
- Variables declared in the task definition
|
|
||||||
- Variables given while calling a task from another
|
|
||||||
(See [Calling another task](#calling-another-task) above)
|
|
||||||
- Global variables (those declared in the `vars:` option in the Taskfile)
|
|
||||||
- Environment variables
|
|
||||||
|
|
||||||
Example of sending parameters with environment variables:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ TASK_VARIABLE=a-value task do-something
|
|
||||||
```
|
|
||||||
|
|
||||||
> TIP: A special variable `.TASK` is always available containing the task name.
|
|
||||||
|
|
||||||
Since some shells don't support above syntax to set environment variables
|
|
||||||
(Windows) tasks also accepts a similar style when not in the beginning of
|
|
||||||
the command.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
|
||||||
```
|
|
||||||
|
|
||||||
Example of locally declared vars:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
print-var:
|
|
||||||
cmds:
|
|
||||||
- echo "{{.VAR}}"
|
|
||||||
vars:
|
|
||||||
VAR: Hello!
|
|
||||||
```
|
|
||||||
|
|
||||||
Example of global vars in a `Taskfile.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
vars:
|
|
||||||
GREETING: Hello from Taskfile!
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
greet:
|
|
||||||
cmds:
|
|
||||||
- echo "{{.GREETING}}"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dynamic variables
|
|
||||||
|
|
||||||
The below syntax (`sh:` prop in a variable) is considered a dynamic variable.
|
|
||||||
The value will be treated as a command and the output assigned. If there is one
|
|
||||||
or more trailing newlines, the last newline will be trimmed.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
cmds:
|
|
||||||
- go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go
|
|
||||||
vars:
|
|
||||||
GIT_COMMIT:
|
|
||||||
sh: git log -n 1 --format=%h
|
|
||||||
```
|
|
||||||
|
|
||||||
This works for all types of variables.
|
|
||||||
|
|
||||||
## Forwarding CLI arguments to commands
|
|
||||||
|
|
||||||
If `--` is given in the CLI, all following paramaters are added to a
|
|
||||||
special `.CLI_ARGS` variable. This is useful to forward arguments to another
|
|
||||||
command.
|
|
||||||
|
|
||||||
The below example will run `yarn install`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ task yarn -- install
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
yarn:
|
|
||||||
cmds:
|
|
||||||
- yarn {{.CLI_ARGS}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Go's template engine
|
|
||||||
|
|
||||||
Task parse commands as [Go's template engine][gotemplate] before executing
|
|
||||||
them. Variables are accessible through dot syntax (`.VARNAME`).
|
|
||||||
|
|
||||||
All functions by the Go's [slim-sprig lib](https://go-task.github.io/slim-sprig/)
|
|
||||||
are available. The following example gets the current date in a given format:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
print-date:
|
|
||||||
cmds:
|
|
||||||
- echo {{now | date "2006-01-02"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
Task also adds the following functions:
|
|
||||||
|
|
||||||
- `OS`: Returns operating system. Possible values are "windows", "linux",
|
|
||||||
"darwin" (macOS) and "freebsd".
|
|
||||||
- `ARCH`: return the architecture Task was compiled to: "386", "amd64", "arm"
|
|
||||||
or "s390x".
|
|
||||||
- `splitLines`: Splits Unix (\n) and Windows (\r\n) styled newlines.
|
|
||||||
- `catLines`: Replaces Unix (\n) and Windows (\r\n) styled newlines with a space.
|
|
||||||
- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\`
|
|
||||||
path format to `/`.
|
|
||||||
- `fromSlash`: Opposite of `toSlash`. Does nothing on Unix, but on Windows
|
|
||||||
converts a string from `\` path format to `/`.
|
|
||||||
- `exeExt`: Returns the right executable extension for the current OS
|
|
||||||
(`".exe"` for Windows, `""` for others).
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
print-os:
|
|
||||||
cmds:
|
|
||||||
- echo '{{OS}} {{ARCH}}'
|
|
||||||
- echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}'
|
|
||||||
# This will be path/to/file on Unix but path\to\file on Windows
|
|
||||||
- echo '{{fromSlash "path/to/file"}}'
|
|
||||||
enumerated-file:
|
|
||||||
vars:
|
|
||||||
CONTENT: |
|
|
||||||
foo
|
|
||||||
bar
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
cat << EOF > output.txt
|
|
||||||
{{range $i, $line := .CONTENT | splitLines -}}
|
|
||||||
{{printf "%3d" $i}}: {{$line}}
|
|
||||||
{{end}}EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
## Help
|
|
||||||
|
|
||||||
Running `task --list` (or `task -l`) lists all tasks with a description.
|
|
||||||
The following Taskfile:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
desc: Build the go binary.
|
|
||||||
cmds:
|
|
||||||
- go build -v -i main.go
|
|
||||||
|
|
||||||
test:
|
|
||||||
desc: Run all the go tests.
|
|
||||||
cmds:
|
|
||||||
- go test -race ./...
|
|
||||||
|
|
||||||
js:
|
|
||||||
cmds:
|
|
||||||
- minify -o public/script.js src/js
|
|
||||||
|
|
||||||
css:
|
|
||||||
cmds:
|
|
||||||
- minify -o public/style.css src/css
|
|
||||||
```
|
|
||||||
|
|
||||||
would print the following output:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
* build: Build the go binary.
|
|
||||||
* test: Run all the go tests.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Display summary of task
|
|
||||||
|
|
||||||
Running `task --summary task-name` will show a summary of a task.
|
|
||||||
The following Taskfile:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
release:
|
|
||||||
deps: [build]
|
|
||||||
summary: |
|
|
||||||
Release your project to github
|
|
||||||
|
|
||||||
It will build your project before starting the release.
|
|
||||||
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.
|
|
||||||
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*.
|
|
||||||
|
|
||||||
## Overriding task name
|
|
||||||
|
|
||||||
Sometimes you may want to override the task name print on summary, up-to-date
|
|
||||||
messages to STDOUT, etc. In this case you can just set `label:`, which can also
|
|
||||||
be interpolated with variables:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
- task: print
|
|
||||||
vars:
|
|
||||||
MESSAGE: hello
|
|
||||||
- task: print
|
|
||||||
vars:
|
|
||||||
MESSAGE: world
|
|
||||||
|
|
||||||
print:
|
|
||||||
label: 'print-{{.MESSAGE}}'
|
|
||||||
cmds:
|
|
||||||
- echo "{{.MESSAGE}}"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Silent mode
|
|
||||||
|
|
||||||
Silent mode disables echoing of commands before Task runs it.
|
|
||||||
For the following Taskfile:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- echo "Print something"
|
|
||||||
```
|
|
||||||
|
|
||||||
Normally this will be print:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
echo "Print something"
|
|
||||||
Print something
|
|
||||||
```
|
|
||||||
|
|
||||||
With silent mode on, the below will be print instead:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
Print something
|
|
||||||
```
|
|
||||||
|
|
||||||
There are four ways to enable silent mode:
|
|
||||||
|
|
||||||
* At command level:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- cmd: echo "Print something"
|
|
||||||
silent: true
|
|
||||||
```
|
|
||||||
|
|
||||||
* At task level:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- echo "Print something"
|
|
||||||
silent: true
|
|
||||||
```
|
|
||||||
|
|
||||||
* Globally at Taskfile level:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
silent: true
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- echo "Print something"
|
|
||||||
```
|
|
||||||
|
|
||||||
* Or globally with `--silent` or `-s` flag
|
|
||||||
|
|
||||||
If you want to suppress STDOUT instead, just redirect a command to `/dev/null`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- echo "This will print nothing" > /dev/null
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dry run mode
|
|
||||||
|
|
||||||
Dry run mode (`--dry`) compiles and steps through each task, printing the commands
|
|
||||||
that would be run without executing them. This is useful for debugging your Taskfiles.
|
|
||||||
|
|
||||||
## Ignore errors
|
|
||||||
|
|
||||||
You have the option to ignore errors during command execution.
|
|
||||||
Given the following Taskfile:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- exit 1
|
|
||||||
- echo "Hello World"
|
|
||||||
```
|
|
||||||
|
|
||||||
Task will abort the execution after running `exit 1` because the status code `1` stands for `EXIT_FAILURE`.
|
|
||||||
However it is possible to continue with execution using `ignore_error`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
echo:
|
|
||||||
cmds:
|
|
||||||
- cmd: exit 1
|
|
||||||
ignore_error: true
|
|
||||||
- echo "Hello World"
|
|
||||||
```
|
|
||||||
|
|
||||||
`ignore_error` can also be set for a task, which mean errors will be suppressed
|
|
||||||
for all commands. But keep in mind this option won't propagate to other tasks
|
|
||||||
called either by `deps` or `cmds`!
|
|
||||||
|
|
||||||
## Output syntax
|
|
||||||
|
|
||||||
By default, Task just redirect the STDOUT and STDERR of the running commands
|
|
||||||
to the shell in real time. This is good for having live feedback for log
|
|
||||||
printed by commands, but the output can become messy if you have multiple
|
|
||||||
commands running at the same time and printing lots of stuff.
|
|
||||||
|
|
||||||
To make this more customizable, there are currently three different output
|
|
||||||
options you can choose:
|
|
||||||
|
|
||||||
- `interleaved` (default)
|
|
||||||
- `group`
|
|
||||||
- `prefixed`
|
|
||||||
|
|
||||||
To choose another one, just set it to root in the Taskfile:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
output: 'group'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
The `group` output will print the entire output of a command once, after it
|
|
||||||
finishes, so you won't have live feedback for commands that take a long time
|
|
||||||
to run.
|
|
||||||
|
|
||||||
The `prefix` output will prefix every line printed by a command with
|
|
||||||
`[task-name] ` as the prefix, but you can customize the prefix for a command
|
|
||||||
with the `prefix:` attribute:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
output: prefixed
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
deps:
|
|
||||||
- task: print
|
|
||||||
vars: {TEXT: foo}
|
|
||||||
- task: print
|
|
||||||
vars: {TEXT: bar}
|
|
||||||
- task: print
|
|
||||||
vars: {TEXT: baz}
|
|
||||||
|
|
||||||
print:
|
|
||||||
cmds:
|
|
||||||
- echo "{{.TEXT}}"
|
|
||||||
prefix: "print-{{.TEXT}}"
|
|
||||||
silent: true
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ task default
|
|
||||||
[print-foo] foo
|
|
||||||
[print-bar] bar
|
|
||||||
[print-baz] baz
|
|
||||||
```
|
|
||||||
|
|
||||||
> The `output` option can also be specified by the `--output` or `-o` flags.
|
|
||||||
|
|
||||||
## Short task syntax
|
|
||||||
|
|
||||||
Starting on Task v3, you can now write tasks with a shorter syntax if they
|
|
||||||
have the default settings (e.g. no custom `env:`, `vars:`, `desc:`, `silent:` , etc):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build: go build -v -o ./app{{exeExt}} .
|
|
||||||
|
|
||||||
run:
|
|
||||||
- task: build
|
|
||||||
- ./app{{exeExt}} -h localhost -p 8080
|
|
||||||
```
|
|
||||||
|
|
||||||
## Watch tasks
|
|
||||||
|
|
||||||
With the flags `--watch` or `-w` task will watch for file changes
|
|
||||||
and run the task again. This requires the `sources` attribute to be given,
|
|
||||||
so task knows which files to watch.
|
|
||||||
|
|
||||||
[gotemplate]: https://golang.org/pkg/text/template/
|
|
||||||
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify
|
|
||||||
24
errors.go
24
errors.go
@@ -10,6 +10,14 @@ var (
|
|||||||
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
|
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type taskFileNotFound struct {
|
||||||
|
taskFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err taskFileNotFound) Error() string {
|
||||||
|
return fmt.Sprintf(`task: No task file found (is it named "%s"?). Use "task --init" to create a new one`, err.taskFile)
|
||||||
|
}
|
||||||
|
|
||||||
type taskNotFoundError struct {
|
type taskNotFoundError struct {
|
||||||
taskName string
|
taskName string
|
||||||
}
|
}
|
||||||
@@ -27,6 +35,22 @@ func (err *taskRunError) Error() string {
|
|||||||
return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
|
return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cyclicDepError struct {
|
||||||
|
taskName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *cyclicDepError) Error() string {
|
||||||
|
return fmt.Sprintf(`task: Cyclic dependency of task "%s" detected`, err.taskName)
|
||||||
|
}
|
||||||
|
|
||||||
|
type cantWatchNoSourcesError struct {
|
||||||
|
taskName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *cantWatchNoSourcesError) Error() string {
|
||||||
|
return fmt.Sprintf(`task: Can't watch task "%s" because it has no specified sources`, err.taskName)
|
||||||
|
}
|
||||||
|
|
||||||
// MaximumTaskCallExceededError is returned when a task is called too
|
// MaximumTaskCallExceededError is returned when a task is called too
|
||||||
// many times. In this case you probably have a cyclic dependendy or
|
// many times. In this case you probably have a cyclic dependendy or
|
||||||
// infinite loop
|
// infinite loop
|
||||||
|
|||||||
17
go.mod
17
go.mod
@@ -1,17 +0,0 @@
|
|||||||
module github.com/go-task/task/v3
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/fatih/color v1.7.0
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0
|
|
||||||
github.com/joho/godotenv v1.3.0
|
|
||||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
|
||||||
github.com/mattn/go-zglob v0.0.1
|
|
||||||
github.com/radovskyb/watcher v1.0.5
|
|
||||||
github.com/spf13/pflag v1.0.5
|
|
||||||
github.com/stretchr/testify v1.5.1
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
|
||||||
mvdan.cc/sh/v3 v3.2.4
|
|
||||||
)
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
59
go.sum
59
go.sum
@@ -1,59 +0,0 @@
|
|||||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
|
||||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
|
|
||||||
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
|
||||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/radovskyb/watcher v1.0.5 h1:wqt7gb+HjGacvFoLTKeT44C+XVPxu7bvHvKT1IvZ7rw=
|
|
||||||
github.com/radovskyb/watcher v1.0.5/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
|
||||||
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
|
|
||||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407 h1:5zh5atpUEdIc478E/ebrIaHLKcfVvG6dL/fGv7BcMoM=
|
|
||||||
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
mvdan.cc/editorconfig v0.1.1-0.20200121172147-e40951bde157/go.mod h1:Ge4atmRUYqueGppvJ7JNrtqpqokoJEFxYbP0Z+WeKS8=
|
|
||||||
mvdan.cc/sh/v3 v3.2.4 h1:+fZaWcXWRjYAvqzEKoDhDM3DkxdDUykU2iw0VMKFe9s=
|
|
||||||
mvdan.cc/sh/v3 v3.2.4/go.mod h1:fPQmabBpREM/XQ9YXSU5ZFZ/Sm+PmKP9/vkFHgYKJEI=
|
|
||||||
13
help.go
13
help.go
@@ -5,23 +5,22 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrintTasksHelp prints help os tasks that have a description
|
// PrintTasksHelp prints help os tasks that have a description
|
||||||
func (e *Executor) PrintTasksHelp() {
|
func (e *Executor) PrintTasksHelp() {
|
||||||
tasks := e.tasksWithDesc()
|
tasks := e.tasksWithDesc()
|
||||||
if len(tasks) == 0 {
|
if len(tasks) == 0 {
|
||||||
e.Logger.Outf(logger.Yellow, "task: No tasks with description available")
|
e.Logger.Outf("task: No tasks with description available")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
|
e.Logger.Outf("task: Available tasks for this project:")
|
||||||
|
|
||||||
// Format in tab-separated columns with a tab stop of 8.
|
// Format in tab-separated columns with a tab stop of 8.
|
||||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
|
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
fmt.Fprintf(w, "* %s: \t%s\n", task.Name(), task.Desc)
|
fmt.Fprintf(w, "* %s: \t%s\n", task.Task, task.Desc)
|
||||||
}
|
}
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
@@ -30,10 +29,6 @@ func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
|
|||||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||||
for _, task := range e.Taskfile.Tasks {
|
for _, task := range e.Taskfile.Tasks {
|
||||||
if task.Desc != "" {
|
if task.Desc != "" {
|
||||||
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
|
|
||||||
if err == nil {
|
|
||||||
task = compiledTask
|
|
||||||
}
|
|
||||||
tasks = append(tasks, task)
|
tasks = append(tasks, task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
init.go
4
init.go
@@ -8,9 +8,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultTaskfile = `# https://taskfile.dev
|
const defaultTaskfile = `# github.com/go-task/task
|
||||||
|
|
||||||
version: '3'
|
version: '2'
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
GREETING: Hello, World!
|
GREETING: Hello, World!
|
||||||
|
|||||||
383
install-task.sh
383
install-task.sh
@@ -1,383 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
# Code generated by godownloader on 2021-01-12T13:40:40Z. DO NOT EDIT.
|
|
||||||
#
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
this=$1
|
|
||||||
cat <<EOF
|
|
||||||
$this: download go binaries for go-task/task
|
|
||||||
|
|
||||||
Usage: $this [-b] bindir [-d] [tag]
|
|
||||||
-b sets bindir or installation directory, Defaults to ./bin
|
|
||||||
-d turns on debug logging
|
|
||||||
[tag] is a tag from
|
|
||||||
https://github.com/go-task/task/releases
|
|
||||||
If tag is missing, then the latest will be used.
|
|
||||||
|
|
||||||
Generated by godownloader
|
|
||||||
https://github.com/goreleaser/godownloader
|
|
||||||
|
|
||||||
EOF
|
|
||||||
exit 2
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_args() {
|
|
||||||
#BINDIR is ./bin unless set be ENV
|
|
||||||
# over-ridden by flag below
|
|
||||||
|
|
||||||
BINDIR=${BINDIR:-./bin}
|
|
||||||
while getopts "b:dh?x" arg; do
|
|
||||||
case "$arg" in
|
|
||||||
b) BINDIR="$OPTARG" ;;
|
|
||||||
d) log_set_priority 10 ;;
|
|
||||||
h | \?) usage "$0" ;;
|
|
||||||
x) set -x ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
shift $((OPTIND - 1))
|
|
||||||
TAG=$1
|
|
||||||
}
|
|
||||||
# this function wraps all the destructive operations
|
|
||||||
# if a curl|bash cuts off the end of the script due to
|
|
||||||
# network, either nothing will happen or will syntax error
|
|
||||||
# out preventing half-done work
|
|
||||||
execute() {
|
|
||||||
tmpdir=$(mktemp -d)
|
|
||||||
log_debug "downloading files into ${tmpdir}"
|
|
||||||
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
|
||||||
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
|
||||||
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
|
||||||
srcdir="${tmpdir}"
|
|
||||||
(cd "${tmpdir}" && untar "${TARBALL}")
|
|
||||||
test ! -d "${BINDIR}" && install -d "${BINDIR}"
|
|
||||||
for binexe in $BINARIES; do
|
|
||||||
if [ "$OS" = "windows" ]; then
|
|
||||||
binexe="${binexe}.exe"
|
|
||||||
fi
|
|
||||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
|
||||||
log_info "installed ${BINDIR}/${binexe}"
|
|
||||||
done
|
|
||||||
rm -rf "${tmpdir}"
|
|
||||||
}
|
|
||||||
get_binaries() {
|
|
||||||
case "$PLATFORM" in
|
|
||||||
darwin/amd64) BINARIES="task" ;;
|
|
||||||
darwin/arm64) BINARIES="task" ;;
|
|
||||||
darwin/armv6) BINARIES="task" ;;
|
|
||||||
linux/386) BINARIES="task" ;;
|
|
||||||
linux/amd64) BINARIES="task" ;;
|
|
||||||
linux/arm64) BINARIES="task" ;;
|
|
||||||
linux/armv6) BINARIES="task" ;;
|
|
||||||
windows/386) BINARIES="task" ;;
|
|
||||||
windows/amd64) BINARIES="task" ;;
|
|
||||||
windows/arm64) BINARIES="task" ;;
|
|
||||||
windows/armv6) BINARIES="task" ;;
|
|
||||||
*)
|
|
||||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
tag_to_version() {
|
|
||||||
if [ -z "${TAG}" ]; then
|
|
||||||
log_info "checking GitHub for latest tag"
|
|
||||||
else
|
|
||||||
log_info "checking GitHub for tag '${TAG}'"
|
|
||||||
fi
|
|
||||||
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
|
|
||||||
if test -z "$REALTAG"; then
|
|
||||||
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# if version starts with 'v', remove it
|
|
||||||
TAG="$REALTAG"
|
|
||||||
VERSION=${TAG#v}
|
|
||||||
}
|
|
||||||
adjust_format() {
|
|
||||||
# change format (tar.gz or zip) based on OS
|
|
||||||
case ${OS} in
|
|
||||||
windows) FORMAT=zip ;;
|
|
||||||
esac
|
|
||||||
true
|
|
||||||
}
|
|
||||||
adjust_os() {
|
|
||||||
# adjust archive name based on OS
|
|
||||||
true
|
|
||||||
}
|
|
||||||
adjust_arch() {
|
|
||||||
# adjust archive name based on ARCH
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
cat /dev/null <<EOF
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
https://github.com/client9/shlib - portable posix shell functions
|
|
||||||
Public domain - http://unlicense.org
|
|
||||||
https://github.com/client9/shlib/blob/master/LICENSE.md
|
|
||||||
but credit (and pull requests) appreciated.
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
EOF
|
|
||||||
is_command() {
|
|
||||||
command -v "$1" >/dev/null
|
|
||||||
}
|
|
||||||
echoerr() {
|
|
||||||
echo "$@" 1>&2
|
|
||||||
}
|
|
||||||
log_prefix() {
|
|
||||||
echo "$0"
|
|
||||||
}
|
|
||||||
_logp=6
|
|
||||||
log_set_priority() {
|
|
||||||
_logp="$1"
|
|
||||||
}
|
|
||||||
log_priority() {
|
|
||||||
if test -z "$1"; then
|
|
||||||
echo "$_logp"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
[ "$1" -le "$_logp" ]
|
|
||||||
}
|
|
||||||
log_tag() {
|
|
||||||
case $1 in
|
|
||||||
0) echo "emerg" ;;
|
|
||||||
1) echo "alert" ;;
|
|
||||||
2) echo "crit" ;;
|
|
||||||
3) echo "err" ;;
|
|
||||||
4) echo "warning" ;;
|
|
||||||
5) echo "notice" ;;
|
|
||||||
6) echo "info" ;;
|
|
||||||
7) echo "debug" ;;
|
|
||||||
*) echo "$1" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
log_debug() {
|
|
||||||
log_priority 7 || return 0
|
|
||||||
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
|
|
||||||
}
|
|
||||||
log_info() {
|
|
||||||
log_priority 6 || return 0
|
|
||||||
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
|
|
||||||
}
|
|
||||||
log_err() {
|
|
||||||
log_priority 3 || return 0
|
|
||||||
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
|
|
||||||
}
|
|
||||||
log_crit() {
|
|
||||||
log_priority 2 || return 0
|
|
||||||
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
|
|
||||||
}
|
|
||||||
uname_os() {
|
|
||||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
||||||
case "$os" in
|
|
||||||
cygwin_nt*) os="windows" ;;
|
|
||||||
mingw*) os="windows" ;;
|
|
||||||
msys_nt*) os="windows" ;;
|
|
||||||
esac
|
|
||||||
echo "$os"
|
|
||||||
}
|
|
||||||
uname_arch() {
|
|
||||||
arch=$(uname -m)
|
|
||||||
case $arch in
|
|
||||||
x86_64) arch="amd64" ;;
|
|
||||||
x86) arch="386" ;;
|
|
||||||
i686) arch="386" ;;
|
|
||||||
i386) arch="386" ;;
|
|
||||||
aarch64) arch="arm64" ;;
|
|
||||||
armv5*) arch="armv5" ;;
|
|
||||||
armv6*) arch="armv6" ;;
|
|
||||||
armv7*) arch="armv7" ;;
|
|
||||||
esac
|
|
||||||
echo ${arch}
|
|
||||||
}
|
|
||||||
uname_os_check() {
|
|
||||||
os=$(uname_os)
|
|
||||||
case "$os" in
|
|
||||||
darwin) return 0 ;;
|
|
||||||
dragonfly) return 0 ;;
|
|
||||||
freebsd) return 0 ;;
|
|
||||||
linux) return 0 ;;
|
|
||||||
android) return 0 ;;
|
|
||||||
nacl) return 0 ;;
|
|
||||||
netbsd) return 0 ;;
|
|
||||||
openbsd) return 0 ;;
|
|
||||||
plan9) return 0 ;;
|
|
||||||
solaris) return 0 ;;
|
|
||||||
windows) return 0 ;;
|
|
||||||
esac
|
|
||||||
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
uname_arch_check() {
|
|
||||||
arch=$(uname_arch)
|
|
||||||
case "$arch" in
|
|
||||||
386) return 0 ;;
|
|
||||||
amd64) return 0 ;;
|
|
||||||
arm64) return 0 ;;
|
|
||||||
armv5) return 0 ;;
|
|
||||||
armv6) return 0 ;;
|
|
||||||
armv7) return 0 ;;
|
|
||||||
ppc64) return 0 ;;
|
|
||||||
ppc64le) return 0 ;;
|
|
||||||
mips) return 0 ;;
|
|
||||||
mipsle) return 0 ;;
|
|
||||||
mips64) return 0 ;;
|
|
||||||
mips64le) return 0 ;;
|
|
||||||
s390x) return 0 ;;
|
|
||||||
amd64p32) return 0 ;;
|
|
||||||
esac
|
|
||||||
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
untar() {
|
|
||||||
tarball=$1
|
|
||||||
case "${tarball}" in
|
|
||||||
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
|
|
||||||
*.tar) tar --no-same-owner -xf "${tarball}" ;;
|
|
||||||
*.zip) unzip "${tarball}" ;;
|
|
||||||
*)
|
|
||||||
log_err "untar unknown archive format for ${tarball}"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
http_download_curl() {
|
|
||||||
local_file=$1
|
|
||||||
source_url=$2
|
|
||||||
header=$3
|
|
||||||
if [ -z "$header" ]; then
|
|
||||||
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
|
||||||
else
|
|
||||||
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
|
||||||
fi
|
|
||||||
if [ "$code" != "200" ]; then
|
|
||||||
log_debug "http_download_curl received HTTP status $code"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
http_download_wget() {
|
|
||||||
local_file=$1
|
|
||||||
source_url=$2
|
|
||||||
header=$3
|
|
||||||
if [ -z "$header" ]; then
|
|
||||||
wget -q -O "$local_file" "$source_url"
|
|
||||||
else
|
|
||||||
wget -q --header "$header" -O "$local_file" "$source_url"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
http_download() {
|
|
||||||
log_debug "http_download $2"
|
|
||||||
if is_command curl; then
|
|
||||||
http_download_curl "$@"
|
|
||||||
return
|
|
||||||
elif is_command wget; then
|
|
||||||
http_download_wget "$@"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
log_crit "http_download unable to find wget or curl"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
http_copy() {
|
|
||||||
tmp=$(mktemp)
|
|
||||||
http_download "${tmp}" "$1" "$2" || return 1
|
|
||||||
body=$(cat "$tmp")
|
|
||||||
rm -f "${tmp}"
|
|
||||||
echo "$body"
|
|
||||||
}
|
|
||||||
github_release() {
|
|
||||||
owner_repo=$1
|
|
||||||
version=$2
|
|
||||||
test -z "$version" && version="latest"
|
|
||||||
giturl="https://github.com/${owner_repo}/releases/${version}"
|
|
||||||
json=$(http_copy "$giturl" "Accept:application/json")
|
|
||||||
test -z "$json" && return 1
|
|
||||||
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
|
|
||||||
test -z "$version" && return 1
|
|
||||||
echo "$version"
|
|
||||||
}
|
|
||||||
hash_sha256() {
|
|
||||||
TARGET=${1:-/dev/stdin}
|
|
||||||
if is_command gsha256sum; then
|
|
||||||
hash=$(gsha256sum "$TARGET") || return 1
|
|
||||||
echo "$hash" | cut -d ' ' -f 1
|
|
||||||
elif is_command sha256sum; then
|
|
||||||
hash=$(sha256sum "$TARGET") || return 1
|
|
||||||
echo "$hash" | cut -d ' ' -f 1
|
|
||||||
elif is_command shasum; then
|
|
||||||
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
|
||||||
echo "$hash" | cut -d ' ' -f 1
|
|
||||||
elif is_command openssl; then
|
|
||||||
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
|
||||||
echo "$hash" | cut -d ' ' -f a
|
|
||||||
else
|
|
||||||
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
hash_sha256_verify() {
|
|
||||||
TARGET=$1
|
|
||||||
checksums=$2
|
|
||||||
if [ -z "$checksums" ]; then
|
|
||||||
log_err "hash_sha256_verify checksum file not specified in arg2"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
BASENAME=${TARGET##*/}
|
|
||||||
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
|
||||||
if [ -z "$want" ]; then
|
|
||||||
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(hash_sha256 "$TARGET")
|
|
||||||
if [ "$want" != "$got" ]; then
|
|
||||||
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
cat /dev/null <<EOF
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
End of functions from https://github.com/client9/shlib
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
EOF
|
|
||||||
|
|
||||||
PROJECT_NAME="task"
|
|
||||||
OWNER=go-task
|
|
||||||
REPO="task"
|
|
||||||
BINARY=task
|
|
||||||
FORMAT=tar.gz
|
|
||||||
OS=$(uname_os)
|
|
||||||
ARCH=$(uname_arch)
|
|
||||||
PREFIX="$OWNER/$REPO"
|
|
||||||
|
|
||||||
# use in logging routines
|
|
||||||
log_prefix() {
|
|
||||||
echo "$PREFIX"
|
|
||||||
}
|
|
||||||
PLATFORM="${OS}/${ARCH}"
|
|
||||||
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
|
|
||||||
|
|
||||||
uname_os_check "$OS"
|
|
||||||
uname_arch_check "$ARCH"
|
|
||||||
|
|
||||||
parse_args "$@"
|
|
||||||
|
|
||||||
get_binaries
|
|
||||||
|
|
||||||
tag_to_version
|
|
||||||
|
|
||||||
adjust_format
|
|
||||||
|
|
||||||
adjust_os
|
|
||||||
|
|
||||||
adjust_arch
|
|
||||||
|
|
||||||
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
|
||||||
|
|
||||||
NAME=${BINARY}_${OS}_${ARCH}
|
|
||||||
TARBALL=${NAME}.${FORMAT}
|
|
||||||
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
|
||||||
CHECKSUM=task_checksums.txt
|
|
||||||
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
|
||||||
|
|
||||||
|
|
||||||
execute
|
|
||||||
36
internal/args/args.go
Normal file
36
internal/args/args.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package args
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrVariableWithoutTask is returned when variables are given before any task
|
||||||
|
ErrVariableWithoutTask = errors.New("task: variable given before any task")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse parses command line argument: tasks and vars of each task
|
||||||
|
func Parse(args ...string) ([]taskfile.Call, error) {
|
||||||
|
var calls []taskfile.Call
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
if !strings.Contains(arg, "=") {
|
||||||
|
calls = append(calls, taskfile.Call{Task: arg})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(calls) < 1 {
|
||||||
|
return nil, ErrVariableWithoutTask
|
||||||
|
}
|
||||||
|
|
||||||
|
if calls[len(calls)-1].Vars == nil {
|
||||||
|
calls[len(calls)-1].Vars = make(taskfile.Vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
pair := strings.SplitN(arg, "=", 2)
|
||||||
|
calls[len(calls)-1].Vars[pair[0]] = taskfile.Var{Static: pair[1]}
|
||||||
|
}
|
||||||
|
return calls, nil
|
||||||
|
}
|
||||||
70
internal/args/args_test.go
Normal file
70
internal/args/args_test.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package args_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-task/task/internal/args"
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArgs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Args []string
|
||||||
|
Expected []taskfile.Call
|
||||||
|
Err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Args: []string{"task-a", "task-b", "task-c"},
|
||||||
|
Expected: []taskfile.Call{
|
||||||
|
{Task: "task-a"},
|
||||||
|
{Task: "task-b"},
|
||||||
|
{Task: "task-c"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||||
|
Expected: []taskfile.Call{
|
||||||
|
{
|
||||||
|
Task: "task-a",
|
||||||
|
Vars: taskfile.Vars{
|
||||||
|
"FOO": taskfile.Var{Static: "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{Task: "task-b"},
|
||||||
|
{
|
||||||
|
Task: "task-c",
|
||||||
|
Vars: taskfile.Vars{
|
||||||
|
"BAR": taskfile.Var{Static: "baz"},
|
||||||
|
"BAZ": taskfile.Var{Static: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||||
|
Expected: []taskfile.Call{
|
||||||
|
{
|
||||||
|
Task: "task-a",
|
||||||
|
Vars: taskfile.Vars{
|
||||||
|
"CONTENT": taskfile.Var{Static: "with some spaces"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Args: []string{"FOO=bar", "task-a"},
|
||||||
|
Err: args.ErrVariableWithoutTask,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
||||||
|
calls, err := args.Parse(test.Args...)
|
||||||
|
assert.Equal(t, test.Err, err)
|
||||||
|
assert.Equal(t, test.Expected, calls)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compiler handles compilation of a task before its execution.
|
// Compiler handles compilation of a task before its execution.
|
||||||
// E.g. variable merger, template processing, etc.
|
// E.g. variable merger, template processing, etc.
|
||||||
type Compiler interface {
|
type Compiler interface {
|
||||||
GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error)
|
||||||
FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
HandleDynamicVar(v taskfile.Var) (string, error)
|
||||||
HandleDynamicVar(v taskfile.Var, dir string) (string, error)
|
|
||||||
ResetCache()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,21 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetEnviron the all return all environment variables encapsulated on a
|
// GetEnviron the all return all environment variables encapsulated on a
|
||||||
// taskfile.Vars
|
// taskfile.Vars
|
||||||
func GetEnviron() *taskfile.Vars {
|
func GetEnviron() taskfile.Vars {
|
||||||
m := &taskfile.Vars{}
|
var (
|
||||||
for _, e := range os.Environ() {
|
env = os.Environ()
|
||||||
|
m = make(taskfile.Vars, len(env))
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, e := range env {
|
||||||
keyVal := strings.SplitN(e, "=", 2)
|
keyVal := strings.SplitN(e, "=", 2)
|
||||||
key, val := keyVal[0], keyVal[1]
|
key, val := keyVal[0], keyVal[1]
|
||||||
m.Set(key, taskfile.Var{Static: val})
|
m[key] = taskfile.Var{Static: val}
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|||||||
136
internal/compiler/v1/compiler_v1.go
Normal file
136
internal/compiler/v1/compiler_v1.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-task/task/internal/compiler"
|
||||||
|
"github.com/go-task/task/internal/execext"
|
||||||
|
"github.com/go-task/task/internal/logger"
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
"github.com/go-task/task/internal/templater"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ compiler.Compiler = &CompilerV1{}
|
||||||
|
|
||||||
|
type CompilerV1 struct {
|
||||||
|
Dir string
|
||||||
|
Vars taskfile.Vars
|
||||||
|
|
||||||
|
Logger *logger.Logger
|
||||||
|
|
||||||
|
dynamicCache map[string]string
|
||||||
|
muDynamicCache sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVariables returns fully resolved variables following the priority order:
|
||||||
|
// 1. Call variables (should already have been resolved)
|
||||||
|
// 2. Environment (should not need to be resolved)
|
||||||
|
// 3. Task variables, resolved with access to:
|
||||||
|
// - call, taskvars and environment variables
|
||||||
|
// 4. Taskvars variables, resolved with access to:
|
||||||
|
// - environment variables
|
||||||
|
func (c *CompilerV1) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
|
||||||
|
merge := func(dest taskfile.Vars, srcs ...taskfile.Vars) {
|
||||||
|
for _, src := range srcs {
|
||||||
|
for k, v := range src {
|
||||||
|
dest[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
varsKeys := func(srcs ...taskfile.Vars) []string {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
for _, src := range srcs {
|
||||||
|
for k := range src {
|
||||||
|
m[k] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lst := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
lst = append(lst, k)
|
||||||
|
}
|
||||||
|
return lst
|
||||||
|
}
|
||||||
|
replaceVars := func(dest taskfile.Vars, keys []string) error {
|
||||||
|
r := templater.Templater{Vars: dest}
|
||||||
|
for _, k := range keys {
|
||||||
|
v := dest[k]
|
||||||
|
dest[k] = taskfile.Var{
|
||||||
|
Static: r.Replace(v.Static),
|
||||||
|
Sh: r.Replace(v.Sh),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.Err()
|
||||||
|
}
|
||||||
|
resolveShell := func(dest taskfile.Vars, keys []string) error {
|
||||||
|
for _, k := range keys {
|
||||||
|
v := dest[k]
|
||||||
|
static, err := c.HandleDynamicVar(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dest[k] = taskfile.Var{Static: static}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
update := func(dest taskfile.Vars, srcs ...taskfile.Vars) error {
|
||||||
|
merge(dest, srcs...)
|
||||||
|
// updatedKeys ensures template evaluation is run only once.
|
||||||
|
updatedKeys := varsKeys(srcs...)
|
||||||
|
if err := replaceVars(dest, updatedKeys); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return resolveShell(dest, updatedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve taskvars variables to "result" with environment override variables.
|
||||||
|
override := compiler.GetEnviron()
|
||||||
|
result := make(taskfile.Vars, len(c.Vars)+len(t.Vars)+len(override))
|
||||||
|
if err := update(result, c.Vars, override); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Resolve task variables to "result" with environment and call override variables.
|
||||||
|
merge(override, call.Vars)
|
||||||
|
if err := update(result, t.Vars, override); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompilerV1) HandleDynamicVar(v taskfile.Var) (string, error) {
|
||||||
|
if v.Static != "" || v.Sh == "" {
|
||||||
|
return v.Static, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.muDynamicCache.Lock()
|
||||||
|
defer c.muDynamicCache.Unlock()
|
||||||
|
|
||||||
|
if c.dynamicCache == nil {
|
||||||
|
c.dynamicCache = make(map[string]string, 30)
|
||||||
|
}
|
||||||
|
if result, ok := c.dynamicCache[v.Sh]; ok {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
opts := &execext.RunCommandOptions{
|
||||||
|
Command: v.Sh,
|
||||||
|
Dir: c.Dir,
|
||||||
|
Stdout: &stdout,
|
||||||
|
Stderr: c.Logger.Stderr,
|
||||||
|
}
|
||||||
|
if err := execext.RunCommand(opts); err != nil {
|
||||||
|
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim a single trailing newline from the result to make most command
|
||||||
|
// output easier to use in shell commands.
|
||||||
|
result := strings.TrimSuffix(stdout.String(), "\n")
|
||||||
|
|
||||||
|
c.dynamicCache[v.Sh] = result
|
||||||
|
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -2,16 +2,15 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/compiler"
|
"github.com/go-task/task/internal/compiler"
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
"github.com/go-task/task/internal/execext"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/internal/templater"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ compiler.Compiler = &CompilerV2{}
|
var _ compiler.Compiler = &CompilerV2{}
|
||||||
@@ -19,8 +18,8 @@ var _ compiler.Compiler = &CompilerV2{}
|
|||||||
type CompilerV2 struct {
|
type CompilerV2 struct {
|
||||||
Dir string
|
Dir string
|
||||||
|
|
||||||
Taskvars *taskfile.Vars
|
Taskvars taskfile.Vars
|
||||||
TaskfileVars *taskfile.Vars
|
TaskfileVars taskfile.Vars
|
||||||
|
|
||||||
Expansions int
|
Expansions int
|
||||||
|
|
||||||
@@ -30,25 +29,15 @@ type CompilerV2 struct {
|
|||||||
muDynamicCache sync.Mutex
|
muDynamicCache sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// FastGetVariables is a no-op on v2
|
|
||||||
func (c *CompilerV2) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
|
||||||
return c.GetVariables(t, call)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVariables returns fully resolved variables following the priority order:
|
// GetVariables returns fully resolved variables following the priority order:
|
||||||
// 1. Task variables
|
// 1. Task variables
|
||||||
// 2. Call variables
|
// 2. Call variables
|
||||||
// 3. Taskfile variables
|
// 3. Taskfile variables
|
||||||
// 4. Taskvars file variables
|
// 4. Taskvars file variables
|
||||||
// 5. Environment variables
|
// 5. Environment variables
|
||||||
func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
|
||||||
vr := varResolver{
|
vr := varResolver{c: c, vars: compiler.GetEnviron()}
|
||||||
c: c,
|
for _, vars := range []taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} {
|
||||||
vars: compiler.GetEnviron(),
|
|
||||||
}
|
|
||||||
vr.vars.Set("TASK", taskfile.Var{Static: t.Task})
|
|
||||||
|
|
||||||
for _, vars := range []*taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} {
|
|
||||||
for i := 0; i < c.Expansions; i++ {
|
for i := 0; i < c.Expansions; i++ {
|
||||||
vr.merge(vars)
|
vr.merge(vars)
|
||||||
}
|
}
|
||||||
@@ -58,32 +47,31 @@ func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfi
|
|||||||
|
|
||||||
type varResolver struct {
|
type varResolver struct {
|
||||||
c *CompilerV2
|
c *CompilerV2
|
||||||
vars *taskfile.Vars
|
vars taskfile.Vars
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vr *varResolver) merge(vars *taskfile.Vars) {
|
func (vr *varResolver) merge(vars taskfile.Vars) {
|
||||||
if vr.err != nil {
|
if vr.err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tr := templater.Templater{Vars: vr.vars}
|
tr := templater.Templater{Vars: vr.vars}
|
||||||
vars.Range(func(k string, v taskfile.Var) error {
|
for k, v := range vars {
|
||||||
v = taskfile.Var{
|
v = taskfile.Var{
|
||||||
Static: tr.Replace(v.Static),
|
Static: tr.Replace(v.Static),
|
||||||
Sh: tr.Replace(v.Sh),
|
Sh: tr.Replace(v.Sh),
|
||||||
}
|
}
|
||||||
static, err := vr.c.HandleDynamicVar(v, "")
|
static, err := vr.c.HandleDynamicVar(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vr.err = err
|
vr.err = err
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
vr.vars.Set(k, taskfile.Var{Static: static})
|
vr.vars[k] = taskfile.Var{Static: static}
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
vr.err = tr.Err()
|
vr.err = tr.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ string) (string, error) {
|
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
|
||||||
if v.Static != "" || v.Sh == "" {
|
if v.Static != "" || v.Sh == "" {
|
||||||
return v.Static, nil
|
return v.Static, nil
|
||||||
}
|
}
|
||||||
@@ -101,10 +89,11 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ string) (string, error)
|
|||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
opts := &execext.RunCommandOptions{
|
opts := &execext.RunCommandOptions{
|
||||||
Command: v.Sh,
|
Command: v.Sh,
|
||||||
|
Dir: c.Dir,
|
||||||
Stdout: &stdout,
|
Stdout: &stdout,
|
||||||
Stderr: c.Logger.Stderr,
|
Stderr: c.Logger.Stderr,
|
||||||
}
|
}
|
||||||
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
if err := execext.RunCommand(opts); err != nil {
|
||||||
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
|
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,15 +102,7 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ string) (string, error)
|
|||||||
result := strings.TrimSuffix(stdout.String(), "\n")
|
result := strings.TrimSuffix(stdout.String(), "\n")
|
||||||
|
|
||||||
c.dynamicCache[v.Sh] = result
|
c.dynamicCache[v.Sh] = result
|
||||||
c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetCache clear the dymanic variables cache
|
|
||||||
func (c *CompilerV2) ResetCache() {
|
|
||||||
c.muDynamicCache.Lock()
|
|
||||||
defer c.muDynamicCache.Unlock()
|
|
||||||
|
|
||||||
c.dynamicCache = nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
package v3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/compiler"
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ compiler.Compiler = &CompilerV3{}
|
|
||||||
|
|
||||||
type CompilerV3 struct {
|
|
||||||
Dir string
|
|
||||||
|
|
||||||
TaskfileEnv *taskfile.Vars
|
|
||||||
TaskfileVars *taskfile.Vars
|
|
||||||
|
|
||||||
Logger *logger.Logger
|
|
||||||
|
|
||||||
dynamicCache map[string]string
|
|
||||||
muDynamicCache sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
|
||||||
return c.getVariables(t, call, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
|
||||||
return c.getVariables(t, call, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) getVariables(t *taskfile.Task, call taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
|
|
||||||
result := compiler.GetEnviron()
|
|
||||||
result.Set("TASK", taskfile.Var{Static: t.Task})
|
|
||||||
|
|
||||||
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
|
|
||||||
return func(k string, v taskfile.Var) error {
|
|
||||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
|
||||||
|
|
||||||
if !evaluateShVars {
|
|
||||||
result.Set(k, taskfile.Var{Static: tr.Replace(v.Static)})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v = taskfile.Var{
|
|
||||||
Static: tr.Replace(v.Static),
|
|
||||||
Sh: tr.Replace(v.Sh),
|
|
||||||
Dir: v.Dir,
|
|
||||||
}
|
|
||||||
if err := tr.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
static, err := c.HandleDynamicVar(v, dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
result.Set(k, taskfile.Var{Static: static})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rangeFunc := getRangeFunc(c.Dir)
|
|
||||||
|
|
||||||
if err := c.TaskfileEnv.Range(rangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := call.Vars.Range(rangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(@andreynering): We're manually joining these paths here because
|
|
||||||
// this is the raw task, not the compiled one.
|
|
||||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
|
||||||
dir := tr.Replace(t.Dir)
|
|
||||||
if err := tr.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(dir) {
|
|
||||||
dir = filepath.Join(c.Dir, dir)
|
|
||||||
}
|
|
||||||
taskRangeFunc := getRangeFunc(dir)
|
|
||||||
|
|
||||||
if err := t.Vars.Range(taskRangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) HandleDynamicVar(v taskfile.Var, dir string) (string, error) {
|
|
||||||
if v.Static != "" || v.Sh == "" {
|
|
||||||
return v.Static, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.muDynamicCache.Lock()
|
|
||||||
defer c.muDynamicCache.Unlock()
|
|
||||||
|
|
||||||
if c.dynamicCache == nil {
|
|
||||||
c.dynamicCache = make(map[string]string, 30)
|
|
||||||
}
|
|
||||||
if result, ok := c.dynamicCache[v.Sh]; ok {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(@andreynering): If a var have a specific dir, use this instead
|
|
||||||
if v.Dir != "" {
|
|
||||||
dir = v.Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
opts := &execext.RunCommandOptions{
|
|
||||||
Command: v.Sh,
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &stdout,
|
|
||||||
Stderr: c.Logger.Stderr,
|
|
||||||
}
|
|
||||||
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
|
||||||
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim a single trailing newline from the result to make most command
|
|
||||||
// output easier to use in shell commands.
|
|
||||||
result := strings.TrimSuffix(stdout.String(), "\n")
|
|
||||||
|
|
||||||
c.dynamicCache[v.Sh] = result
|
|
||||||
c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetCache clear the dymanic variables cache
|
|
||||||
func (c *CompilerV3) ResetCache() {
|
|
||||||
c.muDynamicCache.Lock()
|
|
||||||
defer c.muDynamicCache.Unlock()
|
|
||||||
|
|
||||||
c.dynamicCache = nil
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package execext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ io.ReadWriteCloser = devNull{}
|
|
||||||
|
|
||||||
type devNull struct{}
|
|
||||||
|
|
||||||
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
|
|
||||||
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
|
|
||||||
func (devNull) Close() error { return nil }
|
|
||||||
@@ -4,18 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/interp"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/syntax"
|
||||||
"mvdan.cc/sh/v3/shell"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunCommandOptions is the options for the RunCommand func
|
// RunCommandOptions is the options for the RunCommand func
|
||||||
type RunCommandOptions struct {
|
type RunCommandOptions struct {
|
||||||
|
Context context.Context
|
||||||
Command string
|
Command string
|
||||||
Dir string
|
Dir string
|
||||||
Env []string
|
Env []string
|
||||||
@@ -30,7 +27,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RunCommand runs a shell command
|
// RunCommand runs a shell command
|
||||||
func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
func RunCommand(opts *RunCommandOptions) error {
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
return ErrNilOptions
|
return ErrNilOptions
|
||||||
}
|
}
|
||||||
@@ -40,50 +37,20 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
environ := opts.Env
|
r := interp.Runner{
|
||||||
if len(environ) == 0 {
|
Context: opts.Context,
|
||||||
environ = os.Environ()
|
Dir: opts.Dir,
|
||||||
}
|
Env: opts.Env,
|
||||||
|
|
||||||
r, err := interp.New(
|
Exec: interp.DefaultExec,
|
||||||
interp.Params("-e"),
|
Open: interp.OpenDevImpls(interp.DefaultOpen),
|
||||||
interp.Dir(opts.Dir),
|
|
||||||
interp.Env(expand.ListEnviron(environ...)),
|
Stdin: opts.Stdin,
|
||||||
interp.OpenHandler(openHandler),
|
Stdout: opts.Stdout,
|
||||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
Stderr: opts.Stderr,
|
||||||
)
|
}
|
||||||
if err != nil {
|
if err = r.Reset(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return r.Run(ctx, p)
|
return r.Run(p)
|
||||||
}
|
|
||||||
|
|
||||||
// IsExitError returns true the given error is an exis status error
|
|
||||||
func IsExitError(err error) bool {
|
|
||||||
if _, ok := interp.IsExitStatus(err); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
|
||||||
// if available.
|
|
||||||
func Expand(s string) (string, error) {
|
|
||||||
s = filepath.ToSlash(s)
|
|
||||||
s = strings.Replace(s, " ", `\ `, -1)
|
|
||||||
fields, err := shell.Fields(s, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if len(fields) > 0 {
|
|
||||||
return fields[0], nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
|
||||||
if path == "/dev/null" {
|
|
||||||
return devNull{}, nil
|
|
||||||
}
|
|
||||||
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,38 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PrintFunc func(io.Writer, string, ...interface{})
|
|
||||||
|
|
||||||
var (
|
|
||||||
Default PrintFunc = color.New(color.Reset).FprintfFunc()
|
|
||||||
Blue PrintFunc = color.New(color.FgBlue).FprintfFunc()
|
|
||||||
Green PrintFunc = color.New(color.FgGreen).FprintfFunc()
|
|
||||||
Cyan PrintFunc = color.New(color.FgCyan).FprintfFunc()
|
|
||||||
Yellow PrintFunc = color.New(color.FgYellow).FprintfFunc()
|
|
||||||
Magenta PrintFunc = color.New(color.FgMagenta).FprintfFunc()
|
|
||||||
Red PrintFunc = color.New(color.FgRed).FprintfFunc()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logger is just a wrapper that prints stuff to STDOUT or STDERR,
|
|
||||||
// with optional color.
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
Verbose bool
|
Verbose bool
|
||||||
Color bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outf prints stuff to STDOUT.
|
func (l *Logger) Outf(s string, args ...interface{}) {
|
||||||
func (l *Logger) Outf(print PrintFunc, s string, args ...interface{}) {
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
s, args = "%s", []interface{}{s}
|
s, args = "%s", []interface{}{s}
|
||||||
}
|
}
|
||||||
if !l.Color {
|
fmt.Fprintf(l.Stdout, s+"\n", args...)
|
||||||
print = Default
|
|
||||||
}
|
|
||||||
print(l.Stdout, s+"\n", args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerboseOutf prints stuff to STDOUT if verbose mode is enabled.
|
func (l *Logger) VerboseOutf(s string, args ...interface{}) {
|
||||||
func (l *Logger) VerboseOutf(print PrintFunc, s string, args ...interface{}) {
|
|
||||||
if l.Verbose {
|
if l.Verbose {
|
||||||
l.Outf(print, s, args...)
|
l.Outf(s, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errf prints stuff to STDERR.
|
func (l *Logger) Errf(s string, args ...interface{}) {
|
||||||
func (l *Logger) Errf(print PrintFunc, s string, args ...interface{}) {
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
s, args = "%s", []interface{}{s}
|
s, args = "%s", []interface{}{s}
|
||||||
}
|
}
|
||||||
if !l.Color {
|
fmt.Fprintf(l.Stderr, s+"\n", args...)
|
||||||
print = Default
|
|
||||||
}
|
|
||||||
print(l.Stderr, s+"\n", args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerboseErrf prints stuff to STDERR if verbose mode is enabled.
|
func (l *Logger) VerboseErrf(s string, args ...interface{}) {
|
||||||
func (l *Logger) VerboseErrf(print PrintFunc, s string, args ...interface{}) {
|
|
||||||
if l.Verbose {
|
if l.Verbose {
|
||||||
l.Errf(print, s, args...)
|
l.Errf(s, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Group struct{}
|
|
||||||
|
|
||||||
func (Group) WrapWriter(w io.Writer, _ string) io.Writer {
|
|
||||||
return &groupWriter{writer: w}
|
|
||||||
}
|
|
||||||
|
|
||||||
type groupWriter struct {
|
|
||||||
writer io.Writer
|
|
||||||
buff bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *groupWriter) Write(p []byte) (int, error) {
|
|
||||||
return gw.buff.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *groupWriter) Close() error {
|
|
||||||
_, err := io.Copy(gw.writer, &gw.buff)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Interleaved struct{}
|
|
||||||
|
|
||||||
func (Interleaved) WrapWriter(w io.Writer, _ string) io.Writer {
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Output interface {
|
|
||||||
WrapWriter(w io.Writer, prefix string) io.Writer
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package output_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/output"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInterleaved(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
var o output.Output = output.Interleaved{}
|
|
||||||
var w = o.WrapWriter(&b, "")
|
|
||||||
|
|
||||||
fmt.Fprintln(w, "foo\nbar")
|
|
||||||
assert.Equal(t, "foo\nbar\n", b.String())
|
|
||||||
fmt.Fprintln(w, "baz")
|
|
||||||
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGroup(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
var o output.Output = output.Group{}
|
|
||||||
var w = o.WrapWriter(&b, "").(io.WriteCloser)
|
|
||||||
|
|
||||||
fmt.Fprintln(w, "foo\nbar")
|
|
||||||
assert.Equal(t, "", b.String())
|
|
||||||
fmt.Fprintln(w, "baz")
|
|
||||||
assert.Equal(t, "", b.String())
|
|
||||||
assert.NoError(t, w.Close())
|
|
||||||
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrefixed(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
var o output.Output = output.Prefixed{}
|
|
||||||
var w = o.WrapWriter(&b, "prefix").(io.WriteCloser)
|
|
||||||
|
|
||||||
t.Run("simple use cases", func(t *testing.T) {
|
|
||||||
b.Reset()
|
|
||||||
|
|
||||||
fmt.Fprintln(w, "foo\nbar")
|
|
||||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n", b.String())
|
|
||||||
fmt.Fprintln(w, "baz")
|
|
||||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n[prefix] baz\n", b.String())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("multiple writes for a single line", func(t *testing.T) {
|
|
||||||
b.Reset()
|
|
||||||
|
|
||||||
for _, char := range []string{"T", "e", "s", "t", "!"} {
|
|
||||||
fmt.Fprint(w, char)
|
|
||||||
assert.Equal(t, "", b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, w.Close())
|
|
||||||
assert.Equal(t, "[prefix] Test!\n", b.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Prefixed struct{}
|
|
||||||
|
|
||||||
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.Writer {
|
|
||||||
return &prefixWriter{writer: w, prefix: prefix}
|
|
||||||
}
|
|
||||||
|
|
||||||
type prefixWriter struct {
|
|
||||||
writer io.Writer
|
|
||||||
prefix string
|
|
||||||
buff bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pw *prefixWriter) Write(p []byte) (int, error) {
|
|
||||||
n, err := pw.buff.Write(p)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, pw.writeOutputLines(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pw *prefixWriter) Close() error {
|
|
||||||
return pw.writeOutputLines(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pw *prefixWriter) writeOutputLines(force bool) error {
|
|
||||||
for {
|
|
||||||
switch line, err := pw.buff.ReadString('\n'); err {
|
|
||||||
case nil:
|
|
||||||
if err = pw.writeLine(line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case io.EOF:
|
|
||||||
// if this line was not a complete line, re-add to the buffer
|
|
||||||
if !force && !strings.HasSuffix(line, "\n") {
|
|
||||||
_, err = pw.buff.WriteString(line)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pw.writeLine(line)
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pw *prefixWriter) writeLine(line string) error {
|
|
||||||
if line == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(line, "\n") {
|
|
||||||
line += "\n"
|
|
||||||
}
|
|
||||||
_, err := fmt.Fprintf(pw.writer, "[%s] %s", pw.prefix, line)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -14,26 +14,19 @@ 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 {
|
||||||
BaseDir string
|
Dir string
|
||||||
TaskDir string
|
Task string
|
||||||
Task string
|
Sources []string
|
||||||
Sources []string
|
|
||||||
Generates []string
|
|
||||||
Dry bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUpToDate implements the Checker interface
|
// IsUpToDate implements the Checker interface
|
||||||
func (c *Checksum) IsUpToDate() (bool, error) {
|
func (c *Checksum) IsUpToDate() (bool, error) {
|
||||||
if len(c.Sources) == 0 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
checksumFile := c.checksumFilePath()
|
checksumFile := c.checksumFilePath()
|
||||||
|
|
||||||
data, _ := ioutil.ReadFile(checksumFile)
|
data, _ := ioutil.ReadFile(checksumFile)
|
||||||
oldMd5 := strings.TrimSpace(string(data))
|
oldMd5 := strings.TrimSpace(string(data))
|
||||||
|
|
||||||
sources, err := globs(c.TaskDir, c.Sources)
|
sources, err := glob(c.Dir, c.Sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -43,29 +36,10 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Dry {
|
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
|
||||||
_ = os.MkdirAll(filepath.Join(c.BaseDir, ".task", "checksum"), 0755)
|
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
||||||
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
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.TaskDir, g)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if len(generates) == 0 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldMd5 == newMd5, nil
|
return oldMd5 == newMd5, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,14 +47,21 @@ 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
|
||||||
}
|
}
|
||||||
@@ -89,26 +70,13 @@ func (c *Checksum) checksum(files ...string) (string, error) {
|
|||||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value implements the Checker Interface
|
|
||||||
func (c *Checksum) Value() (interface{}, error) {
|
|
||||||
return c.checksum()
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnError implements the Checker interface
|
// OnError implements the Checker interface
|
||||||
func (c *Checksum) OnError() error {
|
func (c *Checksum) OnError() error {
|
||||||
if len(c.Sources) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return os.Remove(c.checksumFilePath())
|
return os.Remove(c.checksumFilePath())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kind implements the Checker Interface
|
|
||||||
func (*Checksum) Kind() string {
|
|
||||||
return "checksum"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Checksum) checksumFilePath() string {
|
func (c *Checksum) checksumFilePath() string {
|
||||||
return filepath.Join(c.BaseDir, ".task", "checksum", c.normalizeFilename(c.Task))
|
return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task))
|
||||||
}
|
}
|
||||||
|
|
||||||
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
|
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
|
||||||
|
|||||||
@@ -1,50 +1,28 @@
|
|||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/mattn/go-zglob"
|
"github.com/mattn/go-zglob"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func globs(dir string, globs []string) ([]string, error) {
|
func glob(dir string, globs []string) (files []string, err error) {
|
||||||
files := make([]string, 0)
|
|
||||||
for _, g := range globs {
|
for _, g := range globs {
|
||||||
f, err := glob(dir, g)
|
if !filepath.IsAbs(g) {
|
||||||
|
g = filepath.Join(dir, g)
|
||||||
|
}
|
||||||
|
g, err = homedir.Expand(g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := zglob.Glob(g)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
files = append(files, f...)
|
files = append(files, f...)
|
||||||
}
|
}
|
||||||
sort.Strings(files)
|
sort.Strings(files)
|
||||||
return files, nil
|
return
|
||||||
}
|
|
||||||
|
|
||||||
func glob(dir string, g string) ([]string, error) {
|
|
||||||
files := make([]string, 0)
|
|
||||||
if !filepath.IsAbs(g) {
|
|
||||||
g = filepath.Join(dir, g)
|
|
||||||
}
|
|
||||||
g, err := execext.Expand(g)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fs, err := zglob.Glob(g)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, f := range fs {
|
|
||||||
info, err := os.Stat(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,6 @@ func (None) IsUpToDate() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value implements the Checker interface
|
|
||||||
func (None) Value() (interface{}, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (None) Kind() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnError implements the Checker interface
|
// OnError implements the Checker interface
|
||||||
func (None) OnError() error {
|
func (None) OnError() error {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -9,7 +9,5 @@ var (
|
|||||||
// Checker is an interface that checks if the status is up-to-date
|
// Checker is an interface that checks if the status is up-to-date
|
||||||
type Checker interface {
|
type Checker interface {
|
||||||
IsUpToDate() (bool, error)
|
IsUpToDate() (bool, error)
|
||||||
Value() (interface{}, error)
|
|
||||||
OnError() error
|
OnError() error
|
||||||
Kind() string
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sources, err := globs(t.Dir, t.Sources)
|
sources, err := glob(t.Dir, t.Sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
generates, err := globs(t.Dir, t.Generates)
|
generates, err := glob(t.Dir, t.Generates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -41,29 +41,6 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
|
|||||||
return !generatesMinTime.Before(sourcesMaxTime), nil
|
return !generatesMinTime.Before(sourcesMaxTime), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Timestamp) Kind() string {
|
|
||||||
return "timestamp"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements the Checker Interface
|
|
||||||
func (t *Timestamp) Value() (interface{}, error) {
|
|
||||||
sources, err := globs(t.Dir, t.Sources)
|
|
||||||
if err != nil {
|
|
||||||
return time.Now(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
sourcesMaxTime, err := getMaxTime(sources...)
|
|
||||||
if err != nil {
|
|
||||||
return time.Now(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourcesMaxTime.IsZero() {
|
|
||||||
return time.Unix(0, 0), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return sourcesMaxTime, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMinTime(files ...string) (time.Time, error) {
|
func getMinTime(files ...string) (time.Time, error) {
|
||||||
var t time.Time
|
var t time.Time
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
package summary
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
|
|
||||||
for i, call := range c {
|
|
||||||
PrintSpaceBetweenSummaries(l, i)
|
|
||||||
PrintTask(l, t.Tasks[call.Task])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
|
|
||||||
spaceRequired := i > 0
|
|
||||||
if !spaceRequired {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Outf(logger.Default, "")
|
|
||||||
l.Outf(logger.Default, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
|
||||||
printTaskName(l, t)
|
|
||||||
printTaskDescribingText(t, l)
|
|
||||||
printTaskDependencies(l, t)
|
|
||||||
printTaskCommands(l, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTaskDescribingText(t *taskfile.Task, l *logger.Logger) {
|
|
||||||
if hasSummary(t) {
|
|
||||||
printTaskSummary(l, t)
|
|
||||||
} else if hasDescription(t) {
|
|
||||||
printTaskDescription(l, t)
|
|
||||||
} else {
|
|
||||||
printNoDescriptionOrSummary(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasSummary(t *taskfile.Task) bool {
|
|
||||||
return t.Summary != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
|
|
||||||
lines := strings.Split(t.Summary, "\n")
|
|
||||||
for i, line := range lines {
|
|
||||||
notLastLine := i+1 < len(lines)
|
|
||||||
if notLastLine || line != "" {
|
|
||||||
l.Outf(logger.Default, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
|
||||||
l.Outf(logger.Default, "task: %s", t.Name())
|
|
||||||
l.Outf(logger.Default, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasDescription(t *taskfile.Task) bool {
|
|
||||||
return t.Desc != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
|
|
||||||
l.Outf(logger.Default, t.Desc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printNoDescriptionOrSummary(l *logger.Logger) {
|
|
||||||
l.Outf(logger.Default, "(task does not have description or summary)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
|
|
||||||
if len(t.Deps) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Outf(logger.Default, "")
|
|
||||||
l.Outf(logger.Default, "dependencies:")
|
|
||||||
|
|
||||||
for _, d := range t.Deps {
|
|
||||||
l.Outf(logger.Default, " - %s", d.Task)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
|
||||||
if len(t.Cmds) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Outf(logger.Default, "")
|
|
||||||
l.Outf(logger.Default, "commands:")
|
|
||||||
for _, c := range t.Cmds {
|
|
||||||
isCommand := c.Cmd != ""
|
|
||||||
if isCommand {
|
|
||||||
l.Outf(logger.Default, " - %s", c.Cmd)
|
|
||||||
} else {
|
|
||||||
l.Outf(logger.Default, " - Task: %s", c.Task)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
package summary_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
|
||||||
"github.com/go-task/task/v3/internal/summary"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPrintsDependenciesIfPresent(t *testing.T) {
|
|
||||||
buffer, l := createDummyLogger()
|
|
||||||
task := &taskfile.Task{
|
|
||||||
Deps: []*taskfile.Dep{
|
|
||||||
{Task: "dep1"},
|
|
||||||
{Task: "dep2"},
|
|
||||||
{Task: "dep3"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
summary.PrintTask(&l, task)
|
|
||||||
|
|
||||||
assert.Contains(t, buffer.String(), "\ndependencies:\n - dep1\n - dep2\n - dep3\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDummyLogger() (*bytes.Buffer, logger.Logger) {
|
|
||||||
buffer := &bytes.Buffer{}
|
|
||||||
l := logger.Logger{
|
|
||||||
Stderr: buffer,
|
|
||||||
Stdout: buffer,
|
|
||||||
Verbose: false,
|
|
||||||
}
|
|
||||||
return buffer, l
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
|
|
||||||
buffer, l := createDummyLogger()
|
|
||||||
task := &taskfile.Task{
|
|
||||||
Deps: []*taskfile.Dep{},
|
|
||||||
}
|
|
||||||
|
|
||||||
summary.PrintTask(&l, task)
|
|
||||||
|
|
||||||
assert.NotContains(t, buffer.String(), "dependencies:")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrintTaskName(t *testing.T) {
|
|
||||||
buffer, l := createDummyLogger()
|
|
||||||
task := &taskfile.Task{
|
|
||||||
Task: "my-task-name",
|
|
||||||
}
|
|
||||||
|
|
||||||
summary.PrintTask(&l, task)
|
|
||||||
|
|
||||||
assert.Contains(t, buffer.String(), "task: my-task-name\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrintTaskCommandsIfPresent(t *testing.T) {
|
|
||||||
buffer, l := createDummyLogger()
|
|
||||||
task := &taskfile.Task{
|
|
||||||
Cmds: []*taskfile.Cmd{
|
|
||||||
{Cmd: "command-1"},
|
|
||||||
{Cmd: "command-2"},
|
|
||||||
{Task: "task-1"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
summary.PrintTask(&l, task)
|
|
||||||
|
|
||||||
assert.Contains(t, buffer.String(), "\ncommands:\n")
|
|
||||||
assert.Contains(t, buffer.String(), "\n - command-1\n")
|
|
||||||
assert.Contains(t, buffer.String(), "\n - command-2\n")
|
|
||||||
assert.Contains(t, buffer.String(), "\n - Task: task-1\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoesNotPrintCommandIfMissing(t *testing.T) {
|
|
||||||
buffer, l := createDummyLogger()
|
|
||||||
task := &taskfile.Task{
|
|
||||||
Cmds: []*taskfile.Cmd{},
|
|
||||||
}
|
|
||||||
|
|
||||||
summary.PrintTask(&l, task)
|
|
||||||
|
|
||||||
assert.NotContains(t, buffer.String(), "commands")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLayout(t *testing.T) {
|
|
||||||
buffer, l := createDummyLogger()
|
|
||||||
task := &taskfile.Task{
|
|
||||||
Task: "sample-task",
|
|
||||||
Summary: "line1\nline2\nline3\n",
|
|
||||||
Deps: []*taskfile.Dep{
|
|
||||||
{Task: "dependency"},
|
|
||||||
},
|
|
||||||
Cmds: []*taskfile.Cmd{
|
|
||||||
{Cmd: "command"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
summary.PrintTask(&l, task)
|
|
||||||
|
|
||||||
assert.Equal(t, expectedOutput(), buffer.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectedOutput() string {
|
|
||||||
expected := `task: sample-task
|
|
||||||
|
|
||||||
line1
|
|
||||||
line2
|
|
||||||
line3
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
- dependency
|
|
||||||
|
|
||||||
commands:
|
|
||||||
- command
|
|
||||||
`
|
|
||||||
return expected
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrintDescriptionAsFallback(t *testing.T) {
|
|
||||||
buffer, l := createDummyLogger()
|
|
||||||
taskWithoutSummary := &taskfile.Task{
|
|
||||||
Desc: "description",
|
|
||||||
}
|
|
||||||
|
|
||||||
taskWithSummary := &taskfile.Task{
|
|
||||||
Desc: "description",
|
|
||||||
Summary: "summary",
|
|
||||||
}
|
|
||||||
taskWithoutSummaryOrDescription := &taskfile.Task{}
|
|
||||||
|
|
||||||
summary.PrintTask(&l, taskWithoutSummary)
|
|
||||||
|
|
||||||
assert.Contains(t, buffer.String(), "description")
|
|
||||||
|
|
||||||
buffer.Reset()
|
|
||||||
summary.PrintTask(&l, taskWithSummary)
|
|
||||||
|
|
||||||
assert.NotContains(t, buffer.String(), "description")
|
|
||||||
|
|
||||||
buffer.Reset()
|
|
||||||
summary.PrintTask(&l, taskWithoutSummaryOrDescription)
|
|
||||||
|
|
||||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrintAllWithSpaces(t *testing.T) {
|
|
||||||
buffer, l := createDummyLogger()
|
|
||||||
|
|
||||||
t1 := &taskfile.Task{Task: "t1"}
|
|
||||||
t2 := &taskfile.Task{Task: "t2"}
|
|
||||||
t3 := &taskfile.Task{Task: "t3"}
|
|
||||||
|
|
||||||
tasks := make(taskfile.Tasks, 3)
|
|
||||||
tasks["t1"] = t1
|
|
||||||
tasks["t2"] = t2
|
|
||||||
tasks["t3"] = t3
|
|
||||||
|
|
||||||
summary.PrintTasks(&l,
|
|
||||||
&taskfile.Taskfile{Tasks: tasks},
|
|
||||||
[]taskfile.Call{{Task: "t1"}, {Task: "t2"}, {Task: "t3"}})
|
|
||||||
|
|
||||||
assert.True(t, strings.HasPrefix(buffer.String(), "task: t1"))
|
|
||||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t2")
|
|
||||||
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t3")
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,5 +3,5 @@ package taskfile
|
|||||||
// Call is the parameters to a task call
|
// Call is the parameters to a task call
|
||||||
type Call struct {
|
type Call struct {
|
||||||
Task string
|
Task string
|
||||||
Vars *Vars
|
Vars Vars
|
||||||
}
|
}
|
||||||
@@ -7,17 +7,16 @@ import (
|
|||||||
|
|
||||||
// Cmd is a task command
|
// Cmd is a task command
|
||||||
type Cmd struct {
|
type Cmd struct {
|
||||||
Cmd string
|
Cmd string
|
||||||
Silent bool
|
Silent bool
|
||||||
Task string
|
Task string
|
||||||
Vars *Vars
|
Vars Vars
|
||||||
IgnoreError bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dep is a task dependency
|
// Dep is a task dependency
|
||||||
type Dep struct {
|
type Dep struct {
|
||||||
Task string
|
Task string
|
||||||
Vars *Vars
|
Vars Vars
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -39,19 +38,17 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var cmdStruct struct {
|
var cmdStruct struct {
|
||||||
Cmd string
|
Cmd string
|
||||||
Silent bool
|
Silent bool
|
||||||
IgnoreError bool `yaml:"ignore_error"`
|
|
||||||
}
|
}
|
||||||
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
|
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
|
||||||
c.Cmd = cmdStruct.Cmd
|
c.Cmd = cmdStruct.Cmd
|
||||||
c.Silent = cmdStruct.Silent
|
c.Silent = cmdStruct.Silent
|
||||||
c.IgnoreError = cmdStruct.IgnoreError
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var taskCall struct {
|
var taskCall struct {
|
||||||
Task string
|
Task string
|
||||||
Vars *Vars
|
Vars Vars
|
||||||
}
|
}
|
||||||
if err := unmarshal(&taskCall); err == nil {
|
if err := unmarshal(&taskCall); err == nil {
|
||||||
c.Task = taskCall.Task
|
c.Task = taskCall.Task
|
||||||
@@ -70,7 +67,7 @@ func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
}
|
}
|
||||||
var taskCall struct {
|
var taskCall struct {
|
||||||
Task string
|
Task string
|
||||||
Vars *Vars
|
Vars Vars
|
||||||
}
|
}
|
||||||
if err := unmarshal(&taskCall); err == nil {
|
if err := unmarshal(&taskCall); err == nil {
|
||||||
d.Task = taskCall.Task
|
d.Task = taskCall.Task
|
||||||
20
internal/taskfile/task.go
Normal file
20
internal/taskfile/task.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
// Tasks representas a group of tasks
|
||||||
|
type Tasks map[string]*Task
|
||||||
|
|
||||||
|
// Task represents a task
|
||||||
|
type Task struct {
|
||||||
|
Task string
|
||||||
|
Cmds []*Cmd
|
||||||
|
Deps []*Dep
|
||||||
|
Desc string
|
||||||
|
Sources []string
|
||||||
|
Generates []string
|
||||||
|
Status []string
|
||||||
|
Dir string
|
||||||
|
Vars Vars
|
||||||
|
Env Vars
|
||||||
|
Silent bool
|
||||||
|
Method string
|
||||||
|
}
|
||||||
35
internal/taskfile/taskfile.go
Normal file
35
internal/taskfile/taskfile.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
// Taskfile represents a Taskfile.yml
|
||||||
|
type Taskfile struct {
|
||||||
|
Version string
|
||||||
|
Expansions int
|
||||||
|
Vars Vars
|
||||||
|
Tasks Tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||||
|
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
if err := unmarshal(&tf.Tasks); err == nil {
|
||||||
|
tf.Version = "1"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskfile struct {
|
||||||
|
Version string
|
||||||
|
Expansions int
|
||||||
|
Vars Vars
|
||||||
|
Tasks Tasks
|
||||||
|
}
|
||||||
|
if err := unmarshal(&taskfile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tf.Version = taskfile.Version
|
||||||
|
tf.Expansions = taskfile.Expansions
|
||||||
|
tf.Vars = taskfile.Vars
|
||||||
|
tf.Tasks = taskfile.Tasks
|
||||||
|
if tf.Expansions <= 0 {
|
||||||
|
tf.Expansions = 2
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@ package taskfile_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCmdParse(t *testing.T) {
|
func TestCmdParse(t *testing.T) {
|
||||||
@@ -33,12 +33,9 @@ vars:
|
|||||||
{
|
{
|
||||||
yamlTaskCall,
|
yamlTaskCall,
|
||||||
&taskfile.Cmd{},
|
&taskfile.Cmd{},
|
||||||
&taskfile.Cmd{Task: "another-task", Vars: &taskfile.Vars{
|
&taskfile.Cmd{Task: "another-task", Vars: taskfile.Vars{
|
||||||
Keys: []string{"PARAM1", "PARAM2"},
|
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
||||||
Mapping: map[string]taskfile.Var{
|
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
||||||
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
|
||||||
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
|
||||||
},
|
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -49,12 +46,9 @@ vars:
|
|||||||
{
|
{
|
||||||
yamlTaskCall,
|
yamlTaskCall,
|
||||||
&taskfile.Dep{},
|
&taskfile.Dep{},
|
||||||
&taskfile.Dep{Task: "another-task", Vars: &taskfile.Vars{
|
&taskfile.Dep{Task: "another-task", Vars: taskfile.Vars{
|
||||||
Keys: []string{"PARAM1", "PARAM2"},
|
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
||||||
Mapping: map[string]taskfile.Var{
|
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
||||||
"PARAM1": taskfile.Var{Static: "VALUE1"},
|
|
||||||
"PARAM2": taskfile.Var{Static: "VALUE2"},
|
|
||||||
},
|
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
58
internal/taskfile/var.go
Normal file
58
internal/taskfile/var.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCantUnmarshalVar is returned for invalid var YAML.
|
||||||
|
ErrCantUnmarshalVar = errors.New("task: can't unmarshal var value")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vars is a string[string] variables map.
|
||||||
|
type Vars map[string]Var
|
||||||
|
|
||||||
|
// ToStringMap converts Vars to a string map containing only the static
|
||||||
|
// variables
|
||||||
|
func (vs Vars) ToStringMap() (m map[string]string) {
|
||||||
|
m = make(map[string]string, len(vs))
|
||||||
|
for k, v := range vs {
|
||||||
|
if v.Sh != "" {
|
||||||
|
// Dynamic variable is not yet resolved; trigger
|
||||||
|
// <no value> to be used in templates.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[k] = v.Static
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Var represents either a static or dynamic variable.
|
||||||
|
type Var struct {
|
||||||
|
Static string
|
||||||
|
Sh string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||||
|
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var str string
|
||||||
|
if err := unmarshal(&str); err == nil {
|
||||||
|
if strings.HasPrefix(str, "$") {
|
||||||
|
v.Sh = strings.TrimPrefix(str, "$")
|
||||||
|
} else {
|
||||||
|
v.Static = str
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sh struct {
|
||||||
|
Sh string
|
||||||
|
}
|
||||||
|
if err := unmarshal(&sh); err == nil {
|
||||||
|
v.Sh = sh.Sh
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrCantUnmarshalVar
|
||||||
|
}
|
||||||
45
internal/taskfile/version/version.go
Normal file
45
internal/taskfile/version/version.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Masterminds/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
v1 = mustVersion("1")
|
||||||
|
v2 = mustVersion("2")
|
||||||
|
|
||||||
|
isV1 = mustConstraint("= 1")
|
||||||
|
isV2 = mustConstraint(">= 2")
|
||||||
|
isV21 = mustConstraint(">= 2.1")
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsV1 returns if is a given Taskfile version is version 1
|
||||||
|
func IsV1(v *semver.Version) bool {
|
||||||
|
return isV1.Check(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsV2 returns if is a given Taskfile version is at least version 2
|
||||||
|
func IsV2(v *semver.Version) bool {
|
||||||
|
return isV2.Check(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsV21 returns if is a given Taskfile version is at least version 2
|
||||||
|
func IsV21(v *semver.Version) bool {
|
||||||
|
return isV21.Check(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustVersion(s string) *semver.Version {
|
||||||
|
v, err := semver.NewVersion(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustConstraint(s string) *semver.Constraints {
|
||||||
|
c, err := semver.NewConstraint(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/go-task/slim-sprig"
|
"github.com/Masterminds/sprig"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ package templater
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Templater is a help struct that allow us to call "replaceX" funcs multiple
|
// Templater is a help struct that allow us to call "replaceX" funcs multiple
|
||||||
@@ -13,15 +12,10 @@ import (
|
|||||||
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
||||||
// return the zero value.
|
// return the zero value.
|
||||||
type Templater struct {
|
type Templater struct {
|
||||||
Vars *taskfile.Vars
|
Vars taskfile.Vars
|
||||||
RemoveNoValue bool
|
|
||||||
|
|
||||||
cacheMap map[string]interface{}
|
strMap map[string]string
|
||||||
err error
|
err error
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Templater) ResetCache() {
|
|
||||||
r.cacheMap = r.Vars.ToCacheMap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) Replace(str string) string {
|
func (r *Templater) Replace(str string) string {
|
||||||
@@ -35,18 +29,15 @@ func (r *Templater) Replace(str string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.cacheMap == nil {
|
if r.strMap == nil {
|
||||||
r.cacheMap = r.Vars.ToCacheMap()
|
r.strMap = r.Vars.ToStringMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err = templ.Execute(&b, r.cacheMap); err != nil {
|
if err = templ.Execute(&b, r.strMap); err != nil {
|
||||||
r.err = err
|
r.err = err
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if r.RemoveNoValue {
|
|
||||||
return strings.ReplaceAll(b.String(), "<no value>", "")
|
|
||||||
}
|
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,22 +53,19 @@ func (r *Templater) ReplaceSlice(strs []string) []string {
|
|||||||
return new
|
return new
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) ReplaceVars(vars *taskfile.Vars) *taskfile.Vars {
|
func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars {
|
||||||
if r.err != nil || vars.Len() == 0 {
|
if r.err != nil || len(vars) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var new taskfile.Vars
|
new := make(taskfile.Vars, len(vars))
|
||||||
vars.Range(func(k string, v taskfile.Var) error {
|
for k, v := range vars {
|
||||||
new.Set(k, taskfile.Var{
|
new[k] = taskfile.Var{
|
||||||
Static: r.Replace(v.Static),
|
Static: r.Replace(v.Static),
|
||||||
Live: v.Live,
|
|
||||||
Sh: r.Replace(v.Sh),
|
Sh: r.Replace(v.Sh),
|
||||||
})
|
}
|
||||||
return nil
|
}
|
||||||
})
|
return new
|
||||||
|
|
||||||
return &new
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) Err() error {
|
func (r *Templater) Err() error {
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package task
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrPreconditionFailed is returned when a precondition fails
|
|
||||||
ErrPreconditionFailed = errors.New("task: precondition not met")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task) (bool, error) {
|
|
||||||
for _, p := range t.Preconditions {
|
|
||||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
|
||||||
Command: p.Sh,
|
|
||||||
Dir: t.Dir,
|
|
||||||
Env: getEnviron(t),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
e.Logger.Errf(logger.Magenta, "task: %s", p.Msg)
|
|
||||||
return false, ErrPreconditionFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
79
status.go
79
status.go
@@ -4,36 +4,35 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
"github.com/go-task/task/internal/execext"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/internal/status"
|
||||||
"github.com/go-task/task/v3/internal/status"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Status returns an error if any the of given tasks is not up-to-date
|
// Status returns an error if any the of given tasks is not up-to-date
|
||||||
func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
|
func (e *Executor) Status(calls ...taskfile.Call) error {
|
||||||
for _, call := range calls {
|
for _, call := range calls {
|
||||||
t, err := e.CompiledTask(call)
|
t, ok := e.Taskfile.Tasks[call.Task]
|
||||||
if err != nil {
|
if !ok {
|
||||||
return err
|
return &taskNotFoundError{taskName: call.Task}
|
||||||
}
|
}
|
||||||
isUpToDate, err := e.isTaskUpToDate(ctx, t)
|
isUpToDate, err := isTaskUpToDate(e.Context, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !isUpToDate {
|
if !isUpToDate {
|
||||||
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Name())
|
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
|
func isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||||
if len(t.Status) > 0 {
|
if len(t.Status) > 0 {
|
||||||
return e.isTaskUpToDateStatus(ctx, t)
|
return isTaskUpToDateStatus(ctx, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
checker, err := e.getStatusChecker(t)
|
checker, err := getStatusChecker(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -41,62 +40,46 @@ func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool,
|
|||||||
return checker.IsUpToDate()
|
return checker.IsUpToDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) statusOnError(t *taskfile.Task) error {
|
func statusOnError(t *taskfile.Task) error {
|
||||||
checker, err := e.getStatusChecker(t)
|
checker, err := getStatusChecker(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return checker.OnError()
|
return checker.OnError()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
func getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
||||||
method := t.Method
|
switch t.Method {
|
||||||
if method == "" {
|
case "", "timestamp":
|
||||||
method = e.Taskfile.Method
|
return &status.Timestamp{
|
||||||
}
|
Dir: t.Dir,
|
||||||
switch method {
|
Sources: t.Sources,
|
||||||
case "timestamp":
|
Generates: t.Generates,
|
||||||
return e.timestampChecker(t), nil
|
}, nil
|
||||||
case "checksum":
|
case "checksum":
|
||||||
return e.checksumChecker(t), nil
|
return &status.Checksum{
|
||||||
|
Dir: t.Dir,
|
||||||
|
Task: t.Task,
|
||||||
|
Sources: t.Sources,
|
||||||
|
}, nil
|
||||||
case "none":
|
case "none":
|
||||||
return status.None{}, nil
|
return status.None{}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf(`task: invalid method "%s"`, method)
|
return nil, fmt.Errorf(`task: invalid method "%s"`, t.Method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
|
func isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||||
return &status.Timestamp{
|
|
||||||
Dir: t.Dir,
|
|
||||||
Sources: t.Sources,
|
|
||||||
Generates: t.Generates,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) checksumChecker(t *taskfile.Task) status.Checker {
|
|
||||||
return &status.Checksum{
|
|
||||||
BaseDir: e.Dir,
|
|
||||||
TaskDir: t.Dir,
|
|
||||||
Task: t.Name(),
|
|
||||||
Sources: t.Sources,
|
|
||||||
Generates: t.Generates,
|
|
||||||
Dry: e.Dry,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
|
|
||||||
for _, s := range t.Status {
|
for _, s := range t.Status {
|
||||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
err := execext.RunCommand(&execext.RunCommandOptions{
|
||||||
|
Context: ctx,
|
||||||
Command: s,
|
Command: s,
|
||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
Env: getEnviron(t),
|
Env: getEnviron(t),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err)
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s)
|
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|||||||
346
task.go
346
task.go
@@ -2,27 +2,26 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/compiler"
|
"github.com/go-task/task/internal/compiler"
|
||||||
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
|
compilerv1 "github.com/go-task/task/internal/compiler/v1"
|
||||||
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
|
compilerv2 "github.com/go-task/task/internal/compiler/v2"
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
"github.com/go-task/task/internal/execext"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/output"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
"github.com/go-task/task/v3/internal/summary"
|
"github.com/go-task/task/internal/taskfile/version"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
"github.com/go-task/task/v3/taskfile/read"
|
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// TaskFilePath is the default Taskfile
|
||||||
|
TaskFilePath = "Taskfile"
|
||||||
// MaximumTaskCall is the max number of times a task can be called.
|
// MaximumTaskCall is the max number of times a task can be called.
|
||||||
// This exists to prevent infinite loops on cyclic dependencies
|
// This exists to prevent infinite loops on cyclic dependencies
|
||||||
MaximumTaskCall = 100
|
MaximumTaskCall = 100
|
||||||
@@ -31,37 +30,28 @@ 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
|
||||||
|
Watch bool
|
||||||
|
Verbose bool
|
||||||
|
Silent bool
|
||||||
|
|
||||||
Dir string
|
Context context.Context
|
||||||
Entrypoint string
|
|
||||||
Force bool
|
|
||||||
Watch bool
|
|
||||||
Verbose bool
|
|
||||||
Silent bool
|
|
||||||
Dry bool
|
|
||||||
Summary bool
|
|
||||||
Parallel bool
|
|
||||||
Color bool
|
|
||||||
Concurrency int
|
|
||||||
|
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
Compiler compiler.Compiler
|
Compiler compiler.Compiler
|
||||||
Output output.Output
|
|
||||||
OutputStyle string
|
|
||||||
|
|
||||||
taskvars *taskfile.Vars
|
taskvars taskfile.Vars
|
||||||
|
|
||||||
concurrencySemaphore chan struct{}
|
taskCallCount map[string]*int32
|
||||||
taskCallCount map[string]*int32
|
|
||||||
mkdirMutexMap map[string]*sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs Task
|
// Run runs Task
|
||||||
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
func (e *Executor) Run(calls ...taskfile.Call) error {
|
||||||
// check if given tasks exist
|
// check if given tasks exist
|
||||||
for _, c := range calls {
|
for _, c := range calls {
|
||||||
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
||||||
@@ -71,60 +61,32 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Summary {
|
|
||||||
for i, c := range calls {
|
|
||||||
compiledTask, err := e.FastCompiledTask(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
summary.PrintSpaceBetweenSummaries(e.Logger, i)
|
|
||||||
summary.PrintTask(e.Logger, compiledTask)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Watch {
|
if e.Watch {
|
||||||
return e.watchTasks(calls...)
|
return e.watchTasks(calls...)
|
||||||
}
|
}
|
||||||
|
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
|
||||||
for _, c := range calls {
|
for _, c := range calls {
|
||||||
c := c
|
if err := e.RunTask(e.Context, c); err != nil {
|
||||||
if e.Parallel {
|
return err
|
||||||
g.Go(func() error { return e.RunTask(ctx, c) })
|
|
||||||
} else {
|
|
||||||
if err := e.RunTask(ctx, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return g.Wait()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 == "" {
|
if err := e.readTaskfile(); err != nil {
|
||||||
e.Entrypoint = "Taskfile.yml"
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := e.Taskfile.ParsedVersion()
|
v, err := semver.NewVersion(e.Taskfile.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf(`task: could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v < 3.0 {
|
if e.Context == nil {
|
||||||
e.taskvars, err = read.Taskvars(e.Dir)
|
e.Context = context.Background()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Stdin == nil {
|
if e.Stdin == nil {
|
||||||
e.Stdin = os.Stdin
|
e.Stdin = os.Stdin
|
||||||
}
|
}
|
||||||
@@ -138,28 +100,15 @@ func (e *Executor) Setup() error {
|
|||||||
Stdout: e.Stdout,
|
Stdout: e.Stdout,
|
||||||
Stderr: e.Stderr,
|
Stderr: e.Stderr,
|
||||||
Verbose: e.Verbose,
|
Verbose: e.Verbose,
|
||||||
Color: e.Color,
|
|
||||||
}
|
}
|
||||||
|
switch {
|
||||||
if v < 2 {
|
case version.IsV1(v):
|
||||||
return fmt.Errorf(`task: Taskfile versions prior to v2 are not supported anymore`)
|
e.Compiler = &compilerv1.CompilerV1{
|
||||||
}
|
Dir: e.Dir,
|
||||||
|
Vars: e.taskvars,
|
||||||
// consider as equal to the greater version if round
|
Logger: e.Logger,
|
||||||
if v == 2.0 {
|
}
|
||||||
v = 2.6
|
case version.IsV2(v):
|
||||||
}
|
|
||||||
|
|
||||||
if v > 3.0 {
|
|
||||||
return fmt.Errorf(`task: Taskfile versions greater than v3.0 not implemented in the version of Task`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Color available only on v3
|
|
||||||
if v < 3 {
|
|
||||||
e.Logger.Color = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if v < 3 {
|
|
||||||
e.Compiler = &compilerv2.CompilerV2{
|
e.Compiler = &compilerv2.CompilerV2{
|
||||||
Dir: e.Dir,
|
Dir: e.Dir,
|
||||||
Taskvars: e.taskvars,
|
Taskvars: e.taskvars,
|
||||||
@@ -167,91 +116,13 @@ func (e *Executor) Setup() error {
|
|||||||
Expansions: e.Taskfile.Expansions,
|
Expansions: e.Taskfile.Expansions,
|
||||||
Logger: e.Logger,
|
Logger: e.Logger,
|
||||||
}
|
}
|
||||||
} else {
|
case version.IsV21(v):
|
||||||
e.Compiler = &compilerv3.CompilerV3{
|
return fmt.Errorf(`task: Taskfile versions greater than v2 not implemented in the version of Task`)
|
||||||
Dir: e.Dir,
|
|
||||||
TaskfileEnv: e.Taskfile.Env,
|
|
||||||
TaskfileVars: e.Taskfile.Vars,
|
|
||||||
Logger: e.Logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v < 2.1 && e.Taskfile.Output != "" {
|
|
||||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
|
||||||
}
|
|
||||||
if v < 2.2 && e.Taskfile.Includes.Len() > 0 {
|
|
||||||
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
|
||||||
}
|
|
||||||
if v >= 3.0 && e.Taskfile.Expansions > 2 {
|
|
||||||
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.OutputStyle != "" {
|
|
||||||
e.Taskfile.Output = e.OutputStyle
|
|
||||||
}
|
|
||||||
switch e.Taskfile.Output {
|
|
||||||
case "", "interleaved":
|
|
||||||
e.Output = output.Interleaved{}
|
|
||||||
case "group":
|
|
||||||
e.Output = output.Group{}
|
|
||||||
case "prefixed":
|
|
||||||
e.Output = output.Prefixed{}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Taskfile.Method == "" {
|
|
||||||
if v >= 3 {
|
|
||||||
e.Taskfile.Method = "checksum"
|
|
||||||
} else {
|
|
||||||
e.Taskfile.Method = "timestamp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v <= 2.1 {
|
|
||||||
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
|
||||||
|
|
||||||
for _, task := range e.Taskfile.Tasks {
|
|
||||||
if task.IgnoreError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, cmd := range task.Cmds {
|
|
||||||
if cmd.IgnoreError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v < 2.6 {
|
|
||||||
for _, task := range e.Taskfile.Tasks {
|
|
||||||
if len(task.Preconditions) > 0 {
|
|
||||||
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v < 3 {
|
|
||||||
err := e.Taskfile.Includes.Range(func(_ string, taskfile taskfile.IncludedTaskfile) error {
|
|
||||||
if taskfile.AdvancedImport {
|
|
||||||
return errors.New(`task: Import with additional parameters is only available starting on Taskfile version v3`)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Concurrency > 0 {
|
|
||||||
e.concurrencySemaphore = make(chan struct{}, e.Concurrency)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -266,85 +137,42 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
return &MaximumTaskCallExceededError{task: call.Task}
|
return &MaximumTaskCallExceededError{task: call.Task}
|
||||||
}
|
}
|
||||||
|
|
||||||
release := e.acquireConcurrencyLimit()
|
|
||||||
defer release()
|
|
||||||
|
|
||||||
if err := e.runDeps(ctx, t); err != nil {
|
if err := e.runDeps(ctx, t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !e.Force {
|
if !e.Force {
|
||||||
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
|
upToDate, err := isTaskUpToDate(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if upToDate {
|
||||||
upToDate, err := e.isTaskUpToDate(ctx, t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if upToDate && preCondMet {
|
|
||||||
if !e.Silent {
|
if !e.Silent {
|
||||||
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Name())
|
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.mkdir(t); err != nil {
|
|
||||||
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v", t.Dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range t.Cmds {
|
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 := statusOnError(t); err2 != nil {
|
||||||
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v", err2)
|
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if execext.IsExitError(err) && t.IgnoreError {
|
|
||||||
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return &taskRunError{t.Task, err}
|
return &taskRunError{t.Task, err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
||||||
reacquire := e.releaseConcurrencyLimit()
|
|
||||||
defer reacquire()
|
|
||||||
|
|
||||||
for _, d := range t.Deps {
|
for _, d := range t.Deps {
|
||||||
d := d
|
d := d
|
||||||
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
err := e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
return e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,56 +182,23 @@ func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
|||||||
func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error {
|
func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error {
|
||||||
cmd := t.Cmds[i]
|
cmd := t.Cmds[i]
|
||||||
|
|
||||||
switch {
|
if cmd.Cmd == "" {
|
||||||
case cmd.Task != "":
|
return e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
||||||
reacquire := e.releaseConcurrencyLimit()
|
|
||||||
defer reacquire()
|
|
||||||
|
|
||||||
err := e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case cmd.Cmd != "":
|
|
||||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
|
||||||
e.Logger.Errf(logger.Green, "task: %s", cmd.Cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Dry {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
|
|
||||||
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
|
|
||||||
defer func() {
|
|
||||||
if _, ok := stdOut.(*os.File); !ok {
|
|
||||||
if closer, ok := stdOut.(io.Closer); ok {
|
|
||||||
closer.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := stdErr.(*os.File); !ok {
|
|
||||||
if closer, ok := stdErr.(io.Closer); ok {
|
|
||||||
closer.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
|
||||||
Command: cmd.Cmd,
|
|
||||||
Dir: t.Dir,
|
|
||||||
Env: getEnviron(t),
|
|
||||||
Stdin: e.Stdin,
|
|
||||||
Stdout: stdOut,
|
|
||||||
Stderr: stdErr,
|
|
||||||
})
|
|
||||||
if execext.IsExitError(err) && cmd.IgnoreError {
|
|
||||||
e.Logger.VerboseErrf(logger.Yellow, "task: command error ignored: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
|
||||||
|
e.Logger.Errf(cmd.Cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return execext.RunCommand(&execext.RunCommandOptions{
|
||||||
|
Context: ctx,
|
||||||
|
Command: cmd.Cmd,
|
||||||
|
Dir: t.Dir,
|
||||||
|
Env: getEnviron(t),
|
||||||
|
Stdin: e.Stdin,
|
||||||
|
Stdout: e.Stdout,
|
||||||
|
Stderr: e.Stderr,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEnviron(t *taskfile.Task) []string {
|
func getEnviron(t *taskfile.Task) []string {
|
||||||
@@ -411,20 +206,9 @@ func getEnviron(t *taskfile.Task) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
environ := os.Environ()
|
envs := os.Environ()
|
||||||
|
for k, v := range t.Env.ToStringMap() {
|
||||||
for k, v := range t.Env.ToCacheMap() {
|
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
||||||
str, isString := v.(string)
|
|
||||||
if !isString {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
environ = append(environ, fmt.Sprintf("%s=%s", k, str))
|
|
||||||
}
|
}
|
||||||
|
return envs
|
||||||
return environ
|
|
||||||
}
|
}
|
||||||
|
|||||||
643
task_test.go
643
task_test.go
@@ -2,19 +2,17 @@ package task_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/go-task/task"
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
|
||||||
"github.com/go-task/task/v3"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// fileContentTest provides a basic reusable test-case for running a Taskfile
|
// fileContentTest provides a basic reusable test-case for running a Taskfile
|
||||||
@@ -41,7 +39,7 @@ func (fct fileContentTest) Run(t *testing.T) {
|
|||||||
Stderr: ioutil.Discard,
|
Stderr: ioutil.Discard,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: fct.Target}), "e.Run(target)")
|
assert.NoError(t, e.Run(taskfile.Call{Task: fct.Target}), "e.Run(target)")
|
||||||
|
|
||||||
for name, expectContent := range fct.Files {
|
for name, expectContent := range fct.Files {
|
||||||
t.Run(fct.name(name), func(t *testing.T) {
|
t.Run(fct.name(name), func(t *testing.T) {
|
||||||
@@ -54,16 +52,7 @@ func (fct fileContentTest) Run(t *testing.T) {
|
|||||||
assert.Equal(t, expectContent, s, "unexpected file content")
|
assert.Equal(t, expectContent, s, "unexpected file content")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyTask(t *testing.T) {
|
|
||||||
e := &task.Executor{
|
|
||||||
Dir: "testdata/empty_task",
|
|
||||||
Stdout: ioutil.Discard,
|
|
||||||
Stderr: ioutil.Discard,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnv(t *testing.T) {
|
func TestEnv(t *testing.T) {
|
||||||
@@ -72,13 +61,49 @@ func TestEnv(t *testing.T) {
|
|||||||
Target: "default",
|
Target: "default",
|
||||||
TrimSpace: false,
|
TrimSpace: false,
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
"env.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
||||||
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVarsV1(t *testing.T) {
|
||||||
|
tt := fileContentTest{
|
||||||
|
Dir: "testdata/vars/v1",
|
||||||
|
Target: "default",
|
||||||
|
TrimSpace: true,
|
||||||
|
Files: map[string]string{
|
||||||
|
// hello task:
|
||||||
|
"foo.txt": "foo",
|
||||||
|
"bar.txt": "bar",
|
||||||
|
"baz.txt": "baz",
|
||||||
|
"tmpl_foo.txt": "foo",
|
||||||
|
"tmpl_bar.txt": "<no value>",
|
||||||
|
"tmpl_foo2.txt": "foo2",
|
||||||
|
"tmpl_bar2.txt": "bar2",
|
||||||
|
"shtmpl_foo.txt": "foo",
|
||||||
|
"shtmpl_foo2.txt": "foo2",
|
||||||
|
"nestedtmpl_foo.txt": "{{.FOO}}",
|
||||||
|
"nestedtmpl_foo2.txt": "foo2",
|
||||||
|
"foo2.txt": "foo2",
|
||||||
|
"bar2.txt": "bar2",
|
||||||
|
"baz2.txt": "baz2",
|
||||||
|
"tmpl2_foo.txt": "<no value>",
|
||||||
|
"tmpl2_foo2.txt": "foo2",
|
||||||
|
"tmpl2_bar.txt": "<no value>",
|
||||||
|
"tmpl2_bar2.txt": "<no value>",
|
||||||
|
"shtmpl2_foo.txt": "<no value>",
|
||||||
|
"shtmpl2_foo2.txt": "foo2",
|
||||||
|
"nestedtmpl2_foo2.txt": "{{.FOO2}}",
|
||||||
|
"override.txt": "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Run(t)
|
||||||
|
// Ensure identical results when running hello task directly.
|
||||||
|
tt.Target = "hello"
|
||||||
|
tt.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestVarsV2(t *testing.T) {
|
func TestVarsV2(t *testing.T) {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
Dir: "testdata/vars/v2",
|
Dir: "testdata/vars/v2",
|
||||||
@@ -108,7 +133,6 @@ func TestVarsV2(t *testing.T) {
|
|||||||
"nestedtmpl2_foo2.txt": "<no value>",
|
"nestedtmpl2_foo2.txt": "<no value>",
|
||||||
"override.txt": "bar",
|
"override.txt": "bar",
|
||||||
"nested.txt": "Taskvars-TaskfileVars-TaskVars",
|
"nested.txt": "Taskvars-TaskfileVars-TaskVars",
|
||||||
"task_name.txt": "hello",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
@@ -117,23 +141,8 @@ func TestVarsV2(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVarsV3(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/vars/v3",
|
|
||||||
Target: "default",
|
|
||||||
Files: map[string]string{
|
|
||||||
"missing-var.txt": "\n",
|
|
||||||
"var-order.txt": "ABCDEF\n",
|
|
||||||
"dependent-sh.txt": "123456\n",
|
|
||||||
"with-call.txt": "Hi, ABC123!\n",
|
|
||||||
"from-dot-env.txt": "From .env file\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultilineVars(t *testing.T) {
|
func TestMultilineVars(t *testing.T) {
|
||||||
for _, dir := range []string{"testdata/vars/v2/multiline"} {
|
for _, dir := range []string{"testdata/vars/v1/multiline", "testdata/vars/v2/multiline"} {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
Target: "default",
|
Target: "default",
|
||||||
@@ -157,7 +166,7 @@ func TestMultilineVars(t *testing.T) {
|
|||||||
|
|
||||||
func TestVarsInvalidTmpl(t *testing.T) {
|
func TestVarsInvalidTmpl(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
dir = "testdata/vars/v2"
|
dir = "testdata/vars/v1"
|
||||||
target = "invalid-var-tmpl"
|
target = "invalid-var-tmpl"
|
||||||
expectError = "template: :1: unexpected EOF"
|
expectError = "template: :1: unexpected EOF"
|
||||||
)
|
)
|
||||||
@@ -168,23 +177,7 @@ func TestVarsInvalidTmpl(t *testing.T) {
|
|||||||
Stderr: ioutil.Discard,
|
Stderr: ioutil.Discard,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||||
assert.EqualError(t, e.Run(context.Background(), taskfile.Call{Task: target}), expectError, "e.Run(target)")
|
assert.EqualError(t, e.Run(taskfile.Call{Task: target}), expectError, "e.Run(target)")
|
||||||
}
|
|
||||||
|
|
||||||
func TestConcurrency(t *testing.T) {
|
|
||||||
const (
|
|
||||||
dir = "testdata/concurrency"
|
|
||||||
target = "default"
|
|
||||||
)
|
|
||||||
|
|
||||||
e := &task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: ioutil.Discard,
|
|
||||||
Stderr: ioutil.Discard,
|
|
||||||
Concurrency: 1,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}), "e.Run(target)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParams(t *testing.T) {
|
func TestParams(t *testing.T) {
|
||||||
@@ -236,12 +229,12 @@ func TestDeps(t *testing.T) {
|
|||||||
Stderr: ioutil.Discard,
|
Stderr: ioutil.Discard,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: "default"}))
|
||||||
|
|
||||||
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 exist", f)
|
t.Errorf("File %s should exists", f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,7 +246,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 exist: %v", err)
|
t.Errorf("File should not exists: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
@@ -264,78 +257,35 @@ func TestStatus(t *testing.T) {
|
|||||||
Silent: true,
|
Silent: true,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
|
||||||
|
|
||||||
if _, err := os.Stat(file); err != nil {
|
if _, err := os.Stat(file); err != nil {
|
||||||
t.Errorf("File should exist: %v", err)
|
t.Errorf("File should exists: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Silent = false
|
e.Silent = false
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
|
||||||
|
|
||||||
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
|
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
|
||||||
t.Errorf("Wrong output message: %s", buff.String())
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrecondition(t *testing.T) {
|
|
||||||
const dir = "testdata/precondition"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := &task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
}
|
|
||||||
|
|
||||||
// A precondition that has been met
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
|
||||||
if buff.String() != "" {
|
|
||||||
t.Errorf("Got Output when none was expected: %s", buff.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// A precondition that was not met
|
|
||||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "impossible"}))
|
|
||||||
|
|
||||||
if buff.String() != "task: 1 != 0 obviously!\n" {
|
|
||||||
t.Errorf("Wrong output message: %s", buff.String())
|
|
||||||
}
|
|
||||||
buff.Reset()
|
|
||||||
|
|
||||||
// Calling a task with a precondition in a dependency fails the task
|
|
||||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "depends_on_impossible"}))
|
|
||||||
|
|
||||||
if buff.String() != "task: 1 != 0 obviously!\n" {
|
|
||||||
t.Errorf("Wrong output message: %s", buff.String())
|
|
||||||
}
|
|
||||||
buff.Reset()
|
|
||||||
|
|
||||||
// Calling a task with a precondition in a cmd fails the task
|
|
||||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "executes_failing_task_as_cmd"}))
|
|
||||||
if buff.String() != "task: 1 != 0 obviously!\n" {
|
|
||||||
t.Errorf("Wrong output message: %s", buff.String())
|
|
||||||
}
|
|
||||||
buff.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerates(t *testing.T) {
|
func TestGenerates(t *testing.T) {
|
||||||
const dir = "testdata/generates"
|
var srcTask = "sub/src.txt"
|
||||||
|
var relTask = "rel.txt"
|
||||||
const (
|
var absTask = "abs.txt"
|
||||||
srcTask = "sub/src.txt"
|
|
||||||
relTask = "rel.txt"
|
|
||||||
absTask = "abs.txt"
|
|
||||||
fileWithSpaces = "my text file.txt"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// This test does not work with a relative dir.
|
||||||
|
dir, err := filepath.Abs("testdata/generates")
|
||||||
|
assert.NoError(t, err)
|
||||||
var srcFile = filepath.Join(dir, srcTask)
|
var srcFile = filepath.Join(dir, srcTask)
|
||||||
|
|
||||||
for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
|
for _, task := range []string{srcTask, relTask, absTask} {
|
||||||
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 exist: %v", err)
|
t.Errorf("File should not exists: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,19 +297,19 @@ func TestGenerates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
for _, theTask := range []string{relTask, absTask, fileWithSpaces} {
|
for _, theTask := range []string{relTask, absTask} {
|
||||||
var destFile = filepath.Join(dir, theTask)
|
var destFile = filepath.Join(dir, theTask)
|
||||||
var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
|
var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
|
||||||
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
|
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
|
||||||
|
|
||||||
// Run task for the first time.
|
// Run task for the first time.
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: theTask}))
|
||||||
|
|
||||||
if _, err := os.Stat(srcFile); err != nil {
|
if _, err := os.Stat(srcFile); err != nil {
|
||||||
t.Errorf("File should exist: %v", err)
|
t.Errorf("File should exists: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(destFile); err != nil {
|
if _, err := os.Stat(destFile); err != nil {
|
||||||
t.Errorf("File should exist: %v", err)
|
t.Errorf("File should exists: %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 {
|
||||||
@@ -368,7 +318,7 @@ func TestGenerates(t *testing.T) {
|
|||||||
buff.Reset()
|
buff.Reset()
|
||||||
|
|
||||||
// Re-run task to ensure it's now found to be up-to-date.
|
// Re-run task to ensure it's now found to be up-to-date.
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: theTask}))
|
||||||
if buff.String() != upToDate {
|
if buff.String() != upToDate {
|
||||||
t.Errorf("Wrong output message: %s", buff.String())
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
}
|
}
|
||||||
@@ -399,136 +349,24 @@ func TestStatusChecksum(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
_, err := os.Stat(filepath.Join(dir, f))
|
_, err := os.Stat(filepath.Join(dir, f))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buff.Reset()
|
buff.Reset()
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
|
||||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLabelUpToDate(t *testing.T) {
|
|
||||||
const dir = "testdata/label_uptodate"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
|
||||||
assert.Contains(t, buff.String(), "foobar")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLabelSummary(t *testing.T) {
|
|
||||||
const dir = "testdata/label_summary"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Summary: true,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
|
||||||
assert.Contains(t, buff.String(), "foobar")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLabelInStatus(t *testing.T) {
|
|
||||||
const dir = "testdata/label_status"
|
|
||||||
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
err := e.Status(context.Background(), taskfile.Call{Task: "foo"})
|
|
||||||
if assert.Error(t, err) {
|
|
||||||
assert.Contains(t, err.Error(), "foobar")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLabelWithVariableExpansion(t *testing.T) {
|
|
||||||
const dir = "testdata/label_var"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
|
||||||
assert.Contains(t, buff.String(), "foobaz")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLabelInSummary(t *testing.T) {
|
|
||||||
const dir = "testdata/label_summary"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
|
||||||
assert.Contains(t, buff.String(), "foobar")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLabelInList(t *testing.T) {
|
|
||||||
const dir = "testdata/label_list"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
e.PrintTasksHelp()
|
|
||||||
assert.Contains(t, buff.String(), "foobar")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatusVariables(t *testing.T) {
|
|
||||||
const dir = "testdata/status_vars"
|
|
||||||
|
|
||||||
_ = os.RemoveAll(filepath.Join(dir, ".task"))
|
|
||||||
_ = os.Remove(filepath.Join(dir, "generated.txt"))
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
Silent: false,
|
|
||||||
Verbose: true,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
|
||||||
|
|
||||||
assert.Contains(t, buff.String(), "d41d8cd98f00b204e9800998ecf8427e")
|
|
||||||
|
|
||||||
inf, err := os.Stat(filepath.Join(dir, "source.txt"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
ts := fmt.Sprintf("%d", inf.ModTime().Unix())
|
|
||||||
tf := fmt.Sprintf("%s", inf.ModTime())
|
|
||||||
|
|
||||||
assert.Contains(t, buff.String(), ts)
|
|
||||||
assert.Contains(t, buff.String(), tf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
func TestInit(t *testing.T) {
|
||||||
const dir = "testdata/init"
|
const dir = "testdata/init"
|
||||||
var file = filepath.Join(dir, "Taskfile.yml")
|
var file = filepath.Join(dir, "Taskfile.yml")
|
||||||
|
|
||||||
_ = 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 exist")
|
t.Errorf("Taskfile.yml should not exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
|
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
|
||||||
@@ -536,7 +374,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 exist")
|
t.Errorf("Taskfile.yml should exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +387,7 @@ func TestCyclicDep(t *testing.T) {
|
|||||||
Stderr: ioutil.Discard,
|
Stderr: ioutil.Discard,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(context.Background(), taskfile.Call{Task: "task-1"}))
|
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(taskfile.Call{Task: "task-1"}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskVersion(t *testing.T) {
|
func TestTaskVersion(t *testing.T) {
|
||||||
@@ -557,6 +395,7 @@ func TestTaskVersion(t *testing.T) {
|
|||||||
Dir string
|
Dir string
|
||||||
Version string
|
Version string
|
||||||
}{
|
}{
|
||||||
|
{"testdata/version/v1", "1"},
|
||||||
{"testdata/version/v2", "2"},
|
{"testdata/version/v2", "2"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,345 +412,3 @@ func TestTaskVersion(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskIgnoreErrors(t *testing.T) {
|
|
||||||
const dir = "testdata/ignore_errors"
|
|
||||||
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: ioutil.Discard,
|
|
||||||
Stderr: ioutil.Discard,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-should-pass"}))
|
|
||||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "task-should-fail"}))
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "cmd-should-pass"}))
|
|
||||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "cmd-should-fail"}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExpand(t *testing.T) {
|
|
||||||
const dir = "testdata/expand"
|
|
||||||
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Couldn't get $HOME: %v", err)
|
|
||||||
}
|
|
||||||
var buff bytes.Buffer
|
|
||||||
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "pwd"}))
|
|
||||||
assert.Equal(t, home, strings.TrimSpace(buff.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDry(t *testing.T) {
|
|
||||||
const dir = "testdata/dry"
|
|
||||||
|
|
||||||
file := filepath.Join(dir, "file.txt")
|
|
||||||
_ = os.Remove(file)
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
Dry: true,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
|
||||||
|
|
||||||
assert.Equal(t, "task: touch file.txt", strings.TrimSpace(buff.String()))
|
|
||||||
if _, err := os.Stat(file); err == nil {
|
|
||||||
t.Errorf("File should not exist %s", file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDryChecksum tests if the checksum file is not being written to disk
|
|
||||||
// if the dry mode is enabled.
|
|
||||||
func TestDryChecksum(t *testing.T) {
|
|
||||||
const dir = "testdata/dry_checksum"
|
|
||||||
|
|
||||||
checksumFile := filepath.Join(dir, ".task/checksum/default")
|
|
||||||
_ = os.Remove(checksumFile)
|
|
||||||
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: ioutil.Discard,
|
|
||||||
Stderr: ioutil.Discard,
|
|
||||||
Dry: true,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
|
||||||
|
|
||||||
_, err := os.Stat(checksumFile)
|
|
||||||
assert.Error(t, err, "checksum file should not exist")
|
|
||||||
|
|
||||||
e.Dry = false
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
|
||||||
_, err = os.Stat(checksumFile)
|
|
||||||
assert.NoError(t, err, "checksum file should exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIncludes(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/includes",
|
|
||||||
Target: "default",
|
|
||||||
TrimSpace: true,
|
|
||||||
Files: map[string]string{
|
|
||||||
"main.txt": "main",
|
|
||||||
"included_directory.txt": "included_directory",
|
|
||||||
"included_directory_without_dir.txt": "included_directory_without_dir",
|
|
||||||
"included_taskfile_without_dir.txt": "included_taskfile_without_dir",
|
|
||||||
"./module2/included_directory_with_dir.txt": "included_directory_with_dir",
|
|
||||||
"./module2/included_taskfile_with_dir.txt": "included_taskfile_with_dir",
|
|
||||||
"os_include.txt": "os",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIncorrectVersionIncludes(t *testing.T) {
|
|
||||||
const dir = "testdata/incorrect_includes"
|
|
||||||
expectedError := "task: Import with additional parameters is only available starting on Taskfile version v3"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
Silent: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.EqualError(t, e.Setup(), expectedError)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIncludesEmptyMain(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/includes_empty",
|
|
||||||
Target: "included:default",
|
|
||||||
TrimSpace: true,
|
|
||||||
Files: map[string]string{
|
|
||||||
"file.txt": "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIncludesDependencies(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/includes_deps",
|
|
||||||
Target: "default",
|
|
||||||
TrimSpace: true,
|
|
||||||
Files: map[string]string{
|
|
||||||
"default.txt": "default",
|
|
||||||
"called_dep.txt": "called_dep",
|
|
||||||
"called_task.txt": "called_task",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIncludesCallingRoot(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/includes_call_root_task",
|
|
||||||
Target: "included:call-root",
|
|
||||||
TrimSpace: true,
|
|
||||||
Files: map[string]string{
|
|
||||||
"root_task.txt": "root task",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSummary(t *testing.T) {
|
|
||||||
const dir = "testdata/summary"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
Summary: true,
|
|
||||||
Silent: true,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
expectedOutput := string(data)
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
expectedOutput = strings.Replace(expectedOutput, "\r\n", "\n", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, expectedOutput, buff.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
|
|
||||||
const expected = "dir"
|
|
||||||
const dir = "testdata/" + expected
|
|
||||||
var out bytes.Buffer
|
|
||||||
e := &task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &out,
|
|
||||||
Stderr: &out,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
|
|
||||||
|
|
||||||
// got should be the "dir" part of "testdata/dir"
|
|
||||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
|
||||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
|
|
||||||
const expected = "exists"
|
|
||||||
const dir = "testdata/dir/explicit_exists"
|
|
||||||
var out bytes.Buffer
|
|
||||||
e := &task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &out,
|
|
||||||
Stderr: &out,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
|
|
||||||
|
|
||||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
|
||||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
|
|
||||||
const expected = "createme"
|
|
||||||
const dir = "testdata/dir/explicit_doesnt_exist/"
|
|
||||||
const toBeCreated = dir + expected
|
|
||||||
const target = "whereami"
|
|
||||||
var out bytes.Buffer
|
|
||||||
e := &task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &out,
|
|
||||||
Stderr: &out,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that the directory to be created doesn't actually exist.
|
|
||||||
_ = os.RemoveAll(toBeCreated)
|
|
||||||
if _, err := os.Stat(toBeCreated); err == nil {
|
|
||||||
t.Errorf("Directory should not exist: %v", err)
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}))
|
|
||||||
|
|
||||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
|
||||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
|
||||||
|
|
||||||
// Clean-up after ourselves only if no error.
|
|
||||||
_ = os.RemoveAll(toBeCreated)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamicVariablesShouldRunOnTheTaskDir(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/dir/dynamic_var",
|
|
||||||
Target: "default",
|
|
||||||
TrimSpace: false,
|
|
||||||
Files: map[string]string{
|
|
||||||
"subdirectory/from_root_taskfile.txt": "subdirectory\n",
|
|
||||||
"subdirectory/from_included_taskfile.txt": "subdirectory\n",
|
|
||||||
"subdirectory/from_included_taskfile_task.txt": "subdirectory\n",
|
|
||||||
"subdirectory/from_interpolated_dir.txt": "subdirectory\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDisplaysErrorOnUnsupportedVersion(t *testing.T) {
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: "testdata/version/v1",
|
|
||||||
Stdout: ioutil.Discard,
|
|
||||||
Stderr: ioutil.Discard,
|
|
||||||
}
|
|
||||||
err := e.Setup()
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Equal(t, "task: Taskfile versions prior to v2 are not supported anymore", err.Error())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShortTaskNotation(t *testing.T) {
|
|
||||||
const dir = "testdata/short_task_notation"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
Silent: true,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
|
||||||
assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/dotenv/default",
|
|
||||||
Target: "default",
|
|
||||||
TrimSpace: false,
|
|
||||||
Files: map[string]string{
|
|
||||||
"include.txt": "INCLUDE1='from_include1' INCLUDE2='from_include2'\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
|
|
||||||
const dir = "testdata/dotenv/error_included_envs"
|
|
||||||
const entry = "Taskfile.yml"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Entrypoint: entry,
|
|
||||||
Summary: true,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := e.Setup()
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "move the dotenv")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDotenvShouldAllowMissingEnv(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/dotenv/missing_env",
|
|
||||||
Target: "default",
|
|
||||||
TrimSpace: false,
|
|
||||||
Files: map[string]string{
|
|
||||||
"include.txt": "INCLUDE1='' INCLUDE2=''\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExitImmediately(t *testing.T) {
|
|
||||||
const dir = "testdata/exit_immediately"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
Silent: true,
|
|
||||||
}
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
|
|
||||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
|
||||||
assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
|
|
||||||
}
|
|
||||||
|
|||||||
74
taskfile.go
Normal file
74
taskfile.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readTaskfile parses Taskfile from the disk
|
||||||
|
func (e *Executor) readTaskfile() error {
|
||||||
|
path := filepath.Join(e.Dir, TaskFilePath)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
e.Taskfile, err = e.readTaskfileData(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
osTasks, err := e.readTaskfileData(fmt.Sprintf("%s_%s", path, runtime.GOOS))
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case taskFileNotFound:
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := mergo.MapWithOverwrite(&e.Taskfile.Tasks, osTasks.Tasks); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, task := range e.Taskfile.Tasks {
|
||||||
|
task.Task = name
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.readTaskvars()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Executor) readTaskfileData(path string) (*taskfile.Taskfile, error) {
|
||||||
|
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
|
||||||
|
var taskfile taskfile.Taskfile
|
||||||
|
return &taskfile, yaml.Unmarshal(b, &taskfile)
|
||||||
|
}
|
||||||
|
return nil, taskFileNotFound{path}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Executor) readTaskvars() error {
|
||||||
|
var (
|
||||||
|
file = filepath.Join(e.Dir, TaskvarsFilePath)
|
||||||
|
osSpecificFile = fmt.Sprintf("%s_%s", file, runtime.GOOS)
|
||||||
|
)
|
||||||
|
|
||||||
|
if b, err := ioutil.ReadFile(file + ".yml"); err == nil {
|
||||||
|
if err := yaml.Unmarshal(b, &e.taskvars); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, err := ioutil.ReadFile(osSpecificFile + ".yml"); err == nil {
|
||||||
|
osTaskvars := make(taskfile.Vars, 10)
|
||||||
|
if err := yaml.Unmarshal(b, &osTaskvars); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for k, v := range osTaskvars {
|
||||||
|
e.taskvars[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package taskfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrCantUnmarshalIncludedTaskfile is returned for invalid var YAML.
|
|
||||||
ErrCantUnmarshalIncludedTaskfile = errors.New("task: can't unmarshal included value")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IncludedTaskfile represents information about included tasksfile
|
|
||||||
type IncludedTaskfile struct {
|
|
||||||
Taskfile string
|
|
||||||
Dir string
|
|
||||||
AdvancedImport bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IncludedTaskfiles represents information about included tasksfiles
|
|
||||||
type IncludedTaskfiles struct {
|
|
||||||
Keys []string
|
|
||||||
Mapping map[string]IncludedTaskfile
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
||||||
func (tfs *IncludedTaskfiles) UnmarshalYAML(node *yaml.Node) error {
|
|
||||||
if node.Kind != yaml.MappingNode {
|
|
||||||
return errors.New("task: includes is not a map")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(@andreynering): on this style of custom unmarsheling,
|
|
||||||
// even number contains the keys, while odd numbers contains
|
|
||||||
// the values.
|
|
||||||
for i := 0; i < len(node.Content); i += 2 {
|
|
||||||
keyNode := node.Content[i]
|
|
||||||
valueNode := node.Content[i+1]
|
|
||||||
|
|
||||||
var v IncludedTaskfile
|
|
||||||
if err := valueNode.Decode(&v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tfs.Set(keyNode.Value, v)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the length of the map
|
|
||||||
func (tfs *IncludedTaskfiles) Len() int {
|
|
||||||
if tfs == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return len(tfs.Keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge merges the given IncludedTaskfiles into the caller one
|
|
||||||
func (tfs *IncludedTaskfiles) Merge(other *IncludedTaskfiles) {
|
|
||||||
other.Range(func(key string, value IncludedTaskfile) error {
|
|
||||||
tfs.Set(key, value)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets a value to a given key
|
|
||||||
func (tfs *IncludedTaskfiles) Set(key string, includedTaskfile IncludedTaskfile) {
|
|
||||||
if tfs.Mapping == nil {
|
|
||||||
tfs.Mapping = make(map[string]IncludedTaskfile, 1)
|
|
||||||
}
|
|
||||||
if !stringSliceContains(tfs.Keys, key) {
|
|
||||||
tfs.Keys = append(tfs.Keys, key)
|
|
||||||
}
|
|
||||||
tfs.Mapping[key] = includedTaskfile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range allows you to loop into the included taskfiles in its right order
|
|
||||||
func (tfs *IncludedTaskfiles) Range(yield func(key string, includedTaskfile IncludedTaskfile) error) error {
|
|
||||||
if tfs == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, k := range tfs.Keys {
|
|
||||||
if err := yield(k, tfs.Mapping[k]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
|
||||||
func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var str string
|
|
||||||
if err := unmarshal(&str); err == nil {
|
|
||||||
it.Taskfile = str
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var includedTaskfile struct {
|
|
||||||
Taskfile string
|
|
||||||
Dir string
|
|
||||||
}
|
|
||||||
if err := unmarshal(&includedTaskfile); err == nil {
|
|
||||||
it.Dir = includedTaskfile.Dir
|
|
||||||
it.Taskfile = includedTaskfile.Taskfile
|
|
||||||
it.AdvancedImport = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrCantUnmarshalIncludedTaskfile
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package taskfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NamespaceSeparator contains the character that separates namescapes
|
|
||||||
const NamespaceSeparator = ":"
|
|
||||||
|
|
||||||
// Merge merges the second Taskfile into the first
|
|
||||||
func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
|
||||||
if t1.Version != t2.Version {
|
|
||||||
return fmt.Errorf(`Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t2.Expansions != 0 && t2.Expansions != 2 {
|
|
||||||
t1.Expansions = t2.Expansions
|
|
||||||
}
|
|
||||||
if t2.Output != "" {
|
|
||||||
t1.Output = t2.Output
|
|
||||||
}
|
|
||||||
|
|
||||||
if t1.Includes == nil {
|
|
||||||
t1.Includes = &IncludedTaskfiles{}
|
|
||||||
}
|
|
||||||
t1.Includes.Merge(t2.Includes)
|
|
||||||
|
|
||||||
if t1.Vars == nil {
|
|
||||||
t1.Vars = &Vars{}
|
|
||||||
}
|
|
||||||
if t1.Env == nil {
|
|
||||||
t1.Env = &Vars{}
|
|
||||||
}
|
|
||||||
t1.Vars.Merge(t2.Vars)
|
|
||||||
t1.Env.Merge(t2.Env)
|
|
||||||
|
|
||||||
if t1.Tasks == nil {
|
|
||||||
t1.Tasks = make(Tasks)
|
|
||||||
}
|
|
||||||
for k, v := range t2.Tasks {
|
|
||||||
// FIXME(@andreynering): Refactor this block, otherwise we can
|
|
||||||
// have serious side-effects in the future, since we're editing
|
|
||||||
// the original references instead of deep copying them.
|
|
||||||
|
|
||||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = v
|
|
||||||
|
|
||||||
for _, dep := range v.Deps {
|
|
||||||
dep.Task = taskNameWithNamespace(dep.Task, namespaces...)
|
|
||||||
}
|
|
||||||
for _, cmd := range v.Cmds {
|
|
||||||
if cmd.Task != "" {
|
|
||||||
cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func taskNameWithNamespace(taskName string, namespaces ...string) string {
|
|
||||||
if strings.HasPrefix(taskName, ":") {
|
|
||||||
return strings.TrimPrefix(taskName, ":")
|
|
||||||
}
|
|
||||||
return strings.Join(append(namespaces, taskName), NamespaceSeparator)
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package taskfile_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPreconditionParse(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
content string
|
|
||||||
v interface{}
|
|
||||||
expected interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"test -f foo.txt",
|
|
||||||
&taskfile.Precondition{},
|
|
||||||
&taskfile.Precondition{Sh: `test -f foo.txt`, Msg: "`test -f foo.txt` failed"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sh: '[ 1 = 0 ]'",
|
|
||||||
&taskfile.Precondition{},
|
|
||||||
&taskfile.Precondition{Sh: "[ 1 = 0 ]", Msg: "[ 1 = 0 ] failed"},
|
|
||||||
},
|
|
||||||
{`
|
|
||||||
sh: "[ 1 = 2 ]"
|
|
||||||
msg: "1 is not 2"
|
|
||||||
`,
|
|
||||||
&taskfile.Precondition{},
|
|
||||||
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
|
||||||
},
|
|
||||||
{`
|
|
||||||
sh: "[ 1 = 2 ]"
|
|
||||||
msg: "1 is not 2"
|
|
||||||
`,
|
|
||||||
&taskfile.Precondition{},
|
|
||||||
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
err := yaml.Unmarshal([]byte(test.content), test.v)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, test.expected, test.v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package read
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
|
|
||||||
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
|
|
||||||
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
|
||||||
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Taskfile reads a Taskfile for a given directory
|
|
||||||
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
|
||||||
path := filepath.Join(dir, entrypoint)
|
|
||||||
if _, err := os.Stat(path); err != nil {
|
|
||||||
return nil, fmt.Errorf(`task: No Taskfile found on "%s". Use "task --init" to create a new one`, path)
|
|
||||||
}
|
|
||||||
t, err := readTaskfile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := t.ParsedVersion()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if v >= 3.0 {
|
|
||||||
for _, dotEnvPath := range t.Dotenv {
|
|
||||||
if !filepath.IsAbs(dotEnvPath) {
|
|
||||||
dotEnvPath = filepath.Join(dir, dotEnvPath)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
envs, err := godotenv.Read(dotEnvPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for key, value := range envs {
|
|
||||||
if _, ok := t.Env.Mapping[key]; !ok {
|
|
||||||
t.Env.Set(key, taskfile.Var{Static: value})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
|
|
||||||
if v >= 3.0 {
|
|
||||||
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
|
|
||||||
includedTask = taskfile.IncludedTaskfile{
|
|
||||||
Taskfile: tr.Replace(includedTask.Taskfile),
|
|
||||||
Dir: tr.Replace(includedTask.Dir),
|
|
||||||
AdvancedImport: includedTask.AdvancedImport,
|
|
||||||
}
|
|
||||||
if err := tr.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if filepath.IsAbs(includedTask.Taskfile) {
|
|
||||||
path = includedTask.Taskfile
|
|
||||||
} else {
|
|
||||||
path = filepath.Join(dir, includedTask.Taskfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
path = filepath.Join(path, "Taskfile.yml")
|
|
||||||
}
|
|
||||||
includedTaskfile, err := readTaskfile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if includedTaskfile.Includes.Len() > 0 {
|
|
||||||
return ErrIncludedTaskfilesCantHaveIncludes
|
|
||||||
}
|
|
||||||
|
|
||||||
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
|
|
||||||
return ErrIncludedTaskfilesCantHaveDotenvs
|
|
||||||
}
|
|
||||||
|
|
||||||
if includedTask.AdvancedImport {
|
|
||||||
for k, v := range includedTaskfile.Vars.Mapping {
|
|
||||||
o := v
|
|
||||||
o.Dir = filepath.Join(dir, includedTask.Dir)
|
|
||||||
includedTaskfile.Vars.Mapping[k] = o
|
|
||||||
}
|
|
||||||
for k, v := range includedTaskfile.Env.Mapping {
|
|
||||||
o := v
|
|
||||||
o.Dir = filepath.Join(dir, includedTask.Dir)
|
|
||||||
includedTaskfile.Env.Mapping[k] = o
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, task := range includedTaskfile.Tasks {
|
|
||||||
if !filepath.IsAbs(task.Dir) {
|
|
||||||
task.Dir = filepath.Join(includedTask.Dir, task.Dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if v < 3.0 {
|
|
||||||
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
|
|
||||||
if _, err = os.Stat(path); err == nil {
|
|
||||||
osTaskfile, err := readTaskfile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = taskfile.Merge(t, osTaskfile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, task := range t.Tasks {
|
|
||||||
if task == nil {
|
|
||||||
task = &taskfile.Task{}
|
|
||||||
t.Tasks[name] = task
|
|
||||||
}
|
|
||||||
task.Task = name
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTaskfile(file string) (*taskfile.Taskfile, error) {
|
|
||||||
f, err := os.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var t taskfile.Taskfile
|
|
||||||
return &t, yaml.NewDecoder(f).Decode(&t)
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package read
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Taskvars reads a Taskvars for a given directory
|
|
||||||
func Taskvars(dir string) (*taskfile.Vars, error) {
|
|
||||||
vars := &taskfile.Vars{}
|
|
||||||
|
|
||||||
path := filepath.Join(dir, "Taskvars.yml")
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
vars, err = readTaskvars(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
path = filepath.Join(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
osVars, err := readTaskvars(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
vars.Merge(osVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
return vars, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTaskvars(file string) (*taskfile.Vars, error) {
|
|
||||||
f, err := os.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var vars taskfile.Vars
|
|
||||||
return &vars, yaml.NewDecoder(f).Decode(&vars)
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package taskfile
|
|
||||||
|
|
||||||
func stringSliceContains(s []string, str string) bool {
|
|
||||||
for _, v := range s {
|
|
||||||
if v == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package taskfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tasks represents a group of tasks
|
|
||||||
type Tasks map[string]*Task
|
|
||||||
|
|
||||||
// Task represents a task
|
|
||||||
type Task struct {
|
|
||||||
Task string
|
|
||||||
Cmds []*Cmd
|
|
||||||
Deps []*Dep
|
|
||||||
Label string
|
|
||||||
Desc string
|
|
||||||
Summary string
|
|
||||||
Sources []string
|
|
||||||
Generates []string
|
|
||||||
Status []string
|
|
||||||
Preconditions []*Precondition
|
|
||||||
Dir string
|
|
||||||
Vars *Vars
|
|
||||||
Env *Vars
|
|
||||||
Silent bool
|
|
||||||
Method string
|
|
||||||
Prefix string
|
|
||||||
IgnoreError bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrCantUnmarshalTask is returned for invalid task YAML
|
|
||||||
ErrCantUnmarshalTask = errors.New("task: can't unmarshal task value")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *Task) Name() string {
|
|
||||||
if t.Label != "" {
|
|
||||||
return t.Label
|
|
||||||
}
|
|
||||||
return t.Task
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var cmd Cmd
|
|
||||||
if err := unmarshal(&cmd); err == nil && cmd.Cmd != "" {
|
|
||||||
t.Cmds = append(t.Cmds, &cmd)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmds []*Cmd
|
|
||||||
if err := unmarshal(&cmds); err == nil && len(cmds) > 0 {
|
|
||||||
t.Cmds = cmds
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var task struct {
|
|
||||||
Cmds []*Cmd
|
|
||||||
Deps []*Dep
|
|
||||||
Label string
|
|
||||||
Desc string
|
|
||||||
Summary string
|
|
||||||
Sources []string
|
|
||||||
Generates []string
|
|
||||||
Status []string
|
|
||||||
Preconditions []*Precondition
|
|
||||||
Dir string
|
|
||||||
Vars *Vars
|
|
||||||
Env *Vars
|
|
||||||
Silent bool
|
|
||||||
Method string
|
|
||||||
Prefix string
|
|
||||||
IgnoreError bool `yaml:"ignore_error"`
|
|
||||||
}
|
|
||||||
if err := unmarshal(&task); err == nil {
|
|
||||||
t.Cmds = task.Cmds
|
|
||||||
t.Deps = task.Deps
|
|
||||||
t.Label = task.Label
|
|
||||||
t.Desc = task.Desc
|
|
||||||
t.Summary = task.Summary
|
|
||||||
t.Sources = task.Sources
|
|
||||||
t.Generates = task.Generates
|
|
||||||
t.Status = task.Status
|
|
||||||
t.Preconditions = task.Preconditions
|
|
||||||
t.Dir = task.Dir
|
|
||||||
t.Vars = task.Vars
|
|
||||||
t.Env = task.Env
|
|
||||||
t.Silent = task.Silent
|
|
||||||
t.Method = task.Method
|
|
||||||
t.Prefix = task.Prefix
|
|
||||||
t.IgnoreError = task.IgnoreError
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrCantUnmarshalTask
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package taskfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Taskfile represents a Taskfile.yml
|
|
||||||
type Taskfile struct {
|
|
||||||
Version string
|
|
||||||
Expansions int
|
|
||||||
Output string
|
|
||||||
Method string
|
|
||||||
Includes *IncludedTaskfiles
|
|
||||||
Vars *Vars
|
|
||||||
Env *Vars
|
|
||||||
Tasks Tasks
|
|
||||||
Silent bool
|
|
||||||
Dotenv []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
|
||||||
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var taskfile struct {
|
|
||||||
Version string
|
|
||||||
Expansions int
|
|
||||||
Output string
|
|
||||||
Method string
|
|
||||||
Includes *IncludedTaskfiles
|
|
||||||
Vars *Vars
|
|
||||||
Env *Vars
|
|
||||||
Tasks Tasks
|
|
||||||
Silent bool
|
|
||||||
Dotenv []string
|
|
||||||
}
|
|
||||||
if err := unmarshal(&taskfile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tf.Version = taskfile.Version
|
|
||||||
tf.Expansions = taskfile.Expansions
|
|
||||||
tf.Output = taskfile.Output
|
|
||||||
tf.Method = taskfile.Method
|
|
||||||
tf.Includes = taskfile.Includes
|
|
||||||
tf.Vars = taskfile.Vars
|
|
||||||
tf.Env = taskfile.Env
|
|
||||||
tf.Tasks = taskfile.Tasks
|
|
||||||
tf.Silent = taskfile.Silent
|
|
||||||
tf.Dotenv = taskfile.Dotenv
|
|
||||||
if tf.Expansions <= 0 {
|
|
||||||
tf.Expansions = 2
|
|
||||||
}
|
|
||||||
if tf.Vars == nil {
|
|
||||||
tf.Vars = &Vars{}
|
|
||||||
}
|
|
||||||
if tf.Env == nil {
|
|
||||||
tf.Env = &Vars{}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsedVersion returns the version as a float64
|
|
||||||
func (tf *Taskfile) ParsedVersion() (float64, error) {
|
|
||||||
v, err := strconv.ParseFloat(tf.Version, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, tf.Version, err)
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
133
taskfile/var.go
133
taskfile/var.go
@@ -1,133 +0,0 @@
|
|||||||
package taskfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrCantUnmarshalVar is returned for invalid var YAML.
|
|
||||||
ErrCantUnmarshalVar = errors.New("task: can't unmarshal var value")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Vars is a string[string] variables map.
|
|
||||||
type Vars struct {
|
|
||||||
Keys []string
|
|
||||||
Mapping map[string]Var
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
||||||
func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
|
|
||||||
if node.Kind != yaml.MappingNode {
|
|
||||||
return errors.New("task: vars is not a map")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(@andreynering): on this style of custom unmarsheling,
|
|
||||||
// even number contains the keys, while odd numbers contains
|
|
||||||
// the values.
|
|
||||||
for i := 0; i < len(node.Content); i += 2 {
|
|
||||||
keyNode := node.Content[i]
|
|
||||||
valueNode := node.Content[i+1]
|
|
||||||
|
|
||||||
var v Var
|
|
||||||
if err := valueNode.Decode(&v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
vs.Set(keyNode.Value, v)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge merges the given Vars into the caller one
|
|
||||||
func (vs *Vars) Merge(other *Vars) {
|
|
||||||
other.Range(func(key string, value Var) error {
|
|
||||||
vs.Set(key, value)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets a value to a given key
|
|
||||||
func (vs *Vars) Set(key string, value Var) {
|
|
||||||
if vs.Mapping == nil {
|
|
||||||
vs.Mapping = make(map[string]Var, 1)
|
|
||||||
}
|
|
||||||
if !stringSliceContains(vs.Keys, key) {
|
|
||||||
vs.Keys = append(vs.Keys, key)
|
|
||||||
}
|
|
||||||
vs.Mapping[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range allows you to loop into the vars in its right order
|
|
||||||
func (vs *Vars) Range(yield func(key string, value Var) error) error {
|
|
||||||
if vs == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, k := range vs.Keys {
|
|
||||||
if err := yield(k, vs.Mapping[k]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToCacheMap converts Vars to a map containing only the static
|
|
||||||
// variables
|
|
||||||
func (vs *Vars) ToCacheMap() (m map[string]interface{}) {
|
|
||||||
m = make(map[string]interface{}, vs.Len())
|
|
||||||
vs.Range(func(k string, v Var) error {
|
|
||||||
if v.Sh != "" {
|
|
||||||
// Dynamic variable is not yet resolved; trigger
|
|
||||||
// <no value> to be used in templates.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Live != nil {
|
|
||||||
m[k] = v.Live
|
|
||||||
} else {
|
|
||||||
m[k] = v.Static
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the size of the map
|
|
||||||
func (vs *Vars) Len() int {
|
|
||||||
if vs == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return len(vs.Keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Var represents either a static or dynamic variable.
|
|
||||||
type Var struct {
|
|
||||||
Static string
|
|
||||||
Live interface{}
|
|
||||||
Sh string
|
|
||||||
Dir string
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
|
||||||
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var str string
|
|
||||||
if err := unmarshal(&str); err == nil {
|
|
||||||
if strings.HasPrefix(str, "$") {
|
|
||||||
v.Sh = strings.TrimPrefix(str, "$")
|
|
||||||
} else {
|
|
||||||
v.Static = str
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var sh struct {
|
|
||||||
Sh string
|
|
||||||
}
|
|
||||||
if err := unmarshal(&sh); err == nil {
|
|
||||||
v.Sh = sh.Sh
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrCantUnmarshalVar
|
|
||||||
}
|
|
||||||
20
testdata/checksum/Taskfile.yml
vendored
20
testdata/checksum/Taskfile.yml
vendored
@@ -1,12 +1,8 @@
|
|||||||
version: '3'
|
build:
|
||||||
|
cmds:
|
||||||
tasks:
|
- cp ./source.txt ./generated.txt
|
||||||
build:
|
sources:
|
||||||
cmds:
|
- ./source.txt
|
||||||
- cp ./source.txt ./generated.txt
|
generates:
|
||||||
sources:
|
- ./generated.txt
|
||||||
- ./**/glob-with-inexistent-file.txt
|
method: checksum
|
||||||
- ./source.txt
|
|
||||||
generates:
|
|
||||||
- ./generated.txt
|
|
||||||
method: checksum
|
|
||||||
|
|||||||
32
testdata/concurrency/Taskfile.yml
vendored
32
testdata/concurrency/Taskfile.yml
vendored
@@ -1,32 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
deps:
|
|
||||||
- t1
|
|
||||||
|
|
||||||
t1:
|
|
||||||
deps:
|
|
||||||
- t3
|
|
||||||
- t4
|
|
||||||
cmds:
|
|
||||||
- task: t2
|
|
||||||
- echo done 1
|
|
||||||
t2:
|
|
||||||
deps:
|
|
||||||
- t5
|
|
||||||
- t6
|
|
||||||
cmds:
|
|
||||||
- echo done 2
|
|
||||||
t3:
|
|
||||||
cmds:
|
|
||||||
- echo done 3
|
|
||||||
t4:
|
|
||||||
cmds:
|
|
||||||
- echo done 4
|
|
||||||
t5:
|
|
||||||
cmds:
|
|
||||||
- echo done 5
|
|
||||||
t6:
|
|
||||||
cmds:
|
|
||||||
- echo done 6
|
|
||||||
15
testdata/cyclic/Taskfile.yml
vendored
15
testdata/cyclic/Taskfile.yml
vendored
@@ -1,10 +1,7 @@
|
|||||||
version: '3'
|
task-1:
|
||||||
|
deps:
|
||||||
|
- task: task-2
|
||||||
|
|
||||||
tasks:
|
task-2:
|
||||||
task-1:
|
deps:
|
||||||
deps:
|
- task: task-1
|
||||||
- task: task-2
|
|
||||||
|
|
||||||
task-2:
|
|
||||||
deps:
|
|
||||||
- task: task-1
|
|
||||||
|
|||||||
85
testdata/deps/Taskfile.yml
vendored
85
testdata/deps/Taskfile.yml
vendored
@@ -1,56 +1,53 @@
|
|||||||
version: '3'
|
default:
|
||||||
|
deps: [d1, d2, d3]
|
||||||
|
|
||||||
tasks:
|
d1:
|
||||||
default:
|
deps: [d11, d12, d13]
|
||||||
deps: [d1, d2, d3]
|
cmds:
|
||||||
|
- echo 'Text' > d1.txt
|
||||||
|
|
||||||
d1:
|
d2:
|
||||||
deps: [d11, d12, d13]
|
deps: [d21, d22, d23]
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d1.txt
|
- echo 'Text' > d2.txt
|
||||||
|
|
||||||
d2:
|
d3:
|
||||||
deps: [d21, d22, d23]
|
deps: [d31, d32, d33]
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d2.txt
|
- echo 'Text' > d3.txt
|
||||||
|
|
||||||
d3:
|
d11:
|
||||||
deps: [d31, d32, d33]
|
cmds:
|
||||||
cmds:
|
- echo 'Text' > d11.txt
|
||||||
- echo 'Text' > d3.txt
|
|
||||||
|
|
||||||
d11:
|
d12:
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d11.txt
|
- echo 'Text' > d12.txt
|
||||||
|
|
||||||
d12:
|
d13:
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d12.txt
|
- echo 'Text' > d13.txt
|
||||||
|
|
||||||
d13:
|
d21:
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d13.txt
|
- echo 'Text' > d21.txt
|
||||||
|
|
||||||
d21:
|
d22:
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d21.txt
|
- echo 'Text' > d22.txt
|
||||||
|
|
||||||
d22:
|
d23:
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d22.txt
|
- echo 'Text' > d23.txt
|
||||||
|
|
||||||
d23:
|
d31:
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d23.txt
|
- echo 'Text' > d31.txt
|
||||||
|
|
||||||
d31:
|
d32:
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d31.txt
|
- echo 'Text' > d32.txt
|
||||||
|
|
||||||
d32:
|
d33:
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'Text' > d32.txt
|
- echo 'Text' > d33.txt
|
||||||
|
|
||||||
d33:
|
|
||||||
cmds:
|
|
||||||
- echo 'Text' > d33.txt
|
|
||||||
|
|||||||
7
testdata/dir/Taskfile.yml
vendored
7
testdata/dir/Taskfile.yml
vendored
@@ -1,7 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
whereami:
|
|
||||||
cmds:
|
|
||||||
- pwd
|
|
||||||
silent: true
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user