blob: 2cc49f0c5bc9c41aaffeee333faa7a40fb5a0a9e [file] [log] [blame]
// 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.base.Preconditions;
import com.google.common.collect.ImmutableMultimap;
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.FakeSubincludeTarget;
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.output.AspectResolver.BuildFileDependencyMode;
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.Builder;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.SourceFile;
import com.google.devtools.build.lib.syntax.Environment;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 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 boolean relativeLocations = false;
protected boolean includeDefaultValues = true;
protected void setDependencyFilter(QueryOptions options) {
this.dependencyFilter = OutputFormatter.getDependencyFilter(options);
}
@Override
public String getName() {
return "proto";
}
@Override
public void setOptions(QueryOptions options, AspectResolver aspectResolver) {
super.setOptions(options, aspectResolver);
this.relativeLocations = options.relativeLocations;
this.includeDefaultValues = options.protoIncludeDefaultValues;
}
@Override
public OutputFormatterCallback<Target> createPostFactoStreamCallback(
final PrintStream out, final QueryOptions options) {
return new OutputFormatterCallback<Target>() {
private Builder queryResult;
@Override
public void start() {
queryResult = Build.QueryResult.newBuilder();
}
@Override
protected void processOutput(Iterable<Target> partialResult)
throws IOException, InterruptedException {
for (Target target : partialResult) {
queryResult.addTarget(toTargetProtoBuffer(target));
}
}
@Override
public void close() throws IOException {
queryResult.build().writeTo(out);
}
};
}
@Override
public OutputFormatterCallback<Target> createStreamCallback(
PrintStream out, QueryOptions options, QueryEnvironment<?> env) {
return createPostFactoStreamCallback(out, options);
}
private static Iterable<Target> getSortedLabels(Digraph<Target> result) {
return Iterables.transform(
result.getTopologicalOrder(new TargetOrdering()), EXTRACT_NODE_LABEL);
}
@Override
protected Iterable<Target> getOrderedTargets(Digraph<Target> result, QueryOptions options) {
return options.orderOutput == OrderOutput.FULL ? getSortedLabels(result) : result.getLabels();
}
/**
* Converts a logical {@link Target} object into a {@link Build.Target} protobuffer.
*/
protected 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);
}
Map<Attribute, Build.Attribute> serializedAttributes = Maps.newHashMap();
for (Attribute attr : rule.getAttributes()) {
if (!includeDefaultValues && !rule.isAttributeValueExplicitlySpecified(attr)
|| !includeAttribute(rule, attr)) {
continue;
}
Iterable<Object> possibleAttributeValues =
AggregatingAttributeMapper.of(rule).getPossibleAttributeValues(rule, attr);
Object flattenedAttributeValue =
AggregatingAttributeMapper.flattenAttributeValues(
attr.getType(), possibleAttributeValues);
Build.Attribute serializedAttribute =
AttributeFormatter.getAttributeProto(
attr,
flattenedAttributeValue,
rule.isAttributeValueExplicitlySpecified(attr),
/*encodeBooleanAndTriStateAsIntegerAndString=*/ true);
rulePb.addAttribute(serializedAttribute);
serializedAttributes.put(attr, serializedAttribute);
}
postProcess(rule, rulePb, serializedAttributes);
Environment env = rule.getRuleClassObject().getRuleDefinitionEnvironment();
if (env != 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(
com.google.devtools.build.lib.syntax.Type.STRING))
.setStringValue(env.getTransitiveContentHashCode()));
}
ImmutableMultimap<Attribute, Label> aspectsDependencies =
aspectResolver.computeAspectDependencies(target, dependencyFilter);
// Add information about additional attributes from aspects.
for (Entry<Attribute, Collection<Label>> entry : aspectsDependencies.asMap().entrySet()) {
Attribute attribute = entry.getKey();
Collection<Label> labels = entry.getValue();
if (!includeAspectAttribute(attribute, labels)) {
continue;
}
Object attributeValue = getAspectAttributeValue(attribute, labels);
Build.Attribute serializedAttribute =
AttributeFormatter.getAttributeProto(
attribute,
attributeValue,
/*explicitlySpecified=*/ false,
/*encodeBooleanAndTriStateAsIntegerAndString=*/ true);
rulePb.addAttribute(serializedAttribute);
}
if (includeRuleInputsAndOutputs()) {
// Add all deps from aspects as rule inputs of current target.
for (Label label : aspectsDependencies.values()) {
rulePb.addRuleInput(label.toString());
}
// Include explicit elements for all direct inputs and outputs of a rule;
// this goes beyond what is available from the attributes above, since it
// may also (depending on options) include implicit outputs,
// host-configuration outputs, and default values.
for (Label label : rule.getLabels(dependencyFilter)) {
rulePb.addRuleInput(label.toString());
}
for (OutputFile outputFile : rule.getOutputFiles()) {
Label fileLabel = outputFile.getLabel();
rulePb.addRuleOutput(fileLabel.toString());
}
}
for (String feature : rule.getFeatures()) {
rulePb.addDefaultSetting(feature);
}
targetPb.setType(RULE);
targetPb.setRule(rulePb);
} else if (target instanceof OutputFile) {
OutputFile outputFile = (OutputFile) target;
Label label = outputFile.getLabel();
Rule generatingRule = outputFile.getGeneratingRule();
GeneratedFile.Builder output =
GeneratedFile.newBuilder()
.setGeneratingRule(generatingRule.getLabel().toString())
.setName(label.toString());
if (includeLocation()) {
output.setLocation(location);
}
targetPb.setType(GENERATED_FILE);
targetPb.setGeneratedFile(output.build());
} else if (target instanceof InputFile) {
InputFile inputFile = (InputFile) target;
Label label = inputFile.getLabel();
Build.SourceFile.Builder input = Build.SourceFile.newBuilder()
.setName(label.toString());
if (includeLocation()) {
input.setLocation(location);
}
if (inputFile.getName().equals("BUILD")) {
Set<Label> subincludeLabels = new LinkedHashSet<>();
subincludeLabels.addAll(aspectResolver == null
? inputFile.getPackage().getSubincludeLabels()
: aspectResolver.computeBuildFileDependencies(
inputFile.getPackage(), BuildFileDependencyMode.SUBINCLUDE));
subincludeLabels.addAll(aspectResolver == null
? inputFile.getPackage().getSkylarkFileDependencies()
: aspectResolver.computeBuildFileDependencies(
inputFile.getPackage(), BuildFileDependencyMode.SKYLARK));
for (Label skylarkFileDep : subincludeLabels) {
input.addSubinclude(skylarkFileDep.toString());
}
for (String feature : inputFile.getPackage().getFeatures()) {
input.addFeature(feature);
}
input.setPackageContainsErrors(inputFile.getPackage().containsErrors());
}
for (Label visibilityDependency : target.getVisibility().getDependencyLabels()) {
input.addPackageGroup(visibilityDependency.toString());
}
for (Label visibilityDeclaration : target.getVisibility().getDeclaredLabels()) {
input.addVisibilityLabel(visibilityDeclaration.toString());
}
targetPb.setType(SOURCE_FILE);
targetPb.setSourceFile(input);
} else if (target instanceof FakeSubincludeTarget) {
Label label = target.getLabel();
SourceFile.Builder input = SourceFile.newBuilder()
.setName(label.toString());
if (includeLocation()) {
input.setLocation(location);
}
targetPb.setType(SOURCE_FILE);
targetPb.setSourceFile(input.build());
} else if (target instanceof PackageGroup) {
PackageGroup packageGroup = (PackageGroup) target;
Build.PackageGroup.Builder packageGroupPb = Build.PackageGroup.newBuilder()
.setName(packageGroup.getLabel().toString());
for (String containedPackage : packageGroup.getContainedPackages()) {
packageGroupPb.addContainedPackage(containedPackage);
}
for (Label include : packageGroup.getIncludes()) {
packageGroupPb.addIncludedPackageGroup(include.toString());
}
targetPb.setType(PACKAGE_GROUP);
targetPb.setPackageGroup(packageGroupPb);
} else if (target instanceof EnvironmentGroup) {
EnvironmentGroup envGroup = (EnvironmentGroup) target;
Build.EnvironmentGroup.Builder envGroupPb =
Build.EnvironmentGroup
.newBuilder()
.setName(envGroup.getLabel().toString());
for (Label env : envGroup.getEnvironments()) {
envGroupPb.addEnvironment(env.toString());
}
for (Label defaultEnv : envGroup.getDefaults()) {
envGroupPb.addDefault(defaultEnv.toString());
}
targetPb.setType(ENVIRONMENT_GROUP);
targetPb.setEnvironmentGroup(envGroupPb);
} else {
throw new IllegalArgumentException(target.toString());
}
return targetPb.build();
}
private static Object getAspectAttributeValue(Attribute attribute, Collection<Label> labels) {
com.google.devtools.build.lib.syntax.Type<?> attributeType = attribute.getType();
if (attributeType.equals(BuildType.LABEL)) {
Preconditions.checkState(labels.size() == 1, "attribute=%s, labels=%s", attribute, labels);
return Iterables.getOnlyElement(labels);
} else {
Preconditions.checkState(
attributeType.equals(BuildType.LABEL_LIST),
"attribute=%s, type=%s, labels=%s",
attribute,
attributeType,
labels);
return labels;
}
}
/** Further customize the proto output */
protected void postProcess(Rule rule, Build.Rule.Builder rulePb, Map<Attribute,
Build.Attribute> serializedAttributes) { }
/** Filter out some attributes */
protected boolean includeAttribute(Rule rule, Attribute attr) {
return true;
}
/** Allow filtering of aspect attributes. */
protected boolean includeAspectAttribute(Attribute attr, Collection<Label> value) {
return true;
}
protected boolean includeRuleDefinitionEnvironment() {
return true;
}
protected boolean includeRuleInputsAndOutputs() {
return true;
}
protected boolean includeLocation() {
return true;
}
}