Skip to content

Commit

Permalink
feat(defer): expose EXIT_CODE special variable to defer:
Browse files Browse the repository at this point in the history
Co-authored-by: Dor Sahar <dorsahar@icloud.com>
  • Loading branch information
andreynering and dorimon-1 committed Aug 15, 2024
1 parent 35119c1 commit 8ce1ef2
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 14 deletions.
8 changes: 0 additions & 8 deletions internal/execext/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,6 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
return r.Run(ctx, p)
}

// IsExitError returns true the given error is an exis status error
func IsExitError(err error) bool {
if _, ok := interp.IsExitStatus(err); ok {
return true
}
return false
}

// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
// if available.
func Expand(s string) (string, error) {
Expand Down
31 changes: 25 additions & 6 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"sync/atomic"
"time"

"mvdan.cc/sh/v3/interp"

"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/env"
Expand Down Expand Up @@ -247,9 +249,11 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v\n", t.Dir, err)
}

var deferredExitCode uint8

for i := range t.Cmds {
if t.Cmds[i].Defer {
defer e.runDeferred(t, call, i)
defer e.runDeferred(t, call, i, &deferredExitCode)
continue
}

Expand All @@ -258,9 +262,13 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v\n", err2)
}

if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
continue
exitCode, isExitError := interp.IsExitStatus(err)
if isExitError {
if t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
continue
}
deferredExitCode = exitCode
}

if call.Indirect {
Expand Down Expand Up @@ -312,10 +320,21 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
return g.Wait()
}

func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int) {
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int, deferredExitCode *uint8) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

cmd := t.Cmds[i]
vars, _ := e.Compiler.FastGetVariables(t, call)
cache := &templater.Cache{Vars: vars}
extra := map[string]any{}

if deferredExitCode != nil && *deferredExitCode > 0 {
extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode)
}

cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)

if err := e.runCommand(ctx, t, call, i); err != nil {
e.Logger.VerboseErrf(logger.Yellow, "task: ignored error in deferred cmd: %s\n", err.Error())
}
Expand Down Expand Up @@ -372,7 +391,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *ast.Call,
if closeErr := close(err); closeErr != nil {
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
}
if execext.IsExitError(err) && cmd.IgnoreError {
if _, isExitError := interp.IsExitStatus(err); isExitError && cmd.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
return nil
}
Expand Down
28 changes: 28 additions & 0 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1738,6 +1738,34 @@ task-1 ran successfully
assert.Contains(t, buff.String(), expectedOutputOrder)
}

func TestExitCodeZero(t *testing.T) {
const dir = "testdata/exit_code"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
require.NoError(t, e.Setup())

require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "exit-zero"}))
assert.Equal(t, "EXIT_CODE=", strings.TrimSpace(buff.String()))
}

func TestExitCodeOne(t *testing.T) {
const dir = "testdata/exit_code"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
require.NoError(t, e.Setup())

require.Error(t, e.Run(context.Background(), &ast.Call{Task: "exit-one"}))
assert.Equal(t, "EXIT_CODE=1", strings.TrimSpace(buff.String()))
}

func TestIgnoreNilElements(t *testing.T) {
tests := []struct {
name string
Expand Down
17 changes: 17 additions & 0 deletions testdata/exit_code/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: '3'

silent: true

vars:
PREFIX: EXIT_CODE=

tasks:
exit-zero:
cmds:
- defer: echo {{.PREFIX}}{{.EXIT_CODE}}
- exit 0

exit-one:
cmds:
- defer: echo {{.PREFIX}}{{.EXIT_CODE}}
- exit 1
6 changes: 6 additions & 0 deletions variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
}
continue
}
// Defer commands are replaced in a lazy manner because
// we need to include EXIT_CODE.
if cmd.Defer {
new.Cmds = append(new.Cmds, cmd.DeepCopy())
continue
}
newCmd := cmd.DeepCopy()
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
newCmd.Task = templater.Replace(cmd.Task, cache)
Expand Down

0 comments on commit 8ce1ef2

Please sign in to comment.