Skip to content

Commit 04c373b

Browse files
authored
Add --output=files mode to cquery (bazelbuild#15979)
With the new output mode `--output=files`, cquery lists all files advertised by the matched targets in the currently requested output groups. This new mode has the following advantages over `--output=starlark` combined with an appropriate handcrafted `--starlark:expr`: * provides a canonical answer to the very common "Where are my build outputs?" question * is more friendly to new users as it doesn't require knowing about providers and non-BUILD dialect Starlark * takes the value of `--output_groups` into account * stays as close to the logic for build summaries printed by `bazel build` as possible Fixes bazelbuild#8739 RELNOTES: `cquery`'s new output mode [`--output=files`](https://bazel.build/docs/cquery#files-output) lists the output files of the targets matching the query. It takes the current value of `--output_groups` into account. Closes bazelbuild#15552. PiperOrigin-RevId: 462630629 Change-Id: Ic648f22aa160ee57b476180561b444f08799ebb6
1 parent 14c944a commit 04c373b

17 files changed

+344
-32
lines changed

site/docs/cquery.html

+20
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,26 @@ <h3>Graph output</h3>
452452

453453
</p>
454454

455+
<h3>Files output</h3>
456+
457+
<pre>
458+
--output=files
459+
</pre>
460+
461+
<p>
462+
This option prints a list of the output files produced by each target matched
463+
by the query similar to the list printed at the end of a <code>bazel build</code>
464+
invocation. The output contains only the files advertised in the requested
465+
output groups as determined by the
466+
<a href="https://docs.bazel.build/version/main/reference/command-line-reference#flag--output_groups"><code>--output_groups</code></a>
467+
flag and never contains source files.
468+
469+
Note: The output of <code>bazel cquery --output=files //pkg:foo</code> contains the output
470+
files of <code>//pkg:foo</code> in <i>all</i> configurations that occur in the build (also
471+
see the <a href="#target-pattern-evaluation">section on target pattern evaluation</a>. If that
472+
is not desired, wrap you query in <a href="#config"><code>config(..., target)</code></a>.
473+
</p>
474+
455475
<h3>Defining the output format using Starlark</h3>
456476

457477
<pre>

src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java

+32
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.google.common.collect.ImmutableSet;
2222
import com.google.common.collect.Iterables;
2323
import com.google.devtools.build.lib.actions.Artifact;
24+
import com.google.devtools.build.lib.analysis.configuredtargets.InputFileConfiguredTarget;
25+
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
2426
import com.google.devtools.build.lib.analysis.test.TestProvider;
2527
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
2628
import com.google.devtools.build.lib.collect.nestedset.NestedSet.Node;
@@ -236,6 +238,36 @@ static ArtifactsToBuild getAllArtifactsToBuild(
236238
allOutputGroups.build(), /*allOutputGroupsImportant=*/ allOutputGroupsImportant);
237239
}
238240

241+
/**
242+
* Returns false if the build outputs provided by the target should never be shown to users.
243+
*
244+
* <p>Always returns false for hidden rules and source file targets.
245+
*/
246+
public static boolean shouldConsiderForDisplay(ConfiguredTarget configuredTarget) {
247+
// TODO(bazel-team): this is quite ugly. Add a marker provider for this check.
248+
if (configuredTarget instanceof InputFileConfiguredTarget) {
249+
// Suppress display of source files (because we do no work to build them).
250+
return false;
251+
}
252+
if (configuredTarget instanceof RuleConfiguredTarget) {
253+
RuleConfiguredTarget ruleCt = (RuleConfiguredTarget) configuredTarget;
254+
if (ruleCt.getRuleClassString().contains("$")) {
255+
// Suppress display of hidden rules
256+
return false;
257+
}
258+
}
259+
return true;
260+
}
261+
262+
/**
263+
* Returns true if the given artifact should be shown to users as a build output.
264+
*
265+
* <p>Always returns false for middleman and source artifacts.
266+
*/
267+
public static boolean shouldDisplay(Artifact artifact) {
268+
return !artifact.isSourceArtifact() && !artifact.isMiddlemanArtifact();
269+
}
270+
239271
/**
240272
* Recursive procedure filtering a target/aspect's declared {@code
241273
* NestedSet<ArtifactsInOutputGroup>} and {@code NestedSet<Artifact>} to only include {@link

src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java

+3-18
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
2626
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper;
2727
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
28-
import com.google.devtools.build.lib.analysis.configuredtargets.InputFileConfiguredTarget;
2928
import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget;
30-
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
3129
import com.google.devtools.build.lib.cmdline.Label;
3230
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
3331
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -140,7 +138,7 @@ void showBuildResult(
140138
TopLevelArtifactHelper.getAllArtifactsToBuild(target, context)
141139
.getImportantArtifacts()
142140
.toList()) {
143-
if (shouldPrint(artifact)) {
141+
if (TopLevelArtifactHelper.shouldDisplay(artifact)) {
144142
if (headerFlag) {
145143
outErr.printErr("Target " + label + " up-to-date:\n");
146144
headerFlag = false;
@@ -195,7 +193,7 @@ void showBuildResult(
195193
outErr.printErr("Aspect " + aspectName + " of " + label + " up-to-date:\n");
196194
headerFlag = false;
197195
}
198-
if (shouldPrint(importantArtifact)) {
196+
if (TopLevelArtifactHelper.shouldDisplay(importantArtifact)) {
199197
outErr.printErrLn(formatArtifactForShowResults(prettyPrinter, importantArtifact));
200198
}
201199
}
@@ -216,10 +214,6 @@ void showBuildResult(
216214
}
217215
}
218216

219-
private boolean shouldPrint(Artifact artifact) {
220-
return !artifact.isSourceArtifact() && !artifact.isMiddlemanArtifact();
221-
}
222-
223217
private String formatArtifactForShowResults(PathPrettyPrinter prettyPrinter, Artifact artifact) {
224218
return " " + prettyPrinter.getPrettyPath(artifact.getPath().asFragment());
225219
}
@@ -268,18 +262,9 @@ private Collection<ConfiguredTarget> filterTargetsToPrint(
268262
Collection<ConfiguredTarget> configuredTargets) {
269263
ImmutableList.Builder<ConfiguredTarget> result = ImmutableList.builder();
270264
for (ConfiguredTarget configuredTarget : configuredTargets) {
271-
// TODO(bazel-team): this is quite ugly. Add a marker provider for this check.
272-
if (configuredTarget instanceof InputFileConfiguredTarget) {
273-
// Suppress display of source files (because we do no work to build them).
265+
if (!TopLevelArtifactHelper.shouldConsiderForDisplay(configuredTarget)) {
274266
continue;
275267
}
276-
if (configuredTarget instanceof RuleConfiguredTarget) {
277-
RuleConfiguredTarget ruleCt = (RuleConfiguredTarget) configuredTarget;
278-
if (ruleCt.getRuleClassString().contains("$")) {
279-
// Suppress display of hidden rules
280-
continue;
281-
}
282-
}
283268
if (configuredTarget instanceof OutputFileConfiguredTarget) {
284269
// Suppress display of generated files (because they appear underneath
285270
// their generating rule), EXCEPT those ones which are not part of the

src/main/java/com/google/devtools/build/lib/buildtool/CqueryBuildTool.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ protected ConfiguredTargetQueryEnvironment getQueryEnvironment(
5757
env.getRelativeWorkingDirectory(),
5858
env.getPackageManager().getPackagePath(),
5959
() -> walkableGraph,
60-
cqueryOptions);
60+
cqueryOptions,
61+
request.getTopLevelArtifactContext());
6162
}
6263
}

src/main/java/com/google/devtools/build/lib/query2/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ java_library(
4949
"//src/main/java/com/google/devtools/build/lib/analysis:target_and_configuration",
5050
"//src/main/java/com/google/devtools/build/lib/analysis:toolchain_collection",
5151
"//src/main/java/com/google/devtools/build/lib/analysis:toolchain_context",
52+
"//src/main/java/com/google/devtools/build/lib/analysis:top_level_artifact_context",
5253
"//src/main/java/com/google/devtools/build/lib/buildeventstream",
5354
"//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
5455
"//src/main/java/com/google/devtools/build/lib/causes",

src/main/java/com/google/devtools/build/lib/query2/cquery/BuildOutputFormatterCallback.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class BuildOutputFormatterCallback extends CqueryThreadsafeCallback {
3737
OutputStream out,
3838
SkyframeExecutor skyframeExecutor,
3939
TargetAccessor<KeyedConfiguredTarget> accessor) {
40-
super(eventHandler, options, out, skyframeExecutor, accessor);
40+
super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false);
4141
}
4242

4343
@Override

src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java

+13-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.common.util.concurrent.MoreExecutors;
2626
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
2727
import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
28+
import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
2829
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
2930
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
3031
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
@@ -81,6 +82,8 @@ public class ConfiguredTargetQueryEnvironment
8182

8283
private CqueryOptions cqueryOptions;
8384

85+
private final TopLevelArtifactContext topLevelArtifactContext;
86+
8487
private final KeyExtractor<KeyedConfiguredTarget, ConfiguredTargetKey>
8588
configuredTargetKeyExtractor;
8689

@@ -119,7 +122,8 @@ public ConfiguredTargetQueryEnvironment(
119122
PathFragment parserPrefix,
120123
PathPackageLocator pkgPath,
121124
Supplier<WalkableGraph> walkableGraphSupplier,
122-
Set<Setting> settings)
125+
Set<Setting> settings,
126+
TopLevelArtifactContext topLevelArtifactContext)
123127
throws InterruptedException {
124128
super(
125129
keepGoing,
@@ -135,6 +139,7 @@ public ConfiguredTargetQueryEnvironment(
135139
this.configuredTargetKeyExtractor = KeyedConfiguredTarget::getConfiguredTargetKey;
136140
this.transitiveConfigurations =
137141
getTransitiveConfigurations(transitiveConfigurationKeys, walkableGraphSupplier.get());
142+
this.topLevelArtifactContext = topLevelArtifactContext;
138143
}
139144

140145
public ConfiguredTargetQueryEnvironment(
@@ -147,7 +152,8 @@ public ConfiguredTargetQueryEnvironment(
147152
PathFragment parserPrefix,
148153
PathPackageLocator pkgPath,
149154
Supplier<WalkableGraph> walkableGraphSupplier,
150-
CqueryOptions cqueryOptions)
155+
CqueryOptions cqueryOptions,
156+
TopLevelArtifactContext topLevelArtifactContext)
151157
throws InterruptedException {
152158
this(
153159
keepGoing,
@@ -159,7 +165,8 @@ public ConfiguredTargetQueryEnvironment(
159165
parserPrefix,
160166
pkgPath,
161167
walkableGraphSupplier,
162-
cqueryOptions.toSettings());
168+
cqueryOptions.toSettings(),
169+
topLevelArtifactContext);
163170
this.cqueryOptions = cqueryOptions;
164171
}
165172

@@ -270,7 +277,9 @@ private static ImmutableMap<String, BuildConfiguration> getTransitiveConfigurati
270277
accessor,
271278
kct -> getFwdDeps(ImmutableList.of(kct))),
272279
new StarlarkOutputFormatterCallback(
273-
eventHandler, cqueryOptions, out, skyframeExecutor, accessor));
280+
eventHandler, cqueryOptions, out, skyframeExecutor, accessor),
281+
new FilesOutputFormatterCallback(
282+
eventHandler, cqueryOptions, out, skyframeExecutor, accessor, topLevelArtifactContext));
274283
}
275284

276285
@Override

src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryThreadsafeCallback.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616

1717
import com.google.common.annotations.VisibleForTesting;
18+
import com.google.common.collect.ImmutableSet;
1819
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
1920
import com.google.devtools.build.lib.events.ExtendedEventHandler;
2021
import com.google.devtools.build.lib.query2.NamedThreadSafeOutputFormatterCallback;
@@ -55,14 +56,16 @@ public abstract class CqueryThreadsafeCallback
5556
protected final ConfiguredTargetAccessor accessor;
5657

5758
private final List<String> result = new ArrayList<>();
59+
private final boolean uniquifyResults;
5860

5961
@SuppressWarnings("DefaultCharset")
6062
CqueryThreadsafeCallback(
6163
ExtendedEventHandler eventHandler,
6264
CqueryOptions options,
6365
OutputStream out,
6466
SkyframeExecutor skyframeExecutor,
65-
TargetAccessor<KeyedConfiguredTarget> accessor) {
67+
TargetAccessor<KeyedConfiguredTarget> accessor,
68+
boolean uniquifyResults) {
6669
this.eventHandler = eventHandler;
6770
this.options = options;
6871
if (out != null) {
@@ -72,6 +75,7 @@ public abstract class CqueryThreadsafeCallback
7275
}
7376
this.skyframeExecutor = skyframeExecutor;
7477
this.accessor = (ConfiguredTargetAccessor) accessor;
78+
this.uniquifyResults = uniquifyResults;
7579
}
7680

7781
public void addResult(String string) {
@@ -86,7 +90,8 @@ public List<String> getResult() {
8690
@Override
8791
public void close(boolean failFast) throws InterruptedException, IOException {
8892
if (!failFast && printStream != null) {
89-
for (String s : result) {
93+
List<String> resultsToPrint = uniquifyResults ? ImmutableSet.copyOf(result).asList() : result;
94+
for (String s : resultsToPrint) {
9095
// TODO(ulfjack): We should use queryOptions.getLineTerminator() instead.
9196
printStream.append(s).append("\n");
9297
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package com.google.devtools.build.lib.query2.cquery;
15+
16+
import com.google.devtools.build.lib.actions.Artifact;
17+
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
18+
import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
19+
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper;
20+
import com.google.devtools.build.lib.events.ExtendedEventHandler;
21+
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.TargetAccessor;
22+
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
23+
import java.io.IOException;
24+
import java.io.OutputStream;
25+
26+
/**
27+
* Cquery output formatter that prints the set of output files advertised by the matched targets.
28+
*/
29+
public class FilesOutputFormatterCallback extends CqueryThreadsafeCallback {
30+
31+
private final TopLevelArtifactContext topLevelArtifactContext;
32+
33+
FilesOutputFormatterCallback(
34+
ExtendedEventHandler eventHandler,
35+
CqueryOptions options,
36+
OutputStream out,
37+
SkyframeExecutor skyframeExecutor,
38+
TargetAccessor<KeyedConfiguredTarget> accessor,
39+
TopLevelArtifactContext topLevelArtifactContext) {
40+
// Different targets may provide the same artifact, so we deduplicate the collection of all
41+
// results at the end.
42+
super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ true);
43+
this.topLevelArtifactContext = topLevelArtifactContext;
44+
}
45+
46+
@Override
47+
public String getName() {
48+
return "files";
49+
}
50+
51+
@Override
52+
public void processOutput(Iterable<KeyedConfiguredTarget> partialResult)
53+
throws IOException, InterruptedException {
54+
for (KeyedConfiguredTarget keyedTarget : partialResult) {
55+
ConfiguredTarget target = keyedTarget.getConfiguredTarget();
56+
if (!TopLevelArtifactHelper.shouldConsiderForDisplay(target)) {
57+
continue;
58+
}
59+
TopLevelArtifactHelper.getAllArtifactsToBuild(target, topLevelArtifactContext)
60+
.getImportantArtifacts()
61+
.toList()
62+
.stream()
63+
.filter(TopLevelArtifactHelper::shouldDisplay)
64+
.map(Artifact::getExecPathString)
65+
.forEach(this::addResult);
66+
}
67+
}
68+
}

