Stop "query --output=proto" crashing on label-dict aspect attributes.
It's unfortunately complicated to properly support these attributes (we need
to define the proto spec more finely). This change just reports a proper error
vs. crashing Blaze. See the new comments in ProtoOutputFormatter for details.
PiperOrigin-RevId: 299903363
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 4a90723..9ed0d21 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -30,6 +30,7 @@
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.Globber.BadGlobException;
import com.google.devtools.build.lib.packages.PackageValidator.InvalidPackageException;
@@ -136,6 +137,7 @@
protected Iterable<EnvironmentExtension> environmentExtensions = ImmutableList.of();
protected PackageValidator packageValidator = PackageValidator.NOOP_VALIDATOR;
protected boolean doChecksForTesting = true;
+ protected Reporter reporter;
public BuilderForTesting setEnvironmentExtensions(
Iterable<EnvironmentExtension> environmentExtensions) {
@@ -153,6 +155,11 @@
return this;
}
+ public BuilderForTesting setReporter(Reporter reporter) {
+ this.reporter = reporter;
+ return this;
+ }
+
public abstract PackageFactory build(RuleClassProvider ruleClassProvider, FileSystem fs);
}
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/OutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/engine/OutputFormatterCallback.java
index 50708d6..c66eefe 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/OutputFormatterCallback.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/OutputFormatterCallback.java
@@ -67,11 +67,11 @@
* Use an {@code OutputFormatterCallback} with an already computed set of targets. Note that this
* does not work in stream mode, as the {@code targets} would already be computed.
*
- * <p>The intended usage of this method is to use {@code StreamedFormatter} formaters in non
+ * <p>The intended usage of this method is to use {@code StreamedFormatter} formatters in non
* streaming contexts.
*/
- public static <T> void processAllTargets(OutputFormatterCallback<T> callback,
- Iterable<T> targets) throws IOException, InterruptedException {
+ public static <T> void processAllTargets(OutputFormatterCallback<T> callback, Iterable<T> targets)
+ throws IOException, InterruptedException {
boolean failFast = true;
try {
callback.start();
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/AbstractUnorderedFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/AbstractUnorderedFormatter.java
index 6a6917a..336849b 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/AbstractUnorderedFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/AbstractUnorderedFormatter.java
@@ -15,6 +15,7 @@
package com.google.devtools.build.lib.query2.query.output;
import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.graph.Node;
import com.google.devtools.build.lib.packages.Target;
@@ -30,11 +31,19 @@
@Override
public void setOptions(CommonQueryOptions options, AspectResolver aspectResolver) {}
+ /** Sets a handler for reporting status output / errors. */
+ public void setEventHandler(EventHandler eventHandler) {}
+
@Override
public void output(
- QueryOptions options, Digraph<Target> result, OutputStream out, AspectResolver aspectResolver)
+ QueryOptions options,
+ Digraph<Target> result,
+ OutputStream out,
+ AspectResolver aspectResolver,
+ EventHandler eventHandler)
throws IOException, InterruptedException {
setOptions(options, aspectResolver);
+ setEventHandler(eventHandler);
OutputFormatterCallback.processAllTargets(
createPostFactoStreamCallback(out, options), getOrderedTargets(result, options));
}
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/GraphOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/GraphOutputFormatter.java
index 7e9f377..3d71a0f 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/GraphOutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/GraphOutputFormatter.java
@@ -19,6 +19,7 @@
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.collect.EquivalenceRelation;
+import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.graph.DotOutputVisitor;
import com.google.devtools.build.lib.graph.LabelSerializer;
@@ -58,7 +59,8 @@
QueryOptions options,
Digraph<Target> result,
OutputStream out,
- AspectResolver aspectProvider) {
+ AspectResolver aspectProvider,
+ EventHandler eventHandler) {
this.graphNodeStringLimit = options.graphNodeStringLimit;
this.graphConditionalEdgesLimit = options.graphConditionalEdgesLimit;
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/MaxrankOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/MaxrankOutputFormatter.java
index 83dba1a..777cc3d 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/MaxrankOutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/MaxrankOutputFormatter.java
@@ -16,6 +16,7 @@
import static java.util.Comparator.comparingInt;
+import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.graph.Node;
import com.google.devtools.build.lib.packages.Target;
@@ -51,7 +52,11 @@
@Override
public void output(
- QueryOptions options, Digraph<Target> result, OutputStream out, AspectResolver aspectResolver)
+ QueryOptions options,
+ Digraph<Target> result,
+ OutputStream out,
+ AspectResolver aspectResolver,
+ EventHandler eventHandler)
throws IOException {
// In order to handle cycles correctly, we need work on the strong
// component graph, as cycles should be treated a "clump" of nodes all on
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/MinrankOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/MinrankOutputFormatter.java
index efc5cb6..8e020cd 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/MinrankOutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/MinrankOutputFormatter.java
@@ -15,6 +15,7 @@
package com.google.devtools.build.lib.query2.query.output;
import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.graph.Node;
import com.google.devtools.build.lib.packages.Target;
@@ -63,7 +64,11 @@
@Override
public void output(
- QueryOptions options, Digraph<Target> result, OutputStream out, AspectResolver aspectResolver)
+ QueryOptions options,
+ Digraph<Target> result,
+ OutputStream out,
+ AspectResolver aspectResolver,
+ EventHandler eventHandler)
throws IOException {
PrintStream printStream = new PrintStream(out);
// getRoots() isn't defined for cyclic graphs, so in order to handle
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/OutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/OutputFormatter.java
index 77770fc..a3e8314 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/OutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/OutputFormatter.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.query2.query.output;
+import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment;
@@ -54,6 +55,10 @@
* by the QueryEnvironment), and print it to "out".
*/
public abstract void output(
- QueryOptions options, Digraph<Target> result, OutputStream out, AspectResolver aspectProvider)
+ QueryOptions options,
+ Digraph<Target> result,
+ OutputStream out,
+ AspectResolver aspectProvider,
+ EventHandler eventHandler)
throws IOException, InterruptedException;
}
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/ProtoOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/ProtoOutputFormatter.java
index 23db2a6..e0b580b 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/ProtoOutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/ProtoOutputFormatter.java
@@ -24,11 +24,14 @@
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.graph.Node;
import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
@@ -98,6 +101,8 @@
private boolean includeLocations = true;
private boolean includeRuleInputsAndOutputs = true;
+ private EventHandler eventHandler;
+
@Override
public String getName() {
return "proto";
@@ -116,6 +121,11 @@
this.includeRuleInputsAndOutputs = options.protoIncludeRuleInputsAndOutputs;
}
+ @Override
+ public void setEventHandler(EventHandler eventHandler) {
+ this.eventHandler = eventHandler;
+ }
+
private static Predicate<String> newAttributePredicate(List<String> outputAttributes) {
if (outputAttributes.equals(ImmutableList.of("all"))) {
return Predicates.alwaysTrue();
@@ -198,7 +208,7 @@
if (!includeAspectAttribute(attribute, labels)) {
continue;
}
- Object attributeValue = getAspectAttributeValue(attribute, labels);
+ Object attributeValue = getAspectAttributeValue(target, attribute, labels);
Build.Attribute serializedAttribute =
AttributeFormatter.getAttributeProto(
attribute,
@@ -369,11 +379,29 @@
&& ruleAttributePredicate.apply(attr.getName());
}
- private static Object getAspectAttributeValue(Attribute attribute, Collection<Label> labels) {
+ private Object getAspectAttributeValue(
+ Target target, Attribute attribute, Collection<Label> labels) {
Type<?> attributeType = attribute.getType();
if (attributeType.equals(BuildType.LABEL)) {
Preconditions.checkState(labels.size() == 1, "attribute=%s, labels=%s", attribute, labels);
return Iterables.getOnlyElement(labels);
+ } else if (attributeType.equals(BuildType.LABEL_KEYED_STRING_DICT)) {
+ // Ideally we'd support LABEL_KEYED_STRING_DICT by getting the value directly from the aspect
+ // definition vs. trying to reverse-construct it from the flattened labels as this method
+ // does. Unfortunately any proper support surfaces a latent bug between --output=proto and
+ // aspect attributes: "{@code labels} isn't the set of labels for a single attribute value but
+ // for all values of all attributes with the same name. We can have multiple attributes with
+ // the same name because multiple aspects may attach to a rule, and nothing is stopping them
+ // from defining the same attribute names. That means the "Attribute" proto message doesn't
+ // really represent a single attribute, in spite of its documented purpose. This all calls for
+ // an API design upgrade to properly consider these relationships. Details at b/149982967.
+ eventHandler.handle(
+ Event.error(
+ String.format(
+ "Target \"%s\", aspect attribute \"%s\": type \"%s\" not yet supported with"
+ + " --output=proto. See b/149982967",
+ target.getLabel(), attribute.getName(), BuildType.LABEL_KEYED_STRING_DICT)));
+ return ImmutableMap.of();
} else {
Preconditions.checkState(
attributeType.equals(BuildType.LABEL_LIST),
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/QueryOutputUtils.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/QueryOutputUtils.java
index 89bf13f..5e6506f 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/QueryOutputUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/QueryOutputUtils.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.query2.query.output;
+import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.profiler.Profiler;
@@ -37,9 +38,14 @@
&& formatter instanceof StreamedFormatter;
}
- public static void output(QueryOptions queryOptions, QueryEvalResult result,
- Set<Target> targetsResult, OutputFormatter formatter, OutputStream outputStream,
- AspectResolver aspectResolver)
+ public static void output(
+ QueryOptions queryOptions,
+ QueryEvalResult result,
+ Set<Target> targetsResult,
+ OutputFormatter formatter,
+ OutputStream outputStream,
+ AspectResolver aspectResolver,
+ EventHandler eventHandler)
throws IOException, InterruptedException {
/*
* This is not really streaming, but we are using the streaming interface for writing into the
@@ -63,7 +69,7 @@
}
try (SilentCloseable closeable = Profiler.instance().profile("formatter.output")) {
- formatter.output(queryOptions, subgraph, outputStream, aspectResolver);
+ formatter.output(queryOptions, subgraph, outputStream, aspectResolver, eventHandler);
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/genquery/GenQuery.java b/src/main/java/com/google/devtools/build/lib/rules/genquery/GenQuery.java
index fbbc6bb..252e1e2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/genquery/GenQuery.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/genquery/GenQuery.java
@@ -404,7 +404,8 @@
result,
formatter,
outputStream,
- queryOptions.aspectDeps.createResolver(packageProvider, getEventHandler(ruleContext)));
+ queryOptions.aspectDeps.createResolver(packageProvider, getEventHandler(ruleContext)),
+ getEventHandler(ruleContext));
outputStream.close();
} catch (ClosedByInterruptException e) {
throw new InterruptedException(e.getMessage());
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
index c0ced79..66964a7 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
@@ -158,7 +158,8 @@
targets,
formatter,
out,
- queryOptions.aspectDeps.createResolver(env.getPackageManager(), env.getReporter()));
+ queryOptions.aspectDeps.createResolver(env.getPackageManager(), env.getReporter()),
+ env.getReporter());
} catch (ClosedByInterruptException | InterruptedException e) {
env.getReporter().handle(Event.error("query interrupted"));
return Either.ofLeft(BlazeCommandResult.exitCode(ExitCode.INTERRUPTED));
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/QueryIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/buildtool/QueryIntegrationTest.java
index f3840da..8d4952a 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/QueryIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/QueryIntegrationTest.java
@@ -303,13 +303,15 @@
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ Reporter reporter = new Reporter(new EventBus(), events.collector());
QueryOutputUtils.output(
- queryOptions, result,
- callback.getResult(), formatter,
+ queryOptions,
+ result,
+ callback.getResult(),
+ formatter,
outputStream,
- queryOptions.aspectDeps.createResolver(
- env.getPackageManager(),
- new Reporter(new EventBus(), events.collector())));
+ queryOptions.aspectDeps.createResolver(env.getPackageManager(), reporter),
+ reporter);
return outputStream.toByteArray();
}
diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/PackageLoadingTestCase.java b/src/test/java/com/google/devtools/build/lib/packages/util/PackageLoadingTestCase.java
index 7fea1b1..56e9db9 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/util/PackageLoadingTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/util/PackageLoadingTestCase.java
@@ -100,6 +100,7 @@
loadingMock
.getPackageFactoryBuilderForTesting(directories)
.setEnvironmentExtensions(getEnvironmentExtensions())
+ .setReporter(reporter)
.build(ruleClassProvider, fileSystem);
skyframeExecutor = createSkyframeExecutor();
setUpSkyframe();