| // 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(); | |
| } | |
| } | |
| } |