diff --git a/cmd/task/task.go b/cmd/task/task.go index 20e4d612..3c0fc609 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -68,6 +68,7 @@ func main() { silent bool dry bool summary bool + exitCode bool parallel bool concurrency int dir string @@ -89,6 +90,7 @@ func main() { pflag.BoolVarP(¶llel, "parallel", "p", false, "executes tasks provided on command line in parallel") pflag.BoolVar(&dry, "dry", 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") pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution") pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`) pflag.StringVarP(&output.Name, "output", "o", "", "sets output style: [interleaved|group|prefixed]") @@ -216,7 +218,13 @@ func main() { if err := e.Run(ctx, calls...); err != nil { e.Logger.Errf(logger.Red, "%v", err) - os.Exit(1) + code := 1 + if exitCode { + if tre, ok := err.(*task.TaskRunError); ok { + code = tre.ExitCode() + } + } + os.Exit(code) } } diff --git a/errors.go b/errors.go index a12df253..e2aa16b0 100644 --- a/errors.go +++ b/errors.go @@ -3,6 +3,7 @@ package task import ( "errors" "fmt" + "mvdan.cc/sh/v3/interp" ) var ( @@ -18,15 +19,23 @@ func (err *taskNotFoundError) Error() string { return fmt.Sprintf(`task: Task "%s" not found`, err.taskName) } -type taskRunError struct { +type TaskRunError struct { taskName string err error } -func (err *taskRunError) Error() string { +func (err *TaskRunError) Error() string { return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err) } +func (err *TaskRunError) ExitCode() int { + if c, ok := interp.IsExitStatus(err.err); ok { + return int(c) + } + + return 1 +} + // MaximumTaskCallExceededError is returned when a task is called too // many times. In this case you probably have a cyclic dependendy or // infinite loop diff --git a/task.go b/task.go index ec224260..6714d8e3 100644 --- a/task.go +++ b/task.go @@ -363,7 +363,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { continue } - return &taskRunError{t.Task, err} + return &TaskRunError{t.Task, err} } } e.Logger.VerboseErrf(logger.Magenta, `task: "%s" finished`, call.Task) diff --git a/task_test.go b/task_test.go index a6a47b76..1094ea65 100644 --- a/task_test.go +++ b/task_test.go @@ -1275,3 +1275,22 @@ VAR_2 is included-default-var2 t.Log(buff.String()) assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder) } + +func TestErrorCode(t *testing.T) { + const dir = "testdata/error_code" + + var buff bytes.Buffer + e := &task.Executor{ + Dir: dir, + Stdout: &buff, + Stderr: &buff, + Silent: true, + } + assert.NoError(t, e.Setup()) + + err := e.Run(context.Background(), taskfile.Call{Task: "test-exit-code"}) + assert.Error(t, err) + casted, ok := err.(*task.TaskRunError) + assert.True(t, ok, "cannot cast returned error to *task.TaskRunError") + assert.Equal(t, 42, casted.ExitCode(), "unexpected exit code from task") +} diff --git a/testdata/error_code/Taskfile.yml b/testdata/error_code/Taskfile.yml new file mode 100644 index 00000000..b2c83ff8 --- /dev/null +++ b/testdata/error_code/Taskfile.yml @@ -0,0 +1,6 @@ +version: '3' + +tasks: + test-exit-code: + cmds: + - exit 42 diff --git a/watch.go b/watch.go index 78ca06d9..b93e2d30 100644 --- a/watch.go +++ b/watch.go @@ -88,7 +88,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error { } func isContextError(err error) bool { - if taskRunErr, ok := err.(*taskRunError); ok { + if taskRunErr, ok := err.(*TaskRunError); ok { err = taskRunErr.err }