mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58ab26c4ab | ||
|
|
65d332dfd0 | ||
|
|
5eaf0b2dcd | ||
|
|
56f3735b38 | ||
|
|
23d578ac8c | ||
|
|
1bf850592c | ||
|
|
0be05795b9 | ||
|
|
08a2a91180 | ||
|
|
84cc5e57b0 | ||
|
|
5aa68e47e5 | ||
|
|
15aa4b86af | ||
|
|
114d5e1404 | ||
|
|
8ab5fe0e80 | ||
|
|
c89a6add48 | ||
|
|
888071e234 | ||
|
|
ff2e0f846a | ||
|
|
3c177d3fdc | ||
|
|
41bc490e0f | ||
|
|
f8e3742d11 | ||
|
|
a6100b39f8 | ||
|
|
1275ab1b5b | ||
|
|
0c05dcbe0f | ||
|
|
e9983e299f | ||
|
|
a450f2daea | ||
|
|
c77c8a419b | ||
|
|
a233b52c65 | ||
|
|
0e2c9cc88f | ||
|
|
dd9cec611a | ||
|
|
6985413f93 | ||
|
|
cf77768c82 | ||
|
|
6c3b13b676 | ||
|
|
ad45c7aeb3 | ||
|
|
e4b4d04abd | ||
|
|
a3bdb6c40a | ||
|
|
eb39dd94d0 | ||
|
|
21cd573770 | ||
|
|
281d259e6e | ||
|
|
1cb5daf73e | ||
|
|
3747b2ab7f | ||
|
|
d727ef5393 | ||
|
|
a72b65b3b2 | ||
|
|
ef3b853728 | ||
|
|
f302b50519 | ||
|
|
c243b0ec7e | ||
|
|
32158dac87 | ||
|
|
0a59890a46 | ||
|
|
defbcf6acd | ||
|
|
045d054a5f | ||
|
|
0941de3318 | ||
|
|
b259edeb65 | ||
|
|
35119c12ab | ||
|
|
f6ff775d11 | ||
|
|
5e9851f42f | ||
|
|
51c569ef37 | ||
|
|
1ca432a80d | ||
|
|
e781b3d4e0 | ||
|
|
81ff1cdea0 | ||
|
|
1f2cbfb932 | ||
|
|
4b6c79aca5 | ||
|
|
5739495739 | ||
|
|
9d72fa3250 | ||
|
|
4123ffc780 | ||
|
|
cdafc67bef | ||
|
|
9ee4f21d62 | ||
|
|
133086d647 | ||
|
|
88b095020e | ||
|
|
cc14996b71 | ||
|
|
375106c988 | ||
|
|
6ce6a38899 | ||
|
|
76030c9146 | ||
|
|
a71020eab5 | ||
|
|
6bef2ff8a9 | ||
|
|
413dcd28a8 | ||
|
|
da6f5c66a0 | ||
|
|
6012da7a21 | ||
|
|
46c5eafe35 | ||
|
|
830b745112 | ||
|
|
b52d4e4f40 | ||
|
|
3aaa3223a0 | ||
|
|
a9ff58d0fe | ||
|
|
eeaebaf8c7 | ||
|
|
2213141fcb | ||
|
|
19956889a7 | ||
|
|
4c580ebf18 | ||
|
|
3dccde270a | ||
|
|
53dd0b138a | ||
|
|
ea85909e8b | ||
|
|
6bf6fe7ead | ||
|
|
f39c6352ac | ||
|
|
4294cc92b9 | ||
|
|
40d77156df | ||
|
|
856ba3b8c2 | ||
|
|
0810ef01b0 | ||
|
|
527bbc3bf5 | ||
|
|
912bbcab8e | ||
|
|
aa45491510 | ||
|
|
1e25ceab29 | ||
|
|
a74b0bc679 | ||
|
|
a3fce1c302 | ||
|
|
7958cf50b3 | ||
|
|
b0efbad591 | ||
|
|
30e9c7d4cd | ||
|
|
baa5e2c378 | ||
|
|
cc97e2da1d | ||
|
|
a55e21bbb7 | ||
|
|
8d138a5eea | ||
|
|
635e3f4e7d | ||
|
|
252d549e3f | ||
|
|
182d43e8d8 | ||
|
|
f35e51e4e5 | ||
|
|
fb3c64c46e | ||
|
|
7535467f45 | ||
|
|
3e5cd6cdfd | ||
|
|
dcc060af89 | ||
|
|
55593090fa | ||
|
|
57c094f415 | ||
|
|
2f4876b71c | ||
|
|
725f929778 | ||
|
|
8266b28b48 | ||
|
|
f5c7472f64 | ||
|
|
ced3e7a579 | ||
|
|
36dd71b122 | ||
|
|
21531b6291 | ||
|
|
bfc9d7847d | ||
|
|
3397f2855f | ||
|
|
78a69c4c3e | ||
|
|
01716f55b3 | ||
|
|
ca364c20bb | ||
|
|
ee901fe568 | ||
|
|
7fa06eedf4 | ||
|
|
651033c5a7 | ||
|
|
17f6e816d8 | ||
|
|
cd259a741f | ||
|
|
c81dbda157 | ||
|
|
e23ef818ea | ||
|
|
ddd9964db7 | ||
|
|
a5b949f5dc | ||
|
|
630e58767b | ||
|
|
d87e5de56f | ||
|
|
f75aa1f84b | ||
|
|
53235f07ad | ||
|
|
f19c520f23 | ||
|
|
6951e5cd0c | ||
|
|
24059a4b76 | ||
|
|
fa022be1f9 | ||
|
|
a3b9554efd | ||
|
|
16070c7a24 | ||
|
|
72d9671fcf | ||
|
|
d01b3c8979 | ||
|
|
4024b4fa37 | ||
|
|
54c7f35b00 | ||
|
|
3efb437c9a | ||
|
|
e9448bd4be | ||
|
|
8f3180a9fa | ||
|
|
1d230af90d | ||
|
|
fb9f6c20ab | ||
|
|
6854b4c300 | ||
|
|
b10c573270 | ||
|
|
6ecfb634d2 | ||
|
|
6b3f8e29bb | ||
|
|
220bf74a9e | ||
|
|
0a027df50d | ||
|
|
a50580b5a1 | ||
|
|
1890722b75 | ||
|
|
1ff618cc17 | ||
|
|
eb2783fcce |
@@ -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
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +1,2 @@
|
||||
* text=auto
|
||||
*.mdx -linguist-detectable
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
||||
github: [andreynering, pd93]
|
||||
github: [andreynering, pd93, vmaerten]
|
||||
open_collective: task
|
||||
custom: https://taskfile.dev/donate/
|
||||
|
||||
14
.github/pull_request_template.md
vendored
14
.github/pull_request_template.md
vendored
@@ -1,5 +1,9 @@
|
||||
> Thanks for your pull request, we really appreciate contributions!
|
||||
>
|
||||
> Please understand that it may take some time to be reviewed.
|
||||
>
|
||||
> Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
|
||||
<!--
|
||||
|
||||
Thanks for your pull request, we really appreciate contributions!
|
||||
|
||||
Please understand that it may take some time to be reviewed.
|
||||
|
||||
Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
|
||||
|
||||
-->
|
||||
|
||||
48
.github/renovate.json
vendored
Normal file
48
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
"group:allNonMajor",
|
||||
"schedule:monthly"
|
||||
],
|
||||
"mode": "full",
|
||||
"reviewers": ["team:developer"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
"groupName": "Github Action",
|
||||
"labels": ["area: github actions", "area: dependencies"],
|
||||
"matchPackageNames": [
|
||||
"*"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["npm", "nvm"],
|
||||
"groupName": "Website",
|
||||
"labels": ["lang: javascript", "area: dependencies"],
|
||||
"matchPackageNames": [
|
||||
"*"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["gomod"],
|
||||
"groupName": "golang",
|
||||
"labels": ["lang: go", "area: dependencies"],
|
||||
"matchPackageNames": [
|
||||
"*"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
27
.github/workflows/lint.yml
vendored
27
.github/workflows/lint.yml
vendored
@@ -13,19 +13,19 @@ 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@v3
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.55.2
|
||||
version: v1.60.1
|
||||
|
||||
lint-jsonschema:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -34,10 +34,25 @@ jobs:
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: install check-jsonschema
|
||||
run: python -m pip install 'check-jsonschema==0.27.3'
|
||||
|
||||
- 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.')
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -10,12 +10,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21.x
|
||||
go-version: 1.22.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -13,18 +13,18 @@ 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:
|
||||
- name: Set up Go ${{matrix.go-version}}
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
|
||||
38
.github/workflows/upload-source-documents.yml
vendored
38
.github/workflows/upload-source-documents.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Upload Source Documents
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
push_files_to_crowdin:
|
||||
name: Push files to Crowdin
|
||||
if: github.repository == 'go-task/task'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Verify changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: |
|
||||
website/docs
|
||||
website/blog
|
||||
website/i18n/en
|
||||
website/src/pages
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload source documents
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: task crowdin:push
|
||||
env:
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
working-directory: ./website
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -10,6 +10,9 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Graphvis files
|
||||
*.gv
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
@@ -21,8 +24,7 @@ dist/
|
||||
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/*
|
||||
!.vscode/*-sample.json
|
||||
.vscode/settings.json
|
||||
.fleet/
|
||||
|
||||
# exuberant ctags
|
||||
|
||||
@@ -11,7 +11,7 @@ linters:
|
||||
|
||||
linters-settings:
|
||||
goimports:
|
||||
local-prefixes: github.com/go-task/task
|
||||
local-prefixes: github.com/go-task
|
||||
gofmt:
|
||||
rewrite-rules:
|
||||
- pattern: 'interface{}'
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- binary: task
|
||||
main: ./cmd/task
|
||||
@@ -11,11 +14,16 @@ builds:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
goarm:
|
||||
- '6'
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: '386'
|
||||
- goos: darwin
|
||||
goarch: riscv64
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
@@ -41,7 +49,7 @@ release:
|
||||
draft: true
|
||||
|
||||
snapshot:
|
||||
name_template: "{{.Tag}}"
|
||||
version_template: "{{.Tag}}"
|
||||
|
||||
checksum:
|
||||
name_template: "task_checksums.txt"
|
||||
@@ -71,7 +79,7 @@ brews:
|
||||
description: Task runner / simpler Make alternative written in Go
|
||||
license: MIT
|
||||
homepage: https://taskfile.dev
|
||||
folder: Formula
|
||||
directory: Formula
|
||||
repository:
|
||||
owner: go-task
|
||||
name: homebrew-tap
|
||||
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"editorconfig.editorconfig",
|
||||
"golang.go",
|
||||
"task.vscode-task"
|
||||
]
|
||||
}
|
||||
116
CHANGELOG.md
116
CHANGELOG.md
@@ -1,5 +1,121 @@
|
||||
# Changelog
|
||||
|
||||
## v3.39.2 - 2024-09-19
|
||||
|
||||
- Fix dynamic variables not working properly for a defer: statement (#1803,
|
||||
#1818 by @vmaerten).
|
||||
|
||||
## v3.39.1 - 2024-09-18
|
||||
|
||||
- Added Renovate configuration to automatically create PRs to keep dependencies
|
||||
up to date (#1783 by @vmaerten).
|
||||
- Fixed a bug where the help was displayed twice (#1805, #1806 by @vmaerten).
|
||||
- Fixed a bug where ZSH and PowerShell completions did not work when using the
|
||||
recommended method. (#1813, #1809 by @vmaerten and @shirayu)
|
||||
- Fix variables not working properly for a `defer:` statement (#1803, #1814 by
|
||||
@vmaerten and @andreynering).
|
||||
|
||||
## 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).
|
||||
- Some YAML parsing errors will now show in a more user friendly way (#1619 by
|
||||
@pd93).
|
||||
- Prefixed outputs will now be colorized by default (#1572 by
|
||||
@AlexanderArvidsson)
|
||||
- [References](https://taskfile.dev/usage/#referencing-other-variables) are now
|
||||
generally available (no experiments required) (#1654 by @pd93).
|
||||
- Templating functions can now be used in references (#1645, #1654 by @pd93).
|
||||
- Added a new
|
||||
[templating reference page](https://taskfile.dev/reference/templating/) to the
|
||||
documentation (#1614, #1653 by @pd93).
|
||||
- If using the
|
||||
[Map Variables experiment (1)](https://taskfile.dev/experiments/map-variables/?proposal=1),
|
||||
references are available by
|
||||
[prefixing a string with a `#`](https://taskfile.dev/experiments/map-variables/?proposal=1#references)
|
||||
(#1654 by @pd93).
|
||||
- If using the
|
||||
[Map Variables experiment (2)](https://taskfile.dev/experiments/map-variables/?proposal=2),
|
||||
the `yaml` and `json` keys are no longer available (#1654 by @pd93).
|
||||
- Added a new `TASK_REMOTE_DIR` environment variable to configure where cached
|
||||
remote Taskfiles are stored (#1661 by @vmaerten).
|
||||
- Added a new `--clear-cache` flag to clear the cache of remote Taskfiles (#1639
|
||||
by @vmaerten).
|
||||
- Improved the readability of cached remote Taskfile filenames (#1636 by
|
||||
@vmaerten).
|
||||
- Starting releasing a binary for the `riscv64` architecture on Linux (#1699 by
|
||||
@mengzhuo).
|
||||
- Added `CLI_SILENT` and `CLI_VERBOSE` variables (#1480, #1669 by @Vince-Smith).
|
||||
- Fixed a couple of bugs with the `prompt:` feature (#1657 by @pd93).
|
||||
- Fixed JSON Schema to disallow invalid properties (#1657 by @pd93).
|
||||
- Fixed version checks not working as intended (#872, #1663 by @vmaerten).
|
||||
- Fixed a bug where included tasks were run multiple times even if `run: once`
|
||||
was set (#852, #1655 by @pd93).
|
||||
- Fixed some bugs related to column formatting in the terminal (#1350, #1637,
|
||||
#1656 by @vmaerten).
|
||||
|
||||
## v3.37.2 - 2024-05-12
|
||||
|
||||
- Fixed a bug where an empty Taskfile would cause a panic (#1648 by @pd93).
|
||||
- Fixed a bug where includes Taskfile variable were not being merged correctly
|
||||
(#1643, #1649 by @pd93).
|
||||
|
||||
## v3.37.1 - 2024-05-09
|
||||
|
||||
- Fix bug where non-string values (numbers, bools) added to `env:` weren't been
|
||||
correctly exported (#1640, #1641 by @vmaerten and @andreynering).
|
||||
|
||||
## v3.37.0 - 2024-05-08
|
||||
|
||||
- Released the
|
||||
[Any Variables experiment](https://taskfile.dev/blog/any-variables), but
|
||||
[_without support for maps_](https://github.com/go-task/task/issues/1415#issuecomment-2044756925)
|
||||
(#1415, #1547 by @pd93).
|
||||
- Refactored how Task reads, parses and merges Taskfiles using a DAG (#1563,
|
||||
#1607 by @pd93).
|
||||
- Fix a bug which stopped tasks from using `stdin` as input (#1593, #1623 by
|
||||
@pd93).
|
||||
- Fix error when a file or directory in the project contained a special char
|
||||
like `&`, `(` or `)` (#1551, #1584 by @andreynering).
|
||||
- Added alias `q` for template function `shellQuote` (#1601, #1603 by @vergenzt)
|
||||
- Added support for `~` on ZSH completions (#1613 by @jwater7).
|
||||
- Added the ability to pass variables by reference using Go template syntax when
|
||||
the
|
||||
[Map Variables experiment](https://taskfile.dev/experiments/map-variables/) is
|
||||
enabled (#1612 by @pd93).
|
||||
- Added support for environment variables in the templating engine in `includes`
|
||||
(#1610 by @vmaerten).
|
||||
|
||||
## v3.36.0 - 2024-04-08
|
||||
|
||||
- Added support for
|
||||
|
||||
50
Taskfile.yml
50
Taskfile.yml
@@ -27,7 +27,7 @@ tasks:
|
||||
- go install -v ./cmd/task
|
||||
|
||||
generate:
|
||||
desc: Runs Go generate to create mocks
|
||||
desc: Runs Mockery to create mocks
|
||||
aliases: [gen, g]
|
||||
deps: [install:mockery]
|
||||
sources:
|
||||
@@ -57,6 +57,7 @@ tasks:
|
||||
|
||||
clean:
|
||||
desc: Cleans temp files and folders
|
||||
aliases: [clear]
|
||||
cmds:
|
||||
- rm -rf dist/
|
||||
- rm -rf tmp/
|
||||
@@ -123,10 +124,53 @@ tasks:
|
||||
cmds:
|
||||
- go install github.com/goreleaser/goreleaser@latest
|
||||
|
||||
release:
|
||||
release:*:
|
||||
desc: Prepare the project for a new release
|
||||
summary: |
|
||||
This task will do the following:
|
||||
|
||||
- Update the version and date in the CHANGELOG.md file
|
||||
- Update the version in the package.json and package-lock.json files
|
||||
- Copy the latest docs to the "current" version on the website
|
||||
- Commit the changes
|
||||
- Create a new tag
|
||||
- Push the commit/tag to the repository
|
||||
- Create a GitHub release
|
||||
|
||||
To use the task, simply run "task release:<version>" where "<version>" is is one of:
|
||||
|
||||
- "major" - Bumps the major number
|
||||
- "minor" - Bumps the minor number
|
||||
- "patch" - Bumps the patch number
|
||||
- A semver compatible version number (e.g. "1.2.3")
|
||||
vars:
|
||||
VERSION:
|
||||
sh: "go run ./cmd/release --version {{index .MATCH 0}}"
|
||||
COMPLETE_MESSAGE: |
|
||||
Creating release with GoReleaser: https://github.com/go-task/task/actions/workflows/release.yml
|
||||
|
||||
Please wait for the CI to finish and then do the following:
|
||||
|
||||
- Copy the changelog for v{{.VERSION}} to the GitHub release
|
||||
- Publish the package to NPM with `task npm:publish`
|
||||
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
|
||||
preconditions:
|
||||
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
|
||||
msg: "You must be on the main branch to release"
|
||||
- sh: "[[ -z $(git diff --shortstat main) ]]"
|
||||
msg: "You must have a clean working tree to release"
|
||||
prompt: "Are you sure you want to release version {{.VERSION}}?"
|
||||
cmds:
|
||||
- go run ./cmd/release {{.CLI_ARGS}}
|
||||
- cmd: echo "Releasing v{{.VERSION}}"
|
||||
silent: true
|
||||
- "go run ./cmd/release {{.VERSION}}"
|
||||
- "git add --all"
|
||||
- "git commit -m v{{.VERSION}}"
|
||||
- "git push"
|
||||
- "git tag v{{.VERSION}}"
|
||||
- "git push origin tag v{{.VERSION}}"
|
||||
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
|
||||
silent: true
|
||||
|
||||
npm:publish:
|
||||
desc: Publish release to npm
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/otiai10/copy"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,6 +26,16 @@ var (
|
||||
versionRegex = regexp.MustCompile(`(?m)^ "version": "\d+\.\d+\.\d+",$`)
|
||||
)
|
||||
|
||||
// Flags
|
||||
var (
|
||||
versionFlag bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
pflag.BoolVarP(&versionFlag, "version", "v", false, "resolved version number")
|
||||
pflag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := release(); err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -33,7 +44,7 @@ func main() {
|
||||
}
|
||||
|
||||
func release() error {
|
||||
if len(os.Args) != 2 {
|
||||
if len(pflag.Args()) != 1 {
|
||||
return errors.New("error: expected version number")
|
||||
}
|
||||
|
||||
@@ -42,11 +53,14 @@ func release() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bumpVersion(version, os.Args[1]); err != nil {
|
||||
if err := bumpVersion(version, pflag.Arg(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(version)
|
||||
if versionFlag {
|
||||
fmt.Println(version)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := changelog(version); err != nil {
|
||||
return err
|
||||
|
||||
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
ver "github.com/go-task/task/v3/internal/version"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -44,8 +44,12 @@ func main() {
|
||||
}
|
||||
|
||||
func run() error {
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(os.Stderr)
|
||||
logger := &logger.Logger{
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Verbose: flags.Verbose,
|
||||
Color: flags.Color,
|
||||
}
|
||||
|
||||
if err := flags.Validate(); err != nil {
|
||||
return err
|
||||
@@ -55,7 +59,7 @@ func run() error {
|
||||
entrypoint := flags.Entrypoint
|
||||
|
||||
if flags.Version {
|
||||
fmt.Printf("Task version: %s\n", ver.GetVersion())
|
||||
fmt.Printf("Task version: %s\n", ver.GetVersionWithSum())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -65,26 +69,29 @@ func run() error {
|
||||
}
|
||||
|
||||
if flags.Experiments {
|
||||
l := &logger.Logger{
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Verbose: flags.Verbose,
|
||||
Color: flags.Color,
|
||||
}
|
||||
return experiments.List(l)
|
||||
return experiments.List(logger)
|
||||
}
|
||||
|
||||
if flags.Init {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
if err := task.InitTaskfile(os.Stdout, wd); err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
@@ -128,15 +135,18 @@ func run() error {
|
||||
OutputStyle: flags.Output,
|
||||
TaskSorter: taskSorter,
|
||||
}
|
||||
|
||||
listOptions := task.NewListOptions(flags.List, flags.ListAll, flags.ListJson, flags.NoStatus)
|
||||
if err := listOptions.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.Setup(); err != nil {
|
||||
err := e.Setup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if experiments.AnyVariables.Enabled {
|
||||
logger.Warnf("The 'Any Variables' experiment flag is no longer required to use non-map variable types. If you wish to use map variables, please use 'TASK_X_MAP_VARIABLES' instead. See https://github.com/go-task/task/issues/1585\n")
|
||||
}
|
||||
|
||||
// If the download flag is specified, we should stop execution as soon as
|
||||
// taskfile is downloaded
|
||||
@@ -144,6 +154,14 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.ClearCache {
|
||||
cache, err := taskfile.NewCache(e.TempDir.Remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cache.Clear()
|
||||
}
|
||||
|
||||
if (listOptions.ShouldListTasks()) && flags.Silent {
|
||||
return e.ListTaskNames(flags.ListAll)
|
||||
}
|
||||
@@ -178,7 +196,9 @@ func run() error {
|
||||
|
||||
globals.Set("CLI_ARGS", ast.Var{Value: cliArgs})
|
||||
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
e.Taskfile.Vars.Merge(globals)
|
||||
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
e.Taskfile.Vars.Merge(globals, nil)
|
||||
|
||||
if !flags.Watch {
|
||||
e.InterceptInterruptSignals()
|
||||
|
||||
34
completion.go
Normal file
34
completion.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#compdef task
|
||||
|
||||
local context state state_descr line
|
||||
compdef _task task
|
||||
typeset -A opt_args
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
||||
@@ -12,7 +11,9 @@ function __task_list() {
|
||||
local taskfile item task desc
|
||||
|
||||
cmd=(task)
|
||||
taskfile="${(v)opt_args[(i)-t|--taskfile]}"
|
||||
taskfile=${(Qv)opt_args[(i)-t|--taskfile]}
|
||||
taskfile=${taskfile//\~/$HOME}
|
||||
|
||||
|
||||
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
@@ -37,26 +38,33 @@ function __task_list() {
|
||||
_describe 'Task to run' scripts
|
||||
}
|
||||
|
||||
_arguments \
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
|
||||
'(-c --color)'{-c,--color}'[colored output]' \
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]' \
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]' \
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]' \
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
|
||||
+ '(operation)' \
|
||||
{-l,--list}'[list describable tasks]' \
|
||||
{-a,--list-all}'[list all tasks]' \
|
||||
{-i,--init}'[create new Taskfile.yml]' \
|
||||
'(-*)'{-h,--help}'[show help]' \
|
||||
'(-*)--version[show version and exit]' \
|
||||
'*: :__task_list'
|
||||
_task() {
|
||||
_arguments \
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
|
||||
'(-c --color)'{-c,--color}'[colored output]' \
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]' \
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]' \
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]' \
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
|
||||
+ '(operation)' \
|
||||
{-l,--list}'[list describable tasks]' \
|
||||
{-a,--list-all}'[list all tasks]' \
|
||||
{-i,--init}'[create new Taskfile.yml]' \
|
||||
'(-*)'{-h,--help}'[show help]' \
|
||||
'(-*)--version[show version and exit]' \
|
||||
'*: :__task_list'
|
||||
}
|
||||
|
||||
# don't run the completion function when being source-ed or eval-ed
|
||||
if [ "$funcstack[1]" = "_task" ]; then
|
||||
_task "$@"
|
||||
fi
|
||||
|
||||
179
errors/error_taskfile_decode.go
Normal file
179
errors/error_taskfile_decode.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/quick"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/fatih/color"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed themes/*.xml
|
||||
var embedded embed.FS
|
||||
|
||||
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
|
||||
|
||||
func init() {
|
||||
r, err := embedded.Open("themes/task.xml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
style, err := chroma.NewXMLStyle(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
styles.Register(style)
|
||||
}
|
||||
|
||||
type (
|
||||
TaskfileDecodeError struct {
|
||||
Message string
|
||||
Location string
|
||||
Line int
|
||||
Column int
|
||||
Tag string
|
||||
Snippet TaskfileSnippet
|
||||
Err error
|
||||
}
|
||||
TaskfileSnippet struct {
|
||||
Lines []string
|
||||
StartLine int
|
||||
EndLine int
|
||||
Padding int
|
||||
}
|
||||
)
|
||||
|
||||
func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError {
|
||||
// If the error is already a DecodeError, return it
|
||||
taskfileInvalidErr := &TaskfileDecodeError{}
|
||||
if errors.As(err, &taskfileInvalidErr) {
|
||||
return taskfileInvalidErr
|
||||
}
|
||||
return &TaskfileDecodeError{
|
||||
Line: node.Line,
|
||||
Column: node.Column,
|
||||
Tag: node.ShortTag(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Error() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Print the error message
|
||||
if err.Message != "" {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", err.Message))
|
||||
} else {
|
||||
// Extract the errors from the TypeError
|
||||
te := &yaml.TypeError{}
|
||||
if errors.As(err.Err, &te) {
|
||||
if len(te.Errors) > 1 {
|
||||
fmt.Fprintln(buf, color.RedString("errs:"))
|
||||
for _, message := range te.Errors {
|
||||
fmt.Fprintln(buf, color.RedString("- %s", extractTypeErrorMessage(message)))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", extractTypeErrorMessage(te.Errors[0])))
|
||||
}
|
||||
} else {
|
||||
// Otherwise print the error message normally
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", err.Err))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(buf, color.RedString("file: %s:%d:%d", err.Location, err.Line, err.Column))
|
||||
|
||||
// Print the snippet
|
||||
maxLineNumberDigits := digits(err.Snippet.EndLine)
|
||||
lineNumberSpacer := strings.Repeat(" ", maxLineNumberDigits)
|
||||
columnSpacer := strings.Repeat(" ", err.Column-1)
|
||||
for i, line := range err.Snippet.Lines {
|
||||
currentLine := err.Snippet.StartLine + i + 1
|
||||
|
||||
lineIndicator := " "
|
||||
if currentLine == err.Line {
|
||||
lineIndicator = ">"
|
||||
}
|
||||
columnIndicator := "^"
|
||||
|
||||
// Print each line
|
||||
lineIndicator = color.RedString(lineIndicator)
|
||||
columnIndicator = color.RedString(columnIndicator)
|
||||
lineNumberFormat := fmt.Sprintf("%%%dd", maxLineNumberDigits)
|
||||
lineNumber := fmt.Sprintf(lineNumberFormat, currentLine)
|
||||
fmt.Fprintf(buf, "%s %s | %s", lineIndicator, lineNumber, line)
|
||||
|
||||
// Print the column indicator
|
||||
if currentLine == err.Line {
|
||||
fmt.Fprintf(buf, "\n %s | %s%s", lineNumberSpacer, columnSpacer, columnIndicator)
|
||||
}
|
||||
|
||||
// If there are more lines to print, add a newline
|
||||
if i < len(err.Snippet.Lines)-1 {
|
||||
fmt.Fprintln(buf)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Code() int {
|
||||
return CodeTaskfileDecode
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithMessage(format string, a ...any) *TaskfileDecodeError {
|
||||
err.Message = fmt.Sprintf(format, a...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithTypeMessage(t string) *TaskfileDecodeError {
|
||||
err.Message = fmt.Sprintf("cannot unmarshal %s into %s", err.Tag, t)
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithFileInfo(location string, b []byte, padding int) *TaskfileDecodeError {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := quick.Highlight(buf, string(b), "yaml", "terminal", "task"); err != nil {
|
||||
buf.WriteString(string(b))
|
||||
}
|
||||
lines := strings.Split(buf.String(), "\n")
|
||||
start := max(err.Line-1-padding, 0)
|
||||
end := min(err.Line+padding, len(lines)-1)
|
||||
|
||||
err.Location = location
|
||||
err.Snippet = TaskfileSnippet{
|
||||
Lines: lines[start:end],
|
||||
StartLine: start,
|
||||
EndLine: end,
|
||||
Padding: padding,
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func extractTypeErrorMessage(message string) string {
|
||||
matches := typeErrorRegex.FindStringSubmatch(message)
|
||||
if len(matches) == 2 {
|
||||
return matches[1]
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func digits(number int) int {
|
||||
count := 0
|
||||
for number != 0 {
|
||||
number /= 10
|
||||
count += 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -12,13 +12,15 @@ const (
|
||||
const (
|
||||
CodeTaskfileNotFound int = iota + 100
|
||||
CodeTaskfileAlreadyExists
|
||||
CodeTaskfileInvalid
|
||||
CodeTaskfileDecode
|
||||
CodeTaskfileFetchFailed
|
||||
CodeTaskfileNotTrusted
|
||||
CodeTaskfileNotSecure
|
||||
CodeTaskfileCacheNotFound
|
||||
CodeTaskfileVersionCheckError
|
||||
CodeTaskfileNetworkTimeout
|
||||
CodeTaskfileInvalid
|
||||
CodeTaskfileCycle
|
||||
)
|
||||
|
||||
// Task related exit codes
|
||||
@@ -56,3 +58,8 @@ func Is(err, target error) bool {
|
||||
func As(err error, target any) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
// Unwrap wraps the standard errors.Unwrap function so that we don't need to alias that package.
|
||||
func Unwrap(err error) error {
|
||||
return errors.Unwrap(err)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -174,3 +174,21 @@ func (err *TaskfileNetworkTimeoutError) Error() string {
|
||||
func (err *TaskfileNetworkTimeoutError) Code() int {
|
||||
return CodeTaskfileNetworkTimeout
|
||||
}
|
||||
|
||||
// TaskfileCycleError is returned when we detect that a Taskfile includes a
|
||||
// set of Taskfiles that include each other in a cycle.
|
||||
type TaskfileCycleError struct {
|
||||
Source string
|
||||
Destination string
|
||||
}
|
||||
|
||||
func (err TaskfileCycleError) Error() string {
|
||||
return fmt.Sprintf("task: include cycle detected between %s <--> %s",
|
||||
err.Source,
|
||||
err.Destination,
|
||||
)
|
||||
}
|
||||
|
||||
func (err TaskfileCycleError) Code() int {
|
||||
return CodeTaskfileCycle
|
||||
}
|
||||
|
||||
17
errors/themes/task.xml
Normal file
17
errors/themes/task.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<style name="task">
|
||||
<entry type="Background" style="bg:#eee8d5"/>
|
||||
<entry type="Keyword" style="#859900"/>
|
||||
<entry type="KeywordConstant" style=""/>
|
||||
<entry type="KeywordNamespace" style="#dc322f"/>
|
||||
<entry type="KeywordType" style=""/>
|
||||
<entry type="Name" style="#268bd2"/>
|
||||
<entry type="NameBuiltin" style="#cb4b16"/>
|
||||
<entry type="NameClass" style="#cb4b16"/>
|
||||
<entry type="NameTag" style=""/>
|
||||
<entry type="Literal" style="#2aa198"/>
|
||||
<entry type="LiteralNumber" style=""/>
|
||||
<entry type="OperatorWord" style="#859900"/>
|
||||
<entry type="Comment" style="italic #93a1a1"/>
|
||||
<entry type="Generic" style="#d33682"/>
|
||||
<entry type="Text" style="#586e75"/>
|
||||
</style>
|
||||
22
go.mod
22
go.mod
@@ -1,14 +1,18 @@
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
go 1.21
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/Ladicle/tabwriter v1.0.0
|
||||
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/fatih/color v1.16.0
|
||||
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.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
|
||||
@@ -16,18 +20,20 @@ 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.19.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 (
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
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.19.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
||||
|
||||
50
go.sum
50
go.sum
@@ -1,17 +1,33 @@
|
||||
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/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.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=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
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/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.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=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
@@ -25,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=
|
||||
@@ -51,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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
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=
|
||||
|
||||
7
hash.go
7
hash.go
@@ -1,6 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-task/task/v3/internal/hash"
|
||||
@@ -8,11 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func (e *Executor) GetHash(t *ast.Task) (string, error) {
|
||||
r := t.Run
|
||||
if r == "" {
|
||||
r = e.Taskfile.Run
|
||||
}
|
||||
|
||||
r := cmp.Or(t.Run, e.Taskfile.Run)
|
||||
var h hash.HashFunc
|
||||
switch r {
|
||||
case "always":
|
||||
|
||||
35
help.go
35
help.go
@@ -7,8 +7,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/Ladicle/tabwriter"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/go-task/task/v3/internal/editors"
|
||||
@@ -105,7 +105,8 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
||||
for _, task := range tasks {
|
||||
e.Logger.FOutf(w, logger.Yellow, "* ")
|
||||
e.Logger.FOutf(w, logger.Green, task.Task)
|
||||
e.Logger.FOutf(w, logger.Default, ": \t%s", task.Desc)
|
||||
desc := strings.ReplaceAll(task.Desc, "\n", " ")
|
||||
e.Logger.FOutf(w, logger.Default, ": \t%s", desc)
|
||||
if len(task.Aliases) > 0 {
|
||||
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
||||
}
|
||||
@@ -159,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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -185,12 +184,12 @@ 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.WithTempDir(e.TempDir.Fingerprint),
|
||||
fingerprint.WithDry(e.Dry),
|
||||
fingerprint.WithLogger(e.Logger),
|
||||
)
|
||||
@@ -198,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
|
||||
})
|
||||
|
||||
@@ -3,14 +3,12 @@ package compiler
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -48,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
|
||||
}
|
||||
@@ -62,10 +60,6 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
||||
cache := &templater.Cache{Vars: result}
|
||||
// Replace values
|
||||
newVar := templater.ReplaceVar(v, cache)
|
||||
// If the variable is a reference, we can resolve it
|
||||
if newVar.Ref != "" {
|
||||
newVar.Value = result.Get(newVar.Ref).Value
|
||||
}
|
||||
// If the variable should not be evaluated, but is nil, set it to an empty string
|
||||
// This stops empty interface errors when using the templater to replace values later
|
||||
if !evaluateShVars && newVar.Value == nil {
|
||||
@@ -81,18 +75,6 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
||||
if err := cache.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Evaluate JSON
|
||||
if newVar.Json != "" {
|
||||
if err := json.Unmarshal([]byte(newVar.Json), &newVar.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Evaluate YAML
|
||||
if newVar.Yaml != "" {
|
||||
if err := yaml.Unmarshal([]byte(newVar.Yaml), &newVar.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// If the variable is not dynamic, we can set it and return
|
||||
if newVar.Value != nil || newVar.Sh == "" {
|
||||
result.Set(k, ast.Var{Value: newVar.Value})
|
||||
@@ -197,9 +179,11 @@ 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,
|
||||
"TASKFILE": t.Location.Taskfile,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
25
internal/env/env.go
vendored
25
internal/env/env.go
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -11,21 +12,27 @@ func Get(t *ast.Task) []string {
|
||||
if t.Env == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
environ := os.Environ()
|
||||
|
||||
for k, v := range t.Env.ToCacheMap() {
|
||||
str, isString := v.(string)
|
||||
if !isString {
|
||||
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=%s", k, str))
|
||||
environ = append(environ, fmt.Sprintf("%s=%v", k, v))
|
||||
}
|
||||
|
||||
return environ
|
||||
}
|
||||
|
||||
func isTypeAllowed(v any) bool {
|
||||
switch v.(type) {
|
||||
case string, bool, int, float32, float64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,19 +90,14 @@ 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) {
|
||||
s = filepath.ToSlash(s)
|
||||
s = strings.ReplaceAll(s, " ", `\ `)
|
||||
s = strings.ReplaceAll(s, "&", `\&`)
|
||||
s = strings.ReplaceAll(s, "(", `\(`)
|
||||
s = strings.ReplaceAll(s, ")", `\)`)
|
||||
fields, err := shell.Fields(s, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/Ladicle/tabwriter"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
@@ -28,6 +28,8 @@ var (
|
||||
GentleForce Experiment
|
||||
RemoteTaskfiles Experiment
|
||||
AnyVariables Experiment
|
||||
MapVariables Experiment
|
||||
EnvPrecedence Experiment
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -35,6 +37,8 @@ func init() {
|
||||
GentleForce = New("GENTLE_FORCE")
|
||||
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 {
|
||||
@@ -68,6 +72,7 @@ func getEnvFilePath() string {
|
||||
fs := pflag.NewFlagSet("experiments", pflag.ContinueOnError)
|
||||
fs.StringVarP(&dir, "dir", "d", "", "Sets directory of execution.")
|
||||
fs.StringVarP(&taskfile, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
|
||||
fs.Usage = func() {}
|
||||
_ = fs.Parse(os.Args[1:])
|
||||
// If the directory is set, find a .env file in that directory.
|
||||
if dir != "" {
|
||||
@@ -101,6 +106,7 @@ func List(l *logger.Logger) error {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, ' ', 0)
|
||||
printExperiment(w, l, GentleForce)
|
||||
printExperiment(w, l, RemoteTaskfiles)
|
||||
printExperiment(w, l, AnyVariables)
|
||||
printExperiment(w, l, MapVariables)
|
||||
printExperiment(w, l, EnvPrecedence)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package flags
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
@@ -37,6 +38,7 @@ var (
|
||||
Version bool
|
||||
Help bool
|
||||
Init bool
|
||||
Completion string
|
||||
List bool
|
||||
ListAll bool
|
||||
ListJson bool
|
||||
@@ -64,10 +66,13 @@ var (
|
||||
Experiments bool
|
||||
Download bool
|
||||
Offline bool
|
||||
ClearCache bool
|
||||
Timeout time.Duration
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(os.Stderr)
|
||||
pflag.Usage = func() {
|
||||
log.Print(usage)
|
||||
pflag.PrintDefaults()
|
||||
@@ -76,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.")
|
||||
@@ -116,6 +122,7 @@ func init() {
|
||||
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
|
||||
pflag.BoolVar(&Offline, "offline", false, "Forces Task to only use local or cached Taskfiles.")
|
||||
pflag.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
|
||||
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
||||
}
|
||||
|
||||
pflag.Parse()
|
||||
@@ -126,6 +133,10 @@ func Validate() error {
|
||||
return errors.New("task: You can't set both --download and --offline flags")
|
||||
}
|
||||
|
||||
if Download && ClearCache {
|
||||
return errors.New("task: You can't set both --download and --clear-cache flags")
|
||||
}
|
||||
|
||||
if Global && Dir != "" {
|
||||
log.Fatal("task: You can't set both --global and --dir")
|
||||
return nil
|
||||
|
||||
@@ -15,7 +15,7 @@ func Empty(*ast.Task) (string, error) {
|
||||
}
|
||||
|
||||
func Name(t *ast.Task) (string, error) {
|
||||
return t.Task, nil
|
||||
return fmt.Sprintf("%s:%s", t.Location.Taskfile, t.LocalName()), nil
|
||||
}
|
||||
|
||||
func Hash(t *ast.Task) (string, error) {
|
||||
|
||||
@@ -52,6 +52,30 @@ func Red() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_RED", color.FgRed)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightBlue() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_BLUE", color.FgHiBlue)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightGreen() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_GREEN", color.FgHiGreen)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightCyan() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_CYAN", color.FgHiCyan)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightYellow() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_YELLOW", color.FgHiYellow)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightMagenta() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_MAGENTA", color.FgHiMagenta)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightRed() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_RED", color.FgHiRed)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func envColor(env string, defaultColor color.Attribute) []color.Attribute {
|
||||
if os.Getenv("FORCE_COLOR") != "" {
|
||||
color.NoColor = false
|
||||
@@ -65,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, ";")
|
||||
}
|
||||
@@ -138,6 +162,10 @@ func (l *Logger) VerboseErrf(color Color, s string, args ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Warnf(message string, args ...any) {
|
||||
l.Errf(Yellow, message, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Prompt(color Color, prompt string, defaultValue string, continueValues ...string) error {
|
||||
if l.AssumeYes {
|
||||
l.Outf(color, "%s [assuming yes]\n", prompt)
|
||||
@@ -152,7 +180,7 @@ func (l *Logger) Prompt(color Color, prompt string, defaultValue string, continu
|
||||
return errors.New("no continue values provided")
|
||||
}
|
||||
|
||||
l.Outf(color, "%s [%s/%s]\n", prompt, strings.ToLower(continueValues[0]), strings.ToUpper(defaultValue))
|
||||
l.Outf(color, "%s [%s/%s]: ", prompt, strings.ToLower(continueValues[0]), strings.ToUpper(defaultValue))
|
||||
|
||||
reader := bufio.NewReader(l.Stdin)
|
||||
input, err := reader.ReadString('\n')
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ type Output interface {
|
||||
type CloseFunc func(err error) error
|
||||
|
||||
// Build the Output for the requested ast.Output.
|
||||
func BuildFor(o *ast.Output) (Output, error) {
|
||||
func BuildFor(o *ast.Output, logger *logger.Logger) (Output, error) {
|
||||
switch o.Name {
|
||||
case "interleaved", "":
|
||||
if err := checkOutputGroupUnset(o); err != nil {
|
||||
@@ -32,7 +33,7 @@ func BuildFor(o *ast.Output) (Output, error) {
|
||||
if err := checkOutputGroupUnset(o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Prefixed{}, nil
|
||||
return NewPrefixed(logger), nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/omap"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
@@ -107,7 +109,11 @@ func TestGroupErrorOnlyShowsOutputOnError(t *testing.T) {
|
||||
|
||||
func TestPrefixed(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Prefixed{}
|
||||
l := &logger.Logger{
|
||||
Color: false,
|
||||
}
|
||||
|
||||
var o output.Output = output.NewPrefixed(l)
|
||||
w, _, cleanup := o.WrapWriter(&b, io.Discard, "prefix", nil)
|
||||
|
||||
t.Run("simple use cases", func(t *testing.T) {
|
||||
@@ -132,3 +138,33 @@ func TestPrefixed(t *testing.T) {
|
||||
assert.Equal(t, "[prefix] Test!\n", b.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrefixedWithColor(t *testing.T) {
|
||||
color.NoColor = false
|
||||
|
||||
var b bytes.Buffer
|
||||
l := &logger.Logger{
|
||||
Color: true,
|
||||
}
|
||||
|
||||
var o output.Output = output.NewPrefixed(l)
|
||||
|
||||
writers := make([]io.Writer, 16)
|
||||
for i := range writers {
|
||||
writers[i], _, _ = o.WrapWriter(&b, io.Discard, fmt.Sprintf("prefix-%d", i), nil)
|
||||
}
|
||||
|
||||
t.Run("colors should loop", func(t *testing.T) {
|
||||
for i, w := range writers {
|
||||
b.Reset()
|
||||
|
||||
color := output.PrefixColorSequence[i%len(output.PrefixColorSequence)]
|
||||
|
||||
var prefix bytes.Buffer
|
||||
l.FOutf(&prefix, color, fmt.Sprintf("prefix-%d", i))
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, fmt.Sprintf("[%s] foo\n[%s] bar\n", prefix.String(), prefix.String()), b.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,20 +6,36 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
)
|
||||
|
||||
type Prefixed struct{}
|
||||
type Prefixed struct {
|
||||
logger *logger.Logger
|
||||
seen map[string]uint
|
||||
counter *uint
|
||||
}
|
||||
|
||||
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
|
||||
pw := &prefixWriter{writer: stdOut, prefix: prefix}
|
||||
func NewPrefixed(logger *logger.Logger) Prefixed {
|
||||
var counter uint
|
||||
|
||||
return Prefixed{
|
||||
seen: make(map[string]uint),
|
||||
counter: &counter,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
|
||||
pw := &prefixWriter{writer: stdOut, prefix: prefix, prefixed: &p}
|
||||
return pw, pw, func(error) error { return pw.close() }
|
||||
}
|
||||
|
||||
type prefixWriter struct {
|
||||
writer io.Writer
|
||||
prefix string
|
||||
buff bytes.Buffer
|
||||
writer io.Writer
|
||||
prefixed *Prefixed
|
||||
prefix string
|
||||
buff bytes.Buffer
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Write(p []byte) (int, error) {
|
||||
@@ -56,6 +72,11 @@ func (pw *prefixWriter) writeOutputLines(force bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
var PrefixColorSequence = []logger.Color{
|
||||
logger.Yellow, logger.Blue, logger.Magenta, logger.Cyan, logger.Green, logger.Red,
|
||||
logger.BrightYellow, logger.BrightBlue, logger.BrightMagenta, logger.BrightCyan, logger.BrightGreen, logger.BrightRed,
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) writeLine(line string) error {
|
||||
if line == "" {
|
||||
return nil
|
||||
@@ -63,6 +84,27 @@ func (pw *prefixWriter) writeLine(line string) error {
|
||||
if !strings.HasSuffix(line, "\n") {
|
||||
line += "\n"
|
||||
}
|
||||
_, err := fmt.Fprintf(pw.writer, "[%s] %s", pw.prefix, line)
|
||||
|
||||
idx, ok := pw.prefixed.seen[pw.prefix]
|
||||
|
||||
if !ok {
|
||||
idx = *pw.prefixed.counter
|
||||
pw.prefixed.seen[pw.prefix] = idx
|
||||
|
||||
*pw.prefixed.counter++
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(pw.writer, "["); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
color := PrefixColorSequence[idx%uint(len(PrefixColorSequence))]
|
||||
pw.prefixed.logger.FOutf(pw.writer, color, pw.prefix)
|
||||
|
||||
if _, err := fmt.Fprint(pw.writer, "] "); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := fmt.Fprint(pw.writer, line)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
sprig "github.com/go-task/slim-sprig/v3"
|
||||
"github.com/go-task/template"
|
||||
)
|
||||
|
||||
var templateFuncs template.FuncMap
|
||||
@@ -73,12 +73,16 @@ func init() {
|
||||
return spew.Sdump(v)
|
||||
},
|
||||
}
|
||||
|
||||
// aliases
|
||||
taskFuncs["q"] = taskFuncs["shellQuote"]
|
||||
|
||||
// Deprecated aliases for renamed functions.
|
||||
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
|
||||
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
||||
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
||||
|
||||
templateFuncs = sprig.TxtFuncMap()
|
||||
templateFuncs = template.FuncMap(sprig.TxtFuncMap())
|
||||
for k, v := range taskFuncs {
|
||||
templateFuncs[k] = v
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ package templater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
"github.com/go-task/template"
|
||||
)
|
||||
|
||||
// Cache is a help struct that allow us to call "replaceX" funcs multiple
|
||||
@@ -29,6 +30,33 @@ func (r *Cache) Err() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
func ResolveRef(ref string, cache *Cache) any {
|
||||
// If there is already an error, do nothing
|
||||
if cache.err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize the cache map if it's not already initialized
|
||||
if cache.cacheMap == nil {
|
||||
cache.cacheMap = cache.Vars.ToCacheMap()
|
||||
}
|
||||
|
||||
if ref == "." {
|
||||
return cache.cacheMap
|
||||
}
|
||||
t, err := template.New("resolver").Funcs(templateFuncs).Parse(fmt.Sprintf("{{%s}}", ref))
|
||||
if err != nil {
|
||||
cache.err = err
|
||||
return nil
|
||||
}
|
||||
val, err := t.Resolve(cache.cacheMap)
|
||||
if err != nil {
|
||||
cache.err = err
|
||||
return nil
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func Replace[T any](v T, cache *Cache) T {
|
||||
return ReplaceWithExtra(v, cache, nil)
|
||||
}
|
||||
@@ -91,14 +119,15 @@ func ReplaceVar(v ast.Var, cache *Cache) ast.Var {
|
||||
}
|
||||
|
||||
func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var {
|
||||
if v.Ref != "" {
|
||||
return ast.Var{Value: ResolveRef(v.Ref, cache)}
|
||||
}
|
||||
return ast.Var{
|
||||
Value: ReplaceWithExtra(v.Value, cache, extra),
|
||||
Sh: ReplaceWithExtra(v.Sh, cache, extra),
|
||||
Live: v.Live,
|
||||
Ref: v.Ref,
|
||||
Dir: v.Dir,
|
||||
Json: ReplaceWithExtra(v.Json, cache, extra),
|
||||
Yaml: ReplaceWithExtra(v.Yaml, cache, extra),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,21 +5,27 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var version = ""
|
||||
|
||||
func GetVersion() string {
|
||||
if version != "" {
|
||||
return version
|
||||
}
|
||||
var (
|
||||
version = ""
|
||||
sum = ""
|
||||
)
|
||||
|
||||
func init() {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok || info.Main.Version == "" {
|
||||
return "unknown"
|
||||
version = "unknown"
|
||||
} else {
|
||||
if version == "" {
|
||||
version = info.Main.Version
|
||||
}
|
||||
sum = info.Main.Sum
|
||||
}
|
||||
|
||||
ver := info.Main.Version
|
||||
if info.Main.Sum != "" {
|
||||
ver += fmt.Sprintf(" (%s)", info.Main.Sum)
|
||||
}
|
||||
return ver
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
func GetVersionWithSum() string {
|
||||
return fmt.Sprintf("%s (%s)", version, sum)
|
||||
}
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.36.0",
|
||||
"version": "3.39.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.36.0",
|
||||
"version": "3.39.2",
|
||||
"description": "A task runner / simpler Make alternative written in Go",
|
||||
"scripts": {
|
||||
"postinstall": "go-npm install",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
44
setup.go
44
setup.go
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -63,19 +64,22 @@ func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||
}
|
||||
|
||||
func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||
var err error
|
||||
e.Taskfile, err = taskfile.Read(
|
||||
reader := taskfile.NewReader(
|
||||
node,
|
||||
e.Insecure,
|
||||
e.Download,
|
||||
e.Offline,
|
||||
e.Timeout,
|
||||
e.TempDir,
|
||||
e.TempDir.Remote,
|
||||
e.Logger,
|
||||
)
|
||||
graph, err := reader.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Taskfile, err = graph.Merge(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,12 +105,15 @@ func (e *Executor) setupFuzzyModel() {
|
||||
}
|
||||
|
||||
func (e *Executor) setupTempDir() error {
|
||||
if e.TempDir != "" {
|
||||
if e.TempDir != (TempDir{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if os.Getenv("TASK_TEMP_DIR") == "" {
|
||||
e.TempDir = filepathext.SmartJoin(e.Dir, ".task")
|
||||
e.TempDir = TempDir{
|
||||
Remote: filepathext.SmartJoin(e.Dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(e.Dir, ".task"),
|
||||
}
|
||||
} else if filepath.IsAbs(os.Getenv("TASK_TEMP_DIR")) || strings.HasPrefix(os.Getenv("TASK_TEMP_DIR"), "~") {
|
||||
tempDir, err := execext.Expand(os.Getenv("TASK_TEMP_DIR"))
|
||||
if err != nil {
|
||||
@@ -114,9 +121,28 @@ func (e *Executor) setupTempDir() error {
|
||||
}
|
||||
projectDir, _ := filepath.Abs(e.Dir)
|
||||
projectName := filepath.Base(projectDir)
|
||||
e.TempDir = filepathext.SmartJoin(tempDir, projectName)
|
||||
e.TempDir = TempDir{
|
||||
Remote: tempDir,
|
||||
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
|
||||
}
|
||||
|
||||
} else {
|
||||
e.TempDir = filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR"))
|
||||
e.TempDir = TempDir{
|
||||
Remote: filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR")),
|
||||
Fingerprint: filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR")),
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("TASK_REMOTE_DIR") != "" {
|
||||
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
|
||||
}
|
||||
e.TempDir.Remote = remoteTempDir
|
||||
} else {
|
||||
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -152,7 +178,7 @@ func (e *Executor) setupOutput() error {
|
||||
}
|
||||
|
||||
var err error
|
||||
e.Output, err = output.BuildFor(&e.OutputStyle)
|
||||
e.Output, err = output.BuildFor(&e.OutputStyle, e.Logger)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
15
signals.go
15
signals.go
@@ -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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (e *Executor) Status(ctx context.Context, calls ...*ast.Call) error {
|
||||
// Check if the task is up-to-date
|
||||
isUpToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
|
||||
fingerprint.WithMethod(method),
|
||||
fingerprint.WithTempDir(e.TempDir),
|
||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||
fingerprint.WithDry(e.Dry),
|
||||
fingerprint.WithLogger(e.Logger),
|
||||
)
|
||||
@@ -46,7 +46,7 @@ func (e *Executor) statusOnError(t *ast.Task) error {
|
||||
if method == "" {
|
||||
method = e.Taskfile.Method
|
||||
}
|
||||
checker, err := fingerprint.NewSourcesChecker(method, e.TempDir, e.Dry)
|
||||
checker, err := fingerprint.NewSourcesChecker(method, e.TempDir.Fingerprint, e.Dry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
73
task.go
73
task.go
@@ -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"
|
||||
@@ -34,13 +36,18 @@ const (
|
||||
MaximumTaskCall = 1000
|
||||
)
|
||||
|
||||
type TempDir struct {
|
||||
Remote string
|
||||
Fingerprint string
|
||||
}
|
||||
|
||||
// Executor executes a Taskfile
|
||||
type Executor struct {
|
||||
Taskfile *ast.Taskfile
|
||||
|
||||
Dir string
|
||||
Entrypoint string
|
||||
TempDir string
|
||||
TempDir TempDir
|
||||
Force bool
|
||||
ForceAll bool
|
||||
Insecure bool
|
||||
@@ -183,16 +190,6 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
release := e.acquireConcurrencyLimit()
|
||||
defer release()
|
||||
|
||||
if t.Prompt != "" {
|
||||
if err := e.Logger.Prompt(logger.Yellow, t.Prompt, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
|
||||
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
||||
} else if errors.Is(err, logger.ErrPromptCancelled) {
|
||||
return &errors.TaskCancelledByUserError{TaskName: call.Task}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return e.startExecution(ctx, t, func(ctx context.Context) error {
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: %q started\n", call.Task)
|
||||
if err := e.runDeps(ctx, t); err != nil {
|
||||
@@ -205,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
|
||||
}
|
||||
|
||||
@@ -222,7 +219,7 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
|
||||
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
|
||||
fingerprint.WithMethod(method),
|
||||
fingerprint.WithTempDir(e.TempDir),
|
||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||
fingerprint.WithDry(e.Dry),
|
||||
fingerprint.WithLogger(e.Logger),
|
||||
)
|
||||
@@ -238,13 +235,25 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
}
|
||||
}
|
||||
|
||||
if t.Prompt != "" && !e.Dry {
|
||||
if err := e.Logger.Prompt(logger.Yellow, t.Prompt, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
|
||||
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
||||
} else if errors.Is(err, logger.ErrPromptCancelled) {
|
||||
return &errors.TaskCancelledByUserError{TaskName: call.Task}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.mkdir(t); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -253,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 {
|
||||
@@ -307,10 +320,26 @@ 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()
|
||||
|
||||
origTask, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := t.Cmds[i]
|
||||
vars, _ := e.Compiler.GetVariables(origTask, 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())
|
||||
}
|
||||
@@ -367,7 +396,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
|
||||
}
|
||||
@@ -489,14 +518,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
|
||||
})
|
||||
}
|
||||
|
||||
268
task_test.go
268
task_test.go
@@ -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,17 +61,19 @@ 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: filepathext.SmartJoin(fct.Dir, ".task"),
|
||||
Dir: fct.Dir,
|
||||
TempDir: task.TempDir{
|
||||
Remote: filepathext.SmartJoin(fct.Dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(fct.Dir, ".task"),
|
||||
},
|
||||
Entrypoint: fct.Entrypoint,
|
||||
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)
|
||||
@@ -95,17 +98,40 @@ func TestEmptyTask(t *testing.T) {
|
||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
|
||||
}
|
||||
|
||||
func TestEmptyTaskfile(t *testing.T) {
|
||||
e := &task.Executor{
|
||||
Dir: "testdata/empty_taskfile",
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
require.Error(t, e.Setup(), "e.Setup()")
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
t.Setenv("QUX", "from_os")
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/env",
|
||||
Target: "default",
|
||||
TrimSpace: false,
|
||||
Files: map[string]string{
|
||||
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
||||
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
|
||||
"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) {
|
||||
@@ -262,11 +288,14 @@ func TestStatus(t *testing.T) {
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
TempDir: filepathext.SmartJoin(dir, ".task"),
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
Dir: dir,
|
||||
TempDir: task.TempDir{
|
||||
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||
},
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
require.NoError(t, e.Setup())
|
||||
// gen-foo creates foo.txt, and will always fail it's status check.
|
||||
@@ -458,7 +487,10 @@ func TestStatusChecksum(t *testing.T) {
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
tempdir := filepathext.SmartJoin(dir, ".task")
|
||||
tempdir := task.TempDir{
|
||||
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||
}
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
TempDir: tempdir,
|
||||
@@ -475,7 +507,7 @@ func TestStatusChecksum(t *testing.T) {
|
||||
|
||||
// Capture the modification time, so we can ensure the checksum file
|
||||
// is not regenerated when the hash hasn't changed.
|
||||
s, err := os.Stat(filepathext.SmartJoin(tempdir, "checksum/"+test.task))
|
||||
s, err := os.Stat(filepathext.SmartJoin(tempdir.Fingerprint, "checksum/"+test.task))
|
||||
require.NoError(t, err)
|
||||
time := s.ModTime()
|
||||
|
||||
@@ -483,7 +515,7 @@ func TestStatusChecksum(t *testing.T) {
|
||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.task}))
|
||||
assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String())
|
||||
|
||||
s, err = os.Stat(filepathext.SmartJoin(tempdir, "checksum/"+test.task))
|
||||
s, err = os.Stat(filepathext.SmartJoin(tempdir.Fingerprint, "checksum/"+test.task))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, time, s.ModTime())
|
||||
})
|
||||
@@ -793,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) {
|
||||
@@ -804,8 +837,11 @@ func TestStatusVariables(t *testing.T) {
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
TempDir: filepathext.SmartJoin(dir, ".task"),
|
||||
Dir: dir,
|
||||
TempDir: task.TempDir{
|
||||
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||
},
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: false,
|
||||
@@ -953,11 +989,14 @@ func TestDryChecksum(t *testing.T) {
|
||||
_ = os.Remove(checksumFile)
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
TempDir: filepathext.SmartJoin(dir, ".task"),
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
Dry: true,
|
||||
Dir: dir,
|
||||
TempDir: task.TempDir{
|
||||
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||
},
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
Dry: true,
|
||||
}
|
||||
require.NoError(t, e.Setup())
|
||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
|
||||
@@ -1032,7 +1071,7 @@ func TestIncludesIncorrect(t *testing.T) {
|
||||
|
||||
err := e.Setup()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "task: Failed to parse testdata/includes_incorrect/incomplete.yml:")
|
||||
assert.Contains(t, err.Error(), "Failed to parse testdata/includes_incorrect/incomplete.yml:", err.Error())
|
||||
}
|
||||
|
||||
func TestIncludesEmptyMain(t *testing.T) {
|
||||
@@ -1191,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 {
|
||||
@@ -1199,15 +1277,17 @@ func TestIncludesInterpolation(t *testing.T) {
|
||||
expectedErr bool
|
||||
expectedOutput string
|
||||
}{
|
||||
{"include", "include", false, "includes_interpolation\n"},
|
||||
{"include with dir", "include-with-dir", false, "included\n"},
|
||||
{"include", "include", false, "include\n"},
|
||||
{"include_with_env_variable", "include-with-env-variable", false, "include_with_env_variable\n"},
|
||||
{"include_with_dir", "include-with-dir", false, "included\n"},
|
||||
}
|
||||
t.Setenv("MODULE", "included")
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Dir: filepath.Join(dir, test.name),
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
@@ -1225,6 +1305,34 @@ func TestIncludesInterpolation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncludedTaskfileVarMerging(t *testing.T) {
|
||||
const dir = "testdata/included_taskfile_var_merging"
|
||||
tests := []struct {
|
||||
name string
|
||||
task string
|
||||
expectedOutput string
|
||||
}{
|
||||
{"foo", "foo:pwd", "included_taskfile_var_merging/foo\n"},
|
||||
{"bar", "bar:pwd", "included_taskfile_var_merging/bar\n"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
err := e.Run(context.Background(), &ast.Call{Task: test.task})
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, buff.String(), test.expectedOutput)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInternalTask(t *testing.T) {
|
||||
const dir = "testdata/internal_task"
|
||||
tests := []struct {
|
||||
@@ -1624,6 +1732,26 @@ func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestRunOnceSharedDeps(t *testing.T) {
|
||||
const dir = "testdata/run_once_shared_deps"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
ForceAll: true,
|
||||
}
|
||||
require.NoError(t, e.Setup())
|
||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build"}))
|
||||
|
||||
rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library"`)
|
||||
matches := rx.FindAllStringSubmatch(buff.String(), -1)
|
||||
assert.Len(t, matches, 1)
|
||||
assert.Contains(t, buff.String(), `task: [service-a:build] echo "build a"`)
|
||||
assert.Contains(t, buff.String(), `task: [service-b:build] echo "build b"`)
|
||||
}
|
||||
|
||||
func TestDeferredCmds(t *testing.T) {
|
||||
const dir = "testdata/deferred"
|
||||
var buff bytes.Buffer
|
||||
@@ -1649,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, "FOO=bar - DYNAMIC_FOO=bar - 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, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=1", strings.TrimSpace(buff.String()))
|
||||
}
|
||||
|
||||
func TestIgnoreNilElements(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -2220,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",
|
||||
@@ -2277,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"},
|
||||
@@ -2386,3 +2557,48 @@ func TestWildcard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReference(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
call string
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
name: "reference in command",
|
||||
call: "ref-cmd",
|
||||
expectedOutput: "1\n",
|
||||
},
|
||||
{
|
||||
name: "reference in dependency",
|
||||
call: "ref-dep",
|
||||
expectedOutput: "1\n",
|
||||
},
|
||||
{
|
||||
name: "reference using templating resolver",
|
||||
call: "ref-resolver",
|
||||
expectedOutput: "1\n",
|
||||
},
|
||||
{
|
||||
name: "reference using templating resolver and dynamic var",
|
||||
call: "ref-resolver-sh",
|
||||
expectedOutput: "Alice has 3 children called Bob, Charlie, and Diane\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.call, func(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: "testdata/var_references",
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
Force: true,
|
||||
}
|
||||
require.NoError(t, e.Setup())
|
||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
|
||||
assert.Equal(t, test.expectedOutput, buff.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
@@ -46,7 +45,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var cmd string
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
c.Cmd = cmd
|
||||
return nil
|
||||
@@ -110,8 +109,8 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in command")
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("command")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// Dep is a task dependency
|
||||
@@ -32,7 +32,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var task string
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
d.Task = task
|
||||
return nil
|
||||
@@ -45,7 +45,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
|
||||
Silent bool
|
||||
}
|
||||
if err := node.Decode(&taskCall); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
d.Task = taskCall.Task
|
||||
d.For = taskCall.For
|
||||
@@ -54,5 +54,5 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot unmarshal %s into dependency", node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("dependency")
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"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 {
|
||||
@@ -22,7 +23,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var from string
|
||||
if err := node.Decode(&from); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
f.From = from
|
||||
return nil
|
||||
@@ -30,28 +31,35 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.SequenceNode:
|
||||
var list []any
|
||||
if err := node.Decode(&list); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
f.List = list
|
||||
return nil
|
||||
|
||||
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 && forStruct.Var != "" {
|
||||
f.Var = forStruct.Var
|
||||
f.Split = forStruct.Split
|
||||
f.As = forStruct.As
|
||||
return nil
|
||||
if err := node.Decode(&forStruct); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: invalid keys in for", node.Line)
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into for", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("for")
|
||||
}
|
||||
|
||||
func (f *For) DeepCopy() *For {
|
||||
@@ -59,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
type Glob struct {
|
||||
@@ -13,20 +13,22 @@ type Glob struct {
|
||||
|
||||
func (g *Glob) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
g.Glob = node.Value
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var glob struct {
|
||||
Exclude string
|
||||
}
|
||||
if err := node.Decode(&glob); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
g.Glob = glob.Exclude
|
||||
g.Negate = true
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("glob")
|
||||
}
|
||||
|
||||
129
taskfile/ast/graph.go
Normal file
129
taskfile/ast/graph.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/dominikbraun/graph"
|
||||
"github.com/dominikbraun/graph/draw"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type TaskfileGraph struct {
|
||||
sync.Mutex
|
||||
graph.Graph[string, *TaskfileVertex]
|
||||
}
|
||||
|
||||
// A TaskfileVertex is a vertex on the Taskfile DAG.
|
||||
type TaskfileVertex struct {
|
||||
URI string
|
||||
Taskfile *Taskfile
|
||||
}
|
||||
|
||||
func taskfileHash(vertex *TaskfileVertex) string {
|
||||
return vertex.URI
|
||||
}
|
||||
|
||||
func NewTaskfileGraph() *TaskfileGraph {
|
||||
return &TaskfileGraph{
|
||||
sync.Mutex{},
|
||||
graph.New(taskfileHash,
|
||||
graph.Directed(),
|
||||
graph.PreventCycles(),
|
||||
graph.Rooted(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (tfg *TaskfileGraph) Visualize(filename string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return draw.DOT(tfg.Graph, f)
|
||||
}
|
||||
|
||||
func (tfg *TaskfileGraph) Merge() (*Taskfile, error) {
|
||||
hashes, err := graph.TopologicalSort(tfg.Graph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
predecessorMap, err := tfg.PredecessorMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Loop over each vertex in reverse topological order except for the root vertex.
|
||||
// This gives us a loop over every included Taskfile in an order which is safe to merge.
|
||||
for i := len(hashes) - 1; i > 0; i-- {
|
||||
hash := hashes[i]
|
||||
|
||||
// Get the included vertex
|
||||
includedVertex, err := tfg.Vertex(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create an error group to wait for all the included Taskfiles to be merged with all its parents
|
||||
var g errgroup.Group
|
||||
|
||||
// Loop over edge that leads to a vertex that includes the current vertex
|
||||
for _, edge := range predecessorMap[hash] {
|
||||
|
||||
// Start a goroutine to process each included Taskfile
|
||||
g.Go(func() error {
|
||||
// Get the base vertex
|
||||
vertex, err := tfg.Vertex(edge.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the merge options
|
||||
includes, ok := edge.Properties.Data.([]*Include)
|
||||
if !ok {
|
||||
return fmt.Errorf("task: Failed to get merge options")
|
||||
}
|
||||
|
||||
// Merge the included Taskfiles into the parent Taskfile
|
||||
for _, include := range includes {
|
||||
if err := vertex.Taskfile.Merge(
|
||||
includedVertex.Taskfile,
|
||||
include,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all the go routines to finish
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the root vertex
|
||||
rootVertex, err := tfg.Vertex(hashes[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = rootVertex.Taskfile.Tasks.Range(func(name string, task *Task) error {
|
||||
if task == nil {
|
||||
task = &Task{}
|
||||
rootVertex.Taskfile.Tasks.Set(name, task)
|
||||
}
|
||||
task.Task = name
|
||||
return nil
|
||||
})
|
||||
|
||||
return rootVertex.Taskfile, nil
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
omap "github.com/go-task/task/v3/internal/omap"
|
||||
)
|
||||
|
||||
@@ -18,11 +17,12 @@ type Include struct {
|
||||
Aliases []string
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
Flatten bool
|
||||
}
|
||||
|
||||
// Includes represents information about included tasksfiles
|
||||
type Includes struct {
|
||||
omap.OrderedMap[string, Include]
|
||||
omap.OrderedMap[string, *Include]
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
@@ -38,15 +38,15 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
|
||||
|
||||
var v Include
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Namespace = keyNode.Value
|
||||
includes.Set(keyNode.Value, v)
|
||||
includes.Set(keyNode.Value, &v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfiles", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("includes")
|
||||
}
|
||||
|
||||
// Len returns the length of the map
|
||||
@@ -58,7 +58,7 @@ func (includes *Includes) Len() int {
|
||||
}
|
||||
|
||||
// Wrapper around OrderedMap.Set to ensure we don't get nil pointer errors
|
||||
func (includes *Includes) Range(f func(k string, v Include) error) error {
|
||||
func (includes *Includes) Range(f func(k string, v *Include) error) error {
|
||||
if includes == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var str string
|
||||
if err := node.Decode(&str); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
include.Taskfile = str
|
||||
return nil
|
||||
@@ -82,11 +82,12 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Flatten bool
|
||||
Aliases []string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := node.Decode(&includedTaskfile); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
include.Taskfile = includedTaskfile.Taskfile
|
||||
include.Dir = includedTaskfile.Dir
|
||||
@@ -95,10 +96,11 @@ 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
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfile", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("include")
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of IncludedTaskfile and copies
|
||||
@@ -115,5 +117,6 @@ func (include *Include) DeepCopy() *Include {
|
||||
Internal: include.Internal,
|
||||
AdvancedImport: include.AdvancedImport,
|
||||
Vars: include.Vars.DeepCopy(),
|
||||
Flatten: include.Flatten,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// Output of the Task output
|
||||
@@ -25,7 +25,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var name string
|
||||
if err := node.Decode(&name); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
s.Name = name
|
||||
return nil
|
||||
@@ -35,10 +35,10 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
||||
Group *OutputGroup
|
||||
}
|
||||
if err := node.Decode(&tmp); err != nil {
|
||||
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
if tmp.Group == nil {
|
||||
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`output style must have the "group" key when in mapping form`)
|
||||
}
|
||||
*s = Output{
|
||||
Name: "group",
|
||||
@@ -47,7 +47,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into output", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("output")
|
||||
}
|
||||
|
||||
// OutputGroup is the style options specific to the Group style.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/goext"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ type ErrInvalidPlatform struct {
|
||||
}
|
||||
|
||||
func (err *ErrInvalidPlatform) Error() string {
|
||||
return fmt.Sprintf(`task: Invalid platform "%s"`, err.Platform)
|
||||
return fmt.Sprintf(`invalid platform "%s"`, err.Platform)
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
@@ -39,14 +40,14 @@ func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var platform string
|
||||
if err := node.Decode(&platform); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
if err := p.parsePlatform(platform); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into platform", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("platform")
|
||||
}
|
||||
|
||||
// parsePlatform takes a string representing an OS/Arch combination (or either on their own)
|
||||
|
||||
@@ -26,10 +26,10 @@ func TestPlatformParsing(t *testing.T) {
|
||||
{Input: "windows/amd64", ExpectedOS: "windows", ExpectedArch: "amd64"},
|
||||
{Input: "windows/arm64", ExpectedOS: "windows", ExpectedArch: "arm64"},
|
||||
|
||||
{Input: "invalid", Error: `task: Invalid platform "invalid"`},
|
||||
{Input: "invalid/invalid", Error: `task: Invalid platform "invalid/invalid"`},
|
||||
{Input: "windows/invalid", Error: `task: Invalid platform "windows/invalid"`},
|
||||
{Input: "invalid/amd64", Error: `task: Invalid platform "invalid/amd64"`},
|
||||
{Input: "invalid", Error: `invalid platform "invalid"`},
|
||||
{Input: "invalid/invalid", Error: `invalid platform "invalid/invalid"`},
|
||||
{Input: "windows/invalid", Error: `invalid platform "windows/invalid"`},
|
||||
{Input: "invalid/amd64", Error: `invalid platform "invalid/amd64"`},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
|
||||
var ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// Precondition represents a precondition necessary for a task to run
|
||||
type Precondition struct {
|
||||
@@ -33,7 +31,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var cmd string
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
p.Sh = cmd
|
||||
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
||||
@@ -45,7 +43,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
|
||||
Msg string
|
||||
}
|
||||
if err := node.Decode(&sh); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
p.Sh = sh.Sh
|
||||
p.Msg = sh.Msg
|
||||
@@ -55,5 +53,5 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into precondition", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("precondition")
|
||||
}
|
||||
|
||||
@@ -7,42 +7,45 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
Task string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Prompt string
|
||||
Summary string
|
||||
Requires *Requires
|
||||
Aliases []string
|
||||
Sources []*Glob
|
||||
Generates []*Glob
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Task string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Prompt string
|
||||
Summary string
|
||||
Requires *Requires
|
||||
Aliases []string
|
||||
Sources []*Glob
|
||||
Generates []*Glob
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
Watch bool
|
||||
Location *Location
|
||||
// Populated during merging
|
||||
Namespace string
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
Platforms []*Platform
|
||||
Location *Location
|
||||
Watch bool
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
@@ -52,6 +55,13 @@ func (t *Task) Name() string {
|
||||
return t.Task
|
||||
}
|
||||
|
||||
func (t *Task) LocalName() string {
|
||||
name := t.Task
|
||||
name = strings.TrimPrefix(name, t.Namespace)
|
||||
name = strings.TrimPrefix(name, ":")
|
||||
return name
|
||||
}
|
||||
|
||||
// WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values.
|
||||
func (t *Task) WildcardMatch(name string) (bool, []string) {
|
||||
// Convert the name into a regex string
|
||||
@@ -83,7 +93,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var cmd Cmd
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
t.Cmds = append(t.Cmds, &cmd)
|
||||
return nil
|
||||
@@ -92,7 +102,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.SequenceNode:
|
||||
var cmds []*Cmd
|
||||
if err := node.Decode(&cmds); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
t.Cmds = cmds
|
||||
return nil
|
||||
@@ -130,11 +140,11 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Watch bool
|
||||
}
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
if task.Cmd != nil {
|
||||
if task.Cmds != nil {
|
||||
return fmt.Errorf("yaml: line %d: task cannot have both cmd and cmds", node.Line)
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("task cannot have both cmd and cmds")
|
||||
}
|
||||
t.Cmds = []*Cmd{task.Cmd}
|
||||
} else {
|
||||
@@ -169,7 +179,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("task")
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Task and copies
|
||||
@@ -209,6 +219,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Platforms: deepcopy.Slice(t.Platforms),
|
||||
Location: t.Location.DeepCopy(),
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// NamespaceSeparator contains the character that separates namespaces
|
||||
@@ -13,6 +15,9 @@ const NamespaceSeparator = ":"
|
||||
|
||||
var V3 = semver.MustParse("3")
|
||||
|
||||
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
||||
var ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
||||
|
||||
// Taskfile is the abstract syntax tree for a Taskfile
|
||||
type Taskfile struct {
|
||||
Location string
|
||||
@@ -36,6 +41,9 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
||||
if !t1.Version.Equal(t2.Version) {
|
||||
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
||||
}
|
||||
if len(t2.Dotenv) > 0 {
|
||||
return ErrIncludedTaskfilesCantHaveDotenvs
|
||||
}
|
||||
if t2.Output.IsSet() {
|
||||
t1.Output = t2.Output
|
||||
}
|
||||
@@ -45,10 +53,9 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
||||
if t1.Env == nil {
|
||||
t1.Env = &Vars{}
|
||||
}
|
||||
t1.Vars.Merge(t2.Vars)
|
||||
t1.Env.Merge(t2.Env)
|
||||
t1.Tasks.Merge(t2.Tasks, include)
|
||||
return nil
|
||||
t1.Vars.Merge(t2.Vars, include)
|
||||
t1.Env.Merge(t2.Env, include)
|
||||
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
||||
}
|
||||
|
||||
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
@@ -70,7 +77,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
Interval time.Duration
|
||||
}
|
||||
if err := node.Decode(&taskfile); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
tf.Version = taskfile.Version
|
||||
tf.Output = taskfile.Output
|
||||
@@ -94,5 +101,5 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("taskfile")
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/omap"
|
||||
)
|
||||
|
||||
@@ -44,58 +47,82 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
return matchingTasks
|
||||
}
|
||||
|
||||
func (t1 *Tasks) Merge(t2 Tasks, include *Include) {
|
||||
_ = t2.Range(func(k 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 dependencies, commands and aliases
|
||||
for _, dep := range task.Deps {
|
||||
if dep != nil && dep.Task != "" {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
|
||||
}
|
||||
}
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd != nil && cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
task.Dir = filepathext.SmartJoin(include.Dir, task.Dir)
|
||||
if task.IncludeVars == nil {
|
||||
task.IncludeVars = &Vars{}
|
||||
}
|
||||
task.IncludeVars.Merge(include.Vars, nil)
|
||||
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(k, 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 {
|
||||
@@ -103,7 +130,7 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.MappingNode:
|
||||
tasks := omap.New[string, *Task]()
|
||||
if err := node.Decode(&tasks); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
@@ -135,7 +162,7 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into tasks", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("tasks")
|
||||
}
|
||||
|
||||
func taskNameWithNamespace(taskName string, namespace string) string {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/internal/omap"
|
||||
)
|
||||
@@ -45,11 +45,17 @@ func (vs *Vars) Range(f func(k string, v Var) error) error {
|
||||
}
|
||||
|
||||
// Wrapper around OrderedMap.Merge to ensure we don't get nil pointer errors
|
||||
func (vs *Vars) Merge(other *Vars) {
|
||||
func (vs *Vars) Merge(other *Vars, include *Include) {
|
||||
if vs == nil || other == nil {
|
||||
return
|
||||
}
|
||||
vs.OrderedMap.Merge(other.OrderedMap)
|
||||
_ = other.Range(func(key string, value Var) error {
|
||||
if include != nil && include.AdvancedImport {
|
||||
value.Dir = include.Dir
|
||||
}
|
||||
vs.Set(key, value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Wrapper around OrderedMap.Len to ensure we don't get nil pointer errors
|
||||
@@ -77,19 +83,17 @@ type Var struct {
|
||||
Live any
|
||||
Sh string
|
||||
Ref string
|
||||
Json string
|
||||
Yaml string
|
||||
Dir string
|
||||
}
|
||||
|
||||
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
if experiments.AnyVariables.Enabled {
|
||||
if experiments.MapVariables.Enabled {
|
||||
|
||||
// This implementation is not backwards-compatible and replaces the 'sh' key with map variables
|
||||
if experiments.AnyVariables.Value == "1" {
|
||||
if experiments.MapVariables.Value == "1" {
|
||||
var value any
|
||||
if err := node.Decode(&value); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
// If the value is a string and it starts with $, then it's a shell command
|
||||
if str, ok := value.(string); ok {
|
||||
@@ -97,41 +101,41 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
v.Sh = str
|
||||
return nil
|
||||
}
|
||||
if str, ok = strings.CutPrefix(str, "#"); ok {
|
||||
v.Ref = str
|
||||
return nil
|
||||
}
|
||||
}
|
||||
v.Value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// This implementation IS backwards-compatible and keeps the 'sh' key and allows map variables to be added under the `map` key
|
||||
if experiments.AnyVariables.Value == "2" {
|
||||
if experiments.MapVariables.Value == "2" {
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
key := node.Content[0].Value
|
||||
switch key {
|
||||
case "sh", "ref", "map", "json", "yaml":
|
||||
case "sh", "ref", "map":
|
||||
var m struct {
|
||||
Sh string
|
||||
Ref string
|
||||
Map any
|
||||
Json string
|
||||
Yaml string
|
||||
Sh string
|
||||
Ref string
|
||||
Map any
|
||||
}
|
||||
if err := node.Decode(&m); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Sh = m.Sh
|
||||
v.Ref = m.Ref
|
||||
v.Value = m.Map
|
||||
v.Json = m.Json
|
||||
v.Yaml = m.Yaml
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf(`yaml: line %d: %q is not a valid variable type. Try "sh", "ref", "map", "json", "yaml" or using a scalar value`, node.Line, key)
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map" or using a scalar value`, key)
|
||||
}
|
||||
default:
|
||||
var value any
|
||||
if err := node.Decode(&value); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Value = value
|
||||
return nil
|
||||
@@ -141,24 +145,30 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
var str string
|
||||
if err := node.Decode(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Value = str
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var sh struct {
|
||||
Sh string
|
||||
key := node.Content[0].Value
|
||||
switch key {
|
||||
case "sh", "ref":
|
||||
var m struct {
|
||||
Sh string
|
||||
Ref string
|
||||
}
|
||||
if err := node.Decode(&m); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Sh = m.Sh
|
||||
v.Ref = m.Ref
|
||||
return nil
|
||||
default:
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("maps cannot be assigned to variables")
|
||||
}
|
||||
if err := node.Decode(&sh); err != nil {
|
||||
return err
|
||||
|
||||
default:
|
||||
var value any
|
||||
if err := node.Decode(&value); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Sh = sh.Sh
|
||||
v.Value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variable", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
@@ -50,9 +50,23 @@ func (c *Cache) key(node Node) string {
|
||||
}
|
||||
|
||||
func (c *Cache) cacheFilePath(node Node) string {
|
||||
return filepath.Join(c.dir, fmt.Sprintf("%s.yaml", c.key(node)))
|
||||
return c.filePath(node, "yaml")
|
||||
}
|
||||
|
||||
func (c *Cache) checksumFilePath(node Node) string {
|
||||
return filepath.Join(c.dir, fmt.Sprintf("%s.checksum", c.key(node)))
|
||||
return c.filePath(node, "checksum")
|
||||
}
|
||||
|
||||
func (c *Cache) filePath(node Node, suffix string) string {
|
||||
lastDir, filename := node.FilenameAndLastDir()
|
||||
prefix := filename
|
||||
// Means it's not "", nor "." nor "/", so it's a valid directory
|
||||
if len(lastDir) > 1 {
|
||||
prefix = fmt.Sprintf("%s-%s", lastDir, filename)
|
||||
}
|
||||
return filepath.Join(c.dir, fmt.Sprintf("%s.%s.%s", prefix, c.key(node), suffix))
|
||||
}
|
||||
|
||||
func (c *Cache) Clear() error {
|
||||
return os.RemoveAll(c.dir)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ type Node interface {
|
||||
Parent() Node
|
||||
Location() string
|
||||
Dir() string
|
||||
Optional() bool
|
||||
Remote() bool
|
||||
ResolveEntrypoint(entrypoint string) (string, error)
|
||||
ResolveDir(dir string) (string, error)
|
||||
FilenameAndLastDir() (string, string)
|
||||
}
|
||||
|
||||
func NewRootNode(
|
||||
@@ -31,9 +31,8 @@ func NewRootNode(
|
||||
timeout time.Duration,
|
||||
) (Node, error) {
|
||||
dir = getDefaultDir(entrypoint, dir)
|
||||
// Check if there is something to read on STDIN
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode()&os.ModeCharDevice) == 0 && stat.Size() > 0 {
|
||||
// If the entrypoint is "-", we read from stdin
|
||||
if entrypoint == "-" {
|
||||
return NewStdinNode(dir)
|
||||
}
|
||||
return NewNode(l, entrypoint, dir, insecure, timeout)
|
||||
|
||||
@@ -2,22 +2,20 @@ package taskfile
|
||||
|
||||
type (
|
||||
NodeOption func(*BaseNode)
|
||||
// BaseNode is a generic node that implements the Parent() and Optional()
|
||||
// methods of the NodeReader interface. It does not implement the Read() method
|
||||
// and it designed to be embedded in other node types so that this boilerplate
|
||||
// code does not need to be repeated.
|
||||
// BaseNode is a generic node that implements the Parent() methods of the
|
||||
// NodeReader interface. It does not implement the Read() method and it
|
||||
// designed to be embedded in other node types so that this boilerplate code
|
||||
// does not need to be repeated.
|
||||
BaseNode struct {
|
||||
parent Node
|
||||
optional bool
|
||||
dir string
|
||||
parent Node
|
||||
dir string
|
||||
}
|
||||
)
|
||||
|
||||
func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
|
||||
node := &BaseNode{
|
||||
parent: nil,
|
||||
optional: false,
|
||||
dir: dir,
|
||||
parent: nil,
|
||||
dir: dir,
|
||||
}
|
||||
|
||||
// Apply options
|
||||
@@ -38,16 +36,6 @@ func (node *BaseNode) Parent() Node {
|
||||
return node.parent
|
||||
}
|
||||
|
||||
func WithOptional(optional bool) NodeOption {
|
||||
return func(node *BaseNode) {
|
||||
node.optional = optional
|
||||
}
|
||||
}
|
||||
|
||||
func (node *BaseNode) Optional() bool {
|
||||
return node.optional
|
||||
}
|
||||
|
||||
func (node *BaseNode) Dir() string {
|
||||
return node.dir
|
||||
}
|
||||
|
||||
@@ -112,3 +112,7 @@ func (node *FileNode) ResolveDir(dir string) (string, error) {
|
||||
entrypointDir := filepath.Dir(node.Entrypoint)
|
||||
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||
}
|
||||
|
||||
func (node *FileNode) FilenameAndLastDir() (string, string) {
|
||||
return "", filepath.Base(node.Entrypoint)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
@@ -110,3 +114,8 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) {
|
||||
entrypointDir := filepath.Dir(node.Dir())
|
||||
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||
}
|
||||
|
||||
func (node *HTTPNode) FilenameAndLastDir() (string, string) {
|
||||
dir, filename := filepath.Split(node.URL.Path)
|
||||
return filepath.Base(dir), filename
|
||||
}
|
||||
|
||||
@@ -72,3 +72,7 @@ func (node *StdinNode) ResolveDir(dir string) (string, error) {
|
||||
|
||||
return filepathext.SmartJoin(node.Dir(), path), nil
|
||||
}
|
||||
|
||||
func (node *StdinNode) FilenameAndLastDir() (string, string) {
|
||||
return "", "__stdin__"
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/dominikbraun/graph"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
@@ -24,38 +27,89 @@ Continue?`
|
||||
Continue?`
|
||||
)
|
||||
|
||||
// Read reads a Read for a given directory
|
||||
// Uses current dir when dir is left empty. Uses Read.yml
|
||||
// or Read.yaml when entrypoint is left empty
|
||||
func Read(
|
||||
// A Reader will recursively read Taskfiles from a given source using a directed
|
||||
// acyclic graph (DAG).
|
||||
type Reader struct {
|
||||
graph *ast.TaskfileGraph
|
||||
node Node
|
||||
insecure bool
|
||||
download bool
|
||||
offline bool
|
||||
timeout time.Duration
|
||||
tempDir string
|
||||
logger *logger.Logger
|
||||
}
|
||||
|
||||
func NewReader(
|
||||
node Node,
|
||||
insecure bool,
|
||||
download bool,
|
||||
offline bool,
|
||||
timeout time.Duration,
|
||||
tempDir string,
|
||||
l *logger.Logger,
|
||||
) (*ast.Taskfile, error) {
|
||||
var _taskfile func(Node) (*ast.Taskfile, error)
|
||||
_taskfile = func(node Node) (*ast.Taskfile, error) {
|
||||
tf, err := readTaskfile(node, download, offline, timeout, tempDir, l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger *logger.Logger,
|
||||
) *Reader {
|
||||
return &Reader{
|
||||
graph: ast.NewTaskfileGraph(),
|
||||
node: node,
|
||||
insecure: insecure,
|
||||
download: download,
|
||||
offline: offline,
|
||||
timeout: timeout,
|
||||
tempDir: tempDir,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the Taskfile is set and has a schema version
|
||||
if tf == nil || tf.Version == nil {
|
||||
return nil, &errors.TaskfileVersionCheckError{URI: node.Location()}
|
||||
}
|
||||
func (r *Reader) Read() (*ast.TaskfileGraph, error) {
|
||||
// Recursively loop through each Taskfile, adding vertices/edges to the graph
|
||||
if err := r.include(r.node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tf.Includes.Range(func(namespace string, include ast.Include) error {
|
||||
cache := &templater.Cache{Vars: tf.Vars}
|
||||
include = ast.Include{
|
||||
return r.graph, nil
|
||||
}
|
||||
|
||||
func (r *Reader) include(node Node) error {
|
||||
// Create a new vertex for the Taskfile
|
||||
vertex := &ast.TaskfileVertex{
|
||||
URI: node.Location(),
|
||||
Taskfile: nil,
|
||||
}
|
||||
|
||||
// Add the included Taskfile to the DAG
|
||||
// If the vertex already exists, we return early since its Taskfile has
|
||||
// already been read and its children explored
|
||||
if err := r.graph.AddVertex(vertex); err == graph.ErrVertexAlreadyExists {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read and parse the Taskfile from the file and add it to the vertex
|
||||
var err error
|
||||
vertex.Taskfile, err = r.readNode(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create an error group to wait for all included Taskfiles to be read
|
||||
var g errgroup.Group
|
||||
|
||||
// Loop over each included taskfile
|
||||
_ = vertex.Taskfile.Includes.Range(func(namespace string, include *ast.Include) error {
|
||||
vars := compiler.GetEnviron()
|
||||
vars.Merge(vertex.Taskfile.Vars, nil)
|
||||
// Start a goroutine to process each included Taskfile
|
||||
g.Go(func() error {
|
||||
cache := &templater.Cache{Vars: vars}
|
||||
include = &ast.Include{
|
||||
Namespace: include.Namespace,
|
||||
Taskfile: templater.Replace(include.Taskfile, cache),
|
||||
Dir: templater.Replace(include.Dir, cache),
|
||||
Optional: include.Optional,
|
||||
Internal: include.Internal,
|
||||
Flatten: include.Flatten,
|
||||
Aliases: include.Aliases,
|
||||
AdvancedImport: include.AdvancedImport,
|
||||
Vars: include.Vars,
|
||||
@@ -69,14 +123,13 @@ func Read(
|
||||
return err
|
||||
}
|
||||
|
||||
dir, err := node.ResolveDir(include.Dir)
|
||||
include.Dir, err = node.ResolveDir(include.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
includeReaderNode, err := NewNode(l, entrypoint, dir, insecure, timeout,
|
||||
includeNode, err := NewNode(r.logger, entrypoint, include.Dir, r.insecure, r.timeout,
|
||||
WithParent(node),
|
||||
WithOptional(include.Optional),
|
||||
)
|
||||
if err != nil {
|
||||
if include.Optional {
|
||||
@@ -85,123 +138,90 @@ func Read(
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkCircularIncludes(includeReaderNode); err != nil {
|
||||
// Recurse into the included Taskfile
|
||||
if err := r.include(includeNode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
includedTaskfile, err := _taskfile(includeReaderNode)
|
||||
if err != nil {
|
||||
if include.Optional {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
// Create an edge between the Taskfiles
|
||||
r.graph.Lock()
|
||||
defer r.graph.Unlock()
|
||||
edge, err := r.graph.Edge(node.Location(), includeNode.Location())
|
||||
if err == graph.ErrEdgeNotFound {
|
||||
// If the edge doesn't exist, create it
|
||||
err = r.graph.AddEdge(
|
||||
node.Location(),
|
||||
includeNode.Location(),
|
||||
graph.EdgeData([]*ast.Include{include}),
|
||||
graph.EdgeWeight(1),
|
||||
)
|
||||
} else {
|
||||
// If the edge already exists
|
||||
edgeData := append(edge.Properties.Data.([]*ast.Include), include)
|
||||
err = r.graph.UpdateEdge(
|
||||
node.Location(),
|
||||
includeNode.Location(),
|
||||
graph.EdgeData(edgeData),
|
||||
graph.EdgeWeight(len(edgeData)),
|
||||
)
|
||||
}
|
||||
|
||||
if len(includedTaskfile.Dotenv) > 0 {
|
||||
return ErrIncludedTaskfilesCantHaveDotenvs
|
||||
}
|
||||
|
||||
if include.AdvancedImport {
|
||||
// nolint: errcheck
|
||||
includedTaskfile.Vars.Range(func(k string, v ast.Var) error {
|
||||
o := v
|
||||
o.Dir = dir
|
||||
includedTaskfile.Vars.Set(k, o)
|
||||
return nil
|
||||
})
|
||||
// nolint: errcheck
|
||||
includedTaskfile.Env.Range(func(k string, v ast.Var) error {
|
||||
o := v
|
||||
o.Dir = dir
|
||||
includedTaskfile.Env.Set(k, o)
|
||||
return nil
|
||||
})
|
||||
|
||||
for _, task := range includedTaskfile.Tasks.Values() {
|
||||
task.Dir = filepathext.SmartJoin(dir, task.Dir)
|
||||
if task.IncludeVars == nil {
|
||||
task.IncludeVars = &ast.Vars{}
|
||||
}
|
||||
task.IncludeVars.Merge(include.Vars)
|
||||
task.IncludedTaskfileVars = includedTaskfile.Vars
|
||||
if errors.Is(err, graph.ErrEdgeCreatesCycle) {
|
||||
return errors.TaskfileCycleError{
|
||||
Source: node.Location(),
|
||||
Destination: includeNode.Location(),
|
||||
}
|
||||
}
|
||||
|
||||
if err = tf.Merge(includedTaskfile, &include); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
for _, task := range tf.Tasks.Values() {
|
||||
// If the task is not defined, create a new one
|
||||
if task == nil {
|
||||
task = &ast.Task{}
|
||||
}
|
||||
// Set the location of the taskfile for each task
|
||||
if task.Location.Taskfile == "" {
|
||||
task.Location.Taskfile = tf.Location
|
||||
}
|
||||
}
|
||||
|
||||
return tf, nil
|
||||
}
|
||||
return _taskfile(node)
|
||||
// Wait for all the go routines to finish
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func readTaskfile(
|
||||
node Node,
|
||||
download,
|
||||
offline bool,
|
||||
timeout time.Duration,
|
||||
tempDir string,
|
||||
l *logger.Logger,
|
||||
) (*ast.Taskfile, error) {
|
||||
func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
|
||||
var b []byte
|
||||
var err error
|
||||
var cache *Cache
|
||||
|
||||
if node.Remote() {
|
||||
cache, err = NewCache(tempDir)
|
||||
cache, err = NewCache(r.tempDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If the file is remote and we're in offline mode, check if we have a cached copy
|
||||
if node.Remote() && offline {
|
||||
if node.Remote() && r.offline {
|
||||
if b, err = cache.read(node); errors.Is(err, os.ErrNotExist) {
|
||||
return nil, &errors.TaskfileCacheNotFoundError{URI: node.Location()}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.VerboseOutf(logger.Magenta, "task: [%s] Fetched cached copy\n", node.Location())
|
||||
|
||||
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Fetched cached copy\n", node.Location())
|
||||
} else {
|
||||
|
||||
downloaded := false
|
||||
ctx, cf := context.WithTimeout(context.Background(), timeout)
|
||||
ctx, cf := context.WithTimeout(context.Background(), r.timeout)
|
||||
defer cf()
|
||||
|
||||
// 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 download {
|
||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: timeout}
|
||||
if r.download {
|
||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout}
|
||||
}
|
||||
// Search for any cached copies
|
||||
if b, err = cache.read(node); errors.Is(err, os.ErrNotExist) {
|
||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: timeout, CheckedCache: true}
|
||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout, CheckedCache: true}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.VerboseOutf(logger.Magenta, "task: [%s] Network timeout. Fetched cached copy\n", node.Location())
|
||||
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Network timeout. Fetched cached copy\n", node.Location())
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@@ -210,7 +230,7 @@ func readTaskfile(
|
||||
|
||||
// If the node was remote, we need to check the checksum
|
||||
if node.Remote() && downloaded {
|
||||
l.VerboseOutf(logger.Magenta, "task: [%s] Fetched remote copy\n", node.Location())
|
||||
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Fetched remote copy\n", node.Location())
|
||||
|
||||
// Get the checksums
|
||||
checksum := checksum(b)
|
||||
@@ -225,7 +245,7 @@ func readTaskfile(
|
||||
prompt = fmt.Sprintf(taskfileChangedPrompt, node.Location())
|
||||
}
|
||||
if prompt != "" {
|
||||
if err := l.Prompt(logger.Yellow, prompt, "n", "y", "yes"); err != nil {
|
||||
if err := r.logger.Prompt(logger.Yellow, prompt, "n", "y", "yes"); err != nil {
|
||||
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
||||
}
|
||||
}
|
||||
@@ -237,7 +257,7 @@ func readTaskfile(
|
||||
return nil, err
|
||||
}
|
||||
// Cache the file
|
||||
l.VerboseOutf(logger.Magenta, "task: [%s] Caching downloaded file\n", node.Location())
|
||||
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Caching downloaded file\n", node.Location())
|
||||
if err = cache.write(node, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -245,33 +265,33 @@ func readTaskfile(
|
||||
}
|
||||
}
|
||||
|
||||
var t ast.Taskfile
|
||||
if err := yaml.Unmarshal(b, &t); err != nil {
|
||||
var tf ast.Taskfile
|
||||
if err := yaml.Unmarshal(b, &tf); err != nil {
|
||||
// Decode the taskfile and add the file info the any errors
|
||||
taskfileInvalidErr := &errors.TaskfileDecodeError{}
|
||||
if errors.As(err, &taskfileInvalidErr) {
|
||||
return nil, taskfileInvalidErr.WithFileInfo(node.Location(), b, 2)
|
||||
}
|
||||
return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(node.Location()), Err: err}
|
||||
}
|
||||
t.Location = node.Location()
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
// Check that the Taskfile is set and has a schema version
|
||||
if tf.Version == nil {
|
||||
return nil, &errors.TaskfileVersionCheckError{URI: node.Location()}
|
||||
}
|
||||
|
||||
func checkCircularIncludes(node Node) error {
|
||||
if node == nil {
|
||||
return errors.New("task: failed to check for include cycle: node was nil")
|
||||
}
|
||||
if node.Parent() == nil {
|
||||
return errors.New("task: failed to check for include cycle: node.Parent was nil")
|
||||
}
|
||||
curNode := node
|
||||
location := node.Location()
|
||||
for curNode.Parent() != nil {
|
||||
curNode = curNode.Parent()
|
||||
curLocation := curNode.Location()
|
||||
if curLocation == location {
|
||||
return fmt.Errorf("task: include cycle detected between %s <--> %s",
|
||||
curLocation,
|
||||
node.Parent().Location(),
|
||||
)
|
||||
// Set the taskfile/task's locations
|
||||
tf.Location = node.Location()
|
||||
for _, task := range tf.Tasks.Values() {
|
||||
// If the task is not defined, create a new one
|
||||
if task == nil {
|
||||
task = &ast.Task{}
|
||||
}
|
||||
// Set the location of the taskfile for each task
|
||||
if task.Location.Taskfile == "" {
|
||||
task.Location.Taskfile = tf.Location
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
return &tf, nil
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -16,9 +17,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
||||
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
||||
|
||||
defaultTaskfiles = []string{
|
||||
"Taskfile.yml",
|
||||
"taskfile.yml",
|
||||
@@ -29,7 +27,6 @@ var (
|
||||
"Taskfile.dist.yaml",
|
||||
"taskfile.dist.yaml",
|
||||
}
|
||||
|
||||
allowedContentTypes = []string{
|
||||
"text/plain",
|
||||
"text/yaml",
|
||||
@@ -44,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 {
|
||||
@@ -54,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()
|
||||
|
||||
13
testdata/desc/Taskfile.yml
vendored
Normal file
13
testdata/desc/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
version: 3
|
||||
tasks:
|
||||
build:
|
||||
aliases:
|
||||
- b
|
||||
desc: |
|
||||
Multi-line escription with alias which is super long long long long long long
|
||||
another line
|
||||
third line long long long long long long long long
|
||||
test:
|
||||
aliases:
|
||||
- t
|
||||
desc: Single line description with alias
|
||||
19
testdata/env/Taskfile.yml
vendored
19
testdata/env/Taskfile.yml
vendored
@@ -8,12 +8,15 @@ env:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BAZ: "{{.BAZ}}"
|
||||
QUX: from_taskfile
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: local
|
||||
- task: global
|
||||
- task: not-overriden
|
||||
- task: multiple_type
|
||||
|
||||
local:
|
||||
vars:
|
||||
@@ -31,3 +34,19 @@ tasks:
|
||||
BAR: overriden
|
||||
cmds:
|
||||
- echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > global.txt
|
||||
|
||||
multiple_type:
|
||||
env:
|
||||
FOO: 1
|
||||
BAR: true
|
||||
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
|
||||
|
||||
25
testdata/exit_code/Taskfile.yml
vendored
Normal file
25
testdata/exit_code/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
version: '3'
|
||||
|
||||
silent: true
|
||||
|
||||
vars:
|
||||
PREFIX: EXIT_CODE=
|
||||
|
||||
tasks:
|
||||
exit-zero:
|
||||
vars:
|
||||
FOO: bar
|
||||
DYNAMIC_FOO:
|
||||
sh: echo 'bar'
|
||||
cmds:
|
||||
- defer: echo FOO={{.FOO}} - DYNAMIC_FOO={{.DYNAMIC_FOO}} - {{.PREFIX}}{{.EXIT_CODE}}
|
||||
- exit 0
|
||||
|
||||
exit-one:
|
||||
vars:
|
||||
FOO: bar
|
||||
DYNAMIC_FOO:
|
||||
sh: echo 'bar'
|
||||
cmds:
|
||||
- defer: echo FOO={{.FOO}} - DYNAMIC_FOO={{.DYNAMIC_FOO}} - {{.PREFIX}}{{.EXIT_CODE}}
|
||||
- exit 1
|
||||
8
testdata/for/cmds/Taskfile.yml
vendored
8
testdata/for/cmds/Taskfile.yml
vendored
@@ -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:
|
||||
|
||||
10
testdata/for/deps/Taskfile.yml
vendored
10
testdata/for/deps/Taskfile.yml
vendored
@@ -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:
|
||||
|
||||
6
testdata/include_with_vars/Taskfile.yml
vendored
6
testdata/include_with_vars/Taskfile.yml
vendored
@@ -2,15 +2,15 @@ version: "3"
|
||||
|
||||
includes:
|
||||
included1:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
taskfile: include/Taskfile.include1.yml
|
||||
vars:
|
||||
VAR_1: included1-var1
|
||||
included2:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
taskfile: include/Taskfile.include2.yml
|
||||
vars:
|
||||
VAR_1: included2-var1
|
||||
included3:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
taskfile: include/Taskfile.include3.yml
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
|
||||
11
testdata/include_with_vars/include/Taskfile.include2.yml
vendored
Normal file
11
testdata/include_with_vars/include/Taskfile.include2.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
VAR_1: '{{.VAR_1 | default "included-default-var1"}}'
|
||||
VAR_2: '{{.VAR_2 | default "included-default-var2"}}'
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
cmds:
|
||||
- echo "VAR_1 is {{.VAR_1}}"
|
||||
- echo "VAR_2 is {{.VAR_2}}"
|
||||
11
testdata/include_with_vars/include/Taskfile.include3.yml
vendored
Normal file
11
testdata/include_with_vars/include/Taskfile.include3.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
VAR_1: '{{.VAR_1 | default "included-default-var1"}}'
|
||||
VAR_2: '{{.VAR_2 | default "included-default-var2"}}'
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
cmds:
|
||||
- echo "VAR_1 is {{.VAR_1}}"
|
||||
- echo "VAR_2 is {{.VAR_2}}"
|
||||
12
testdata/included_taskfile_var_merging/Taskfile.yaml
vendored
Normal file
12
testdata/included_taskfile_var_merging/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: "3"
|
||||
|
||||
includes:
|
||||
foo:
|
||||
taskfile: ./foo/Taskfile.yaml
|
||||
bar:
|
||||
taskfile: ./bar/Taskfile.yaml
|
||||
|
||||
tasks:
|
||||
stub:
|
||||
cmds:
|
||||
- echo 0
|
||||
11
testdata/included_taskfile_var_merging/bar/Taskfile.yaml
vendored
Normal file
11
testdata/included_taskfile_var_merging/bar/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
DIR: bar
|
||||
|
||||
tasks:
|
||||
pwd:
|
||||
dir: ./{{ .DIR }}
|
||||
cmds:
|
||||
- echo "{{ .DIR }}"
|
||||
- pwd
|
||||
11
testdata/included_taskfile_var_merging/foo/Taskfile.yaml
vendored
Normal file
11
testdata/included_taskfile_var_merging/foo/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
DIR: foo
|
||||
|
||||
tasks:
|
||||
pwd:
|
||||
dir: ./{{ .DIR }}
|
||||
cmds:
|
||||
- echo "{{ .DIR }}"
|
||||
- pwd
|
||||
1
testdata/includes_flatten/.gitignore
vendored
Normal file
1
testdata/includes_flatten/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.txt
|
||||
12
testdata/includes_flatten/Taskfile.multiple.yml
vendored
Normal file
12
testdata/includes_flatten/Taskfile.multiple.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: ./included
|
||||
flatten: true
|
||||
|
||||
tasks:
|
||||
gen:
|
||||
cmds:
|
||||
- echo "gen multiple"
|
||||
|
||||
3
testdata/includes_flatten/Taskfile.with_default.yml
vendored
Normal file
3
testdata/includes_flatten/Taskfile.with_default.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
version: '3'
|
||||
tasks:
|
||||
default: echo "default from included flatten"
|
||||
15
testdata/includes_flatten/Taskfile.yml
vendored
Normal file
15
testdata/includes_flatten/Taskfile.yml
vendored
Normal 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"
|
||||
|
||||
|
||||
23
testdata/includes_flatten/included/Taskfile.yml
vendored
Normal file
23
testdata/includes_flatten/included/Taskfile.yml
vendored
Normal 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
|
||||
6
testdata/includes_flatten/nested/Taskfile.yml
vendored
Normal file
6
testdata/includes_flatten/nested/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
from_nested:
|
||||
cmds:
|
||||
- echo "from nested"
|
||||
10
testdata/includes_interpolation/Taskfile.yml
vendored
10
testdata/includes_interpolation/Taskfile.yml
vendored
@@ -1,10 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
MODULE_NAME: included
|
||||
|
||||
includes:
|
||||
include: './{{.MODULE_NAME}}/Taskfile.yml'
|
||||
include-with-dir:
|
||||
taskfile: './{{.MODULE_NAME}}/Taskfile.yml'
|
||||
dir: '{{.MODULE_NAME}}'
|
||||
7
testdata/includes_interpolation/include/Taskfile.yml
vendored
Normal file
7
testdata/includes_interpolation/include/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
MODULE_NAME: included
|
||||
|
||||
includes:
|
||||
include: '../{{.MODULE_NAME}}/Taskfile.yml'
|
||||
9
testdata/includes_interpolation/include_with_dir/Taskfile.yml
vendored
Normal file
9
testdata/includes_interpolation/include_with_dir/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
MODULE_NAME: included
|
||||
|
||||
includes:
|
||||
include-with-dir:
|
||||
taskfile: '../{{.MODULE_NAME}}/Taskfile.yml'
|
||||
dir: '../{{.MODULE_NAME}}'
|
||||
4
testdata/includes_interpolation/include_with_env_variable/Taskfile.yml
vendored
Normal file
4
testdata/includes_interpolation/include_with_env_variable/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: "3"
|
||||
|
||||
includes:
|
||||
include-with-env-variable: '../{{.MODULE}}/Taskfile.yml'
|
||||
@@ -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"
|
||||
|
||||
11
testdata/run_once_shared_deps/Taskfile.yml
vendored
Normal file
11
testdata/run_once_shared_deps/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
service-a: ./service-a
|
||||
service-b: ./service-b
|
||||
|
||||
tasks:
|
||||
build:
|
||||
deps:
|
||||
- service-a:build
|
||||
- service-b:build
|
||||
9
testdata/run_once_shared_deps/library/Taskfile.yml
vendored
Normal file
9
testdata/run_once_shared_deps/library/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
run: once
|
||||
cmds:
|
||||
- echo "build library"
|
||||
sources:
|
||||
- src/**/*
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user