Skip to content

Commit 10c4796

Browse files
nickfigginsmvdan
andcommitted
pkg/tool/exec: add a Run.mustSucceed parameter
Currently, exec.Run results in a fatal error if the command returns a non-zero exit status, meaning that the "success" field can only ever be used by other CUE expressions when it is set to true. Add a mustSucceed boolean parameter, defaulting to true to keep the default behavior fully backwards compatible. When set to false, exec errors will allow "cue cmd" to continue with the "success" field set to false. Fixes #2632. Closes #2702 as merged as of commit a454651. Signed-off-by: Nick Figgins <[email protected]> Co-authored-by: Daniel Martí <[email protected]> Change-Id: If52e16549dddfbc74db514cd562cf9fefc26f9f0 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1172991 Reviewed-by: Roger Peppe <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent d805c22 commit 10c4796

File tree

5 files changed

+112
-28
lines changed

5 files changed

+112
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
! exec cue cmd cannotFail
2+
cmp stderr cannotFail-stderr.golden
3+
4+
exec cue cmd canFail
5+
cmp stdout canFail-stdout.golden
6+
7+
-- cue.mod/module.cue --
8+
module: "example.com"
9+
10+
-- cannotFail-stderr.golden --
11+
task failed: command "false" failed: exit status 1
12+
-- canFail-stdout.golden --
13+
errExit:
14+
success=false
15+
hasStderr=false
16+
17+
errNotFound:
18+
success=false
19+
hasStderr=true
20+
-- foo_tool.cue --
21+
package foo
22+
23+
import (
24+
"tool/cli"
25+
"tool/exec"
26+
)
27+
28+
command: cannotFail: {
29+
one: exec.Run & {
30+
cmd: ["false"]
31+
}
32+
}
33+
34+
command: canFail: {
35+
errExit: exec.Run & {
36+
cmd: ["false"]
37+
stderr: string
38+
mustSucceed: false
39+
}
40+
errNotFound: exec.Run & {
41+
cmd: ["program-does-not-exist"]
42+
stderr: string
43+
mustSucceed: false
44+
}
45+
second: cli.Print & {
46+
text: """
47+
errExit:
48+
success=\(errExit.success)
49+
hasStderr=\(errExit.stderr != "")
50+
51+
errNotFound:
52+
success=\(errNotFound.success)
53+
hasStderr=\(errNotFound.stderr != "")
54+
"""
55+
}
56+
}

pkg/tool/exec/exec.cue

+4
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ Run: {
4848
// code or false otherwise. The user can explicitly specify the value
4949
// force a fatal error if the desired success code is not reached.
5050
success: bool
51+
52+
// mustSucceed indicates whether a command must succeed, in which case success==false results in a fatal error.
53+
// This option is enabled by default, but may be disabled to control what is done when a command execution fails.
54+
mustSucceed: bool | *true
5155
}

pkg/tool/exec/exec.go

+23-5
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ func (c *execCmd) Run(ctx *task.Context) (res interface{}, err error) {
7171
cmd.Stderr = ctx.Stderr
7272
}
7373

74+
// TODO(mvdan): exec.Run declares mustSucceed as a regular field with a default of true.
75+
// We should be able to rely on that here, removing the need for Exists and repeating the default.
76+
mustSucceed := true
77+
if v := ctx.Obj.LookupPath(cue.ParsePath("mustSucceed")); v.Exists() {
78+
mustSucceed, err = v.Bool()
79+
if err != nil {
80+
return nil, errors.Wrapf(err, v.Pos(), "invalid bool value")
81+
}
82+
}
83+
7484
update := map[string]interface{}{}
7585
if captureOut {
7686
var stdout []byte
@@ -80,15 +90,23 @@ func (c *execCmd) Run(ctx *task.Context) (res interface{}, err error) {
8090
err = cmd.Run()
8191
}
8292
update["success"] = err == nil
83-
if err != nil {
84-
if exit := (*exec.ExitError)(nil); errors.As(err, &exit) && captureErr {
93+
94+
if err == nil {
95+
return update, nil
96+
}
97+
98+
if captureErr {
99+
if exit := (*exec.ExitError)(nil); errors.As(err, &exit) {
85100
update["stderr"] = string(exit.Stderr)
86101
} else {
87-
update = nil
102+
update["stderr"] = err.Error()
88103
}
89-
err = fmt.Errorf("command %q failed: %v", doc, err)
90104
}
91-
return update, err
105+
if !mustSucceed {
106+
return update, nil
107+
}
108+
109+
return nil, fmt.Errorf("command %q failed: %v", doc, err)
92110
}
93111

94112
func mkCommand(ctx *task.Context) (c *exec.Cmd, doc string, err error) {

pkg/tool/exec/pkg.go

+9-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/flow/testdata/template.txtar

+20-19
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ graph TD
4848
}
4949
-- out/run/t1/stats --
5050
Leaks: 0
51-
Freed: 42
52-
Reused: 35
51+
Freed: 45
52+
Reused: 38
5353
Allocs: 7
5454
Retain: 0
5555

56-
Unifications: 25
57-
Conjuncts: 65
58-
Disjuncts: 42
56+
Unifications: 26
57+
Conjuncts: 68
58+
Disjuncts: 45
5959
-- out/run/t2 --
6060
graph TD
6161
t0("root.get [Terminated]")
@@ -67,10 +67,11 @@ graph TD
6767
$id: "tool/exec.Run"
6868
cmd: "go run cuelang.org/go/cmd/cue import -f -p json -l #Workflow: jsonschema: - --outfile pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue"
6969
env: {}
70-
stdout: "foo"
71-
stderr: null
72-
stdin: (*null | string | bytes) & GET.response.body
73-
success: bool
70+
stdout: "foo"
71+
stderr: null
72+
stdin: (*null | string | bytes) & GET.response.body
73+
success: bool
74+
mustSucceed: true
7475

7576
//cue:path: root.get
7677
let GET = {
@@ -90,24 +91,24 @@ graph TD
9091
}
9192
-- out/run/t2/stats --
9293
Leaks: 0
93-
Freed: 42
94-
Reused: 42
94+
Freed: 45
95+
Reused: 45
9596
Allocs: 0
9697
Retain: 0
9798

98-
Unifications: 25
99-
Conjuncts: 69
100-
Disjuncts: 42
99+
Unifications: 26
100+
Conjuncts: 72
101+
Disjuncts: 45
101102
-- out/run/stats/totals --
102103
Leaks: 0
103-
Freed: 84
104-
Reused: 77
104+
Freed: 90
105+
Reused: 83
105106
Allocs: 7
106107
Retain: 0
107108

108-
Unifications: 50
109-
Conjuncts: 134
110-
Disjuncts: 84
109+
Unifications: 52
110+
Conjuncts: 140
111+
Disjuncts: 90
111112
-- out/run/t3 --
112113
graph TD
113114
t0("root.get [Terminated]")

0 commit comments

Comments
 (0)