mirror of
https://github.com/go-task/task.git
synced 2026-06-24 13:15:51 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,6 +10,9 @@
|
|||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
# Graphvis files
|
||||||
|
*.gv
|
||||||
|
|
||||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
.glide/
|
.glide/
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ linters:
|
|||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
goimports:
|
goimports:
|
||||||
local-prefixes: github.com/go-task/task
|
local-prefixes: github.com/go-task
|
||||||
gofmt:
|
gofmt:
|
||||||
rewrite-rules:
|
rewrite-rules:
|
||||||
- pattern: 'interface{}'
|
- pattern: 'interface{}'
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ brews:
|
|||||||
description: Task runner / simpler Make alternative written in Go
|
description: Task runner / simpler Make alternative written in Go
|
||||||
license: MIT
|
license: MIT
|
||||||
homepage: https://taskfile.dev
|
homepage: https://taskfile.dev
|
||||||
folder: Formula
|
directory: Formula
|
||||||
repository:
|
repository:
|
||||||
owner: go-task
|
owner: go-task
|
||||||
name: homebrew-tap
|
name: homebrew-tap
|
||||||
|
|||||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,5 +1,37 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
@pd03).
|
||||||
|
- 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
|
## v3.36.0 - 2024-04-08
|
||||||
|
|
||||||
- Added support for
|
- Added support for
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -44,8 +43,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
log.SetFlags(0)
|
logger := &logger.Logger{
|
||||||
log.SetOutput(os.Stderr)
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
Verbose: flags.Verbose,
|
||||||
|
Color: flags.Color,
|
||||||
|
}
|
||||||
|
|
||||||
if err := flags.Validate(); err != nil {
|
if err := flags.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -65,22 +68,16 @@ func run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if flags.Experiments {
|
if flags.Experiments {
|
||||||
l := &logger.Logger{
|
return experiments.List(logger)
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
Verbose: flags.Verbose,
|
|
||||||
Color: flags.Color,
|
|
||||||
}
|
|
||||||
return experiments.List(l)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags.Init {
|
if flags.Init {
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
if err := task.InitTaskfile(os.Stdout, wd); err != nil {
|
if err := task.InitTaskfile(os.Stdout, wd); err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -138,6 +135,10 @@ func run() error {
|
|||||||
return err
|
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
|
// If the download flag is specified, we should stop execution as soon as
|
||||||
// taskfile is downloaded
|
// taskfile is downloaded
|
||||||
if flags.Download {
|
if flags.Download {
|
||||||
@@ -178,7 +179,7 @@ func run() error {
|
|||||||
|
|
||||||
globals.Set("CLI_ARGS", ast.Var{Value: cliArgs})
|
globals.Set("CLI_ARGS", ast.Var{Value: cliArgs})
|
||||||
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||||
e.Taskfile.Vars.Merge(globals)
|
e.Taskfile.Vars.Merge(globals, nil)
|
||||||
|
|
||||||
if !flags.Watch {
|
if !flags.Watch {
|
||||||
e.InterceptInterruptSignals()
|
e.InterceptInterruptSignals()
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ function __task_list() {
|
|||||||
local taskfile item task desc
|
local taskfile item task desc
|
||||||
|
|
||||||
cmd=(task)
|
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
|
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||||
enabled=1
|
enabled=1
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ const (
|
|||||||
CodeTaskfileCacheNotFound
|
CodeTaskfileCacheNotFound
|
||||||
CodeTaskfileVersionCheckError
|
CodeTaskfileVersionCheckError
|
||||||
CodeTaskfileNetworkTimeout
|
CodeTaskfileNetworkTimeout
|
||||||
|
_ // CodeTaskfileDuplicateInclude
|
||||||
|
CodeTaskfileCycle
|
||||||
)
|
)
|
||||||
|
|
||||||
// Task related exit codes
|
// Task related exit codes
|
||||||
|
|||||||
@@ -174,3 +174,21 @@ func (err *TaskfileNetworkTimeoutError) Error() string {
|
|||||||
func (err *TaskfileNetworkTimeoutError) Code() int {
|
func (err *TaskfileNetworkTimeoutError) Code() int {
|
||||||
return CodeTaskfileNetworkTimeout
|
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
|
||||||
|
}
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -1,12 +1,14 @@
|
|||||||
module github.com/go-task/task/v3
|
module github.com/go-task/task/v3
|
||||||
|
|
||||||
go 1.21
|
go 1.21.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.2.1
|
github.com/Masterminds/semver/v3 v3.2.1
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
github.com/dominikbraun/graph v0.23.0
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||||
|
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mattn/go-zglob v0.0.4
|
github.com/mattn/go-zglob v0.0.4
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -4,12 +4,16 @@ github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
|||||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||||
|
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
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/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 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
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/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
|
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90 h1:JBbiZ2CXIZ9Upe3O2yI5+3ksWoa7hNVNi4BINs8TIrs=
|
||||||
|
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90/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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
|||||||
cache := &templater.Cache{Vars: result}
|
cache := &templater.Cache{Vars: result}
|
||||||
// Replace values
|
// Replace values
|
||||||
newVar := templater.ReplaceVar(v, cache)
|
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
|
// 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
|
// This stops empty interface errors when using the templater to replace values later
|
||||||
if !evaluateShVars && newVar.Value == nil {
|
if !evaluateShVars && newVar.Value == nil {
|
||||||
|
|||||||
16
internal/env/env.go
vendored
16
internal/env/env.go
vendored
@@ -13,19 +13,25 @@ func Get(t *ast.Task) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
environ := os.Environ()
|
environ := os.Environ()
|
||||||
|
|
||||||
for k, v := range t.Env.ToCacheMap() {
|
for k, v := range t.Env.ToCacheMap() {
|
||||||
str, isString := v.(string)
|
if !isTypeAllowed(v) {
|
||||||
if !isString {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
environ = append(environ, fmt.Sprintf("%s=%v", k, v))
|
||||||
environ = append(environ, fmt.Sprintf("%s=%s", k, str))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return environ
|
return environ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isTypeAllowed(v any) bool {
|
||||||
|
switch v.(type) {
|
||||||
|
case string, bool, int, float32, float64:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ func IsExitError(err error) bool {
|
|||||||
func Expand(s string) (string, error) {
|
func Expand(s string) (string, error) {
|
||||||
s = filepath.ToSlash(s)
|
s = filepath.ToSlash(s)
|
||||||
s = strings.ReplaceAll(s, " ", `\ `)
|
s = strings.ReplaceAll(s, " ", `\ `)
|
||||||
|
s = strings.ReplaceAll(s, "&", `\&`)
|
||||||
|
s = strings.ReplaceAll(s, "(", `\(`)
|
||||||
|
s = strings.ReplaceAll(s, ")", `\)`)
|
||||||
fields, err := shell.Fields(s, nil)
|
fields, err := shell.Fields(s, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ var (
|
|||||||
GentleForce Experiment
|
GentleForce Experiment
|
||||||
RemoteTaskfiles Experiment
|
RemoteTaskfiles Experiment
|
||||||
AnyVariables Experiment
|
AnyVariables Experiment
|
||||||
|
MapVariables Experiment
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -35,6 +36,7 @@ func init() {
|
|||||||
GentleForce = New("GENTLE_FORCE")
|
GentleForce = New("GENTLE_FORCE")
|
||||||
RemoteTaskfiles = New("REMOTE_TASKFILES")
|
RemoteTaskfiles = New("REMOTE_TASKFILES")
|
||||||
AnyVariables = New("ANY_VARIABLES", "1", "2")
|
AnyVariables = New("ANY_VARIABLES", "1", "2")
|
||||||
|
MapVariables = New("MAP_VARIABLES", "1", "2")
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(xName string, enabledValues ...string) Experiment {
|
func New(xName string, enabledValues ...string) Experiment {
|
||||||
@@ -101,6 +103,6 @@ func List(l *logger.Logger) error {
|
|||||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, ' ', 0)
|
||||||
printExperiment(w, l, GentleForce)
|
printExperiment(w, l, GentleForce)
|
||||||
printExperiment(w, l, RemoteTaskfiles)
|
printExperiment(w, l, RemoteTaskfiles)
|
||||||
printExperiment(w, l, AnyVariables)
|
printExperiment(w, l, MapVariables)
|
||||||
return w.Flush()
|
return w.Flush()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package flags
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -68,6 +69,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
pflag.Usage = func() {
|
pflag.Usage = func() {
|
||||||
log.Print(usage)
|
log.Print(usage)
|
||||||
pflag.PrintDefaults()
|
pflag.PrintDefaults()
|
||||||
|
|||||||
@@ -138,6 +138,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 {
|
func (l *Logger) Prompt(color Color, prompt string, defaultValue string, continueValues ...string) error {
|
||||||
if l.AssumeYes {
|
if l.AssumeYes {
|
||||||
l.Outf(color, "%s [assuming yes]\n", prompt)
|
l.Outf(color, "%s [assuming yes]\n", prompt)
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"mvdan.cc/sh/v3/shell"
|
"mvdan.cc/sh/v3/shell"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
sprig "github.com/go-task/slim-sprig/v3"
|
sprig "github.com/go-task/slim-sprig/v3"
|
||||||
|
"github.com/go-task/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
var templateFuncs template.FuncMap
|
var templateFuncs template.FuncMap
|
||||||
@@ -73,12 +73,16 @@ func init() {
|
|||||||
return spew.Sdump(v)
|
return spew.Sdump(v)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// aliases
|
||||||
|
taskFuncs["q"] = taskFuncs["shellQuote"]
|
||||||
|
|
||||||
// Deprecated aliases for renamed functions.
|
// Deprecated aliases for renamed functions.
|
||||||
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
|
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
|
||||||
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
||||||
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
||||||
|
|
||||||
templateFuncs = sprig.TxtFuncMap()
|
templateFuncs = template.FuncMap(sprig.TxtFuncMap())
|
||||||
for k, v := range taskFuncs {
|
for k, v := range taskFuncs {
|
||||||
templateFuncs[k] = v
|
templateFuncs[k] = v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"maps"
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/deepcopy"
|
"github.com/go-task/task/v3/internal/deepcopy"
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"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
|
// Cache is a help struct that allow us to call "replaceX" funcs multiple
|
||||||
@@ -29,6 +29,25 @@ func (r *Cache) Err() error {
|
|||||||
return r.err
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := template.ResolveRef(ref, cache.cacheMap)
|
||||||
|
if err != nil {
|
||||||
|
cache.err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
func Replace[T any](v T, cache *Cache) T {
|
func Replace[T any](v T, cache *Cache) T {
|
||||||
return ReplaceWithExtra(v, cache, nil)
|
return ReplaceWithExtra(v, cache, nil)
|
||||||
}
|
}
|
||||||
@@ -91,6 +110,9 @@ func ReplaceVar(v ast.Var, cache *Cache) ast.Var {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) 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{
|
return ast.Var{
|
||||||
Value: ReplaceWithExtra(v.Value, cache, extra),
|
Value: ReplaceWithExtra(v.Value, cache, extra),
|
||||||
Sh: ReplaceWithExtra(v.Sh, cache, extra),
|
Sh: ReplaceWithExtra(v.Sh, cache, extra),
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@go-task/cli",
|
"name": "@go-task/cli",
|
||||||
"version": "3.36.0",
|
"version": "3.37.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@go-task/cli",
|
"name": "@go-task/cli",
|
||||||
"version": "3.36.0",
|
"version": "3.37.2",
|
||||||
"description": "A task runner / simpler Make alternative written in Go",
|
"description": "A task runner / simpler Make alternative written in Go",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "go-npm install",
|
"postinstall": "go-npm install",
|
||||||
|
|||||||
7
setup.go
7
setup.go
@@ -63,8 +63,7 @@ func (e *Executor) getRootNode() (taskfile.Node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) readTaskfile(node taskfile.Node) error {
|
func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||||
var err error
|
reader := taskfile.NewReader(
|
||||||
e.Taskfile, err = taskfile.Read(
|
|
||||||
node,
|
node,
|
||||||
e.Insecure,
|
e.Insecure,
|
||||||
e.Download,
|
e.Download,
|
||||||
@@ -73,9 +72,13 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
|
|||||||
e.TempDir,
|
e.TempDir,
|
||||||
e.Logger,
|
e.Logger,
|
||||||
)
|
)
|
||||||
|
graph, err := reader.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if e.Taskfile, err = graph.Merge(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
50
task_test.go
50
task_test.go
@@ -95,14 +95,24 @@ func TestEmptyTask(t *testing.T) {
|
|||||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
|
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) {
|
func TestEnv(t *testing.T) {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
Dir: "testdata/env",
|
Dir: "testdata/env",
|
||||||
Target: "default",
|
Target: "default",
|
||||||
TrimSpace: false,
|
TrimSpace: false,
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
||||||
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
|
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
|
||||||
|
"multiple_type.txt": "FOO='1' BAR='true' BAZ='1.1'\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
@@ -1199,15 +1209,17 @@ func TestIncludesInterpolation(t *testing.T) {
|
|||||||
expectedErr bool
|
expectedErr bool
|
||||||
expectedOutput string
|
expectedOutput string
|
||||||
}{
|
}{
|
||||||
{"include", "include", false, "includes_interpolation\n"},
|
{"include", "include", false, "include\n"},
|
||||||
{"include with dir", "include-with-dir", false, "included\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 {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
Dir: dir,
|
Dir: filepath.Join(dir, test.name),
|
||||||
Stdout: &buff,
|
Stdout: &buff,
|
||||||
Stderr: &buff,
|
Stderr: &buff,
|
||||||
Silent: true,
|
Silent: true,
|
||||||
@@ -1225,6 +1237,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) {
|
func TestInternalTask(t *testing.T) {
|
||||||
const dir = "testdata/internal_task"
|
const dir = "testdata/internal_task"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ type Include struct {
|
|||||||
|
|
||||||
// Includes represents information about included tasksfiles
|
// Includes represents information about included tasksfiles
|
||||||
type Includes struct {
|
type Includes struct {
|
||||||
omap.OrderedMap[string, Include]
|
omap.OrderedMap[string, *Include]
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||||
@@ -41,7 +41,7 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.Namespace = keyNode.Value
|
v.Namespace = keyNode.Value
|
||||||
includes.Set(keyNode.Value, v)
|
includes.Set(keyNode.Value, &v)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ func (includes *Includes) Len() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper around OrderedMap.Set to ensure we don't get nil pointer errors
|
// 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 {
|
if includes == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,6 +14,9 @@ const NamespaceSeparator = ":"
|
|||||||
|
|
||||||
var V3 = semver.MustParse("3")
|
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
|
// Taskfile is the abstract syntax tree for a Taskfile
|
||||||
type Taskfile struct {
|
type Taskfile struct {
|
||||||
Location string
|
Location string
|
||||||
@@ -36,6 +40,9 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
|||||||
if !t1.Version.Equal(t2.Version) {
|
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)
|
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() {
|
if t2.Output.IsSet() {
|
||||||
t1.Output = t2.Output
|
t1.Output = t2.Output
|
||||||
}
|
}
|
||||||
@@ -45,9 +52,9 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
|||||||
if t1.Env == nil {
|
if t1.Env == nil {
|
||||||
t1.Env = &Vars{}
|
t1.Env = &Vars{}
|
||||||
}
|
}
|
||||||
t1.Vars.Merge(t2.Vars)
|
t1.Vars.Merge(t2.Vars, include)
|
||||||
t1.Env.Merge(t2.Env)
|
t1.Env.Merge(t2.Env, include)
|
||||||
t1.Tasks.Merge(t2.Tasks, include)
|
t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
"github.com/go-task/task/v3/internal/omap"
|
"github.com/go-task/task/v3/internal/omap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
|
|||||||
return matchingTasks
|
return matchingTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t1 *Tasks) Merge(t2 Tasks, include *Include) {
|
func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) {
|
||||||
_ = t2.Range(func(k string, v *Task) error {
|
_ = t2.Range(func(k string, v *Task) error {
|
||||||
// We do a deep copy of the task struct here to ensure that no data can
|
// We do a deep copy of the task struct here to ensure that no data can
|
||||||
// be changed elsewhere once the taskfile is merged.
|
// be changed elsewhere once the taskfile is merged.
|
||||||
@@ -54,20 +55,25 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include) {
|
|||||||
// taskfile are marked as internal
|
// taskfile are marked as internal
|
||||||
task.Internal = task.Internal || (include != nil && include.Internal)
|
task.Internal = task.Internal || (include != nil && include.Internal)
|
||||||
|
|
||||||
// Add namespaces to dependencies, commands and aliases
|
// Add namespaces to task dependencies
|
||||||
for _, dep := range task.Deps {
|
for _, dep := range task.Deps {
|
||||||
if dep != nil && dep.Task != "" {
|
if dep != nil && dep.Task != "" {
|
||||||
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
|
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add namespaces to task commands
|
||||||
for _, cmd := range task.Cmds {
|
for _, cmd := range task.Cmds {
|
||||||
if cmd != nil && cmd.Task != "" {
|
if cmd != nil && cmd.Task != "" {
|
||||||
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
|
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add namespaces to task aliases
|
||||||
for i, alias := range task.Aliases {
|
for i, alias := range task.Aliases {
|
||||||
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
|
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add namespace aliases
|
// Add namespace aliases
|
||||||
if include != nil {
|
if include != nil {
|
||||||
for _, namespaceAlias := range include.Aliases {
|
for _, namespaceAlias := range include.Aliases {
|
||||||
@@ -78,6 +84,15 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
// Add the task to the merged taskfile
|
// Add the task to the merged taskfile
|
||||||
taskNameWithNamespace := taskNameWithNamespace(k, include.Namespace)
|
taskNameWithNamespace := taskNameWithNamespace(k, include.Namespace)
|
||||||
task.Task = taskNameWithNamespace
|
task.Task = taskNameWithNamespace
|
||||||
|
|||||||
@@ -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
|
// 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 {
|
if vs == nil || other == nil {
|
||||||
return
|
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
|
// Wrapper around OrderedMap.Len to ensure we don't get nil pointer errors
|
||||||
@@ -83,10 +89,10 @@ type Var struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
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
|
// 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
|
var value any
|
||||||
if err := node.Decode(&value); err != nil {
|
if err := node.Decode(&value); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -103,7 +109,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This implementation IS backwards-compatible and keeps the 'sh' key and allows map variables to be added under the `map` key
|
// 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 {
|
switch node.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
key := node.Content[0].Value
|
key := node.Content[0].Value
|
||||||
@@ -141,15 +147,10 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
|
|
||||||
switch node.Kind {
|
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:
|
case yaml.MappingNode:
|
||||||
|
if len(node.Content) > 2 || node.Content[0].Value != "sh" {
|
||||||
|
return fmt.Errorf(`task: line %d: maps cannot be assigned to variables`, node.Line)
|
||||||
|
}
|
||||||
var sh struct {
|
var sh struct {
|
||||||
Sh string
|
Sh string
|
||||||
}
|
}
|
||||||
@@ -158,7 +159,13 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
}
|
}
|
||||||
v.Sh = sh.Sh
|
v.Sh = sh.Sh
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variable", node.Line, node.ShortTag())
|
default:
|
||||||
|
var value any
|
||||||
|
if err := node.Decode(&value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Value = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ type Node interface {
|
|||||||
Parent() Node
|
Parent() Node
|
||||||
Location() string
|
Location() string
|
||||||
Dir() string
|
Dir() string
|
||||||
Optional() bool
|
|
||||||
Remote() bool
|
Remote() bool
|
||||||
ResolveEntrypoint(entrypoint string) (string, error)
|
ResolveEntrypoint(entrypoint string) (string, error)
|
||||||
ResolveDir(dir string) (string, error)
|
ResolveDir(dir string) (string, error)
|
||||||
@@ -31,9 +30,8 @@ func NewRootNode(
|
|||||||
timeout time.Duration,
|
timeout time.Duration,
|
||||||
) (Node, error) {
|
) (Node, error) {
|
||||||
dir = getDefaultDir(entrypoint, dir)
|
dir = getDefaultDir(entrypoint, dir)
|
||||||
// Check if there is something to read on STDIN
|
// If the entrypoint is "-", we read from stdin
|
||||||
stat, _ := os.Stdin.Stat()
|
if entrypoint == "-" {
|
||||||
if (stat.Mode()&os.ModeCharDevice) == 0 && stat.Size() > 0 {
|
|
||||||
return NewStdinNode(dir)
|
return NewStdinNode(dir)
|
||||||
}
|
}
|
||||||
return NewNode(l, entrypoint, dir, insecure, timeout)
|
return NewNode(l, entrypoint, dir, insecure, timeout)
|
||||||
|
|||||||
@@ -2,22 +2,20 @@ package taskfile
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
NodeOption func(*BaseNode)
|
NodeOption func(*BaseNode)
|
||||||
// BaseNode is a generic node that implements the Parent() and Optional()
|
// BaseNode is a generic node that implements the Parent() methods of the
|
||||||
// methods of the NodeReader interface. It does not implement the Read() method
|
// NodeReader interface. It does not implement the Read() method and it
|
||||||
// and it designed to be embedded in other node types so that this boilerplate
|
// designed to be embedded in other node types so that this boilerplate code
|
||||||
// code does not need to be repeated.
|
// does not need to be repeated.
|
||||||
BaseNode struct {
|
BaseNode struct {
|
||||||
parent Node
|
parent Node
|
||||||
optional bool
|
dir string
|
||||||
dir string
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
|
func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
|
||||||
node := &BaseNode{
|
node := &BaseNode{
|
||||||
parent: nil,
|
parent: nil,
|
||||||
optional: false,
|
dir: dir,
|
||||||
dir: dir,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply options
|
// Apply options
|
||||||
@@ -38,16 +36,6 @@ func (node *BaseNode) Parent() Node {
|
|||||||
return node.parent
|
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 {
|
func (node *BaseNode) Dir() string {
|
||||||
return node.dir
|
return node.dir
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dominikbraun/graph"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"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/filepathext"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
@@ -24,33 +27,83 @@ Continue?`
|
|||||||
Continue?`
|
Continue?`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Read reads a Read for a given directory
|
// A Reader will recursively read Taskfiles from a given source using a directed
|
||||||
// Uses current dir when dir is left empty. Uses Read.yml
|
// acyclic graph (DAG).
|
||||||
// or Read.yaml when entrypoint is left empty
|
type Reader struct {
|
||||||
func Read(
|
graph *ast.TaskfileGraph
|
||||||
|
node Node
|
||||||
|
insecure bool
|
||||||
|
download bool
|
||||||
|
offline bool
|
||||||
|
timeout time.Duration
|
||||||
|
tempDir string
|
||||||
|
logger *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(
|
||||||
node Node,
|
node Node,
|
||||||
insecure bool,
|
insecure bool,
|
||||||
download bool,
|
download bool,
|
||||||
offline bool,
|
offline bool,
|
||||||
timeout time.Duration,
|
timeout time.Duration,
|
||||||
tempDir string,
|
tempDir string,
|
||||||
l *logger.Logger,
|
logger *logger.Logger,
|
||||||
) (*ast.Taskfile, error) {
|
) *Reader {
|
||||||
var _taskfile func(Node) (*ast.Taskfile, error)
|
return &Reader{
|
||||||
_taskfile = func(node Node) (*ast.Taskfile, error) {
|
graph: ast.NewTaskfileGraph(),
|
||||||
tf, err := readTaskfile(node, download, offline, timeout, tempDir, l)
|
node: node,
|
||||||
if err != nil {
|
insecure: insecure,
|
||||||
return nil, err
|
download: download,
|
||||||
}
|
offline: offline,
|
||||||
|
timeout: timeout,
|
||||||
|
tempDir: tempDir,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check that the Taskfile is set and has a schema version
|
func (r *Reader) Read() (*ast.TaskfileGraph, error) {
|
||||||
if tf == nil || tf.Version == nil {
|
// Recursively loop through each Taskfile, adding vertices/edges to the graph
|
||||||
return nil, &errors.TaskfileVersionCheckError{URI: node.Location()}
|
if err := r.include(r.node); err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = tf.Includes.Range(func(namespace string, include ast.Include) error {
|
return r.graph, nil
|
||||||
cache := &templater.Cache{Vars: tf.Vars}
|
}
|
||||||
include = ast.Include{
|
|
||||||
|
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,
|
Namespace: include.Namespace,
|
||||||
Taskfile: templater.Replace(include.Taskfile, cache),
|
Taskfile: templater.Replace(include.Taskfile, cache),
|
||||||
Dir: templater.Replace(include.Dir, cache),
|
Dir: templater.Replace(include.Dir, cache),
|
||||||
@@ -69,14 +122,13 @@ func Read(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := node.ResolveDir(include.Dir)
|
include.Dir, err = node.ResolveDir(include.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
includeReaderNode, err := NewNode(l, entrypoint, dir, insecure, timeout,
|
includeNode, err := NewNode(r.logger, entrypoint, include.Dir, r.insecure, r.timeout,
|
||||||
WithParent(node),
|
WithParent(node),
|
||||||
WithOptional(include.Optional),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if include.Optional {
|
if include.Optional {
|
||||||
@@ -85,106 +137,72 @@ func Read(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkCircularIncludes(includeReaderNode); err != nil {
|
// Recurse into the included Taskfile
|
||||||
|
if err := r.include(includeNode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
includedTaskfile, err := _taskfile(includeReaderNode)
|
// Create an edge between the Taskfiles
|
||||||
if err != nil {
|
r.graph.Lock()
|
||||||
if include.Optional {
|
defer r.graph.Unlock()
|
||||||
return nil
|
edge, err := r.graph.Edge(node.Location(), includeNode.Location())
|
||||||
}
|
if err == graph.ErrEdgeNotFound {
|
||||||
return err
|
// 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 errors.Is(err, graph.ErrEdgeCreatesCycle) {
|
||||||
if len(includedTaskfile.Dotenv) > 0 {
|
return errors.TaskfileCycleError{
|
||||||
return ErrIncludedTaskfilesCantHaveDotenvs
|
Source: node.Location(),
|
||||||
}
|
Destination: includeNode.Location(),
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
if err = tf.Merge(includedTaskfile, &include); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
return nil
|
||||||
return nil, err
|
})
|
||||||
}
|
|
||||||
|
|
||||||
for _, task := range tf.Tasks.Values() {
|
// Wait for all the go routines to finish
|
||||||
// If the task is not defined, create a new one
|
return g.Wait()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readTaskfile(
|
func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
|
||||||
node Node,
|
|
||||||
download,
|
|
||||||
offline bool,
|
|
||||||
timeout time.Duration,
|
|
||||||
tempDir string,
|
|
||||||
l *logger.Logger,
|
|
||||||
) (*ast.Taskfile, error) {
|
|
||||||
var b []byte
|
var b []byte
|
||||||
var err error
|
var err error
|
||||||
var cache *Cache
|
var cache *Cache
|
||||||
|
|
||||||
if node.Remote() {
|
if node.Remote() {
|
||||||
cache, err = NewCache(tempDir)
|
cache, err = NewCache(r.tempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the file is remote and we're in offline mode, check if we have a cached copy
|
// 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) {
|
if b, err = cache.read(node); errors.Is(err, os.ErrNotExist) {
|
||||||
return nil, &errors.TaskfileCacheNotFoundError{URI: node.Location()}
|
return nil, &errors.TaskfileCacheNotFoundError{URI: node.Location()}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
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 {
|
} else {
|
||||||
|
|
||||||
downloaded := false
|
downloaded := false
|
||||||
ctx, cf := context.WithTimeout(context.Background(), timeout)
|
ctx, cf := context.WithTimeout(context.Background(), r.timeout)
|
||||||
defer cf()
|
defer cf()
|
||||||
|
|
||||||
// Read the file
|
// Read the file
|
||||||
@@ -192,16 +210,16 @@ func readTaskfile(
|
|||||||
// If we timed out then we likely have a network issue
|
// If we timed out then we likely have a network issue
|
||||||
if node.Remote() && errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
if node.Remote() && errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||||
// If a download was requested, then we can't use a cached copy
|
// If a download was requested, then we can't use a cached copy
|
||||||
if download {
|
if r.download {
|
||||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: timeout}
|
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout}
|
||||||
}
|
}
|
||||||
// Search for any cached copies
|
// Search for any cached copies
|
||||||
if b, err = cache.read(node); errors.Is(err, os.ErrNotExist) {
|
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 {
|
} else if err != nil {
|
||||||
return nil, err
|
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 {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
@@ -210,7 +228,7 @@ func readTaskfile(
|
|||||||
|
|
||||||
// If the node was remote, we need to check the checksum
|
// If the node was remote, we need to check the checksum
|
||||||
if node.Remote() && downloaded {
|
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
|
// Get the checksums
|
||||||
checksum := checksum(b)
|
checksum := checksum(b)
|
||||||
@@ -225,7 +243,7 @@ func readTaskfile(
|
|||||||
prompt = fmt.Sprintf(taskfileChangedPrompt, node.Location())
|
prompt = fmt.Sprintf(taskfileChangedPrompt, node.Location())
|
||||||
}
|
}
|
||||||
if prompt != "" {
|
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()}
|
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +255,7 @@ func readTaskfile(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Cache the file
|
// 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 {
|
if err = cache.write(node, b); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -245,33 +263,28 @@ func readTaskfile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var t ast.Taskfile
|
var tf ast.Taskfile
|
||||||
if err := yaml.Unmarshal(b, &t); err != nil {
|
if err := yaml.Unmarshal(b, &tf); err != nil {
|
||||||
return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(node.Location()), Err: err}
|
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 {
|
// Set the taskfile/task's locations
|
||||||
if node == nil {
|
tf.Location = node.Location()
|
||||||
return errors.New("task: failed to check for include cycle: node was nil")
|
for _, task := range tf.Tasks.Values() {
|
||||||
}
|
// If the task is not defined, create a new one
|
||||||
if node.Parent() == nil {
|
if task == nil {
|
||||||
return errors.New("task: failed to check for include cycle: node.Parent was nil")
|
task = &ast.Task{}
|
||||||
}
|
}
|
||||||
curNode := node
|
// Set the location of the taskfile for each task
|
||||||
location := node.Location()
|
if task.Location.Taskfile == "" {
|
||||||
for curNode.Parent() != nil {
|
task.Location.Taskfile = tf.Location
|
||||||
curNode = curNode.Parent()
|
|
||||||
curLocation := curNode.Location()
|
|
||||||
if curLocation == location {
|
|
||||||
return fmt.Errorf("task: include cycle detected between %s <--> %s",
|
|
||||||
curLocation,
|
|
||||||
node.Parent().Location(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return &tf, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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{
|
defaultTaskfiles = []string{
|
||||||
"Taskfile.yml",
|
"Taskfile.yml",
|
||||||
"taskfile.yml",
|
"taskfile.yml",
|
||||||
@@ -29,7 +26,6 @@ var (
|
|||||||
"Taskfile.dist.yaml",
|
"Taskfile.dist.yaml",
|
||||||
"taskfile.dist.yaml",
|
"taskfile.dist.yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedContentTypes = []string{
|
allowedContentTypes = []string{
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"text/yaml",
|
"text/yaml",
|
||||||
|
|||||||
0
testdata/empty_taskfile/Taskfile.yml
vendored
Normal file
0
testdata/empty_taskfile/Taskfile.yml
vendored
Normal file
9
testdata/env/Taskfile.yml
vendored
9
testdata/env/Taskfile.yml
vendored
@@ -14,6 +14,7 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- task: local
|
- task: local
|
||||||
- task: global
|
- task: global
|
||||||
|
- task: multiple_type
|
||||||
|
|
||||||
local:
|
local:
|
||||||
vars:
|
vars:
|
||||||
@@ -31,3 +32,11 @@ tasks:
|
|||||||
BAR: overriden
|
BAR: overriden
|
||||||
cmds:
|
cmds:
|
||||||
- echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > global.txt
|
- 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
|
||||||
|
|||||||
6
testdata/include_with_vars/Taskfile.yml
vendored
6
testdata/include_with_vars/Taskfile.yml
vendored
@@ -2,15 +2,15 @@ version: "3"
|
|||||||
|
|
||||||
includes:
|
includes:
|
||||||
included1:
|
included1:
|
||||||
taskfile: include/Taskfile.include.yml
|
taskfile: include/Taskfile.include1.yml
|
||||||
vars:
|
vars:
|
||||||
VAR_1: included1-var1
|
VAR_1: included1-var1
|
||||||
included2:
|
included2:
|
||||||
taskfile: include/Taskfile.include.yml
|
taskfile: include/Taskfile.include2.yml
|
||||||
vars:
|
vars:
|
||||||
VAR_1: included2-var1
|
VAR_1: included2-var1
|
||||||
included3:
|
included3:
|
||||||
taskfile: include/Taskfile.include.yml
|
taskfile: include/Taskfile.include3.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
task1:
|
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
|
||||||
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'
|
||||||
43
testdata/vars/any2/Taskfile.yml
vendored
43
testdata/vars/any2/Taskfile.yml
vendored
@@ -8,6 +8,7 @@ tasks:
|
|||||||
- task: ref
|
- task: ref
|
||||||
- task: ref-sh
|
- task: ref-sh
|
||||||
- task: ref-dep
|
- task: ref-dep
|
||||||
|
- task: ref-resolver
|
||||||
- task: json
|
- task: json
|
||||||
- task: yaml
|
- task: yaml
|
||||||
|
|
||||||
@@ -16,10 +17,10 @@ tasks:
|
|||||||
MAP:
|
MAP:
|
||||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: MAP
|
ref: .MAP
|
||||||
|
|
||||||
nested-map:
|
nested-map:
|
||||||
vars:
|
vars:
|
||||||
@@ -44,12 +45,12 @@ tasks:
|
|||||||
MAP:
|
MAP:
|
||||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||||
MAP_REF:
|
MAP_REF:
|
||||||
ref: MAP
|
ref: .MAP
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: MAP_REF
|
ref: .MAP_REF
|
||||||
|
|
||||||
ref-sh:
|
ref-sh:
|
||||||
vars:
|
vars:
|
||||||
@@ -58,22 +59,34 @@ tasks:
|
|||||||
JSON:
|
JSON:
|
||||||
json: "{{.JSON_STRING}}"
|
json: "{{.JSON_STRING}}"
|
||||||
MAP_REF:
|
MAP_REF:
|
||||||
ref: JSON
|
ref: .JSON
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: MAP_REF
|
ref: .MAP_REF
|
||||||
|
|
||||||
ref-dep:
|
ref-dep:
|
||||||
vars:
|
vars:
|
||||||
MAP:
|
MAP:
|
||||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||||
deps:
|
deps:
|
||||||
|
- task: print-story
|
||||||
|
vars:
|
||||||
|
VAR:
|
||||||
|
ref: .MAP
|
||||||
|
|
||||||
|
ref-resolver:
|
||||||
|
vars:
|
||||||
|
MAP:
|
||||||
|
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||||
|
MAP_REF:
|
||||||
|
ref: .MAP
|
||||||
|
cmds:
|
||||||
- task: print-var
|
- task: print-var
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: MAP
|
ref: (index .MAP_REF.children 0).name
|
||||||
|
|
||||||
json:
|
json:
|
||||||
vars:
|
vars:
|
||||||
@@ -82,10 +95,10 @@ tasks:
|
|||||||
JSON:
|
JSON:
|
||||||
json: "{{.JSON_STRING}}"
|
json: "{{.JSON_STRING}}"
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: JSON
|
ref: .JSON
|
||||||
|
|
||||||
yaml:
|
yaml:
|
||||||
vars:
|
vars:
|
||||||
@@ -94,12 +107,16 @@ tasks:
|
|||||||
YAML:
|
YAML:
|
||||||
yaml: "{{.YAML_STRING}}"
|
yaml: "{{.YAML_STRING}}"
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: YAML
|
ref: .YAML
|
||||||
|
|
||||||
print-var:
|
print-var:
|
||||||
|
cmds:
|
||||||
|
- echo "{{.VAR}}"
|
||||||
|
|
||||||
|
print-story:
|
||||||
cmds:
|
cmds:
|
||||||
- >-
|
- >-
|
||||||
echo "{{.VAR.name}} has {{len .VAR.children}} children called
|
echo "{{.VAR.name}} has {{len .VAR.children}} children called
|
||||||
|
|||||||
28
variables.go
28
variables.go
@@ -104,9 +104,9 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
}
|
}
|
||||||
|
|
||||||
new.Env = &ast.Vars{}
|
new.Env = &ast.Vars{}
|
||||||
new.Env.Merge(templater.ReplaceVars(e.Taskfile.Env, cache))
|
new.Env.Merge(templater.ReplaceVars(e.Taskfile.Env, cache), nil)
|
||||||
new.Env.Merge(templater.ReplaceVars(dotenvEnvs, cache))
|
new.Env.Merge(templater.ReplaceVars(dotenvEnvs, cache), nil)
|
||||||
new.Env.Merge(templater.ReplaceVars(origTask.Env, cache))
|
new.Env.Merge(templater.ReplaceVars(origTask.Env, cache), nil)
|
||||||
if evaluateShVars {
|
if evaluateShVars {
|
||||||
err = new.Env.Range(func(k string, v ast.Var) error {
|
err = new.Env.Range(func(k string, v ast.Var) error {
|
||||||
// If the variable is not dynamic, we can set it and return
|
// If the variable is not dynamic, we can set it and return
|
||||||
@@ -164,17 +164,6 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
||||||
newCmd.Task = templater.Replace(cmd.Task, cache)
|
newCmd.Task = templater.Replace(cmd.Task, cache)
|
||||||
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
|
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
|
||||||
// Loop over the command's variables and resolve any references to other variables
|
|
||||||
err := cmd.Vars.Range(func(k string, v ast.Var) error {
|
|
||||||
if v.Ref != "" {
|
|
||||||
refVal := vars.Get(v.Ref)
|
|
||||||
newCmd.Vars.Set(k, refVal)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
new.Cmds = append(new.Cmds, newCmd)
|
new.Cmds = append(new.Cmds, newCmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,17 +203,6 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
newDep := dep.DeepCopy()
|
newDep := dep.DeepCopy()
|
||||||
newDep.Task = templater.Replace(dep.Task, cache)
|
newDep.Task = templater.Replace(dep.Task, cache)
|
||||||
newDep.Vars = templater.ReplaceVars(dep.Vars, cache)
|
newDep.Vars = templater.ReplaceVars(dep.Vars, cache)
|
||||||
// Loop over the dep's variables and resolve any references to other variables
|
|
||||||
err := dep.Vars.Range(func(k string, v ast.Var) error {
|
|
||||||
if v.Ref != "" {
|
|
||||||
refVal := vars.Get(v.Ref)
|
|
||||||
newDep.Vars.Set(k, refVal)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
new.Deps = append(new.Deps, newDep)
|
new.Deps = append(new.Deps, newDep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ communicate these kinds of thoughts to the community. So, with that in mind,
|
|||||||
this is the first (hopefully of many) blog posts talking about Task and what
|
this is the first (hopefully of many) blog posts talking about Task and what
|
||||||
we're up to.
|
we're up to.
|
||||||
|
|
||||||
<!--truncate-->
|
{/* truncate */}
|
||||||
|
|
||||||
## :calendar: So, what have we been up to?
|
## :calendar: So, what have we been up to?
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ I plan to write more of these blog posts in the future on a variety of
|
|||||||
Task-related topics, so make sure to check in occasionally and see what we're up
|
Task-related topics, so make sure to check in occasionally and see what we're up
|
||||||
to!
|
to!
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
{/* prettier-ignore-start */}
|
||||||
[vscode-task]: https://github.com/go-task/vscode-task
|
[vscode-task]: https://github.com/go-task/vscode-task
|
||||||
[crowdin]: https://crowdin.com
|
[crowdin]: https://crowdin.com
|
||||||
[contributors]: https://github.com/go-task/task/graphs/contributors
|
[contributors]: https://github.com/go-task/task/graphs/contributors
|
||||||
@@ -139,4 +139,4 @@ to!
|
|||||||
[experiments-project]: https://github.com/orgs/go-task/projects/1
|
[experiments-project]: https://github.com/orgs/go-task/projects/1
|
||||||
[gentle-force-experiment]: https://github.com/go-task/task/issues/1200
|
[gentle-force-experiment]: https://github.com/go-task/task/issues/1200
|
||||||
[remote-taskfiles-experiment]: https://github.com/go-task/task/issues/1317
|
[remote-taskfiles-experiment]: https://github.com/go-task/task/issues/1317
|
||||||
<!-- prettier-ignore-end -->
|
{/* prettier-ignore-end */}
|
||||||
181
website/blog/2024-05-09-any-variables.mdx
Normal file
181
website/blog/2024-05-09-any-variables.mdx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
---
|
||||||
|
title: Any Variables
|
||||||
|
description: Task variables are no longer limited to strings!
|
||||||
|
slug: any-variables
|
||||||
|
authors: [pd93]
|
||||||
|
tags: [experiments, variables]
|
||||||
|
image: https://i.imgur.com/mErPwqL.png
|
||||||
|
hide_table_of_contents: false
|
||||||
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
|
Task has always had variables, but even though you were able to define them
|
||||||
|
using different YAML types, they would always be converted to strings by Task.
|
||||||
|
This limited users to string manipulation and encouraged messy workarounds for
|
||||||
|
simple problems. Starting from [v3.37.0][v3.37.0], this is no longer the case!
|
||||||
|
Task now supports most variable types, including **booleans**, **integers**,
|
||||||
|
**floats** and **arrays**!
|
||||||
|
|
||||||
|
{/* truncate */}
|
||||||
|
|
||||||
|
## What's the big deal?
|
||||||
|
|
||||||
|
These changes allow you to use variables in a much more natural way and opens up
|
||||||
|
a wide variety of sprig functions that were previously useless. Take a look at
|
||||||
|
some of the examples below for some inspiration.
|
||||||
|
|
||||||
|
### Evaluating booleans
|
||||||
|
|
||||||
|
No more comparing strings to "true" or "false". Now you can use actual boolean
|
||||||
|
values in your templates:
|
||||||
|
|
||||||
|
<Tabs defaultValue="2"
|
||||||
|
values={[
|
||||||
|
{label: 'Before', value: '1'},
|
||||||
|
{label: 'After', value: '2'}
|
||||||
|
]}>
|
||||||
|
|
||||||
|
<TabItem value="1">
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
BOOL: true # <-- Parsed as a string even though its a YAML boolean
|
||||||
|
cmds:
|
||||||
|
- '{{if eq .BOOL "true"}}echo foo{{end}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="2">
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
BOOL: true # <-- Parsed as a boolean
|
||||||
|
cmds:
|
||||||
|
- '{{if .BOOL}}echo foo{{end}}' # <-- No need to compare to "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
|
### Arithmetic
|
||||||
|
|
||||||
|
You can now perform basic arithmetic operations on integer and float variables:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
INT: 10
|
||||||
|
FLOAT: 3.14159
|
||||||
|
cmds:
|
||||||
|
- 'echo {{add .INT .FLOAT}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use any of the following arithmetic functions: `add`, `sub`, `mul`,
|
||||||
|
`div`, `mod`, `max`, `min`, `floor`, `ceil`, `round` and `randInt`. Check out
|
||||||
|
the [slim-sprig math documentation][slim-sprig-math] for more information.
|
||||||
|
|
||||||
|
### Arrays
|
||||||
|
|
||||||
|
You can now range over arrays inside templates and use list-based functions:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
ARRAY: [1, 2, 3]
|
||||||
|
cmds:
|
||||||
|
- 'echo {{range .ARRAY}}{{.}}{{end}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use any of the following list-based functions: `first`, `rest`, `last`,
|
||||||
|
`initial`, `append`, `prepend`, `concat`, `reverse`, `uniq`, `without`, `has`,
|
||||||
|
`compact`, `slice` and `chunk`. Check out the [slim-sprg lists
|
||||||
|
documentation][slim-sprig-list] for more information.
|
||||||
|
|
||||||
|
### Looping over variables using `for`
|
||||||
|
|
||||||
|
Previously, you would have to use a delimiter separated string to loop over an
|
||||||
|
arbitrary list of items in a variable and split them by using the `split` subkey
|
||||||
|
to specify the delimiter. However, we have now added support for looping over
|
||||||
|
"collection-type" variables using the `for` keyword, so now you are able to loop
|
||||||
|
over list variables directly:
|
||||||
|
|
||||||
|
<Tabs defaultValue="2"
|
||||||
|
values={[
|
||||||
|
{label: 'Before', value: '1'},
|
||||||
|
{label: 'After', value: '2'}
|
||||||
|
]}>
|
||||||
|
|
||||||
|
<TabItem value="1">
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
LIST: 'foo,bar,baz'
|
||||||
|
cmds:
|
||||||
|
- for:
|
||||||
|
var: LIST
|
||||||
|
split: ','
|
||||||
|
cmd: echo {{.ITEM}}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="2">
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
LIST: ['foo', 'bar', 'baz']
|
||||||
|
cmds:
|
||||||
|
- for:
|
||||||
|
var: LIST
|
||||||
|
cmd: echo {{.ITEM}}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
|
## What about maps?
|
||||||
|
|
||||||
|
Maps were originally included in the Any Variables experiment. However, they
|
||||||
|
weren't quite ready yet. Instead of making you wait for everything to be ready
|
||||||
|
at once, we have released support for all other variable types and we will
|
||||||
|
continue working on map support in the new "[Map Variables][map-variables]"
|
||||||
|
experiment.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
If you were previously using maps with the Any Variables experiment and wish to
|
||||||
|
continue using them, you will need to enable the new [Map Variables
|
||||||
|
experiment][map-variables] instead.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
We're looking for feedback on a couple of different proposals, so please give
|
||||||
|
them a go and let us know what you think. :pray:
|
||||||
|
|
||||||
|
{/* prettier-ignore-start */}
|
||||||
|
[v3.37.0]: https://github.com/go-task/task/releases/tag/v3.37.0
|
||||||
|
[slim-sprig-math]: https://go-task.github.io/slim-sprig/math.html
|
||||||
|
[slim-sprig-list]: https://go-task.github.io/slim-sprig/lists.html
|
||||||
|
[map-variables]: /experiments/map-variables
|
||||||
|
{/* prettier-ignore-end */}
|
||||||
@@ -266,7 +266,7 @@ vars:
|
|||||||
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
||||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
||||||
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
||||||
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/main/src/go/build/syslist.go). Task will be skipped otherwise. |
|
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Task will be skipped otherwise. |
|
||||||
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ tasks:
|
|||||||
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
||||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
||||||
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
||||||
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/main/src/go/build/syslist.go). Command will be skipped otherwise. |
|
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Command will be skipped otherwise. |
|
||||||
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,38 @@ sidebar_position: 14
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
@pd03).
|
||||||
|
- 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
|
## v3.36.0 - 2024-04-08
|
||||||
|
|
||||||
- Added support for
|
- Added support for
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
|
|||||||
---
|
---
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
|
[experiments]: /experiments
|
||||||
|
[experiments-workflow]: /experiments#workflow
|
||||||
[task]: https://github.com/go-task/task
|
[task]: https://github.com/go-task/task
|
||||||
[vscode-task]: https://github.com/go-task/vscode-task
|
[vscode-task]: https://github.com/go-task/vscode-task
|
||||||
[go]: https://go.dev
|
[go]: https://go.dev
|
||||||
|
|||||||
@@ -45,5 +45,5 @@ if you want to adopt the new behavior, you can continue to use the `--force`
|
|||||||
flag as you do now!
|
flag as you do now!
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[enabling-experiments]: /experiments/#enabling-experiments
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
slug: /experiments/any-variables/
|
slug: /experiments/map-variables/
|
||||||
---
|
---
|
||||||
|
|
||||||
import Tabs from '@theme/Tabs';
|
import Tabs from '@theme/Tabs';
|
||||||
import TabItem from '@theme/TabItem';
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
# Any Variables (#1415)
|
# Map Variables (#1585)
|
||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
|
|
||||||
@@ -15,19 +15,9 @@ environment. They are intended for testing and feedback only.
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Currently, Task only supports string variables. This experiment allows you to
|
Currently, Task supports all variable types except for maps. This experiment
|
||||||
specify and use the following variable types:
|
adds two different proposals for map variables. Click on the tabs below to
|
||||||
|
switch between them.
|
||||||
- `string`
|
|
||||||
- `bool`
|
|
||||||
- `int`
|
|
||||||
- `float`
|
|
||||||
- `array`
|
|
||||||
- `map`
|
|
||||||
|
|
||||||
This allows you to have a lot more flexibility in how you use variables in
|
|
||||||
Task's templating engine. There are two active proposals for this experiment.
|
|
||||||
Click on the tabs below to switch between them.
|
|
||||||
|
|
||||||
<Tabs defaultValue="1" queryString="proposal"
|
<Tabs defaultValue="1" queryString="proposal"
|
||||||
values={[
|
values={[
|
||||||
@@ -48,13 +38,11 @@ This experiment proposal breaks the following functionality:
|
|||||||
:::info
|
:::info
|
||||||
|
|
||||||
To enable this experiment, set the environment variable:
|
To enable this experiment, set the environment variable:
|
||||||
`TASK_X_ANY_VARIABLES=1`. Check out [our guide to enabling experiments
|
`TASK_X_MAP_VARIABLES=1`. Check out [our guide to enabling experiments
|
||||||
][enabling-experiments] for more information.
|
][enabling-experiments] for more information.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Maps
|
|
||||||
|
|
||||||
This proposal removes support for the `sh` keyword in favour of a new syntax for
|
This proposal removes support for the `sh` keyword in favour of a new syntax for
|
||||||
dynamically defined variables, This allows you to define a map directly as you
|
dynamically defined variables, This allows you to define a map directly as you
|
||||||
would for any other type:
|
would for any other type:
|
||||||
@@ -111,19 +99,16 @@ will now need to escape the `$` with a backslash (`\`) to stop Task from
|
|||||||
executing it as a command.
|
executing it as a command.
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<TabItem value="2">
|
<TabItem value="2">
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
To enable this experiment, set the environment variable:
|
To enable this experiment, set the environment variable:
|
||||||
`TASK_X_ANY_VARIABLES=2`. Check out [our guide to enabling experiments
|
`TASK_X_MAP_VARIABLES=2`. Check out [our guide to enabling experiments
|
||||||
][enabling-experiments] for more information.
|
][enabling-experiments] for more information.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Maps
|
|
||||||
|
|
||||||
This proposal maintains backwards-compatibility and the `sh` subkey and adds
|
This proposal maintains backwards-compatibility and the `sh` subkey and adds
|
||||||
another new `map` subkey for defining map variables:
|
another new `map` subkey for defining map variables:
|
||||||
|
|
||||||
@@ -150,7 +135,13 @@ objects/arrays. This is similar to the `fromJSON` template function, but means
|
|||||||
that you only have to parse the JSON/YAML once when you declare the variable,
|
that you only have to parse the JSON/YAML once when you declare the variable,
|
||||||
instead of every time you want to access a value.
|
instead of every time you want to access a value.
|
||||||
|
|
||||||
Before:
|
<Tabs defaultValue="2"
|
||||||
|
values={[
|
||||||
|
{label: 'Before', value: '1'},
|
||||||
|
{label: 'After', value: '2'}
|
||||||
|
]}>
|
||||||
|
|
||||||
|
<TabItem value="1">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -164,7 +155,8 @@ tasks:
|
|||||||
- 'echo {{(fromJSON .FOO).b}}'
|
- 'echo {{(fromJSON .FOO).b}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
After:
|
</TabItem>
|
||||||
|
<TabItem value="2">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -179,12 +171,26 @@ tasks:
|
|||||||
- 'echo {{.FOO.b}}'
|
- 'echo {{.FOO.b}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
## Variables by reference
|
## Variables by reference
|
||||||
|
|
||||||
Lastly, this proposal adds support for defining and passing variables by
|
Lastly, this proposal adds support for defining and passing variables by
|
||||||
reference. This is really important now that variables can be types other than a
|
reference. This is really important now that variables can be types other than a
|
||||||
string. Previously, to send a variable from one task to another, you would have
|
string.
|
||||||
to use the templating system to pass it:
|
|
||||||
|
Previously, to send a variable from one task to another, you would have to use
|
||||||
|
the templating system. Unfortunately, the templater _always_ outputs a string
|
||||||
|
and operations on the passed variable may not have behaved as expected. With
|
||||||
|
this proposal, you can now pass variables by reference using the `ref` subkey:
|
||||||
|
|
||||||
|
<Tabs defaultValue="2"
|
||||||
|
values={[
|
||||||
|
{label: 'Before', value: '1'},
|
||||||
|
{label: 'After', value: '2'}
|
||||||
|
]}>
|
||||||
|
|
||||||
|
<TabItem value="1">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -202,10 +208,8 @@ tasks:
|
|||||||
- 'echo {{index .FOO 0}}' # <-- FOO is a string so the task outputs '91' which is the ASCII code for '[' instead of the expected 'A'
|
- 'echo {{index .FOO 0}}' # <-- FOO is a string so the task outputs '91' which is the ASCII code for '[' instead of the expected 'A'
|
||||||
```
|
```
|
||||||
|
|
||||||
Unfortunately, this results in the value always being passed as a string as this
|
</TabItem>
|
||||||
is the output type of the templater and operations on the passed variable may
|
<TabItem value="2">
|
||||||
not behave as expected. With this proposal, you can now pass variables by
|
|
||||||
reference using the `ref` subkey:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -218,12 +222,14 @@ tasks:
|
|||||||
- task: bar
|
- task: bar
|
||||||
vars:
|
vars:
|
||||||
FOO:
|
FOO:
|
||||||
ref: FOO # <-- FOO gets passed by reference to bar and maintains its type
|
ref: .FOO # <-- FOO gets passed by reference to bar and maintains its type
|
||||||
bar:
|
bar:
|
||||||
cmds:
|
cmds:
|
||||||
- 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected
|
- 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
This means that the type of the variable is maintained when it is passed to
|
This means that the type of the variable is maintained when it is passed to
|
||||||
another Task. This also works the same way when calling `deps` and when defining
|
another Task. This also works the same way when calling `deps` and when defining
|
||||||
a variable and can be used in any combination:
|
a variable and can be used in any combination:
|
||||||
@@ -236,27 +242,54 @@ tasks:
|
|||||||
vars:
|
vars:
|
||||||
FOO: [A, B, C] # <-- FOO is defined as an array
|
FOO: [A, B, C] # <-- FOO is defined as an array
|
||||||
BAR:
|
BAR:
|
||||||
ref: FOO # <-- BAR is defined as a reference to FOO
|
ref: .FOO # <-- BAR is defined as a reference to FOO
|
||||||
deps:
|
deps:
|
||||||
- task: bar
|
- task: bar
|
||||||
vars:
|
vars:
|
||||||
BAR:
|
BAR:
|
||||||
ref: BAR # <-- BAR gets passed by reference to bar and maintains its type
|
ref: .BAR # <-- BAR gets passed by reference to bar and maintains its type
|
||||||
bar:
|
bar:
|
||||||
cmds:
|
cmds:
|
||||||
- 'echo {{index .BAR 0}}' # <-- BAR still refers to FOO so the task outputs 'A'
|
- 'echo {{index .BAR 0}}' # <-- BAR still refers to FOO so the task outputs 'A'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
All references use the same templating syntax as regular templates, so in
|
||||||
|
addition to simply calling `.FOO`, you can also pass subkeys (`.FOO.BAR`) or
|
||||||
|
indexes (`index .FOO 0`) and use functions (`len .FOO`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
FOO: [A, B, C] # <-- FOO is defined as an array
|
||||||
|
cmds:
|
||||||
|
- task: bar
|
||||||
|
vars:
|
||||||
|
FOO:
|
||||||
|
ref: index .FOO 0 # <-- The element at index 0 is passed by reference to bar
|
||||||
|
bar:
|
||||||
|
cmds:
|
||||||
|
- 'echo {{.MYVAR}}' # <-- FOO is just the letter 'A'
|
||||||
|
```
|
||||||
|
|
||||||
</TabItem></Tabs>
|
</TabItem></Tabs>
|
||||||
|
|
||||||
---
|
## Looping over maps
|
||||||
|
|
||||||
## Common to both proposals
|
This experiment also adds support for looping over maps using the `for` keyword,
|
||||||
|
just like arrays. In addition to the `{{.ITEM}}` variable being populated when
|
||||||
|
looping over a map, we also make an additional `{{.KEY}}` variable available
|
||||||
|
that holds the string value of the map key.
|
||||||
|
|
||||||
Both proposals add support for all other variable types by directly defining
|
<Tabs defaultValue="1" queryString="proposal"
|
||||||
them in the Taskfile. For example:
|
values={[
|
||||||
|
{label: 'Proposal 1', value: '1'},
|
||||||
|
{label: 'Proposal 2', value: '2'}
|
||||||
|
]}>
|
||||||
|
|
||||||
### Evaluating booleans
|
<TabItem value="1">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -264,64 +297,15 @@ version: 3
|
|||||||
tasks:
|
tasks:
|
||||||
foo:
|
foo:
|
||||||
vars:
|
vars:
|
||||||
BOOL: false
|
MAP: {a: 1, b: 2, c: 3}
|
||||||
cmds:
|
|
||||||
- '{{if .BOOL}}echo foo{{end}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arithmetic
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: 3
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
vars:
|
|
||||||
INT: 10
|
|
||||||
FLOAT: 3.14159
|
|
||||||
cmds:
|
|
||||||
- 'echo {{add .INT .FLOAT}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ranging
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: 3
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
vars:
|
|
||||||
ARRAY: [1, 2, 3]
|
|
||||||
cmds:
|
|
||||||
- 'echo {{range .ARRAY}}{{.}}{{end}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
There are many more templating functions which can be used with the new types of
|
|
||||||
variables. For a full list, see the [slim-sprig][slim-sprig] documentation.
|
|
||||||
|
|
||||||
## Looping over variables
|
|
||||||
|
|
||||||
Previously, you would have to use a delimiter separated string to loop over an
|
|
||||||
arbitrary list of items in a variable and split them by using the `split` subkey
|
|
||||||
to specify the delimiter:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: 3
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
vars:
|
|
||||||
LIST: 'foo,bar,baz'
|
|
||||||
cmds:
|
cmds:
|
||||||
- for:
|
- for:
|
||||||
var: LIST
|
var: MAP
|
||||||
split: ','
|
cmd: 'echo "{{.KEY}}: {{.ITEM}}"'
|
||||||
cmd: echo {{.ITEM}}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Both of these proposals add support for looping over "collection-type" variables
|
</TabItem>
|
||||||
using the `for` keyword, so now you are able to loop over a map/array variable
|
<TabItem value="2">
|
||||||
directly:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -329,18 +313,23 @@ version: 3
|
|||||||
tasks:
|
tasks:
|
||||||
foo:
|
foo:
|
||||||
vars:
|
vars:
|
||||||
LIST: [foo, bar, baz]
|
map:
|
||||||
|
MAP: {a: 1, b: 2, c: 3}
|
||||||
cmds:
|
cmds:
|
||||||
- for:
|
- for:
|
||||||
var: LIST
|
var: MAP
|
||||||
cmd: echo {{.ITEM}}
|
cmd: 'echo "{{.KEY}}: {{.ITEM}}"'
|
||||||
```
|
```
|
||||||
|
|
||||||
When looping over a map we also make an additional `{{.KEY}}` variable availabe
|
:::note
|
||||||
that holds the string value of the map key. Remember that maps are unordered, so
|
|
||||||
|
Remember that maps are unordered, so
|
||||||
the order in which the items are looped over is random.
|
the order in which the items are looped over is random.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[enabling-experiments]: /experiments/#enabling-experiments
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
[slim-sprig]: https://go-task.github.io/slim-sprig/
|
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
@@ -48,6 +48,20 @@ tasks:
|
|||||||
and you run `task my-remote-namespace:hello`, it will print the text: "Hello
|
and you run `task my-remote-namespace:hello`, it will print the text: "Hello
|
||||||
from the remote Taskfile!" to your console.
|
from the remote Taskfile!" to your console.
|
||||||
|
|
||||||
|
The Taskfile location is processed by the templating system, so you can
|
||||||
|
reference environment variables in your URL if you need to add authentication.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
my-remote-namespace: https://{{.TOKEN}}@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
`TOKEN=my-token task my-remote-namespace:hello` will be resolved by Task to
|
||||||
|
`https://my-token@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml`
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
Running commands from sources that you do not control is always a potential
|
Running commands from sources that you do not control is always a potential
|
||||||
@@ -99,6 +113,6 @@ the `--timeout` flag and specifying a duration. For example, `--timeout 5s` will
|
|||||||
set the timeout to 5 seconds.
|
set the timeout to 5 seconds.
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[enabling-experiments]: /experiments/#enabling-experiments
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
[man-in-the-middle-attacks]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
|
[man-in-the-middle-attacks]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -38,5 +38,5 @@ information.
|
|||||||
\{Short explanation of how users should migrate to the new behavior\}
|
\{Short explanation of how users should migrate to the new behavior\}
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[enabling-experiments]: /experiments/#enabling-experiments
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ brew install go-task
|
|||||||
|
|
||||||
### pkgx
|
### pkgx
|
||||||
|
|
||||||
If you're on macOS or Linux and have [pkgx](https://pkgx.sh/) installed, getting Task is as
|
If you're on macOS or Linux and have [pkgx][pkgx] installed, getting Task is as
|
||||||
simple as running:
|
simple as running:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -299,5 +299,5 @@ Invoke-Expression -Command path/to/task.ps1
|
|||||||
[godownloader]: https://github.com/goreleaser/godownloader
|
[godownloader]: https://github.com/goreleaser/godownloader
|
||||||
[choco]: https://chocolatey.org/
|
[choco]: https://chocolatey.org/
|
||||||
[scoop]: https://scoop.sh/
|
[scoop]: https://scoop.sh/
|
||||||
[tea]: https://tea.xyz/
|
[pkgx]: https://pkgx.sh/
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -256,8 +256,8 @@ The variable priority order was also different:
|
|||||||
4. `Taskvars.yml` variables
|
4. `Taskvars.yml` variables
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[deprecate-version-2-schema]: /deprecations/version-2-schema/
|
[deprecate-version-2-schema]: ./deprecations/version_2_schema.mdx
|
||||||
[output]: /usage#output-syntax
|
[output]: ./usage.mdx#output-syntax
|
||||||
[ignore_errors]: /usage#ignore-errors
|
[ignore_errors]: ./usage.mdx#ignore-errors
|
||||||
[includes]: /usage#including-other-taskfiles
|
[includes]: ./usage.mdx#including-other-taskfiles
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -121,13 +121,14 @@ tasks:
|
|||||||
### Reading a Taskfile from stdin
|
### Reading a Taskfile from stdin
|
||||||
|
|
||||||
Taskfile also supports reading from stdin. This is useful if you are generating
|
Taskfile also supports reading from stdin. This is useful if you are generating
|
||||||
Taskfiles dynamically and don't want write them to disk. This works just like
|
Taskfiles dynamically and don't want write them to disk. To tell task to read
|
||||||
any other program that supports stdin. For example:
|
from stdin, you must specify the `-t/--taskfile` flag with the special `-`
|
||||||
|
value. You may then pipe into Task as you would any other program:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
task < <(cat ./Taskfile.yml)
|
task -t - <(cat ./Taskfile.yml)
|
||||||
# OR
|
# OR
|
||||||
cat ./Taskfile.yml | task
|
cat ./Taskfile.yml | task -t -
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
@@ -947,8 +948,26 @@ tasks:
|
|||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
When doing interpolation of variables, Task will look for the below. They are
|
Task allows you to set variables using the `vars` keyword. The following
|
||||||
listed below in order of importance (i.e. most important first):
|
variable types are supported:
|
||||||
|
|
||||||
|
- `string`
|
||||||
|
- `bool`
|
||||||
|
- `int`
|
||||||
|
- `float`
|
||||||
|
- `array`
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
Maps are not supported by default, but there is an
|
||||||
|
[experiment][map-variables] that can be enabled to add support. If
|
||||||
|
you're interested in this functionality, we would appreciate your feedback.
|
||||||
|
:pray:
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Variables can be set in many places in a Taskfile. When executing templates,
|
||||||
|
Task will look for variables in the order listed below (most important first):
|
||||||
|
|
||||||
- Variables declared in the task definition
|
- Variables declared in the task definition
|
||||||
- Variables given while calling a task from another (See
|
- Variables given while calling a task from another (See
|
||||||
@@ -1093,8 +1112,8 @@ tasks:
|
|||||||
### Looping over variables
|
### Looping over variables
|
||||||
|
|
||||||
To loop over the contents of a variable, you simply need to specify the variable
|
To loop over the contents of a variable, you simply need to specify the variable
|
||||||
you want to loop over. By default, variables will be split on any whitespace
|
you want to loop over. By default, string variables will be split on any
|
||||||
characters.
|
whitespace characters.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '3'
|
version: '3'
|
||||||
@@ -1108,8 +1127,8 @@ tasks:
|
|||||||
cmd: cat {{.ITEM}}
|
cmd: cat {{.ITEM}}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need to split on a different character, you can do this by specifying the
|
If you need to split a string on a different character, you can do this by
|
||||||
`split` property:
|
specifying the `split` property:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '3'
|
version: '3'
|
||||||
@@ -1123,6 +1142,26 @@ tasks:
|
|||||||
cmd: cat {{.ITEM}}
|
cmd: cat {{.ITEM}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also loop over arrays directly (and maps if you have the
|
||||||
|
[maps experiment](/experiments/map-variables) enabled):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
LIST: [foo, bar, baz]
|
||||||
|
cmds:
|
||||||
|
- for:
|
||||||
|
var: LIST
|
||||||
|
cmd: echo {{.ITEM}}
|
||||||
|
```
|
||||||
|
|
||||||
|
When looping over a map we also make an additional `{{.KEY}}` variable available
|
||||||
|
that holds the string value of the map key. Remember that maps are unordered, so
|
||||||
|
the order in which the items are looped over is random.
|
||||||
|
|
||||||
All of this also works with dynamic variables!
|
All of this also works with dynamic variables!
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -1377,7 +1416,7 @@ Task also adds the following functions:
|
|||||||
converts a string from `/` path format to `\`.
|
converts a string from `/` path format to `\`.
|
||||||
- `exeExt`: Returns the right executable extension for the current OS (`".exe"`
|
- `exeExt`: Returns the right executable extension for the current OS (`".exe"`
|
||||||
for Windows, `""` for others).
|
for Windows, `""` for others).
|
||||||
- `shellQuote`: Quotes a string to make it safe for use in shell scripts. Task
|
- `shellQuote` (aliased to `q`): Quotes a string to make it safe for use in shell scripts. Task
|
||||||
uses [this Go function](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote)
|
uses [this Go function](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote)
|
||||||
for this. The Bash dialect is assumed.
|
for this. The Bash dialect is assumed.
|
||||||
- `splitArgs`: Splits a string as if it were a command's arguments. Task uses
|
- `splitArgs`: Splits a string as if it were a command's arguments. Task uses
|
||||||
@@ -1956,4 +1995,5 @@ if called by another task, either directly or as a dependency.
|
|||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[gotemplate]: https://golang.org/pkg/text/template/
|
[gotemplate]: https://golang.org/pkg/text/template/
|
||||||
|
[map-variables]: ./experiments/map_variables.mdx
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ const config: Config = {
|
|||||||
tagline: 'A task runner / simpler Make alternative written in Go ',
|
tagline: 'A task runner / simpler Make alternative written in Go ',
|
||||||
url: 'https://taskfile.dev',
|
url: 'https://taskfile.dev',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'warn',
|
||||||
onBrokenMarkdownLinks: 'throw',
|
onBrokenMarkdownLinks: 'warn',
|
||||||
favicon: 'img/favicon.ico',
|
favicon: 'img/favicon.ico',
|
||||||
|
|
||||||
organizationName: 'go-task',
|
organizationName: 'go-task',
|
||||||
@@ -31,56 +31,12 @@ const config: Config = {
|
|||||||
|
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
locales: [
|
locales: ['en'],
|
||||||
'en',
|
|
||||||
'es-ES',
|
|
||||||
'fr-FR',
|
|
||||||
'ja-JP',
|
|
||||||
'pt-BR',
|
|
||||||
'ru-RU',
|
|
||||||
'tr-TR',
|
|
||||||
'zh-Hans'
|
|
||||||
],
|
|
||||||
localeConfigs: {
|
localeConfigs: {
|
||||||
en: {
|
en: {
|
||||||
label: 'English',
|
label: 'English',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'en-US'
|
htmlLang: 'en-US'
|
||||||
},
|
|
||||||
'es-ES': {
|
|
||||||
label: `Español (${translationProgress['es-ES'] || 0}%)`,
|
|
||||||
direction: 'ltr',
|
|
||||||
htmlLang: 'es-ES'
|
|
||||||
},
|
|
||||||
'fr-FR': {
|
|
||||||
label: `Français (${translationProgress['fr'] || 0}%)`,
|
|
||||||
direction: 'ltr',
|
|
||||||
htmlLang: 'fr-FR'
|
|
||||||
},
|
|
||||||
'ja-JP': {
|
|
||||||
label: `日本語 (${translationProgress['ja'] || 0}%)`,
|
|
||||||
direction: 'ltr',
|
|
||||||
htmlLang: 'ja-JP'
|
|
||||||
},
|
|
||||||
'pt-BR': {
|
|
||||||
label: `Português (${translationProgress['pt-BR'] || 0}%)`,
|
|
||||||
direction: 'ltr',
|
|
||||||
htmlLang: 'pt-BR'
|
|
||||||
},
|
|
||||||
'ru-RU': {
|
|
||||||
label: `Pусский (${translationProgress['ru'] || 0}%)`,
|
|
||||||
direction: 'ltr',
|
|
||||||
htmlLang: 'ru-RU'
|
|
||||||
},
|
|
||||||
'tr-TR': {
|
|
||||||
label: `Türkçe (${translationProgress['tr'] || 0}%)`,
|
|
||||||
direction: 'ltr',
|
|
||||||
htmlLang: 'tr-TR'
|
|
||||||
},
|
|
||||||
'zh-Hans': {
|
|
||||||
label: `简体中文 (${translationProgress['zh-CN'] || 0}%)`,
|
|
||||||
direction: 'ltr',
|
|
||||||
htmlLang: 'zh-Hans'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -168,16 +124,6 @@ const config: Config = {
|
|||||||
position: 'right',
|
position: 'right',
|
||||||
dropdownActiveClassDisabled: true,
|
dropdownActiveClassDisabled: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'localeDropdown',
|
|
||||||
position: 'right',
|
|
||||||
dropdownItemsAfter: [
|
|
||||||
{
|
|
||||||
to: '/translate/',
|
|
||||||
label: 'Help Us Translate'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
href: GITHUB_URL,
|
href: GITHUB_URL,
|
||||||
position: 'right',
|
position: 'right',
|
||||||
|
|||||||
@@ -285,9 +285,9 @@
|
|||||||
"yaml": {
|
"yaml": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The value will parsed as a YAML string and stored in the variable"
|
"description": "The value will parsed as a YAML string and stored in the variable"
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
},
|
||||||
}
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"task_call": {
|
"task_call": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -512,7 +512,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"exclude": {
|
"exclude": {
|
||||||
"description": "File or glob patter to exclude from the list",
|
"description": "File or glob pattern to exclude from the list",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,7 +648,7 @@
|
|||||||
"$ref": "#/definitions/tasks"
|
"$ref": "#/definitions/tasks"
|
||||||
},
|
},
|
||||||
"silent": {
|
"silent": {
|
||||||
"description": "Default 'silent' options for this Taskfile. If `false`, can be overidden with `true` in a task by task basis.",
|
"description": "Default 'silent' options for this Taskfile. If `false`, can be overridden with `true` in a task by task basis.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"set": {
|
"set": {
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ vars:
|
|||||||
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
||||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
||||||
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
||||||
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/main/src/go/build/syslist.go). Task will be skipped otherwise. |
|
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Task will be skipped otherwise. |
|
||||||
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ tasks:
|
|||||||
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
||||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
||||||
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
||||||
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/main/src/go/build/syslist.go). Command will be skipped otherwise. |
|
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Command will be skipped otherwise. |
|
||||||
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,38 @@ sidebar_position: 14
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
@pd03).
|
||||||
|
- 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
|
## v3.36.0 - 2024-04-08
|
||||||
|
|
||||||
- Added support for
|
- Added support for
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
|
|||||||
---
|
---
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
|
[experiments]: /experiments
|
||||||
|
[experiments-workflow]: /experiments#workflow
|
||||||
[task]: https://github.com/go-task/task
|
[task]: https://github.com/go-task/task
|
||||||
[vscode-task]: https://github.com/go-task/vscode-task
|
[vscode-task]: https://github.com/go-task/vscode-task
|
||||||
[go]: https://go.dev
|
[go]: https://go.dev
|
||||||
|
|||||||
@@ -45,5 +45,5 @@ if you want to adopt the new behavior, you can continue to use the `--force`
|
|||||||
flag as you do now!
|
flag as you do now!
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[enabling-experiments]: /experiments/#enabling-experiments
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
slug: /experiments/any-variables/
|
slug: /experiments/map-variables/
|
||||||
---
|
---
|
||||||
|
|
||||||
import Tabs from '@theme/Tabs';
|
import Tabs from '@theme/Tabs';
|
||||||
import TabItem from '@theme/TabItem';
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
# Any Variables (#1415)
|
# Map Variables (#1585)
|
||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
|
|
||||||
@@ -15,19 +15,9 @@ environment. They are intended for testing and feedback only.
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Currently, Task only supports string variables. This experiment allows you to
|
Currently, Task supports all variable types except for maps. This experiment
|
||||||
specify and use the following variable types:
|
adds two different proposals for map variables. Click on the tabs below to
|
||||||
|
switch between them.
|
||||||
- `string`
|
|
||||||
- `bool`
|
|
||||||
- `int`
|
|
||||||
- `float`
|
|
||||||
- `array`
|
|
||||||
- `map`
|
|
||||||
|
|
||||||
This allows you to have a lot more flexibility in how you use variables in
|
|
||||||
Task's templating engine. There are two active proposals for this experiment.
|
|
||||||
Click on the tabs below to switch between them.
|
|
||||||
|
|
||||||
<Tabs defaultValue="1" queryString="proposal"
|
<Tabs defaultValue="1" queryString="proposal"
|
||||||
values={[
|
values={[
|
||||||
@@ -48,13 +38,11 @@ This experiment proposal breaks the following functionality:
|
|||||||
:::info
|
:::info
|
||||||
|
|
||||||
To enable this experiment, set the environment variable:
|
To enable this experiment, set the environment variable:
|
||||||
`TASK_X_ANY_VARIABLES=1`. Check out [our guide to enabling experiments
|
`TASK_X_MAP_VARIABLES=1`. Check out [our guide to enabling experiments
|
||||||
][enabling-experiments] for more information.
|
][enabling-experiments] for more information.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Maps
|
|
||||||
|
|
||||||
This proposal removes support for the `sh` keyword in favour of a new syntax for
|
This proposal removes support for the `sh` keyword in favour of a new syntax for
|
||||||
dynamically defined variables, This allows you to define a map directly as you
|
dynamically defined variables, This allows you to define a map directly as you
|
||||||
would for any other type:
|
would for any other type:
|
||||||
@@ -111,19 +99,16 @@ will now need to escape the `$` with a backslash (`\`) to stop Task from
|
|||||||
executing it as a command.
|
executing it as a command.
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<TabItem value="2">
|
<TabItem value="2">
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
To enable this experiment, set the environment variable:
|
To enable this experiment, set the environment variable:
|
||||||
`TASK_X_ANY_VARIABLES=2`. Check out [our guide to enabling experiments
|
`TASK_X_MAP_VARIABLES=2`. Check out [our guide to enabling experiments
|
||||||
][enabling-experiments] for more information.
|
][enabling-experiments] for more information.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Maps
|
|
||||||
|
|
||||||
This proposal maintains backwards-compatibility and the `sh` subkey and adds
|
This proposal maintains backwards-compatibility and the `sh` subkey and adds
|
||||||
another new `map` subkey for defining map variables:
|
another new `map` subkey for defining map variables:
|
||||||
|
|
||||||
@@ -150,7 +135,13 @@ objects/arrays. This is similar to the `fromJSON` template function, but means
|
|||||||
that you only have to parse the JSON/YAML once when you declare the variable,
|
that you only have to parse the JSON/YAML once when you declare the variable,
|
||||||
instead of every time you want to access a value.
|
instead of every time you want to access a value.
|
||||||
|
|
||||||
Before:
|
<Tabs defaultValue="2"
|
||||||
|
values={[
|
||||||
|
{label: 'Before', value: '1'},
|
||||||
|
{label: 'After', value: '2'}
|
||||||
|
]}>
|
||||||
|
|
||||||
|
<TabItem value="1">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -164,7 +155,8 @@ tasks:
|
|||||||
- 'echo {{(fromJSON .FOO).b}}'
|
- 'echo {{(fromJSON .FOO).b}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
After:
|
</TabItem>
|
||||||
|
<TabItem value="2">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -179,12 +171,26 @@ tasks:
|
|||||||
- 'echo {{.FOO.b}}'
|
- 'echo {{.FOO.b}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
## Variables by reference
|
## Variables by reference
|
||||||
|
|
||||||
Lastly, this proposal adds support for defining and passing variables by
|
Lastly, this proposal adds support for defining and passing variables by
|
||||||
reference. This is really important now that variables can be types other than a
|
reference. This is really important now that variables can be types other than a
|
||||||
string. Previously, to send a variable from one task to another, you would have
|
string.
|
||||||
to use the templating system to pass it:
|
|
||||||
|
Previously, to send a variable from one task to another, you would have to use
|
||||||
|
the templating system. Unfortunately, the templater _always_ outputs a string
|
||||||
|
and operations on the passed variable may not have behaved as expected. With
|
||||||
|
this proposal, you can now pass variables by reference using the `ref` subkey:
|
||||||
|
|
||||||
|
<Tabs defaultValue="2"
|
||||||
|
values={[
|
||||||
|
{label: 'Before', value: '1'},
|
||||||
|
{label: 'After', value: '2'}
|
||||||
|
]}>
|
||||||
|
|
||||||
|
<TabItem value="1">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -202,10 +208,8 @@ tasks:
|
|||||||
- 'echo {{index .FOO 0}}' # <-- FOO is a string so the task outputs '91' which is the ASCII code for '[' instead of the expected 'A'
|
- 'echo {{index .FOO 0}}' # <-- FOO is a string so the task outputs '91' which is the ASCII code for '[' instead of the expected 'A'
|
||||||
```
|
```
|
||||||
|
|
||||||
Unfortunately, this results in the value always being passed as a string as this
|
</TabItem>
|
||||||
is the output type of the templater and operations on the passed variable may
|
<TabItem value="2">
|
||||||
not behave as expected. With this proposal, you can now pass variables by
|
|
||||||
reference using the `ref` subkey:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -218,12 +222,14 @@ tasks:
|
|||||||
- task: bar
|
- task: bar
|
||||||
vars:
|
vars:
|
||||||
FOO:
|
FOO:
|
||||||
ref: FOO # <-- FOO gets passed by reference to bar and maintains its type
|
ref: .FOO # <-- FOO gets passed by reference to bar and maintains its type
|
||||||
bar:
|
bar:
|
||||||
cmds:
|
cmds:
|
||||||
- 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected
|
- 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
This means that the type of the variable is maintained when it is passed to
|
This means that the type of the variable is maintained when it is passed to
|
||||||
another Task. This also works the same way when calling `deps` and when defining
|
another Task. This also works the same way when calling `deps` and when defining
|
||||||
a variable and can be used in any combination:
|
a variable and can be used in any combination:
|
||||||
@@ -236,27 +242,54 @@ tasks:
|
|||||||
vars:
|
vars:
|
||||||
FOO: [A, B, C] # <-- FOO is defined as an array
|
FOO: [A, B, C] # <-- FOO is defined as an array
|
||||||
BAR:
|
BAR:
|
||||||
ref: FOO # <-- BAR is defined as a reference to FOO
|
ref: .FOO # <-- BAR is defined as a reference to FOO
|
||||||
deps:
|
deps:
|
||||||
- task: bar
|
- task: bar
|
||||||
vars:
|
vars:
|
||||||
BAR:
|
BAR:
|
||||||
ref: BAR # <-- BAR gets passed by reference to bar and maintains its type
|
ref: .BAR # <-- BAR gets passed by reference to bar and maintains its type
|
||||||
bar:
|
bar:
|
||||||
cmds:
|
cmds:
|
||||||
- 'echo {{index .BAR 0}}' # <-- BAR still refers to FOO so the task outputs 'A'
|
- 'echo {{index .BAR 0}}' # <-- BAR still refers to FOO so the task outputs 'A'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
All references use the same templating syntax as regular templates, so in
|
||||||
|
addition to simply calling `.FOO`, you can also pass subkeys (`.FOO.BAR`) or
|
||||||
|
indexes (`index .FOO 0`) and use functions (`len .FOO`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
FOO: [A, B, C] # <-- FOO is defined as an array
|
||||||
|
cmds:
|
||||||
|
- task: bar
|
||||||
|
vars:
|
||||||
|
FOO:
|
||||||
|
ref: index .FOO 0 # <-- The element at index 0 is passed by reference to bar
|
||||||
|
bar:
|
||||||
|
cmds:
|
||||||
|
- 'echo {{.MYVAR}}' # <-- FOO is just the letter 'A'
|
||||||
|
```
|
||||||
|
|
||||||
</TabItem></Tabs>
|
</TabItem></Tabs>
|
||||||
|
|
||||||
---
|
## Looping over maps
|
||||||
|
|
||||||
## Common to both proposals
|
This experiment also adds support for looping over maps using the `for` keyword,
|
||||||
|
just like arrays. In addition to the `{{.ITEM}}` variable being populated when
|
||||||
|
looping over a map, we also make an additional `{{.KEY}}` variable available
|
||||||
|
that holds the string value of the map key.
|
||||||
|
|
||||||
Both proposals add support for all other variable types by directly defining
|
<Tabs defaultValue="1" queryString="proposal"
|
||||||
them in the Taskfile. For example:
|
values={[
|
||||||
|
{label: 'Proposal 1', value: '1'},
|
||||||
|
{label: 'Proposal 2', value: '2'}
|
||||||
|
]}>
|
||||||
|
|
||||||
### Evaluating booleans
|
<TabItem value="1">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -264,64 +297,15 @@ version: 3
|
|||||||
tasks:
|
tasks:
|
||||||
foo:
|
foo:
|
||||||
vars:
|
vars:
|
||||||
BOOL: false
|
MAP: {a: 1, b: 2, c: 3}
|
||||||
cmds:
|
|
||||||
- '{{if .BOOL}}echo foo{{end}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arithmetic
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: 3
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
vars:
|
|
||||||
INT: 10
|
|
||||||
FLOAT: 3.14159
|
|
||||||
cmds:
|
|
||||||
- 'echo {{add .INT .FLOAT}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ranging
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: 3
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
vars:
|
|
||||||
ARRAY: [1, 2, 3]
|
|
||||||
cmds:
|
|
||||||
- 'echo {{range .ARRAY}}{{.}}{{end}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
There are many more templating functions which can be used with the new types of
|
|
||||||
variables. For a full list, see the [slim-sprig][slim-sprig] documentation.
|
|
||||||
|
|
||||||
## Looping over variables
|
|
||||||
|
|
||||||
Previously, you would have to use a delimiter separated string to loop over an
|
|
||||||
arbitrary list of items in a variable and split them by using the `split` subkey
|
|
||||||
to specify the delimiter:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: 3
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
vars:
|
|
||||||
LIST: 'foo,bar,baz'
|
|
||||||
cmds:
|
cmds:
|
||||||
- for:
|
- for:
|
||||||
var: LIST
|
var: MAP
|
||||||
split: ','
|
cmd: 'echo "{{.KEY}}: {{.ITEM}}"'
|
||||||
cmd: echo {{.ITEM}}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Both of these proposals add support for looping over "collection-type" variables
|
</TabItem>
|
||||||
using the `for` keyword, so now you are able to loop over a map/array variable
|
<TabItem value="2">
|
||||||
directly:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 3
|
version: 3
|
||||||
@@ -329,18 +313,23 @@ version: 3
|
|||||||
tasks:
|
tasks:
|
||||||
foo:
|
foo:
|
||||||
vars:
|
vars:
|
||||||
LIST: [foo, bar, baz]
|
map:
|
||||||
|
MAP: {a: 1, b: 2, c: 3}
|
||||||
cmds:
|
cmds:
|
||||||
- for:
|
- for:
|
||||||
var: LIST
|
var: MAP
|
||||||
cmd: echo {{.ITEM}}
|
cmd: 'echo "{{.KEY}}: {{.ITEM}}"'
|
||||||
```
|
```
|
||||||
|
|
||||||
When looping over a map we also make an additional `{{.KEY}}` variable availabe
|
:::note
|
||||||
that holds the string value of the map key. Remember that maps are unordered, so
|
|
||||||
|
Remember that maps are unordered, so
|
||||||
the order in which the items are looped over is random.
|
the order in which the items are looped over is random.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[enabling-experiments]: /experiments/#enabling-experiments
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
[slim-sprig]: https://go-task.github.io/slim-sprig/
|
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
@@ -48,6 +48,20 @@ tasks:
|
|||||||
and you run `task my-remote-namespace:hello`, it will print the text: "Hello
|
and you run `task my-remote-namespace:hello`, it will print the text: "Hello
|
||||||
from the remote Taskfile!" to your console.
|
from the remote Taskfile!" to your console.
|
||||||
|
|
||||||
|
The Taskfile location is processed by the templating system, so you can
|
||||||
|
reference environment variables in your URL if you need to add authentication.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
my-remote-namespace: https://{{.TOKEN}}@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
`TOKEN=my-token task my-remote-namespace:hello` will be resolved by Task to
|
||||||
|
`https://my-token@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml`
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
Running commands from sources that you do not control is always a potential
|
Running commands from sources that you do not control is always a potential
|
||||||
@@ -99,6 +113,6 @@ the `--timeout` flag and specifying a duration. For example, `--timeout 5s` will
|
|||||||
set the timeout to 5 seconds.
|
set the timeout to 5 seconds.
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[enabling-experiments]: /experiments/#enabling-experiments
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
[man-in-the-middle-attacks]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
|
[man-in-the-middle-attacks]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -38,5 +38,5 @@ information.
|
|||||||
\{Short explanation of how users should migrate to the new behavior\}
|
\{Short explanation of how users should migrate to the new behavior\}
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[enabling-experiments]: /experiments/#enabling-experiments
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ brew install go-task
|
|||||||
|
|
||||||
### pkgx
|
### pkgx
|
||||||
|
|
||||||
If you're on macOS or Linux and have [pkgx](https://pkgx.sh/) installed, getting Task is as
|
If you're on macOS or Linux and have [pkgx][pkgx] installed, getting Task is as
|
||||||
simple as running:
|
simple as running:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -299,5 +299,5 @@ Invoke-Expression -Command path/to/task.ps1
|
|||||||
[godownloader]: https://github.com/goreleaser/godownloader
|
[godownloader]: https://github.com/goreleaser/godownloader
|
||||||
[choco]: https://chocolatey.org/
|
[choco]: https://chocolatey.org/
|
||||||
[scoop]: https://scoop.sh/
|
[scoop]: https://scoop.sh/
|
||||||
[tea]: https://tea.xyz/
|
[pkgx]: https://pkgx.sh/
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -256,8 +256,8 @@ The variable priority order was also different:
|
|||||||
4. `Taskvars.yml` variables
|
4. `Taskvars.yml` variables
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[deprecate-version-2-schema]: /deprecations/version-2-schema/
|
[deprecate-version-2-schema]: ./deprecations/version_2_schema.mdx
|
||||||
[output]: /usage#output-syntax
|
[output]: ./usage.mdx#output-syntax
|
||||||
[ignore_errors]: /usage#ignore-errors
|
[ignore_errors]: ./usage.mdx#ignore-errors
|
||||||
[includes]: /usage#including-other-taskfiles
|
[includes]: ./usage.mdx#including-other-taskfiles
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
@@ -121,13 +121,14 @@ tasks:
|
|||||||
### Reading a Taskfile from stdin
|
### Reading a Taskfile from stdin
|
||||||
|
|
||||||
Taskfile also supports reading from stdin. This is useful if you are generating
|
Taskfile also supports reading from stdin. This is useful if you are generating
|
||||||
Taskfiles dynamically and don't want write them to disk. This works just like
|
Taskfiles dynamically and don't want write them to disk. To tell task to read
|
||||||
any other program that supports stdin. For example:
|
from stdin, you must specify the `-t/--taskfile` flag with the special `-`
|
||||||
|
value. You may then pipe into Task as you would any other program:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
task < <(cat ./Taskfile.yml)
|
task -t - <(cat ./Taskfile.yml)
|
||||||
# OR
|
# OR
|
||||||
cat ./Taskfile.yml | task
|
cat ./Taskfile.yml | task -t -
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
@@ -947,8 +948,26 @@ tasks:
|
|||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
When doing interpolation of variables, Task will look for the below. They are
|
Task allows you to set variables using the `vars` keyword. The following
|
||||||
listed below in order of importance (i.e. most important first):
|
variable types are supported:
|
||||||
|
|
||||||
|
- `string`
|
||||||
|
- `bool`
|
||||||
|
- `int`
|
||||||
|
- `float`
|
||||||
|
- `array`
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
Maps are not supported by default, but there is an
|
||||||
|
[experiment][map-variables] that can be enabled to add support. If
|
||||||
|
you're interested in this functionality, we would appreciate your feedback.
|
||||||
|
:pray:
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Variables can be set in many places in a Taskfile. When executing templates,
|
||||||
|
Task will look for variables in the order listed below (most important first):
|
||||||
|
|
||||||
- Variables declared in the task definition
|
- Variables declared in the task definition
|
||||||
- Variables given while calling a task from another (See
|
- Variables given while calling a task from another (See
|
||||||
@@ -1093,8 +1112,8 @@ tasks:
|
|||||||
### Looping over variables
|
### Looping over variables
|
||||||
|
|
||||||
To loop over the contents of a variable, you simply need to specify the variable
|
To loop over the contents of a variable, you simply need to specify the variable
|
||||||
you want to loop over. By default, variables will be split on any whitespace
|
you want to loop over. By default, string variables will be split on any
|
||||||
characters.
|
whitespace characters.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '3'
|
version: '3'
|
||||||
@@ -1108,8 +1127,8 @@ tasks:
|
|||||||
cmd: cat {{.ITEM}}
|
cmd: cat {{.ITEM}}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need to split on a different character, you can do this by specifying the
|
If you need to split a string on a different character, you can do this by
|
||||||
`split` property:
|
specifying the `split` property:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '3'
|
version: '3'
|
||||||
@@ -1123,6 +1142,26 @@ tasks:
|
|||||||
cmd: cat {{.ITEM}}
|
cmd: cat {{.ITEM}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also loop over arrays directly (and maps if you have the
|
||||||
|
[maps experiment](/experiments/map-variables) enabled):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
LIST: [foo, bar, baz]
|
||||||
|
cmds:
|
||||||
|
- for:
|
||||||
|
var: LIST
|
||||||
|
cmd: echo {{.ITEM}}
|
||||||
|
```
|
||||||
|
|
||||||
|
When looping over a map we also make an additional `{{.KEY}}` variable available
|
||||||
|
that holds the string value of the map key. Remember that maps are unordered, so
|
||||||
|
the order in which the items are looped over is random.
|
||||||
|
|
||||||
All of this also works with dynamic variables!
|
All of this also works with dynamic variables!
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -1377,7 +1416,7 @@ Task also adds the following functions:
|
|||||||
converts a string from `/` path format to `\`.
|
converts a string from `/` path format to `\`.
|
||||||
- `exeExt`: Returns the right executable extension for the current OS (`".exe"`
|
- `exeExt`: Returns the right executable extension for the current OS (`".exe"`
|
||||||
for Windows, `""` for others).
|
for Windows, `""` for others).
|
||||||
- `shellQuote`: Quotes a string to make it safe for use in shell scripts. Task
|
- `shellQuote` (aliased to `q`): Quotes a string to make it safe for use in shell scripts. Task
|
||||||
uses [this Go function](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote)
|
uses [this Go function](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote)
|
||||||
for this. The Bash dialect is assumed.
|
for this. The Bash dialect is assumed.
|
||||||
- `splitArgs`: Splits a string as if it were a command's arguments. Task uses
|
- `splitArgs`: Splits a string as if it were a command's arguments. Task uses
|
||||||
@@ -1956,4 +1995,5 @@ if called by another task, either directly or as a dependency.
|
|||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[gotemplate]: https://golang.org/pkg/text/template/
|
[gotemplate]: https://golang.org/pkg/text/template/
|
||||||
|
[map-variables]: ./experiments/map_variables.mdx
|
||||||
{/* prettier-ignore-end */}
|
{/* prettier-ignore-end */}
|
||||||
|
|||||||
Reference in New Issue
Block a user