| // Copyright 2014 Google Inc. 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.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableList.Builder; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| 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.AbstractBuilder.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.FilesetEntry; |
| import com.google.devtools.build.lib.syntax.GlobCriteria; |
| import com.google.devtools.build.lib.syntax.GlobList; |
| import com.google.devtools.build.lib.syntax.Label; |
| import com.google.devtools.build.lib.syntax.Label.SyntaxException; |
| import com.google.devtools.build.lib.vfs.FileSystem; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Functionality to deserialize loaded packages. |
| */ |
| public class PackageDeserializer { |
| |
| // Workaround for Java serialization not allowing to pass in a context manually. |
| // volatile is needed to ensure that the objects are published safely. |
| // TODO(bazel-team): Subclass ObjectOutputStream to pass through environment variables. |
| public static volatile RuleClassProvider defaultRuleClassProvider; |
| public static volatile FileSystem defaultDeserializerFileSystem; |
| |
| private class Context { |
| private final Package.Builder packageBuilder; |
| private final Path buildFilePath; |
| |
| public Context(Path buildFilePath, Package.Builder packageBuilder) { |
| this.buildFilePath = buildFilePath; |
| this.packageBuilder = packageBuilder; |
| } |
| |
| Location deserializeLocation(Build.Location location) { |
| return new ExplicitLocation(buildFilePath, location); |
| } |
| |
| ParsedAttributeValue deserializeAttribute(Type<?> expectedType, |
| Build.Attribute attrPb) |
| throws PackageDeserializationException { |
| Object value = deserializeAttributeValue(expectedType, attrPb); |
| return new ParsedAttributeValue( |
| attrPb.hasExplicitlySpecified() && attrPb.getExplicitlySpecified(), value, |
| deserializeLocation(attrPb.getParseableLocation())); |
| } |
| |
| void deserializeInputFile(Build.SourceFile sourceFile) |
| throws PackageDeserializationException { |
| InputFile inputFile; |
| try { |
| inputFile = packageBuilder.createInputFile( |
| deserializeLabel(sourceFile.getName()).getName(), |
| deserializeLocation(sourceFile.getParseableLocation())); |
| } catch (GeneratedLabelConflict e) { |
| throw new PackageDeserializationException(e); |
| } |
| |
| if (!sourceFile.getVisibilityLabelList().isEmpty() || sourceFile.hasLicense()) { |
| packageBuilder.setVisibilityAndLicense(inputFile, |
| PackageFactory.getVisibility(deserializeLabels(sourceFile.getVisibilityLabelList())), |
| deserializeLicense(sourceFile.getLicense())); |
| } |
| } |
| |
| void deserializePackageGroup(Build.PackageGroup packageGroupPb) |
| throws PackageDeserializationException { |
| List<String> specifications = new ArrayList<>(); |
| for (String containedPackage : packageGroupPb.getContainedPackageList()) { |
| specifications.add("//" + containedPackage); |
| } |
| |
| try { |
| packageBuilder.addPackageGroup( |
| deserializeLabel(packageGroupPb.getName()).getName(), |
| specifications, |
| deserializeLabels(packageGroupPb.getIncludedPackageGroupList()), |
| NullEventHandler.INSTANCE, // TODO(bazel-team): Handle errors properly |
| deserializeLocation(packageGroupPb.getParseableLocation())); |
| } catch (Label.SyntaxException | Package.NameConflictException e) { |
| throw new PackageDeserializationException(e); |
| } |
| } |
| |
| void deserializeRule(Build.Rule rulePb) |
| throws PackageDeserializationException { |
| RuleClass ruleClass = ruleClassProvider.getRuleClassMap().get(rulePb.getRuleClass()); |
| if (ruleClass == null) { |
| throw new PackageDeserializationException( |
| String.format("Invalid rule class '%s'", ruleClass)); |
| } |
| |
| Map<String, ParsedAttributeValue> 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()); |
| Location ruleLocation = deserializeLocation(rulePb.getParseableLocation()); |
| try { |
| Rule rule = ruleClass.createRuleWithParsedAttributeValues( |
| ruleLabel, packageBuilder, ruleLocation, attributeValues, |
| NullEventHandler.INSTANCE); |
| packageBuilder.addRule(rule); |
| |
| Preconditions.checkState(!rule.containsErrors()); |
| } catch (NameConflictException | SyntaxException e) { |
| throw new PackageDeserializationException(e); |
| } |
| } |
| } |
| |
| private final FileSystem fileSystem; |
| private final RuleClassProvider ruleClassProvider; |
| |
| @Immutable |
| private static final class ExplicitLocation extends Location { |
| private final PathFragment path; |
| private final int startLine; |
| private final int startColumn; |
| private final int endLine; |
| private final int endColumn; |
| |
| private ExplicitLocation(Path path, Build.Location location) { |
| super( |
| location.hasStartOffset() && location.hasEndOffset() ? location.getStartOffset() : 0, |
| location.hasStartOffset() && location.hasEndOffset() ? location.getEndOffset() : 0); |
| this.path = path.asFragment(); |
| if (location.hasStartLine() && location.hasStartColumn() |
| && location.hasEndLine() && location.hasEndColumn()) { |
| this.startLine = location.getStartLine(); |
| this.startColumn = location.getStartColumn(); |
| this.endLine = location.getEndLine(); |
| this.endColumn = location.getEndColumn(); |
| } else { |
| this.startLine = 0; |
| this.startColumn = 0; |
| this.endLine = 0; |
| this.endColumn = 0; |
| } |
| } |
| |
| @Override |
| public PathFragment getPath() { |
| return path; |
| } |
| |
| @Override |
| public LineAndColumn getStartLineAndColumn() { |
| return new LineAndColumn(startLine, startColumn); |
| } |
| |
| @Override |
| public LineAndColumn getEndLineAndColumn() { |
| return new LineAndColumn(endLine, endColumn); |
| } |
| } |
| |
| public PackageDeserializer(FileSystem fileSystem, RuleClassProvider ruleClassProvider) { |
| if (fileSystem == null) { |
| fileSystem = defaultDeserializerFileSystem; |
| } |
| this.fileSystem = Preconditions.checkNotNull(fileSystem); |
| if (ruleClassProvider == null) { |
| ruleClassProvider = defaultRuleClassProvider; |
| } |
| this.ruleClassProvider = Preconditions.checkNotNull(ruleClassProvider); |
| } |
| |
| /** |
| * 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.parseRepositoryLabel(labelName); |
| } catch (Label.SyntaxException e) { |
| throw new PackageDeserializationException("Invalid label: " + 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#serializePackage}. |
| */ |
| private void deserializeInternal(Build.Package packagePb, StoredEventHandler eventHandler, |
| Package.Builder builder) throws PackageDeserializationException { |
| Path buildFile = fileSystem.getPath(packagePb.getBuildFilePath()); |
| Preconditions.checkNotNull(buildFile); |
| Context context = new Context(buildFile, 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.SourceFile sourceFile : packagePb.getSourceFileList()) { |
| context.deserializeInputFile(sourceFile); |
| } |
| |
| for (Build.PackageGroup packageGroupPb : |
| packagePb.getPackageGroupList()) { |
| context.deserializePackageGroup(packageGroupPb); |
| } |
| |
| for (Build.Rule rulePb : packagePb.getRuleList()) { |
| context.deserializeRule(rulePb); |
| } |
| |
| for (Build.Event event : packagePb.getEventList()) { |
| deserializeEvent(context, eventHandler, event); |
| } |
| |
| if (packagePb.hasContainsErrors() && packagePb.getContainsErrors()) { |
| builder.setContainsErrors(); |
| } |
| if (packagePb.hasContainsTemporaryErrors() && packagePb.getContainsTemporaryErrors()) { |
| builder.setContainsTemporaryErrors(); |
| } |
| } |
| |
| /** |
| * Deserialize a protocol message to a package. The inverse of |
| * {@link PackageSerializer#serializePackage}. |
| */ |
| public Package deserialize(Build.Package packagePb) |
| throws PackageDeserializationException { |
| Package.Builder builder; |
| try { |
| builder = new Package.Builder( |
| new PackageIdentifier(packagePb.getRepository(), new PathFragment(packagePb.getName()))); |
| } catch (SyntaxException e) { |
| throw new PackageDeserializationException(e); |
| } |
| StoredEventHandler eventHandler = new StoredEventHandler(); |
| deserializeInternal(packagePb, eventHandler, builder); |
| builder.addEvents(eventHandler.getEvents()); |
| return builder.build(); |
| } |
| |
| private static void deserializeEvent( |
| Context context, StoredEventHandler eventHandler, Build.Event event) { |
| Location location = null; |
| if (event.hasLocation()) { |
| location = context.deserializeLocation(event.getLocation()); |
| } |
| |
| String message = event.getMessage(); |
| switch (event.getKind()) { |
| case ERROR: eventHandler.handle(Event.error(location, message)); break; |
| case WARNING: eventHandler.handle(Event.warn(location, message)); break; |
| case INFO: eventHandler.handle(Event.info(location, message)); break; |
| case PROGRESS: eventHandler.handle(Event.progress(location, 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 |
| private 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 == Type.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 == Type.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: { |
| ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| for (Build.StringDictEntry entry : attrPb.getStringDictValueList()) { |
| builder.put(entry.getKey(), entry.getValue()); |
| } |
| return builder.build(); |
| } |
| |
| case STRING_DICT_UNARY: { |
| ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| for (StringDictUnaryEntry entry : attrPb.getStringDictUnaryValueList()) { |
| builder.put(entry.getKey(), entry.getValue()); |
| } |
| return builder.build(); |
| } |
| |
| case FILESET_ENTRY_LIST: |
| return deserializeFilesetEntries(attrPb.getFilesetListValueList()); |
| |
| case LABEL_LIST_DICT: { |
| ImmutableMap.Builder<String, List<Label>> builder = ImmutableMap.builder(); |
| for (Build.LabelListDictEntry entry : attrPb.getLabelListDictValueList()) { |
| builder.put(entry.getKey(), deserializeLabels(entry.getValueList())); |
| } |
| return builder.build(); |
| } |
| |
| case STRING_LIST_DICT: { |
| ImmutableMap.Builder<String, List<String>> builder = ImmutableMap.builder(); |
| for (Build.StringListDictEntry entry : attrPb.getStringListDictValueList()) { |
| builder.put(entry.getKey(), ImmutableList.copyOf(entry.getValueList())); |
| } |
| return builder.build(); |
| } |
| |
| case BOOLEAN: |
| return attrPb.hasBooleanValue() ? attrPb.getBooleanValue() : null; |
| |
| case TRISTATE: |
| return attrPb.hasStringValue() ? deserializeTriStateValue(attrPb.getStringValue()) : null; |
| |
| 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(); |
| } |
| } |
| } |