mirror of
https://github.com/go-task/task.git
synced 2025-12-14 18:57:43 +01:00
feat: add --failfast and failtest: true to control dependencies (#2525)
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- A small behavior change was made to dependencies. Task will now wait for all
|
||||
dependencies to finish running before continuing, even if any of them fail.
|
||||
To opt for the previous behavior, set `failfast: true` either on your
|
||||
`.taskrc.yml` or per task, or use the `--failfast` flag, which will also work
|
||||
for `--parallel` (#1246, #2525 by @andreynering).
|
||||
- Fix RPM upload to Cloudsmith by including the version in the filename to
|
||||
ensure unique filenames (#2507 by @vmaerten).
|
||||
- Fix `run: when_changed` to work properly for Taskfiles included multiple times
|
||||
|
||||
@@ -74,6 +74,7 @@ complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set director
|
||||
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing'
|
||||
complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command'
|
||||
complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments'
|
||||
complete -c $GO_TASK_PROGNAME -s F -l failfast -d 'when running tasks in parallel, stop all tasks if one fails'
|
||||
complete -c $GO_TASK_PROGNAME -s f -l force -d 'force execution even when up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -s g -l global -d 'run global Taskfile from home directory'
|
||||
complete -c $GO_TASK_PROGNAME -s h -l help -d 'show help'
|
||||
|
||||
@@ -20,6 +20,8 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock {
|
||||
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--exit-code', '--exit-code', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--experiments', '--experiments', [CompletionResultType]::ParameterName, 'list experiments'),
|
||||
[CompletionResult]::new('-F', '-F', [CompletionResultType]::ParameterName, 'fail fast on pallalel tasks'),
|
||||
[CompletionResult]::new('--failfast', '--failfast', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('--force', '--force', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
|
||||
@@ -55,6 +55,7 @@ _task() {
|
||||
standard_args=(
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: '
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]'
|
||||
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]'
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]'
|
||||
'(-c --color)'{-c,--color}'[colored output]'
|
||||
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell)'
|
||||
|
||||
14
executor.go
14
executor.go
@@ -48,6 +48,7 @@ type (
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
|
||||
// I/O
|
||||
Stdin io.Reader
|
||||
@@ -517,3 +518,16 @@ type versionCheckOption struct {
|
||||
func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
|
||||
e.EnableVersionCheck = o.enableVersionCheck
|
||||
}
|
||||
|
||||
// WithFailfast tells the [Executor] whether or not to check the version of
|
||||
func WithFailfast(failfast bool) ExecutorOption {
|
||||
return &failfastOption{failfast}
|
||||
}
|
||||
|
||||
type failfastOption struct {
|
||||
failfast bool
|
||||
}
|
||||
|
||||
func (o *failfastOption) ApplyToExecutor(e *Executor) {
|
||||
e.Failfast = o.failfast
|
||||
}
|
||||
|
||||
@@ -1020,3 +1020,50 @@ func TestIncludeChecksum(t *testing.T) {
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestFailfast(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("default"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/failfast/default"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
WithRunError(),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Option", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("default"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/failfast/default"),
|
||||
task.WithSilent(true),
|
||||
task.WithFailfast(true),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
WithRunError(),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Task", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("task"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/failfast/task"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
WithRunError(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ var (
|
||||
Output ast.Output
|
||||
Color bool
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
Global bool
|
||||
Experiments bool
|
||||
Download bool
|
||||
@@ -138,6 +139,7 @@ func init() {
|
||||
pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
|
||||
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
|
||||
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
|
||||
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
|
||||
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
|
||||
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
|
||||
|
||||
@@ -256,6 +258,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
task.WithOutputStyle(Output),
|
||||
task.WithTaskSorter(sorter),
|
||||
task.WithVersionCheck(true),
|
||||
task.WithFailfast(Failfast),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
10
task.go
10
task.go
@@ -78,7 +78,10 @@ func (e *Executor) Run(ctx context.Context, calls ...*Call) error {
|
||||
return err
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
g := &errgroup.Group{}
|
||||
if e.Failfast {
|
||||
g, ctx = errgroup.WithContext(ctx)
|
||||
}
|
||||
for _, c := range regularCalls {
|
||||
if e.Parallel {
|
||||
g.Go(func() error { return e.RunTask(ctx, c) })
|
||||
@@ -257,7 +260,10 @@ func (e *Executor) mkdir(t *ast.Task) error {
|
||||
}
|
||||
|
||||
func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
g := &errgroup.Group{}
|
||||
if e.Failfast || t.Failfast {
|
||||
g, ctx = errgroup.WithContext(ctx)
|
||||
}
|
||||
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
defer reacquire()
|
||||
|
||||
@@ -42,6 +42,7 @@ type Task struct {
|
||||
Platforms []*Platform
|
||||
Watch bool
|
||||
Location *Location
|
||||
Failfast bool
|
||||
// Populated during merging
|
||||
Namespace string `hash:"ignore"`
|
||||
IncludeVars *Vars
|
||||
@@ -143,6 +144,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Platforms []*Platform
|
||||
Requires *Requires
|
||||
Watch bool
|
||||
Failfast bool
|
||||
}
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
@@ -181,6 +183,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.Platforms = task.Platforms
|
||||
t.Requires = task.Requires
|
||||
t.Watch = task.Watch
|
||||
t.Failfast = task.Failfast
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -226,6 +229,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
FullName: t.FullName,
|
||||
Failfast: t.Failfast,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type TaskRC struct {
|
||||
Concurrency *int `yaml:"concurrency"`
|
||||
Remote Remote `yaml:"remote"`
|
||||
Experiments map[string]int `yaml:"experiments"`
|
||||
Failfast bool `yaml:"failfast"`
|
||||
}
|
||||
|
||||
type Remote struct {
|
||||
@@ -53,4 +54,5 @@ func (t *TaskRC) Merge(other *TaskRC) {
|
||||
|
||||
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
|
||||
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
|
||||
t.Failfast = cmp.Or(other.Failfast, t.Failfast)
|
||||
}
|
||||
|
||||
14
testdata/failfast/default/Taskfile.yaml
vendored
Normal file
14
testdata/failfast/default/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps:
|
||||
- dep1
|
||||
- dep2
|
||||
- dep3
|
||||
- dep4
|
||||
|
||||
dep1: sleep 0.1 && echo 'dep1'
|
||||
dep2: sleep 0.2 && echo 'dep2'
|
||||
dep3: sleep 0.3 && echo 'dep3'
|
||||
dep4: exit 1
|
||||
1
testdata/failfast/default/testdata/TestFailfast-Default-default-err-run.golden
vendored
Normal file
1
testdata/failfast/default/testdata/TestFailfast-Default-default-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
|
||||
3
testdata/failfast/default/testdata/TestFailfast-Default-default.golden
vendored
Normal file
3
testdata/failfast/default/testdata/TestFailfast-Default-default.golden
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dep1
|
||||
dep2
|
||||
dep3
|
||||
1
testdata/failfast/default/testdata/TestFailfast-Option-default-err-run.golden
vendored
Normal file
1
testdata/failfast/default/testdata/TestFailfast-Option-default-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
|
||||
1
testdata/failfast/default/testdata/TestFailfast-Option-default.golden
vendored
Normal file
1
testdata/failfast/default/testdata/TestFailfast-Option-default.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
15
testdata/failfast/task/Taskfile.yaml
vendored
Normal file
15
testdata/failfast/task/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps:
|
||||
- dep1
|
||||
- dep2
|
||||
- dep3
|
||||
- dep4
|
||||
failfast: true
|
||||
|
||||
dep1: sleep 0.1 && echo 'dep1'
|
||||
dep2: sleep 0.2 && echo 'dep2'
|
||||
dep3: sleep 0.3 && echo 'dep3'
|
||||
dep4: exit 1
|
||||
1
testdata/failfast/task/testdata/TestFailfast-Task-task-err-run.golden
vendored
Normal file
1
testdata/failfast/task/testdata/TestFailfast-Task-task-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
|
||||
1
testdata/failfast/task/testdata/TestFailfast-Task-task.golden
vendored
Normal file
1
testdata/failfast/task/testdata/TestFailfast-Task-task.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -71,6 +71,7 @@ func (e *Executor) CompiledTaskForTaskList(call *Call) (*ast.Task, error) {
|
||||
Requires: origTask.Requires,
|
||||
Watch: origTask.Watch,
|
||||
Namespace: origTask.Namespace,
|
||||
Failfast: origTask.Failfast,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -125,6 +126,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
Location: origTask.Location,
|
||||
Requires: origTask.Requires,
|
||||
Watch: origTask.Watch,
|
||||
Failfast: origTask.Failfast,
|
||||
Namespace: origTask.Namespace,
|
||||
FullName: fullName,
|
||||
}
|
||||
|
||||
@@ -591,6 +591,30 @@ tasks:
|
||||
- echo {{.TEXT}}
|
||||
```
|
||||
|
||||
### Fail-fast dependencies
|
||||
|
||||
By default, Task waits for all dependencies to finish running before continuing.
|
||||
If you want Task to stop executing further dependencies as soon as one fails,
|
||||
you can set `failfast: true` on your [`.taskrc.yml`][config] or for a specific
|
||||
task:
|
||||
|
||||
```yaml
|
||||
# .taskrc.yml
|
||||
failfast: true # applies to all tasks
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Taskfile.yml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps: [task1, task2, task3]
|
||||
failfast: true # applies only to this task
|
||||
```
|
||||
|
||||
Alternatively, you can use `--failfast`, which also work for `--parallel`.
|
||||
|
||||
## Platform specific tasks and commands
|
||||
|
||||
If you want to restrict the running of tasks to explicit platforms, this can be
|
||||
@@ -2384,5 +2408,6 @@ to us.
|
||||
|
||||
:::
|
||||
|
||||
[config]: /docs/reference/config
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[templating-reference]: /docs/reference/templating
|
||||
|
||||
@@ -110,6 +110,14 @@ task deploy --silent
|
||||
|
||||
### Execution Control
|
||||
|
||||
#### `-F, --failfast`
|
||||
|
||||
Stop executing dependencies as soon as one of them fails.
|
||||
|
||||
```bash
|
||||
task build --failfast
|
||||
```
|
||||
|
||||
#### `-f, --force`
|
||||
|
||||
Force execution even when the task is up-to-date.
|
||||
|
||||
@@ -102,6 +102,17 @@ verbose: true
|
||||
concurrency: 4
|
||||
```
|
||||
|
||||
### `failfast`
|
||||
|
||||
- **Type**: `boolean`
|
||||
- **Default**: `false`
|
||||
- **Description**: Stop executing dependencies as soon as one of them fail
|
||||
- **CLI equivalent**: [`-F, --failfast`](./cli.md#f-failfast)
|
||||
|
||||
```yaml
|
||||
failfast: true
|
||||
```
|
||||
|
||||
## Example Configuration
|
||||
|
||||
Here's a complete example of a `.taskrc.yml` file with all available options:
|
||||
|
||||
@@ -61,6 +61,11 @@
|
||||
"type": "integer",
|
||||
"description": "Number of concurrent tasks to run",
|
||||
"minimum": 1
|
||||
},
|
||||
"failfast": {
|
||||
"description": "When running tasks in parallel, stop all tasks if one fails.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -201,6 +201,11 @@
|
||||
"description": "Configures a task to run in watch mode automatically.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"failfast": {
|
||||
"description": "When running tasks in parallel, stop all tasks if one fails.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user