// Copyright 2014 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.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; import com.google.devtools.build.lib.graph.DotOutputVisitor; import com.google.devtools.build.lib.graph.LabelSerializer; import com.google.devtools.build.lib.graph.Node; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.query2.output.QueryOptions.OrderOutput; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; /** * An output formatter that prints the result as factored graph in AT&T * GraphViz format. */ class GraphOutputFormatter extends OutputFormatter { private int graphNodeStringLimit; private int graphConditionalEdgesLimit; @Override public String getName() { return "graph"; } @Override public void output( QueryOptions options, Digraph 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, conditionalEdges); } else { outputUnfactored( result, new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)), sortLabels, options, conditionalEdges); } } private void outputUnfactored( Digraph result, PrintWriter out, boolean sortLabels, final QueryOptions options, ConditionalEdges conditionalEdges) { result.visitNodesBeforeEdges( new DotOutputVisitor(out, LABEL_STRINGIFIER) { @Override public void beginVisit() { super.beginVisit(); // TODO(bazel-team): (2009) make this the default in Digraph. out.printf(" node [shape=box];%s", options.getLineTerminator()); } @Override public void visitEdge(Node lhs, Node 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); } private static final Ordering> NODE_COMPARATOR = Ordering.from(new TargetOrdering()).onResultOf(EXTRACT_NODE_LABEL); private static final Comparator>> ITERABLE_COMPARATOR = NODE_COMPARATOR.lexicographical(); /** * Given {@code collectionOfUnorderedSets}, a collection of sets of nodes, returns a collection * of sets with the same elements as {@code collectionOfUnorderedSets} but with a stable * iteration order within each set given by the target ordering, and the collection ordered by the * same induced order. */ private static Collection>> orderPartition( Collection>> collectionOfUnorderedSets) { List>> result = new ArrayList<>(); for (Set> part : collectionOfUnorderedSets) { List> toSort = new ArrayList<>(part); Collections.sort(toSort, NODE_COMPARATOR); result.add(ImmutableSet.copyOf(toSort)); } Collections.sort(result, ITERABLE_COMPARATOR); return result; } private void outputFactored( Digraph result, PrintWriter out, final boolean sortLabels, ConditionalEdges conditionalEdges) { EquivalenceRelation> equivalenceRelation = createEquivalenceRelation(); Collection>> partition = CollectionUtils.partition(result.getNodes(), equivalenceRelation); if (sortLabels) { partition = orderPartition(partition); } Digraph>> factoredGraph = result.createImageUnderPartition(partition); // Concatenate the labels of all topologically-equivalent nodes. LabelSerializer>> labelSerializer = new LabelSerializer>>() { @Override public String serialize(Node>> node) { int actualLimit = graphNodeStringLimit - RESERVED_LABEL_CHARS; boolean firstItem = true; StringBuilder buf = new StringBuilder(); int count = 0; for (Node eqNode : node.getLabel()) { String labelString = eqNode.getLabel().getLabel().toString(); if (!firstItem) { buf.append("\\n"); // Use -1 to denote no limit, as it is easier than trying to pass MAX_INT on the cmdline if (graphNodeStringLimit != -1 && (buf.length() + labelString.length() > actualLimit)) { buf.append("...and "); buf.append(node.getLabel().size() - count); buf.append(" more items"); break; } } buf.append(labelString); count++; firstItem = false; } return buf.toString(); } }; factoredGraph.visitNodesBeforeEdges( new DotOutputVisitor>>(out, labelSerializer) { @Override public void beginVisit() { super.beginVisit(); // TODO(bazel-team): (2009) make this the default in Digraph. out.println(" node [shape=box];"); } @Override public void visitEdge(Node>> lhs, Node>> 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); } /** * Returns an equivalence relation for nodes in the specified graph. * *

Two nodes are considered equal iff they have equal topology (predecessors and successors). * * TODO(bazel-team): Make this a method of Digraph. */ @SuppressWarnings("ReferenceEquality") private static