| // 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.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableList.Builder; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Interner; |
| import com.google.common.collect.Interners; |
| import com.google.common.collect.Sets; |
| import com.google.common.hash.HashCode; |
| import com.google.common.hash.Hasher; |
| import com.google.common.hash.Hashing; |
| 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.EventHandler; |
| 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.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.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * 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); |
| |
| /** Description of what rule attributes of each rule should be deserialized. */ |
| AttributesToDeserialize attributesToDeserialize(); |
| } |
| |
| /** |
| * A class that defines what attributes to keep after deserialization. Note that all attributes of |
| * type label are kept in order to navigate between dependencies. |
| * |
| * <p>If {@code addSyntheticAttributeHash} is {@code true}, a synthetic attribute is added to each |
| * Rule that contains a stable hash of the entire serialized rule for the sake of permitting |
| * equality comparisons that respect the attributes that were dropped according to {@code |
| * attributesToKeep}. |
| */ |
| public static class AttributesToDeserialize { |
| |
| private final boolean addSyntheticAttributeHash; |
| private final Predicate<String> shouldKeepAttributeWithName; |
| |
| public AttributesToDeserialize(boolean addSyntheticAttributeHash, |
| Predicate<String> shouldKeepAttributeWithName) { |
| this.addSyntheticAttributeHash = addSyntheticAttributeHash; |
| this.shouldKeepAttributeWithName = shouldKeepAttributeWithName; |
| } |
| |
| public boolean includeAttribute(String attr) { return shouldKeepAttributeWithName.apply(attr); } |
| } |
| |
| public static final AttributesToDeserialize DESERIALIZE_ALL_ATTRS = |
| new AttributesToDeserialize(false, Predicates.<String>alwaysTrue()); |
| |
| // 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 Interner<Label> LABEL_INTERNER = Interners.newWeakInterner(); |
| |
| /** Class encapsulating state for a single package deserialization. */ |
| private static class DeserializationContext { |
| private final Package.Builder packageBuilder; |
| |
| private 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 |
| ); |
| } |
| |
| private static 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 static void deserializePackageGroup(DeserializationContext context, |
| Build.PackageGroup packageGroupPb) throws PackageDeserializationException { |
| List<String> 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<String, ParsedAttributeValue> attributeValues = new HashMap<>(); |
| AttributesToDeserialize attrToDeserialize = |
| packageDeserializationEnvironment.attributesToDeserialize(); |
| |
| Hasher hasher = Hashing.md5().newHasher(); |
| for (Build.Attribute attrPb : rulePb.getAttributeList()) { |
| Type<?> type = ruleClass.getAttributeByName(attrPb.getName()).getType(); |
| attributeValues.put(attrPb.getName(), deserializeAttribute(type, attrPb)); |
| if (attrToDeserialize.addSyntheticAttributeHash) { |
| // TODO(bazel-team): This might give false positives because of explicit vs implicit. |
| hasher.putBytes(attrPb.toByteArray()); |
| } |
| } |
| AttributeContainerWithoutLocation attributeContainer = |
| new AttributeContainerWithoutLocation(ruleClass, hasher.hash()); |
| |
| Label ruleLabel = deserializeLabel(rulePb.getName()); |
| try { |
| Rule rule = createRuleWithParsedAttributeValues(ruleClass, |
| ruleLabel, context.packageBuilder, ruleLocation, attributeValues, |
| NullEventHandler.INSTANCE, attributeContainer); |
| context.packageBuilder.addRule(rule); |
| |
| // Remove the attribute after it is added to package in order to pass the validations |
| // and be able to compute all the outputs. |
| if (attrToDeserialize != DESERIALIZE_ALL_ATTRS) { |
| for (String attrName : attributeValues.keySet()) { |
| Attribute attribute = ruleClass.getAttributeByName(attrName); |
| if (!(attrToDeserialize.shouldKeepAttributeWithName.apply(attrName) |
| || BuildType.isLabelType(attribute.getType()))) { |
| attributeContainer.clearIfNotLabel(attrName); |
| } |
| } |
| } |
| |
| 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 LABEL_INTERNER.intern(Label.parseAbsolute(labelName)); |
| } catch (LabelSyntaxException e) { |
| throw new PackageDeserializationException( |
| "Invalid label '" + labelName + "':" + e.getMessage(), e); |
| } |
| } |
| |
| private static List<Label> deserializeLabels(List<String> labelNames) |
| throws PackageDeserializationException { |
| ImmutableList.Builder<Label> result = ImmutableList.builder(); |
| for (String labelName : labelNames) { |
| result.add(deserializeLabel(labelName)); |
| } |
| |
| return result.build(); |
| } |
| |
| private static License deserializeLicense(Build.License licensePb) |
| throws PackageDeserializationException { |
| List<String> licenseStrings = new ArrayList<>(); |
| licenseStrings.addAll(licensePb.getLicenseTypeList()); |
| for (String exception : licensePb.getExceptionList()) { |
| licenseStrings.add("exception=" + exception); |
| } |
| |
| try { |
| return License.parseLicense(licenseStrings); |
| } catch (LicenseParsingException e) { |
| throw new PackageDeserializationException(e); |
| } |
| } |
| |
| private static Set<DistributionType> deserializeDistribs(List<String> distributions) |
| throws PackageDeserializationException { |
| try { |
| return License.parseDistributions(distributions); |
| } catch (LicenseParsingException e) { |
| throw new PackageDeserializationException(e); |
| } |
| } |
| |
| private static TriState deserializeTriStateValue(String value) |
| throws PackageDeserializationException { |
| if (value.equals("yes")) { |
| return TriState.YES; |
| } else if (value.equals("no")) { |
| return TriState.NO; |
| } else if (value.equals("auto")) { |
| return TriState.AUTO; |
| } else { |
| throw new PackageDeserializationException( |
| String.format("Invalid tristate value: '%s'", value)); |
| } |
| } |
| |
| private static List<FilesetEntry> deserializeFilesetEntries( |
| List<Build.FilesetEntry> filesetPbs) |
| throws PackageDeserializationException { |
| ImmutableList.Builder<FilesetEntry> result = ImmutableList.builder(); |
| for (Build.FilesetEntry filesetPb : filesetPbs) { |
| Label srcLabel = deserializeLabel(filesetPb.getSource()); |
| List<Label> files = |
| filesetPb.getFilesPresent() ? deserializeLabels(filesetPb.getFileList()) : null; |
| List<String> excludes = |
| filesetPb.getExcludeList().isEmpty() |
| ? null : ImmutableList.copyOf(filesetPb.getExcludeList()); |
| String destDir = filesetPb.getDestinationDirectory(); |
| FilesetEntry.SymlinkBehavior symlinkBehavior = |
| pbToSymlinkBehavior(filesetPb.getSymlinkBehavior()); |
| String stripPrefix = filesetPb.hasStripPrefix() ? filesetPb.getStripPrefix() : null; |
| |
| result.add( |
| new FilesetEntry(srcLabel, files, excludes, destDir, symlinkBehavior, stripPrefix)); |
| } |
| |
| return result.build(); |
| } |
| |
| /** |
| * Deserialize a package from its representation as a protocol message. The inverse of |
| * {@link PackageSerializer#serialize}. |
| * @throws IOException |
| * @throws InterruptedException |
| */ |
| private void deserializeInternal(Build.Package packagePb, StoredEventHandler eventHandler, |
| Package.Builder builder, InputStream in) |
| throws PackageDeserializationException, IOException, InterruptedException { |
| Path buildFile = packageDeserializationEnvironment.getPath(packagePb.getBuildFilePath()); |
| Preconditions.checkNotNull(buildFile); |
| DeserializationContext context = new DeserializationContext(builder); |
| builder.setFilename(buildFile); |
| |
| if (packagePb.hasDefaultVisibilitySet() && packagePb.getDefaultVisibilitySet()) { |
| builder.setDefaultVisibility( |
| PackageFactory.getVisibility( |
| deserializeLabels(packagePb.getDefaultVisibilityLabelList()))); |
| } |
| |
| // It's important to do this after setting the default visibility, since that implicitly sets |
| // this bit to true |
| builder.setDefaultVisibilitySet(packagePb.getDefaultVisibilitySet()); |
| if (packagePb.hasDefaultTestonly()) { |
| builder.setDefaultTestonly(packagePb.getDefaultTestonly()); |
| } |
| if (packagePb.hasDefaultDeprecation()) { |
| builder.setDefaultDeprecation(packagePb.getDefaultDeprecation()); |
| } |
| |
| builder.setDefaultCopts(packagePb.getDefaultCoptList()); |
| if (packagePb.hasDefaultHdrsCheck()) { |
| builder.setDefaultHdrsCheck(packagePb.getDefaultHdrsCheck()); |
| } |
| if (packagePb.hasDefaultLicense()) { |
| builder.setDefaultLicense(deserializeLicense(packagePb.getDefaultLicense())); |
| } |
| builder.setDefaultDistribs(deserializeDistribs(packagePb.getDefaultDistribList())); |
| |
| for (String subinclude : packagePb.getSubincludeLabelList()) { |
| Label label = deserializeLabel(subinclude); |
| builder.addSubinclude(label, null); |
| } |
| |
| ImmutableList.Builder<Label> skylarkFileDependencies = ImmutableList.builder(); |
| for (String skylarkFile : packagePb.getSkylarkLabelList()) { |
| skylarkFileDependencies.add(deserializeLabel(skylarkFile)); |
| } |
| builder.setSkylarkFileDependencies(skylarkFileDependencies.build()); |
| |
| MakeEnvironment.Builder makeEnvBuilder = new MakeEnvironment.Builder(); |
| for (Build.MakeVar makeVar : packagePb.getMakeVariableList()) { |
| for (Build.MakeVarBinding binding : makeVar.getBindingList()) { |
| makeEnvBuilder.update( |
| makeVar.getName(), binding.getValue(), binding.getPlatformSetRegexp()); |
| } |
| } |
| builder.setMakeEnv(makeEnvBuilder); |
| |
| for (Build.Event event : packagePb.getEventList()) { |
| deserializeEvent(eventHandler, event); |
| } |
| |
| if (packagePb.hasContainsErrors() && packagePb.getContainsErrors()) { |
| builder.setContainsErrors(); |
| } |
| |
| builder.setWorkspaceName(packagePb.getWorkspaceName()); |
| |
| deserializeTargets(in, context); |
| } |
| |
| private void deserializeTargets(InputStream in, DeserializationContext context) |
| throws IOException, PackageDeserializationException, InterruptedException { |
| Build.TargetOrTerminator tot; |
| while (!(tot = Build.TargetOrTerminator.parseDelimitedFrom(in)).getIsTerminator()) { |
| Build.Target target = tot.getTarget(); |
| switch (target.getType()) { |
| case SOURCE_FILE: |
| deserializeInputFile(context, target.getSourceFile()); |
| break; |
| case PACKAGE_GROUP: |
| deserializePackageGroup(context, target.getPackageGroup()); |
| break; |
| case RULE: |
| deserializeRule(context, target.getRule()); |
| break; |
| default: |
| throw new IllegalStateException("Unexpected Target type: " + target.getType()); |
| } |
| } |
| } |
| |
| /** |
| * Deserializes a {@link Package} from {@code in}. The inverse of |
| * {@link PackageSerializer#serialize}. |
| * |
| * <p>Expects {@code in} to contain a single |
| * {@link com.google.devtools.build.lib.query2.proto.proto2api.Build.Package} message followed |
| * by a series of |
| * {@link com.google.devtools.build.lib.query2.proto.proto2api.Build.TargetOrTerminator} |
| * messages encoding the associated targets. |
| * |
| * @param in stream to read from |
| * @return a new {@link Package} as read from {@code in} |
| * @throws PackageDeserializationException on failures deserializing the input |
| * @throws IOException on failures reading from {@code in} |
| * @throws InterruptedException |
| */ |
| public Package deserialize(InputStream in) |
| throws PackageDeserializationException, IOException, InterruptedException { |
| try { |
| return deserializeInternal(in); |
| } catch (PackageDeserializationException | RuntimeException e) { |
| LOG.log(Level.WARNING, "Failed to deserialize Package object", e); |
| throw e; |
| } |
| } |
| |
| private Package deserializeInternal(InputStream in) |
| throws PackageDeserializationException, IOException, InterruptedException { |
| // Read the initial Package message so we have the data to initialize the builder. We will read |
| // the Targets in individually later. |
| Build.Package packagePb = Build.Package.parseDelimitedFrom(in); |
| Package.Builder builder; |
| try { |
| builder = new Package.Builder( |
| PackageIdentifier |
| .create(packagePb.getRepository(), new PathFragment(packagePb.getName())), |
| null); |
| } catch (LabelSyntaxException e) { |
| throw new PackageDeserializationException(e); |
| } |
| StoredEventHandler eventHandler = new StoredEventHandler(); |
| deserializeInternal(packagePb, eventHandler, builder, in); |
| builder.addEvents(eventHandler.getEvents()); |
| return builder.build(); |
| } |
| |
| private static void deserializeEvent(StoredEventHandler eventHandler, Build.Event event) { |
| String message = event.getMessage(); |
| switch (event.getKind()) { |
| case ERROR: eventHandler.handle(Event.error(message)); break; |
| case WARNING: eventHandler.handle(Event.warn(message)); break; |
| case INFO: eventHandler.handle(Event.info(message)); break; |
| case PROGRESS: eventHandler.handle(Event.progress(message)); break; |
| default: break; // Ignore |
| } |
| } |
| |
| private static List<?> deserializeGlobs(List<?> matches, |
| Build.Attribute attrPb) { |
| if (attrPb.getGlobCriteriaCount() == 0) { |
| return matches; |
| } |
| |
| Builder<GlobCriteria> criteriaBuilder = ImmutableList.builder(); |
| for (Build.GlobCriteria criteriaPb : attrPb.getGlobCriteriaList()) { |
| if (criteriaPb.hasGlob() && criteriaPb.getGlob()) { |
| criteriaBuilder.add(GlobCriteria.fromGlobCall( |
| ImmutableList.copyOf(criteriaPb.getIncludeList()), |
| ImmutableList.copyOf(criteriaPb.getExcludeList()))); |
| } else { |
| criteriaBuilder.add( |
| GlobCriteria.fromList(ImmutableList.copyOf(criteriaPb.getIncludeList()))); |
| } |
| } |
| |
| @SuppressWarnings({"unchecked", "rawtypes"}) GlobList<?> result = |
| new GlobList(criteriaBuilder.build(), matches); |
| return result; |
| } |
| |
| // TODO(bazel-team): Verify that these put sane values in the attribute |
| @VisibleForTesting |
| static Object deserializeAttributeValue(Type<?> expectedType, |
| Build.Attribute attrPb) |
| throws PackageDeserializationException { |
| switch (attrPb.getType()) { |
| case INTEGER: |
| return attrPb.hasIntValue() ? new Integer(attrPb.getIntValue()) : null; |
| |
| case STRING: |
| if (!attrPb.hasStringValue()) { |
| return null; |
| } else if (expectedType == BuildType.NODEP_LABEL) { |
| return deserializeLabel(attrPb.getStringValue()); |
| } else { |
| return attrPb.getStringValue(); |
| } |
| |
| case LABEL: |
| case OUTPUT: |
| return attrPb.hasStringValue() ? deserializeLabel(attrPb.getStringValue()) : null; |
| |
| case STRING_LIST: |
| if (expectedType == BuildType.NODEP_LABEL_LIST) { |
| return deserializeGlobs(deserializeLabels(attrPb.getStringListValueList()), attrPb); |
| } else { |
| return deserializeGlobs(ImmutableList.copyOf(attrPb.getStringListValueList()), attrPb); |
| } |
| |
| case LABEL_LIST: |
| case OUTPUT_LIST: |
| return deserializeGlobs(deserializeLabels(attrPb.getStringListValueList()), attrPb); |
| |
| case DISTRIBUTION_SET: |
| return deserializeDistribs(attrPb.getStringListValueList()); |
| |
| case LICENSE: |
| return attrPb.hasLicense() ? deserializeLicense(attrPb.getLicense()) : null; |
| |
| case STRING_DICT: { |
| // Building an immutable map will fail if the builder was given duplicate keys. These entry |
| // lists may contain duplicate keys if the serialized map value was configured (e.g. via |
| // the select function) and the different configuration values had keys in common. This is |
| // because serialization flattens configurable map-valued attributes. |
| // |
| // As long as serialization does this flattening, to avoid failure during deserialization, |
| // we dedupe entries in the list by their keys. |
| // TODO(bazel-team): Serialize and deserialize configured values with fidelity (without |
| // flattening them). |
| ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| HashSet<String> keysSeenSoFar = Sets.newHashSet(); |
| for (Build.StringDictEntry entry : attrPb.getStringDictValueList()) { |
| String key = entry.getKey(); |
| if (keysSeenSoFar.add(key)) { |
| builder.put(key, entry.getValue()); |
| } |
| } |
| return builder.build(); |
| } |
| |
| case STRING_DICT_UNARY: { |
| // See STRING_DICT case's comment about why this dedupes entries by their keys. |
| ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| HashSet<String> keysSeenSoFar = Sets.newHashSet(); |
| for (StringDictUnaryEntry entry : attrPb.getStringDictUnaryValueList()) { |
| String key = entry.getKey(); |
| if (keysSeenSoFar.add(key)) { |
| builder.put(key, entry.getValue()); |
| } |
| } |
| return builder.build(); |
| } |
| |
| case FILESET_ENTRY_LIST: |
| return deserializeFilesetEntries(attrPb.getFilesetListValueList()); |
| |
| case LABEL_LIST_DICT: { |
| // See STRING_DICT case's comment about why this dedupes entries by their keys. |
| ImmutableMap.Builder<String, List<Label>> builder = ImmutableMap.builder(); |
| HashSet<String> keysSeenSoFar = Sets.newHashSet(); |
| for (Build.LabelListDictEntry entry : attrPb.getLabelListDictValueList()) { |
| String key = entry.getKey(); |
| if (keysSeenSoFar.add(key)) { |
| builder.put(key, deserializeLabels(entry.getValueList())); |
| } |
| } |
| return builder.build(); |
| } |
| |
| case STRING_LIST_DICT: { |
| // See STRING_DICT case's comment about why this dedupes entries by their keys. |
| ImmutableMap.Builder<String, List<String>> builder = ImmutableMap.builder(); |
| HashSet<String> keysSeenSoFar = Sets.newHashSet(); |
| for (Build.StringListDictEntry entry : attrPb.getStringListDictValueList()) { |
| String key = entry.getKey(); |
| if (keysSeenSoFar.add(key)) { |
| builder.put(key, ImmutableList.copyOf(entry.getValueList())); |
| } |
| } |
| return builder.build(); |
| } |
| |
| case BOOLEAN: |
| return attrPb.hasBooleanValue() ? attrPb.getBooleanValue() : null; |
| |
| case TRISTATE: |
| return attrPb.hasStringValue() ? deserializeTriStateValue(attrPb.getStringValue()) : null; |
| |
| case INTEGER_LIST: |
| return ImmutableList.copyOf(attrPb.getIntListValueList()); |
| |
| default: |
| throw new PackageDeserializationException("Invalid discriminator: " + attrPb.getType()); |
| } |
| } |
| |
| private static FilesetEntry.SymlinkBehavior pbToSymlinkBehavior( |
| Build.FilesetEntry.SymlinkBehavior symlinkBehavior) { |
| switch (symlinkBehavior) { |
| case COPY: |
| return FilesetEntry.SymlinkBehavior.COPY; |
| case DEREFERENCE: |
| return FilesetEntry.SymlinkBehavior.DEREFERENCE; |
| default: |
| throw new IllegalStateException(); |
| } |
| } |
| |
| /** |
| * An special {@code AttributeContainer} implementation that does not keep |
| * the location and can contain a hashcode of the target attributes. |
| */ |
| public static class AttributeContainerWithoutLocation extends AttributeContainer { |
| |
| @Nullable |
| private final HashCode syntheticAttrHash; |
| |
| private AttributeContainerWithoutLocation(RuleClass ruleClass, |
| @Nullable HashCode syntheticAttrHash) { |
| super(ruleClass, null); |
| this.syntheticAttrHash = syntheticAttrHash; |
| } |
| |
| @Override |
| public Location getAttributeLocation(String attrName) { |
| return EmptyLocation.INSTANCE; |
| } |
| |
| @Override |
| void setAttributeLocation(int attrIndex, Location location) { |
| throw new UnsupportedOperationException("Setting location not supported"); |
| } |
| |
| @Override |
| void setAttributeLocation(Attribute attribute, Location location) { |
| throw new UnsupportedOperationException("Setting location not supported"); |
| } |
| |
| |
| @Nullable |
| public HashCode getSyntheticAttrHash() { |
| return syntheticAttrHash; |
| } |
| |
| private void clearIfNotLabel(String attr) { |
| setAttributeValueByName(attr, null); |
| } |
| } |
| |
| /** |
| * Creates a rule with the attribute values that are already parsed. |
| * |
| * <p><b>WARNING:</b> This assumes that the attribute values here have the right type and |
| * bypasses some sanity checks. If they are of the wrong type, everything will come down burning. |
| */ |
| @SuppressWarnings("unchecked") |
| private static Rule createRuleWithParsedAttributeValues(RuleClass ruleClass, Label label, |
| Package.Builder pkgBuilder, Location ruleLocation, |
| Map<String, ParsedAttributeValue> attributeValues, EventHandler eventHandler, |
| AttributeContainer attributeContainer) |
| throws LabelSyntaxException, InterruptedException { |
| Rule rule = pkgBuilder.newRuleWithLabelAndAttrContainer(label, ruleClass, ruleLocation, |
| attributeContainer); |
| rule.checkValidityPredicate(eventHandler); |
| |
| for (Attribute attribute : rule.getRuleClassObject().getAttributes()) { |
| ParsedAttributeValue value = attributeValues.get(attribute.getName()); |
| if (attribute.isMandatory()) { |
| Preconditions.checkState(value != null); |
| } |
| |
| if (value == null) { |
| continue; |
| } |
| |
| rule.setAttributeValue(attribute, value.value, value.explicitlySpecified); |
| ruleClass.checkAllowedValues(rule, attribute, eventHandler); |
| |
| if (attribute.getName().equals("visibility")) { |
| // TODO(bazel-team): Verify that this cast works |
| rule.setVisibility(PackageFactory.getVisibility((List<Label>) value.value)); |
| } |
| } |
| |
| rule.populateOutputFiles(eventHandler, pkgBuilder); |
| Preconditions.checkState(!rule.containsErrors()); |
| return rule; |
| } |
| |
| private static class ParsedAttributeValue { |
| private final boolean explicitlySpecified; |
| private final Object value; |
| |
| private ParsedAttributeValue(boolean explicitlySpecified, Object value) { |
| this.explicitlySpecified = explicitlySpecified; |
| this.value = value; |
| } |
| } |
| } |