From c03a6845fea6381543de55b7cfeecce647275bc3 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 12 Sep 2023 21:08:02 +0200 Subject: [PATCH 1/3] Command pre/post hooks --- task.go | 18 ++++++++++++++++++ taskfile/cmds.go | 6 ++++++ taskfile/taskfile.go | 3 +++ 3 files changed, 27 insertions(+) create mode 100644 taskfile/cmds.go diff --git a/task.go b/task.go index dd540a41dc..91b7becdfd 100644 --- a/task.go +++ b/task.go @@ -336,6 +336,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi } stdOut, stdErr, close := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater) + e.runCommandHook(ctx, t, stdOut, stdErr, e.Taskfile.Cmds.Pre, "pre") err = execext.RunCommand(ctx, &execext.RunCommandOptions{ Command: cmd.Cmd, Dir: t.Dir, @@ -346,6 +347,8 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi Stdout: stdOut, Stderr: stdErr, }) + e.runCommandHook(ctx, t, stdOut, stdErr, e.Taskfile.Cmds.Post, "post") + if closeErr := close(err); closeErr != nil { e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr) } @@ -359,6 +362,21 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi } } +func (e *Executor) runCommandHook(ctx context.Context, t *taskfile.Task, stdOut io.Writer, stdErr io.Writer, cmd string, kind string) { + if cmd == "" { + return + } + if err := execext.RunCommand(ctx, &execext.RunCommandOptions{ + Command: cmd, + Dir: t.Dir, + Env: env.Get(t), + Stdout: stdOut, + Stderr: stdErr, + }); err != nil { + e.Logger.Errf(logger.Red, "task: unable to run %s-command: %w", kind, err) + } +} + 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..d1897bcb54 --- /dev/null +++ b/taskfile/cmds.go @@ -0,0 +1,6 @@ +package taskfile + +type Cmds struct { + Pre string + Post string +} 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 From 367bbca12ce4573776988894b1ae9ec151aa067c Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 12 Sep 2023 23:28:09 +0200 Subject: [PATCH 2/3] Task separator hook / PoC --- task.go | 58 +++++++++++++++++++++++++++++++++++++++--------- taskfile/cmds.go | 5 +++-- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/task.go b/task.go index 91b7becdfd..1d51b5b39d 100644 --- a/task.go +++ b/task.go @@ -138,6 +138,8 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error { return g.Wait() } +var taskOutputSeparated = false + // RunTask runs a task by its name func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { t, err := e.CompiledTask(call) @@ -224,6 +226,10 @@ 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 !taskOutputSeparated { + e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.TaskSeparator, "separator") + taskOutputSeparated = true + } for i := range t.Cmds { if t.Cmds[i].Defer { defer e.runDeferred(t, call, i) @@ -247,6 +253,10 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { return &errors.TaskRunError{TaskName: t.Task, Err: err} } } + if !taskOutputSeparated { + e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.TaskSeparator, "separator") + taskOutputSeparated = true + } e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task) return nil }) @@ -334,9 +344,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, stdOut, stdErr, e.Taskfile.Cmds.Pre, "pre") + e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.Pre, "pre") + taskOutputSeparated = false err = execext.RunCommand(ctx, &execext.RunCommandOptions{ Command: cmd.Cmd, Dir: t.Dir, @@ -347,9 +358,9 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi Stdout: stdOut, Stderr: stdErr, }) - e.runCommandHook(ctx, t, stdOut, stdErr, e.Taskfile.Cmds.Post, "post") + e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.Post, "post") - if closeErr := close(err); closeErr != nil { + 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 { @@ -362,19 +373,46 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi } } -func (e *Executor) runCommandHook(ctx context.Context, t *taskfile.Task, stdOut io.Writer, stdErr io.Writer, cmd string, kind string) { +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), - Stdout: stdOut, - Stderr: stdErr, + 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 { diff --git a/taskfile/cmds.go b/taskfile/cmds.go index d1897bcb54..d0c604c979 100644 --- a/taskfile/cmds.go +++ b/taskfile/cmds.go @@ -1,6 +1,7 @@ package taskfile type Cmds struct { - Pre string - Post string + Pre string + Post string + TaskSeparator string `yaml:"task_separator"` } From 432da354714a8d26ea5f35020810e3eedaa70b98 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 12 Sep 2023 23:55:10 +0200 Subject: [PATCH 3/3] Hook / task separator --- setup.go | 1 + task.go | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) 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 1d51b5b39d..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 @@ -138,8 +139,6 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error { return g.Wait() } -var taskOutputSeparated = false - // RunTask runs a task by its name func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { t, err := e.CompiledTask(call) @@ -226,9 +225,8 @@ 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 !taskOutputSeparated { + if atomic.CompareAndSwapInt32(e.taskSeparated, 0, 1) { e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.TaskSeparator, "separator") - taskOutputSeparated = true } for i := range t.Cmds { if t.Cmds[i].Defer { @@ -253,9 +251,8 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { return &errors.TaskRunError{TaskName: t.Task, Err: err} } } - if !taskOutputSeparated { + if atomic.CompareAndSwapInt32(e.taskSeparated, 0, 1) { e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.TaskSeparator, "separator") - taskOutputSeparated = true } e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task) return nil @@ -347,7 +344,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi stdOut, stdErr, closer := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater) e.runCommandHook(ctx, t, call, e.Taskfile.Cmds.Pre, "pre") - taskOutputSeparated = false + atomic.StoreInt32(e.taskSeparated, 0) err = execext.RunCommand(ctx, &execext.RunCommandOptions{ Command: cmd.Cmd, Dir: t.Dir,