aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/packages/PackageDeserializer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/packages/PackageDeserializer.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/PackageDeserializer.java536
1 files changed, 536 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageDeserializer.java b/src/main/java/com/google/devtools/build/lib/packages/PackageDeserializer.java
new file mode 100644
index 0000000000..5eca0f43c9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageDeserializer.java
@@ -0,0 +1,536 @@
+// 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() : false,
+ 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.hasDefaultObsolete()) {
+ builder.setDefaultObsolete(packagePb.getDefaultObsolete());
+ }
+ 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 new Integer(attrPb.getIntValue());
+
+ case STRING:
+ if (expectedType == Type.NODEP_LABEL) {
+ return deserializeLabel(attrPb.getStringValue());
+ } else {
+ return attrPb.getStringValue();
+ }
+
+ case LABEL:
+ case OUTPUT:
+ return deserializeLabel(attrPb.getStringValue());
+
+ 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 deserializeLicense(attrPb.getLicense());
+
+ 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.getBooleanValue();
+
+ case TRISTATE:
+ return deserializeTriStateValue(attrPb.getStringValue());
+
+ 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();
+ }
+ }
+}