Compare commits

...

45 Commits

Author SHA1 Message Date
Pete Davison
1275ab1b5b v3.39.0 2024-09-07 20:05:46 +00:00
dependabot[bot]
0c05dcbe0f chore(deps): bump github.com/mattn/go-zglob from 0.0.5 to 0.0.6 (#1791)
Bumps [github.com/mattn/go-zglob](https://github.com/mattn/go-zglob) from 0.0.5 to 0.0.6.
- [Commits](https://github.com/mattn/go-zglob/compare/v0.0.5...v0.0.6)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-zglob
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-07 21:00:52 +01:00
Pete Davison
e9983e299f chore: changelog for #1713 2024-09-07 20:00:00 +00:00
dependabot[bot]
a450f2daea chore(deps): bump golang.org/x/term from 0.23.0 to 0.24.0 (#1790)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.23.0 to 0.24.0.
- [Commits](https://github.com/golang/term/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-07 20:55:27 +01:00
Valentin Maerten
c77c8a419b refactor: check if the remote exists just before reading it (#1713)
* refactor: check if the remote exists in the read to avoid doing it in offline mode

* fix: timeout error was not working

* fix: use cached copy if available
2024-09-07 20:54:05 +01:00
Andrey Nering
a233b52c65 chore: add changelog for #1777, #1778 2024-09-06 10:48:25 -03:00
Valentin Maerten
0e2c9cc88f fix: include flatten with a default task (#1778) 2024-09-06 10:44:28 -03:00
dependabot[bot]
dd9cec611a chore(deps): bump micromatch from 4.0.5 to 4.0.8 in /website (#1789)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 13:43:32 +00:00
dependabot[bot]
6985413f93 chore(deps): bump webpack from 5.91.0 to 5.94.0 in /website (#1776)
Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 10:41:39 -03:00
dependabot[bot]
cf77768c82 chore(deps): bump github.com/Masterminds/semver/v3 from 3.2.1 to 3.3.0 (#1779)
Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.2.1 to 3.3.0.
- [Release notes](https://github.com/Masterminds/semver/releases)
- [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Masterminds/semver/compare/v3.2.1...v3.3.0)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/semver/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 10:40:55 -03:00
dependabot[bot]
6c3b13b676 chore(deps): bump github.com/mattn/go-zglob from 0.0.4 to 0.0.5 (#1780)
Bumps [github.com/mattn/go-zglob](https://github.com/mattn/go-zglob) from 0.0.4 to 0.0.5.
- [Commits](https://github.com/mattn/go-zglob/compare/v0.0.4...v0.0.5)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-zglob
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 10:40:31 -03:00
Pete Davison
ad45c7aeb3 chore: changelog for #1784 2024-09-02 21:45:19 +00:00
Pete Davison
e4b4d04abd fix: matrix loops should be deterministic (#1784) 2024-09-02 22:43:54 +01:00
Valentin Maerten
a3bdb6c40a chore: changelog for #1782 2024-09-02 22:10:05 +02:00
Valentin Maerten
eb39dd94d0 fix(completion): display aliases in fish completion (#1782) 2024-09-02 16:06:01 -04:00
Pete Davison
21cd573770 chore: changelog for #1767 2024-09-02 19:32:19 +00:00
Pete Davison
281d259e6e feat: loop over a matrix (#1767) 2024-09-02 20:29:00 +01:00
Pete Davison
1cb5daf73e chore: changelog for #1157 2024-09-02 19:28:41 +00:00
Pete Davison
3747b2ab7f feat: completion command (#1157) 2024-09-02 19:21:53 +00:00
Andrey Nering
d727ef5393 website: add @vmaerten as a maintainer 2024-09-02 16:04:52 -03:00
Valentin Maerten
a72b65b3b2 chore: changelog for #1704 2024-08-26 23:19:05 +02:00
Valentin Maerten
ef3b853728 feat: add option to declare an included Taskfile as flatten (#1704) 2024-08-26 17:17:39 -04:00
Valentin Maerten
f302b50519 chore: changelog for #1715 2024-08-25 23:06:58 +02:00
Valentin Maerten
c243b0ec7e fix(remote): TASK_REMOTE_DIR does not work when absolute (#1715) 2024-08-25 17:03:28 -04:00
Thanu Poptiphueng
32158dac87 docs: fix variable name (#1754) 2024-08-24 22:24:42 -03:00
Andrey Nering
0a59890a46 chore(dev): add .vscode/extensions.json with recommended extensions 2024-08-24 22:16:22 -03:00
Andrey Nering
defbcf6acd chore: add changelog for #1764 2024-08-24 21:57:09 -03:00
Daniel Story
045d054a5f feat: add ALIAS special var (#1764) 2024-08-24 21:50:45 -03:00
dependabot[bot]
0941de3318 chore(deps): bump mvdan.cc/sh/v3 from 3.8.0 to 3.9.0 (#1765)
Bumps [mvdan.cc/sh/v3](https://github.com/mvdan/sh) from 3.8.0 to 3.9.0.
- [Release notes](https://github.com/mvdan/sh/releases)
- [Changelog](https://github.com/mvdan/sh/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdan/sh/compare/v3.8.0...v3.9.0)

---
updated-dependencies:
- dependency-name: mvdan.cc/sh/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 13:30:05 -03:00
Andrey Nering
b259edeb65 feat(defer): expose EXIT_CODE special variable to defer: (#1762)
Co-authored-by: Dor Sahar <dorsahar@icloud.com>
2024-08-14 22:53:14 -03:00
dependabot[bot]
35119c12ab chore(deps): bump axios from 1.6.2 to 1.7.4 in /website (#1760)
Bumps [axios](https://github.com/axios/axios) from 1.6.2 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.2...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 11:46:36 -03:00
Pete Davison
f6ff775d11 chore: changelog for #1758 2024-08-14 13:39:56 +00:00
Pete Davison
5e9851f42f Update minimum go version (#1758)
* feat: update minimum version to 1.22

* refactor: use int range iterator

* refactor: loop variables

* refactor: replace slicesext.FirstNonZero with cmp.Or

* refactor: use slices.Concat instead of append

* fix: unused param

* fix: linting
2024-08-14 08:37:05 -05:00
dependabot[bot]
51c569ef37 chore(deps): bump golang.org/x/term from 0.21.0 to 0.23.0 (#1751)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.21.0 to 0.23.0.
- [Commits](https://github.com/golang/term/compare/v0.21.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-11 11:31:48 -03:00
dependabot[bot]
1ca432a80d chore(deps): bump golang.org/x/sync from 0.7.0 to 0.8.0 (#1752)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/sync/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-11 11:28:10 -03:00
Ryan Halliday
e781b3d4e0 docs: update syslist links to the new location (#1747) 2024-08-09 13:14:03 +00:00
JonZeolla
81ff1cdea0 docs: fix special variables link (#1730) 2024-07-25 17:19:41 +00:00
Pete Davison
1f2cbfb932 chore: changelog for #1633 2024-07-16 22:48:15 +00:00
Valentin Maerten
4b6c79aca5 feat: experiment taskfile envs take precedence over os envs (#1633)
* feat: experiment taskfile envs take precedence over os envs

* fix test

* fix typo

Co-authored-by: Andrey Nering <andrey@nering.com.br>

* docs: add p about default

---------

Co-authored-by: Andrey Nering <andrey@nering.com.br>
2024-07-16 23:44:34 +01:00
Pete Davison
5739495739 chore: changelog for #1719 2024-07-16 20:07:02 +00:00
Valentin Maerten
9d72fa3250 ci: add new workflow to check if versioned_docs has been modified (#1719) 2024-07-16 21:03:50 +01:00
Pete Davison
4123ffc780 chore: update go-task/template to tagged version 2024-07-16 17:51:55 +00:00
Valentin Maerten
cdafc67bef docs: add CLI_SILENT and CLI_VERBOSE in the docs (#1717) 2024-07-08 21:18:21 +00:00
Valentin Maerten
9ee4f21d62 fix: --version when a version is provided with -ldflags (#1711) 2024-07-05 14:53:36 -03:00
Alessio Perugini
133086d647 docs: update setup-task version (#1710) 2024-07-05 11:38:44 +00:00
72 changed files with 1364 additions and 343 deletions

View File

@@ -8,6 +8,6 @@ charset = utf-8
trim_trailing_whitespace = true
indent_style = tab
[*.{md,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash,fish}]
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash,fish}]
indent_style = space
indent_size = 2

2
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,3 @@
github: [andreynering, pd93]
github: [andreynering, pd93, vmaerten]
open_collective: task
custom: https://taskfile.dev/donate/

View File

@@ -13,7 +13,7 @@ jobs:
name: Lint
strategy:
matrix:
go-version: [1.21.x, 1.22.x]
go-version: [1.22.x, 1.23.x]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
@@ -25,7 +25,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.55.2
version: v1.60.1
lint-jsonschema:
runs-on: ubuntu-latest
@@ -41,3 +41,18 @@ jobs:
- name: check-jsonschema (metaschema)
run: check-jsonschema --check-metaschema website/static/schema.json
check_doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get changed files in the docs folder
id: changed-files-specific
uses: tj-actions/changed-files@v44
with:
files: website/versioned_docs/**
- uses: actions/github-script@v7
if: steps.changed-files-specific.outputs.any_changed == 'true'
with:
script: |
core.setFailed('website/versioned_docs has changed. Instead you need to update the docs in the website/docs folder.')

View File

@@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.21.x
go-version: 1.22.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2

View File

@@ -13,7 +13,7 @@ jobs:
name: Test
strategy:
matrix:
go-version: [1.21.x, 1.22.x]
go-version: [1.22.x, 1.23.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:

3
.gitignore vendored
View File

@@ -24,8 +24,7 @@ dist/
# editors
.idea/
.vscode/*
!.vscode/*-sample.json
.vscode/settings.json
.fleet/
# exuberant ctags

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"editorconfig.editorconfig",
"golang.go",
"task.vscode-task"
]
}

View File

@@ -1,5 +1,36 @@
# Changelog
## v3.39.0 - 2024-09-07
- Added
[Env Precedence Experiment](https://taskfile.dev/experiments/env-precedence)
(#1038, #1633 by @vmaerten).
- Added a CI lint job to ensure that the docs are updated correctly (#1719 by
@vmaerten).
- Updated minimum required Go version to 1.22 (#1758 by @pd93).
- Expose a new `EXIT_CODE` special variable on `defer:` when a command finishes
with a non-zero exit code (#1484, #1762 by @dorimon-1 and @andreynering).
- Expose a new `ALIAS` special variable, which will contain the alias used to
call the current task. Falls back to the task name. (#1764 by @DanStory).
- Fixed `TASK_REMOTE_DIR` environment variable not working when the path was
absolute. (#1715 by @vmaerten).
- Added an option to declare an included Taskfile as flattened (#1704 by
@vmaerten).
- Added a new
[`--completion` flag](https://taskfile.dev/installation/#setup-completions) to
output completion scripts for various shells (#293, #1157 by @pd93).
- This is now the preferred way to install completions.
- The completion scripts in the `completion` directory
[are now deprecated](https://taskfile.dev/deprecations/completion-scripts/).
- Added the ability to
[loop over a matrix of values](https://taskfile.dev/usage/#looping-over-a-matrix)
(#1766, #1767, #1784 by @pd93).
- Fixed a bug in fish completion where aliases were not displayed (#1781, #1782
by @vmaerten).
- Fixed panic when having a flattened included Taskfile that contains a
`default` task (#1777, #1778 by @vmaerten).
- Optimized file existence checks for remote Taskfiles (#1713 by @vmaerten).
## v3.38.0 - 2024-06-30
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).

View File

@@ -83,6 +83,15 @@ func run() error {
return nil
}
if flags.Completion != "" {
script, err := task.Completion(flags.Completion)
if err != nil {
return err
}
fmt.Println(script)
return nil
}
if flags.Global {
home, err := os.UserHomeDir()
if err != nil {

34
completion.go Normal file
View File

@@ -0,0 +1,34 @@
package task
import (
_ "embed"
"fmt"
)
//go:embed completion/bash/task.bash
var completionBash string
//go:embed completion/fish/task.fish
var completionFish string
//go:embed completion/ps/task.ps1
var completionPowershell string
//go:embed completion/zsh/_task
var completionZsh string
func Completion(completion string) (string, error) {
// Get the file extension for the selected shell
switch completion {
case "bash":
return completionBash, nil
case "fish":
return completionFish, nil
case "powershell":
return completionPowershell, nil
case "zsh":
return completionZsh, nil
default:
return "", fmt.Errorf("unknown shell: %s", completion)
}
}

View File

@@ -10,7 +10,7 @@ function __task_get_tasks --description "Prints all available tasks with their d
end
# Grab names and descriptions (if any) of the tasks
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s*\(.*\)\s*(aliases.*/\1\t\2/' -e 's/\* \(.*\):\s*\(.*\)/\1\t\2/'| string split0)
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s*\(.*\)\s*(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):\s*\(.*\)/\1\t\2/'| string split0)
if test $output
echo $output
end

View File

@@ -80,6 +80,19 @@ func (err *TaskNameConflictError) Code() int {
return CodeTaskNameConflict
}
type TaskNameFlattenConflictError struct {
TaskName string
Include string
}
func (err *TaskNameFlattenConflictError) Error() string {
return fmt.Sprintf(`task: Found multiple tasks (%s) included by "%s""`, err.TaskName, err.Include)
}
func (err *TaskNameFlattenConflictError) Code() int {
return CodeTaskNameConflict
}
// TaskCalledTooManyTimesError is returned when the maximum task call limit is
// exceeded. This is to prevent infinite loops and cyclic dependencies.
type TaskCalledTooManyTimesError struct {

17
go.mod
View File

@@ -1,18 +1,18 @@
module github.com/go-task/task/v3
go 1.21.0
go 1.22.0
require (
github.com/Ladicle/tabwriter v1.0.0
github.com/Masterminds/semver/v3 v3.2.1
github.com/Masterminds/semver/v3 v3.3.0
github.com/alecthomas/chroma/v2 v2.14.0
github.com/davecgh/go-spew v1.1.1
github.com/dominikbraun/graph v0.23.0
github.com/fatih/color v1.17.0
github.com/go-task/slim-sprig/v3 v3.0.0
github.com/go-task/template v0.0.0-20240602015157-960e6f576656
github.com/go-task/template v0.1.0
github.com/joho/godotenv v1.5.1
github.com/mattn/go-zglob v0.0.4
github.com/mattn/go-zglob v0.0.6
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/otiai10/copy v1.14.0
github.com/radovskyb/watcher v1.0.7
@@ -20,10 +20,10 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/zeebo/xxh3 v1.0.2
golang.org/x/sync v0.7.0
golang.org/x/term v0.21.0
golang.org/x/sync v0.8.0
golang.org/x/term v0.24.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/v3 v3.8.0
mvdan.cc/sh/v3 v3.9.0
)
require (
@@ -31,8 +31,9 @@ require (
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/sys v0.25.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

34
go.sum
View File

@@ -1,7 +1,7 @@
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
@@ -18,12 +18,12 @@ github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucV
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-task/template v0.0.0-20240602015157-960e6f576656 h1:knZZ4zVdTBQnevBz0zSES++4Mr7wr+cHopLvHabIgkA=
github.com/go-task/template v0.0.0-20240602015157-960e6f576656/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetTE=
github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
@@ -41,10 +41,12 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM=
github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A=
github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
@@ -67,18 +69,18 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8=
mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY=
mvdan.cc/sh/v3 v3.9.0 h1:it14fyjCdQUk4jf/aYxLO3FG8jFarR9GzMCtnlvvD7c=
mvdan.cc/sh/v3 v3.9.0/go.mod h1:cdBk8bgoiBI7lSZqK5JhUuq7OB64VQ7fgm85xelw3Nk=

View File

@@ -1,15 +1,15 @@
package task
import (
"cmp"
"fmt"
"github.com/go-task/task/v3/internal/hash"
"github.com/go-task/task/v3/internal/slicesext"
"github.com/go-task/task/v3/taskfile/ast"
)
func (e *Executor) GetHash(t *ast.Task) (string, error) {
r := slicesext.FirstNonZero(t.Run, e.Taskfile.Run)
r := cmp.Or(t.Run, e.Taskfile.Run)
var h hash.HashFunc
switch r {
case "always":

28
help.go
View File

@@ -160,23 +160,21 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
}
var g errgroup.Group
for i := range tasks {
task := tasks[i]
j := i
aliases := []string{}
if len(task.Aliases) > 0 {
aliases = task.Aliases
if len(tasks[i].Aliases) > 0 {
aliases = tasks[i].Aliases
}
g.Go(func() error {
o.Tasks[j] = editors.Task{
Name: task.Name(),
Desc: task.Desc,
Summary: task.Summary,
o.Tasks[i] = editors.Task{
Name: tasks[i].Name(),
Desc: tasks[i].Desc,
Summary: tasks[i].Summary,
Aliases: aliases,
UpToDate: false,
Location: &editors.Location{
Line: task.Location.Line,
Column: task.Location.Column,
Taskfile: task.Location.Taskfile,
Line: tasks[i].Location.Line,
Column: tasks[i].Location.Column,
Taskfile: tasks[i].Location.Taskfile,
},
}
@@ -186,10 +184,10 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
// Get the fingerprinting method to use
method := e.Taskfile.Method
if task.Method != "" {
method = task.Method
if tasks[i].Method != "" {
method = tasks[i].Method
}
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), task,
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), tasks[i],
fingerprint.WithMethod(method),
fingerprint.WithTempDir(e.TempDir.Fingerprint),
fingerprint.WithDry(e.Dry),
@@ -199,7 +197,7 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
return err
}
o.Tasks[j].UpToDate = upToDate
o.Tasks[i].UpToDate = upToDate
return nil
})

View File

@@ -46,7 +46,7 @@ func (c *Compiler) FastGetVariables(t *ast.Task, call *ast.Call) (*ast.Vars, err
func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool) (*ast.Vars, error) {
result := GetEnviron()
if t != nil {
specialVars, err := c.getSpecialVars(t)
specialVars, err := c.getSpecialVars(t, call)
if err != nil {
return nil, err
}
@@ -179,9 +179,10 @@ func (c *Compiler) ResetCache() {
c.dynamicCache = nil
}
func (c *Compiler) getSpecialVars(t *ast.Task) (map[string]string, error) {
func (c *Compiler) getSpecialVars(t *ast.Task, call *ast.Call) (map[string]string, error) {
return map[string]string{
"TASK": t.Task,
"ALIAS": call.Task,
"TASK_EXE": filepath.ToSlash(os.Args[0]),
"ROOT_TASKFILE": filepathext.SmartJoin(c.Dir, c.Entrypoint),
"ROOT_DIR": c.Dir,

View File

@@ -85,7 +85,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
case reflect.Struct:
// Loop over each field and call traverseFunc recursively
for i := 0; i < v.NumField(); i += 1 {
for i := range v.NumField() {
if err := traverseFunc(copy.Field(i), v.Field(i)); err != nil {
return err
}
@@ -95,7 +95,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
// Create an empty copy from the original value's type
copy.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Cap()))
// Loop over each element and call traverseFunc recursively
for i := 0; i < v.Len(); i += 1 {
for i := range v.Len() {
if err := traverseFunc(copy.Index(i), v.Index(i)); err != nil {
return err
}

9
internal/env/env.go vendored
View File

@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -11,15 +12,15 @@ func Get(t *ast.Task) []string {
if t.Env == nil {
return nil
}
environ := os.Environ()
for k, v := range t.Env.ToCacheMap() {
if !isTypeAllowed(v) {
continue
}
if _, alreadySet := os.LookupEnv(k); alreadySet {
continue
if !experiments.EnvPrecedence.Enabled {
if _, alreadySet := os.LookupEnv(k); alreadySet {
continue
}
}
environ = append(environ, fmt.Sprintf("%s=%v", k, v))
}

View File

@@ -90,14 +90,6 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
return r.Run(ctx, p)
}
// IsExitError returns true the given error is an exis status error
func IsExitError(err error) bool {
if _, ok := interp.IsExitStatus(err); ok {
return true
}
return false
}
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
// if available.
func Expand(s string) (string, error) {

View File

@@ -29,6 +29,7 @@ var (
RemoteTaskfiles Experiment
AnyVariables Experiment
MapVariables Experiment
EnvPrecedence Experiment
)
func init() {
@@ -37,6 +38,7 @@ func init() {
RemoteTaskfiles = New("REMOTE_TASKFILES")
AnyVariables = New("ANY_VARIABLES", "1", "2")
MapVariables = New("MAP_VARIABLES", "1", "2")
EnvPrecedence = New("ENV_PRECEDENCE")
}
func New(xName string, enabledValues ...string) Experiment {
@@ -104,5 +106,6 @@ func List(l *logger.Logger) error {
printExperiment(w, l, GentleForce)
printExperiment(w, l, RemoteTaskfiles)
printExperiment(w, l, MapVariables)
printExperiment(w, l, EnvPrecedence)
return w.Flush()
}

View File

@@ -38,6 +38,7 @@ var (
Version bool
Help bool
Init bool
Completion string
List bool
ListAll bool
ListJson bool
@@ -80,6 +81,7 @@ func init() {
pflag.BoolVar(&Version, "version", false, "Show Task version.")
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
pflag.StringVar(&Completion, "completion", "", "Generates shell completion script.")
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(&ListJson, "json", "j", false, "Formats task list as JSON.")

View File

@@ -89,7 +89,7 @@ func envColor(env string, defaultColor color.Attribute) []color.Attribute {
// Otherwise, split by semicolons (ANSI color codes) and use them as is.
attributeStrs := strings.Split(override, ",")
if len(attributeStrs) == 3 {
attributeStrs = append([]string{"38", "2"}, attributeStrs...)
attributeStrs = slices.Concat([]string{"38", "2"}, attributeStrs)
} else {
attributeStrs = strings.Split(override, ";")
}

View File

@@ -18,13 +18,3 @@ func UniqueJoin[T cmp.Ordered](ss ...[]T) []T {
slices.Sort(r)
return slices.Compact(r)
}
func FirstNonZero[T comparable](values ...T) T {
var zero T
for _, v := range values {
if v != zero {
return v
}
}
return zero
}

View File

@@ -15,7 +15,9 @@ func init() {
if !ok || info.Main.Version == "" {
version = "unknown"
} else {
version = info.Main.Version
if version == "" {
version = info.Main.Version
}
sum = info.Main.Sum
}
}

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.38.0",
"version": "3.39.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.38.0",
"version": "3.39.0",
"description": "A task runner / simpler Make alternative written in Go",
"scripts": {
"postinstall": "go-npm install",

View File

@@ -1,13 +1,11 @@
package task
import (
"context"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/taskfile/ast"
)
func (e *Executor) areTaskRequiredVarsSet(ctx context.Context, t *ast.Task, call *ast.Call) error {
func (e *Executor) areTaskRequiredVarsSet(t *ast.Task, call *ast.Call) error {
if t.Requires == nil || len(t.Requires.Vars) == 0 {
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"sync"
@@ -95,7 +96,7 @@ func (e *Executor) setupFuzzyModel() {
words = append(words, taskName)
for _, task := range e.Taskfile.Tasks.Values() {
words = append(words, task.Aliases...)
words = slices.Concat(words, task.Aliases)
}
}
@@ -133,8 +134,8 @@ func (e *Executor) setupTempDir() error {
}
if os.Getenv("TASK_REMOTE_DIR") != "" {
if filepath.IsAbs(os.Getenv("TASK_TEMP_DIR")) || strings.HasPrefix(os.Getenv("TASK_TEMP_DIR"), "~") {
remoteTempDir, err := execext.Expand(filepathext.SmartJoin(e.Dir, ".task"))
if filepath.IsAbs(os.Getenv("TASK_REMOTE_DIR")) || strings.HasPrefix(os.Getenv("TASK_REMOTE_DIR"), "~") {
remoteTempDir, err := execext.Expand(os.Getenv("TASK_REMOTE_DIR"))
if err != nil {
return err
}

View File

@@ -8,24 +8,25 @@ import (
"github.com/go-task/task/v3/internal/logger"
)
const interruptSignalsCount = 3
// NOTE(@andreynering): This function intercepts SIGINT and SIGTERM signals
// so the Task process is not killed immediately and processes running have
// time to do cleanup work.
func (e *Executor) InterceptInterruptSignals() {
ch := make(chan os.Signal, 3)
ch := make(chan os.Signal, interruptSignalsCount)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
go func() {
for i := 1; i <= 3; i++ {
for i := range interruptSignalsCount {
sig := <-ch
if i < 3 {
e.Logger.Outf(logger.Yellow, "task: Signal received: %q\n", sig)
continue
if i+1 >= interruptSignalsCount {
e.Logger.Errf(logger.Red, "task: Signal received for the third time: %q. Forcing shutdown\n", sig)
os.Exit(1)
}
e.Logger.Errf(logger.Red, "task: Signal received for the third time: %q. Forcing shutdown\n", sig)
os.Exit(1)
e.Logger.Outf(logger.Yellow, "task: Signal received: %q\n", sig)
}
}()
}

View File

@@ -20,9 +20,7 @@ import (
"time"
)
var (
SLEEPIT, _ = filepath.Abs("./bin/sleepit")
)
var SLEEPIT, _ = filepath.Abs("./bin/sleepit")
func TestSignalSentToProcessGroup(t *testing.T) {
task, err := getTaskPath()
@@ -147,7 +145,7 @@ func TestSignalSentToProcessGroup(t *testing.T) {
// where the negative PID means the corresponding process group. Note that
// this negative PID works only as long as the caller of the kill(2) system
// call has a different PID, which is the case for this test.
for i := 1; i <= tc.sendSigs; i++ {
for range tc.sendSigs - 1 {
if err := syscall.Kill(-sut.Process.Pid, syscall.SIGINT); err != nil {
t.Fatalf("sending INT signal to the process group: %v", err)
}

39
task.go
View File

@@ -11,6 +11,8 @@ import (
"sync/atomic"
"time"
"mvdan.cc/sh/v3/interp"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/env"
@@ -200,7 +202,7 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
return err
}
if err := e.areTaskRequiredVarsSet(ctx, t, call); err != nil {
if err := e.areTaskRequiredVarsSet(t, call); err != nil {
return err
}
@@ -247,9 +249,11 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v\n", t.Dir, err)
}
var deferredExitCode uint8
for i := range t.Cmds {
if t.Cmds[i].Defer {
defer e.runDeferred(t, call, i)
defer e.runDeferred(t, call, i, &deferredExitCode)
continue
}
@@ -258,9 +262,13 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v\n", err2)
}
if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
continue
exitCode, isExitError := interp.IsExitStatus(err)
if isExitError {
if t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
continue
}
deferredExitCode = exitCode
}
if call.Indirect {
@@ -312,10 +320,21 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
return g.Wait()
}
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int) {
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int, deferredExitCode *uint8) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cmd := t.Cmds[i]
vars, _ := e.Compiler.FastGetVariables(t, call)
cache := &templater.Cache{Vars: vars}
extra := map[string]any{}
if deferredExitCode != nil && *deferredExitCode > 0 {
extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode)
}
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
if err := e.runCommand(ctx, t, call, i); err != nil {
e.Logger.VerboseErrf(logger.Yellow, "task: ignored error in deferred cmd: %s\n", err.Error())
}
@@ -372,7 +391,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *ast.Call,
if closeErr := close(err); closeErr != nil {
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
}
if execext.IsExitError(err) && cmd.IgnoreError {
if _, isExitError := interp.IsExitStatus(err); isExitError && cmd.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
return nil
}
@@ -494,14 +513,12 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) {
// Compile the list of tasks
for i := range tasks {
idx := i
task := tasks[idx]
g.Go(func() error {
compiledTask, err := e.FastCompiledTask(&ast.Call{Task: task.Task})
compiledTask, err := e.FastCompiledTask(&ast.Call{Task: tasks[i].Task})
if err != nil {
return err
}
tasks[idx] = compiledTask
tasks[i] = compiledTask
return nil
})
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -60,7 +61,6 @@ func (fct fileContentTest) Run(t *testing.T) {
for f := range fct.Files {
_ = os.Remove(filepathext.SmartJoin(fct.Dir, f))
}
e := &task.Executor{
Dir: fct.Dir,
TempDir: task.TempDir{
@@ -71,9 +71,9 @@ func (fct fileContentTest) Run(t *testing.T) {
Stdout: io.Discard,
Stderr: io.Discard,
}
require.NoError(t, e.Setup(), "e.Setup()")
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: fct.Target}), "e.Run(target)")
for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
path := filepathext.SmartJoin(e.Dir, name)
@@ -108,6 +108,7 @@ func TestEmptyTaskfile(t *testing.T) {
}
func TestEnv(t *testing.T) {
t.Setenv("QUX", "from_os")
tt := fileContentTest{
Dir: "testdata/env",
Target: "default",
@@ -116,9 +117,21 @@ func TestEnv(t *testing.T) {
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
"multiple_type.txt": "FOO='1' BAR='true' BAZ='1.1'\n",
"not-overriden.txt": "QUX='from_os'\n",
},
}
tt.Run(t)
t.Setenv("TASK_X_ENV_PRECEDENCE", "1")
experiments.EnvPrecedence = experiments.New("ENV_PRECEDENCE")
ttt := fileContentTest{
Dir: "testdata/env",
Target: "overriden",
TrimSpace: false,
Files: map[string]string{
"overriden.txt": "QUX='from_taskfile'\n",
},
}
ttt.Run(t)
}
func TestVars(t *testing.T) {
@@ -812,7 +825,8 @@ func TestListDescInterpolation(t *testing.T) {
t.Error(err)
}
assert.Contains(t, buff.String(), "bar")
assert.Contains(t, buff.String(), "foo-var")
assert.Contains(t, buff.String(), "bar-var")
}
func TestStatusVariables(t *testing.T) {
@@ -1216,6 +1230,45 @@ func TestIncludesInternal(t *testing.T) {
}
}
func TestIncludesFlatten(t *testing.T) {
const dir = "testdata/includes_flatten"
tests := []struct {
name string
taskfile string
task string
expectedErr bool
expectedOutput string
}{
{name: "included flatten", taskfile: "Taskfile.yml", task: "gen", expectedOutput: "gen from included\n"},
{name: "included flatten with default", taskfile: "Taskfile.yml", task: "default", expectedOutput: "default from included flatten\n"},
{name: "included flatten can call entrypoint tasks", taskfile: "Taskfile.yml", task: "from_entrypoint", expectedOutput: "from entrypoint\n"},
{name: "included flatten with deps", taskfile: "Taskfile.yml", task: "with_deps", expectedOutput: "gen from included\nwith_deps from included\n"},
{name: "included flatten nested", taskfile: "Taskfile.yml", task: "from_nested", expectedOutput: "from nested\n"},
{name: "included flatten multiple same task", taskfile: "Taskfile.multiple.yml", task: "gen", expectedErr: true, expectedOutput: "task: Found multiple tasks (gen) included by \"included\"\""},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Entrypoint: dir + "/" + test.taskfile,
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
err := e.Setup()
if test.expectedErr {
assert.EqualError(t, err, test.expectedOutput)
} else {
require.NoError(t, err)
_ = e.Run(context.Background(), &ast.Call{Task: test.task})
assert.Equal(t, test.expectedOutput, buff.String())
}
})
}
}
func TestIncludesInterpolation(t *testing.T) {
const dir = "testdata/includes_interpolation"
tests := []struct {
@@ -1724,6 +1777,34 @@ task-1 ran successfully
assert.Contains(t, buff.String(), expectedOutputOrder)
}
func TestExitCodeZero(t *testing.T) {
const dir = "testdata/exit_code"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "exit-zero"}))
assert.Equal(t, "EXIT_CODE=", strings.TrimSpace(buff.String()))
}
func TestExitCodeOne(t *testing.T) {
const dir = "testdata/exit_code"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "exit-one"}))
assert.Equal(t, "EXIT_CODE=1", strings.TrimSpace(buff.String()))
}
func TestIgnoreNilElements(t *testing.T) {
tests := []struct {
name string
@@ -2295,6 +2376,10 @@ func TestForCmds(t *testing.T) {
name: "loop-explicit",
expectedOutput: "a\nb\nc\n",
},
{
name: "loop-matrix",
expectedOutput: "windows/amd64\nwindows/arm64\nlinux/amd64\nlinux/arm64\ndarwin/amd64\ndarwin/arm64\n",
},
{
name: "loop-sources",
expectedOutput: "bar\nfoo\n",
@@ -2352,6 +2437,17 @@ func TestForDeps(t *testing.T) {
name: "loop-explicit",
expectedOutputContains: []string{"a\n", "b\n", "c\n"},
},
{
name: "loop-matrix",
expectedOutputContains: []string{
"windows/amd64\n",
"windows/arm64\n",
"linux/amd64\n",
"linux/arm64\n",
"darwin/amd64\n",
"darwin/arm64\n",
},
},
{
name: "loop-sources",
expectedOutputContains: []string{"bar\n", "foo\n"},

View File

@@ -5,14 +5,16 @@ import (
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
"github.com/go-task/task/v3/internal/omap"
)
type For struct {
From string
List []any
Var string
Split string
As string
From string
List []any
Matrix omap.OrderedMap[string, []any]
Var string
Split string
As string
}
func (f *For) UnmarshalYAML(node *yaml.Node) error {
@@ -36,16 +38,21 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode:
var forStruct struct {
Var string
Split string
As string
Matrix omap.OrderedMap[string, []any]
Var string
Split string
As string
}
if err := node.Decode(&forStruct); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
if forStruct.Var == "" {
if forStruct.Var == "" && forStruct.Matrix.Len() == 0 {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for")
}
if forStruct.Var != "" && forStruct.Matrix.Len() != 0 {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("cannot use both var and matrix in for")
}
f.Matrix = forStruct.Matrix
f.Var = forStruct.Var
f.Split = forStruct.Split
f.As = forStruct.As
@@ -60,10 +67,11 @@ func (f *For) DeepCopy() *For {
return nil
}
return &For{
From: f.From,
List: deepcopy.Slice(f.List),
Var: f.Var,
Split: f.Split,
As: f.As,
From: f.From,
List: deepcopy.Slice(f.List),
Matrix: f.Matrix.DeepCopy(),
Var: f.Var,
Split: f.Split,
As: f.As,
}
}

View File

@@ -17,6 +17,7 @@ type Include struct {
Aliases []string
AdvancedImport bool
Vars *Vars
Flatten bool
}
// Includes represents information about included tasksfiles
@@ -81,6 +82,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
Dir string
Optional bool
Internal bool
Flatten bool
Aliases []string
Vars *Vars
}
@@ -94,6 +96,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
include.Aliases = includedTaskfile.Aliases
include.AdvancedImport = true
include.Vars = includedTaskfile.Vars
include.Flatten = includedTaskfile.Flatten
return nil
}
@@ -114,5 +117,6 @@ func (include *Include) DeepCopy() *Include {
Internal: include.Internal,
AdvancedImport: include.AdvancedImport,
Vars: include.Vars.DeepCopy(),
Flatten: include.Flatten,
}
}

View File

@@ -55,8 +55,7 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
}
t1.Vars.Merge(t2.Vars, include)
t1.Env.Merge(t2.Env, include)
t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
return nil
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
}
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {

View File

@@ -2,6 +2,7 @@ package ast
import (
"fmt"
"slices"
"strings"
"gopkg.in/yaml.v3"
@@ -46,43 +47,48 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
return matchingTasks
}
func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) {
_ = t2.Range(func(name string, v *Task) error {
func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) error {
err := t2.Range(func(name string, v *Task) error {
// We do a deep copy of the task struct here to ensure that no data can
// be changed elsewhere once the taskfile is merged.
task := v.DeepCopy()
// Set the task to internal if EITHER the included task or the included
// taskfile are marked as internal
task.Internal = task.Internal || (include != nil && include.Internal)
// Add namespaces to task dependencies
for _, dep := range task.Deps {
if dep != nil && dep.Task != "" {
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
}
}
// Add namespaces to task commands
for _, cmd := range task.Cmds {
if cmd != nil && cmd.Task != "" {
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
}
}
// Add namespaces to task aliases
for i, alias := range task.Aliases {
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
}
// Add namespace aliases
if include != nil {
for _, namespaceAlias := range include.Aliases {
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
for _, alias := range v.Aliases {
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
taskName := name
if !include.Flatten {
// Add namespaces to task dependencies
for _, dep := range task.Deps {
if dep != nil && dep.Task != "" {
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
}
}
// Add namespaces to task commands
for _, cmd := range task.Cmds {
if cmd != nil && cmd.Task != "" {
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
}
}
// Add namespaces to task aliases
for i, alias := range task.Aliases {
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
}
// Add namespace aliases
if include != nil {
for _, namespaceAlias := range include.Aliases {
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
for _, alias := range v.Aliases {
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
}
}
}
taskName = taskNameWithNamespace(name, include.Namespace)
task.Namespace = include.Namespace
task.Task = taskName
}
if include.AdvancedImport {
@@ -94,25 +100,29 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) {
task.IncludedTaskfileVars = includedTaskfileVars.DeepCopy()
}
if t1.Get(taskName) != nil {
return &errors.TaskNameFlattenConflictError{
TaskName: taskName,
Include: include.Namespace,
}
}
// Add the task to the merged taskfile
taskNameWithNamespace := taskNameWithNamespace(name, include.Namespace)
task.Namespace = include.Namespace
task.Task = taskNameWithNamespace
t1.Set(taskNameWithNamespace, task)
t1.Set(taskName, task)
return nil
})
// If the included Taskfile has a default task and the parent namespace has
// If the included Taskfile has a default task, being not flattened and the parent namespace has
// no task with a matching name, we can add an alias so that the user can
// run the included Taskfile's default task without specifying its full
// name. If the parent namespace has aliases, we add another alias for each
// of them.
if t2.Get("default") != nil && t1.Get(include.Namespace) == nil {
if t2.Get("default") != nil && t1.Get(include.Namespace) == nil && !include.Flatten {
defaultTaskName := fmt.Sprintf("%s:default", include.Namespace)
t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Namespace)
t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Aliases...)
t1.Get(defaultTaskName).Aliases = slices.Concat(t1.Get(defaultTaskName).Aliases, include.Aliases)
}
return err
}
func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {

View File

@@ -17,7 +17,9 @@ import (
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
type HTTPNode struct {
*BaseNode
URL *url.URL
URL *url.URL
logger *logger.Logger
timeout time.Duration
}
func NewHTTPNode(
@@ -36,18 +38,12 @@ func NewHTTPNode(
if url.Scheme == "http" && !insecure {
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
}
ctx, cf := context.WithTimeout(context.Background(), timeout)
defer cf()
url, err = RemoteExists(ctx, l, url)
if err != nil {
return nil, err
}
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, &errors.TaskfileNetworkTimeoutError{URI: url.String(), Timeout: timeout}
}
return &HTTPNode{
BaseNode: base,
URL: url,
timeout: timeout,
logger: l,
}, nil
}
@@ -60,6 +56,11 @@ func (node *HTTPNode) Remote() bool {
}
func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
url, err := RemoteExists(ctx, node.logger, node.URL, node.timeout)
if err != nil {
return nil, err
}
node.URL = url
req, err := http.NewRequest("GET", node.URL.String(), nil)
if err != nil {
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
@@ -67,6 +68,9 @@ func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.URL.String(), Timeout: node.timeout}
}
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
}
defer resp.Body.Close()

View File

@@ -109,6 +109,7 @@ func (r *Reader) include(node Node) error {
Dir: templater.Replace(include.Dir, cache),
Optional: include.Optional,
Internal: include.Internal,
Flatten: include.Flatten,
Aliases: include.Aliases,
AdvancedImport: include.AdvancedImport,
Vars: include.Vars,
@@ -207,8 +208,9 @@ func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
// Read the file
b, err = node.Read(ctx)
var taskfileNetworkTimeoutError *errors.TaskfileNetworkTimeoutError
// If we timed out then we likely have a network issue
if node.Remote() && errors.Is(ctx.Err(), context.DeadlineExceeded) {
if node.Remote() && errors.As(err, &taskfileNetworkTimeoutError) {
// If a download was requested, then we can't use a cached copy
if r.download {
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout}

View File

@@ -8,6 +8,7 @@ import (
"path/filepath"
"slices"
"strings"
"time"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
@@ -40,7 +41,7 @@ var (
// at the given URL with any of the default Taskfile files names. If any of
// these match a file, the first matching path will be returned. If no files are
// found, an error will be returned.
func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL) (*url.URL, error) {
func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL, timeout time.Duration) (*url.URL, error) {
// Create a new HEAD request for the given URL to check if the resource exists
req, err := http.NewRequest("HEAD", u.String(), nil)
if err != nil {
@@ -50,6 +51,9 @@ func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL) (*url.URL,
// Request the given URL
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, &errors.TaskfileNetworkTimeoutError{URI: u.String(), Timeout: timeout}
}
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
}
defer resp.Body.Close()

View File

@@ -8,12 +8,14 @@ env:
FOO: foo
BAR: bar
BAZ: "{{.BAZ}}"
QUX: from_taskfile
tasks:
default:
cmds:
- task: local
- task: global
- task: not-overriden
- task: multiple_type
local:
@@ -40,3 +42,11 @@ tasks:
BAZ: 1.1
cmds:
- echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > multiple_type.txt
not-overriden:
cmds:
- echo "QUX='$QUX'" > not-overriden.txt
overriden:
cmds:
- echo "QUX='$QUX'" > overriden.txt

17
testdata/exit_code/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
version: '3'
silent: true
vars:
PREFIX: EXIT_CODE=
tasks:
exit-zero:
cmds:
- defer: echo {{.PREFIX}}{{.EXIT_CODE}}
- exit 0
exit-one:
cmds:
- defer: echo {{.PREFIX}}{{.EXIT_CODE}}
- exit 1

View File

@@ -7,6 +7,14 @@ tasks:
- for: ["a", "b", "c"]
cmd: echo "{{.ITEM}}"
loop-matrix:
cmds:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
# Loop over the task's sources
loop-sources:
sources:

View File

@@ -9,6 +9,16 @@ tasks:
vars:
TEXT: "{{.ITEM}}"
loop-matrix:
deps:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
task: echo
vars:
TEXT: "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
# Loop over the task's sources
loop-sources:
sources:

1
testdata/includes_flatten/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.txt

View File

@@ -0,0 +1,12 @@
version: '3'
includes:
included:
taskfile: ./included
flatten: true
tasks:
gen:
cmds:
- echo "gen multiple"

View File

@@ -0,0 +1,3 @@
version: '3'
tasks:
default: echo "default from included flatten"

15
testdata/includes_flatten/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
version: '3'
includes:
included:
taskfile: ./included
dir: ./included
flatten: true
with_default:
taskfile: ./Taskfile.with_default.yml
flatten: true
tasks:
from_entrypoint: echo "from entrypoint"

View File

@@ -0,0 +1,23 @@
version: '3'
includes:
nested:
taskfile: ../nested
flatten: true
tasks:
gen:
cmds:
- echo "gen from included"
with_deps:
deps:
- gen
cmds:
- echo "with_deps from included"
pwd:
desc: Print working directory
cmds:
- pwd

View File

@@ -0,0 +1,6 @@
version: '3'
tasks:
from_nested:
cmds:
- echo "from nested"

View File

@@ -1,8 +1,12 @@
version: '3'
vars:
FOO: bar
FOO: foo
BAR: bar
tasks:
foo:
desc: "task has desc with {{.FOO}} var"
desc: "task has desc with {{.FOO}}-var"
bar:
desc: "task has desc with {{.BAR}}-var"

View File

@@ -6,8 +6,16 @@ includes:
dir: ./included
tasks:
print-task: echo {{.TASK}}
print-task:
aliases: [echo-task]
cmds:
- echo {{.TASK}}
print-root-dir: echo {{.ROOT_DIR}}
print-taskfile: echo {{.TASKFILE}}
print-taskfile-dir: echo {{.TASKFILE_DIR}}
print-task-version: echo {{.TASK_VERSION}}
print-task-alias:
aliases: [echo-task-alias]
cmds:
- echo "{{.ALIAS}}"
print-task-alias-default: echo "{{.ALIAS}}"

View File

@@ -1,8 +1,16 @@
version: '3'
tasks:
print-task: echo {{.TASK}}
print-task:
aliases: [echo-task]
cmds:
- echo {{.TASK}}
print-root-dir: echo {{.ROOT_DIR}}
print-taskfile: echo {{.TASKFILE}}
print-taskfile-dir: echo {{.TASKFILE_DIR}}
print-task-version: echo {{.TASK_VERSION}}
print-task-alias:
aliases: [echo-task-alias]
cmds:
- echo "{{.ALIAS}}"
print-task-alias-default: echo "{{.ALIAS}}"

View File

@@ -11,6 +11,7 @@ import (
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/fingerprint"
"github.com/go-task/task/v3/internal/omap"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -161,6 +162,12 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
}
continue
}
// Defer commands are replaced in a lazy manner because
// we need to include EXIT_CODE.
if cmd.Defer {
new.Cmds = append(new.Cmds, cmd.DeepCopy())
continue
}
newCmd := cmd.DeepCopy()
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
newCmd.Task = templater.Replace(cmd.Task, cache)
@@ -265,9 +272,13 @@ func itemsFromFor(
) ([]any, []string, error) {
var keys []string // The list of keys to loop over (only if looping over a map)
var values []any // The list of values to loop over
// Get the list from a matrix
if f.Matrix.Len() != 0 {
return asAnySlice(product(f.Matrix)), nil, nil
}
// Get the list from the explicit for list
if f.List != nil && len(f.List) > 0 {
values = f.List
if len(f.List) > 0 {
return f.List, nil, nil
}
// Get the list from the task sources
if f.From == "sources" {
@@ -316,3 +327,39 @@ func itemsFromFor(
}
return values, keys, nil
}
// product generates the cartesian product of the input map of slices.
func product(inputMap omap.OrderedMap[string, []any]) []map[string]any {
if inputMap.Len() == 0 {
return nil
}
// Start with an empty product result
result := []map[string]any{{}}
// Iterate over each slice in the slices
_ = inputMap.Range(func(key string, slice []any) error {
var newResult []map[string]any
// For each combination in the current result
for _, combination := range result {
// Append each element from the current slice to the combinations
for _, item := range slice {
newComb := make(map[string]any, len(combination))
// Copy the existing combination
for k, v := range combination {
newComb[k] = v
}
// Add the current item with the corresponding key
newComb[key] = item
newResult = append(newResult, newComb)
}
}
// Update result with the new combinations
result = newResult
return nil
})
return result
}

View File

@@ -5,6 +5,37 @@ sidebar_position: 14
# Changelog
## v3.39.0 - 2024-09-07
- Added
[Env Precedence Experiment](https://taskfile.dev/experiments/env-precedence)
(#1038, #1633 by @vmaerten).
- Added a CI lint job to ensure that the docs are updated correctly (#1719 by
@vmaerten).
- Updated minimum required Go version to 1.22 (#1758 by @pd93).
- Expose a new `EXIT_CODE` special variable on `defer:` when a command finishes
with a non-zero exit code (#1484, #1762 by @dorimon-1 and @andreynering).
- Expose a new `ALIAS` special variable, which will contain the alias used to
call the current task. Falls back to the task name. (#1764 by @DanStory).
- Fixed `TASK_REMOTE_DIR` environment variable not working when the path was
absolute. (#1715 by @vmaerten).
- Added an option to declare an included Taskfile as flattened (#1704 by
@vmaerten).
- Added a new
[`--completion` flag](https://taskfile.dev/installation/#setup-completions) to
output completion scripts for various shells (#293, #1157 by @pd93).
- This is now the preferred way to install completions.
- The completion scripts in the `completion` directory
[are now deprecated](https://taskfile.dev/deprecations/completion-scripts/).
- Added the ability to
[loop over a matrix of values](https://taskfile.dev/usage/#looping-over-a-matrix)
(#1766, #1767, #1784 by @pd93).
- Fixed a bug in fish completion where aliases were not displayed (#1781, #1782
by @vmaerten).
- Fixed panic when having a flattened included Taskfile that contains a
`default` task (#1777, #1778 by @vmaerten).
- Optimized file existence checks for remote Taskfiles (#1713 by @vmaerten).
## v3.38.0 - 2024-06-30
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).

View File

@@ -0,0 +1,25 @@
---
slug: /deprecations/completion-scripts/
---
# Completion Scripts
:::warning
This deprecation breaks the following functionality:
- Any direct references to the completion scripts in the Task git repository
:::
Direct use of the completion scripts in the `completion/*` directory of the
[github.com/go-task/task][task] Git repository is deprecated. Any shell
configuration that directly refers to these scripts will potentially break in
the future as the scripts may be moved or deleted entirely. Any configuration
should be updated to use the [new method for generating shell
completions][completions] instead.
{/* prettier-ignore-start */}
[completions]: ../installation.mdx#setup-completions
[task]: https://github.com/go-task/task
{/* prettier-ignore-end */}

View File

@@ -0,0 +1,74 @@
---
slug: '/experiments/env-precedence'
---
# Env Precedence (#1038)
:::caution
All experimental features are subject to breaking changes and/or removal _at any
time_. We strongly recommend that you do not use these features in a production
environment. They are intended for testing and feedback only.
:::
:::warning
This experiment breaks the following functionality:
- environment variable will take precedence over OS environment variables
:::
:::info
To enable this experiment, set the environment variable:
`TASK_X_ENV_PRECEDENCE=1`. Check out [our guide to enabling
experiments][enabling-experiments] for more information.
:::
Before this experiment, the OS variable took precedence over the task
environment variable. This experiment changes the precedence to make the task
environment variable take precedence over the OS variable.
Consider the following example:
```yml
version: '3'
tasks:
default:
env:
KEY: 'other'
cmds:
- echo "$KEY"
```
Running `KEY=some task` before this experiment, the output would be `some`, but
after this experiment, the output would be `other`.
If you still want to get the OS variable, you can use the template function env
like follow : `{{env "OS_VAR"}}`.
```yml
version: '3'
tasks:
default:
env:
KEY: 'other'
cmds:
- echo "$KEY"
- echo {{env "KEY"}}
```
Running `KEY=some task`, the output would be `other` and `some`.
Like other variables/envs, you can also fall back to a given value using the
default template function:
```yml
MY_ENV: '{{.MY_ENV | default "fallback"}}'
```
{/* prettier-ignore-start */}
[enabling-experiments]: ./experiments.mdx#enabling-experiments
{/* prettier-ignore-end */}

View File

@@ -3,6 +3,9 @@ slug: /installation/
sidebar_position: 2
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Installation
Task offers many installation methods. Check out the available methods below.
@@ -209,7 +212,7 @@ If you want to install Task in GitHub Actions you can try using
```yaml
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -247,65 +250,76 @@ released binary.
## Setup completions
Download the autocompletion file corresponding to your shell.
Some installation methods will automatically install completions too, but if
this isn't working for you or your chosen method doesn't include them, you can
run `task --completion <shell>` to output a completion script for any supported
shell. There are a couple of ways these completions can be added to your shell
config:
[All completions are available on the Task repository](https://github.com/go-task/task/tree/main/completion).
### Option 1. Load the completions in your shell's startup config (Recommended)
### Bash
This method loads the completion script from the currently installed version of
task every time you create a new shell. This ensures that your completions are
always up-to-date.
First, ensure that you installed bash-completion using your package manager.
<Tabs values={[ {label: 'bash', value: '1'}, {label: 'zsh', value: '2'},
{label: 'fish', value: '3'},
{label: 'powershell', value: '4'}
]}>
Make the completion file executable:
<TabItem value="1">
```shell title="~/.bashrc"
eval "$(task --completion bash)"
```
</TabItem>
<TabItem value="2">
```shell title="~/.zshrc"
eval "$(task --completion zsh)"
```
</TabItem>
<TabItem value="3">
```shell title="~/.config/fish/config.fish"
task --completion fish | source
```
</TabItem>
<TabItem value="4">
```powershell title="$PROFILE\Microsoft.PowerShell_profile.ps1"
Invoke-Expression (&task --completion powershell)
```
</TabItem></Tabs>
### Option 2. Copy the script to your shell's completions directory
This method requires you to manually update the completions whenever Task is
updated. However, it is useful if you want to modify the completions yourself.
<Tabs
values={[
{label: 'bash', value: '1'},
{label: 'zsh', value: '2'},
{label: 'fish', value: '3'}
]}>
<TabItem value="1">
```shell
chmod +x path/to/task.bash
task --completion bash > /etc/bash_completion.d/task
```
</TabItem>
After, add this to your `~/.bash_profile`:
<TabItem value="2">
```shell
source path/to/task.bash
task --completion zsh > /usr/local/share/zsh/site-functions/_task
```
</TabItem>
### ZSH
Put the `_task` file somewhere in your `$FPATH`:
<TabItem value="3">
```shell
mv path/to/_task /usr/local/share/zsh/site-functions/_task
```
Ensure that the following is present in your `~/.zshrc`:
```shell
autoload -U compinit
compinit -i
```
ZSH version 5.7 or later is recommended.
### Fish
Move the `task.fish` completion script:
```shell
mv path/to/task.fish ~/.config/fish/completions/task.fish
```
### PowerShell
Open your profile script with:
```powershell
mkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue
notepad $profile
```
Add the line and save the file:
```shell
Invoke-Expression -Command path/to/task.ps1
task --completion fish > ~/.config/fish/completions/task.fish
```
</TabItem></Tabs>
{/* prettier-ignore-start */}
[go]: https://golang.org/

View File

@@ -8,7 +8,7 @@ toc_max_heading_level: 5
# Schema Reference
| Attribute | Type | Default | Description |
| ---------- | ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|------------|------------------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `version` | `string` | | Version of the Taskfile. The current version is `3`. |
| `output` | `string` | `interleaved` | Output mode. Available options: `interleaved`, `group` and `prefixed`. |
| `method` | `string` | `checksum` | Default method in this Taskfile. Can be overridden in a task by task basis. Available options: `checksum`, `timestamp` and `none`. |
@@ -26,10 +26,11 @@ toc_max_heading_level: 5
## Include
| Attribute | Type | Default | Description |
| ---------- | --------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|------------|-----------------------|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `taskfile` | `string` | | The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile. |
| `dir` | `string` | The parent Taskfile directory | The working directory of the included tasks when run. |
| `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. |
| `flatten` | `bool` | `false` | If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, an error will be thrown. |
| `internal` | `bool` | `false` | Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`. |
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
| `vars` | `map[string]Variable` | | A set of variables to apply to the included Taskfile. |
@@ -106,7 +107,7 @@ vars:
| `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. |
| `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/internal/syslist/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). |
@@ -140,7 +141,7 @@ 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. |
| `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/internal/syslist/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). |

View File

@@ -101,10 +101,13 @@ engine. If you define a variable with the same name as a special variable, the
special variable will be overridden.
| Var | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI. |
| `CLI_FORCE` | A boolean containing whether the `--force` or `--force-all` flags were set. |
| `CLI_SILENT` | A boolean containing whether the `--silent` flag was set. |
| `CLI_VERBOSE` | A boolean containing whether the `--verbose` flag was set. |
| `TASK` | The name of the current task. |
| `ALIAS` | The alias used for the current task, otherwise matches `TASK`. |
| `TASK_EXE` | The Task executable name or path. |
| `ROOT_TASKFILE` | The absolute path of the root Taskfile. |
| `ROOT_DIR` | The absolute path of the root Taskfile directory. |
@@ -115,6 +118,7 @@ special variable will be overridden.
| `TIMESTAMP` | The date object of the greatest timestamp of the files listed in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
| `TASK_VERSION` | The current version of task. |
| `ITEM` | The value of the current iteration when using the `for` property. Can be changed to a different variable name using `as:`. |
| `EXIT_CODE` | Available exclusively inside the `defer:` command. Contains the failed command exit code. Only set when non-zero. |
## Functions

View File

@@ -334,6 +334,117 @@ includes:
internal: true
```
### Flatten includes
You can flatten the included Taskfile tasks into the main Taskfile by using the `flatten` option.
It means that the included Taskfile tasks will be available without the namespace.
<Tabs defaultValue="1"
values={[
{label: 'Taskfile.yml', value: '1'},
{label: 'Included.yml', value: '2'}
]}>
<TabItem value="1">
```yaml
version: '3'
includes:
lib:
taskfile: ./Included.yml
flatten: true
tasks:
greet:
cmds:
- echo "Greet"
- task: foo
```
</TabItem>
<TabItem value="2">
```yaml
version: '3'
tasks:
foo:
cmds:
- echo "Foo"
```
</TabItem></Tabs>
If you run `task -a` it will print :
```sh
task: Available tasks for this project:
* greet:
* foo
```
You can run `task foo` directly without the namespace.
You can also reference the task in other tasks without the namespace. So if you run `task greet` it will run `greet` and `foo` tasks and the output will be :
```text
```
If multiple tasks have the same name, an error will be thrown:
<Tabs defaultValue="1"
values={[
{label: 'Taskfile.yml', value: '1'},
{label: 'Included.yml', value: '2'}
]}>
<TabItem value="1">
```yaml
version: '3'
includes:
lib:
taskfile: ./Included.yml
flatten: true
tasks:
greet:
cmds:
- echo "Greet"
- task: foo
```
</TabItem>
<TabItem value="2">
```yaml
version: '3'
tasks:
greet:
cmds:
- echo "Foo"
```
</TabItem></Tabs>
If you run `task -a` it will print:
```text
task: Found multiple tasks (greet) included by "lib"
```
### Vars of included Taskfiles
You can also specify variables when including a Taskfile. This may be useful for
@@ -506,7 +617,7 @@ 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).
[here](https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go).
The `build-windows` task below will run only on Windows, and on any
architecture:
@@ -1181,14 +1292,14 @@ tasks:
ref: index .FOO 0 # <-- The element at index 0 is passed by reference to bar
bar:
cmds:
- 'echo {{.MYVAR}}' # <-- FOO is just the letter 'A'
- 'echo {{.FOO}}' # <-- FOO is just the letter 'A'
```
## Looping over values
As of v3.28.0, Task allows you to loop over certain values and execute a command
for each. There are a number of ways to do this depending on the type of value
you want to loop over.
Task allows you to loop over certain values and execute a command for each.
There are a number of ways to do this depending on the type of value you want to
loop over.
### Looping over a static list
@@ -1205,6 +1316,37 @@ tasks:
cmd: cat {{ .ITEM }}
```
### Looping over a matrix
If you need to loop over all permutations of multiple lists, you can use the
`matrix` property. This should be familiar to anyone who has used a matrix in a
CI/CD pipeline.
```yaml
version: '3'
tasks:
default:
silent: true
cmds:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
```
This will output:
```txt
windows/amd64
windows/arm64
linux/amd64
linux/arm64
darwin/amd64
darwin/arm64
```
### Looping over your task's sources
You are also able to loop over the sources of your task:
@@ -1228,7 +1370,7 @@ match that glob.
Source paths will always be returned as paths relative to the task directory. If
you need to convert this to an absolute path, you can use the built-in
`joinPath` function. There are some [special variables](/api/#special-variables)
`joinPath` function. There are some [special variables](/reference/templating/#special-variables)
that you may find useful for this.
```yaml
@@ -1520,6 +1662,20 @@ commands are executed in the reverse order if you schedule multiple of them.
:::
A special variable `.EXIT_CODE` is exposed when a command exited with a non-zero
exit code. You can check its presence to know if the task completed successfully
or not:
```yaml
version: '3'
tasks:
default:
cmds:
- defer: echo '{{if .EXIT_CODE}}Failed with {{.EXIT_CODE}}!{{else}}Success!{{end}}'
- exit 1
```
## Help
Running `task --list` (or `task -l`) lists all tasks with a description. The

View File

@@ -19,11 +19,12 @@ the website homepage and on the GitHub repository README. Make contact with
## GitHub Sponsors
The preferred way to donate to the maintainers is via GitHub Sponsors. Just use
the following links to do your donation. We suggest a 50/50 split to each
the following links to do your donation. We suggest a equal weight split to each
maintainer of the total amount you plan to donate to the project.
- [@andreynering](https://github.com/sponsors/andreynering)
- [@pd93](https://github.com/sponsors/pd93)
- [@vmaerten](https://github.com/sponsors/vmaerten)
## Open Collective

View File

@@ -431,6 +431,9 @@
},
{
"$ref": "#/definitions/for_var"
},
{
"$ref": "#/definitions/for_matrix"
}
]
},
@@ -467,6 +470,12 @@
"additionalProperties": false,
"required": ["var"]
},
"for_matrix": {
"description": "A matrix of values to iterate over",
"type": "object",
"additionalProperties": true,
"required": ["matrix"]
},
"precondition": {
"anyOf": [
{
@@ -610,6 +619,10 @@
"description": "If `true`, no errors will be thrown if the specified file does not exist.",
"type": "boolean"
},
"flatten": {
"description": "If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, an error will be thrown.",
"type": "boolean"
},
"internal": {
"description": "Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`.",
"type": "boolean"

View File

@@ -5,6 +5,37 @@ sidebar_position: 14
# Changelog
## v3.39.0 - 2024-09-07
- Added
[Env Precedence Experiment](https://taskfile.dev/experiments/env-precedence)
(#1038, #1633 by @vmaerten).
- Added a CI lint job to ensure that the docs are updated correctly (#1719 by
@vmaerten).
- Updated minimum required Go version to 1.22 (#1758 by @pd93).
- Expose a new `EXIT_CODE` special variable on `defer:` when a command finishes
with a non-zero exit code (#1484, #1762 by @dorimon-1 and @andreynering).
- Expose a new `ALIAS` special variable, which will contain the alias used to
call the current task. Falls back to the task name. (#1764 by @DanStory).
- Fixed `TASK_REMOTE_DIR` environment variable not working when the path was
absolute. (#1715 by @vmaerten).
- Added an option to declare an included Taskfile as flattened (#1704 by
@vmaerten).
- Added a new
[`--completion` flag](https://taskfile.dev/installation/#setup-completions) to
output completion scripts for various shells (#293, #1157 by @pd93).
- This is now the preferred way to install completions.
- The completion scripts in the `completion` directory
[are now deprecated](https://taskfile.dev/deprecations/completion-scripts/).
- Added the ability to
[loop over a matrix of values](https://taskfile.dev/usage/#looping-over-a-matrix)
(#1766, #1767, #1784 by @pd93).
- Fixed a bug in fish completion where aliases were not displayed (#1781, #1782
by @vmaerten).
- Fixed panic when having a flattened included Taskfile that contains a
`default` task (#1777, #1778 by @vmaerten).
- Optimized file existence checks for remote Taskfiles (#1713 by @vmaerten).
## v3.38.0 - 2024-06-30
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).

View File

@@ -0,0 +1,25 @@
---
slug: /deprecations/completion-scripts/
---
# Completion Scripts
:::warning
This deprecation breaks the following functionality:
- Any direct references to the completion scripts in the Task git repository
:::
Direct use of the completion scripts in the `completion/*` directory of the
[github.com/go-task/task][task] Git repository is deprecated. Any shell
configuration that directly refers to these scripts will potentially break in
the future as the scripts may be moved or deleted entirely. Any configuration
should be updated to use the [new method for generating shell
completions][completions] instead.
{/* prettier-ignore-start */}
[completions]: ../installation.mdx#setup-completions
[task]: https://github.com/go-task/task
{/* prettier-ignore-end */}

View File

@@ -0,0 +1,74 @@
---
slug: '/experiments/env-precedence'
---
# Env Precedence (#1038)
:::caution
All experimental features are subject to breaking changes and/or removal _at any
time_. We strongly recommend that you do not use these features in a production
environment. They are intended for testing and feedback only.
:::
:::warning
This experiment breaks the following functionality:
- environment variable will take precedence over OS environment variables
:::
:::info
To enable this experiment, set the environment variable:
`TASK_X_ENV_PRECEDENCE=1`. Check out [our guide to enabling
experiments][enabling-experiments] for more information.
:::
Before this experiment, the OS variable took precedence over the task
environment variable. This experiment changes the precedence to make the task
environment variable take precedence over the OS variable.
Consider the following example:
```yml
version: '3'
tasks:
default:
env:
KEY: 'other'
cmds:
- echo "$KEY"
```
Running `KEY=some task` before this experiment, the output would be `some`, but
after this experiment, the output would be `other`.
If you still want to get the OS variable, you can use the template function env
like follow : `{{env "OS_VAR"}}`.
```yml
version: '3'
tasks:
default:
env:
KEY: 'other'
cmds:
- echo "$KEY"
- echo {{env "KEY"}}
```
Running `KEY=some task`, the output would be `other` and `some`.
Like other variables/envs, you can also fall back to a given value using the
default template function:
```yml
MY_ENV: '{{.MY_ENV | default "fallback"}}'
```
{/* prettier-ignore-start */}
[enabling-experiments]: ./experiments.mdx#enabling-experiments
{/* prettier-ignore-end */}

View File

@@ -3,6 +3,9 @@ slug: /installation/
sidebar_position: 2
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Installation
Task offers many installation methods. Check out the available methods below.
@@ -209,7 +212,7 @@ If you want to install Task in GitHub Actions you can try using
```yaml
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -247,65 +250,76 @@ released binary.
## Setup completions
Download the autocompletion file corresponding to your shell.
Some installation methods will automatically install completions too, but if
this isn't working for you or your chosen method doesn't include them, you can
run `task --completion <shell>` to output a completion script for any supported
shell. There are a couple of ways these completions can be added to your shell
config:
[All completions are available on the Task repository](https://github.com/go-task/task/tree/main/completion).
### Option 1. Load the completions in your shell's startup config (Recommended)
### Bash
This method loads the completion script from the currently installed version of
task every time you create a new shell. This ensures that your completions are
always up-to-date.
First, ensure that you installed bash-completion using your package manager.
<Tabs values={[ {label: 'bash', value: '1'}, {label: 'zsh', value: '2'},
{label: 'fish', value: '3'},
{label: 'powershell', value: '4'}
]}>
Make the completion file executable:
<TabItem value="1">
```shell title="~/.bashrc"
eval "$(task --completion bash)"
```
</TabItem>
<TabItem value="2">
```shell title="~/.zshrc"
eval "$(task --completion zsh)"
```
</TabItem>
<TabItem value="3">
```shell title="~/.config/fish/config.fish"
task --completion fish | source
```
</TabItem>
<TabItem value="4">
```powershell title="$PROFILE\Microsoft.PowerShell_profile.ps1"
Invoke-Expression (&task --completion powershell)
```
</TabItem></Tabs>
### Option 2. Copy the script to your shell's completions directory
This method requires you to manually update the completions whenever Task is
updated. However, it is useful if you want to modify the completions yourself.
<Tabs
values={[
{label: 'bash', value: '1'},
{label: 'zsh', value: '2'},
{label: 'fish', value: '3'}
]}>
<TabItem value="1">
```shell
chmod +x path/to/task.bash
task --completion bash > /etc/bash_completion.d/task
```
</TabItem>
After, add this to your `~/.bash_profile`:
<TabItem value="2">
```shell
source path/to/task.bash
task --completion zsh > /usr/local/share/zsh/site-functions/_task
```
</TabItem>
### ZSH
Put the `_task` file somewhere in your `$FPATH`:
<TabItem value="3">
```shell
mv path/to/_task /usr/local/share/zsh/site-functions/_task
```
Ensure that the following is present in your `~/.zshrc`:
```shell
autoload -U compinit
compinit -i
```
ZSH version 5.7 or later is recommended.
### Fish
Move the `task.fish` completion script:
```shell
mv path/to/task.fish ~/.config/fish/completions/task.fish
```
### PowerShell
Open your profile script with:
```powershell
mkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue
notepad $profile
```
Add the line and save the file:
```shell
Invoke-Expression -Command path/to/task.ps1
task --completion fish > ~/.config/fish/completions/task.fish
```
</TabItem></Tabs>
{/* prettier-ignore-start */}
[go]: https://golang.org/

View File

@@ -8,7 +8,7 @@ toc_max_heading_level: 5
# Schema Reference
| Attribute | Type | Default | Description |
| ---------- | ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|------------|------------------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `version` | `string` | | Version of the Taskfile. The current version is `3`. |
| `output` | `string` | `interleaved` | Output mode. Available options: `interleaved`, `group` and `prefixed`. |
| `method` | `string` | `checksum` | Default method in this Taskfile. Can be overridden in a task by task basis. Available options: `checksum`, `timestamp` and `none`. |
@@ -26,10 +26,11 @@ toc_max_heading_level: 5
## Include
| Attribute | Type | Default | Description |
| ---------- | --------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|------------|-----------------------|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `taskfile` | `string` | | The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile. |
| `dir` | `string` | The parent Taskfile directory | The working directory of the included tasks when run. |
| `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. |
| `flatten` | `bool` | `false` | If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, an error will be thrown. |
| `internal` | `bool` | `false` | Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`. |
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
| `vars` | `map[string]Variable` | | A set of variables to apply to the included Taskfile. |
@@ -106,7 +107,7 @@ vars:
| `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. |
| `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/internal/syslist/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). |
@@ -140,7 +141,7 @@ 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. |
| `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/internal/syslist/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). |

View File

@@ -101,10 +101,13 @@ engine. If you define a variable with the same name as a special variable, the
special variable will be overridden.
| Var | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI. |
| `CLI_FORCE` | A boolean containing whether the `--force` or `--force-all` flags were set. |
| `CLI_SILENT` | A boolean containing whether the `--silent` flag was set. |
| `CLI_VERBOSE` | A boolean containing whether the `--verbose` flag was set. |
| `TASK` | The name of the current task. |
| `ALIAS` | The alias used for the current task, otherwise matches `TASK`. |
| `TASK_EXE` | The Task executable name or path. |
| `ROOT_TASKFILE` | The absolute path of the root Taskfile. |
| `ROOT_DIR` | The absolute path of the root Taskfile directory. |
@@ -115,6 +118,7 @@ special variable will be overridden.
| `TIMESTAMP` | The date object of the greatest timestamp of the files listed in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
| `TASK_VERSION` | The current version of task. |
| `ITEM` | The value of the current iteration when using the `for` property. Can be changed to a different variable name using `as:`. |
| `EXIT_CODE` | Available exclusively inside the `defer:` command. Contains the failed command exit code. Only set when non-zero. |
## Functions

View File

@@ -334,6 +334,117 @@ includes:
internal: true
```
### Flatten includes
You can flatten the included Taskfile tasks into the main Taskfile by using the `flatten` option.
It means that the included Taskfile tasks will be available without the namespace.
<Tabs defaultValue="1"
values={[
{label: 'Taskfile.yml', value: '1'},
{label: 'Included.yml', value: '2'}
]}>
<TabItem value="1">
```yaml
version: '3'
includes:
lib:
taskfile: ./Included.yml
flatten: true
tasks:
greet:
cmds:
- echo "Greet"
- task: foo
```
</TabItem>
<TabItem value="2">
```yaml
version: '3'
tasks:
foo:
cmds:
- echo "Foo"
```
</TabItem></Tabs>
If you run `task -a` it will print :
```sh
task: Available tasks for this project:
* greet:
* foo
```
You can run `task foo` directly without the namespace.
You can also reference the task in other tasks without the namespace. So if you run `task greet` it will run `greet` and `foo` tasks and the output will be :
```text
```
If multiple tasks have the same name, an error will be thrown:
<Tabs defaultValue="1"
values={[
{label: 'Taskfile.yml', value: '1'},
{label: 'Included.yml', value: '2'}
]}>
<TabItem value="1">
```yaml
version: '3'
includes:
lib:
taskfile: ./Included.yml
flatten: true
tasks:
greet:
cmds:
- echo "Greet"
- task: foo
```
</TabItem>
<TabItem value="2">
```yaml
version: '3'
tasks:
greet:
cmds:
- echo "Foo"
```
</TabItem></Tabs>
If you run `task -a` it will print:
```text
task: Found multiple tasks (greet) included by "lib"
```
### Vars of included Taskfiles
You can also specify variables when including a Taskfile. This may be useful for
@@ -506,7 +617,7 @@ 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).
[here](https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go).
The `build-windows` task below will run only on Windows, and on any
architecture:
@@ -1181,14 +1292,14 @@ tasks:
ref: index .FOO 0 # <-- The element at index 0 is passed by reference to bar
bar:
cmds:
- 'echo {{.MYVAR}}' # <-- FOO is just the letter 'A'
- 'echo {{.FOO}}' # <-- FOO is just the letter 'A'
```
## Looping over values
As of v3.28.0, Task allows you to loop over certain values and execute a command
for each. There are a number of ways to do this depending on the type of value
you want to loop over.
Task allows you to loop over certain values and execute a command for each.
There are a number of ways to do this depending on the type of value you want to
loop over.
### Looping over a static list
@@ -1205,6 +1316,37 @@ tasks:
cmd: cat {{ .ITEM }}
```
### Looping over a matrix
If you need to loop over all permutations of multiple lists, you can use the
`matrix` property. This should be familiar to anyone who has used a matrix in a
CI/CD pipeline.
```yaml
version: '3'
tasks:
default:
silent: true
cmds:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
```
This will output:
```txt
windows/amd64
windows/arm64
linux/amd64
linux/arm64
darwin/amd64
darwin/arm64
```
### Looping over your task's sources
You are also able to loop over the sources of your task:
@@ -1228,7 +1370,7 @@ match that glob.
Source paths will always be returned as paths relative to the task directory. If
you need to convert this to an absolute path, you can use the built-in
`joinPath` function. There are some [special variables](/api/#special-variables)
`joinPath` function. There are some [special variables](/reference/templating/#special-variables)
that you may find useful for this.
```yaml
@@ -1520,6 +1662,20 @@ commands are executed in the reverse order if you schedule multiple of them.
:::
A special variable `.EXIT_CODE` is exposed when a command exited with a non-zero
exit code. You can check its presence to know if the task completed successfully
or not:
```yaml
version: '3'
tasks:
default:
cmds:
- defer: echo '{{if .EXIT_CODE}}Failed with {{.EXIT_CODE}}!{{else}}Success!{{end}}'
- exit 1
```
## Help
Running `task --list` (or `task -l`) lists all tasks with a description. The

View File

@@ -2048,22 +2048,6 @@
dependencies:
"@types/ms" "*"
"@types/eslint-scope@^3.7.3":
version "3.7.7"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5"
integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==
dependencies:
"@types/eslint" "*"
"@types/estree" "*"
"@types/eslint@*":
version "8.56.7"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.7.tgz#c33b5b5a9cfb66881beb7b5be6c34aa3e81d3366"
integrity sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==
dependencies:
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree-jsx@^1.0.0":
version "1.0.3"
resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.3.tgz"
@@ -2148,16 +2132,16 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/json-schema@*", "@types/json-schema@^7.0.8":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/json-schema@^7.0.8":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/mdast@^4.0.0", "@types/mdast@^4.0.2":
version "4.0.3"
resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz"
@@ -2479,10 +2463,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
acorn-import-assertions@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
acorn-import-attributes@^1.9.5:
version "1.9.5"
resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
acorn-jsx@^5.0.0:
version "5.3.2"
@@ -2692,11 +2676,11 @@ autoprefixer@^10.4.12, autoprefixer@^10.4.14:
postcss-value-parser "^4.2.0"
axios@^1:
version "1.6.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2"
integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==
version "1.7.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2"
integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==
dependencies:
follow-redirects "^1.15.0"
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
@@ -2833,7 +2817,7 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^3.0.2, braces@~3.0.2:
braces@^3.0.3, braces@~3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
@@ -3786,10 +3770,10 @@ encodeurl@~1.0.2:
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
enhanced-resolve@^5.16.0:
version "5.16.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787"
integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==
enhanced-resolve@^5.17.1:
version "5.17.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -4152,7 +4136,7 @@ flat@^5.0.2:
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
follow-redirects@^1.0.0, follow-redirects@^1.15.0:
follow-redirects@^1.0.0, follow-redirects@^1.15.6:
version "1.15.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
@@ -6006,11 +5990,11 @@ micromark@^4.0.0:
micromark-util-types "^2.0.0"
micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
version "4.0.5"
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies:
braces "^3.0.2"
braces "^3.0.3"
picomatch "^2.3.1"
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
@@ -8429,20 +8413,19 @@ webpack-sources@^3.2.3:
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.88.1:
version "5.91.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9"
integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==
version "5.94.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"
integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.5"
"@webassemblyjs/ast" "^1.12.1"
"@webassemblyjs/wasm-edit" "^1.12.1"
"@webassemblyjs/wasm-parser" "^1.12.1"
acorn "^8.7.1"
acorn-import-assertions "^1.9.0"
acorn-import-attributes "^1.9.5"
browserslist "^4.21.10"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.16.0"
enhanced-resolve "^5.17.1"
es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"