| // 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.query.output; |
| |
| import com.google.common.base.Ascii; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.License; |
| 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 com.google.devtools.build.lib.packages.TriState; |
| import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback; |
| import com.google.devtools.build.lib.query2.engine.QueryEnvironment; |
| import com.google.devtools.build.lib.query2.engine.SynchronizedDelegatingOutputFormatterCallback; |
| import com.google.devtools.build.lib.query2.engine.ThreadSafeOutputFormatterCallback; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.BiPredicate; |
| import net.starlark.java.eval.Printer; |
| import net.starlark.java.eval.StarlarkThread; |
| |
| /** |
| * 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. |
| */ |
| public class BuildOutputFormatter extends AbstractUnorderedFormatter { |
| |
| /** |
| * Generic interface for determining the possible values for a given attribute on a given rule, as |
| * precisely as the implementation knows. For example, cquery knows which branch a <code>select |
| * </code> takes, while query doesn't. |
| */ |
| public interface AttributeReader { |
| Iterable<Object> getPossibleValues(Rule rule, Attribute attr); |
| } |
| |
| /** |
| * Generic logic for formatting a target BUILD-style. |
| * |
| * <p>This logic is implementation-agnostic. So it can be shared across query, cquery, and other |
| * implementations. |
| */ |
| public static class TargetOutputter { |
| private final Writer writer; |
| private final BiPredicate<Rule, Attribute> preserveSelect; |
| private final String lineTerm; |
| private final Set<Label> printed = CompactHashSet.create(); |
| |
| /** |
| * @param writer where to write output |
| * @param preserveSelect a predicate that determines if the given attribute on the given rule is |
| * a <code>select</code> that should be output as-is (without figuring out which branch it |
| * takes). This is useful for implementations that can't evaluate <code>select</code>. |
| * @param lineTerm character to add to the end of each line |
| */ |
| public TargetOutputter( |
| Writer writer, BiPredicate<Rule, Attribute> preserveSelect, String lineTerm) { |
| this.writer = writer; |
| this.preserveSelect = preserveSelect; |
| this.lineTerm = lineTerm; |
| } |
| |
| /** Outputs a given target in BUILD-style syntax. */ |
| public void output(Target target, AttributeReader attrReader) throws IOException { |
| Rule rule = target.getAssociatedRule(); |
| if (rule == null || printed.contains(rule.getLabel())) { |
| return; |
| } |
| outputRule(rule, attrReader, writer); |
| printed.add(rule.getLabel()); |
| } |
| |
| /** Outputs a given rule in BUILD-style syntax. */ |
| private void outputRule(Rule rule, AttributeReader attrReader, Writer writer) |
| throws IOException { |
| // TODO(b/151151653): display the filenames in root-relative form. |
| // This is an incompatible change, but Blaze users (and their editors) |
| // must already be equipped to handle relative paths as all compiler |
| // error messages are execroot-relative. |
| |
| writer.append("# ").append(rule.getLocation().toString()).append(lineTerm); |
| writer.append(rule.getRuleClass()).append("(").append(lineTerm); |
| writer.append(" name = \"").append(rule.getName()).append("\",").append(lineTerm); |
| |
| for (Attribute attr : rule.getAttributes()) { |
| // Ignore the "name" attribute here, as we already print it above. |
| // This is not strictly necessary, but convention has it that the |
| // name attribute is printed first. |
| if ("name".equals(attr.getName())) { |
| continue; |
| } |
| if (preserveSelect.test(rule, attr)) { |
| writer |
| .append(" ") |
| .append(attr.getPublicName()) |
| .append(" = ") |
| .append(reconstructSelect(rule, attr)) |
| .append(",") |
| .append(lineTerm); |
| continue; |
| } |
| AttributeValueSource attributeValueSource = |
| AttributeValueSource.forRuleAndAttribute(rule, attr); |
| if (attributeValueSource != AttributeValueSource.RULE) { |
| continue; // Don't print default values. |
| } |
| |
| Iterable<Object> values = attrReader.getPossibleValues(rule, attr); |
| if (Iterables.size(values) != 1) { |
| // Computed defaults that depend on configurable attributes can have multiple values. |
| continue; |
| } |
| writer |
| .append(" ") |
| .append(attr.getPublicName()) |
| .append(" = ") |
| .append(outputRawAttrValue(Iterables.getOnlyElement(values))) |
| .append(",") |
| .append(lineTerm); |
| } |
| writer.append(")").append(lineTerm); |
| |
| // Display the instantiation stack, if any. |
| appendStack( |
| String.format("# Rule %s instantiated at (most recent call last):", rule.getName()), |
| rule.getCallStack().toList()); |
| |
| // Display the stack of the rule class definition, if any. |
| appendStack( |
| String.format("# Rule %s defined at (most recent call last):", rule.getRuleClass()), |
| rule.getRuleClassObject().getCallStack()); |
| |
| // TODO(adonovan): also list inputs and outputs of the rule. |
| |
| writer.append(lineTerm); |
| } |
| |
| private void appendStack(String title, List<StarlarkThread.CallStackEntry> stack) |
| throws IOException { |
| // For readability, ensure columns line up. |
| int maxLocLen = 0; |
| for (StarlarkThread.CallStackEntry fr : stack) { |
| maxLocLen = Math.max(maxLocLen, fr.location.toString().length()); |
| } |
| if (maxLocLen > 0) { |
| writer.append(title).append(lineTerm); |
| for (StarlarkThread.CallStackEntry fr : stack) { |
| String loc = fr.location.toString(); // TODO(b/151151653): display root-relative |
| // Java's String.format doesn't support |
| // right-padding with %*s, so we must loop. |
| writer.append("# ").append(loc); |
| for (int i = loc.length(); i < maxLocLen; i++) { |
| writer.append(' '); |
| } |
| writer.append(" in ").append(fr.name).append(lineTerm); |
| } |
| } |
| } |
| |
| /** Outputs the given attribute value BUILD-style. Does not support selects. */ |
| private String outputRawAttrValue(Object value) { |
| if (value instanceof License) { |
| List<String> licenseTypes = new ArrayList<>(); |
| for (License.LicenseType licenseType : ((License) value).getLicenseTypes()) { |
| licenseTypes.add(Ascii.toLowerCase(licenseType.toString())); |
| } |
| value = licenseTypes; |
| } else if (value instanceof TriState) { |
| value = ((TriState) value).toInt(); |
| } |
| return new Printer() { |
| // Print labels in their canonical form. |
| @Override |
| public Printer repr(Object o) { |
| return super.repr(o instanceof Label ? ((Label) o).getCanonicalForm() : o); |
| } |
| }.repr(value).toString(); |
| } |
| |
| /** |
| * Outputs a <code>select</code> BUILD-style without trying to resolve which branch it takes. |
| */ |
| private String reconstructSelect(Rule rule, Attribute attr) { |
| List<String> selectors = new ArrayList<>(); |
| RawAttributeMapper attributeMap = RawAttributeMapper.of(rule); |
| for (BuildType.Selector<?> selector : |
| ((BuildType.SelectorList<?>) attributeMap.getRawAttributeValue(rule, attr)) |
| .getSelectors()) { |
| if (selector.isUnconditional()) { |
| selectors.add( |
| outputRawAttrValue( |
| Iterables.getOnlyElement(selector.getEntries().entrySet()).getValue())); |
| } else { |
| selectors.add(String.format("select(%s)", outputRawAttrValue(selector.getEntries()))); |
| } |
| } |
| return String.join(" + ", selectors); |
| } |
| } |
| |
| /** Query's implementation. */ |
| @Override |
| public OutputFormatterCallback<Target> createPostFactoStreamCallback( |
| OutputStream out, final QueryOptions options) { |
| return new BuildOutputFormatterCallback(out, options.getLineTerminator()); |
| } |
| |
| @Override |
| public ThreadSafeOutputFormatterCallback<Target> createStreamCallback( |
| OutputStream out, QueryOptions options, QueryEnvironment<?> env) { |
| return new SynchronizedDelegatingOutputFormatterCallback<>( |
| createPostFactoStreamCallback(out, options)); |
| } |
| |
| private static class BuildOutputFormatterCallback extends TextOutputFormatterCallback<Target> { |
| private final TargetOutputter targetOutputter; |
| |
| BuildOutputFormatterCallback(OutputStream out, String lineTerm) { |
| super(out); |
| this.targetOutputter = |
| new TargetOutputter( |
| writer, |
| (rule, attr) -> RawAttributeMapper.of(rule).isConfigurable(attr.getName()), |
| lineTerm); |
| } |
| |
| @Override |
| public void processOutput(Iterable<Target> partialResult) throws IOException { |
| for (Target target : partialResult) { |
| targetOutputter.output( |
| target, |
| // Multiple possible values are ignored by the outputter. |
| (rule, attr) -> |
| PossibleAttributeValues.forRuleAndAttribute( |
| rule, attr, /*mayTreatMultipleAsNone=*/ true)); |
| } |
| } |
| } |
| |
| @Override |
| public String getName() { |
| return "build"; |
| } |
| } |