mirror of
https://github.com/go-task/task.git
synced 2026-06-26 14:16:16 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73aba36309 | ||
|
|
cb393ccd3a | ||
|
|
347fcf9f67 | ||
|
|
fce7575b03 | ||
|
|
2da7ddc399 | ||
|
|
1c1be683ab | ||
|
|
4be1050234 | ||
|
|
2efb3533ec | ||
|
|
aa6c7e4b94 | ||
|
|
63c50d13ee | ||
|
|
c1e127e42f | ||
|
|
9e38e8a4db | ||
|
|
b4c95d6b0b | ||
|
|
c4766e2611 | ||
|
|
796097e3ab | ||
|
|
c7d9efebf9 | ||
|
|
8f4306d321 | ||
|
|
435f086cb7 | ||
|
|
01c9158120 | ||
|
|
e235d77d64 | ||
|
|
dbe8131b75 | ||
|
|
0a9d76515e | ||
|
|
0ce1af9ee0 | ||
|
|
c4452d2698 | ||
|
|
491888f6c0 | ||
|
|
e4158dc5e4 | ||
|
|
0307ca8ac6 | ||
|
|
156a273351 | ||
|
|
d6d51a2f8b | ||
|
|
a98b41d657 | ||
|
|
87ec78fbaa | ||
|
|
957bff4b89 | ||
|
|
321f7b59d8 | ||
|
|
41a9316523 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
|||||||
github: andreynering
|
github: [andreynering, pd93]
|
||||||
open_collective: task
|
open_collective: task
|
||||||
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=USD&source=url'
|
custom: https://taskfile.dev/donate/
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
const issue = await github.rest.issues.get({
|
const issue = await github.rest.issues.get({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
|||||||
29
.github/workflows/issue-closed.yml
vendored
Normal file
29
.github/workflows/issue-closed.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: issue closed
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
issue-closed:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GH_PAT}}
|
||||||
|
script: |
|
||||||
|
const labels = await github.paginate(
|
||||||
|
github.rest.issues.listLabelsOnIssue, {
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (labels.find(label => label.name === 'needs triage')) {
|
||||||
|
github.rest.issues.removeLabel({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
name: 'needs triage'
|
||||||
|
})
|
||||||
|
}
|
||||||
3
.github/workflows/issue-needs-triage.yml
vendored
3
.github/workflows/issue-needs-triage.yml
vendored
@@ -5,11 +5,12 @@ on:
|
|||||||
types: [opened]
|
types: [opened]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
needs-triage:
|
issue-needs-triage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
const labels = await github.paginate(
|
const labels = await github.paginate(
|
||||||
github.rest.issues.listLabelsOnIssue, {
|
github.rest.issues.listLabelsOnIssue, {
|
||||||
|
|||||||
12
.golangci.yml
Normal file
12
.golangci.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# NOTE(@andreynering): The linters listed here are additions on top of
|
||||||
|
# those enabled by default:
|
||||||
|
#
|
||||||
|
# https://golangci-lint.run/usage/linters/#enabled-by-default
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- goimports
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
goimports:
|
||||||
|
local-prefixes: github.com/go-task/task
|
||||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,34 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v3.20.0 - 2023-01-14
|
||||||
|
|
||||||
|
- Improve behavior and performance of status checking when using the
|
||||||
|
`timestamp` mode
|
||||||
|
([#976](https://github.com/go-task/task/issues/976), [#977](https://github.com/go-task/task/pull/977) by @aminya).
|
||||||
|
- Performance optimizations were made for large Taskfiles
|
||||||
|
([#982](https://github.com/go-task/task/pull/982) by @pd93).
|
||||||
|
- Add ability to configure options for the [`set`](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)
|
||||||
|
and [`shopt`](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html) builtins
|
||||||
|
([#908](https://github.com/go-task/task/issues/908), [#929](https://github.com/go-task/task/pull/929) by @pd93, [Documentation](http://taskfile.dev/usage/#set-and-shopt)).
|
||||||
|
- Add new `platforms:` attribute to `task` and `cmd`, so it's now possible to
|
||||||
|
choose in which platforms that given task or command will be run on. Possible
|
||||||
|
values are operating system (GOOS), architecture (GOARCH) or a combination of
|
||||||
|
the two. Example: `platforms: [linux]`, `platforms: [amd64]` or
|
||||||
|
`platforms: [linux/amd64]`. Other platforms will be skipped
|
||||||
|
([#978](https://github.com/go-task/task/issues/978), [#980](https://github.com/go-task/task/pull/980) by @leaanthony).
|
||||||
|
|
||||||
|
## v3.19.1 - 2022-12-31
|
||||||
|
|
||||||
|
- Small bug fix: closing `Taskfile.yml` once we're done reading it
|
||||||
|
([#963](https://github.com/go-task/task/issues/963), [#964](https://github.com/go-task/task/pull/964) by @HeCorr).
|
||||||
|
- Fixes a bug in v2 that caused a panic when using a `Taskfile_{{OS}}.yml` file
|
||||||
|
([#961](https://github.com/go-task/task/issues/961), [#971](https://github.com/go-task/task/pull/971) by @pd93).
|
||||||
|
- Fixed a bug where watch intervals set in the Taskfile were not being respected ([#969](https://github.com/go-task/task/pull/969), [#970](https://github.com/go-task/task/pull/970) by @pd93)
|
||||||
|
- Add `--json` flag (alias `-j`) with the intent to improve support for code
|
||||||
|
editors and add room to other possible integrations. This is basic for now,
|
||||||
|
but we plan to add more info in the near future
|
||||||
|
([#936](https://github.com/go-task/task/pull/936) by @davidalpert, [#764](https://github.com/go-task/task/issues/764)).
|
||||||
|
|
||||||
## v3.19.0 - 2022-12-05
|
## v3.19.0 - 2022-12-05
|
||||||
|
|
||||||
- Installation via npm now supports [pnpm](https://pnpm.io/) as well
|
- Installation via npm now supports [pnpm](https://pnpm.io/) as well
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="https://taskfile.dev/installation/">Installation</a> | <a href="https://taskfile.dev/usage/">Documentation</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
|
<a href="https://taskfile.dev/installation/">Installation</a> | <a href="https://taskfile.dev/usage/">Documentation</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://fosstodon.org/@task">Mastodon</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
28
Taskfile.yml
28
Taskfile.yml
@@ -6,13 +6,6 @@ includes:
|
|||||||
taskfile: ./docs
|
taskfile: ./docs
|
||||||
dir: ./docs
|
dir: ./docs
|
||||||
|
|
||||||
vars:
|
|
||||||
GIT_COMMIT:
|
|
||||||
sh: git log -n 1 --format=%h
|
|
||||||
|
|
||||||
GO_PACKAGES:
|
|
||||||
sh: go list ./...
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: '0'
|
CGO_ENABLED: '0'
|
||||||
|
|
||||||
@@ -29,6 +22,9 @@ tasks:
|
|||||||
- './**/*.go'
|
- './**/*.go'
|
||||||
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
|
||||||
|
vars:
|
||||||
|
GIT_COMMIT:
|
||||||
|
sh: git log -n 1 --format=%h
|
||||||
|
|
||||||
mod:
|
mod:
|
||||||
desc: Downloads and tidy Go modules
|
desc: Downloads and tidy Go modules
|
||||||
@@ -47,6 +43,7 @@ tasks:
|
|||||||
aliases: [l]
|
aliases: [l]
|
||||||
sources:
|
sources:
|
||||||
- './**/*.go'
|
- './**/*.go'
|
||||||
|
- .golangci.yml
|
||||||
cmds:
|
cmds:
|
||||||
- golangci-lint run
|
- golangci-lint run
|
||||||
|
|
||||||
@@ -72,12 +69,18 @@ tasks:
|
|||||||
deps: [install]
|
deps: [install]
|
||||||
cmds:
|
cmds:
|
||||||
- go test {{catLines .GO_PACKAGES}}
|
- go test {{catLines .GO_PACKAGES}}
|
||||||
|
vars:
|
||||||
|
GO_PACKAGES:
|
||||||
|
sh: go list ./...
|
||||||
|
|
||||||
test:signals:
|
test:all:
|
||||||
desc: Runs test suite with signals tests included
|
desc: Runs test suite with signals and watch tests included
|
||||||
deps: [install, sleepit:build]
|
deps: [install, sleepit:build]
|
||||||
cmds:
|
cmds:
|
||||||
- go test {{catLines .GO_PACKAGES}} -tags signals
|
- go test {{catLines .GO_PACKAGES}} -tags 'signals watch'
|
||||||
|
vars:
|
||||||
|
GO_PACKAGES:
|
||||||
|
sh: go list ./...
|
||||||
|
|
||||||
test-release:
|
test-release:
|
||||||
desc: Tests release process without publishing
|
desc: Tests release process without publishing
|
||||||
@@ -92,7 +95,7 @@ tasks:
|
|||||||
- rm {{.FILE}}
|
- rm {{.FILE}}
|
||||||
- 'echo "---" >> {{.FILE}}'
|
- 'echo "---" >> {{.FILE}}'
|
||||||
- 'echo "slug: /changelog/" >> {{.FILE}}'
|
- 'echo "slug: /changelog/" >> {{.FILE}}'
|
||||||
- 'echo "sidebar_position: 6" >> {{.FILE}}'
|
- 'echo "sidebar_position: 7" >> {{.FILE}}'
|
||||||
- 'echo "---" >> {{.FILE}}'
|
- 'echo "---" >> {{.FILE}}'
|
||||||
- 'echo "" >> {{.FILE}}'
|
- 'echo "" >> {{.FILE}}'
|
||||||
- 'cat CHANGELOG.md >> {{.FILE}}'
|
- 'cat CHANGELOG.md >> {{.FILE}}'
|
||||||
@@ -105,4 +108,7 @@ tasks:
|
|||||||
packages:
|
packages:
|
||||||
cmds:
|
cmds:
|
||||||
- echo '{{.GO_PACKAGES}}'
|
- echo '{{.GO_PACKAGES}}'
|
||||||
|
vars:
|
||||||
|
GO_PACKAGES:
|
||||||
|
sh: go list ./...
|
||||||
silent: true
|
silent: true
|
||||||
|
|||||||
@@ -130,11 +130,11 @@ func supervisor(
|
|||||||
// The goroutine will prepend its prints with the prefix `name`.
|
// The goroutine will prepend its prints with the prefix `name`.
|
||||||
// The goroutine will simulate some work and will terminate when one of the following
|
// The goroutine will simulate some work and will terminate when one of the following
|
||||||
// conditions happens:
|
// conditions happens:
|
||||||
// 1. When `howlong` is elapsed. This case will be signaled on the `workerDone` channel.
|
// 1. When `howlong` is elapsed. This case will be signaled on the `workerDone` channel.
|
||||||
// 2. When something happens on channel `canceled`. Note that this simulates real-life,
|
// 2. When something happens on channel `canceled`. Note that this simulates real-life,
|
||||||
// so cancellation is not instantaneous: if the caller wants a synchronous cancel,
|
// so cancellation is not instantaneous: if the caller wants a synchronous cancel,
|
||||||
// it should send a message; if instead it wants an asynchronous cancel, it should
|
// it should send a message; if instead it wants an asynchronous cancel, it should
|
||||||
// close the channel.
|
// close the channel.
|
||||||
func worker(
|
func worker(
|
||||||
canceled <-chan struct{},
|
canceled <-chan struct{},
|
||||||
howlong time.Duration,
|
howlong time.Duration,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
@@ -59,6 +60,7 @@ func main() {
|
|||||||
init bool
|
init bool
|
||||||
list bool
|
list bool
|
||||||
listAll bool
|
listAll bool
|
||||||
|
listJson bool
|
||||||
status bool
|
status bool
|
||||||
force bool
|
force bool
|
||||||
watch bool
|
watch bool
|
||||||
@@ -73,7 +75,7 @@ func main() {
|
|||||||
entrypoint string
|
entrypoint string
|
||||||
output taskfile.Output
|
output taskfile.Output
|
||||||
color bool
|
color bool
|
||||||
interval string
|
interval time.Duration
|
||||||
)
|
)
|
||||||
|
|
||||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||||
@@ -81,6 +83,7 @@ func main() {
|
|||||||
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yaml in the current folder")
|
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yaml in the current folder")
|
||||||
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
|
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
|
||||||
pflag.BoolVarP(&listAll, "list-all", "a", false, "lists tasks with or without a description")
|
pflag.BoolVarP(&listAll, "list-all", "a", false, "lists tasks with or without a description")
|
||||||
|
pflag.BoolVarP(&listJson, "json", "j", false, "formats task list as json")
|
||||||
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")
|
||||||
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
|
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
|
||||||
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
|
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
|
||||||
@@ -97,7 +100,7 @@ func main() {
|
|||||||
pflag.StringVar(&output.Group.End, "output-group-end", "", "message template to print after a task's grouped output")
|
pflag.StringVar(&output.Group.End, "output-group-end", "", "message template to print after a task's grouped output")
|
||||||
pflag.BoolVarP(&color, "color", "c", true, "colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable")
|
pflag.BoolVarP(&color, "color", "c", true, "colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable")
|
||||||
pflag.IntVarP(&concurrency, "concurrency", "C", 0, "limit number tasks to run concurrently")
|
pflag.IntVarP(&concurrency, "concurrency", "C", 0, "limit number tasks to run concurrently")
|
||||||
pflag.StringVarP(&interval, "interval", "I", "5s", "interval to watch for changes")
|
pflag.DurationVarP(&interval, "interval", "I", 0, "interval to watch for changes")
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
if versionFlag {
|
if versionFlag {
|
||||||
@@ -162,7 +165,12 @@ func main() {
|
|||||||
OutputStyle: output,
|
OutputStyle: output,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list || listAll) && silent {
|
var listOptions = task.NewListOptions(list, listAll, listJson)
|
||||||
|
if err := listOptions.Validate(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listOptions.ShouldListTasks()) && silent {
|
||||||
e.ListTaskNames(listAll)
|
e.ListTaskNames(listAll)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -176,16 +184,9 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if list {
|
if listOptions.ShouldListTasks() {
|
||||||
if ok := e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc()); !ok {
|
if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil {
|
||||||
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks")
|
os.Exit(1)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if listAll {
|
|
||||||
if ok := e.ListTasks(task.FilterOutInternal()); !ok {
|
|
||||||
e.Logger.Outf(logger.Yellow, "task: No tasks available")
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,3 +35,8 @@ tasks:
|
|||||||
summary: Requires GIT_USER and GIT_PASS envs to be previous set
|
summary: Requires GIT_USER and GIT_PASS envs to be previous set
|
||||||
cmds:
|
cmds:
|
||||||
- npx docusaurus deploy
|
- npx docusaurus deploy
|
||||||
|
|
||||||
|
upgrade:
|
||||||
|
desc: Upgrade Docusaurus
|
||||||
|
cmds:
|
||||||
|
- yarn upgrade @docusaurus/core@latest @docusaurus/preset-classic@latest
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
const GITHUB_URL = 'https://github.com/go-task/task';
|
const GITHUB_URL = 'https://github.com/go-task/task';
|
||||||
const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
||||||
|
const MASTODON_URL = 'https://fosstodon.org/@task';
|
||||||
const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
||||||
const CHINESE_URL = 'https://task-zh.readthedocs.io/zh_CN/latest/';
|
const CHINESE_URL = 'https://task-zh.readthedocs.io/zh_CN/latest/';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
GITHUB_URL,
|
CHINESE_URL,
|
||||||
TWITTER_URL,
|
|
||||||
DISCORD_URL,
|
DISCORD_URL,
|
||||||
CHINESE_URL
|
GITHUB_URL,
|
||||||
|
MASTODON_URL,
|
||||||
|
TWITTER_URL
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ Some environment variables can be overriden to adjust Task behavior.
|
|||||||
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
|
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
|
||||||
| `run` | `string` | `always` | Default 'run' option for this Taskfile. Available options: `always`, `once` and `when_changed`. |
|
| `run` | `string` | `always` | Default 'run' option for this Taskfile. Available options: `always`, `once` and `when_changed`. |
|
||||||
| `interval` | `string` | `5s` | Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid [Go Duration](https://pkg.go.dev/time#ParseDuration). |
|
| `interval` | `string` | `5s` | Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid [Go Duration](https://pkg.go.dev/time#ParseDuration). |
|
||||||
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
### Include
|
### Include
|
||||||
|
|
||||||
@@ -139,6 +141,9 @@ includes:
|
|||||||
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
||||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
||||||
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
||||||
|
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Task will be skipped otherwise. |
|
||||||
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
@@ -189,6 +194,9 @@ tasks:
|
|||||||
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
||||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
||||||
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
||||||
|
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Command will be skipped otherwise. |
|
||||||
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,39 @@
|
|||||||
---
|
---
|
||||||
slug: /changelog/
|
slug: /changelog/
|
||||||
sidebar_position: 6
|
sidebar_position: 7
|
||||||
---
|
---
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v3.20.0 - 2023-01-14
|
||||||
|
|
||||||
|
- Improve behavior and performance of status checking when using the
|
||||||
|
`timestamp` mode
|
||||||
|
([#976](https://github.com/go-task/task/issues/976), [#977](https://github.com/go-task/task/pull/977) by @aminya).
|
||||||
|
- Performance optimizations were made for large Taskfiles
|
||||||
|
([#982](https://github.com/go-task/task/pull/982) by @pd93).
|
||||||
|
- Add ability to configure options for the [`set`](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)
|
||||||
|
and [`shopt`](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html) builtins
|
||||||
|
([#908](https://github.com/go-task/task/issues/908), [#929](https://github.com/go-task/task/pull/929) by @pd93, [Documentation](http://taskfile.dev/usage/#set-and-shopt)).
|
||||||
|
- Add new `platforms:` attribute to `task` and `cmd`, so it's now possible to
|
||||||
|
choose in which platforms that given task or command will be run on. Possible
|
||||||
|
values are operating system (GOOS), architecture (GOARCH) or a combination of
|
||||||
|
the two. Example: `platforms: [linux]`, `platforms: [amd64]` or
|
||||||
|
`platforms: [linux/amd64]`. Other platforms will be skipped
|
||||||
|
([#978](https://github.com/go-task/task/issues/978), [#980](https://github.com/go-task/task/pull/980) by @leaanthony).
|
||||||
|
|
||||||
|
## v3.19.1 - 2022-12-31
|
||||||
|
|
||||||
|
- Small bug fix: closing `Taskfile.yml` once we're done reading it
|
||||||
|
([#963](https://github.com/go-task/task/issues/963), [#964](https://github.com/go-task/task/pull/964) by @HeCorr).
|
||||||
|
- Fixes a bug in v2 that caused a panic when using a `Taskfile_{{OS}}.yml` file
|
||||||
|
([#961](https://github.com/go-task/task/issues/961), [#971](https://github.com/go-task/task/pull/971) by @pd93).
|
||||||
|
- Fixed a bug where watch intervals set in the Taskfile were not being respected ([#969](https://github.com/go-task/task/pull/969), [#970](https://github.com/go-task/task/pull/970) by @pd93)
|
||||||
|
- Add `--json` flag (alias `-j`) with the intent to improve support for code
|
||||||
|
editors and add room to other possible integrations. This is basic for now,
|
||||||
|
but we plan to add more info in the near future
|
||||||
|
([#936](https://github.com/go-task/task/pull/936) by @davidalpert, [#764](https://github.com/go-task/task/issues/764)).
|
||||||
|
|
||||||
## v3.19.0 - 2022-12-05
|
## v3.19.0 - 2022-12-05
|
||||||
|
|
||||||
- Installation via npm now supports [pnpm](https://pnpm.io/) as well
|
- Installation via npm now supports [pnpm](https://pnpm.io/) as well
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
slug: /community/
|
slug: /community/
|
||||||
sidebar_position: 6
|
sidebar_position: 8
|
||||||
---
|
---
|
||||||
|
|
||||||
# Community
|
# Community
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
slug: /contributing/
|
slug: /contributing/
|
||||||
sidebar_position: 7
|
sidebar_position: 9
|
||||||
---
|
---
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
slug: /donate/
|
slug: /donate/
|
||||||
sidebar_position: 10
|
sidebar_position: 12
|
||||||
---
|
---
|
||||||
|
|
||||||
# Donate
|
# Donate
|
||||||
@@ -11,10 +11,23 @@ channels listed below.
|
|||||||
This is just a way of saying "thank you", it won't give you any benefits like
|
This is just a way of saying "thank you", it won't give you any benefits like
|
||||||
higher priority on issues or something similar.
|
higher priority on issues or something similar.
|
||||||
|
|
||||||
|
Companies who donate at least $100/month will be featured as a "Gold Sponsor"
|
||||||
|
in the website homepage and on the GitHub repository README. Make contact with
|
||||||
|
[@andreynering] with the logo you want to be shown.
|
||||||
|
Suspect businesses (gambling, casinos, etc) won't be allowed, though.
|
||||||
|
|
||||||
|
## GitHub Sponsors
|
||||||
|
|
||||||
|
The preferred way to donate to the maintainers is via GitHub Sponsors.
|
||||||
|
Just use the following links to do your donation:
|
||||||
|
|
||||||
|
- [@andreynering](https://github.com/sponsors/andreynering)
|
||||||
|
- [@pd93](https://github.com/sponsors/pd93)
|
||||||
|
|
||||||
## Open Collective
|
## Open Collective
|
||||||
|
|
||||||
Task is on [Open Collective](https://opencollective.com/task) and you have
|
If you prefer [Open Collective](https://opencollective.com/task) you can donate
|
||||||
these options to donate:
|
by using these links:
|
||||||
|
|
||||||
- [$2 per month](https://opencollective.com/task/contribute/backer-4034/checkout)
|
- [$2 per month](https://opencollective.com/task/contribute/backer-4034/checkout)
|
||||||
- [$5 per month](https://opencollective.com/task/contribute/supporter-8404/checkout)
|
- [$5 per month](https://opencollective.com/task/contribute/supporter-8404/checkout)
|
||||||
@@ -22,15 +35,15 @@ these options to donate:
|
|||||||
- [$50 per month](https://opencollective.com/task/contribute/sponsor-28775/checkout)
|
- [$50 per month](https://opencollective.com/task/contribute/sponsor-28775/checkout)
|
||||||
- [Custom value - One-time donation option supported](https://opencollective.com/task/donate)
|
- [Custom value - One-time donation option supported](https://opencollective.com/task/donate)
|
||||||
|
|
||||||
## GitHub Sponsors
|
|
||||||
|
|
||||||
- [@andreynering](https://github.com/sponsors/andreynering)
|
|
||||||
|
|
||||||
## PayPal
|
## PayPal
|
||||||
|
|
||||||
|
You can donate to [@andreynering] via PayPal as well:
|
||||||
|
|
||||||
- [Any value - One-time donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=USD&source=url)
|
- [Any value - One-time donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=USD&source=url)
|
||||||
|
|
||||||
## PIX (Brazil only)
|
## PIX (Brazil only)
|
||||||
|
|
||||||
If you're Brazilian, you can donate any value by
|
And if you're Brazilian, you can also donate to [@andreynering] via PIX by
|
||||||
[using this QR Code](/img/pix.png).
|
[using this QR Code](/img/pix.png).
|
||||||
|
|
||||||
|
[@andreynering]: https://github.com/andreynering
|
||||||
|
|||||||
54
docs/docs/faq.md
Normal file
54
docs/docs/faq.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
slug: /faq/
|
||||||
|
sidebar_position: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
This page contains a list of frequently asked questions about Task.
|
||||||
|
|
||||||
|
- [Why won't my task update my shell environment?](#why-wont-my-task-update-my-shell-environment)
|
||||||
|
- ['x' builtin command doesn't work on Windows](#x-builtin-command-doesnt-work-on-windows)
|
||||||
|
|
||||||
|
## Why won't my task update my shell environment?
|
||||||
|
|
||||||
|
This is a limitation of how shells work. Task runs as a subprocess of your
|
||||||
|
current shell, so it can't change the environment of the shell that started it.
|
||||||
|
This limitation is shared by other task runners and build tools too.
|
||||||
|
|
||||||
|
A common way to work around this is to create a task that will generate output
|
||||||
|
that can be parsed by your shell. For example, to set an environment variable on
|
||||||
|
your shell you can write a task like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
my-shell-env:
|
||||||
|
cmds:
|
||||||
|
- echo "export FOO=foo"
|
||||||
|
- echo "export BAR=bar"
|
||||||
|
```
|
||||||
|
|
||||||
|
Now run `eval $(task my-shell-env)` and the variables `$FOO` and `$BAR` will be
|
||||||
|
available in your shell.
|
||||||
|
|
||||||
|
## 'x' builtin command doesn't work on Windows
|
||||||
|
|
||||||
|
The default shell on Windows (`cmd` and `powershell`) do not have commands like
|
||||||
|
`rm` and `cp` available as builtins. This means that these commands won't work.
|
||||||
|
If you want to make your Taskfile fully cross-platform, you'll need to work
|
||||||
|
around this limitation using one of the following methods:
|
||||||
|
|
||||||
|
- Use the `{{OS}}` function to run an OS-specific script.
|
||||||
|
- Use something like `{{if eq OS "windows"}}powershell {{end}}<my_cmd>` to
|
||||||
|
detect windows and run the command in Powershell directly.
|
||||||
|
- Use a shell on Windows that supports these commands as builtins, such as [Git
|
||||||
|
Bash] or [WSL].
|
||||||
|
|
||||||
|
We want to make improvements to this part of Task and the issues below track
|
||||||
|
this work. Constructive comments and contributions are very welcome!
|
||||||
|
|
||||||
|
- [#197](https://github.com/go-task/task/issues/197)
|
||||||
|
- [mvdan/sh#93](https://github.com/mvdan/sh/issues/93)
|
||||||
|
- [mvdan/sh#97](https://github.com/mvdan/sh/issues/97)
|
||||||
|
|
||||||
|
[Git Bash]: https://gitforwindows.org/
|
||||||
|
[WSL]: https://learn.microsoft.com/en-us/windows/wsl/install
|
||||||
@@ -18,6 +18,15 @@ Task is as simple as running:
|
|||||||
brew install go-task/tap/go-task
|
brew install go-task/tap/go-task
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The above Formula is [maintained by ourselves](https://github.com/go-task/homebrew-tap/blob/master/Formula/go-task.rb).
|
||||||
|
|
||||||
|
Recently, Task was also made available [on the official Homebrew repository](https://formulae.brew.sh/formula/go-task),
|
||||||
|
so you also have that option if you prefer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install go-task
|
||||||
|
```
|
||||||
|
|
||||||
### Snap
|
### Snap
|
||||||
|
|
||||||
Task is available in [Snapcraft][snapcraft], but keep in mind that your
|
Task is available in [Snapcraft][snapcraft], but keep in mind that your
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
slug: /releasing/
|
slug: /releasing/
|
||||||
sidebar_position: 8
|
sidebar_position: 10
|
||||||
---
|
---
|
||||||
|
|
||||||
# Releasing
|
# Releasing
|
||||||
@@ -41,7 +41,7 @@ the [Snapcraft dashboard][snapcraftdashboard].
|
|||||||
|
|
||||||
Scoop is a command-line package manager for the Windows operating system.
|
Scoop is a command-line package manager for the Windows operating system.
|
||||||
Scoop package manifests are maintained by the community.
|
Scoop package manifests are maintained by the community.
|
||||||
Scoop owners usually take care of updating versions there by editing [this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
Scoop owners usually take care of updating versions there by editing [this file](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json).
|
||||||
If you think its Task version is outdated, open an issue to let us know.
|
If you think its Task version is outdated, open an issue to let us know.
|
||||||
|
|
||||||
# Nix
|
# Nix
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
slug: /styleguide/
|
slug: /styleguide/
|
||||||
sidebar_position: 5
|
sidebar_position: 6
|
||||||
---
|
---
|
||||||
|
|
||||||
# Styleguide
|
# Styleguide
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
slug: /taskfile-versions/
|
slug: /taskfile-versions/
|
||||||
sidebar_position: 9
|
sidebar_position: 11
|
||||||
---
|
---
|
||||||
|
|
||||||
# Taskfile Versions
|
# Taskfile Versions
|
||||||
|
|||||||
@@ -439,6 +439,78 @@ tasks:
|
|||||||
- echo {{.TEXT}}
|
- echo {{.TEXT}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Platform specific tasks and commands
|
||||||
|
|
||||||
|
If you want to restrict the running of tasks to explicit platforms, this can be achieved
|
||||||
|
using the `platforms:` key. Tasks can be restricted to a specific OS, architecture or a
|
||||||
|
combination of both.
|
||||||
|
On a mismatch, the task or command will be skipped, and no error will be thrown.
|
||||||
|
|
||||||
|
The values allowed as OS or Arch are valid `GOOS` and `GOARCH` values, as
|
||||||
|
defined by the Go language
|
||||||
|
[here](https://github.com/golang/go/blob/master/src/go/build/syslist.go).
|
||||||
|
|
||||||
|
The `build-windows` task below will run only on Windows, and on any architecture:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build-windows:
|
||||||
|
platforms: [windows]
|
||||||
|
cmds:
|
||||||
|
- echo 'Running command on Windows'
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be restricted to a specific architecture as follows:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build-windows-amd64:
|
||||||
|
platforms: [windows/amd64]
|
||||||
|
cmds:
|
||||||
|
- echo 'Running command on Windows (amd64)'
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to restrict the task to specific architectures:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build-amd64:
|
||||||
|
platforms: [amd64]
|
||||||
|
cmds:
|
||||||
|
- echo 'Running command on amd64'
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple platforms can be specified as follows:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
platforms: [windows/amd64, darwin]
|
||||||
|
cmds:
|
||||||
|
- echo 'Running command on Windows (amd64) and macOS'
|
||||||
|
```
|
||||||
|
|
||||||
|
Individual commands can also be restricted to specific platforms:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- cmd: echo 'Running command on Windows (amd64) and macOS'
|
||||||
|
platforms: [windows/amd64, darwin]
|
||||||
|
- cmd: echo 'Running on all platforms'
|
||||||
|
```
|
||||||
|
|
||||||
## Calling another task
|
## Calling another task
|
||||||
|
|
||||||
When a task has many dependencies, they are executed concurrently. This will
|
When a task has many dependencies, they are executed concurrently. This will
|
||||||
@@ -595,9 +667,9 @@ The method `none` skips any validation and always run the task.
|
|||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
For the `checksum` (default) method to work, it is only necessary to
|
For the `checksum` (default) or `timestamp` method to work, it is only necessary to
|
||||||
inform the source files, but if you want to use the `timestamp` method, you
|
inform the source files.
|
||||||
also need to inform the generated files with `generates`.
|
When the `timestamp` method is used, the last time of the running the task is considered as a generate.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@@ -1348,6 +1420,31 @@ tasks:
|
|||||||
- ./app{{exeExt}} -h localhost -p 8080
|
- ./app{{exeExt}} -h localhost -p 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `set` and `shopt`
|
||||||
|
|
||||||
|
It's possible to specify options to the
|
||||||
|
[`set`](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)
|
||||||
|
and [`shopt`](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html)
|
||||||
|
builtins. This can be added at global, task or command level.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
set: [pipefail]
|
||||||
|
shopt: [globstar]
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# `globstar` required for double star globs to work
|
||||||
|
default: echo **/*.go
|
||||||
|
```
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
Keep in mind that not all options are available in the
|
||||||
|
[shell interpreter library](https://github.com/mvdan/sh) that Task uses.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Watch tasks
|
## Watch tasks
|
||||||
|
|
||||||
With the flags `--watch` or `-w` task will watch for file changes
|
With the flags `--watch` or `-w` task will watch for file changes
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
// Note: type annotations allow type checking and IDEs autocompletion
|
// Note: type annotations allow type checking and IDEs autocompletion
|
||||||
|
|
||||||
const {
|
const {
|
||||||
GITHUB_URL,
|
CHINESE_URL,
|
||||||
TWITTER_URL,
|
|
||||||
DISCORD_URL,
|
DISCORD_URL,
|
||||||
CHINESE_URL
|
GITHUB_URL,
|
||||||
|
MASTODON_URL,
|
||||||
|
TWITTER_URL
|
||||||
} = require('./constants');
|
} = require('./constants');
|
||||||
const lightCodeTheme = require('./src/themes/prismLight');
|
const lightCodeTheme = require('./src/themes/prismLight');
|
||||||
const darkCodeTheme = require('./src/themes/prismDark');
|
const darkCodeTheme = require('./src/themes/prismDark');
|
||||||
@@ -108,6 +109,11 @@ const config = {
|
|||||||
label: 'Twitter',
|
label: 'Twitter',
|
||||||
position: 'right'
|
position: 'right'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: MASTODON_URL,
|
||||||
|
label: 'Mastodon',
|
||||||
|
position: 'right'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: DISCORD_URL,
|
href: DISCORD_URL,
|
||||||
label: 'Discord',
|
label: 'Discord',
|
||||||
@@ -146,6 +152,10 @@ const config = {
|
|||||||
label: 'Twitter',
|
label: 'Twitter',
|
||||||
href: TWITTER_URL
|
href: TWITTER_URL
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Mastodon',
|
||||||
|
href: MASTODON_URL
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Discord',
|
label: 'Discord',
|
||||||
href: DISCORD_URL
|
href: DISCORD_URL
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
"write-heading-ids": "docusaurus write-heading-ids"
|
"write-heading-ids": "docusaurus write-heading-ids"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "2.0.0-beta.20",
|
"@docusaurus/core": "^2.2.0",
|
||||||
"@docusaurus/preset-classic": "2.0.0-beta.20",
|
"@docusaurus/preset-classic": "^2.2.0",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"prism-react-renderer": "^1.3.1",
|
"prism-react-renderer": "^1.3.1",
|
||||||
|
|||||||
67
docs/static/schema.json
vendored
67
docs/static/schema.json
vendored
@@ -108,6 +108,20 @@
|
|||||||
"description": "The directory in which this task should run. Defaults to the current working directory.",
|
"description": "The directory in which this task should run. Defaults to the current working directory.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"set": {
|
||||||
|
"description": "Enables POSIX shell options for all of a task's commands. See https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/3/set"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shopt": {
|
||||||
|
"description": "Enables Bash shell options for all of a task's commands. See https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/3/shopt"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vars": {
|
"vars": {
|
||||||
"description": "A set of variables that can be used in the task.",
|
"description": "A set of variables that can be used in the task.",
|
||||||
"$ref": "#/definitions/3/vars"
|
"$ref": "#/definitions/3/vars"
|
||||||
@@ -155,6 +169,13 @@
|
|||||||
"run": {
|
"run": {
|
||||||
"description": "Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`.",
|
"description": "Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`.",
|
||||||
"$ref": "#/definitions/3/run"
|
"$ref": "#/definitions/3/run"
|
||||||
|
},
|
||||||
|
"platforms": {
|
||||||
|
"description": "Specifies which platforms the task should be run on.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -177,6 +198,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"set": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["allexport", "a", "errexit", "e", "noexec", "n", "noglob", "f", "nounset", "u", "xtrace", "x", "pipefail"]
|
||||||
|
},
|
||||||
|
"shopt": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["expand_aliases", "globstar", "nullglob"]
|
||||||
|
},
|
||||||
"vars": {
|
"vars": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"patternProperties": {
|
"patternProperties": {
|
||||||
@@ -226,6 +255,20 @@
|
|||||||
"description": "Silent mode disables echoing of command before Task runs it",
|
"description": "Silent mode disables echoing of command before Task runs it",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"set": {
|
||||||
|
"description": "Enables POSIX shell options for this command. See https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/3/set"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shopt": {
|
||||||
|
"description": "Enables Bash shell options for this command. See https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/3/shopt"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ignore_error": {
|
"ignore_error": {
|
||||||
"description": "Prevent command from aborting the execution of task even after receiving a status code of 1",
|
"description": "Prevent command from aborting the execution of task even after receiving a status code of 1",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@@ -233,6 +276,13 @@
|
|||||||
"defer": {
|
"defer": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"platforms": {
|
||||||
|
"description": "Specifies which platforms the command should be run on.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -357,6 +407,20 @@
|
|||||||
"description": "Default 'silent' options for this Taskfile. If `false`, can be overidden with `true` in a task by task basis.",
|
"description": "Default 'silent' options for this Taskfile. If `false`, can be overidden with `true` in a task by task basis.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"set": {
|
||||||
|
"description": "Enables POSIX shell options for all commands in the Taskfile. See https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/3/set"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shopt": {
|
||||||
|
"description": "Enables Bash shell options for all commands in the Taskfile. See https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/3/shopt"
|
||||||
|
}
|
||||||
|
},
|
||||||
"dotenv": {
|
"dotenv": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "A list of `.env` file paths to be parsed.",
|
"description": "A list of `.env` file paths to be parsed.",
|
||||||
@@ -370,7 +434,8 @@
|
|||||||
},
|
},
|
||||||
"interval": {
|
"interval": {
|
||||||
"description": "Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid Go duration: https://pkg.go.dev/time#ParseDuration.",
|
"description": "Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid Go duration: https://pkg.go.dev/time#ParseDuration.",
|
||||||
"$ref": "#/definitions/3/run"
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+(?:m|s|ms)$"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
2487
docs/yarn.lock
2487
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
8
go.mod
8
go.mod
@@ -11,9 +11,9 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.1.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899
|
mvdan.cc/sh/v3 v3.6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -21,8 +21,8 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
golang.org/x/sys v0.3.0 // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
golang.org/x/term v0.3.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
26
go.sum
26
go.sum
@@ -1,16 +1,16 @@
|
|||||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
@@ -25,7 +25,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||||
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
@@ -40,15 +40,15 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -56,5 +56,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899 h1:nwm4t5PtLlFd/H342GP50CtYf7vyMCOZkPx3g9shO0c=
|
mvdan.cc/sh/v3 v3.6.0 h1:gtva4EXJ0dFNvl5bHjcUEvws+KRcDslT8VKheTYkbGU=
|
||||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
mvdan.cc/sh/v3 v3.6.0/go.mod h1:U4mhtBLZ32iWhif5/lD+ygy1zrgaQhUu+XFy7C8+TTA=
|
||||||
|
|||||||
106
help.go
106
help.go
@@ -1,6 +1,8 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -9,16 +11,85 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/editors"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
|
"github.com/go-task/task/v3/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ListOptions collects list-related options
|
||||||
|
type ListOptions struct {
|
||||||
|
ListOnlyTasksWithDescriptions bool
|
||||||
|
ListAllTasks bool
|
||||||
|
FormatTaskListAsJSON bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListOptions creates a new ListOptions instance
|
||||||
|
func NewListOptions(list, listAll, listAsJson bool) ListOptions {
|
||||||
|
return ListOptions{
|
||||||
|
ListOnlyTasksWithDescriptions: list,
|
||||||
|
ListAllTasks: listAll,
|
||||||
|
FormatTaskListAsJSON: listAsJson,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldListTasks returns true if one of the options to list tasks has been set to true
|
||||||
|
func (o ListOptions) ShouldListTasks() bool {
|
||||||
|
return o.ListOnlyTasksWithDescriptions || o.ListAllTasks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates that the collection of list-related options are in a valid configuration
|
||||||
|
func (o ListOptions) Validate() error {
|
||||||
|
if o.ListOnlyTasksWithDescriptions && o.ListAllTasks {
|
||||||
|
return fmt.Errorf("task: cannot use --list and --list-all at the same time")
|
||||||
|
}
|
||||||
|
if o.FormatTaskListAsJSON && !o.ShouldListTasks() {
|
||||||
|
return fmt.Errorf("task: --json only applies to --list or --list-all")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters returns the slice of FilterFunc which filters a list
|
||||||
|
// of taskfile.Task according to the given ListOptions
|
||||||
|
func (o ListOptions) Filters() []FilterFunc {
|
||||||
|
filters := []FilterFunc{FilterOutInternal}
|
||||||
|
|
||||||
|
if o.ListOnlyTasksWithDescriptions {
|
||||||
|
filters = append(filters, FilterOutNoDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
// ListTasks prints a list of tasks.
|
// ListTasks prints a list of tasks.
|
||||||
// Tasks that match the given filters will be excluded from the list.
|
// Tasks that match the given filters will be excluded from the list.
|
||||||
// The function returns a boolean indicating whether or not tasks were found.
|
// The function returns a boolean indicating whether tasks were found
|
||||||
func (e *Executor) ListTasks(filters ...FilterFunc) bool {
|
// and an error if one was encountered while preparing the output.
|
||||||
tasks := e.GetTaskList(filters...)
|
func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
||||||
|
tasks, err := e.GetTaskList(o.Filters()...)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if o.FormatTaskListAsJSON {
|
||||||
|
output, err := e.ToEditorOutput(tasks)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(e.Stdout)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
if err := encoder.Encode(output); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(tasks) > 0, nil
|
||||||
|
}
|
||||||
if len(tasks) == 0 {
|
if len(tasks) == 0 {
|
||||||
return false
|
if o.ListOnlyTasksWithDescriptions {
|
||||||
|
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks")
|
||||||
|
} else if o.ListAllTasks {
|
||||||
|
e.Logger.Outf(logger.Yellow, "task: No tasks available")
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
|
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
|
||||||
|
|
||||||
@@ -31,10 +102,12 @@ func (e *Executor) ListTasks(filters ...FilterFunc) bool {
|
|||||||
if len(task.Aliases) > 0 {
|
if len(task.Aliases) > 0 {
|
||||||
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, "\n")
|
_, _ = fmt.Fprint(w, "\n")
|
||||||
}
|
}
|
||||||
w.Flush()
|
if err := w.Flush(); err != nil {
|
||||||
return true
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTaskNames prints only the task names in a Taskfile.
|
// ListTaskNames prints only the task names in a Taskfile.
|
||||||
@@ -69,3 +142,22 @@ func (e *Executor) ListTaskNames(allTasks bool) {
|
|||||||
fmt.Fprintln(w, t)
|
fmt.Fprintln(w, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Executor) ToEditorOutput(tasks []*taskfile.Task) (*editors.Output, error) {
|
||||||
|
o := &editors.Output{
|
||||||
|
Tasks: make([]editors.Task, len(tasks)),
|
||||||
|
}
|
||||||
|
for i, t := range tasks {
|
||||||
|
upToDate, err := e.isTaskUpToDate(context.Background(), t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o.Tasks[i] = editors.Task{
|
||||||
|
Name: t.Name(),
|
||||||
|
Desc: t.Desc,
|
||||||
|
Summary: t.Summary,
|
||||||
|
UpToDate: upToDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|||||||
14
internal/editors/output.go
Normal file
14
internal/editors/output.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package editors
|
||||||
|
|
||||||
|
// Output wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||||
|
type Output struct {
|
||||||
|
Tasks []Task `json:"tasks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task describes a single task
|
||||||
|
type Task struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
|
UpToDate bool `json:"up_to_date"`
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package execext
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -17,12 +18,14 @@ import (
|
|||||||
|
|
||||||
// RunCommandOptions is the options for the RunCommand func
|
// RunCommandOptions is the options for the RunCommand func
|
||||||
type RunCommandOptions struct {
|
type RunCommandOptions struct {
|
||||||
Command string
|
Command string
|
||||||
Dir string
|
Dir string
|
||||||
Env []string
|
Env []string
|
||||||
Stdin io.Reader
|
PosixOpts []string
|
||||||
Stdout io.Writer
|
BashOpts []string
|
||||||
Stderr io.Writer
|
Stdin io.Reader
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -36,9 +39,18 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
return ErrNilOptions
|
return ErrNilOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "")
|
// Set "-e" or "errexit" by default
|
||||||
if err != nil {
|
opts.PosixOpts = append(opts.PosixOpts, "e")
|
||||||
return err
|
|
||||||
|
// Format POSIX options into a slice that mvdan/sh understands
|
||||||
|
var params []string
|
||||||
|
for _, opt := range opts.PosixOpts {
|
||||||
|
if len(opt) == 1 {
|
||||||
|
params = append(params, fmt.Sprintf("-%s", opt))
|
||||||
|
} else {
|
||||||
|
params = append(params, "-o")
|
||||||
|
params = append(params, opt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
environ := opts.Env
|
environ := opts.Env
|
||||||
@@ -47,7 +59,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r, err := interp.New(
|
r, err := interp.New(
|
||||||
interp.Params("-e"),
|
interp.Params(params...),
|
||||||
interp.Env(expand.ListEnviron(environ...)),
|
interp.Env(expand.ListEnviron(environ...)),
|
||||||
interp.ExecHandler(interp.DefaultExecHandler(15*time.Second)),
|
interp.ExecHandler(interp.DefaultExecHandler(15*time.Second)),
|
||||||
interp.OpenHandler(openHandler),
|
interp.OpenHandler(openHandler),
|
||||||
@@ -58,6 +70,25 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parser := syntax.NewParser()
|
||||||
|
|
||||||
|
// Run any shopt commands
|
||||||
|
if len(opts.BashOpts) > 0 {
|
||||||
|
shoptCmdStr := fmt.Sprintf("shopt -s %s", strings.Join(opts.BashOpts, " "))
|
||||||
|
shoptCmd, err := parser.Parse(strings.NewReader(shoptCmdStr), "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.Run(ctx, shoptCmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the user-defined command
|
||||||
|
p, err := parser.Parse(strings.NewReader(opts.Command), "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return r.Run(ctx, p)
|
return r.Run(ctx, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
62
internal/goext/meta.go
Normal file
62
internal/goext/meta.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package goext
|
||||||
|
|
||||||
|
// NOTE(@andreynering): The lists in this file were copied from:
|
||||||
|
//
|
||||||
|
// https://github.com/golang/go/blob/master/src/go/build/syslist.go
|
||||||
|
|
||||||
|
func IsKnownOS(str string) bool {
|
||||||
|
_, known := knownOS[str]
|
||||||
|
return known
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsKnownArch(str string) bool {
|
||||||
|
_, known := knownArch[str]
|
||||||
|
return known
|
||||||
|
}
|
||||||
|
|
||||||
|
var knownOS = map[string]struct{}{
|
||||||
|
"aix": {},
|
||||||
|
"android": {},
|
||||||
|
"darwin": {},
|
||||||
|
"dragonfly": {},
|
||||||
|
"freebsd": {},
|
||||||
|
"hurd": {},
|
||||||
|
"illumos": {},
|
||||||
|
"ios": {},
|
||||||
|
"js": {},
|
||||||
|
"linux": {},
|
||||||
|
"nacl": {},
|
||||||
|
"netbsd": {},
|
||||||
|
"openbsd": {},
|
||||||
|
"plan9": {},
|
||||||
|
"solaris": {},
|
||||||
|
"windows": {},
|
||||||
|
"zos": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
var knownArch = map[string]struct{}{
|
||||||
|
"386": {},
|
||||||
|
"amd64": {},
|
||||||
|
"amd64p32": {},
|
||||||
|
"arm": {},
|
||||||
|
"armbe": {},
|
||||||
|
"arm64": {},
|
||||||
|
"arm64be": {},
|
||||||
|
"loong64": {},
|
||||||
|
"mips": {},
|
||||||
|
"mipsle": {},
|
||||||
|
"mips64": {},
|
||||||
|
"mips64le": {},
|
||||||
|
"mips64p32": {},
|
||||||
|
"mips64p32le": {},
|
||||||
|
"ppc": {},
|
||||||
|
"ppc64": {},
|
||||||
|
"ppc64le": {},
|
||||||
|
"riscv": {},
|
||||||
|
"riscv64": {},
|
||||||
|
"s390": {},
|
||||||
|
"s390x": {},
|
||||||
|
"sparc": {},
|
||||||
|
"sparc64": {},
|
||||||
|
"wasm": {},
|
||||||
|
}
|
||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/output"
|
"github.com/go-task/task/v3/internal/output"
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
|
"github.com/go-task/task/v3/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInterleaved(t *testing.T) {
|
func TestInterleaved(t *testing.T) {
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
// This code is released under the MIT License
|
|
||||||
// Copyright (c) 2020 Marco Molteni and the timeit contributors.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const usage = `sleepit: sleep for the specified duration, optionally handling signals
|
|
||||||
When the line "sleepit: ready" is printed, it means that it is safe to send signals to it
|
|
||||||
|
|
||||||
Usage: sleepit <command> [<args>]
|
|
||||||
|
|
||||||
Commands
|
|
||||||
|
|
||||||
default Use default action: on reception of SIGINT terminate abruptly
|
|
||||||
handle Handle signals: on reception of SIGINT perform cleanup before exiting
|
|
||||||
version Show the sleepit version`
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Filled by the linker.
|
|
||||||
fullVersion = "unknown" // example: v0.0.9-8-g941583d027-dirty
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
os.Exit(run(os.Args[1:]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(args []string) int {
|
|
||||||
if len(args) < 1 {
|
|
||||||
fmt.Fprintln(os.Stderr, usage)
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultCmd := flag.NewFlagSet("default", flag.ExitOnError)
|
|
||||||
defaultSleep := defaultCmd.Duration("sleep", 5*time.Second, "Sleep duration")
|
|
||||||
|
|
||||||
handleCmd := flag.NewFlagSet("handle", flag.ExitOnError)
|
|
||||||
handleSleep := handleCmd.Duration("sleep", 5*time.Second, "Sleep duration")
|
|
||||||
handleCleanup := handleCmd.Duration("cleanup", 5*time.Second, "Cleanup duration")
|
|
||||||
handleTermAfter := handleCmd.Int("term-after", 0,
|
|
||||||
"Terminate immediately after `N` signals.\n"+
|
|
||||||
"Default is to terminate only when the cleanup phase has completed.")
|
|
||||||
|
|
||||||
versionCmd := flag.NewFlagSet("version", flag.ExitOnError)
|
|
||||||
|
|
||||||
switch args[0] {
|
|
||||||
|
|
||||||
case "default":
|
|
||||||
_ = defaultCmd.Parse(args[1:])
|
|
||||||
if len(defaultCmd.Args()) > 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "default: unexpected arguments: %v\n", defaultCmd.Args())
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
return supervisor(*defaultSleep, 0, 0, nil)
|
|
||||||
|
|
||||||
case "handle":
|
|
||||||
_ = handleCmd.Parse(args[1:])
|
|
||||||
if *handleTermAfter == 1 {
|
|
||||||
fmt.Fprintf(os.Stderr, "handle: term-after cannot be 1\n")
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
if len(handleCmd.Args()) > 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "handle: unexpected arguments: %v\n", handleCmd.Args())
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigCh, os.Interrupt) // Ctrl-C -> SIGINT
|
|
||||||
return supervisor(*handleSleep, *handleCleanup, *handleTermAfter, sigCh)
|
|
||||||
|
|
||||||
case "version":
|
|
||||||
_ = versionCmd.Parse(args[1:])
|
|
||||||
if len(versionCmd.Args()) > 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "version: unexpected arguments: %v\n", versionCmd.Args())
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
fmt.Printf("sleepit version %s\n", fullVersion)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
default:
|
|
||||||
fmt.Fprintln(os.Stderr, usage)
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func supervisor(
|
|
||||||
sleep time.Duration,
|
|
||||||
cleanup time.Duration,
|
|
||||||
termAfter int,
|
|
||||||
sigCh <-chan os.Signal,
|
|
||||||
) int {
|
|
||||||
fmt.Printf("sleepit: ready\n")
|
|
||||||
fmt.Printf("sleepit: PID=%d sleep=%v cleanup=%v\n",
|
|
||||||
os.Getpid(), sleep, cleanup)
|
|
||||||
|
|
||||||
cancelWork := make(chan struct{})
|
|
||||||
workerDone := worker(cancelWork, sleep, "work")
|
|
||||||
|
|
||||||
cancelCleaner := make(chan struct{})
|
|
||||||
var cleanerDone <-chan struct{}
|
|
||||||
|
|
||||||
sigCount := 0
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case sig := <-sigCh:
|
|
||||||
sigCount++
|
|
||||||
fmt.Printf("sleepit: got signal=%s count=%d\n", sig, sigCount)
|
|
||||||
if sigCount == 1 {
|
|
||||||
// since `cancelWork` is unbuffered, sending will be synchronous:
|
|
||||||
// we are ensured that the worker has terminated before starting cleanup.
|
|
||||||
// This is important in some real-life situations.
|
|
||||||
cancelWork <- struct{}{}
|
|
||||||
cleanerDone = worker(cancelCleaner, cleanup, "cleanup")
|
|
||||||
}
|
|
||||||
if sigCount == termAfter {
|
|
||||||
cancelCleaner <- struct{}{}
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
case <-workerDone:
|
|
||||||
return 0
|
|
||||||
case <-cleanerDone:
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a worker goroutine and return immediately a `workerDone` channel.
|
|
||||||
// The goroutine will prepend its prints with the prefix `name`.
|
|
||||||
// The goroutine will simulate some work and will terminate when one of the following
|
|
||||||
// conditions happens:
|
|
||||||
// 1. When `howlong` is elapsed. This case will be signaled on the `workerDone` channel.
|
|
||||||
// 2. When something happens on channel `canceled`. Note that this simulates real-life,
|
|
||||||
// so cancellation is not instantaneous: if the caller wants a synchronous cancel,
|
|
||||||
// it should send a message; if instead it wants an asynchronous cancel, it should
|
|
||||||
// close the channel.
|
|
||||||
func worker(
|
|
||||||
canceled <-chan struct{},
|
|
||||||
howlong time.Duration,
|
|
||||||
name string,
|
|
||||||
) <-chan struct{} {
|
|
||||||
workerDone := make(chan struct{})
|
|
||||||
deadline := time.Now().Add(howlong)
|
|
||||||
go func() {
|
|
||||||
fmt.Printf("sleepit: %s started\n", name)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-canceled:
|
|
||||||
fmt.Printf("sleepit: %s canceled\n", name)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
if doSomeWork(deadline) {
|
|
||||||
fmt.Printf("sleepit: %s done\n", name) // <== NOTE THIS LINE
|
|
||||||
workerDone <- struct{}{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return workerDone
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do some work and then return, so that the caller can decide wether to continue or not.
|
|
||||||
// Return true when all work is done.
|
|
||||||
func doSomeWork(deadline time.Time) bool {
|
|
||||||
if time.Now().After(deadline) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
timeout := 100 * time.Millisecond
|
|
||||||
time.Sleep(timeout)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
20
internal/slicesext/slicesext.go
Normal file
20
internal/slicesext/slicesext.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package slicesext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UniqueJoin[T constraints.Ordered](ss ...[]T) []T {
|
||||||
|
var length int
|
||||||
|
for _, s := range ss {
|
||||||
|
length += len(s)
|
||||||
|
}
|
||||||
|
r := make([]T, length)
|
||||||
|
var i int
|
||||||
|
for _, s := range ss {
|
||||||
|
i += copy(r[i:], s)
|
||||||
|
}
|
||||||
|
slices.Sort(r)
|
||||||
|
return slices.Compact(r)
|
||||||
|
}
|
||||||
@@ -85,6 +85,7 @@ func (c *Checksum) checksum(files ...string) (string, error) {
|
|||||||
if _, err = io.Copy(h, f); err != nil {
|
if _, err = io.Copy(h, f); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||||
@@ -109,12 +110,12 @@ func (*Checksum) Kind() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checksum) checksumFilePath() string {
|
func (c *Checksum) checksumFilePath() string {
|
||||||
return filepath.Join(c.TempDir, "checksum", c.normalizeFilename(c.Task))
|
return filepath.Join(c.TempDir, "checksum", normalizeFilename(c.Task))
|
||||||
}
|
}
|
||||||
|
|
||||||
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
|
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
|
||||||
|
|
||||||
// replaces invalid caracters on filenames with "-"
|
// replaces invalid caracters on filenames with "-"
|
||||||
func (*Checksum) normalizeFilename(f string) string {
|
func normalizeFilename(f string) string {
|
||||||
return checksumFilenameRegexp.ReplaceAllString(f, "-")
|
return checksumFilenameRegexp.ReplaceAllString(f, "-")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ func TestNormalizeFilename(t *testing.T) {
|
|||||||
{"foo1bar2baz3", "foo1bar2baz3"},
|
{"foo1bar2baz3", "foo1bar2baz3"},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
assert.Equal(t, test.Out, (&Checksum{}).normalizeFilename(test.In))
|
assert.Equal(t, test.Out, normalizeFilename(test.In))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,24 @@ package status
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Timestamp checks if any source change compared with the generated files,
|
// Timestamp checks if any source change compared with the generated files,
|
||||||
// using file modifications timestamps.
|
// using file modifications timestamps.
|
||||||
type Timestamp struct {
|
type Timestamp struct {
|
||||||
|
TempDir string
|
||||||
|
Task string
|
||||||
Dir string
|
Dir string
|
||||||
Sources []string
|
Sources []string
|
||||||
Generates []string
|
Generates []string
|
||||||
|
Dry bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUpToDate implements the Checker interface
|
// IsUpToDate implements the Checker interface
|
||||||
func (t *Timestamp) IsUpToDate() (bool, error) {
|
func (t *Timestamp) IsUpToDate() (bool, error) {
|
||||||
if len(t.Sources) == 0 || len(t.Generates) == 0 {
|
if len(t.Sources) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,17 +32,51 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcesMaxTime, err := getMaxTime(sources...)
|
timestampFile := t.timestampFilePath()
|
||||||
if err != nil || sourcesMaxTime.IsZero() {
|
|
||||||
|
// If the file exists, add the file path to the generates.
|
||||||
|
// If the generate file is old, the task will be executed.
|
||||||
|
_, err = os.Stat(timestampFile)
|
||||||
|
if err == nil {
|
||||||
|
generates = append(generates, timestampFile)
|
||||||
|
} else {
|
||||||
|
// Create the timestamp file for the next execution when the file does not exist.
|
||||||
|
if !t.Dry {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(timestampFile), 0o755); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
f, err := os.Create(timestampFile)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
taskTime := time.Now()
|
||||||
|
|
||||||
|
// Compare the time of the generates and sources. If the generates are old, the task will be executed.
|
||||||
|
|
||||||
|
// Get the max time of the generates.
|
||||||
|
generateMaxTime, err := getMaxTime(generates...)
|
||||||
|
if err != nil || generateMaxTime.IsZero() {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
generatesMinTime, err := getMinTime(generates...)
|
// Check if any of the source files is newer than the max time of the generates.
|
||||||
if err != nil || generatesMinTime.IsZero() {
|
shouldUpdate, err := anyFileNewerThan(sources, generateMaxTime)
|
||||||
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return !generatesMinTime.Before(sourcesMaxTime), nil
|
// Modify the metadata of the file to the the current time.
|
||||||
|
if !t.Dry {
|
||||||
|
if err := os.Chtimes(timestampFile, taskTime, taskTime); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !shouldUpdate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Timestamp) Kind() string {
|
func (t *Timestamp) Kind() string {
|
||||||
@@ -64,18 +102,6 @@ func (t *Timestamp) Value() (interface{}, error) {
|
|||||||
return sourcesMaxTime, nil
|
return sourcesMaxTime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMinTime(files ...string) (time.Time, error) {
|
|
||||||
var t time.Time
|
|
||||||
for _, f := range files {
|
|
||||||
info, err := os.Stat(f)
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}, err
|
|
||||||
}
|
|
||||||
t = minTime(t, info.ModTime())
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMaxTime(files ...string) (time.Time, error) {
|
func getMaxTime(files ...string) (time.Time, error) {
|
||||||
var t time.Time
|
var t time.Time
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
@@ -88,13 +114,6 @@ func getMaxTime(files ...string) (time.Time, error) {
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func minTime(a, b time.Time) time.Time {
|
|
||||||
if !a.IsZero() && a.Before(b) {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func maxTime(a, b time.Time) time.Time {
|
func maxTime(a, b time.Time) time.Time {
|
||||||
if a.After(b) {
|
if a.After(b) {
|
||||||
return a
|
return a
|
||||||
@@ -102,7 +121,26 @@ func maxTime(a, b time.Time) time.Time {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the modification time of any of the files is newer than the the given time, returns true.
|
||||||
|
// This function is lazy, as it stops when it finds a file newer than the given time.
|
||||||
|
func anyFileNewerThan(files []string, givenTime time.Time) (bool, error) {
|
||||||
|
for _, f := range files {
|
||||||
|
info, err := os.Stat(f)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if info.ModTime().After(givenTime) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
// OnError implements the Checker interface
|
// OnError implements the Checker interface
|
||||||
func (*Timestamp) OnError() error {
|
func (*Timestamp) OnError() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Timestamp) timestampFilePath() string {
|
||||||
|
return filepath.Join(t.TempDir, "timestamp", normalizeFilename(t.Task))
|
||||||
|
}
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@go-task/cli",
|
"name": "@go-task/cli",
|
||||||
"version": "3.19.0",
|
"version": "3.20.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@go-task/cli",
|
"name": "@go-task/cli",
|
||||||
"version": "3.19.0",
|
"version": "3.20.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@go-task/cli",
|
"name": "@go-task/cli",
|
||||||
"version": "3.19.0",
|
"version": "3.20.0",
|
||||||
"description": "A task runner / simpler Make alternative written in Go",
|
"description": "A task runner / simpler Make alternative written in Go",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "go-npm install",
|
"postinstall": "go-npm install",
|
||||||
|
|||||||
@@ -87,9 +87,12 @@ func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
|||||||
|
|
||||||
func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
|
func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
|
||||||
return &status.Timestamp{
|
return &status.Timestamp{
|
||||||
|
TempDir: e.TempDir,
|
||||||
|
Task: t.Name(),
|
||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
Sources: t.Sources,
|
Sources: t.Sources,
|
||||||
Generates: t.Generates,
|
Generates: t.Generates,
|
||||||
|
Dry: e.Dry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
128
task.go
128
task.go
@@ -5,15 +5,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/compiler"
|
"github.com/go-task/task/v3/internal/compiler"
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/output"
|
"github.com/go-task/task/v3/internal/output"
|
||||||
|
"github.com/go-task/task/v3/internal/slicesext"
|
||||||
"github.com/go-task/task/v3/internal/summary"
|
"github.com/go-task/task/v3/internal/summary"
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
@@ -45,7 +48,7 @@ type Executor struct {
|
|||||||
Parallel bool
|
Parallel bool
|
||||||
Color bool
|
Color bool
|
||||||
Concurrency int
|
Concurrency int
|
||||||
Interval string
|
Interval time.Duration
|
||||||
|
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
@@ -72,12 +75,20 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
|||||||
for _, call := range calls {
|
for _, call := range calls {
|
||||||
task, err := e.GetTask(call)
|
task, err := e.GetTask(call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.ListTasks(FilterOutInternal(), FilterOutNoDesc())
|
if _, ok := err.(*taskNotFoundError); ok {
|
||||||
|
if _, err := e.ListTasks(ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Internal {
|
if task.Internal {
|
||||||
e.ListTasks(FilterOutInternal(), FilterOutNoDesc())
|
if _, ok := err.(*taskNotFoundError); ok {
|
||||||
|
if _, err := e.ListTasks(ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return &taskInternalError{taskName: call.Task}
|
return &taskInternalError{taskName: call.Task}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,6 +137,11 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
return e.startExecution(ctx, t, func(ctx context.Context) error {
|
return e.startExecution(ctx, t, func(ctx context.Context) error {
|
||||||
|
if !shouldRunOnCurrentPlatform(t.Platforms) {
|
||||||
|
e.Logger.VerboseOutf(logger.Yellow, `task: "%s" not for current platform - ignored`, call.Task)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" started`, call.Task)
|
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" started`, call.Task)
|
||||||
if err := e.runDeps(ctx, t); err != nil {
|
if err := e.runDeps(ctx, t); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -243,6 +259,11 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case cmd.Cmd != "":
|
case cmd.Cmd != "":
|
||||||
|
if !shouldRunOnCurrentPlatform(cmd.Platforms) {
|
||||||
|
e.Logger.VerboseOutf(logger.Yellow, `task: [%s] %s not for current platform - ignored`, t.Name(), cmd.Cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
||||||
e.Logger.Errf(logger.Green, "task: [%s] %s", t.Name(), cmd.Cmd)
|
e.Logger.Errf(logger.Green, "task: [%s] %s", t.Name(), cmd.Cmd)
|
||||||
}
|
}
|
||||||
@@ -263,17 +284,19 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
stdOut, stdErr, close := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater)
|
stdOut, stdErr, close := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := close(); err != nil {
|
if err := close(); err != nil {
|
||||||
e.Logger.Errf(logger.Red, "task: unable to close writter: %v", err)
|
e.Logger.Errf(logger.Red, "task: unable to close writer: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
|
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
Command: cmd.Cmd,
|
Command: cmd.Cmd,
|
||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
Env: getEnviron(t),
|
Env: getEnviron(t),
|
||||||
Stdin: e.Stdin,
|
PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set, cmd.Set),
|
||||||
Stdout: stdOut,
|
BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt, cmd.Shopt),
|
||||||
Stderr: stdErr,
|
Stdin: e.Stdin,
|
||||||
|
Stdout: stdOut,
|
||||||
|
Stderr: stdErr,
|
||||||
})
|
})
|
||||||
if execext.IsExitError(err) && cmd.IgnoreError {
|
if execext.IsExitError(err) && cmd.IgnoreError {
|
||||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v", t.Name(), err)
|
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v", t.Name(), err)
|
||||||
@@ -377,26 +400,42 @@ func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
return matchingTask, nil
|
return matchingTask, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterFunc func(tasks []*taskfile.Task) []*taskfile.Task
|
type FilterFunc func(task *taskfile.Task) bool
|
||||||
|
|
||||||
func (e *Executor) GetTaskList(filters ...FilterFunc) []*taskfile.Task {
|
func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*taskfile.Task, error) {
|
||||||
tasks := make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
tasks := make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||||
|
|
||||||
|
// Create an error group to wait for each task to be compiled
|
||||||
|
var g errgroup.Group
|
||||||
|
|
||||||
// Fetch and compile the list of tasks
|
// Fetch and compile the list of tasks
|
||||||
for _, task := range e.Taskfile.Tasks {
|
for key := range e.Taskfile.Tasks {
|
||||||
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
|
task := e.Taskfile.Tasks[key]
|
||||||
if err == nil {
|
g.Go(func() error {
|
||||||
task = compiledTask
|
|
||||||
}
|
// Check if we should filter the task
|
||||||
tasks = append(tasks, task)
|
for _, filter := range filters {
|
||||||
|
if filter(task) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile the task
|
||||||
|
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
|
||||||
|
if err == nil {
|
||||||
|
task = compiledTask
|
||||||
|
}
|
||||||
|
tasks = append(tasks, task)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter the tasks
|
// Wait for all the go routines to finish
|
||||||
for _, filter := range filters {
|
if err := g.Wait(); err != nil {
|
||||||
tasks = filter(tasks)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the tasks
|
// Sort the tasks.
|
||||||
// Tasks that are not namespaced should be listed before tasks that are.
|
// Tasks that are not namespaced should be listed before tasks that are.
|
||||||
// We detect this by searching for a ':' in the task name.
|
// We detect this by searching for a ':' in the task name.
|
||||||
sort.Slice(tasks, func(i, j int) bool {
|
sort.Slice(tasks, func(i, j int) bool {
|
||||||
@@ -411,38 +450,27 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) []*taskfile.Task {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
return tasks
|
return tasks, nil
|
||||||
}
|
|
||||||
|
|
||||||
// Filter is a generic task filtering function. It will remove each task in the
|
|
||||||
// slice where the result of the given function is true.
|
|
||||||
func Filter(f func(task *taskfile.Task) bool) FilterFunc {
|
|
||||||
return func(tasks []*taskfile.Task) []*taskfile.Task {
|
|
||||||
shift := 0
|
|
||||||
for _, task := range tasks {
|
|
||||||
if !f(task) {
|
|
||||||
tasks[shift] = task
|
|
||||||
shift++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This loop stops any memory leaks
|
|
||||||
for j := shift; j < len(tasks); j++ {
|
|
||||||
tasks[j] = nil
|
|
||||||
}
|
|
||||||
return slices.Clip(tasks[:shift])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterOutNoDesc removes all tasks that do not contain a description.
|
// FilterOutNoDesc removes all tasks that do not contain a description.
|
||||||
func FilterOutNoDesc() FilterFunc {
|
func FilterOutNoDesc(task *taskfile.Task) bool {
|
||||||
return Filter(func(task *taskfile.Task) bool {
|
return task.Desc == ""
|
||||||
return task.Desc == ""
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterOutInternal removes all tasks that are marked as internal.
|
// FilterOutInternal removes all tasks that are marked as internal.
|
||||||
func FilterOutInternal() FilterFunc {
|
func FilterOutInternal(task *taskfile.Task) bool {
|
||||||
return Filter(func(task *taskfile.Task) bool {
|
return task.Internal
|
||||||
return task.Internal
|
}
|
||||||
})
|
|
||||||
|
func shouldRunOnCurrentPlatform(platforms []*taskfile.Platform) bool {
|
||||||
|
if len(platforms) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, p := range platforms {
|
||||||
|
if (p.OS == "" || p.OS == runtime.GOOS) && (p.Arch == "" || p.Arch == runtime.GOARCH) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
172
task_test.go
172
task_test.go
@@ -10,7 +10,6 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
@@ -606,7 +605,9 @@ func TestNoLabelInList(t *testing.T) {
|
|||||||
Stderr: &buff,
|
Stderr: &buff,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc())
|
if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
assert.Contains(t, buff.String(), "foo")
|
assert.Contains(t, buff.String(), "foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,7 +625,9 @@ func TestListAllShowsNoDesc(t *testing.T) {
|
|||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
e.ListTasks(task.FilterOutInternal())
|
if _, err := e.ListTasks(task.ListOptions{ListAllTasks: true}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
for _, title = range []string{
|
for _, title = range []string{
|
||||||
"foo",
|
"foo",
|
||||||
"voo",
|
"voo",
|
||||||
@@ -646,7 +649,9 @@ func TestListCanListDescOnly(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc())
|
if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
assert.Contains(t, buff.String(), "foo")
|
assert.Contains(t, buff.String(), "foo")
|
||||||
@@ -1034,7 +1039,7 @@ func TestIncludesInternal(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"included internal task via task", "task-1", false, "Hello, World!\n"},
|
{"included internal task via task", "task-1", false, "Hello, World!\n"},
|
||||||
{"included internal task via dep", "task-2", false, "Hello, World!\n"},
|
{"included internal task via dep", "task-2", false, "Hello, World!\n"},
|
||||||
{"included internal direct", "included:task-3", true, ""},
|
{"included internal direct", "included:task-3", true, "task: No tasks with description available. Try --list-all to list all tasks\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@@ -1643,67 +1648,6 @@ func TestEvaluateSymlinksInPaths(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileWatcherInterval(t *testing.T) {
|
|
||||||
const dir = "testdata/watcher_interval"
|
|
||||||
expectedOutput := strings.TrimSpace(`
|
|
||||||
task: Started watching for tasks: default
|
|
||||||
task: [default] echo "Hello, World!"
|
|
||||||
Hello, World!
|
|
||||||
task: [default] echo "Hello, World!"
|
|
||||||
Hello, World!
|
|
||||||
`)
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := &task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
Watch: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, e.Setup())
|
|
||||||
buff.Reset()
|
|
||||||
|
|
||||||
err := os.MkdirAll(filepathext.SmartJoin(dir, "src"), 0755)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
err = os.WriteFile(filepathext.SmartJoin(dir, "src/a"), []byte("test"), 0644)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
go func(ctx context.Context) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
err := e.Run(ctx, taskfile.Call{Task: "default"})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(ctx)
|
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
err = os.WriteFile(filepathext.SmartJoin(dir, "src/a"), []byte("test updated"), 0644)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
time.Sleep(700 * time.Millisecond)
|
|
||||||
cancel()
|
|
||||||
assert.Equal(t, expectedOutput, strings.TrimSpace(buff.String()))
|
|
||||||
buff.Reset()
|
|
||||||
err = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
err = os.RemoveAll(filepathext.SmartJoin(dir, "src"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTaskfileWalk(t *testing.T) {
|
func TestTaskfileWalk(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -1752,3 +1696,99 @@ func TestUserWorkingDirectory(t *testing.T) {
|
|||||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||||
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
|
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlatforms(t *testing.T) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: "testdata/platforms",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build-" + runtime.GOOS}))
|
||||||
|
assert.Equal(t, fmt.Sprintf("task: [build-%s] echo 'Running task on %s'\nRunning task on %s\n", runtime.GOOS, runtime.GOOS, runtime.GOOS), buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPOSIXShellOptsGlobalLevel(t *testing.T) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: "testdata/shopts/global_level",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
err := e.Run(context.Background(), taskfile.Call{Task: "pipefail"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "pipefail\ton\n", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPOSIXShellOptsTaskLevel(t *testing.T) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: "testdata/shopts/task_level",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
err := e.Run(context.Background(), taskfile.Call{Task: "pipefail"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "pipefail\ton\n", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPOSIXShellOptsCommandLevel(t *testing.T) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: "testdata/shopts/command_level",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
err := e.Run(context.Background(), taskfile.Call{Task: "pipefail"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "pipefail\ton\n", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBashShellOptsGlobalLevel(t *testing.T) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: "testdata/shopts/global_level",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
err := e.Run(context.Background(), taskfile.Call{Task: "globstar"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "globstar\ton\n", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBashShellOptsTaskLevel(t *testing.T) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: "testdata/shopts/task_level",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
err := e.Run(context.Background(), taskfile.Call{Task: "globstar"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "globstar\ton\n", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBashShellOptsCommandLevel(t *testing.T) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: "testdata/shopts/command_level",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
err := e.Run(context.Background(), taskfile.Call{Task: "globstar"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "globstar\ton\n", buff.String())
|
||||||
|
}
|
||||||
|
|||||||
152
taskfile/cmd.go
152
taskfile/cmd.go
@@ -1,13 +1,22 @@
|
|||||||
package taskfile
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
// 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
|
||||||
|
Set []string
|
||||||
|
Shopt []string
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
IgnoreError bool
|
IgnoreError bool
|
||||||
Defer bool
|
Defer bool
|
||||||
|
Platforms []*Platform
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dep is a task dependency
|
// Dep is a task dependency
|
||||||
@@ -16,68 +25,99 @@ type Dep struct {
|
|||||||
Vars *Vars
|
Vars *Vars
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||||
func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
switch node.Kind {
|
||||||
var cmd string
|
|
||||||
if err := unmarshal(&cmd); err == nil {
|
case yaml.ScalarNode:
|
||||||
|
var cmd string
|
||||||
|
if err := node.Decode(&cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
c.Cmd = cmd
|
c.Cmd = cmd
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case yaml.MappingNode:
|
||||||
|
|
||||||
|
// A command with additional options
|
||||||
|
var cmdStruct struct {
|
||||||
|
Cmd string
|
||||||
|
Silent bool
|
||||||
|
Set []string
|
||||||
|
Shopt []string
|
||||||
|
IgnoreError bool `yaml:"ignore_error"`
|
||||||
|
Platforms []*Platform
|
||||||
|
}
|
||||||
|
if err := node.Decode(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
|
||||||
|
c.Cmd = cmdStruct.Cmd
|
||||||
|
c.Silent = cmdStruct.Silent
|
||||||
|
c.Set = cmdStruct.Set
|
||||||
|
c.Shopt = cmdStruct.Shopt
|
||||||
|
c.IgnoreError = cmdStruct.IgnoreError
|
||||||
|
c.Platforms = cmdStruct.Platforms
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A deferred command
|
||||||
|
var deferredCmd struct {
|
||||||
|
Defer string
|
||||||
|
}
|
||||||
|
if err := node.Decode(&deferredCmd); err == nil && deferredCmd.Defer != "" {
|
||||||
|
c.Defer = true
|
||||||
|
c.Cmd = deferredCmd.Defer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A deferred task call
|
||||||
|
var deferredCall struct {
|
||||||
|
Defer Call
|
||||||
|
}
|
||||||
|
if err := node.Decode(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
|
||||||
|
c.Defer = true
|
||||||
|
c.Task = deferredCall.Defer.Task
|
||||||
|
c.Vars = deferredCall.Defer.Vars
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A task call
|
||||||
|
var taskCall struct {
|
||||||
|
Task string
|
||||||
|
Vars *Vars
|
||||||
|
}
|
||||||
|
if err := node.Decode(&taskCall); err == nil && taskCall.Task != "" {
|
||||||
|
c.Task = taskCall.Task
|
||||||
|
c.Vars = taskCall.Vars
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
|
||||||
}
|
}
|
||||||
var cmdStruct struct {
|
|
||||||
Cmd string
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
|
||||||
Silent bool
|
|
||||||
IgnoreError bool `yaml:"ignore_error"`
|
|
||||||
}
|
|
||||||
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
|
|
||||||
c.Cmd = cmdStruct.Cmd
|
|
||||||
c.Silent = cmdStruct.Silent
|
|
||||||
c.IgnoreError = cmdStruct.IgnoreError
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var deferredCmd struct {
|
|
||||||
Defer string
|
|
||||||
}
|
|
||||||
if err := unmarshal(&deferredCmd); err == nil && deferredCmd.Defer != "" {
|
|
||||||
c.Defer = true
|
|
||||||
c.Cmd = deferredCmd.Defer
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var deferredCall struct {
|
|
||||||
Defer Call
|
|
||||||
}
|
|
||||||
if err := unmarshal(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
|
|
||||||
c.Defer = true
|
|
||||||
c.Task = deferredCall.Defer.Task
|
|
||||||
c.Vars = deferredCall.Defer.Vars
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var taskCall struct {
|
|
||||||
Task string
|
|
||||||
Vars *Vars
|
|
||||||
}
|
|
||||||
if err := unmarshal(&taskCall); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Task = taskCall.Task
|
|
||||||
c.Vars = taskCall.Vars
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
|
||||||
func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
switch node.Kind {
|
||||||
var task string
|
|
||||||
if err := unmarshal(&task); err == nil {
|
case yaml.ScalarNode:
|
||||||
|
var task string
|
||||||
|
if err := node.Decode(&task); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
d.Task = task
|
d.Task = task
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case yaml.MappingNode:
|
||||||
|
var taskCall struct {
|
||||||
|
Task string
|
||||||
|
Vars *Vars
|
||||||
|
}
|
||||||
|
if err := node.Decode(&taskCall); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.Task = taskCall.Task
|
||||||
|
d.Vars = taskCall.Vars
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
var taskCall struct {
|
|
||||||
Task string
|
return fmt.Errorf("cannot unmarshal %s into dependency", node.ShortTag())
|
||||||
Vars *Vars
|
|
||||||
}
|
|
||||||
if err := unmarshal(&taskCall); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.Task = taskCall.Task
|
|
||||||
d.Vars = taskCall.Vars
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package taskfile
|
package taskfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -32,24 +31,26 @@ type IncludedTaskfiles struct {
|
|||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||||
func (tfs *IncludedTaskfiles) UnmarshalYAML(node *yaml.Node) error {
|
func (tfs *IncludedTaskfiles) UnmarshalYAML(node *yaml.Node) error {
|
||||||
if node.Kind != yaml.MappingNode {
|
switch node.Kind {
|
||||||
return errors.New("task: includes is not a map")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(@andreynering): on this style of custom unmarsheling,
|
case yaml.MappingNode:
|
||||||
// even number contains the keys, while odd numbers contains
|
// NOTE(@andreynering): on this style of custom unmarshalling,
|
||||||
// the values.
|
// even number contains the keys, while odd numbers contains
|
||||||
for i := 0; i < len(node.Content); i += 2 {
|
// the values.
|
||||||
keyNode := node.Content[i]
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
valueNode := node.Content[i+1]
|
keyNode := node.Content[i]
|
||||||
|
valueNode := node.Content[i+1]
|
||||||
|
|
||||||
var v IncludedTaskfile
|
var v IncludedTaskfile
|
||||||
if err := valueNode.Decode(&v); err != nil {
|
if err := valueNode.Decode(&v); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
tfs.Set(keyNode.Value, v)
|
||||||
}
|
}
|
||||||
tfs.Set(keyNode.Value, v)
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfiles", node.Line, node.ShortTag())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the length of the map
|
// Len returns the length of the map
|
||||||
@@ -92,33 +93,40 @@ func (tfs *IncludedTaskfiles) Range(yield func(key string, includedTaskfile Incl
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
func (it *IncludedTaskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||||
func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
switch node.Kind {
|
||||||
var str string
|
|
||||||
if err := unmarshal(&str); err == nil {
|
case yaml.ScalarNode:
|
||||||
|
var str string
|
||||||
|
if err := node.Decode(&str); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
it.Taskfile = str
|
it.Taskfile = str
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case yaml.MappingNode:
|
||||||
|
var includedTaskfile struct {
|
||||||
|
Taskfile string
|
||||||
|
Dir string
|
||||||
|
Optional bool
|
||||||
|
Internal bool
|
||||||
|
Aliases []string
|
||||||
|
Vars *Vars
|
||||||
|
}
|
||||||
|
if err := node.Decode(&includedTaskfile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
it.Taskfile = includedTaskfile.Taskfile
|
||||||
|
it.Dir = includedTaskfile.Dir
|
||||||
|
it.Optional = includedTaskfile.Optional
|
||||||
|
it.Internal = includedTaskfile.Internal
|
||||||
|
it.Aliases = includedTaskfile.Aliases
|
||||||
|
it.AdvancedImport = true
|
||||||
|
it.Vars = includedTaskfile.Vars
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var includedTaskfile struct {
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfile", node.Line, node.ShortTag())
|
||||||
Taskfile string
|
|
||||||
Dir string
|
|
||||||
Optional bool
|
|
||||||
Internal bool
|
|
||||||
Aliases []string
|
|
||||||
Vars *Vars
|
|
||||||
}
|
|
||||||
if err := unmarshal(&includedTaskfile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
it.Taskfile = includedTaskfile.Taskfile
|
|
||||||
it.Dir = includedTaskfile.Dir
|
|
||||||
it.Optional = includedTaskfile.Optional
|
|
||||||
it.Internal = includedTaskfile.Internal
|
|
||||||
it.Aliases = includedTaskfile.Aliases
|
|
||||||
it.AdvancedImport = true
|
|
||||||
it.Vars = includedTaskfile.Vars
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy creates a new instance of IncludedTaskfile and copies
|
// DeepCopy creates a new instance of IncludedTaskfile and copies
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func Merge(t1, t2 *Taskfile, includedTaskfile *IncludedTaskfile, namespaces ...s
|
|||||||
|
|
||||||
// Set the task to internal if EITHER the included task or the included
|
// Set the task to internal if EITHER the included task or the included
|
||||||
// taskfile are marked as internal
|
// taskfile are marked as internal
|
||||||
task.Internal = task.Internal || includedTaskfile.Internal
|
task.Internal = task.Internal || (includedTaskfile != nil && includedTaskfile.Internal)
|
||||||
|
|
||||||
// Add namespaces to dependencies, commands and aliases
|
// Add namespaces to dependencies, commands and aliases
|
||||||
for _, dep := range task.Deps {
|
for _, dep := range task.Deps {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package taskfile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Output of the Task output
|
// Output of the Task output
|
||||||
@@ -17,28 +19,35 @@ func (s *Output) IsSet() bool {
|
|||||||
return s.Name != ""
|
return s.Name != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler
|
func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
||||||
// It accepts a scalar node representing the Output.Name or a mapping node representing the OutputGroup.
|
switch node.Kind {
|
||||||
func (s *Output) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var name string
|
case yaml.ScalarNode:
|
||||||
if err := unmarshal(&name); err == nil {
|
var name string
|
||||||
|
if err := node.Decode(&name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
s.Name = name
|
s.Name = name
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case yaml.MappingNode:
|
||||||
|
var tmp struct {
|
||||||
|
Group *OutputGroup
|
||||||
|
}
|
||||||
|
if err := node.Decode(&tmp); err != nil {
|
||||||
|
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
|
||||||
|
}
|
||||||
|
if tmp.Group == nil {
|
||||||
|
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
|
||||||
|
}
|
||||||
|
*s = Output{
|
||||||
|
Name: "group",
|
||||||
|
Group: *tmp.Group,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
var tmp struct {
|
|
||||||
Group *OutputGroup
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into output", node.Line, node.ShortTag())
|
||||||
}
|
|
||||||
if err := unmarshal(&tmp); err != nil {
|
|
||||||
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
|
|
||||||
}
|
|
||||||
if tmp.Group == nil {
|
|
||||||
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
|
|
||||||
}
|
|
||||||
*s = Output{
|
|
||||||
Name: "group",
|
|
||||||
Group: *tmp.Group,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutputGroup is the style options specific to the Group style.
|
// OutputGroup is the style options specific to the Group style.
|
||||||
|
|||||||
90
taskfile/platforms.go
Normal file
90
taskfile/platforms.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/goext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Platform represents GOOS and GOARCH values
|
||||||
|
type Platform struct {
|
||||||
|
OS string
|
||||||
|
Arch string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrInvalidPlatform struct {
|
||||||
|
Platform string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *ErrInvalidPlatform) Error() string {
|
||||||
|
return fmt.Sprintf(`task: Invalid platform "%s"`, err.Platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||||
|
func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
switch node.Kind {
|
||||||
|
case yaml.ScalarNode:
|
||||||
|
var platform string
|
||||||
|
if err := node.Decode(&platform); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.parsePlatform(platform); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into platform", node.Line, node.ShortTag())
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePlatform takes a string representing an OS/Arch combination (or either on their own)
|
||||||
|
// and parses it into the Platform struct. It returns an error if the input string is invalid.
|
||||||
|
// Valid combinations for input: OS, Arch, OS/Arch
|
||||||
|
func (p *Platform) parsePlatform(input string) error {
|
||||||
|
splitValues := strings.Split(input, "/")
|
||||||
|
if len(splitValues) > 2 {
|
||||||
|
return &ErrInvalidPlatform{Platform: input}
|
||||||
|
}
|
||||||
|
if err := p.parseOsOrArch(splitValues[0]); err != nil {
|
||||||
|
return &ErrInvalidPlatform{Platform: input}
|
||||||
|
}
|
||||||
|
if len(splitValues) == 2 {
|
||||||
|
if err := p.parseArch(splitValues[1]); err != nil {
|
||||||
|
return &ErrInvalidPlatform{Platform: input}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOsOrArch will check if the given input is a valid OS or Arch value.
|
||||||
|
// If so, it will store it. If not, an error is returned
|
||||||
|
func (p *Platform) parseOsOrArch(osOrArch string) error {
|
||||||
|
if osOrArch == "" {
|
||||||
|
return fmt.Errorf("task: Blank OS/Arch value provided")
|
||||||
|
}
|
||||||
|
if goext.IsKnownOS(osOrArch) {
|
||||||
|
p.OS = osOrArch
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if goext.IsKnownArch(osOrArch) {
|
||||||
|
p.Arch = osOrArch
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("task: Invalid OS/Arch value provided (%s)", osOrArch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Platform) parseArch(arch string) error {
|
||||||
|
if arch == "" {
|
||||||
|
return fmt.Errorf("task: Blank Arch value provided")
|
||||||
|
}
|
||||||
|
if p.Arch != "" {
|
||||||
|
return fmt.Errorf("task: Multiple Arch values provided")
|
||||||
|
}
|
||||||
|
if goext.IsKnownArch(arch) {
|
||||||
|
p.Arch = arch
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("task: Invalid Arch value provided (%s)", arch)
|
||||||
|
}
|
||||||
49
taskfile/platforms_test.go
Normal file
49
taskfile/platforms_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlatformParsing(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Input string
|
||||||
|
ExpectedOS string
|
||||||
|
ExpectedArch string
|
||||||
|
Error string
|
||||||
|
}{
|
||||||
|
{Input: "windows", ExpectedOS: "windows", ExpectedArch: ""},
|
||||||
|
{Input: "linux", ExpectedOS: "linux", ExpectedArch: ""},
|
||||||
|
{Input: "darwin", ExpectedOS: "darwin", ExpectedArch: ""},
|
||||||
|
|
||||||
|
{Input: "386", ExpectedOS: "", ExpectedArch: "386"},
|
||||||
|
{Input: "amd64", ExpectedOS: "", ExpectedArch: "amd64"},
|
||||||
|
{Input: "arm64", ExpectedOS: "", ExpectedArch: "arm64"},
|
||||||
|
|
||||||
|
{Input: "windows/386", ExpectedOS: "windows", ExpectedArch: "386"},
|
||||||
|
{Input: "windows/amd64", ExpectedOS: "windows", ExpectedArch: "amd64"},
|
||||||
|
{Input: "windows/arm64", ExpectedOS: "windows", ExpectedArch: "arm64"},
|
||||||
|
|
||||||
|
{Input: "invalid", Error: `task: Invalid platform "invalid"`},
|
||||||
|
{Input: "invalid/invalid", Error: `task: Invalid platform "invalid/invalid"`},
|
||||||
|
{Input: "windows/invalid", Error: `task: Invalid platform "windows/invalid"`},
|
||||||
|
{Input: "invalid/amd64", Error: `task: Invalid platform "invalid/amd64"`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Input, func(t *testing.T) {
|
||||||
|
var p Platform
|
||||||
|
err := p.parsePlatform(test.Input)
|
||||||
|
|
||||||
|
if test.Error != "" {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, test.Error, err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, test.ExpectedOS, p.OS)
|
||||||
|
assert.Equal(t, test.ExpectedArch, p.Arch)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package taskfile
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -17,29 +19,33 @@ type Precondition struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||||
func (p *Precondition) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
|
||||||
var cmd string
|
switch node.Kind {
|
||||||
|
|
||||||
if err := unmarshal(&cmd); err == nil {
|
case yaml.ScalarNode:
|
||||||
|
var cmd string
|
||||||
|
if err := node.Decode(&cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
p.Sh = cmd
|
p.Sh = cmd
|
||||||
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case yaml.MappingNode:
|
||||||
|
var sh struct {
|
||||||
|
Sh string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
if err := node.Decode(&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
|
||||||
}
|
}
|
||||||
|
|
||||||
var sh struct {
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into precondition", node.Line, node.ShortTag())
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ func readTaskfile(file string) (*taskfile.Taskfile, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
var t taskfile.Taskfile
|
var t taskfile.Taskfile
|
||||||
if err := yaml.NewDecoder(f).Decode(&t); err != nil {
|
if err := yaml.NewDecoder(f).Decode(&t); err != nil {
|
||||||
return nil, fmt.Errorf("task: Failed to parse %s:\n%w", filepathext.TryAbsToRel(file), err)
|
return nil, fmt.Errorf("task: Failed to parse %s:\n%w", filepathext.TryAbsToRel(file), err)
|
||||||
|
|||||||
139
taskfile/task.go
139
taskfile/task.go
@@ -1,5 +1,11 @@
|
|||||||
package taskfile
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
// Tasks represents a group of tasks
|
// Tasks represents a group of tasks
|
||||||
type Tasks map[string]*Task
|
type Tasks map[string]*Task
|
||||||
|
|
||||||
@@ -17,6 +23,8 @@ type Task struct {
|
|||||||
Status []string
|
Status []string
|
||||||
Preconditions []*Precondition
|
Preconditions []*Precondition
|
||||||
Dir string
|
Dir string
|
||||||
|
Set []string
|
||||||
|
Shopt []string
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
Env *Vars
|
Env *Vars
|
||||||
Dotenv []string
|
Dotenv []string
|
||||||
@@ -30,6 +38,7 @@ type Task struct {
|
|||||||
IncludeVars *Vars
|
IncludeVars *Vars
|
||||||
IncludedTaskfileVars *Vars
|
IncludedTaskfileVars *Vars
|
||||||
IncludedTaskfile *IncludedTaskfile
|
IncludedTaskfile *IncludedTaskfile
|
||||||
|
Platforms []*Platform
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) Name() string {
|
func (t *Task) Name() string {
|
||||||
@@ -39,67 +48,86 @@ func (t *Task) Name() string {
|
|||||||
return t.Task
|
return t.Task
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||||
var cmd Cmd
|
switch node.Kind {
|
||||||
if err := unmarshal(&cmd); err == nil && cmd.Cmd != "" {
|
|
||||||
|
// Shortcut syntax for a task with a single command
|
||||||
|
case yaml.ScalarNode:
|
||||||
|
var cmd Cmd
|
||||||
|
if err := node.Decode(&cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
t.Cmds = append(t.Cmds, &cmd)
|
t.Cmds = append(t.Cmds, &cmd)
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
var cmds []*Cmd
|
// Shortcut syntax for a simple task with a list of commands
|
||||||
if err := unmarshal(&cmds); err == nil && len(cmds) > 0 {
|
case yaml.SequenceNode:
|
||||||
|
var cmds []*Cmd
|
||||||
|
if err := node.Decode(&cmds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
t.Cmds = cmds
|
t.Cmds = cmds
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
// Full task object
|
||||||
|
case yaml.MappingNode:
|
||||||
|
var task struct {
|
||||||
|
Cmds []*Cmd
|
||||||
|
Deps []*Dep
|
||||||
|
Label string
|
||||||
|
Desc string
|
||||||
|
Summary string
|
||||||
|
Aliases []string
|
||||||
|
Sources []string
|
||||||
|
Generates []string
|
||||||
|
Status []string
|
||||||
|
Preconditions []*Precondition
|
||||||
|
Dir string
|
||||||
|
Set []string
|
||||||
|
Shopt []string
|
||||||
|
Vars *Vars
|
||||||
|
Env *Vars
|
||||||
|
Dotenv []string
|
||||||
|
Silent bool
|
||||||
|
Interactive bool
|
||||||
|
Internal bool
|
||||||
|
Method string
|
||||||
|
Prefix string
|
||||||
|
IgnoreError bool `yaml:"ignore_error"`
|
||||||
|
Run string
|
||||||
|
Platforms []*Platform
|
||||||
|
}
|
||||||
|
if err := node.Decode(&task); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Cmds = task.Cmds
|
||||||
|
t.Deps = task.Deps
|
||||||
|
t.Label = task.Label
|
||||||
|
t.Desc = task.Desc
|
||||||
|
t.Summary = task.Summary
|
||||||
|
t.Aliases = task.Aliases
|
||||||
|
t.Sources = task.Sources
|
||||||
|
t.Generates = task.Generates
|
||||||
|
t.Status = task.Status
|
||||||
|
t.Preconditions = task.Preconditions
|
||||||
|
t.Dir = task.Dir
|
||||||
|
t.Set = task.Set
|
||||||
|
t.Shopt = task.Shopt
|
||||||
|
t.Vars = task.Vars
|
||||||
|
t.Env = task.Env
|
||||||
|
t.Dotenv = task.Dotenv
|
||||||
|
t.Silent = task.Silent
|
||||||
|
t.Interactive = task.Interactive
|
||||||
|
t.Internal = task.Internal
|
||||||
|
t.Method = task.Method
|
||||||
|
t.Prefix = task.Prefix
|
||||||
|
t.IgnoreError = task.IgnoreError
|
||||||
|
t.Run = task.Run
|
||||||
|
t.Platforms = task.Platforms
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var task struct {
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
|
||||||
Cmds []*Cmd
|
|
||||||
Deps []*Dep
|
|
||||||
Label string
|
|
||||||
Desc string
|
|
||||||
Summary string
|
|
||||||
Aliases []string
|
|
||||||
Sources []string
|
|
||||||
Generates []string
|
|
||||||
Status []string
|
|
||||||
Preconditions []*Precondition
|
|
||||||
Dir string
|
|
||||||
Vars *Vars
|
|
||||||
Env *Vars
|
|
||||||
Dotenv []string
|
|
||||||
Silent bool
|
|
||||||
Interactive bool
|
|
||||||
Internal bool
|
|
||||||
Method string
|
|
||||||
Prefix string
|
|
||||||
IgnoreError bool `yaml:"ignore_error"`
|
|
||||||
Run string
|
|
||||||
}
|
|
||||||
if err := unmarshal(&task); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.Cmds = task.Cmds
|
|
||||||
t.Deps = task.Deps
|
|
||||||
t.Label = task.Label
|
|
||||||
t.Desc = task.Desc
|
|
||||||
t.Aliases = task.Aliases
|
|
||||||
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.Dotenv = task.Dotenv
|
|
||||||
t.Silent = task.Silent
|
|
||||||
t.Interactive = task.Interactive
|
|
||||||
t.Internal = task.Internal
|
|
||||||
t.Method = task.Method
|
|
||||||
t.Prefix = task.Prefix
|
|
||||||
t.IgnoreError = task.IgnoreError
|
|
||||||
t.Run = task.Run
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy creates a new instance of Task and copies
|
// DeepCopy creates a new instance of Task and copies
|
||||||
@@ -118,6 +146,8 @@ func (t *Task) DeepCopy() *Task {
|
|||||||
Status: deepCopySlice(t.Status),
|
Status: deepCopySlice(t.Status),
|
||||||
Preconditions: deepCopySlice(t.Preconditions),
|
Preconditions: deepCopySlice(t.Preconditions),
|
||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
|
Set: deepCopySlice(t.Set),
|
||||||
|
Shopt: deepCopySlice(t.Shopt),
|
||||||
Vars: t.Vars.DeepCopy(),
|
Vars: t.Vars.DeepCopy(),
|
||||||
Env: t.Env.DeepCopy(),
|
Env: t.Env.DeepCopy(),
|
||||||
Dotenv: deepCopySlice(t.Dotenv),
|
Dotenv: deepCopySlice(t.Dotenv),
|
||||||
@@ -131,6 +161,7 @@ func (t *Task) DeepCopy() *Task {
|
|||||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||||
IncludedTaskfile: t.IncludedTaskfile.DeepCopy(),
|
IncludedTaskfile: t.IncludedTaskfile.DeepCopy(),
|
||||||
|
Platforms: deepCopySlice(t.Platforms),
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package taskfile
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Taskfile represents a Taskfile.yml
|
// Taskfile represents a Taskfile.yml
|
||||||
@@ -12,59 +15,67 @@ type Taskfile struct {
|
|||||||
Output Output
|
Output Output
|
||||||
Method string
|
Method string
|
||||||
Includes *IncludedTaskfiles
|
Includes *IncludedTaskfiles
|
||||||
|
Set []string
|
||||||
|
Shopt []string
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
Env *Vars
|
Env *Vars
|
||||||
Tasks Tasks
|
Tasks Tasks
|
||||||
Silent bool
|
Silent bool
|
||||||
Dotenv []string
|
Dotenv []string
|
||||||
Run string
|
Run string
|
||||||
Interval string
|
Interval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||||
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
switch node.Kind {
|
||||||
var taskfile struct {
|
|
||||||
Version string
|
case yaml.MappingNode:
|
||||||
Expansions int
|
var taskfile struct {
|
||||||
Output Output
|
Version string
|
||||||
Method string
|
Expansions int
|
||||||
Includes *IncludedTaskfiles
|
Output Output
|
||||||
Vars *Vars
|
Method string
|
||||||
Env *Vars
|
Includes *IncludedTaskfiles
|
||||||
Tasks Tasks
|
Set []string
|
||||||
Silent bool
|
Shopt []string
|
||||||
Dotenv []string
|
Vars *Vars
|
||||||
Run string
|
Env *Vars
|
||||||
Interval string
|
Tasks Tasks
|
||||||
|
Silent bool
|
||||||
|
Dotenv []string
|
||||||
|
Run string
|
||||||
|
Interval time.Duration
|
||||||
|
}
|
||||||
|
if err := node.Decode(&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.Set = taskfile.Set
|
||||||
|
tf.Shopt = taskfile.Shopt
|
||||||
|
tf.Vars = taskfile.Vars
|
||||||
|
tf.Env = taskfile.Env
|
||||||
|
tf.Tasks = taskfile.Tasks
|
||||||
|
tf.Silent = taskfile.Silent
|
||||||
|
tf.Dotenv = taskfile.Dotenv
|
||||||
|
tf.Run = taskfile.Run
|
||||||
|
tf.Interval = taskfile.Interval
|
||||||
|
if tf.Expansions <= 0 {
|
||||||
|
tf.Expansions = 2
|
||||||
|
}
|
||||||
|
if tf.Vars == nil {
|
||||||
|
tf.Vars = &Vars{}
|
||||||
|
}
|
||||||
|
if tf.Env == nil {
|
||||||
|
tf.Env = &Vars{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := unmarshal(&taskfile); err != nil {
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tf.Version = taskfile.Version
|
|
||||||
tf.Expansions = taskfile.Expansions
|
|
||||||
tf.Output = taskfile.Output
|
|
||||||
tf.Method = taskfile.Method
|
|
||||||
tf.Includes = taskfile.Includes
|
|
||||||
tf.Vars = taskfile.Vars
|
|
||||||
tf.Env = taskfile.Env
|
|
||||||
tf.Tasks = taskfile.Tasks
|
|
||||||
tf.Silent = taskfile.Silent
|
|
||||||
tf.Dotenv = taskfile.Dotenv
|
|
||||||
tf.Run = taskfile.Run
|
|
||||||
tf.Interval = taskfile.Interval
|
|
||||||
|
|
||||||
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
|
// ParsedVersion returns the version as a float64
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package taskfile
|
package taskfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@@ -13,26 +13,27 @@ type Vars struct {
|
|||||||
Mapping map[string]Var
|
Mapping map[string]Var
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
||||||
func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
|
func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
|
||||||
if node.Kind != yaml.MappingNode {
|
switch node.Kind {
|
||||||
return errors.New("task: vars is not a map")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(@andreynering): on this style of custom unmarsheling,
|
case yaml.MappingNode:
|
||||||
// even number contains the keys, while odd numbers contains
|
// NOTE(@andreynering): on this style of custom unmarshalling,
|
||||||
// the values.
|
// even number contains the keys, while odd numbers contains
|
||||||
for i := 0; i < len(node.Content); i += 2 {
|
// the values.
|
||||||
keyNode := node.Content[i]
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
valueNode := node.Content[i+1]
|
keyNode := node.Content[i]
|
||||||
|
valueNode := node.Content[i+1]
|
||||||
|
|
||||||
var v Var
|
var v Var
|
||||||
if err := valueNode.Decode(&v); err != nil {
|
if err := valueNode.Decode(&v); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
vs.Set(keyNode.Value, v)
|
||||||
}
|
}
|
||||||
vs.Set(keyNode.Value, v)
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variables", node.Line, node.ShortTag())
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy creates a new instance of Vars and copies
|
// DeepCopy creates a new instance of Vars and copies
|
||||||
@@ -116,20 +117,27 @@ type Var struct {
|
|||||||
Dir string
|
Dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||||
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
switch node.Kind {
|
||||||
var str string
|
|
||||||
if err := unmarshal(&str); err == nil {
|
case yaml.ScalarNode:
|
||||||
|
var str string
|
||||||
|
if err := node.Decode(&str); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
v.Static = str
|
v.Static = str
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case yaml.MappingNode:
|
||||||
|
var sh struct {
|
||||||
|
Sh string
|
||||||
|
}
|
||||||
|
if err := node.Decode(&sh); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Sh = sh.Sh
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var sh struct {
|
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variable", node.Line, node.ShortTag())
|
||||||
Sh string
|
|
||||||
}
|
|
||||||
if err := unmarshal(&sh); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Sh = sh.Sh
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
55
testdata/platforms/Taskfile.yml
vendored
Normal file
55
testdata/platforms/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build-windows:
|
||||||
|
platforms: [windows]
|
||||||
|
cmds:
|
||||||
|
- echo 'Running task on windows'
|
||||||
|
|
||||||
|
build-darwin:
|
||||||
|
platforms: [darwin]
|
||||||
|
cmds:
|
||||||
|
- echo 'Running task on darwin'
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
platforms: [linux]
|
||||||
|
cmds:
|
||||||
|
- echo 'Running task on linux'
|
||||||
|
|
||||||
|
build-freebsd:
|
||||||
|
platforms: [freebsd]
|
||||||
|
cmds:
|
||||||
|
- echo 'Running task on freebsd'
|
||||||
|
|
||||||
|
build-blank-os:
|
||||||
|
platforms: []
|
||||||
|
cmds:
|
||||||
|
- echo 'Running command'
|
||||||
|
|
||||||
|
build-multiple:
|
||||||
|
platforms: []
|
||||||
|
cmds:
|
||||||
|
- cmd: echo 'Running command'
|
||||||
|
- cmd: echo 'Running on Windows'
|
||||||
|
platforms: [windows]
|
||||||
|
- cmd: echo 'Running on Darwin'
|
||||||
|
platforms: [darwin]
|
||||||
|
|
||||||
|
build-amd64:
|
||||||
|
platforms: [amd64]
|
||||||
|
cmds:
|
||||||
|
- echo "Running command on amd64"
|
||||||
|
|
||||||
|
build-arm64:
|
||||||
|
platforms: [arm64]
|
||||||
|
cmds:
|
||||||
|
- echo "Running command on arm64"
|
||||||
|
|
||||||
|
build-mixed:
|
||||||
|
cmds:
|
||||||
|
- cmd: echo 'building on windows/arm64'
|
||||||
|
platforms: [windows/arm64]
|
||||||
|
- cmd: echo 'building on linux/amd64'
|
||||||
|
platforms: [linux/amd64]
|
||||||
|
- cmd: echo 'building on darwin'
|
||||||
|
platforms: [darwin]
|
||||||
14
testdata/shopts/command_level/Taskfile.yml
vendored
Normal file
14
testdata/shopts/command_level/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
silent: true
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
pipefail:
|
||||||
|
cmds:
|
||||||
|
- cmd: set -o | grep pipefail
|
||||||
|
set: [pipefail]
|
||||||
|
|
||||||
|
globstar:
|
||||||
|
cmds:
|
||||||
|
- cmd: shopt | grep globstar
|
||||||
|
shopt: [globstar]
|
||||||
14
testdata/shopts/global_level/Taskfile.yml
vendored
Normal file
14
testdata/shopts/global_level/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
silent: true
|
||||||
|
set: [pipefail]
|
||||||
|
shopt: [globstar]
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
pipefail:
|
||||||
|
cmds:
|
||||||
|
- set -o | grep pipefail
|
||||||
|
|
||||||
|
globstar:
|
||||||
|
cmds:
|
||||||
|
- shopt | grep globstar
|
||||||
14
testdata/shopts/task_level/Taskfile.yml
vendored
Normal file
14
testdata/shopts/task_level/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
silent: true
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
pipefail:
|
||||||
|
set: [pipefail]
|
||||||
|
cmds:
|
||||||
|
- set -o | grep pipefail
|
||||||
|
|
||||||
|
globstar:
|
||||||
|
shopt: [globstar]
|
||||||
|
cmds:
|
||||||
|
- shopt | grep globstar
|
||||||
10
variables.go
10
variables.go
@@ -56,6 +56,8 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
|||||||
Sources: r.ReplaceSlice(origTask.Sources),
|
Sources: r.ReplaceSlice(origTask.Sources),
|
||||||
Generates: r.ReplaceSlice(origTask.Generates),
|
Generates: r.ReplaceSlice(origTask.Generates),
|
||||||
Dir: r.Replace(origTask.Dir),
|
Dir: r.Replace(origTask.Dir),
|
||||||
|
Set: origTask.Set,
|
||||||
|
Shopt: origTask.Shopt,
|
||||||
Vars: nil,
|
Vars: nil,
|
||||||
Env: nil,
|
Env: nil,
|
||||||
Dotenv: r.ReplaceSlice(origTask.Dotenv),
|
Dotenv: r.ReplaceSlice(origTask.Dotenv),
|
||||||
@@ -68,6 +70,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
|||||||
Run: r.Replace(origTask.Run),
|
Run: r.Replace(origTask.Run),
|
||||||
IncludeVars: origTask.IncludeVars,
|
IncludeVars: origTask.IncludeVars,
|
||||||
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
||||||
|
Platforms: origTask.Platforms,
|
||||||
}
|
}
|
||||||
new.Dir, err = execext.Expand(new.Dir)
|
new.Dir, err = execext.Expand(new.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,12 +127,15 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
new.Cmds = append(new.Cmds, &taskfile.Cmd{
|
new.Cmds = append(new.Cmds, &taskfile.Cmd{
|
||||||
Task: r.Replace(cmd.Task),
|
|
||||||
Silent: cmd.Silent,
|
|
||||||
Cmd: r.Replace(cmd.Cmd),
|
Cmd: r.Replace(cmd.Cmd),
|
||||||
|
Silent: cmd.Silent,
|
||||||
|
Task: r.Replace(cmd.Task),
|
||||||
|
Set: cmd.Set,
|
||||||
|
Shopt: cmd.Shopt,
|
||||||
Vars: r.ReplaceVars(cmd.Vars),
|
Vars: r.ReplaceVars(cmd.Vars),
|
||||||
IgnoreError: cmd.IgnoreError,
|
IgnoreError: cmd.IgnoreError,
|
||||||
Defer: cmd.Defer,
|
Defer: cmd.Defer,
|
||||||
|
Platforms: cmd.Platforms,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
watch.go
36
watch.go
@@ -10,10 +10,11 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/radovskyb/watcher"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/status"
|
"github.com/go-task/task/v3/internal/status"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
"github.com/radovskyb/watcher"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultWatchInterval = 5 * time.Second
|
const defaultWatchInterval = 5 * time.Second
|
||||||
@@ -37,23 +38,14 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
var watchIntervalString string
|
var watchInterval time.Duration
|
||||||
|
switch {
|
||||||
if e.Interval != "" {
|
case e.Interval != 0:
|
||||||
watchIntervalString = e.Interval
|
watchInterval = e.Interval
|
||||||
} else if e.Taskfile.Interval != "" {
|
case e.Taskfile.Interval != 0:
|
||||||
watchIntervalString = e.Taskfile.Interval
|
watchInterval = e.Taskfile.Interval
|
||||||
}
|
default:
|
||||||
|
watchInterval = defaultWatchInterval
|
||||||
watchInterval := defaultWatchInterval
|
|
||||||
|
|
||||||
if watchIntervalString != "" {
|
|
||||||
var err error
|
|
||||||
watchInterval, err = parseWatchInterval(watchIntervalString)
|
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Logger.VerboseOutf(logger.Green, "task: Watching for changes every %v", watchInterval)
|
e.Logger.VerboseOutf(logger.Green, "task: Watching for changes every %v", watchInterval)
|
||||||
@@ -185,11 +177,3 @@ func (e *Executor) registerWatchedFiles(w *watcher.Watcher, calls ...taskfile.Ca
|
|||||||
func shouldIgnoreFile(path string) bool {
|
func shouldIgnoreFile(path string) bool {
|
||||||
return strings.Contains(path, "/.git") || strings.Contains(path, "/.task") || strings.Contains(path, "/node_modules")
|
return strings.Contains(path, "/.git") || strings.Contains(path, "/.task") || strings.Contains(path, "/node_modules")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseWatchInterval(watchInterval string) (time.Duration, error) {
|
|
||||||
v, err := time.ParseDuration(watchInterval)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf(`task: Could not parse watch interval "%s": %v`, watchInterval, err)
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|||||||
80
watch_test.go
Normal file
80
watch_test.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
//go:build watch
|
||||||
|
// +build watch
|
||||||
|
|
||||||
|
package task_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3"
|
||||||
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
|
"github.com/go-task/task/v3/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileWatcherInterval(t *testing.T) {
|
||||||
|
const dir = "testdata/watcher_interval"
|
||||||
|
expectedOutput := strings.TrimSpace(`
|
||||||
|
task: Started watching for tasks: default
|
||||||
|
task: [default] echo "Hello, World!"
|
||||||
|
Hello, World!
|
||||||
|
task: [default] echo "Hello, World!"
|
||||||
|
Hello, World!
|
||||||
|
`)
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
Watch: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepathext.SmartJoin(dir, "src"), 0755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = os.WriteFile(filepathext.SmartJoin(dir, "src/a"), []byte("test"), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
go func(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
err := e.Run(ctx, taskfile.Call{Task: "default"})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(ctx)
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
err = os.WriteFile(filepathext.SmartJoin(dir, "src/a"), []byte("test updated"), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
time.Sleep(700 * time.Millisecond)
|
||||||
|
cancel()
|
||||||
|
assert.Equal(t, expectedOutput, strings.TrimSpace(buff.String()))
|
||||||
|
buff.Reset()
|
||||||
|
err = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = os.RemoveAll(filepathext.SmartJoin(dir, "src"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user