Skip to content

Commit 734fdba

Browse files
committed
feat: add --failfast and failtest: true to control dependencies
1 parent 7901cce commit 734fdba

21 files changed

+191
-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 globally or per
8+
task, or use the `--failfast` flag, which will also work for `--parallel`
9+
(#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

executor.go

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

5152
// I/O
5253
Stdin io.Reader
@@ -502,3 +503,16 @@ type versionCheckOption struct {
502503
func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
503504
e.EnableVersionCheck = o.enableVersionCheck
504505
}
506+
507+
// WithFailfast tells the [Executor] whether or not to check the version of
508+
func WithFailfast(failfast bool) ExecutorOption {
509+
return &failfastOption{failfast}
510+
}
511+
512+
type failfastOption struct {
513+
failfast bool
514+
}
515+
516+
func (o *failfastOption) ApplyToExecutor(e *Executor) {
517+
e.Failfast = o.failfast
518+
}

executor_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,3 +996,64 @@ func TestIncludeChecksum(t *testing.T) {
996996
WithFixtureTemplating(),
997997
)
998998
}
999+
1000+
func TestFailfast(t *testing.T) {
1001+
t.Parallel()
1002+
1003+
t.Run("Default", func(t *testing.T) {
1004+
t.Parallel()
1005+
1006+
NewExecutorTest(t,
1007+
WithName("default"),
1008+
WithExecutorOptions(
1009+
task.WithDir("testdata/failfast/default"),
1010+
task.WithSilent(true),
1011+
),
1012+
WithPostProcessFn(PPSortedLines),
1013+
WithRunError(),
1014+
)
1015+
})
1016+
1017+
t.Run("Option", func(t *testing.T) {
1018+
t.Parallel()
1019+
1020+
NewExecutorTest(t,
1021+
WithName("default"),
1022+
WithExecutorOptions(
1023+
task.WithDir("testdata/failfast/default"),
1024+
task.WithSilent(true),
1025+
task.WithFailfast(true),
1026+
),
1027+
WithPostProcessFn(PPSortedLines),
1028+
WithRunError(),
1029+
)
1030+
})
1031+
1032+
t.Run("Global", func(t *testing.T) {
1033+
t.Parallel()
1034+
1035+
NewExecutorTest(t,
1036+
WithName("global"),
1037+
WithExecutorOptions(
1038+
task.WithDir("testdata/failfast/global"),
1039+
task.WithSilent(true),
1040+
),
1041+
WithPostProcessFn(PPSortedLines),
1042+
WithRunError(),
1043+
)
1044+
})
1045+
1046+
t.Run("Task", func(t *testing.T) {
1047+
t.Parallel()
1048+
1049+
NewExecutorTest(t,
1050+
WithName("task"),
1051+
WithExecutorOptions(
1052+
task.WithDir("testdata/failfast/task"),
1053+
task.WithSilent(true),
1054+
),
1055+
WithPostProcessFn(PPSortedLines),
1056+
WithRunError(),
1057+
)
1058+
})
1059+
}

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
@@ -137,6 +138,7 @@ func init() {
137138
pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
138139
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
139140
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
141+
pflag.BoolVar(&Failfast, "failfast", false, "When running tasks in parallel, stop all tasks if one fails.")
140142
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
141143
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
142144

@@ -253,6 +255,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
253255
task.WithOutputStyle(Output),
254256
task.WithTaskSorter(sorter),
255257
task.WithVersionCheck(true),
258+
task.WithFailfast(Failfast),
256259
)
257260
}
258261

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 || e.Taskfile.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 || e.Taskfile.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
}

taskfile/ast/taskfile.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Taskfile struct {
3434
Dotenv []string
3535
Run string
3636
Interval time.Duration
37+
Failfast bool
3738
}
3839

3940
// Merge merges the second Taskfile into the first
@@ -81,6 +82,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
8182
Dotenv []string
8283
Run string
8384
Interval time.Duration
85+
Failfast bool
8486
}
8587
if err := node.Decode(&taskfile); err != nil {
8688
return errors.NewTaskfileDecodeError(err, node)
@@ -98,6 +100,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
98100
tf.Dotenv = taskfile.Dotenv
99101
tf.Run = taskfile.Run
100102
tf.Interval = taskfile.Interval
103+
tf.Failfast = taskfile.Failfast
101104
if tf.Includes == nil {
102105
tf.Includes = NewIncludes()
103106
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: '3'
2+
3+
tasks:
4+
default:
5+
deps:
6+
- dep1
7+
- dep2
8+
- dep3
9+
- dep4
10+
11+
dep1: sleep 0.1 && echo 'dep1'
12+
dep2: sleep 0.2 && echo 'dep2'
13+
dep3: sleep 0.3 && echo 'dep3'
14+
dep4: exit 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dep1
2+
dep2
3+
dep3

0 commit comments

Comments
 (0)