mirror of
https://github.com/go-task/task.git
synced 2026-06-11 09:51:50 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73aba36309 | ||
|
|
cb393ccd3a | ||
|
|
347fcf9f67 | ||
|
|
fce7575b03 | ||
|
|
2da7ddc399 | ||
|
|
1c1be683ab | ||
|
|
4be1050234 | ||
|
|
2efb3533ec | ||
|
|
aa6c7e4b94 | ||
|
|
63c50d13ee | ||
|
|
c1e127e42f | ||
|
|
9e38e8a4db |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
||||
# 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
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</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>
|
||||
</div>
|
||||
|
||||
|
||||
19
Taskfile.yml
19
Taskfile.yml
@@ -6,13 +6,6 @@ includes:
|
||||
taskfile: ./docs
|
||||
dir: ./docs
|
||||
|
||||
vars:
|
||||
GIT_COMMIT:
|
||||
sh: git log -n 1 --format=%h
|
||||
|
||||
GO_PACKAGES:
|
||||
sh: go list ./...
|
||||
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
|
||||
@@ -29,6 +22,9 @@ tasks:
|
||||
- './**/*.go'
|
||||
cmds:
|
||||
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
|
||||
vars:
|
||||
GIT_COMMIT:
|
||||
sh: git log -n 1 --format=%h
|
||||
|
||||
mod:
|
||||
desc: Downloads and tidy Go modules
|
||||
@@ -73,12 +69,18 @@ tasks:
|
||||
deps: [install]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}}
|
||||
vars:
|
||||
GO_PACKAGES:
|
||||
sh: go list ./...
|
||||
|
||||
test:all:
|
||||
desc: Runs test suite with signals and watch tests included
|
||||
deps: [install, sleepit:build]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}} -tags 'signals watch'
|
||||
vars:
|
||||
GO_PACKAGES:
|
||||
sh: go list ./...
|
||||
|
||||
test-release:
|
||||
desc: Tests release process without publishing
|
||||
@@ -106,4 +108,7 @@ tasks:
|
||||
packages:
|
||||
cmds:
|
||||
- echo '{{.GO_PACKAGES}}'
|
||||
vars:
|
||||
GO_PACKAGES:
|
||||
sh: go list ./...
|
||||
silent: true
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
const GITHUB_URL = 'https://github.com/go-task/task';
|
||||
const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
||||
const MASTODON_URL = 'https://fosstodon.org/@task';
|
||||
const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
||||
const CHINESE_URL = 'https://task-zh.readthedocs.io/zh_CN/latest/';
|
||||
|
||||
module.exports = {
|
||||
GITHUB_URL,
|
||||
TWITTER_URL,
|
||||
CHINESE_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. |
|
||||
| `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). |
|
||||
| `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
|
||||
|
||||
@@ -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`. |
|
||||
| `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`. |
|
||||
| `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
|
||||
|
||||
@@ -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`. |
|
||||
| `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`. |
|
||||
| `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
|
||||
|
||||
|
||||
@@ -5,6 +5,23 @@ sidebar_position: 7
|
||||
|
||||
# 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
|
||||
|
||||
@@ -41,7 +41,7 @@ the [Snapcraft dashboard][snapcraftdashboard].
|
||||
|
||||
Scoop is a command-line package manager for the Windows operating system.
|
||||
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.
|
||||
|
||||
# Nix
|
||||
|
||||
@@ -439,6 +439,78 @@ tasks:
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
For the `checksum` (default) method to work, it is only necessary to
|
||||
inform the source files, but if you want to use the `timestamp` method, you
|
||||
also need to inform the generated files with `generates`.
|
||||
For the `checksum` (default) or `timestamp` method to work, it is only necessary to
|
||||
inform the source files.
|
||||
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
|
||||
```
|
||||
|
||||
## `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
|
||||
|
||||
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
|
||||
|
||||
const {
|
||||
GITHUB_URL,
|
||||
TWITTER_URL,
|
||||
CHINESE_URL,
|
||||
DISCORD_URL,
|
||||
CHINESE_URL
|
||||
GITHUB_URL,
|
||||
MASTODON_URL,
|
||||
TWITTER_URL
|
||||
} = require('./constants');
|
||||
const lightCodeTheme = require('./src/themes/prismLight');
|
||||
const darkCodeTheme = require('./src/themes/prismDark');
|
||||
@@ -108,6 +109,11 @@ const config = {
|
||||
label: 'Twitter',
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
href: MASTODON_URL,
|
||||
label: 'Mastodon',
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
href: DISCORD_URL,
|
||||
label: 'Discord',
|
||||
@@ -146,6 +152,10 @@ const config = {
|
||||
label: 'Twitter',
|
||||
href: TWITTER_URL
|
||||
},
|
||||
{
|
||||
label: 'Mastodon',
|
||||
href: MASTODON_URL
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
href: DISCORD_URL
|
||||
|
||||
64
docs/static/schema.json
vendored
64
docs/static/schema.json
vendored
@@ -108,6 +108,20 @@
|
||||
"description": "The directory in which this task should run. Defaults to the current working directory.",
|
||||
"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": {
|
||||
"description": "A set of variables that can be used in the task.",
|
||||
"$ref": "#/definitions/3/vars"
|
||||
@@ -155,6 +169,13 @@
|
||||
"run": {
|
||||
"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"
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
@@ -226,6 +255,20 @@
|
||||
"description": "Silent mode disables echoing of command before Task runs it",
|
||||
"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": {
|
||||
"description": "Prevent command from aborting the execution of task even after receiving a status code of 1",
|
||||
"type": "boolean"
|
||||
@@ -233,6 +276,13 @@
|
||||
"defer": {
|
||||
"description": "",
|
||||
"type": "boolean"
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Specifies which platforms the command should be run on.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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.",
|
||||
"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": {
|
||||
"type": "array",
|
||||
"description": "A list of `.env` file paths to be parsed.",
|
||||
|
||||
@@ -5877,9 +5877,9 @@ json-schema-traverse@^1.0.0:
|
||||
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
|
||||
|
||||
json5@^2.1.2, json5@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
|
||||
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.2.tgz#64471c5bdcc564c18f7c1d4df2e2297f2457c5ab"
|
||||
integrity sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
|
||||
9
help.go
9
help.go
@@ -51,10 +51,10 @@ func (o ListOptions) Validate() error {
|
||||
// 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()}
|
||||
filters := []FilterFunc{FilterOutInternal}
|
||||
|
||||
if o.ListOnlyTasksWithDescriptions {
|
||||
filters = append(filters, FilterOutNoDesc())
|
||||
filters = append(filters, FilterOutNoDesc)
|
||||
}
|
||||
|
||||
return filters
|
||||
@@ -65,7 +65,10 @@ func (o ListOptions) Filters() []FilterFunc {
|
||||
// The function returns a boolean indicating whether tasks were found
|
||||
// and an error if one was encountered while preparing the output.
|
||||
func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
||||
tasks := e.GetTaskList(o.Filters()...)
|
||||
tasks, err := e.GetTaskList(o.Filters()...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if o.FormatTaskListAsJSON {
|
||||
output, err := e.ToEditorOutput(tasks)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package execext
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -17,12 +18,14 @@ import (
|
||||
|
||||
// RunCommandOptions is the options for the RunCommand func
|
||||
type RunCommandOptions struct {
|
||||
Command string
|
||||
Dir string
|
||||
Env []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Command string
|
||||
Dir string
|
||||
Env []string
|
||||
PosixOpts []string
|
||||
BashOpts []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -36,9 +39,18 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
return ErrNilOptions
|
||||
}
|
||||
|
||||
p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "")
|
||||
if err != nil {
|
||||
return err
|
||||
// Set "-e" or "errexit" by default
|
||||
opts.PosixOpts = append(opts.PosixOpts, "e")
|
||||
|
||||
// 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
|
||||
@@ -47,7 +59,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
}
|
||||
|
||||
r, err := interp.New(
|
||||
interp.Params("-e"),
|
||||
interp.Params(params...),
|
||||
interp.Env(expand.ListEnviron(environ...)),
|
||||
interp.ExecHandler(interp.DefaultExecHandler(15*time.Second)),
|
||||
interp.OpenHandler(openHandler),
|
||||
@@ -58,6 +70,25 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
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": {},
|
||||
}
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
@@ -109,12 +110,12 @@ func (*Checksum) Kind() 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]")
|
||||
|
||||
// replaces invalid caracters on filenames with "-"
|
||||
func (*Checksum) normalizeFilename(f string) string {
|
||||
func normalizeFilename(f string) string {
|
||||
return checksumFilenameRegexp.ReplaceAllString(f, "-")
|
||||
}
|
||||
|
||||
@@ -16,6 +16,6 @@ func TestNormalizeFilename(t *testing.T) {
|
||||
{"foo1bar2baz3", "foo1bar2baz3"},
|
||||
}
|
||||
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 (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timestamp checks if any source change compared with the generated files,
|
||||
// using file modifications timestamps.
|
||||
type Timestamp struct {
|
||||
TempDir string
|
||||
Task string
|
||||
Dir string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Dry bool
|
||||
}
|
||||
|
||||
// IsUpToDate implements the Checker interface
|
||||
func (t *Timestamp) IsUpToDate() (bool, error) {
|
||||
if len(t.Sources) == 0 || len(t.Generates) == 0 {
|
||||
if len(t.Sources) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -28,17 +32,51 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
sourcesMaxTime, err := getMaxTime(sources...)
|
||||
if err != nil || sourcesMaxTime.IsZero() {
|
||||
timestampFile := t.timestampFilePath()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
generatesMinTime, err := getMinTime(generates...)
|
||||
if err != nil || generatesMinTime.IsZero() {
|
||||
// Check if any of the source files is newer than the max time of the generates.
|
||||
shouldUpdate, err := anyFileNewerThan(sources, generateMaxTime)
|
||||
if err != 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 {
|
||||
@@ -64,18 +102,6 @@ func (t *Timestamp) Value() (interface{}, error) {
|
||||
return sourcesMaxTime, nil
|
||||
}
|
||||
|
||||
func getMinTime(files ...string) (time.Time, error) {
|
||||
var t time.Time
|
||||
for _, f := range files {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
t = minTime(t, info.ModTime())
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func getMaxTime(files ...string) (time.Time, error) {
|
||||
var t time.Time
|
||||
for _, f := range files {
|
||||
@@ -88,13 +114,6 @@ func getMaxTime(files ...string) (time.Time, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func minTime(a, b time.Time) time.Time {
|
||||
if !a.IsZero() && a.Before(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func maxTime(a, b time.Time) time.Time {
|
||||
if a.After(b) {
|
||||
return a
|
||||
@@ -102,7 +121,26 @@ func maxTime(a, b time.Time) time.Time {
|
||||
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
|
||||
func (*Timestamp) OnError() error {
|
||||
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",
|
||||
"version": "3.19.1",
|
||||
"version": "3.20.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.19.1",
|
||||
"version": "3.20.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.19.1",
|
||||
"version": "3.20.0",
|
||||
"description": "A task runner / simpler Make alternative written in Go",
|
||||
"scripts": {
|
||||
"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 {
|
||||
return &status.Timestamp{
|
||||
TempDir: e.TempDir,
|
||||
Task: t.Name(),
|
||||
Dir: t.Dir,
|
||||
Sources: t.Sources,
|
||||
Generates: t.Generates,
|
||||
Dry: e.Dry,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
111
task.go
111
task.go
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/slicesext"
|
||||
"github.com/go-task/task/v3/internal/summary"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
@@ -135,6 +137,11 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
defer release()
|
||||
|
||||
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)
|
||||
if err := e.runDeps(ctx, t); err != nil {
|
||||
return err
|
||||
@@ -252,6 +259,11 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
||||
}
|
||||
return nil
|
||||
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) {
|
||||
e.Logger.Errf(logger.Green, "task: [%s] %s", t.Name(), cmd.Cmd)
|
||||
}
|
||||
@@ -272,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)
|
||||
defer func() {
|
||||
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{
|
||||
Command: cmd.Cmd,
|
||||
Dir: t.Dir,
|
||||
Env: getEnviron(t),
|
||||
Stdin: e.Stdin,
|
||||
Stdout: stdOut,
|
||||
Stderr: stdErr,
|
||||
Command: cmd.Cmd,
|
||||
Dir: t.Dir,
|
||||
Env: getEnviron(t),
|
||||
PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set, cmd.Set),
|
||||
BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt, cmd.Shopt),
|
||||
Stdin: e.Stdin,
|
||||
Stdout: stdOut,
|
||||
Stderr: stdErr,
|
||||
})
|
||||
if execext.IsExitError(err) && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v", t.Name(), err)
|
||||
@@ -386,23 +400,39 @@ func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) {
|
||||
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))
|
||||
|
||||
// Create an error group to wait for each task to be compiled
|
||||
var g errgroup.Group
|
||||
|
||||
// Fetch and compile the list of tasks
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
|
||||
if err == nil {
|
||||
task = compiledTask
|
||||
}
|
||||
tasks = append(tasks, task)
|
||||
for key := range e.Taskfile.Tasks {
|
||||
task := e.Taskfile.Tasks[key]
|
||||
g.Go(func() error {
|
||||
|
||||
// Check if we should filter the 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
|
||||
for _, filter := range filters {
|
||||
tasks = filter(tasks)
|
||||
// Wait for all the go routines to finish
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sort the tasks.
|
||||
@@ -420,38 +450,27 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) []*taskfile.Task {
|
||||
return false
|
||||
})
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
||||
// 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])
|
||||
}
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
// FilterOutNoDesc removes all tasks that do not contain a description.
|
||||
func FilterOutNoDesc() FilterFunc {
|
||||
return Filter(func(task *taskfile.Task) bool {
|
||||
return task.Desc == ""
|
||||
})
|
||||
func FilterOutNoDesc(task *taskfile.Task) bool {
|
||||
return task.Desc == ""
|
||||
}
|
||||
|
||||
// FilterOutInternal removes all tasks that are marked as internal.
|
||||
func FilterOutInternal() FilterFunc {
|
||||
return Filter(func(task *taskfile.Task) bool {
|
||||
return task.Internal
|
||||
})
|
||||
func FilterOutInternal(task *taskfile.Task) bool {
|
||||
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
|
||||
}
|
||||
|
||||
96
task_test.go
96
task_test.go
@@ -1696,3 +1696,99 @@ func TestUserWorkingDirectory(t *testing.T) {
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ type Cmd struct {
|
||||
Cmd string
|
||||
Silent bool
|
||||
Task string
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
IgnoreError bool
|
||||
Defer bool
|
||||
Platforms []*Platform
|
||||
}
|
||||
|
||||
// Dep is a task dependency
|
||||
@@ -39,12 +42,18 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ type Task struct {
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
@@ -36,6 +38,7 @@ type Task struct {
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
IncludedTaskfile *IncludedTaskfile
|
||||
Platforms []*Platform
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
@@ -80,6 +83,8 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
@@ -90,6 +95,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Prefix string
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
}
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return err
|
||||
@@ -105,6 +111,8 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
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
|
||||
@@ -115,6 +123,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.Prefix = task.Prefix
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.Run = task.Run
|
||||
t.Platforms = task.Platforms
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -137,6 +146,8 @@ func (t *Task) DeepCopy() *Task {
|
||||
Status: deepCopySlice(t.Status),
|
||||
Preconditions: deepCopySlice(t.Preconditions),
|
||||
Dir: t.Dir,
|
||||
Set: deepCopySlice(t.Set),
|
||||
Shopt: deepCopySlice(t.Shopt),
|
||||
Vars: t.Vars.DeepCopy(),
|
||||
Env: t.Env.DeepCopy(),
|
||||
Dotenv: deepCopySlice(t.Dotenv),
|
||||
@@ -150,6 +161,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||
IncludedTaskfile: t.IncludedTaskfile.DeepCopy(),
|
||||
Platforms: deepCopySlice(t.Platforms),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ type Taskfile struct {
|
||||
Output Output
|
||||
Method string
|
||||
Includes *IncludedTaskfiles
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks Tasks
|
||||
@@ -34,6 +36,8 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
Output Output
|
||||
Method string
|
||||
Includes *IncludedTaskfiles
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks Tasks
|
||||
@@ -50,6 +54,8 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
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
|
||||
|
||||
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),
|
||||
Generates: r.ReplaceSlice(origTask.Generates),
|
||||
Dir: r.Replace(origTask.Dir),
|
||||
Set: origTask.Set,
|
||||
Shopt: origTask.Shopt,
|
||||
Vars: nil,
|
||||
Env: nil,
|
||||
Dotenv: r.ReplaceSlice(origTask.Dotenv),
|
||||
@@ -68,6 +70,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
Run: r.Replace(origTask.Run),
|
||||
IncludeVars: origTask.IncludeVars,
|
||||
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
||||
Platforms: origTask.Platforms,
|
||||
}
|
||||
new.Dir, err = execext.Expand(new.Dir)
|
||||
if err != nil {
|
||||
@@ -124,12 +127,15 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
continue
|
||||
}
|
||||
new.Cmds = append(new.Cmds, &taskfile.Cmd{
|
||||
Task: r.Replace(cmd.Task),
|
||||
Silent: cmd.Silent,
|
||||
Cmd: r.Replace(cmd.Cmd),
|
||||
Silent: cmd.Silent,
|
||||
Task: r.Replace(cmd.Task),
|
||||
Set: cmd.Set,
|
||||
Shopt: cmd.Shopt,
|
||||
Vars: r.ReplaceVars(cmd.Vars),
|
||||
IgnoreError: cmd.IgnoreError,
|
||||
Defer: cmd.Defer,
|
||||
Platforms: cmd.Platforms,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user