diff --git a/site/docs/cquery.html b/site/docs/cquery.html index 982f7a26501a63..c42da321d10157 100644 --- a/site/docs/cquery.html +++ b/site/docs/cquery.html @@ -452,6 +452,26 @@

Graph output

+

Files output

+ +
+--output=files
+
+ +

+ This option prints a list of the output files produced by each target matched + by the query similar to the list printed at the end of a bazel build + invocation. The output contains only the files advertised in the requested + output groups as determined by the + --output_groups + flag and never contains source files. + + Note: The output of bazel cquery --output=files //pkg:foo contains the output + files of //pkg:foo in all configurations that occur in the build (also + see the section on target pattern evaluation. If that + is not desired, wrap you query in config(..., target). +

+

Defining the output format using Starlark

diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
index 80e7968c792c98..3f5e966dc3197a 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
@@ -21,6 +21,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.configuredtargets.InputFileConfiguredTarget;
+import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
 import com.google.devtools.build.lib.analysis.test.TestProvider;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet.Node;
@@ -236,6 +238,36 @@ static ArtifactsToBuild getAllArtifactsToBuild(
         allOutputGroups.build(), /*allOutputGroupsImportant=*/ allOutputGroupsImportant);
   }
 
+  /**
+   * Returns false if the build outputs provided by the target should never be shown to users.
+   *
+   * 

Always returns false for hidden rules and source file targets. + */ + public static boolean shouldConsiderForDisplay(ConfiguredTarget configuredTarget) { + // TODO(bazel-team): this is quite ugly. Add a marker provider for this check. + if (configuredTarget instanceof InputFileConfiguredTarget) { + // Suppress display of source files (because we do no work to build them). + return false; + } + if (configuredTarget instanceof RuleConfiguredTarget) { + RuleConfiguredTarget ruleCt = (RuleConfiguredTarget) configuredTarget; + if (ruleCt.getRuleClassString().contains("$")) { + // Suppress display of hidden rules + return false; + } + } + return true; + } + + /** + * Returns true if the given artifact should be shown to users as a build output. + * + *

Always returns false for middleman and source artifacts. + */ + public static boolean shouldDisplay(Artifact artifact) { + return !artifact.isSourceArtifact() && !artifact.isMiddlemanArtifact(); + } + /** * Recursive procedure filtering a target/aspect's declared {@code * NestedSet} and {@code NestedSet} to only include {@link diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java index 54ead9c821e9d3..5acbe837c9d1f7 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java @@ -25,9 +25,7 @@ import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; -import com.google.devtools.build.lib.analysis.configuredtargets.InputFileConfiguredTarget; import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget; -import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; @@ -140,7 +138,7 @@ void showBuildResult( TopLevelArtifactHelper.getAllArtifactsToBuild(target, context) .getImportantArtifacts() .toList()) { - if (shouldPrint(artifact)) { + if (TopLevelArtifactHelper.shouldDisplay(artifact)) { if (headerFlag) { outErr.printErr("Target " + label + " up-to-date:\n"); headerFlag = false; @@ -195,7 +193,7 @@ void showBuildResult( outErr.printErr("Aspect " + aspectName + " of " + label + " up-to-date:\n"); headerFlag = false; } - if (shouldPrint(importantArtifact)) { + if (TopLevelArtifactHelper.shouldDisplay(importantArtifact)) { outErr.printErrLn(formatArtifactForShowResults(prettyPrinter, importantArtifact)); } } @@ -216,10 +214,6 @@ void showBuildResult( } } - private boolean shouldPrint(Artifact artifact) { - return !artifact.isSourceArtifact() && !artifact.isMiddlemanArtifact(); - } - private String formatArtifactForShowResults(PathPrettyPrinter prettyPrinter, Artifact artifact) { return " " + prettyPrinter.getPrettyPath(artifact.getPath().asFragment()); } @@ -268,18 +262,9 @@ private Collection filterTargetsToPrint( Collection configuredTargets) { ImmutableList.Builder result = ImmutableList.builder(); for (ConfiguredTarget configuredTarget : configuredTargets) { - // TODO(bazel-team): this is quite ugly. Add a marker provider for this check. - if (configuredTarget instanceof InputFileConfiguredTarget) { - // Suppress display of source files (because we do no work to build them). + if (!TopLevelArtifactHelper.shouldConsiderForDisplay(configuredTarget)) { continue; } - if (configuredTarget instanceof RuleConfiguredTarget) { - RuleConfiguredTarget ruleCt = (RuleConfiguredTarget) configuredTarget; - if (ruleCt.getRuleClassString().contains("$")) { - // Suppress display of hidden rules - continue; - } - } if (configuredTarget instanceof OutputFileConfiguredTarget) { // Suppress display of generated files (because they appear underneath // their generating rule), EXCEPT those ones which are not part of the diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/CqueryBuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/CqueryBuildTool.java index 9f9815c4c30fa9..771dae380d91a8 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/CqueryBuildTool.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/CqueryBuildTool.java @@ -57,6 +57,7 @@ protected ConfiguredTargetQueryEnvironment getQueryEnvironment( env.getRelativeWorkingDirectory(), env.getPackageManager().getPackagePath(), () -> walkableGraph, - cqueryOptions); + cqueryOptions, + request.getTopLevelArtifactContext()); } } 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 ed7f359e92b7af..6d74039b3094fe 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/BUILD +++ b/src/main/java/com/google/devtools/build/lib/query2/BUILD @@ -49,6 +49,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/analysis:target_and_configuration", "//src/main/java/com/google/devtools/build/lib/analysis:toolchain_collection", "//src/main/java/com/google/devtools/build/lib/analysis:toolchain_context", + "//src/main/java/com/google/devtools/build/lib/analysis:top_level_artifact_context", "//src/main/java/com/google/devtools/build/lib/buildeventstream", "//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto", "//src/main/java/com/google/devtools/build/lib/causes", diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/BuildOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/BuildOutputFormatterCallback.java index caab654f3e6f47..0680eeca76b7ed 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/cquery/BuildOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/BuildOutputFormatterCallback.java @@ -37,7 +37,7 @@ class BuildOutputFormatterCallback extends CqueryThreadsafeCallback { OutputStream out, SkyframeExecutor skyframeExecutor, TargetAccessor accessor) { - super(eventHandler, options, out, skyframeExecutor, accessor); + super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false); } @Override diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java index 2c4ddb09dbb9eb..6ba78edab3ef91 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java @@ -25,6 +25,7 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.ConfiguredTargetValue; +import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; @@ -81,6 +82,8 @@ public class ConfiguredTargetQueryEnvironment private CqueryOptions cqueryOptions; + private final TopLevelArtifactContext topLevelArtifactContext; + private final KeyExtractor configuredTargetKeyExtractor; @@ -119,7 +122,8 @@ public ConfiguredTargetQueryEnvironment( PathFragment parserPrefix, PathPackageLocator pkgPath, Supplier walkableGraphSupplier, - Set settings) + Set settings, + TopLevelArtifactContext topLevelArtifactContext) throws InterruptedException { super( keepGoing, @@ -135,6 +139,7 @@ public ConfiguredTargetQueryEnvironment( this.configuredTargetKeyExtractor = KeyedConfiguredTarget::getConfiguredTargetKey; this.transitiveConfigurations = getTransitiveConfigurations(transitiveConfigurationKeys, walkableGraphSupplier.get()); + this.topLevelArtifactContext = topLevelArtifactContext; } public ConfiguredTargetQueryEnvironment( @@ -147,7 +152,8 @@ public ConfiguredTargetQueryEnvironment( PathFragment parserPrefix, PathPackageLocator pkgPath, Supplier walkableGraphSupplier, - CqueryOptions cqueryOptions) + CqueryOptions cqueryOptions, + TopLevelArtifactContext topLevelArtifactContext) throws InterruptedException { this( keepGoing, @@ -159,7 +165,8 @@ public ConfiguredTargetQueryEnvironment( parserPrefix, pkgPath, walkableGraphSupplier, - cqueryOptions.toSettings()); + cqueryOptions.toSettings(), + topLevelArtifactContext); this.cqueryOptions = cqueryOptions; } @@ -270,7 +277,9 @@ private static ImmutableMap getTransitiveConfigurati accessor, kct -> getFwdDeps(ImmutableList.of(kct))), new StarlarkOutputFormatterCallback( - eventHandler, cqueryOptions, out, skyframeExecutor, accessor)); + eventHandler, cqueryOptions, out, skyframeExecutor, accessor), + new FilesOutputFormatterCallback( + eventHandler, cqueryOptions, out, skyframeExecutor, accessor, topLevelArtifactContext)); } @Override diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryThreadsafeCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryThreadsafeCallback.java index 9cb83bb7e9d88c..a6bc87c3ee8546 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryThreadsafeCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryThreadsafeCallback.java @@ -15,6 +15,7 @@ import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.events.ExtendedEventHandler; import com.google.devtools.build.lib.query2.NamedThreadSafeOutputFormatterCallback; @@ -55,6 +56,7 @@ public abstract class CqueryThreadsafeCallback protected final ConfiguredTargetAccessor accessor; private final List result = new ArrayList<>(); + private final boolean uniquifyResults; @SuppressWarnings("DefaultCharset") CqueryThreadsafeCallback( @@ -62,7 +64,8 @@ public abstract class CqueryThreadsafeCallback CqueryOptions options, OutputStream out, SkyframeExecutor skyframeExecutor, - TargetAccessor accessor) { + TargetAccessor accessor, + boolean uniquifyResults) { this.eventHandler = eventHandler; this.options = options; if (out != null) { @@ -72,6 +75,7 @@ public abstract class CqueryThreadsafeCallback } this.skyframeExecutor = skyframeExecutor; this.accessor = (ConfiguredTargetAccessor) accessor; + this.uniquifyResults = uniquifyResults; } public void addResult(String string) { @@ -86,7 +90,8 @@ public List getResult() { @Override public void close(boolean failFast) throws InterruptedException, IOException { if (!failFast && printStream != null) { - for (String s : result) { + List resultsToPrint = uniquifyResults ? ImmutableSet.copyOf(result).asList() : result; + for (String s : resultsToPrint) { // TODO(ulfjack): We should use queryOptions.getLineTerminator() instead. printStream.append(s).append("\n"); } diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/FilesOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/FilesOutputFormatterCallback.java new file mode 100644 index 00000000000000..d0ceeeaef5b088 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/FilesOutputFormatterCallback.java @@ -0,0 +1,68 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.query2.cquery; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; +import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper; +import com.google.devtools.build.lib.events.ExtendedEventHandler; +import com.google.devtools.build.lib.query2.engine.QueryEnvironment.TargetAccessor; +import com.google.devtools.build.lib.skyframe.SkyframeExecutor; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Cquery output formatter that prints the set of output files advertised by the matched targets. + */ +public class FilesOutputFormatterCallback extends CqueryThreadsafeCallback { + + private final TopLevelArtifactContext topLevelArtifactContext; + + FilesOutputFormatterCallback( + ExtendedEventHandler eventHandler, + CqueryOptions options, + OutputStream out, + SkyframeExecutor skyframeExecutor, + TargetAccessor accessor, + TopLevelArtifactContext topLevelArtifactContext) { + // Different targets may provide the same artifact, so we deduplicate the collection of all + // results at the end. + super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ true); + this.topLevelArtifactContext = topLevelArtifactContext; + } + + @Override + public String getName() { + return "files"; + } + + @Override + public void processOutput(Iterable partialResult) + throws IOException, InterruptedException { + for (KeyedConfiguredTarget keyedTarget : partialResult) { + ConfiguredTarget target = keyedTarget.getConfiguredTarget(); + if (!TopLevelArtifactHelper.shouldConsiderForDisplay(target)) { + continue; + } + TopLevelArtifactHelper.getAllArtifactsToBuild(target, topLevelArtifactContext) + .getImportantArtifacts() + .toList() + .stream() + .filter(TopLevelArtifactHelper::shouldDisplay) + .map(Artifact::getExecPathString) + .forEach(this::addResult); + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/GraphOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/GraphOutputFormatterCallback.java index f73ef1bfc7f423..7630ee31036905 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/cquery/GraphOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/GraphOutputFormatterCallback.java @@ -86,7 +86,7 @@ public Comparator comparator() { SkyframeExecutor skyframeExecutor, TargetAccessor accessor, DepsRetriever depsRetriever) { - super(eventHandler, options, out, skyframeExecutor, accessor); + super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false); this.depsRetriever = depsRetriever; } diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/LabelAndConfigurationOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/LabelAndConfigurationOutputFormatterCallback.java index a23ea24a960df7..cffca9a3f12c05 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/cquery/LabelAndConfigurationOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/LabelAndConfigurationOutputFormatterCallback.java @@ -36,7 +36,7 @@ public class LabelAndConfigurationOutputFormatterCallback extends CqueryThreadsa SkyframeExecutor skyframeExecutor, TargetAccessor accessor, boolean showKind) { - super(eventHandler, options, out, skyframeExecutor, accessor); + super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false); this.showKind = showKind; } diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java index bbd17c9c697861..ea6cc9daea9f19 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java @@ -75,7 +75,7 @@ public String formatName() { TargetAccessor accessor, AspectResolver resolver, OutputType outputType) { - super(eventHandler, options, out, skyframeExecutor, accessor); + super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false); this.outputType = outputType; this.skyframeExecutor = skyframeExecutor; this.resolver = resolver; diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/StarlarkOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/StarlarkOutputFormatterCallback.java index d2c8e8a2886884..fc864bc0dd3bc6 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/cquery/StarlarkOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/StarlarkOutputFormatterCallback.java @@ -142,7 +142,7 @@ public Object providers(ConfiguredTarget target) { SkyframeExecutor skyframeExecutor, TargetAccessor accessor) throws QueryException, InterruptedException { - super(eventHandler, options, out, skyframeExecutor, accessor); + super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false); ParserInput input = null; String exceptionMessagePrefix; diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/TransitionsOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/TransitionsOutputFormatterCallback.java index d3c4369a9a0c61..fc884213a36706 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/cquery/TransitionsOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/TransitionsOutputFormatterCallback.java @@ -83,7 +83,7 @@ public String getName() { TargetAccessor accessor, BuildConfiguration hostConfiguration, @Nullable TransitionFactory trimmingTransitionFactory) { - super(eventHandler, options, out, skyframeExecutor, accessor); + super(eventHandler, options, out, skyframeExecutor, accessor, /*uniquifyResults=*/ false); this.hostConfiguration = hostConfiguration; this.trimmingTransitionFactory = trimmingTransitionFactory; this.partialResultMap = Maps.newHashMap(); diff --git a/src/test/java/com/google/devtools/build/lib/query2/cquery/BUILD b/src/test/java/com/google/devtools/build/lib/query2/cquery/BUILD index f849edb84d2eb4..f02688521f70e4 100644 --- a/src/test/java/com/google/devtools/build/lib/query2/cquery/BUILD +++ b/src/test/java/com/google/devtools/build/lib/query2/cquery/BUILD @@ -165,3 +165,21 @@ java_test( "//third_party:truth", ], ) + +java_test( + name = "FilesOutputFormatterCallbackTest", + srcs = ["FilesOutputFormatterCallbackTest.java"], + shard_count = 4, + deps = [ + ":configured_target_query_helper", + ":configured_target_query_test", + "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster", + "//src/main/java/com/google/devtools/build/lib/analysis:top_level_artifact_context", + "//src/main/java/com/google/devtools/build/lib/events", + "//src/main/java/com/google/devtools/build/lib/query2", + "//src/main/java/com/google/devtools/build/lib/query2/engine", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) diff --git a/src/test/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryHelper.java b/src/test/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryHelper.java index c806135ab6b4b4..6354982a221de4 100644 --- a/src/test/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryHelper.java +++ b/src/test/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryHelper.java @@ -49,7 +49,8 @@ protected ConfiguredTargetQueryEnvironment getPostAnalysisQueryEnvironment( parserPrefix, analysisHelper.getPackageManager().getPackagePath(), () -> walkableGraph, - this.settings); + this.settings, + null); } @Override diff --git a/src/test/java/com/google/devtools/build/lib/query2/cquery/FilesOutputFormatterCallbackTest.java b/src/test/java/com/google/devtools/build/lib/query2/cquery/FilesOutputFormatterCallbackTest.java new file mode 100644 index 00000000000000..5aeb5dd7d0f664 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/query2/cquery/FilesOutputFormatterCallbackTest.java @@ -0,0 +1,172 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.query2.cquery; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.ImmutableList; +import com.google.common.eventbus.EventBus; +import com.google.devtools.build.lib.analysis.OutputGroupInfo; +import com.google.devtools.build.lib.analysis.OutputGroupInfo.ValidationMode; +import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.Reporter; +import com.google.devtools.build.lib.query2.PostAnalysisQueryEnvironment; +import com.google.devtools.build.lib.query2.engine.QueryExpression; +import com.google.devtools.build.lib.query2.engine.QueryParser; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; + +/** Tests cquery's {@link --output=files} format. */ +public class FilesOutputFormatterCallbackTest extends ConfiguredTargetQueryTest { + + private CqueryOptions options; + private Reporter reporter; + private final List events = new ArrayList<>(); + + @Before + public final void defineSimpleRule() throws Exception { + writeFile( + "defs/rules.bzl", + "def _r_impl(ctx):", + " default_file = ctx.actions.declare_file(ctx.attr.name + '_default_file')", + " output_group_only = ctx.actions.declare_file(ctx.attr.name + '_output_group_only')", + " runfile = ctx.actions.declare_file(ctx.attr.name + '_runfile')", + " executable_only = ctx.actions.declare_file(ctx.attr.name + '_executable')", + " files = [default_file, output_group_only, runfile, executable_only]", + " ctx.actions.run_shell(", + " outputs = files,", + " command = '\\n'.join(['touch %s' % file.path for file in files]),", + " )", + " return [", + " DefaultInfo(", + " executable = executable_only,", + " files = depset(", + " direct = [", + " default_file,", + " ctx.file._implicit_source_dep,", + " ctx.file.explicit_source_dep,", + " ],", + " transitive = [info[DefaultInfo].files for info in ctx.attr.deps]", + " ),", + " runfiles = ctx.runfiles([runfile]),", + " ),", + " OutputGroupInfo(", + " foobar = [output_group_only],", + " ),", + " ]", + "r = rule(", + " implementation = _r_impl,", + " executable = True,", + " attrs = {", + " 'deps': attr.label_list(),", + " '_implicit_source_dep': attr.label(default = 'rules.bzl', allow_single_file =" + + " True),", + " 'explicit_source_dep': attr.label(allow_single_file = True),", + " },", + ")"); + writeFile("defs/BUILD", "exports_files(['rules.bzl'])"); + writeFile( + "pkg/BUILD", + "load('//defs:rules.bzl', 'r')", + "r(", + " name = 'main',", + " explicit_source_dep = 'BUILD',", + ")", + "r(", + " name = 'other',", + " deps = [':main'],", + " explicit_source_dep = 'BUILD',", + ")"); + } + + @Before + public final void setUpCqueryOptions() { + this.options = new CqueryOptions(); + this.reporter = new Reporter(new EventBus(), events::add); + } + + private List getOutput(String queryExpression, List outputGroups) + throws Exception { + QueryExpression expression = QueryParser.parse(queryExpression, getDefaultFunctions()); + Set targetPatternSet = new LinkedHashSet<>(); + expression.collectTargetPatterns(targetPatternSet); + PostAnalysisQueryEnvironment env = + ((ConfiguredTargetQueryHelper) helper).getPostAnalysisQueryEnvironment(targetPatternSet); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + FilesOutputFormatterCallback callback = + new FilesOutputFormatterCallback( + reporter, + options, + new PrintStream(output), + getHelper().getSkyframeExecutor(), + env.getAccessor(), + // Based on BuildRequest#getTopLevelArtifactContext. + new TopLevelArtifactContext( + false, + false, + false, + OutputGroupInfo.determineOutputGroups(outputGroups, ValidationMode.OFF, false))); + env.evaluateQuery(expression, callback); + return Arrays.asList(output.toString(UTF_8).split(System.lineSeparator())); + } + + @Test + public void basicQuery_defaultOutputGroup() throws Exception { + List output = getOutput("//pkg:all", ImmutableList.of()); + assertContainsExactlyWithBinDirPrefix( + output, "pkg/main_default_file", "pkg/other_default_file"); + } + + @Test + public void basicQuery_defaultAndCustomOutputGroup() throws Exception { + List output = getOutput("//pkg:main", ImmutableList.of("+foobar")); + assertContainsExactlyWithBinDirPrefix( + output, "pkg/main_default_file", "pkg/main_output_group_only"); + } + + @Test + public void basicQuery_customOutputGroupOnly() throws Exception { + List output = getOutput("//pkg:other", ImmutableList.of("foobar")); + assertContainsExactlyWithBinDirPrefix(output, "pkg/other_output_group_only"); + } + + private void assertContainsExactlyWithBinDirPrefix( + List output, String... binDirRelativePaths) { + if (binDirRelativePaths.length == 0) { + assertThat(output).isEmpty(); + return; + } + + // Extract the configuration-dependent bin dir from the first output. + assertThat(output).isNotEmpty(); + String firstPath = output.get(0); + String binDir = firstPath.substring(0, firstPath.indexOf("bin/") + "bin/".length()); + + assertThat(output) + .containsExactly( + Arrays.stream(binDirRelativePaths) + .map(binDirRelativePath -> binDir + binDirRelativePath) + .toArray()); + } +}