mirror of
https://github.com/go-task/task.git
synced 2025-11-23 22:24:45 +02:00
feat: add --failfast and failtest: true to control dependencies
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 globally 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
|
||||
|
||||
14
executor.go
14
executor.go
@@ -47,6 +47,7 @@ type (
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
|
||||
// I/O
|
||||
Stdin io.Reader
|
||||
@@ -502,3 +503,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
|
||||
}
|
||||
|
||||
@@ -996,3 +996,64 @@ 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("Global", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("global"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/failfast/global"),
|
||||
task.WithSilent(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
|
||||
@@ -137,6 +138,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.BoolVar(&Failfast, "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.")
|
||||
|
||||
@@ -253,6 +255,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 || e.Taskfile.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 || e.Taskfile.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
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ type Taskfile struct {
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
}
|
||||
|
||||
// Merge merges the second Taskfile into the first
|
||||
@@ -81,6 +82,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
}
|
||||
if err := node.Decode(&taskfile); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
@@ -98,6 +100,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
tf.Dotenv = taskfile.Dotenv
|
||||
tf.Run = taskfile.Run
|
||||
tf.Interval = taskfile.Interval
|
||||
tf.Failfast = taskfile.Failfast
|
||||
if tf.Includes == nil {
|
||||
tf.Includes = NewIncludes()
|
||||
}
|
||||
|
||||
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 @@
|
||||
|
||||
16
testdata/failfast/global/Taskfile.yaml
vendored
Normal file
16
testdata/failfast/global/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
version: '3'
|
||||
|
||||
failfast: true
|
||||
|
||||
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/global/testdata/TestFailfast-Global-global-err-run.golden
vendored
Normal file
1
testdata/failfast/global/testdata/TestFailfast-Global-global-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/global/testdata/TestFailfast-Global-global.golden
vendored
Normal file
1
testdata/failfast/global/testdata/TestFailfast-Global-global.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,32 @@ 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`:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
failfast: true # global option so it applies to all tasks
|
||||
|
||||
tasks:
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -740,6 +745,11 @@
|
||||
"description": "Sets a different watch interval when using `--watch`, the default being 100 milliseconds. This string should be a valid Go duration: https://pkg.go.dev/time#ParseDuration.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+(?:m|s|ms)$"
|
||||
},
|
||||
"failfast": {
|
||||
"description": "When running tasks in parallel, stop all tasks if one fails.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
Reference in New Issue
Block a user