From 6400279710f3faa785adcefd8720240924178be6 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Thu, 16 Jan 2025 14:06:52 -0800 Subject: [PATCH] Support --output=commands flag for aquery This output format prints a list of build commands with one command per line, similar to `ninja -t commands`. This is frequently useful when debugging build issues. Fixes #22389. --- site/en/query/aquery.md | 5 +- .../google/devtools/build/lib/query2/BUILD | 1 + .../aquery/ActionGraphQueryEnvironment.java | 16 ++++++- ...ctionGraphTextOutputFormatterCallback.java | 48 +++++++++++++++++-- src/test/shell/integration/aquery_test.sh | 19 ++++++++ 5 files changed, 84 insertions(+), 5 deletions(-) diff --git a/site/en/query/aquery.md b/site/en/query/aquery.md index 14e331b5d53982..8e8da1b9058ac0 100644 --- a/site/en/query/aquery.md +++ b/site/en/query/aquery.md @@ -111,12 +111,15 @@ available during a build. ### Aquery options {:#aquery-options} -#### `--output=(text|summary|proto|jsonproto|textproto), default=text` {:#output} +#### `--output=(text|summary|commands|proto|jsonproto|textproto), default=text` {:#output} The default output format (`text`) is human-readable, use `proto`, `textproto`, or `jsonproto` for machine-readable format. The proto message is `analysis.ActionGraphContainer`. +The `commands` output format prints a list of build commands with +one command per line. + #### `--include_commandline, default=true` {:#include-commandline} Includes the content of the action command lines in the output (potentially large). diff --git a/src/main/java/com/google/devtools/build/lib/query2/BUILD b/src/main/java/com/google/devtools/build/lib/query2/BUILD index 9147c646c5a8f6..c501a4db0361d6 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/BUILD +++ b/src/main/java/com/google/devtools/build/lib/query2/BUILD @@ -116,6 +116,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/util", "//src/main/java/com/google/devtools/build/lib/util:command", "//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code", + "//src/main/java/com/google/devtools/build/lib/util:script_util", "//src/main/java/com/google/devtools/build/lib/util:shell_escaper", "//src/main/java/com/google/devtools/build/lib/util:string_encoding", "//src/main/java/com/google/devtools/build/lib/vfs", diff --git a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java index 66ba74f647d585..5f3c9742b2c4d5 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java @@ -183,7 +183,21 @@ public ConfiguredTargetValueAccessor getAccessor() { AqueryOutputHandler.OutputType.JSON, actionFilters), new ActionGraphTextOutputFormatterCallback( - eventHandler, aqueryOptions, out, accessor, actionFilters, getLabelPrinter()), + eventHandler, + aqueryOptions, + out, + accessor, + ActionGraphTextOutputFormatterCallback.OutputType.TEXT, + actionFilters, + getLabelPrinter()), + new ActionGraphTextOutputFormatterCallback( + eventHandler, + aqueryOptions, + out, + accessor, + ActionGraphTextOutputFormatterCallback.OutputType.COMMANDS, + actionFilters, + getLabelPrinter()), new ActionGraphSummaryOutputFormatterCallback( eventHandler, aqueryOptions, out, accessor, actionFilters)); } diff --git a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphTextOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphTextOutputFormatterCallback.java index 0a97cb982f5d23..1b7c211862739e 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphTextOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphTextOutputFormatterCallback.java @@ -46,6 +46,7 @@ import com.google.devtools.build.lib.skyframe.RuleConfiguredTargetValue; import com.google.devtools.build.lib.util.CommandDescriptionForm; import com.google.devtools.build.lib.util.CommandFailureUtils; +import com.google.devtools.build.lib.util.ScriptUtil; import com.google.devtools.build.lib.util.ShellEscaper; import java.io.IOException; import java.io.OutputStream; @@ -58,8 +59,19 @@ /** Output callback for aquery, prints human readable output. */ class ActionGraphTextOutputFormatterCallback extends AqueryThreadsafeCallback { + public enum OutputType { + TEXT("text"), + COMMANDS("commands"); + + final String formatName; + + OutputType(String formatName) { + this.formatName = formatName; + } + } private final ActionKeyContext actionKeyContext = new ActionKeyContext(); + private final OutputType outputType; private final AqueryActionFilter actionFilters; private final LabelPrinter labelPrinter; private Map paramFileNameToContentMap; @@ -69,16 +81,18 @@ class ActionGraphTextOutputFormatterCallback extends AqueryThreadsafeCallback { AqueryOptions options, OutputStream out, TargetAccessor accessor, + OutputType outputType, AqueryActionFilter actionFilters, LabelPrinter labelPrinter) { super(eventHandler, options, out, accessor); + this.outputType = outputType; this.actionFilters = actionFilters; this.labelPrinter = labelPrinter; } @Override public String getName() { - return "text"; + return outputType.formatName; } @Override @@ -128,8 +142,17 @@ private void writeAction(ActionAnalysisMetadata action, PrintStream printStream) return; } - ActionOwner actionOwner = action.getOwner(); StringBuilder stringBuilder = new StringBuilder(); + switch (outputType) { + case TEXT -> writeText(action, stringBuilder); + case COMMANDS -> writeCommand(action, stringBuilder); + } + printStream.write(stringBuilder.toString().getBytes(UTF_8)); + } + + private void writeText(ActionAnalysisMetadata action, StringBuilder stringBuilder) + throws IOException, CommandLineExpansionException, InterruptedException, EvalException { + ActionOwner actionOwner = action.getOwner(); stringBuilder .append(action.prettyPrint()) .append('\n') @@ -338,8 +361,27 @@ private void writeAction(ActionAnalysisMetadata action, PrintStream printStream) } stringBuilder.append('\n'); + } - printStream.write(stringBuilder.toString().getBytes(UTF_8)); + private void writeCommand(ActionAnalysisMetadata action, StringBuilder stringBuilder) + throws IOException, CommandLineExpansionException, InterruptedException, EvalException { + if (!(action instanceof CommandAction)) { + return; + } + + boolean first = true; + for (String arg : + ((CommandAction) action) + .getArguments().stream() + .map(a -> internalToEscapedUnicode(a)) + .collect(toImmutableList())) { + if (!first) { + stringBuilder.append(' '); + } + ScriptUtil.emitCommandElement(stringBuilder, arg, first); + first = false; + } + stringBuilder.append('\n'); } /** Lazy initialization of paramFileNameToContentMap. */ diff --git a/src/test/shell/integration/aquery_test.sh b/src/test/shell/integration/aquery_test.sh index e66e3ac5100d14..3f0f0ccc6587b2 100755 --- a/src/test/shell/integration/aquery_test.sh +++ b/src/test/shell/integration/aquery_test.sh @@ -148,6 +148,25 @@ EOF assert_not_contains "echo unused" output } +function test_basic_aquery_commands() { + local pkg="${FUNCNAME[0]}" + mkdir -p "$pkg" || fail "mkdir -p $pkg" + cat > "$pkg/BUILD" <<'EOF' +genrule( + name = "bar", + srcs = ["dummy.txt"], + outs = ["bar_out.txt"], + cmd = "echo unused > $(OUTS)", +) +EOF + echo "hello aquery" > "$pkg/in.txt" + + bazel aquery --output=commands "//$pkg:bar" > output 2> "$TEST_log" \ + || fail "Expected success" + cat output >> "$TEST_log" + assert_contains "echo unused" output +} + function test_basic_aquery_proto() { local pkg="${FUNCNAME[0]}" mkdir -p "$pkg" || fail "mkdir -p $pkg"