// 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 static com.google.devtools.build.lib.query2.proto.proto2api.Build.Target.Discriminator.ENVIRONMENT_GROUP; import static com.google.devtools.build.lib.query2.proto.proto2api.Build.Target.Discriminator.GENERATED_FILE; import static com.google.devtools.build.lib.query2.proto.proto2api.Build.Target.Discriminator.PACKAGE_GROUP; import static com.google.devtools.build.lib.query2.proto.proto2api.Build.Target.Discriminator.RULE; import static com.google.devtools.build.lib.query2.proto.proto2api.Build.Target.Discriminator.SOURCE_FILE; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; 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.graph.Digraph; import com.google.devtools.build.lib.packages.AggregatingAttributeMapper; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.AttributeFormatter; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.EnvironmentGroup; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.OutputFile; import com.google.devtools.build.lib.packages.PackageGroup; import com.google.devtools.build.lib.packages.ProtoUtils; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.query2.CommonQueryOptions; import com.google.devtools.build.lib.query2.FakeLoadTarget; 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 com.google.devtools.build.lib.query2.output.OutputFormatter.AbstractUnorderedFormatter; import com.google.devtools.build.lib.query2.output.QueryOptions.OrderOutput; import com.google.devtools.build.lib.query2.proto.proto2api.Build; import com.google.devtools.build.lib.query2.proto.proto2api.Build.GeneratedFile; import com.google.devtools.build.lib.query2.proto.proto2api.Build.QueryResult; import com.google.devtools.build.lib.query2.proto.proto2api.Build.SourceFile; import com.google.devtools.build.lib.syntax.Type; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.annotation.Nullable; /** * An output formatter that outputs a protocol buffer representation * of a query result and outputs the proto bytes to the output print stream. * By taking the bytes and calling {@code mergeFrom()} on a * {@code Build.QueryResult} object the full result can be reconstructed. */ public class ProtoOutputFormatter extends AbstractUnorderedFormatter { /** * A special attribute name for the rule implementation hash code. */ public static final String RULE_IMPLEMENTATION_HASH_ATTR_NAME = "$rule_implementation_hash"; private static final Comparator ATTRIBUTE_NAME = Comparator.comparing(Build.Attribute::getName); @SuppressWarnings("unchecked") private static final ImmutableSet> SCALAR_TYPES = ImmutableSet.>of( Type.INTEGER, Type.STRING, BuildType.LABEL, BuildType.NODEP_LABEL, BuildType.OUTPUT, Type.BOOLEAN, BuildType.TRISTATE, BuildType.LICENSE); private boolean relativeLocations = false; protected boolean includeDefaultValues = true; private Predicate ruleAttributePredicate = Predicates.alwaysTrue(); private boolean flattenSelects = true; protected void setDependencyFilter(QueryOptions options) { this.dependencyFilter = OutputFormatter.getDependencyFilter(options); } @Override public String getName() { return "proto"; } @Override public void setOptions(CommonQueryOptions options, AspectResolver aspectResolver) { super.setOptions(options, aspectResolver); this.relativeLocations = options.relativeLocations; this.includeDefaultValues = options.protoIncludeDefaultValues; this.ruleAttributePredicate = newAttributePredicate(options.protoOutputRuleAttributes); this.flattenSelects = options.protoFlattenSelects; } private static Predicate newAttributePredicate(List outputAttributes) { if (outputAttributes.equals(ImmutableList.of("all"))) { return Predicates.alwaysTrue(); } else if (outputAttributes.isEmpty()) { return Predicates.alwaysFalse(); } else { return Predicates.in(ImmutableSet.copyOf(outputAttributes)); } } @Override public OutputFormatterCallback createPostFactoStreamCallback( final OutputStream out, final QueryOptions options) { return new OutputFormatterCallback() { private QueryResult.Builder queryResult; @Override public void start() { queryResult = Build.QueryResult.newBuilder(); } @Override public void processOutput(Iterable partialResult) throws IOException, InterruptedException { for (Target target : partialResult) { queryResult.addTarget(toTargetProtoBuffer(target)); } } @Override public void close(boolean failFast) throws IOException { if (!failFast) { queryResult.build().writeTo(out); } } }; } @Override public ThreadSafeOutputFormatterCallback createStreamCallback( OutputStream out, QueryOptions options, QueryEnvironment env) { return createStreamCallback(out, options); } @VisibleForTesting public ThreadSafeOutputFormatterCallback createStreamCallback( OutputStream out, QueryOptions options) { return new SynchronizedDelegatingOutputFormatterCallback<>( createPostFactoStreamCallback(out, options)); } private static Iterable getSortedLabels(Digraph result) { return Iterables.transform( result.getTopologicalOrder(new TargetOrdering()), EXTRACT_NODE_LABEL); } @Override protected Iterable getOrderedTargets(Digraph result, QueryOptions options) { return options.orderOutput == OrderOutput.FULL ? getSortedLabels(result) : result.getLabels(); } /** Converts a logical {@link Target} object into a {@link Build.Target} protobuffer. */ @VisibleForTesting public Build.Target toTargetProtoBuffer(Target target) throws InterruptedException { Build.Target.Builder targetPb = Build.Target.newBuilder(); String location = getLocation(target, relativeLocations); if (target instanceof Rule) { Rule rule = (Rule) target; Build.Rule.Builder rulePb = Build.Rule.newBuilder() .setName(rule.getLabel().toString()) .setRuleClass(rule.getRuleClass()); if (includeLocation()) { rulePb.setLocation(location); } addAttributes(rulePb, rule); String transitiveHashCode = rule.getRuleClassObject().getRuleDefinitionEnvironmentHashCode(); if (transitiveHashCode != null && includeRuleDefinitionEnvironment()) { // The RuleDefinitionEnvironment is always defined for Skylark rules and // always null for non Skylark rules. rulePb.addAttribute( Build.Attribute.newBuilder() .setName(RULE_IMPLEMENTATION_HASH_ATTR_NAME) .setType(ProtoUtils.getDiscriminatorFromType(Type.STRING)) .setStringValue(transitiveHashCode)); } ImmutableMultimap aspectsDependencies = aspectResolver.computeAspectDependencies(target, dependencyFilter); // Add information about additional attributes from aspects. List attributes = new ArrayList<>(aspectsDependencies.asMap().size()); for (Map.Entry> entry : aspectsDependencies.asMap().entrySet()) { Attribute attribute = entry.getKey(); Collection