diff --git a/task.go b/task.go index 7021b77a..0e9ccd9f 100644 --- a/task.go +++ b/task.go @@ -8,6 +8,7 @@ import ( "os" "sync" "sync/atomic" + "time" "github.com/go-task/task/v3/internal/compiler" compilerv2 "github.com/go-task/task/v3/internal/compiler/v2" @@ -339,6 +340,11 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { } for i := range t.Cmds { + if t.Cmds[i].Defer { + defer e.runDeferred(t, call, i) + continue + } + if err := e.runCommand(ctx, t, call, i); err != nil { if err2 := e.statusOnError(t); err2 != nil { e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v", err2) @@ -395,6 +401,14 @@ func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error { return g.Wait() } +func (e *Executor) runDeferred(t *taskfile.Task, call taskfile.Call, i int) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + if err := e.runCommand(ctx, t, call, i); err != nil { + e.Logger.VerboseErrf(logger.Yellow, `task: ignored error in deferred cmd: %s`, err.Error()) + } +} + func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error { cmd := t.Cmds[i] diff --git a/task_test.go b/task_test.go index f8e396f7..4d647a0b 100644 --- a/task_test.go +++ b/task_test.go @@ -1044,6 +1044,32 @@ func TestRunOnlyRunsJobsHashOnce(t *testing.T) { tt.Run(t) } +func TestDeferredCmds(t *testing.T) { + const dir = "testdata/deferred" + var buff bytes.Buffer + e := task.Executor{ + Dir: dir, + Stdout: &buff, + Stderr: &buff, + } + assert.NoError(t, e.Setup()) + + expectedOutputOrder := strings.TrimSpace(` +task: [task-2] echo 'cmd ran' +cmd ran +task: [task-2] exit 1 +task: [task-2] echo 'failing' && exit 2 +failing +task: [task-2] echo 'echo ran' +echo ran +task: [task-1] echo 'task-1 ran' +task-1 ran +`) + assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "task-2"})) + fmt.Println(buff.String()) + assert.Contains(t, buff.String(), expectedOutputOrder) +} + func TestIgnoreNilElements(t *testing.T) { tests := []struct { name string diff --git a/taskfile/cmd.go b/taskfile/cmd.go index 7166371b..8d9d5d20 100644 --- a/taskfile/cmd.go +++ b/taskfile/cmd.go @@ -7,6 +7,7 @@ type Cmd struct { Task string Vars *Vars IgnoreError bool + Defer bool } // Dep is a task dependency @@ -33,6 +34,18 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error { c.IgnoreError = cmdStruct.IgnoreError return nil } + var deferredCmd struct { + Defer string + } + if err := unmarshal(&deferredCmd); err == nil && deferredCmd.Defer != "" { + c.Defer = true + if strings.HasPrefix(deferredCmd.Defer, "^") { + c.Task = strings.TrimPrefix(deferredCmd.Defer, "^") + } else { + c.Cmd = deferredCmd.Defer + } + return nil + } var taskCall struct { Task string Vars *Vars diff --git a/taskfile/taskfile_test.go b/taskfile/taskfile_test.go index e6b4470f..c7adf5c6 100644 --- a/taskfile/taskfile_test.go +++ b/taskfile/taskfile_test.go @@ -19,6 +19,8 @@ vars: PARAM1: VALUE1 PARAM2: VALUE2 ` + yamlDeferredTask = `defer: ^some_task` + yamlDeferredCmd = `defer: echo 'test'` ) tests := []struct { content string @@ -41,6 +43,16 @@ vars: }, }}, }, + { + yamlDeferredCmd, + &taskfile.Cmd{}, + &taskfile.Cmd{Cmd: "echo 'test'", Defer: true}, + }, + { + yamlDeferredTask, + &taskfile.Cmd{}, + &taskfile.Cmd{Task: "some_task", Defer: true}, + }, { yamlDep, &taskfile.Dep{}, diff --git a/testdata/deferred/Taskfile.yml b/testdata/deferred/Taskfile.yml new file mode 100644 index 00000000..1bd4295e --- /dev/null +++ b/testdata/deferred/Taskfile.yml @@ -0,0 +1,12 @@ +version: "3" + +tasks: + task-1: + - echo 'task-1 ran' + + task-2: + - defer: "^task-1" + - defer: echo 'echo ran' + - defer: echo 'failing' && exit 2 + - echo 'cmd ran' + - exit 1 diff --git a/variables.go b/variables.go index 9355e24e..6e69e4b6 100644 --- a/variables.go +++ b/variables.go @@ -102,6 +102,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf Cmd: r.Replace(cmd.Cmd), Vars: r.ReplaceVars(cmd.Vars), IgnoreError: cmd.IgnoreError, + Defer: cmd.Defer, }) } }