blob: f07e54c673fd16fde707415555e3c56147251b95 [file] [log] [blame]
// Copyright 2015 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.packages;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.License.DistributionType;
import com.google.devtools.build.lib.packages.MakeEnvironment.Binding;
import com.google.devtools.build.lib.query2.proto.proto2api.Build;
import com.google.protobuf.CodedOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Functionality to serialize loaded packages. */
public class PackageSerializer {
public PackageSerializer() {}
/**
* Serialize a package to {@code out}. The inverse of {@link PackageDeserializer#deserialize}.
*
* <p>Writes pkg as a single
* {@link com.google.devtools.build.lib.query2.proto.proto2api.Build.Package} protocol buffer
* message followed by a series of
* {@link com.google.devtools.build.lib.query2.proto.proto2api.Build.TargetOrTerminator} messages
* encoding the targets.
*
* @param pkg the {@link Package} to be serialized
* @param codedOut the stream to pkg's serialized representation to
* @throws IOException on failure writing to {@code out}
*/
public void serialize(Package pkg, CodedOutputStream codedOut) throws IOException {
Build.Package.Builder builder = Build.Package.newBuilder();
builder.setName(pkg.getName());
builder.setRepository(pkg.getPackageIdentifier().getRepository().getName());
builder.setBuildFilePath(pkg.getFilename().getPathString());
// The extra bit is needed to handle the corner case when the default visibility is [], i.e.
// zero labels.
builder.setDefaultVisibilitySet(pkg.isDefaultVisibilitySet());
if (pkg.isDefaultVisibilitySet()) {
for (Label visibilityLabel : pkg.getDefaultVisibility().getDeclaredLabels()) {
builder.addDefaultVisibilityLabel(visibilityLabel.toString());
}
}
builder.setDefaultTestonly(pkg.getDefaultTestOnly());
if (pkg.getDefaultDeprecation() != null) {
builder.setDefaultDeprecation(pkg.getDefaultDeprecation());
}
for (String defaultCopt : pkg.getDefaultCopts()) {
builder.addDefaultCopt(defaultCopt);
}
if (pkg.isDefaultHdrsCheckSet()) {
builder.setDefaultHdrsCheck(pkg.getDefaultHdrsCheck());
}
builder.setDefaultLicense(serializeLicense(pkg.getDefaultLicense()));
for (DistributionType distributionType : pkg.getDefaultDistribs()) {
builder.addDefaultDistrib(distributionType.toString());
}
for (String feature : pkg.getFeatures()) {
builder.addDefaultSetting(feature);
}
for (Label subincludeLabel : pkg.getSubincludeLabels()) {
builder.addSubincludeLabel(subincludeLabel.toString());
}
for (Label skylarkLabel : pkg.getSkylarkFileDependencies()) {
builder.addSkylarkLabel(skylarkLabel.toString());
}
for (Build.MakeVar makeVar :
serializeMakeEnvironment(pkg.getMakeEnvironment())) {
builder.addMakeVariable(makeVar);
}
for (Event event : pkg.getEvents()) {
builder.addEvent(serializeEvent(event));
}
builder.setContainsErrors(pkg.containsErrors());
builder.setWorkspaceName(pkg.getWorkspaceName());
codedOut.writeMessageNoTag(builder.build());
// Targets are emitted separately as individual protocol buffers as to prevent overwhelming
// protocol buffer deserialization size limits.
emitTargets(pkg.getTargets(), codedOut);
}
private Build.Target serializeInputFile(InputFile inputFile) {
Build.SourceFile.Builder builder = Build.SourceFile.newBuilder();
builder.setName(inputFile.getLabel().getName());
if (inputFile.isVisibilitySpecified()) {
for (Label visibilityLabel : inputFile.getVisibility().getDeclaredLabels()) {
builder.addVisibilityLabel(visibilityLabel.toString());
}
}
if (inputFile.isLicenseSpecified()) {
builder.setLicense(serializeLicense(inputFile.getLicense()));
}
return Build.Target.newBuilder()
.setType(Build.Target.Discriminator.SOURCE_FILE)
.setSourceFile(builder.build())
.build();
}
private Build.Target serializePackageGroup(PackageGroup packageGroup) {
Build.PackageGroup.Builder builder = Build.PackageGroup.newBuilder();
builder.setName(packageGroup.getLabel().getName());
for (PackageSpecification packageSpecification : packageGroup.getPackageSpecifications()) {
builder.addContainedPackage(packageSpecification.toString());
}
for (Label include : packageGroup.getIncludes()) {
builder.addIncludedPackageGroup(include.toString());
}
return Build.Target.newBuilder()
.setType(Build.Target.Discriminator.PACKAGE_GROUP)
.setPackageGroup(builder.build())
.build();
}
public Build.Target serializeRule(Rule rule) {
Build.Rule.Builder builder = Build.Rule.newBuilder();
builder.setName(rule.getLabel().getName());
builder.setRuleClass(rule.getRuleClass());
builder.setPublicByDefault(rule.getRuleClassObject().isPublicByDefault());
for (Attribute attribute : rule.getAttributes()) {
builder.addAttribute(
AttributeSerializer.getAttributeProto(
attribute,
AttributeSerializer.getAttributeValues(rule, attribute),
rule.isAttributeValueExplicitlySpecified(attribute),
/*includeGlobs=*/ true));
}
maybeSerializeAdditionalDataForRule(rule, builder);
return Build.Target.newBuilder()
.setType(Build.Target.Discriminator.RULE)
.setRule(builder.build())
.build();
}
private static List<Build.MakeVar> serializeMakeEnvironment(MakeEnvironment makeEnv) {
List<Build.MakeVar> result = new ArrayList<>();
for (Map.Entry<String, ImmutableList<Binding>> var : makeEnv.getBindings().entrySet()) {
Build.MakeVar.Builder varPb = Build.MakeVar.newBuilder();
varPb.setName(var.getKey());
for (Binding binding : var.getValue()) {
Build.MakeVarBinding.Builder bindingPb = Build.MakeVarBinding.newBuilder();
bindingPb.setValue(binding.getValue());
bindingPb.setPlatformSetRegexp(binding.getPlatformSetRegexp());
varPb.addBinding(bindingPb);
}
result.add(varPb.build());
}
return result;
}
private static Build.License serializeLicense(License license) {
Build.License.Builder result = Build.License.newBuilder();
for (License.LicenseType licenseType : license.getLicenseTypes()) {
result.addLicenseType(licenseType.toString());
}
for (Label exception : license.getExceptions()) {
result.addException(exception.toString());
}
return result.build();
}
private Build.Event serializeEvent(Event event) {
Build.Event.Builder result = Build.Event.newBuilder();
result.setMessage(event.getMessage());
Build.Event.EventKind kind;
switch (event.getKind()) {
case ERROR:
kind = Build.Event.EventKind.ERROR;
break;
case WARNING:
kind = Build.Event.EventKind.WARNING;
break;
case INFO:
kind = Build.Event.EventKind.INFO;
break;
case PROGRESS:
kind = Build.Event.EventKind.PROGRESS;
break;
default: throw new IllegalArgumentException("unexpected event type: " + event.getKind());
}
result.setKind(kind);
return result.build();
}
/** Writes targets as a series of separate TargetOrTerminator messages to out. */
private void emitTargets(Collection<Target> targets, CodedOutputStream codedOut)
throws IOException {
for (Target target : targets) {
if (target instanceof InputFile) {
emitTarget(serializeInputFile((InputFile) target), codedOut);
} else if (target instanceof OutputFile) {
// Output files are not serialized; they are recreated by the RuleClass on deserialization.
} else if (target instanceof PackageGroup) {
emitTarget(serializePackageGroup((PackageGroup) target), codedOut);
} else if (target instanceof Rule) {
emitTarget(serializeRule((Rule) target), codedOut);
}
}
// Terminate stream with isTerminator = true.
codedOut.writeMessageNoTag(Build.TargetOrTerminator.newBuilder()
.setIsTerminator(true)
.build());
}
private static void emitTarget(Build.Target target, CodedOutputStream codedOut)
throws IOException {
codedOut.writeMessageNoTag(Build.TargetOrTerminator.newBuilder()
.setTarget(target)
.build());
}
private static void maybeSerializeAdditionalDataForRule(Rule rule, Build.Rule.Builder builder) {
if (rule.getRuleClassObject().isSkylark()) {
builder.setIsSkylark(true);
// We explicitly serialize the implicit output files for this rule so that we can recreate
// them on deserialization via our fake placeholder rule class's
// RuleClass#getImplicitOutputsFunction. Note that since explicit outputs are already handled
// via the serialization of attributes with type OUTPUT or OUTPUT_LIST we don't bother with
// those.
Collection<OutputFile> outputsFromAttributes = rule.getOutputFileMap().values();
Set<Label> outputLabelsFromAttributes =
Sets.newHashSetWithExpectedSize(outputsFromAttributes.size());
for (OutputFile outputFile : outputsFromAttributes) {
outputLabelsFromAttributes.add(outputFile.getLabel());
}
for (OutputFile outputFile : rule.getOutputFiles()) {
Label label = outputFile.getLabel();
if (!outputLabelsFromAttributes.contains(label)) {
// Two important notes here:
// (i) We are co-opting the otherwise unused rule_output field.
// (ii) We serialize with the name of the label because the logic in
// Rule#populateImplicitOutputFiles assumes the labels aren't absolute. This is nice
// anyways because we don't wastefully serialize the package name.
builder.addRuleOutput(label.getName());
}
}
}
}
}