// 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.packages; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.events.NullEventHandler; import com.google.devtools.build.lib.events.StoredEventHandler; import com.google.devtools.build.lib.packages.License.DistributionType; import com.google.devtools.build.lib.packages.License.LicenseParsingException; import com.google.devtools.build.lib.packages.Package.Builder.GeneratedLabelConflict; import com.google.devtools.build.lib.packages.Package.NameConflictException; import com.google.devtools.build.lib.packages.RuleClass.ParsedAttributeValue; import com.google.devtools.build.lib.query2.proto.proto2api.Build; import com.google.devtools.build.lib.query2.proto.proto2api.Build.StringDictUnaryEntry; import com.google.devtools.build.lib.syntax.GlobCriteria; import com.google.devtools.build.lib.syntax.GlobList; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; /** * Functionality to deserialize loaded packages. */ public class PackageDeserializer { private static final Logger LOG = Logger.getLogger(PackageDeserializer.class.getName()); /** * Provides the deserializer with tools it needs to build a package from its serialized form. */ public interface PackageDeserializationEnvironment { /** Converts the serialized package's path string into a {@link Path} object. */ Path getPath(String buildFilePath); /** Returns a {@link RuleClass} object for the serialized rule. */ RuleClass getRuleClass(Build.Rule rulePb, Location ruleLocation); } // Workaround for Java serialization making it tough to pass in a deserialization environment // manually. // volatile is needed to ensure that the objects are published safely. // TODO(bazel-team): Subclass ObjectOutputStream to pass this through instead. public static volatile PackageDeserializationEnvironment defaultPackageDeserializationEnvironment; // Cache label deserialization across all instances- PackgeDeserializers need to be created on // demand due to initialiation constraints wrt the setting of static members. private static final LoadingCache labelCache = CacheBuilder.newBuilder() .weakValues() .build( new CacheLoader() { @Override public Label load(String labelString) throws PackageDeserializationException { try { return Label.parseAbsolute(labelString); } catch (LabelSyntaxException e) { throw new PackageDeserializationException("Invalid label: " + e.getMessage(), e); } } }); /** Class encapsulating state for a single package deserialization. */ private static class DeserializationContext { private final Package.Builder packageBuilder; public DeserializationContext(Package.Builder packageBuilder) { this.packageBuilder = packageBuilder; } } private final PackageDeserializationEnvironment packageDeserializationEnvironment; /** * Creates a {@link PackageDeserializer} using {@link #defaultPackageDeserializationEnvironment}. */ public PackageDeserializer() { this.packageDeserializationEnvironment = defaultPackageDeserializationEnvironment; } public PackageDeserializer(PackageDeserializationEnvironment packageDeserializationEnvironment) { this.packageDeserializationEnvironment = Preconditions.checkNotNull(packageDeserializationEnvironment); } private static ParsedAttributeValue deserializeAttribute( Type expectedType, Build.Attribute attrPb) throws PackageDeserializationException { Object value = deserializeAttributeValue(expectedType, attrPb); return new ParsedAttributeValue( attrPb.hasExplicitlySpecified() && attrPb.getExplicitlySpecified(), value, EmptyLocation.INSTANCE); } private void deserializeInputFile(DeserializationContext context, Build.SourceFile sourceFile) throws PackageDeserializationException { InputFile inputFile; try { inputFile = context.packageBuilder.createInputFile( deserializeLabel(sourceFile.getName()).getName(), EmptyLocation.INSTANCE); } catch (GeneratedLabelConflict e) { throw new PackageDeserializationException(e); } if (!sourceFile.getVisibilityLabelList().isEmpty() || sourceFile.hasLicense()) { context.packageBuilder.setVisibilityAndLicense(inputFile, PackageFactory.getVisibility(deserializeLabels(sourceFile.getVisibilityLabelList())), deserializeLicense(sourceFile.getLicense())); } } private void deserializePackageGroup(DeserializationContext context, Build.PackageGroup packageGroupPb) throws PackageDeserializationException { List specifications = new ArrayList<>(); for (String containedPackage : packageGroupPb.getContainedPackageList()) { specifications.add("//" + containedPackage); } try { context.packageBuilder.addPackageGroup( deserializeLabel(packageGroupPb.getName()).getName(), specifications, deserializeLabels(packageGroupPb.getIncludedPackageGroupList()), NullEventHandler.INSTANCE, // TODO(bazel-team): Handle errors properly EmptyLocation.INSTANCE); } catch (LabelSyntaxException | Package.NameConflictException e) { throw new PackageDeserializationException(e); } } private void deserializeRule(DeserializationContext context, Build.Rule rulePb) throws PackageDeserializationException, InterruptedException { Location ruleLocation = EmptyLocation.INSTANCE; RuleClass ruleClass = packageDeserializationEnvironment.getRuleClass(rulePb, ruleLocation); Map attributeValues = new HashMap<>(); for (Build.Attribute attrPb : rulePb.getAttributeList()) { Type type = ruleClass.getAttributeByName(attrPb.getName()).getType(); attributeValues.put(attrPb.getName(), deserializeAttribute(type, attrPb)); } Label ruleLabel = deserializeLabel(rulePb.getName()); try { Rule rule = ruleClass.createRuleWithParsedAttributeValues( ruleLabel, context.packageBuilder, ruleLocation, attributeValues, NullEventHandler.INSTANCE, new AttributeContainerWithoutLocation(ruleClass)); context.packageBuilder.addRule(rule); Preconditions.checkState(!rule.containsErrors()); } catch (NameConflictException | LabelSyntaxException e) { throw new PackageDeserializationException(e); } } /** "Empty" location implementation, all methods should return non-null, but empty, values. */ private static class EmptyLocation extends Location { private static final EmptyLocation INSTANCE = new EmptyLocation(); private static final PathFragment DEV_NULL = new PathFragment("/dev/null"); private static final LineAndColumn EMPTY_LINE_AND_COLUMN = new LineAndColumn(0, 0); private EmptyLocation() { super(0, 0); } @Override public PathFragment getPath() { return DEV_NULL; } @Override public LineAndColumn getStartLineAndColumn() { return EMPTY_LINE_AND_COLUMN; } @Override public LineAndColumn getEndLineAndColumn() { return EMPTY_LINE_AND_COLUMN; } @Override public int hashCode() { return 42; } @Override public boolean equals(Object other) { return other instanceof EmptyLocation; } } /** * Exception thrown when something goes wrong during package deserialization. */ public static class PackageDeserializationException extends Exception { private PackageDeserializationException(String message) { super(message); } private PackageDeserializationException(String message, Exception reason) { super(message, reason); } private PackageDeserializationException(Exception reason) { super(reason); } } private static Label deserializeLabel(String labelName) throws PackageDeserializationException { try { return labelCache.get(labelName); } catch (ExecutionException e) { Throwables.propagateIfInstanceOf(e.getCause(), PackageDeserializationException.class); throw new IllegalStateException("Failed to decode label: " + labelName, e); } } private static List