diff --git a/setup.go b/setup.go index 2388039507..55cd62f365 100644 --- a/setup.go +++ b/setup.go @@ -238,6 +238,7 @@ func (e *Executor) setupDefaults() { func (e *Executor) setupConcurrencyState() { e.executionHashes = make(map[string]context.Context) + e.taskSeparated = new(int32) e.taskCallCount = make(map[string]*int32, e.Taskfile.Tasks.Len()) e.mkdirMutexMap = make(map[string]*sync.Mutex, e.Taskfile.Tasks.Len()) for _, k := range e.Taskfile.Tasks.Keys() { diff --git a/task.go b/task.go index dd540a41dc..bbc398a2b3 100644 --- a/task.go +++ b/task.go @@ -79,6 +79,7 @@ type Executor struct { concurrencySemaphore chan struct{} taskCallCount map[string]*int32 + taskSeparated *int32 mkdirMutexMap map[string]*sync.Mutex executionHashes map[string]context.Context executionHashesMutex sync.Mutex @@ -224,6 +225,9 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v\n", t.Dir, err) } + if atomic.CompareAndSwapInt32(e.taskSeparated, 0, 1) { + e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.TaskSeparator, "separator") + } for i := range t.Cmds { if t.Cmds[i].Defer { defer e.runDeferred(t, call, i) @@ -247,6 +251,9 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { return &errors.TaskRunError{TaskName: t.Task, Err: err} } } + if atomic.CompareAndSwapInt32(e.taskSeparated, 0, 1) { + e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.TaskSeparator, "separator") + } e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task) return nil }) @@ -334,8 +341,10 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi if err != nil { return fmt.Errorf("task: failed to get variables: %w", err) } - stdOut, stdErr, close := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater) + stdOut, stdErr, closer := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater) + e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.Pre, "pre") + atomic.StoreInt32(e.taskSeparated, 0) err = execext.RunCommand(ctx, &execext.RunCommandOptions{ Command: cmd.Cmd, Dir: t.Dir, @@ -346,7 +355,9 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi Stdout: stdOut, Stderr: stdErr, }) - if closeErr := close(err); closeErr != nil { + e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.Post, "post") + + if closeErr := closer(err); closeErr != nil { e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr) } if execext.IsExitError(err) && cmd.IgnoreError { @@ -359,6 +370,48 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi } } +func (e *Executor) runCommandHook(ctx context.Context, t *taskfile.Task, call taskfile.Call, cmd string, kind string) { + if cmd == "" { + return + } + + if e.Dry { + return + } + + outputWrapper := e.Output + if t.Interactive { + outputWrapper = output.Interleaved{} + } + vars, err := e.Compiler.FastGetVariables(t, call) + outputTemplater := &templater.Templater{Vars: vars, RemoveNoValue: true} + if err != nil { + e.Logger.Errf(logger.Red, "task: failed to get variables for %s-command: %w", kind, err) + return + } + stdOut, stdErr, closer := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater) + + if err := execext.RunCommand(ctx, &execext.RunCommandOptions{ + Command: cmd, + Dir: t.Dir, + Env: env.Get(t), + PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set), + BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt), + Stdout: stdOut, + Stderr: stdErr, + }); err != nil { + e.Logger.Errf(logger.Red, "task: unable to run %s-command: %w", kind, err) + } + + if closeErr := closer(err); closeErr != nil { + e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr) + } + if execext.IsExitError(err) { + e.Logger.VerboseErrf(logger.Yellow, "task: [%s] %s-command error ignored: %v\n", t.Name(), kind, err) + return + } +} + func (e *Executor) startExecution(ctx context.Context, t *taskfile.Task, execute func(ctx context.Context) error) error { h, err := e.GetHash(t) if err != nil { diff --git a/taskfile/cmds.go b/taskfile/cmds.go new file mode 100644 index 0000000000..d0c604c979 --- /dev/null +++ b/taskfile/cmds.go @@ -0,0 +1,7 @@ +package taskfile + +type Cmds struct { + Pre string + Post string + TaskSeparator string `yaml:"task_separator"` +} diff --git a/taskfile/taskfile.go b/taskfile/taskfile.go index 344d00eb9a..f73e2f8ac1 100644 --- a/taskfile/taskfile.go +++ b/taskfile/taskfile.go @@ -27,6 +27,7 @@ type Taskfile struct { Shopt []string Vars *Vars Env *Vars + Cmds Cmds Tasks Tasks Silent bool Dotenv []string @@ -47,6 +48,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error { Shopt []string Vars *Vars Env *Vars + Cmds Cmds Tasks Tasks Silent bool Dotenv []string @@ -66,6 +68,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error { tf.Vars = taskfile.Vars tf.Env = taskfile.Env tf.Tasks = taskfile.Tasks + tf.Cmds = taskfile.Cmds tf.Silent = taskfile.Silent tf.Dotenv = taskfile.Dotenv tf.Run = taskfile.Run