Skip to content

Commit 8cdc1e4

Browse files
committed
cli: add more execution debugging tools
1 parent 2330dfa commit 8cdc1e4

File tree

11 files changed

+756
-53
lines changed

11 files changed

+756
-53
lines changed

cli/cli_command/register/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ go_library(
1414
"//cli/cli_command",
1515
"//cli/download",
1616
"//cli/execute",
17+
"//cli/executions",
1718
"//cli/explain",
1819
"//cli/fix",
1920
"//cli/index",

cli/cli_command/register/register.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/buildbuddy-io/buildbuddy/cli/cli_command"
1010
"github.com/buildbuddy-io/buildbuddy/cli/download"
1111
"github.com/buildbuddy-io/buildbuddy/cli/execute"
12+
"github.com/buildbuddy-io/buildbuddy/cli/executions"
1213
"github.com/buildbuddy-io/buildbuddy/cli/explain"
1314
"github.com/buildbuddy-io/buildbuddy/cli/fix"
1415
"github.com/buildbuddy-io/buildbuddy/cli/index"
@@ -63,6 +64,11 @@ func register() {
6364
Help: "Executes arbitrary commands using remote execution.",
6465
Handler: execute.HandleExecute,
6566
},
67+
{
68+
Name: "executions",
69+
Help: "Remote execution tools (see 'bb executions --help')",
70+
Handler: executions.HandleExecutions,
71+
},
6672
{
6773
Name: "fix",
6874
Help: "Applies fixes to WORKSPACE and BUILD files.",

cli/execute/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ go_library(
88
importpath = "github.com/buildbuddy-io/buildbuddy/cli/execute",
99
deps = [
1010
"//cli/arg",
11+
"//cli/executions",
1112
"//cli/log",
1213
"//cli/login",
1314
"//proto:remote_execution_go_proto",

cli/execute/execute.go

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/buildbuddy-io/buildbuddy/cli/arg"
11+
"github.com/buildbuddy-io/buildbuddy/cli/executions"
1112
"github.com/buildbuddy-io/buildbuddy/cli/log"
1213
"github.com/buildbuddy-io/buildbuddy/cli/login"
1314
"github.com/buildbuddy-io/buildbuddy/server/real_environment"
@@ -45,6 +46,7 @@ var (
4546
inputRoot = flags.String("input_root", "", "Input root directory. By default, the action will have no inputs. Incompatible with --input_root_digest.")
4647
inputRootDigest = flags.String("input_root_digest", "", "Digest of the input root directory. This is useful to re-run an existing action. Users can also use `bb download` to fetch the input tree locally. Incompatible with --input_root.")
4748
outputPaths = flag.New(flags, "output_path", []string{}, "Path to an expected output file or directory. The path should be relative to the workspace root. This flag can be specified more than once.")
49+
output = flags.String("output", "stdio", "Output format: `stdio` to print command stdout/stderr, `id` to print only execution ID, `json` to print execution response and logs as JSON, or `markdown` (or `md`) to print a markdown summary.")
4850
// Note: bazel has remote_default_exec_properties but it has somewhat
4951
// confusing semantics, so we call this "exec_properties" to avoid
5052
// confusion.
@@ -92,6 +94,13 @@ func HandleExecute(args []string) (int, error) {
9294
log.Print(usage)
9395
return 1, nil
9496
}
97+
*output = strings.ToLower(*output)
98+
if *output == "md" {
99+
*output = "markdown"
100+
}
101+
if *output != "stdio" && *output != "id" && *output != "json" && *output != "markdown" {
102+
return -1, fmt.Errorf("invalid --output %q (allowed values: stdio, id, json, markdown, md)", *output)
103+
}
95104
if err := execute(cmdArgs); err != nil {
96105
return -1, err
97106
}
@@ -187,14 +196,16 @@ func execute(cmdArgs []string) error {
187196
}
188197
log.Debugf("Waiting for execution to complete")
189198
var rsp *rexec.Response
199+
var executionErr error
190200
for {
191201
msg, err := stream.Recv()
192202
if err != nil {
193203
return err
194204
}
195205
if msg.Err != nil {
196-
// We failed to execute.
197-
return msg.Err
206+
// Keep track of execution errors so we can still render available
207+
// response logs for structured output modes.
208+
executionErr = msg.Err
198209
}
199210
// Log execution state
200211
progress := &repb.ExecutionProgress{}
@@ -213,18 +224,51 @@ func execute(cmdArgs []string) error {
213224
break
214225
}
215226
}
227+
// Defensive guard: we should only exit the loop on a Done message, which
228+
// always sets rsp. Keep this check to avoid nil dereference if that
229+
// invariant is ever broken by future changes.
230+
if rsp == nil {
231+
return fmt.Errorf("execute stream ended before completion")
232+
}
216233
log.Debugf("Execution completed in %s", time.Since(stageStart))
217-
stageStart = time.Now()
218-
log.Debugf("Downloading result")
219-
res, err := rexec.GetResult(ctx, env, *instanceName, df, rsp.ExecuteResponse.GetResult())
220-
if err != nil {
221-
return status.WrapError(err, "execution failed")
234+
if executionErr != nil && *output == "stdio" {
235+
return executionErr
222236
}
223-
log.Debugf("Downloaded results in %s", time.Since(stageStart))
224-
log.Debugf("End-to-end execution time: %s", time.Since(start))
237+
var logs *rexec.ExecutionLogs
238+
if *output == "json" || *output == "markdown" {
239+
logs, err = rexec.GetExecutionLogs(ctx, env.GetByteStreamClient(), *instanceName, df, rsp.ExecuteResponse)
240+
if err != nil {
241+
log.Warnf("Could not fetch all execution logs: %s", err)
242+
}
243+
}
244+
switch *output {
245+
case "id":
246+
if _, err := fmt.Fprintln(os.Stdout, rsp.GetName()); err != nil {
247+
return err
248+
}
249+
case "json":
250+
if err := executions.WriteJSONOutput(os.Stdout, rsp.GetName(), rsp.ExecuteResponse, logs); err != nil {
251+
return fmt.Errorf("write json output: %w", err)
252+
}
253+
case "markdown":
254+
if _, err := fmt.Fprint(os.Stdout, executions.RenderMarkdownWithDetails(rsp.GetName(), rsp.ExecuteResponse, logs)); err != nil {
255+
return err
256+
}
257+
case "stdio":
258+
stageStart = time.Now()
259+
log.Debugf("Downloading result")
260+
res, err := rexec.GetResult(ctx, env, *instanceName, df, rsp.ExecuteResponse.GetResult())
261+
if err != nil {
262+
return status.WrapError(err, "execution failed")
263+
}
264+
log.Debugf("Downloaded results in %s", time.Since(stageStart))
225265

226-
os.Stdout.Write(res.Stdout)
227-
os.Stderr.Write(res.Stderr)
266+
os.Stdout.Write(res.Stdout)
267+
os.Stderr.Write(res.Stderr)
268+
default:
269+
return fmt.Errorf("invalid --output %q", *output)
270+
}
271+
log.Debugf("End-to-end execution time: %s", time.Since(start))
228272

229273
if *responseJSONFile != "" {
230274
b, err := protojson.Marshal(rsp.ExecuteResponse)
@@ -235,6 +279,9 @@ func execute(cmdArgs []string) error {
235279
return fmt.Errorf("write response JSON file: %w", err)
236280
}
237281
}
282+
if executionErr != nil {
283+
return executionErr
284+
}
238285

239286
return nil
240287
}

cli/executions/BUILD

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library")
2+
3+
package(default_visibility = ["//cli:__subpackages__"])
4+
5+
go_library(
6+
name = "executions",
7+
srcs = ["executions.go"],
8+
importpath = "github.com/buildbuddy-io/buildbuddy/cli/executions",
9+
deps = [
10+
"//cli/arg",
11+
"//cli/log",
12+
"//cli/login",
13+
"//cli/terminal",
14+
"//proto:execution_stats_go_proto",
15+
"//proto:remote_execution_go_proto",
16+
"//server/util/grpc_client",
17+
"//server/util/rexec",
18+
"@org_golang_google_grpc//codes",
19+
"@org_golang_google_grpc//metadata",
20+
"@org_golang_google_protobuf//encoding/protojson",
21+
"@org_golang_google_protobuf//types/known/timestamppb",
22+
],
23+
)

0 commit comments

Comments
 (0)