diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java | 486 |
1 files changed, 486 insertions, 0 deletions
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 new file mode 100644 index 0000000000..b7a9d64ed4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java @@ -0,0 +1,486 @@ +// Copyright 2014 Google Inc. 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.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.graph.Digraph; +import com.google.devtools.build.lib.graph.Node; +import com.google.devtools.build.lib.packages.AggregatingAttributeMapper; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.syntax.EvalUtils; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.util.BinaryPredicate; +import com.google.devtools.build.lib.util.Pair; +import com.google.devtools.common.options.EnumConverter; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Interface for classes which order, format and print the result of a Blaze + * graph query. + */ +public abstract class OutputFormatter { + + /** + * Discriminator for different kinds of OutputFormatter. + */ + public enum Type { + LABEL, + LABEL_KIND, + BUILD, + MINRANK, + MAXRANK, + PACKAGE, + LOCATION, + GRAPH, + XML, + PROTO, + RECORD, + } + + /** + * Where the value of an attribute comes from + */ + protected enum AttributeValueSource { + RULE, // Explicitly specified on the rule + PACKAGE, // Package default + DEFAULT // Rule class default + } + + public static final Function<Node<Target>, Target> EXTRACT_NODE_LABEL = + new Function<Node<Target>, Target>() { + @Override + public Target apply(Node<Target> input) { + return input.getLabel(); + } + }; + + /** + * Converter from strings to OutputFormatter.Type. + */ + public static class Converter extends EnumConverter<Type> { + public Converter() { super(Type.class, "output formatter"); } + } + + public static ImmutableList<OutputFormatter> getDefaultFormatters() { + return ImmutableList.of( + new LabelOutputFormatter(false), + new LabelOutputFormatter(true), + new BuildOutputFormatter(), + new MinrankOutputFormatter(), + new MaxrankOutputFormatter(), + new PackageOutputFormatter(), + new LocationOutputFormatter(), + new GraphOutputFormatter(), + new XmlOutputFormatter(), + new ProtoOutputFormatter()); + } + + public static String formatterNames(Iterable<OutputFormatter> formatters) { + return Joiner.on(", ").join(Iterables.transform(formatters, + new Function<OutputFormatter, String>() { + @Override + public String apply(OutputFormatter input) { + return input.getName(); + } + })); + } + + /** + * Returns the output formatter for the specified command-line options. + */ + public static OutputFormatter getFormatter( + Iterable<OutputFormatter> formatters, String type) { + for (OutputFormatter formatter : formatters) { + if (formatter.getName().equals(type)) { + return formatter; + } + } + + return null; + } + + /** + * Given a set of query options, returns a BinaryPredicate suitable for + * passing to {@link Rule#getLabels()}, {@link XmlOutputFormatter}, etc. + */ + public static BinaryPredicate<Rule, Attribute> getDependencyFilter(QueryOptions queryOptions) { + // TODO(bazel-team): Optimize: and(ALL_DEPS, x) -> x, etc. + return Rule.and( + queryOptions.includeHostDeps ? Rule.ALL_DEPS : Rule.NO_HOST_DEPS, + queryOptions.includeImplicitDeps ? Rule.ALL_DEPS : Rule.NO_IMPLICIT_DEPS); + } + + /** + * 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, PrintStream out) + throws IOException; + + /** + * Unordered output formatter (wrt. dependency ordering). + * + * <p>Formatters that support unordered output may be used when only the set of query results is + * requested but their ordering is irrelevant. + * + * <p>The benefit of using a unordered formatter is that we can save the potentially expensive + * subgraph extraction step before presenting the query results. + */ + public interface UnorderedFormatter { + void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out) + throws IOException; + } + + /** + * Returns the user-visible name of the output formatter. + */ + public abstract String getName(); + + /** + * An output formatter that prints the labels of the resulting target set in + * topological order, optionally with the target's kind. + */ + private static class LabelOutputFormatter extends OutputFormatter implements UnorderedFormatter{ + + private final boolean showKind; + + public LabelOutputFormatter(boolean showKind) { + this.showKind = showKind; + } + + @Override + public String getName() { + return showKind ? "label_kind" : "label"; + } + + @Override + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out) { + for (Target target : result) { + if (showKind) { + out.print(target.getTargetKind()); + out.print(' '); + } + out.println(target.getLabel()); + } + } + + @Override + public void output(QueryOptions options, Digraph<Target> result, PrintStream out) { + Iterable<Target> ordered = Iterables.transform( + result.getTopologicalOrder(new TargetOrdering()), EXTRACT_NODE_LABEL); + outputUnordered(options, ordered, out); + } + } + + /** + * An ordering of Targets based on the ordering of their labels. + */ + static class TargetOrdering implements Comparator<Target> { + @Override + public int compare(Target o1, Target o2) { + return o1.getLabel().compareTo(o2.getLabel()); + } + } + + /** + * An output formatter that prints the names of the packages of the target + * set, in lexicographical order without duplicates. + */ + private static class PackageOutputFormatter extends OutputFormatter implements + UnorderedFormatter { + @Override + public String getName() { + return "package"; + } + + @Override + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out) { + Set<String> packageNames = Sets.newTreeSet(); + for (Target target : result) { + packageNames.add(target.getLabel().getPackageName()); + } + for (String packageName : packageNames) { + out.println(packageName); + } + } + + @Override + public void output(QueryOptions options, Digraph<Target> result, PrintStream out) { + Iterable<Target> ordered = Iterables.transform( + result.getTopologicalOrder(new TargetOrdering()), EXTRACT_NODE_LABEL); + outputUnordered(options, ordered, out); + } + } + + /** + * An output formatter that prints the labels of the targets, preceded by + * their locations and kinds, in topological order. For output files, the + * location of the generating rule is given; for input files, the location of + * line 1 is given. + */ + private static class LocationOutputFormatter extends OutputFormatter implements + UnorderedFormatter { + @Override + public String getName() { + return "location"; + } + + @Override + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out) { + for (Target target : result) { + Location location = target.getLocation(); + out.println(location.print() + ": " + target.getTargetKind() + " " + target.getLabel()); + } + } + + @Override + public void output(QueryOptions options, Digraph<Target> result, PrintStream out) { + Iterable<Target> ordered = Iterables.transform( + result.getTopologicalOrder(new TargetOrdering()), EXTRACT_NODE_LABEL); + outputUnordered(options, ordered, out); + } + } + + /** + * An output formatter that prints the generating rules using the syntax of + * the BUILD files. If multiple targets are generated by the same rule, it is + * printed only once. + */ + private static class BuildOutputFormatter extends OutputFormatter implements UnorderedFormatter { + @Override + public String getName() { + return "build"; + } + + private void outputRule(Rule rule, PrintStream out) { + out.println(String.format("# %s", rule.getLocation())); + out.println(String.format("%s(", rule.getRuleClass())); + out.println(String.format(" name = \"%s\",", rule.getName())); + + for (Attribute attr : rule.getAttributes()) { + Pair<Iterable<Object>, AttributeValueSource> values = getAttributeValues(rule, attr); + if (Iterables.size(values.first) != 1) { + continue; // TODO(bazel-team): handle configurable attributes. + } + if (values.second != AttributeValueSource.RULE) { + continue; // Don't print default values. + } + Object value = Iterables.getOnlyElement(values.first); + out.print(String.format(" %s = ", attr.getName())); + if (value instanceof Label) { + value = value.toString(); + } else if (value instanceof List<?> && EvalUtils.isImmutable(value)) { + // Display it as a list (and not as a tuple). Attributes can never be tuples. + value = new ArrayList<>((List<?>) value); + } + EvalUtils.prettyPrintValue(value, out); + out.println(","); + } + out.println(String.format(")\n")); + } + + @Override + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out) { + Set<Label> printed = new HashSet<>(); + for (Target target : result) { + Rule rule = target.getAssociatedRule(); + if (rule == null || printed.contains(rule.getLabel())) { + continue; + } + outputRule(rule, out); + printed.add(rule.getLabel()); + } + } + + @Override + public void output(QueryOptions options, Digraph<Target> result, PrintStream out) { + Iterable<Target> ordered = Iterables.transform( + result.getTopologicalOrder(new TargetOrdering()), EXTRACT_NODE_LABEL); + outputUnordered(options, ordered, out); + } + } + + /** + * An output formatter that prints the labels in minimum rank order, preceded by + * their rank number. "Roots" have rank 0, their direct prerequisites have + * rank 1, etc. All nodes in a cycle are considered of equal rank. MINRANK + * shows the lowest rank for a given node, i.e. the length of the shortest + * path from a zero-rank node to it. + * + * If the result came from a <code>deps(x)</code> query, then the MINRANKs + * correspond to the shortest path from x to each of its prerequisites. + */ + private static class MinrankOutputFormatter extends OutputFormatter { + @Override + public String getName() { + return "minrank"; + } + + @Override + public void output(QueryOptions options, Digraph<Target> result, 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 + // cycles should be treated a "clump" of nodes all on the same rank. + // Graphs may contain cycles because there are errors in BUILD files. + + Digraph<Set<Node<Target>>> scGraph = result.getStrongComponentGraph(); + Set<Node<Set<Node<Target>>>> rankNodes = scGraph.getRoots(); + Set<Node<Set<Node<Target>>>> seen = new HashSet<>(); + seen.addAll(rankNodes); + for (int rank = 0; !rankNodes.isEmpty(); rank++) { + // Print out this rank: + for (Node<Set<Node<Target>>> xScc : rankNodes) { + for (Node<Target> x : xScc.getLabel()) { + out.println(rank + " " + x.getLabel().getLabel()); + } + } + + // Find the next rank: + Set<Node<Set<Node<Target>>>> nextRankNodes = new LinkedHashSet<>(); + for (Node<Set<Node<Target>>> x : rankNodes) { + for (Node<Set<Node<Target>>> y : x.getSuccessors()) { + if (seen.add(y)) { + nextRankNodes.add(y); + } + } + } + rankNodes = nextRankNodes; + } + } + } + + /** + * An output formatter that prints the labels in maximum rank order, preceded + * by their rank number. "Roots" have rank 0, all other nodes have a rank + * which is one greater than the maximum rank of each of their predecessors. + * All nodes in a cycle are considered of equal rank. MAXRANK shows the + * highest rank for a given node, i.e. the length of the longest non-cyclic + * path from a zero-rank node to it. + * + * If the result came from a <code>deps(x)</code> query, then the MAXRANKs + * correspond to the longest path from x to each of its prerequisites. + */ + private static class MaxrankOutputFormatter extends OutputFormatter { + @Override + public String getName() { + return "maxrank"; + } + + @Override + public void output(QueryOptions options, Digraph<Target> result, PrintStream out) { + // 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. + + // Dynamic programming algorithm: + // rank(x) = max(rank(p)) + 1 foreach p in preds(x) + // TODO(bazel-team): Move to Digraph. + class DP { + final Map<Node<Set<Node<Target>>>, Integer> ranks = new HashMap<>(); + + int rank(Node<Set<Node<Target>>> node) { + Integer rank = ranks.get(node); + if (rank == null) { + int maxPredRank = -1; + for (Node<Set<Node<Target>>> p : node.getPredecessors()) { + maxPredRank = Math.max(maxPredRank, rank(p)); + } + rank = maxPredRank + 1; + ranks.put(node, rank); + } + return rank; + } + } + DP dp = new DP(); + + // Now sort by rank... + List<Pair<Integer, Label>> output = new ArrayList<>(); + for (Node<Set<Node<Target>>> x : result.getStrongComponentGraph().getNodes()) { + int rank = dp.rank(x); + for (Node<Target> y : x.getLabel()) { + output.add(Pair.of(rank, y.getLabel().getLabel())); + } + } + Collections.sort(output, new Comparator<Pair<Integer, Label>>() { + @Override + public int compare(Pair<Integer, Label> x, Pair<Integer, Label> y) { + return x.first - y.first; + } + }); + + for (Pair<Integer, Label> pair : output) { + out.println(pair.first + " " + pair.second); + } + } + } + + /** + * Returns the possible values of the specified attribute in the specified rule. For + * non-configured attributes, this is a single value. For configurable attributes, this + * may be multiple values. + * + * <p>This is needed because the visibility attribute is replaced with an empty list + * during package loading if it is public or private in order not to visit + * the package called 'visibility'. + * + * @return a pair, where the first value is the set of possible values and the + * second is an enum that tells where the values come from (declared on the + * rule, declared as a package level default or a + * global default) + */ + protected static Pair<Iterable<Object>, AttributeValueSource> getAttributeValues( + Rule rule, Attribute attr) { + List<Object> values = new LinkedList<>(); // Not an ImmutableList: may host null values. + AttributeValueSource source; + + if (attr.getName().equals("visibility")) { + values.add(rule.getVisibility().getDeclaredLabels()); + if (rule.isVisibilitySpecified()) { + source = AttributeValueSource.RULE; + } else if (rule.getPackage().isDefaultVisibilitySet()) { + source = AttributeValueSource.PACKAGE; + } else { + source = AttributeValueSource.DEFAULT; + } + } else { + for (Object o : + AggregatingAttributeMapper.of(rule).visitAttribute(attr.getName(), attr.getType())) { + values.add(o); + } + source = rule.isAttributeValueExplicitlySpecified(attr) + ? AttributeValueSource.RULE : AttributeValueSource.DEFAULT; + } + + return Pair.of((Iterable<Object>) values, source); + } +} |