aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/query2
diff options
context:
space:
mode:
authorGravatar dhananjayn <dhananjayn@google.com>2018-07-23 10:41:03 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-07-23 10:42:16 -0700
commiteb29656b3df5149032ef8892fb9cb29862d2def5 (patch)
tree540a3261579de8db4b3fda8aec797f9cab8e47be /src/main/java/com/google/devtools/build/lib/query2
parent186b887e5862c1502010f097e75bfd8d605620b0 (diff)
Annotate conditional edges with corresponding conditions in `query
--output graph`. Implementation: AIUI, currently the "edges' conditions" are lost [1] when the larger graph is initially constructed. It now does a second pass over dependency subgraph to find all the conditional edges and annotates them in dot output. This can be easily extended in other forms of output, but for now it only annotates edges in dot output. [1]: https://github.com/bazelbuild/bazel/blob/32e9fee4e2192a340d0b1823538bf8e9fdf92b65/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java#L745-L770 PiperOrigin-RevId: 205685823
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/query2')
-rw-r--r--src/main/java/com/google/devtools/build/lib/query2/output/ConditionalEdges.java151
-rw-r--r--src/main/java/com/google/devtools/build/lib/query2/output/GraphOutputFormatter.java107
-rw-r--r--src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java30
-rw-r--r--src/main/java/com/google/devtools/build/lib/query2/output/QueryOptions.java10
-rw-r--r--src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java21
5 files changed, 295 insertions, 24 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/ConditionalEdges.java b/src/main/java/com/google/devtools/build/lib/query2/output/ConditionalEdges.java
new file mode 100644
index 0000000000..67b2c96dc5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/query2/output/ConditionalEdges.java
@@ -0,0 +1,151 @@
+// Copyright 2018 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.output;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.graph.Digraph;
+import com.google.devtools.build.lib.graph.Node;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Target;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Utility class to hold all conditional edges in a graph. Allows easy look-up of all conditions
+ * between two nodes.
+ */
+public class ConditionalEdges {
+ // Map containing all the conditions for all the conditional edges in a graph.
+ private HashMap<
+ Label /* src node */, SetMultimap<Label /* dest node */, Label /* condition labels */>>
+ map;
+
+ public ConditionalEdges() {}
+
+ /** Builds ConditionalEdges from given graph. */
+ public ConditionalEdges(Digraph<Target> graph) {
+ this.map = new HashMap<>();
+
+ for (Node<Target> node : graph.getNodes()) {
+ Rule rule = node.getLabel().getAssociatedRule();
+ if (rule == null) {
+ // rule is null for source files and package groups. Skip them.
+ continue;
+ }
+
+ SetMultimap<Label, Label> conditions = getAllConditions(rule, RawAttributeMapper.of(rule));
+ if (conditions.isEmpty()) {
+ // bail early for most common case of no conditions in the rule.
+ continue;
+ }
+
+ Label nodeLabel = node.getLabel().getLabel();
+ for (Node<Target> succ : node.getSuccessors()) {
+ Label successorLabel = succ.getLabel().getLabel();
+ if (conditions.containsKey(successorLabel)) {
+ insert(nodeLabel, successorLabel, conditions.get(successorLabel));
+ }
+ }
+ }
+ }
+
+ /** Inserts `conditions` for edge src --> dest. */
+ public void insert(Label src, Label dest, Set<Label> conditions) {
+ map.computeIfAbsent(src, (k) -> HashMultimap.create());
+ map.get(src).putAll(dest, conditions);
+ }
+
+ /**
+ * Returns all conditions for edge src --> dest, if they exist. Does not return default
+ * conditions.
+ */
+ public Optional<Set<Label>> get(Label src, Label dest) {
+ if (!map.containsKey(src) || !map.get(src).containsKey(dest)) {
+ return Optional.empty();
+ }
+
+ return Optional.of(map.get(src).get(dest));
+ }
+
+ /**
+ * Returns map of dependency to list of condition-labels.
+ *
+ * <p>Example: For a rule like below,
+ *
+ * <pre>
+ * some_rule(
+ * ...
+ * deps = [
+ * ... default dependencies ...
+ * ] + select ({
+ * "//some:config1": [ "//some:a", "//some:common" ],
+ * "//some:config2": [ "//other:a", "//some:common" ],
+ * "//conditions:default": [ "//some:default" ],
+ * })
+ * )
+ * </pre>
+ *
+ * it returns following map:
+ *
+ * <pre>
+ * {
+ * "//some:a": ["//some:config1" ]
+ * "//other:a": ["//some:config2" ]
+ * "//some:common": ["//some:config1", "//some:config2" ]
+ * "//some:default": [ "//conditions:default" ]
+ * }
+ * </pre>
+ */
+ private SetMultimap<Label, Label> getAllConditions(Rule rule, RawAttributeMapper attributeMap) {
+ SetMultimap<Label, Label> conditions = HashMultimap.create();
+ for (Attribute attr : rule.getAttributes()) {
+ // TODO(bazel-team): Handle the case where dependency exists through both configurable as well
+ // as non-configurable attributes. Currently this prints such an edge as a conditional one.
+ if (!attributeMap.isConfigurable(attr.getName())) {
+ // skip non configurable attributes
+ continue;
+ }
+
+ for (BuildType.Selector<?> selector :
+ ((BuildType.SelectorList<?>) attributeMap.getRawAttributeValue(rule, attr))
+ .getSelectors()) {
+ if (selector.isUnconditional()) {
+ // skip unconditional selectors
+ continue;
+ }
+ for (Map.Entry<Label, ?> entry : selector.getEntries().entrySet()) {
+ if (entry.getValue() instanceof List<?>) {
+ List<?> deps = (List<?>) entry.getValue();
+ for (Object dep : deps) {
+ if (dep instanceof Label) {
+ conditions.put((Label) dep, entry.getKey());
+ }
+ }
+ } else if (entry.getValue() instanceof Label) {
+ conditions.put((Label) entry.getValue(), entry.getKey());
+ }
+ }
+ }
+ }
+ return conditions;
+ }
+};
diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/GraphOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/GraphOutputFormatter.java
index 9640dd6067..9e7072daf5 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/output/GraphOutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/output/GraphOutputFormatter.java
@@ -14,7 +14,9 @@
package com.google.devtools.build.lib.query2.output;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
+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.graph.Digraph;
@@ -33,6 +35,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
/**
@@ -42,6 +45,7 @@ import java.util.Set;
class GraphOutputFormatter extends OutputFormatter {
private int graphNodeStringLimit;
+ private int graphConditionalEdgesLimit;
@Override
public String getName() {
@@ -49,22 +53,38 @@ class GraphOutputFormatter extends OutputFormatter {
}
@Override
- public void output(QueryOptions options, Digraph<Target> result, OutputStream out,
- AspectResolver aspectProvider) {
+ public void output(
+ QueryOptions options,
+ Digraph<Target> result,
+ OutputStream out,
+ AspectResolver aspectProvider,
+ ConditionalEdges conditionalEdges) {
this.graphNodeStringLimit = options.graphNodeStringLimit;
+ this.graphConditionalEdgesLimit = options.graphConditionalEdgesLimit;
boolean sortLabels = options.orderOutput == OrderOutput.FULL;
if (options.graphFactored) {
- outputFactored(result, new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)),
- sortLabels);
+ outputFactored(
+ result,
+ new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)),
+ sortLabels,
+ conditionalEdges);
} else {
- outputUnfactored(result, new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)),
- sortLabels, options);
+ outputUnfactored(
+ result,
+ new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)),
+ sortLabels,
+ options,
+ conditionalEdges);
}
}
private void outputUnfactored(
- Digraph<Target> result, PrintWriter out, boolean sortLabels, final QueryOptions options) {
+ Digraph<Target> result,
+ PrintWriter out,
+ boolean sortLabels,
+ final QueryOptions options,
+ ConditionalEdges conditionalEdges) {
result.visitNodesBeforeEdges(
new DotOutputVisitor<Target>(out, LABEL_STRINGIFIER) {
@Override
@@ -73,6 +93,18 @@ class GraphOutputFormatter extends OutputFormatter {
// TODO(bazel-team): (2009) make this the default in Digraph.
out.printf(" node [shape=box];%s", options.getLineTerminator());
}
+
+ @Override
+ public void visitEdge(Node<Target> lhs, Node<Target> rhs) {
+ super.visitEdge(lhs, rhs);
+
+ String outputLabel =
+ getConditionsGraphLabel(
+ ImmutableSet.of(lhs), ImmutableSet.of(rhs), conditionalEdges);
+ if (!outputLabel.isEmpty()) {
+ out.printf(" [label=\"%s\"];\n", outputLabel);
+ }
+ }
},
sortLabels ? new TargetOrdering() : null);
}
@@ -101,7 +133,11 @@ class GraphOutputFormatter extends OutputFormatter {
return result;
}
- private void outputFactored(Digraph<Target> result, PrintWriter out, final boolean sortLabels) {
+ private void outputFactored(
+ Digraph<Target> result,
+ PrintWriter out,
+ final boolean sortLabels,
+ ConditionalEdges conditionalEdges) {
EquivalenceRelation<Node<Target>> equivalenceRelation = createEquivalenceRelation();
Collection<Set<Node<Target>>> partition =
@@ -150,6 +186,17 @@ class GraphOutputFormatter extends OutputFormatter {
// TODO(bazel-team): (2009) make this the default in Digraph.
out.println(" node [shape=box];");
}
+
+ @Override
+ public void visitEdge(Node<Set<Node<Target>>> lhs, Node<Set<Node<Target>>> rhs) {
+ super.visitEdge(lhs, rhs);
+
+ String outputLabel =
+ getConditionsGraphLabel(lhs.getLabel(), rhs.getLabel(), conditionalEdges);
+ if (!outputLabel.isEmpty()) {
+ out.printf(" [label=\"%s\"];\n", outputLabel);
+ }
+ }
},
sortLabels ? ITERABLE_COMPARATOR : null);
}
@@ -192,6 +239,50 @@ class GraphOutputFormatter extends OutputFormatter {
};
}
+ private String getConditionsGraphLabel(
+ Iterable<Node<Target>> lhs, Iterable<Node<Target>> rhs, ConditionalEdges conditionalEdges) {
+ StringBuilder buf = new StringBuilder();
+
+ if (this.graphConditionalEdgesLimit == 0) {
+ return buf.toString();
+ }
+
+ Set<Label> annotatedLabels = new HashSet<>();
+ for (Node<Target> src : lhs) {
+ Label srcLabel = src.getLabel().getLabel();
+ for (Node<Target> dest : rhs) {
+ Label destLabel = dest.getLabel().getLabel();
+ Optional<Set<Label>> conditions = conditionalEdges.get(srcLabel, destLabel);
+ if (conditions.isPresent()) {
+ boolean firstItem = true;
+
+ int limit =
+ (this.graphConditionalEdgesLimit == -1)
+ ? conditions.get().size()
+ : (this.graphConditionalEdgesLimit - 1);
+
+ for (Label conditionLabel : Iterables.limit(conditions.get(), limit)) {
+ if (!annotatedLabels.add(conditionLabel)) {
+ // duplicate label; skip.
+ continue;
+ }
+
+ if (!firstItem) {
+ buf.append("\\n");
+ }
+
+ buf.append(conditionLabel.getCanonicalForm());
+ firstItem = false;
+ }
+ if (conditions.get().size() > limit) {
+ buf.append("...");
+ }
+ }
+ }
+ }
+ return buf.toString();
+ }
+
private static final int RESERVED_LABEL_CHARS = "\\n...and 9999999 more items".length();
private static final LabelSerializer<Target> LABEL_STRINGIFIER = new LabelSerializer<Target>() {
diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java
index d7ab9d6598..b8c0de86bf 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java
@@ -167,12 +167,16 @@ public abstract class OutputFormatter implements Serializable {
}
/**
- * Format the result (a set of target nodes implicitly ordered according to
- * the graph maintained by the QueryEnvironment), and print it to "out".
+ * Format the result (a set of target nodes implicitly ordered according to the graph maintained
+ * by the QueryEnvironment), and print it to "out".
*/
- public abstract void output(QueryOptions options, Digraph<Target> result, OutputStream out,
- AspectResolver aspectProvider)
- throws IOException, InterruptedException;
+ public abstract void output(
+ QueryOptions options,
+ Digraph<Target> result,
+ OutputStream out,
+ AspectResolver aspectProvider,
+ ConditionalEdges conditionalEdges)
+ throws IOException, InterruptedException;
/**
* Unordered streamed output formatter (wrt. dependency ordering).
@@ -242,7 +246,9 @@ public abstract class OutputFormatter implements Serializable {
QueryOptions options,
Digraph<Target> result,
OutputStream out,
- AspectResolver aspectResolver) throws IOException, InterruptedException {
+ AspectResolver aspectResolver,
+ ConditionalEdges conditionalEdges)
+ throws IOException, InterruptedException {
setOptions(options, aspectResolver);
OutputFormatterCallback.processAllTargets(
createPostFactoStreamCallback(out, options), getOrderedTargets(result, options));
@@ -605,8 +611,9 @@ public abstract class OutputFormatter implements Serializable {
QueryOptions options,
Digraph<Target> result,
OutputStream out,
- AspectResolver aspectResolver)
- throws IOException {
+ AspectResolver aspectResolver,
+ ConditionalEdges conditionalEdges)
+ throws IOException {
PrintStream printStream = new PrintStream(out);
// getRoots() isn't defined for cyclic graphs, so in order to handle
// cycles correctly, we need work on the strong component graph, as
@@ -674,8 +681,9 @@ public abstract class OutputFormatter implements Serializable {
QueryOptions options,
Digraph<Target> result,
OutputStream out,
- AspectResolver aspectResolver)
- throws IOException {
+ AspectResolver aspectResolver,
+ ConditionalEdges conditionalEdges)
+ 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
// the same rank. Graphs may contain cycles because there are errors in BUILD files.
@@ -795,7 +803,7 @@ public abstract class OutputFormatter implements Serializable {
// directly into Type.
return new PossibleAttributeValues(
ImmutableList.<Object>of(
- attributeMap.getReachableLabels(attr.getName(), /*includeSelectKeys=*/false)),
+ attributeMap.getReachableLabels(attr.getName(), /*includeSelectKeys=*/ false)),
source);
} else if ((list =
attributeMap.getConcatenatedSelectorListsOfListType(
diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/QueryOptions.java b/src/main/java/com/google/devtools/build/lib/query2/output/QueryOptions.java
index 862dde3001..fb05af1575 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/output/QueryOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/output/QueryOptions.java
@@ -126,6 +126,16 @@ public class QueryOptions extends CommonQueryOptions {
public int graphNodeStringLimit;
@Option(
+ name = "graph:conditional_edges_limit",
+ defaultValue = "4",
+ documentationCategory = OptionDocumentationCategory.QUERY,
+ effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+ help =
+ "The maximum number of condition labels to show. -1 means no truncation and 0 means no "
+ + "annotation. This option is only applicable to --output=graph.")
+ public int graphConditionalEdgesLimit;
+
+ @Option(
name = "graph:factored",
defaultValue = "true",
documentationCategory = OptionDocumentationCategory.QUERY,
diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java b/src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java
index 42687e2063..57a0655add 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.query2.output;
+import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.query2.engine.DigraphQueryEvalResult;
import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback;
@@ -52,11 +53,21 @@ public class QueryOutputUtils {
@SuppressWarnings("unchecked")
DigraphQueryEvalResult<Target> digraphQueryEvalResult =
(DigraphQueryEvalResult<Target>) result;
- formatter.output(
- queryOptions,
- digraphQueryEvalResult.getGraph().extractSubgraph(targetsResult),
- outputStream,
- aspectResolver);
+ Digraph<Target> subgraph = digraphQueryEvalResult.getGraph().extractSubgraph(targetsResult);
+
+ // Building ConditionalEdges involves traversing the subgraph and so we only do this when
+ // needed.
+ //
+ // TODO(bazel-team): Remove this while adding support for conditional edges in other
+ // formatters.
+ ConditionalEdges conditionalEdges;
+ if (formatter instanceof GraphOutputFormatter) {
+ conditionalEdges = new ConditionalEdges(subgraph);
+ } else {
+ conditionalEdges = new ConditionalEdges();
+ }
+
+ formatter.output(queryOptions, subgraph, outputStream, aspectResolver, conditionalEdges);
}
}
}