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
7 changes: 7 additions & 0 deletions cmd/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ func run() error {
return err
}

tf := e.Taskfile
if flags.FailFast && tf != nil && tf.Tasks != nil {
for t := range tf.Tasks.Values(nil) {
t.FailFast = true
}
}

if flags.ClearCache {
cachePath := filepath.Join(e.TempDir.Remote, "remote")
return os.RemoveAll(cachePath)
Expand Down
4 changes: 4 additions & 0 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ var (
ClearCache bool
Timeout time.Duration
CacheExpiryDuration time.Duration
FailFast bool
)

func init() {
Expand Down Expand Up @@ -156,6 +157,9 @@ func init() {
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
}

pflag.BoolVar(&FailFast, "failfast", false, "Run parallel deps to completion but still exit non-zero if any failed.")

pflag.Parse()
}

Expand Down
51 changes: 40 additions & 11 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"runtime"
"slices"
"sync"
"sync/atomic"

"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -258,23 +259,51 @@ func (e *Executor) mkdir(t *ast.Task) error {
}

func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
g, ctx := errgroup.WithContext(ctx)

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

for _, d := range t.Deps {
d := d
g.Go(func() error {
if t.FailFast {
g, ctx := errgroup.WithContext(ctx)
for _, d := range t.Deps {
d := d
g.Go(func() error {
return e.RunTask(ctx, &Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true})
})
}
return g.Wait()
}

type depResult struct {
idx int
err error
}

results := make(chan depResult, len(t.Deps))
var wg sync.WaitGroup
wg.Add(len(t.Deps))

for i, d := range t.Deps {
i, d := i, d
go func() {
defer wg.Done()
err := e.RunTask(ctx, &Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true})
if err != nil {
return err
}
return nil
})
results <- depResult{idx: i, err: err}
}()
}

return g.Wait()
wg.Wait()
close(results)

var firstErr error
for res := range results {
if res.err != nil && firstErr == nil {
firstErr = res.err
}
}
if firstErr != nil {
return fmt.Errorf("one or more dependencies failed: %w", firstErr)
}
return nil
}

func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, deferredExitCode *uint8) {
Expand Down
4 changes: 4 additions & 0 deletions taskfile/ast/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Task struct {
IncludedTaskfileVars *Vars

FullName string
FailFast bool
}

func (t *Task) Name() string {
Expand Down Expand Up @@ -143,6 +144,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
Platforms []*Platform
Requires *Requires
Watch bool
FailFast *bool `yaml:"failfast"`
}
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 == nil || *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
}
1 change: 1 addition & 0 deletions variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
Watch: origTask.Watch,
Namespace: origTask.Namespace,
FullName: fullName,
FailFast: origTask.FailFast,
}
new.Dir, err = execext.ExpandLiteral(new.Dir)
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions website/src/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ task build --color=false
NO_COLOR=1 task build
```

#### `--failfast`

Run deps and stop on first failure. Enabled by default.

```bash
task build --failfast=false
```

### Task Information

#### `--status`
Expand Down
20 changes: 20 additions & 0 deletions website/src/docs/reference/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,26 @@ tasks:
- go build -o app ./cmd
```

#### `failfast`

- **Type**: `bool`
- **Default**: `true`
- **Description**: Run deps and stop on first failure.

```yaml
tasks:
task-a:
cmds: [ "bash -c 'echo A; sleep 1; exit 1'" ]
task-b:
cmds: [ "bash -c 'echo B; sleep 2; exit 0'" ]
task-c:
cmds: [ "bash -c 'echo C; sleep 3; exit 1'" ]

parent:
deps: [task-a, task-b, task-c]
failfast: false
```

## Command

Individual command configuration within a task.
Expand Down