Skip to content

Commit 9b99866

Browse files
authored
feat: add --failfast and failtest: true to control dependencies (#2525)
1 parent 54e4905 commit 9b99866

24 files changed

+180
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
- A small behavior change was made to dependencies. Task will now wait for all
6+
dependencies to finish running before continuing, even if any of them fail.
7+
To opt for the previous behavior, set `failfast: true` either on your
8+
`.taskrc.yml` or per task, or use the `--failfast` flag, which will also work
9+
for `--parallel` (#1246, #2525 by @andreynering).
510
- Fix RPM upload to Cloudsmith by including the version in the filename to
611
ensure unique filenames (#2507 by @vmaerten).
712
- Fix `run: when_changed` to work properly for Taskfiles included multiple times

completion/fish/task.fish

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set director
7474
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing'
7575
complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command'
7676
complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments'
77+
complete -c $GO_TASK_PROGNAME -s F -l failfast -d 'when running tasks in parallel, stop all tasks if one fails'
7778
complete -c $GO_TASK_PROGNAME -s f -l force -d 'force execution even when up-to-date'
7879
complete -c $GO_TASK_PROGNAME -s g -l global -d 'run global Taskfile from home directory'
7980
complete -c $GO_TASK_PROGNAME -s h -l help -d 'show help'

completion/ps/task.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock {
2020
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'),
2121
[CompletionResult]::new('--exit-code', '--exit-code', [CompletionResultType]::ParameterName, 'pass-through exit code'),
2222
[CompletionResult]::new('--experiments', '--experiments', [CompletionResultType]::ParameterName, 'list experiments'),
23+
[CompletionResult]::new('-F', '-F', [CompletionResultType]::ParameterName, 'fail fast on pallalel tasks'),
24+
[CompletionResult]::new('--failfast', '--failfast', [CompletionResultType]::ParameterName, 'force execution'),
2325
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'force execution'),
2426
[CompletionResult]::new('--force', '--force', [CompletionResultType]::ParameterName, 'force execution'),
2527
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'run global Taskfile'),

completion/zsh/_task

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ _task() {
5555
standard_args=(
5656
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: '
5757
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]'
58+
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]'
5859
'(-f --force)'{-f,--force}'[run even if task is up-to-date]'
5960
'(-c --color)'{-c,--color}'[colored output]'
6061
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell)'

executor.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type (
4848
Color bool
4949
Concurrency int
5050
Interval time.Duration
51+
Failfast bool
5152

5253
// I/O
5354
Stdin io.Reader
@@ -517,3 +518,16 @@ type versionCheckOption struct {
517518
func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
518519
e.EnableVersionCheck = o.enableVersionCheck
519520
}
521+
522+
// WithFailfast tells the [Executor] whether or not to check the version of
523+
func WithFailfast(failfast bool) ExecutorOption {
524+
return &failfastOption{failfast}
525+
}
526+
527+
type failfastOption struct {
528+
failfast bool
529+
}
530+
531+
func (o *failfastOption) ApplyToExecutor(e *Executor) {
532+
e.Failfast = o.failfast
533+
}

executor_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,3 +1020,50 @@ func TestIncludeChecksum(t *testing.T) {
10201020
WithFixtureTemplating(),
10211021
)
10221022
}
1023+
1024+
func TestFailfast(t *testing.T) {
1025+
t.Parallel()
1026+
1027+
t.Run("Default", func(t *testing.T) {
1028+
t.Parallel()
1029+
1030+
NewExecutorTest(t,
1031+
WithName("default"),
1032+
WithExecutorOptions(
1033+
task.WithDir("testdata/failfast/default"),
1034+
task.WithSilent(true),
1035+
),
1036+
WithPostProcessFn(PPSortedLines),
1037+
WithRunError(),
1038+
)
1039+
})
1040+
1041+
t.Run("Option", func(t *testing.T) {
1042+
t.Parallel()
1043+
1044+
NewExecutorTest(t,
1045+
WithName("default"),
1046+
WithExecutorOptions(
1047+
task.WithDir("testdata/failfast/default"),
1048+
task.WithSilent(true),
1049+
task.WithFailfast(true),
1050+
),
1051+
WithPostProcessFn(PPSortedLines),
1052+
WithRunError(),
1053+
)
1054+
})
1055+
1056+
t.Run("Task", func(t *testing.T) {
1057+
t.Parallel()
1058+
1059+
NewExecutorTest(t,
1060+
WithName("task"),
1061+
WithExecutorOptions(
1062+
task.WithDir("testdata/failfast/task"),
1063+
task.WithSilent(true),
1064+
),
1065+
WithPostProcessFn(PPSortedLines),
1066+
WithRunError(),
1067+
)
1068+
})
1069+
}

internal/flags/flags.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ var (
6969
Output ast.Output
7070
Color bool
7171
Interval time.Duration
72+
Failfast bool
7273
Global bool
7374
Experiments bool
7475
Download bool
@@ -138,6 +139,7 @@ func init() {
138139
pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
139140
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
140141
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
142+
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
141143
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
142144
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
143145

@@ -256,6 +258,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
256258
task.WithOutputStyle(Output),
257259
task.WithTaskSorter(sorter),
258260
task.WithVersionCheck(true),
261+
task.WithFailfast(Failfast),
259262
)
260263
}
261264

task.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ func (e *Executor) Run(ctx context.Context, calls ...*Call) error {
7878
return err
7979
}
8080

81-
g, ctx := errgroup.WithContext(ctx)
81+
g := &errgroup.Group{}
82+
if e.Failfast {
83+
g, ctx = errgroup.WithContext(ctx)
84+
}
8285
for _, c := range regularCalls {
8386
if e.Parallel {
8487
g.Go(func() error { return e.RunTask(ctx, c) })
@@ -257,7 +260,10 @@ func (e *Executor) mkdir(t *ast.Task) error {
257260
}
258261

259262
func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
260-
g, ctx := errgroup.WithContext(ctx)
263+
g := &errgroup.Group{}
264+
if e.Failfast || t.Failfast {
265+
g, ctx = errgroup.WithContext(ctx)
266+
}
261267

262268
reacquire := e.releaseConcurrencyLimit()
263269
defer reacquire()

taskfile/ast/task.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type Task struct {
4242
Platforms []*Platform
4343
Watch bool
4444
Location *Location
45+
Failfast bool
4546
// Populated during merging
4647
Namespace string `hash:"ignore"`
4748
IncludeVars *Vars
@@ -143,6 +144,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
143144
Platforms []*Platform
144145
Requires *Requires
145146
Watch bool
147+
Failfast bool
146148
}
147149
if err := node.Decode(&task); err != nil {
148150
return errors.NewTaskfileDecodeError(err, node)
@@ -181,6 +183,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
181183
t.Platforms = task.Platforms
182184
t.Requires = task.Requires
183185
t.Watch = task.Watch
186+
t.Failfast = task.Failfast
184187
return nil
185188
}
186189

@@ -226,6 +229,7 @@ func (t *Task) DeepCopy() *Task {
226229
Requires: t.Requires.DeepCopy(),
227230
Namespace: t.Namespace,
228231
FullName: t.FullName,
232+
Failfast: t.Failfast,
229233
}
230234
return c
231235
}

taskrc/ast/taskrc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type TaskRC struct {
1515
Concurrency *int `yaml:"concurrency"`
1616
Remote Remote `yaml:"remote"`
1717
Experiments map[string]int `yaml:"experiments"`
18+
Failfast bool `yaml:"failfast"`
1819
}
1920

2021
type Remote struct {
@@ -53,4 +54,5 @@ func (t *TaskRC) Merge(other *TaskRC) {
5354

5455
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
5556
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
57+
t.Failfast = cmp.Or(other.Failfast, t.Failfast)
5658
}

0 commit comments

Comments
 (0)