From a790fb7afe3d29ee5da29d312a88a22cee244e74 Mon Sep 17 00:00:00 2001 From: Bruno Delor Date: Thu, 2 Jun 2022 14:22:00 +0200 Subject: [PATCH 1/3] Adds --carry flag to enable carrying error codes from task cmds --- cmd/task/task.go | 10 +++++++++- errors.go | 13 +++++++++++-- task.go | 2 +- watch.go | 2 +- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/cmd/task/task.go b/cmd/task/task.go index 20e4d612..6fa404e3 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -68,6 +68,7 @@ func main() { silent bool dry bool summary bool + carryErr 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.BoolVar(&carryErr, "carry", false, "carry error code if any") 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 carryErr { + 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/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 } From 58c7cc5d05fa20318a34f1c2bebbb5ca3936fc07 Mon Sep 17 00:00:00 2001 From: Bruno Delor Date: Thu, 2 Jun 2022 16:39:28 +0200 Subject: [PATCH 2/3] Adds test TestErrorCode --- task_test.go | 19 +++++++++++++++++++ testdata/error_code/Taskfile.yml | 6 ++++++ 2 files changed, 25 insertions(+) create mode 100644 testdata/error_code/Taskfile.yml 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 From 752d9d5316fefbb0a4711b5b8fc1c81d50b2353f Mon Sep 17 00:00:00 2001 From: Bruno Delor Date: Mon, 6 Jun 2022 09:46:27 +0200 Subject: [PATCH 3/3] Renames option to align with existing tools Shorthand: -x Longhand: --exit-code --- cmd/task/task.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/task/task.go b/cmd/task/task.go index 6fa404e3..3c0fc609 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -68,7 +68,7 @@ func main() { silent bool dry bool summary bool - carryErr bool + exitCode bool parallel bool concurrency int dir string @@ -90,7 +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.BoolVar(&carryErr, "carry", false, "carry error code if any") + 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]") @@ -219,7 +219,7 @@ func main() { if err := e.Run(ctx, calls...); err != nil { e.Logger.Errf(logger.Red, "%v", err) code := 1 - if carryErr { + if exitCode { if tre, ok := err.(*task.TaskRunError); ok { code = tre.ExitCode() }