Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type (
Color bool
Concurrency int
Interval time.Duration
Failfast bool

// I/O
Stdin io.Reader
Expand Down Expand Up @@ -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
}
47 changes: 47 additions & 0 deletions executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -996,3 +996,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(),
)
})
}
3 changes: 3 additions & 0 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var (
Output ast.Output
Color bool
Interval time.Duration
Failfast bool
Global bool
Experiments bool
Download bool
Expand Down Expand Up @@ -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.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.")

Expand Down Expand Up @@ -253,6 +255,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithOutputStyle(Output),
task.WithTaskSorter(sorter),
task.WithVersionCheck(true),
task.WithFailfast(Failfast),
)
}

Expand Down
10 changes: 8 additions & 2 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) })
Expand Down Expand Up @@ -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()
Expand Down
4 changes: 4 additions & 0 deletions taskfile/ast/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Task struct {
Platforms []*Platform
Watch bool
Location *Location
Failfast bool
// Populated during merging
Namespace string `hash:"ignore"`
IncludeVars *Vars
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -226,6 +229,7 @@ func (t *Task) DeepCopy() *Task {
Requires: t.Requires.DeepCopy(),
Namespace: t.Namespace,
FullName: t.FullName,
Failfast: t.Failfast,
}
return c
}
2 changes: 2 additions & 0 deletions taskrc/ast/taskrc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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 {
Expand Down Expand Up @@ -45,4 +46,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 changes: 14 additions & 0 deletions testdata/failfast/default/Taskfile.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dep1
dep2
dep3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

15 changes: 15 additions & 0 deletions testdata/failfast/task/Taskfile.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
}
Expand Down
25 changes: 25 additions & 0 deletions website/src/docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2384,5 +2408,6 @@ to us.

:::

[config]: /docs/reference/config
[gotemplate]: https://golang.org/pkg/text/template/
[templating-reference]: /docs/reference/templating
8 changes: 8 additions & 0 deletions website/src/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions website/src/docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions website/src/public/schema-taskrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,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
Expand Down
5 changes: 5 additions & 0 deletions website/src/public/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
},
Expand Down
Loading