Skip to content

Commit 8ce1ef2

Browse files
feat(defer): expose EXIT_CODE special variable to defer:
Co-authored-by: Dor Sahar <[email protected]>
1 parent 35119c1 commit 8ce1ef2

File tree

5 files changed

+76
-14
lines changed

5 files changed

+76
-14
lines changed

internal/execext/exec.go

-8
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,6 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
9090
return r.Run(ctx, p)
9191
}
9292

93-
// IsExitError returns true the given error is an exis status error
94-
func IsExitError(err error) bool {
95-
if _, ok := interp.IsExitStatus(err); ok {
96-
return true
97-
}
98-
return false
99-
}
100-
10193
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
10294
// if available.
10395
func Expand(s string) (string, error) {

task.go

+25-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"sync/atomic"
1212
"time"
1313

14+
"mvdan.cc/sh/v3/interp"
15+
1416
"github.com/go-task/task/v3/errors"
1517
"github.com/go-task/task/v3/internal/compiler"
1618
"github.com/go-task/task/v3/internal/env"
@@ -247,9 +249,11 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
247249
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v\n", t.Dir, err)
248250
}
249251

252+
var deferredExitCode uint8
253+
250254
for i := range t.Cmds {
251255
if t.Cmds[i].Defer {
252-
defer e.runDeferred(t, call, i)
256+
defer e.runDeferred(t, call, i, &deferredExitCode)
253257
continue
254258
}
255259

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

261-
if execext.IsExitError(err) && t.IgnoreError {
262-
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
263-
continue
265+
exitCode, isExitError := interp.IsExitStatus(err)
266+
if isExitError {
267+
if t.IgnoreError {
268+
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
269+
continue
270+
}
271+
deferredExitCode = exitCode
264272
}
265273

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

315-
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int) {
323+
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int, deferredExitCode *uint8) {
316324
ctx, cancel := context.WithCancel(context.Background())
317325
defer cancel()
318326

327+
cmd := t.Cmds[i]
328+
vars, _ := e.Compiler.FastGetVariables(t, call)
329+
cache := &templater.Cache{Vars: vars}
330+
extra := map[string]any{}
331+
332+
if deferredExitCode != nil && *deferredExitCode > 0 {
333+
extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode)
334+
}
335+
336+
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
337+
319338
if err := e.runCommand(ctx, t, call, i); err != nil {
320339
e.Logger.VerboseErrf(logger.Yellow, "task: ignored error in deferred cmd: %s\n", err.Error())
321340
}
@@ -372,7 +391,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *ast.Call,
372391
if closeErr := close(err); closeErr != nil {
373392
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
374393
}
375-
if execext.IsExitError(err) && cmd.IgnoreError {
394+
if _, isExitError := interp.IsExitStatus(err); isExitError && cmd.IgnoreError {
376395
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
377396
return nil
378397
}

task_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,34 @@ task-1 ran successfully
17381738
assert.Contains(t, buff.String(), expectedOutputOrder)
17391739
}
17401740

1741+
func TestExitCodeZero(t *testing.T) {
1742+
const dir = "testdata/exit_code"
1743+
var buff bytes.Buffer
1744+
e := task.Executor{
1745+
Dir: dir,
1746+
Stdout: &buff,
1747+
Stderr: &buff,
1748+
}
1749+
require.NoError(t, e.Setup())
1750+
1751+
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "exit-zero"}))
1752+
assert.Equal(t, "EXIT_CODE=", strings.TrimSpace(buff.String()))
1753+
}
1754+
1755+
func TestExitCodeOne(t *testing.T) {
1756+
const dir = "testdata/exit_code"
1757+
var buff bytes.Buffer
1758+
e := task.Executor{
1759+
Dir: dir,
1760+
Stdout: &buff,
1761+
Stderr: &buff,
1762+
}
1763+
require.NoError(t, e.Setup())
1764+
1765+
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "exit-one"}))
1766+
assert.Equal(t, "EXIT_CODE=1", strings.TrimSpace(buff.String()))
1767+
}
1768+
17411769
func TestIgnoreNilElements(t *testing.T) {
17421770
tests := []struct {
17431771
name string

testdata/exit_code/Taskfile.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: '3'
2+
3+
silent: true
4+
5+
vars:
6+
PREFIX: EXIT_CODE=
7+
8+
tasks:
9+
exit-zero:
10+
cmds:
11+
- defer: echo {{.PREFIX}}{{.EXIT_CODE}}
12+
- exit 0
13+
14+
exit-one:
15+
cmds:
16+
- defer: echo {{.PREFIX}}{{.EXIT_CODE}}
17+
- exit 1

variables.go

+6
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
161161
}
162162
continue
163163
}
164+
// Defer commands are replaced in a lazy manner because
165+
// we need to include EXIT_CODE.
166+
if cmd.Defer {
167+
new.Cmds = append(new.Cmds, cmd.DeepCopy())
168+
continue
169+
}
164170
newCmd := cmd.DeepCopy()
165171
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
166172
newCmd.Task = templater.Replace(cmd.Task, cache)

0 commit comments

Comments
 (0)