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
67 changes: 42 additions & 25 deletions executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,32 @@ type (
// within them.
Executor struct {
// Flags
Dir string
Entrypoint string
TempDir TempDir
Force bool
ForceAll bool
Insecure bool
Download bool
Offline bool
TrustedHosts []string
Timeout time.Duration
CacheExpiryDuration time.Duration
RemoteCacheDir string
Watch bool
Verbose bool
Silent bool
DisableFuzzy bool
AssumeYes bool
AssumeTerm bool // Used for testing
Dry bool
Summary bool
Parallel bool
Color bool
Concurrency int
Interval time.Duration
Failfast bool
Dir string
Entrypoint string
TempDir TempDir
Force bool
ForceAll bool
Insecure bool
Download bool
Offline bool
TrustedHosts []string
Timeout time.Duration
CacheExpiryDuration time.Duration
RemoteCacheDir string
Watch bool
Verbose bool
Silent bool
DisableFuzzy bool
AssumeYes bool
AssumeTerm bool // Used for testing
Dry bool
Summary bool
Parallel bool
PropagateSharedErrors bool
Color bool
Concurrency int
Interval time.Duration
Failfast bool

// I/O
Stdin io.Reader
Expand Down Expand Up @@ -409,6 +410,22 @@ func (o *parallelOption) ApplyToExecutor(e *Executor) {
e.Parallel = o.parallel
}

// WithPropagateSharedErrors tells the [Executor] to propagate errors from shared
// task executions (e.g. tasks with `run: once`) to all concurrent waiters.
// When disabled, Task will still wait for the shared execution to complete, but
// other waiters will not fail if the shared execution fails (legacy behavior).
func WithPropagateSharedErrors(propagate bool) ExecutorOption {
return &propagateSharedErrorsOption{propagate}
}

type propagateSharedErrorsOption struct {
propagate bool
}

func (o *propagateSharedErrorsOption) ApplyToExecutor(e *Executor) {
e.PropagateSharedErrors = o.propagate
}

// WithColor tells the [Executor] whether or not to output using colorized
// strings.
func WithColor(color bool) ExecutorOption {
Expand Down
81 changes: 42 additions & 39 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,45 +44,46 @@ Options:
`

var (
Version bool
Help bool
Init bool
Completion string
List bool
ListAll bool
ListJson bool
TaskSort string
Status bool
NoStatus bool
Nested bool
Insecure bool
Force bool
ForceAll bool
Watch bool
Verbose bool
Silent bool
DisableFuzzy bool
AssumeYes bool
Dry bool
Summary bool
ExitCode bool
Parallel bool
Concurrency int
Dir string
Entrypoint string
Output ast.Output
Color bool
Interval time.Duration
Failfast bool
Global bool
Experiments bool
Download bool
Offline bool
TrustedHosts []string
ClearCache bool
Timeout time.Duration
CacheExpiryDuration time.Duration
RemoteCacheDir string
Version bool
Help bool
Init bool
Completion string
List bool
ListAll bool
ListJson bool
TaskSort string
Status bool
NoStatus bool
Nested bool
Insecure bool
Force bool
ForceAll bool
Watch bool
Verbose bool
Silent bool
DisableFuzzy bool
AssumeYes bool
Dry bool
Summary bool
ExitCode bool
Parallel bool
PropagateSharedErrors bool
Concurrency int
Dir string
Entrypoint string
Output ast.Output
Color bool
Interval time.Duration
Failfast bool
Global bool
Experiments bool
Download bool
Offline bool
TrustedHosts []string
ClearCache bool
Timeout time.Duration
CacheExpiryDuration time.Duration
RemoteCacheDir string
)

func init() {
Expand Down Expand Up @@ -133,6 +134,7 @@ func init() {
pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.")
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
pflag.BoolVar(&PropagateSharedErrors, "propagate-shared-errors", false, "When tasks share execution (e.g. run: once), propagate errors from the shared task to all dependents.")
pflag.BoolVarP(&Dry, "dry", "n", false, "Compiles and prints tasks in the order that they would be run, without executing them.")
pflag.BoolVar(&Summary, "summary", false, "Show summary about a task.")
pflag.BoolVarP(&ExitCode, "exit-code", "x", false, "Pass-through the exit code of the task command.")
Expand Down Expand Up @@ -284,6 +286,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithDry(Dry || Status),
task.WithSummary(Summary),
task.WithParallel(Parallel),
task.WithPropagateSharedErrors(PropagateSharedErrors),
task.WithColor(Color),
task.WithConcurrency(Concurrency),
task.WithInterval(Interval),
Expand Down
15 changes: 11 additions & 4 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,16 +387,23 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func
defer reacquire()

<-otherExecutionCtx.Done()
return nil

// Legacy behavior: shared executions are waited on, but their errors are not propagated.
if !e.PropagateSharedErrors {
return nil
}
return context.Cause(otherExecutionCtx)
}

ctx, cancel := context.WithCancel(ctx)
defer cancel()
ctx, cancel := context.WithCancelCause(ctx)
defer func() { cancel(err) }()

e.executionHashes[h] = ctx
e.executionHashesMutex.Unlock()

return execute(ctx)
// Save err in variable so it also applied to the cancel defer
err = execute(ctx)
return err
}

// FindMatchingTasks returns a list of tasks that match the given call. A task
Expand Down
26 changes: 26 additions & 0 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1874,6 +1874,32 @@ func TestRunOnceSharedDeps(t *testing.T) {
assert.Contains(t, buff.String(), `task: [service-b:build] echo "build b"`)
}

func TestRunOnceSharedDepsFail(t *testing.T) {
t.Parallel()

const dir = "testdata/run_once_shared_deps_fail"

var buff bytes.Buffer
e := task.NewExecutor(
task.WithDir(dir),
task.WithStdout(&buff),
task.WithStderr(&buff),
task.WithForceAll(true),
task.WithPropagateSharedErrors(true),
)
require.NoError(t, e.Setup())
require.Error(t, e.Run(t.Context(), &task.Call{Task: "build"}))

// The shared dependency should still only be attempted once.
rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library" && exit 1`)
matches := rx.FindAllStringSubmatch(buff.String(), -1)
assert.Len(t, matches, 1)

// If the shared dependency fails, both branches must be blocked from running their own commands.
assert.NotContains(t, buff.String(), `task: [service-a:build] echo "build a"`)
assert.NotContains(t, buff.String(), `task: [service-b:build] echo "build b"`)
}

func TestRunWhenChanged(t *testing.T) {
t.Parallel()

Expand Down
13 changes: 13 additions & 0 deletions testdata/run_once_shared_deps_fail/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3'

includes:
service-a: ./service-a
service-b: ./service-b

tasks:
build:
deps:
- service-a:build
- service-b:build


9 changes: 9 additions & 0 deletions testdata/run_once_shared_deps_fail/library/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: '3'

tasks:
build:
run: once
cmds:
- echo "build library" && exit 1


15 changes: 15 additions & 0 deletions testdata/run_once_shared_deps_fail/service-a/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: '3'

includes:
library:
taskfile: ../library/Taskfile.yml
dir: ../library

tasks:
build:
run: once
deps: [library:build]
cmds:
- echo "build a"


13 changes: 13 additions & 0 deletions testdata/run_once_shared_deps_fail/service-b/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3'

includes:
library:
taskfile: ../library/Taskfile.yml
dir: ../library

tasks:
build:
run: once
deps: [library:build]
cmds:
- echo "build b"
8 changes: 8 additions & 0 deletions website/src/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ Execute multiple tasks in parallel.
task test lint --parallel
```

#### `--propagate-shared-errors`

When tasks share execution (e.g. tasks with `run: once`), propagate errors from the shared task to all dependents.

```bash
task build --propagate-shared-errors
```

#### `-C, --concurrency <number>`

Limit the number of concurrent tasks. Zero means unlimited.
Expand Down