src/main/java/com/google/devtools/build/lib/query2/cquery/GraphOutputFormatterCallback.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public Comparator<KeyedConfiguredTarget> comparator() {
8686
SkyframeExecutor skyframeExecutor,
8787
TargetAccessor<KeyedConfiguredTarget> accessor,
8888
DepsRetriever depsRetriever) {
89-
super(eventHandler, options, out, skyframeExecutor, accessor);
89+
super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false);
9090
this.depsRetriever = depsRetriever;
9191
}
9292

src/main/java/com/google/devtools/build/lib/query2/cquery/LabelAndConfigurationOutputFormatterCallback.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public class LabelAndConfigurationOutputFormatterCallback extends CqueryThreadsa
3636
SkyframeExecutor skyframeExecutor,
3737
TargetAccessor<KeyedConfiguredTarget> accessor,
3838
boolean showKind) {
39-
super(eventHandler, options, out, skyframeExecutor, accessor);
39+
super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false);
4040
this.showKind = showKind;
4141
}
4242

src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public String formatName() {
7575
TargetAccessor<KeyedConfiguredTarget> accessor,
7676
AspectResolver resolver,
7777
OutputType outputType) {
78-
super(eventHandler, options, out, skyframeExecutor, accessor);
78+
super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false);
7979
this.outputType = outputType;
8080
this.skyframeExecutor = skyframeExecutor;
8181
this.resolver = resolver;

src/main/java/com/google/devtools/build/lib/query2/cquery/StarlarkOutputFormatterCallback.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public Object providers(ConfiguredTarget target) {
142142
SkyframeExecutor skyframeExecutor,
143143
TargetAccessor<KeyedConfiguredTarget> accessor)
144144
throws QueryException, InterruptedException {
145-
super(eventHandler, options, out, skyframeExecutor, accessor);
145+
super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false);
146146

147147
ParserInput input = null;
148148
String exceptionMessagePrefix;

src/main/java/com/google/devtools/build/lib/query2/cquery/TransitionsOutputFormatterCallback.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public String getName() {
8383
TargetAccessor<KeyedConfiguredTarget> accessor,
8484
BuildConfiguration hostConfiguration,
8585
@Nullable TransitionFactory<RuleTransitionData> trimmingTransitionFactory) {
86-
super(eventHandler, options, out, skyframeExecutor, accessor);
86+
super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false);
8787
this.hostConfiguration = hostConfiguration;
8888
this.trimmingTransitionFactory = trimmingTransitionFactory;
8989
this.partialResultMap = Maps.newHashMap();

0 commit comments

Comments
 (0)