Open-source Skyframe serialization, and make AppleConfiguration serializable as a pilot. Currently not hooked up to anything, but will be shortly.
PiperOrigin-RevId: 165583517
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index eb3b78d..18b40a4 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -37,6 +37,7 @@
"//src/main/java/com/google/devtools/build/lib/rules/genquery:srcs",
"//src/main/java/com/google/devtools/build/lib/rules/genrule:srcs",
"//src/main/java/com/google/devtools/build/lib/rules/objc:srcs",
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization:srcs",
"//src/main/java/com/google/devtools/build/lib/analysis/featurecontrol:srcs",
"//src/main/java/com/google/devtools/build/lib/analysis/platform:srcs",
"//src/main/java/com/google/devtools/build/lib/analysis/whitelisting:srcs",
@@ -641,6 +642,7 @@
"//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
"//src/main/java/com/google/devtools/build/lib/causes",
"//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//src/main/java/com/google/devtools/common/options",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/apple/AppleCommandLineOptions.java b/src/main/java/com/google/devtools/build/lib/rules/apple/AppleCommandLineOptions.java
index 36c9449..268c47d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/apple/AppleCommandLineOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/apple/AppleCommandLineOptions.java
@@ -14,6 +14,8 @@
package com.google.devtools.build.lib.rules.apple;
+import static com.google.devtools.build.lib.skyframe.serialization.SerializationCommonUtils.STRING_LIST_CODEC;
+
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@@ -24,6 +26,10 @@
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher;
import com.google.devtools.build.lib.rules.apple.ApplePlatform.PlatformType;
+import com.google.devtools.build.lib.skyframe.serialization.EnumCodec;
+import com.google.devtools.build.lib.skyframe.serialization.FastStringCodec;
+import com.google.devtools.build.lib.skyframe.serialization.LabelCodec;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
@@ -34,6 +40,9 @@
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import com.google.devtools.common.options.OptionMetadataTag;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
import java.util.List;
/**
@@ -470,6 +479,8 @@
super(AppleBitcodeMode.class, "apple bitcode mode");
}
}
+
+ static final EnumCodec<AppleBitcodeMode> CODEC = new EnumCodec<>(AppleBitcodeMode.class);
}
@Override
@@ -492,6 +503,65 @@
return host;
}
+ void serialize(CodedOutputStream out) throws IOException, SerializationException {
+ out.writeBoolNoTag(mandatoryMinimumVersion);
+ xcodeVersion.serialize(out);
+ iosSdkVersion.serialize(out);
+ watchOsSdkVersion.serialize(out);
+ tvOsSdkVersion.serialize(out);
+ macOsSdkVersion.serialize(out);
+ iosMinimumOs.serialize(out);
+ watchosMinimumOs.serialize(out);
+ tvosMinimumOs.serialize(out);
+ macosMinimumOs.serialize(out);
+ FastStringCodec.INSTANCE.serialize(iosCpu, out);
+ LabelCodec.INSTANCE.serialize(appleCrosstoolTop, out);
+ PlatformType.CODEC.serialize(applePlatformType, out);
+ FastStringCodec.INSTANCE.serialize(appleSplitCpu, out);
+ ConfigurationDistinguisher.CODEC.serialize(configurationDistinguisher, out);
+ STRING_LIST_CODEC.serialize((ImmutableList<String>) iosMultiCpus, out);
+ STRING_LIST_CODEC.serialize((ImmutableList<String>) watchosCpus, out);
+ STRING_LIST_CODEC.serialize((ImmutableList<String>) tvosCpus, out);
+ STRING_LIST_CODEC.serialize((ImmutableList<String>) macosCpus, out);
+ LabelCodec.INSTANCE.serialize(defaultProvisioningProfile, out);
+ LabelCodec.INSTANCE.serialize(xcodeVersionConfig, out);
+ FastStringCodec.INSTANCE.serialize(xcodeToolchain, out);
+ AppleBitcodeMode.CODEC.serialize(appleBitcodeMode, out);
+ out.writeBoolNoTag(enableAppleCrosstoolTransition);
+ out.writeBoolNoTag(targetUsesAppleCrosstool);
+ }
+
+ static AppleCommandLineOptions deserialize(CodedInputStream in)
+ throws IOException, SerializationException {
+ AppleCommandLineOptions result = new AppleCommandLineOptions();
+ result.mandatoryMinimumVersion = in.readBool();
+ result.xcodeVersion = DottedVersion.deserialize(in);
+ result.iosSdkVersion = DottedVersion.deserialize(in);
+ result.watchOsSdkVersion = DottedVersion.deserialize(in);
+ result.tvOsSdkVersion = DottedVersion.deserialize(in);
+ result.macOsSdkVersion = DottedVersion.deserialize(in);
+ result.iosMinimumOs = DottedVersion.deserialize(in);
+ result.watchosMinimumOs = DottedVersion.deserialize(in);
+ result.tvosMinimumOs = DottedVersion.deserialize(in);
+ result.macosMinimumOs = DottedVersion.deserialize(in);
+ result.iosCpu = FastStringCodec.INSTANCE.deserialize(in);
+ result.appleCrosstoolTop = LabelCodec.INSTANCE.deserialize(in);
+ result.applePlatformType = PlatformType.CODEC.deserialize(in);
+ result.appleSplitCpu = FastStringCodec.INSTANCE.deserialize(in);
+ result.configurationDistinguisher = ConfigurationDistinguisher.CODEC.deserialize(in);
+ result.iosMultiCpus = STRING_LIST_CODEC.deserialize(in);
+ result.watchosCpus = STRING_LIST_CODEC.deserialize(in);
+ result.tvosCpus = STRING_LIST_CODEC.deserialize(in);
+ result.macosCpus = STRING_LIST_CODEC.deserialize(in);
+ result.defaultProvisioningProfile = LabelCodec.INSTANCE.deserialize(in);
+ result.xcodeVersionConfig = LabelCodec.INSTANCE.deserialize(in);
+ result.xcodeToolchain = FastStringCodec.INSTANCE.deserialize(in);
+ result.appleBitcodeMode = AppleBitcodeMode.CODEC.deserialize(in);
+ result.enableAppleCrosstoolTransition = in.readBool();
+ result.targetUsesAppleCrosstool = in.readBool();
+ return result;
+ }
+
/** Converter for the Apple configuration distinguisher. */
public static final class ConfigurationDistinguisherConverter
extends EnumConverter<ConfigurationDistinguisher> {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/apple/AppleConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/apple/AppleConfiguration.java
index 3ab182a..b2a1a98 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/apple/AppleConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/apple/AppleConfiguration.java
@@ -31,13 +31,20 @@
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.apple.AppleCommandLineOptions.AppleBitcodeMode;
import com.google.devtools.build.lib.rules.apple.ApplePlatform.PlatformType;
+import com.google.devtools.build.lib.skyframe.serialization.EnumCodec;
+import com.google.devtools.build.lib.skyframe.serialization.FastStringCodec;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
import com.google.devtools.build.lib.util.Preconditions;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import javax.annotation.Nullable;
/** A configuration containing flags required for Apple platforms and tools. */
@@ -99,10 +106,11 @@
@Nullable private final Label defaultProvisioningProfileLabel;
private final boolean mandatoryMinimumVersion;
+ @VisibleForTesting
AppleConfiguration(
AppleCommandLineOptions options,
- String cpu,
- XcodeVersionProperties xcodeVersionProperties,
+ String iosCpu,
+ @Nullable DottedVersion xcodeVersion,
DottedVersion iosSdkVersion,
DottedVersion iosMinimumOs,
DottedVersion watchosSdkVersion,
@@ -126,8 +134,8 @@
Preconditions.checkNotNull(macosSdkVersion, "macOsSdkVersion");
this.macosMinimumOs = Preconditions.checkNotNull(macosMinimumOs, "macOsMinimumOs");
- this.xcodeVersion = xcodeVersionProperties.getXcodeVersion().orNull();
- this.iosCpu = iosCpuFromCpu(cpu);
+ this.xcodeVersion = xcodeVersion;
+ this.iosCpu = iosCpu;
this.appleSplitCpu = Preconditions.checkNotNull(options.appleSplitCpu, "appleSplitCpu");
this.applePlatformType =
Preconditions.checkNotNull(options.applePlatformType, "applePlatformType");
@@ -670,6 +678,81 @@
.build();
}
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof AppleConfiguration)) {
+ return false;
+ }
+ AppleConfiguration that = (AppleConfiguration) obj;
+ return this.options.equals(that.options)
+ && Objects.equals(this.xcodeVersion, that.xcodeVersion)
+ && this.iosSdkVersion.equals(that.iosSdkVersion)
+ && this.iosMinimumOs.equals(that.iosMinimumOs)
+ && this.watchosSdkVersion.equals(that.watchosSdkVersion)
+ && this.watchosMinimumOs.equals(that.watchosMinimumOs)
+ && this.tvosSdkVersion.equals(that.tvosSdkVersion)
+ && this.tvosMinimumOs.equals(that.tvosMinimumOs)
+ && this.macosSdkVersion.equals(that.macosSdkVersion)
+ && this.macosMinimumOs.equals(that.macosMinimumOs);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ options,
+ xcodeVersion,
+ iosSdkVersion,
+ iosMinimumOs,
+ watchosSdkVersion,
+ watchosMinimumOs,
+ tvosSdkVersion,
+ tvosMinimumOs,
+ macosSdkVersion,
+ macosMinimumOs);
+ }
+
+ void serialize(CodedOutputStream out) throws IOException, SerializationException {
+ options.serialize(out);
+ out.writeStringNoTag(iosCpu);
+ if (xcodeVersion == null) {
+ out.writeBoolNoTag(false);
+ } else {
+ out.writeBoolNoTag(true);
+ xcodeVersion.serialize(out);
+ }
+ iosSdkVersion.serialize(out);
+ iosMinimumOs.serialize(out);
+ watchosSdkVersion.serialize(out);
+ watchosMinimumOs.serialize(out);
+ tvosSdkVersion.serialize(out);
+ tvosMinimumOs.serialize(out);
+ macosSdkVersion.serialize(out);
+ macosMinimumOs.serialize(out);
+ }
+
+ static AppleConfiguration deserialize(CodedInputStream in)
+ throws IOException, SerializationException {
+ AppleCommandLineOptions options = AppleCommandLineOptions.deserialize(in);
+ String iosCpu = FastStringCodec.INSTANCE.deserialize(in);
+ boolean hasXcodeVersion = in.readBool();
+ DottedVersion xcodeVersion = hasXcodeVersion ? DottedVersion.deserialize(in) : null;
+ return new AppleConfiguration(
+ options,
+ iosCpu,
+ xcodeVersion,
+ DottedVersion.deserialize(in),
+ DottedVersion.deserialize(in),
+ DottedVersion.deserialize(in),
+ DottedVersion.deserialize(in),
+ DottedVersion.deserialize(in),
+ DottedVersion.deserialize(in),
+ DottedVersion.deserialize(in),
+ DottedVersion.deserialize(in));
+ }
+
/**
* Loads {@link AppleConfiguration} from build options.
*/
@@ -701,8 +784,8 @@
AppleConfiguration configuration =
new AppleConfiguration(
appleOptions,
- cpu,
- xcodeVersionProperties,
+ iosCpuFromCpu(cpu),
+ xcodeVersionProperties.getXcodeVersion().orNull(),
iosSdkVersion,
iosMinimumOsVersion,
watchosSdkVersion,
@@ -781,5 +864,8 @@
public String getFileSystemName() {
return fileSystemName;
}
+
+ static final EnumCodec<ConfigurationDistinguisher> CODEC =
+ new EnumCodec<>(ConfigurationDistinguisher.class);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/apple/ApplePlatform.java b/src/main/java/com/google/devtools/build/lib/rules/apple/ApplePlatform.java
index 6be2024..bccee16 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/apple/ApplePlatform.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/apple/ApplePlatform.java
@@ -19,6 +19,7 @@
import com.google.devtools.build.lib.packages.Info;
import com.google.devtools.build.lib.packages.NativeProvider;
import com.google.devtools.build.lib.packages.Provider;
+import com.google.devtools.build.lib.skyframe.serialization.EnumCodec;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
@@ -299,5 +300,7 @@
public void repr(SkylarkPrinter printer) {
printer.append(toString());
}
+
+ static final EnumCodec<PlatformType> CODEC = new EnumCodec<>(PlatformType.class);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/apple/BUILD b/src/main/java/com/google/devtools/build/lib/rules/apple/BUILD
index 15ae78d..b6fb301 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/apple/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/rules/apple/BUILD
@@ -14,15 +14,14 @@
"//src/main/java/com/google/devtools/build/lib:events",
"//src/main/java/com/google/devtools/build/lib:packages-internal",
"//src/main/java/com/google/devtools/build/lib:preconditions",
- "//src/main/java/com/google/devtools/build/lib:shell",
"//src/main/java/com/google/devtools/build/lib:skylarkinterface",
"//src/main/java/com/google/devtools/build/lib:syntax",
- "//src/main/java/com/google/devtools/build/lib:vfs",
- "//src/main/java/com/google/devtools/build/lib/actions",
"//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
"//src/main/java/com/google/devtools/common/options",
"//third_party:guava",
"//third_party:jsr305",
+ "//third_party/protobuf:protobuf_java",
],
)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/apple/DottedVersion.java b/src/main/java/com/google/devtools/build/lib/rules/apple/DottedVersion.java
index bdbc910..5fbbef5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/apple/DottedVersion.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/apple/DottedVersion.java
@@ -26,10 +26,14 @@
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.annotation.Nullable;
/**
* Represents a value with multiple components, separated by periods, for example {@code 4.5.6} or
@@ -249,13 +253,35 @@
printer.append(stringRepresentation);
}
+ void serialize(CodedOutputStream out) throws IOException {
+ out.writeInt32NoTag(components.size());
+ for (Component component : components) {
+ component.serialize(out);
+ }
+ out.writeStringNoTag(stringRepresentation);
+ out.writeInt32NoTag(numOriginalComponents);
+ }
+
+ static DottedVersion deserialize(CodedInputStream in) throws IOException {
+ int numComponents = in.readInt32();
+ // TODO(janakr: Presize this if/when https://github.com/google/guava/issues/196 is resolved.
+ ImmutableList.Builder<Component> components = ImmutableList.builder();
+ for (int i = 0; i < numComponents; i++) {
+ components.add(Component.deserialize(in));
+ }
+ return new DottedVersion(components.build(), in.readString(), in.readInt32());
+ }
+
private static final class Component implements Comparable<Component> {
private final int firstNumber;
- private final String alphaSequence;
+ @Nullable private final String alphaSequence;
private final int secondNumber;
private final String stringRepresentation;
- public Component(int firstNumber, String alphaSequence, int secondNumber,
+ public Component(
+ int firstNumber,
+ @Nullable String alphaSequence,
+ int secondNumber,
String stringRepresentation) {
this.firstNumber = firstNumber;
this.alphaSequence = alphaSequence;
@@ -293,5 +319,25 @@
public String toString() {
return stringRepresentation;
}
+
+ void serialize(CodedOutputStream out) throws IOException {
+ if (alphaSequence == null) {
+ out.writeBoolNoTag(false);
+ } else {
+ out.writeBoolNoTag(true);
+ out.writeStringNoTag(alphaSequence);
+ }
+ out.writeInt32NoTag(firstNumber);
+ out.writeInt32NoTag(secondNumber);
+ out.writeStringNoTag(stringRepresentation);
+ }
+
+ static Component deserialize(CodedInputStream in) throws IOException {
+ String alphaSequence = null;
+ if (in.readBool()) {
+ alphaSequence = in.readString();
+ }
+ return new Component(in.readInt32(), alphaSequence, in.readInt32(), in.readString());
+ }
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
index 5d23707..49cd581 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
@@ -21,6 +21,7 @@
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.pkgcache.LoadingResult;
import com.google.devtools.build.lib.pkgcache.TestFilter;
+import com.google.devtools.build.lib.skyframe.serialization.NotSerializableRuntimeException;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java
index 1da1fee..8bc2f2b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java
@@ -21,6 +21,7 @@
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.skyframe.serialization.NotSerializableRuntimeException;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteValue.java
index bed1646..63a55fb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteValue.java
@@ -19,6 +19,7 @@
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.skyframe.serialization.NotSerializableRuntimeException;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
new file mode 100644
index 0000000..6de7a8e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
@@ -0,0 +1,20 @@
+# TODO(janakr): find out how to avoid this default visibility and still have
+# automatic BUILD-file generation.
+package(default_visibility = ["//src:__subpackages__"])
+
+java_library(
+ name = "serialization",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:vfs",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
+ "//third_party:guava",
+ "//third_party/protobuf:protobuf_java",
+ ],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+)
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BaseCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BaseCodec.java
new file mode 100644
index 0000000..dd286fc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BaseCodec.java
@@ -0,0 +1,31 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+/** An opaque interface for codecs that just reveals the {@link Class} of its objects. */
+interface BaseCodec<T> {
+ /**
+ * Returns the class of the objects serialized/deserialized by this codec.
+ *
+ * <p>This is useful for automatically dispatching to the correct codec, e.g. in {@link
+ * ObjectCodecs} and {@link BaseCodecMap}. It may also be useful for automatically registering
+ * codecs for {@link SkyKey}s and {@link SkyValue}s instead of using the current manual mapping
+ * (b/26186886).
+ */
+ Class<T> getEncodedClass();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/EnumCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/EnumCodec.java
new file mode 100644
index 0000000..523620e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/EnumCodec.java
@@ -0,0 +1,59 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+
+/** Codec for an enum. */
+public class EnumCodec<T extends Enum<T>> implements ObjectCodec<T> {
+
+ private final Class<T> enumClass;
+
+ /**
+ * A cached copy of T.values(), to avoid allocating an array upon every deserialization operation.
+ */
+ private final ImmutableList<T> values;
+
+ public EnumCodec(Class<T> enumClass) {
+ this.enumClass = enumClass;
+ this.values = ImmutableList.copyOf(enumClass.getEnumConstants());
+ }
+
+ @Override
+ public Class<T> getEncodedClass() {
+ return enumClass;
+ }
+
+ @Override
+ public void serialize(T value, CodedOutputStream codedOut) throws IOException {
+ Preconditions.checkNotNull(value, "Enum value for %s is null", enumClass);
+ codedOut.writeEnumNoTag(value.ordinal());
+ }
+
+ @Override
+ public T deserialize(CodedInputStream codedIn) throws SerializationException, IOException {
+ int ordinal = codedIn.readEnum();
+ try {
+ return values.get(ordinal);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new SerializationException(
+ "Invalid ordinal for " + enumClass.getName() + " enum: " + ordinal, e);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/FastStringCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/FastStringCodec.java
new file mode 100644
index 0000000..23bf38b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/FastStringCodec.java
@@ -0,0 +1,140 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import sun.misc.Unsafe;
+
+/**
+ * Similar to {@link StringCodec}, except with deserialization optimized for ascii data. It can
+ * still handle UTF-8, though less efficiently than {@link StringCodec}. Should be used when the
+ * majority of the data passing through will be ascii.
+ */
+public class FastStringCodec implements ObjectCodec<String> {
+ public static final FastStringCodec INSTANCE = new FastStringCodec();
+
+ private static final Unsafe theUnsafe;
+ private static final long STRING_VALUE_OFFSET;
+
+ private static final String EMPTY_STRING = "";
+
+ static {
+ theUnsafe = getUnsafe();
+ try {
+ // String's 'value' field stores its char[]. If this field changes name or type then the
+ // reflective check below will fail. We can reasonably expect our approach to be stable for
+ // now, but things are likely to change in java 9, hopefully in a way which obsoletes this
+ // optimization.
+ Field valueField = String.class.getDeclaredField("value");
+ Class<?> valueFieldType = valueField.getType();
+ if (!valueFieldType.equals(char[].class)) {
+ throw new AssertionError(
+ "Expected String's value field to be char[], but was " + valueFieldType);
+ }
+ STRING_VALUE_OFFSET = theUnsafe.objectFieldOffset(valueField);
+ } catch (NoSuchFieldException | SecurityException e) {
+ throw new AssertionError("Failed to find String's 'value' offset", e);
+ }
+ }
+
+ @Override
+ public Class<String> getEncodedClass() {
+ return String.class;
+ }
+
+ @Override
+ public void serialize(String string, CodedOutputStream codedOut) throws IOException {
+ codedOut.writeStringNoTag(string);
+ }
+
+ @Override
+ public String deserialize(CodedInputStream codedIn) throws IOException {
+ int length = codedIn.readInt32();
+ if (length == 0) {
+ return EMPTY_STRING;
+ }
+
+ char[] maybeDecoded = new char[length];
+ for (int i = 0; i < length; i++) {
+ // Read one byte at a time to avoid creating a new ByteString/copy of the underlying array.
+ byte b = codedIn.readRawByte();
+ // Check highest order bit, if it's set we've crossed into extended ascii/utf8.
+ if ((b & 0x80) == 0) {
+ maybeDecoded[i] = (char) b;
+ } else {
+ // Fail, we encountered a non-ascii byte. Copy what we have so far plus and then the rest
+ // of the data into a buffer and let String's constructor do the UTF-8 decoding work.
+ byte[] decodeFrom = new byte[length];
+ for (int j = 0; j < i; j++) {
+ decodeFrom[j] = (byte) maybeDecoded[j];
+ }
+ decodeFrom[i] = b;
+ for (int j = i + 1; j < length; j++) {
+ decodeFrom[j] = codedIn.readRawByte();
+ }
+ return new String(decodeFrom, StandardCharsets.UTF_8);
+ }
+ }
+
+ try {
+ String result = (String) theUnsafe.allocateInstance(String.class);
+ theUnsafe.putObject(result, STRING_VALUE_OFFSET, maybeDecoded);
+ return result;
+ } catch (Exception e) {
+ // This should only catch InstantiationException, but that makes IntelliJ unhappy for
+ // some reason; it insists that that exception cannot be thrown from here, even though it
+ // is set to JDK 8
+ throw new IllegalStateException("Could not create string", e);
+ }
+ }
+
+ /**
+ * Get a reference to {@link sun.misc.Unsafe} or throw an {@link AssertionError} if failing to do
+ * so. Failure is highly unlikely, but possible if the underlying VM stores unsafe in an
+ * unexpected location.
+ */
+ private static Unsafe getUnsafe() {
+ try {
+ // sun.misc.Unsafe is intentionally difficult to get a hold of - it gives us the power to
+ // do things like access raw memory and segfault the JVM.
+ return AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Unsafe>() {
+ @Override
+ public Unsafe run() throws Exception {
+ Class<Unsafe> unsafeClass = Unsafe.class;
+ // Unsafe usually exists in the field 'theUnsafe', however check all fields
+ // in case it's somewhere else in this VM's version of Unsafe.
+ for (Field f : unsafeClass.getDeclaredFields()) {
+ f.setAccessible(true);
+ Object fieldValue = f.get(null);
+ if (unsafeClass.isInstance(fieldValue)) {
+ return unsafeClass.cast(fieldValue);
+ }
+ }
+ throw new AssertionError("Failed to find sun.misc.Unsafe instance");
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ throw new AssertionError("Unable to get sun.misc.Unsafe", pae);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ImmutableListCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ImmutableListCodec.java
new file mode 100644
index 0000000..1b54d1f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ImmutableListCodec.java
@@ -0,0 +1,62 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+
+/** Encodes a list of elements using a specified {@link ObjectCodec}. */
+public class ImmutableListCodec<T> implements ObjectCodec<ImmutableList<T>> {
+
+ private final ObjectCodec<T> codec;
+
+ public ImmutableListCodec(ObjectCodec<T> codec) {
+ this.codec = codec;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Class<ImmutableList<T>> getEncodedClass() {
+ // Compiler doesn't like cast from Class<ImmutableList> -> Class<ImmutableList<T>>, but it
+ // does allow what we see below. Type is lost at runtime anyway, so while gross this works.
+ return (Class<ImmutableList<T>>) ((Class<?>) ImmutableList.class);
+ }
+
+ @Override
+ public void serialize(ImmutableList<T> list, CodedOutputStream codedOut)
+ throws SerializationException, IOException {
+ codedOut.writeInt32NoTag(list.size());
+ for (T item : list) {
+ codec.serialize(item, codedOut);
+ }
+ }
+
+ @Override
+ public ImmutableList<T> deserialize(CodedInputStream codedIn)
+ throws SerializationException, IOException {
+ int length = codedIn.readInt32();
+ if (length < 0) {
+ throw new SerializationException("Expected non-negative length: " + length);
+ }
+
+ ImmutableList.Builder<T> builder = ImmutableList.builder();
+ for (int i = 0; i < length; i++) {
+ builder.add(codec.deserialize(codedIn));
+ }
+ return builder.build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/LabelCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/LabelCodec.java
new file mode 100644
index 0000000..6f8881d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/LabelCodec.java
@@ -0,0 +1,49 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+
+/** Custom serialization logic for {@link Label}s. */
+public class LabelCodec implements ObjectCodec<Label> {
+ public static final LabelCodec INSTANCE = new LabelCodec();
+
+ // TODO(michajlo): Share single instance of package id codec among all the codecs.
+ private final PackageIdentifierCodec packageIdCodec = new PackageIdentifierCodec();
+ private final ObjectCodec<String> stringCodec = new FastStringCodec();
+
+ @Override
+ public Class<Label> getEncodedClass() {
+ return Label.class;
+ }
+
+ @Override
+ public void serialize(Label label, CodedOutputStream codedOut)
+ throws IOException, SerializationException {
+ packageIdCodec.serialize(label.getPackageIdentifier(), codedOut);
+ stringCodec.serialize(label.getName(), codedOut);
+ }
+
+ @Override
+ public Label deserialize(CodedInputStream codedIn) throws SerializationException, IOException {
+ PackageIdentifier packageId = packageIdCodec.deserialize(codedIn);
+ String name = stringCodec.deserialize(codedIn);
+ return Label.createUnvalidated(packageId, name);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/NotSerializableRuntimeException.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/NotSerializableRuntimeException.java
similarity index 93%
rename from src/main/java/com/google/devtools/build/lib/skyframe/NotSerializableRuntimeException.java
rename to src/main/java/com/google/devtools/build/lib/skyframe/serialization/NotSerializableRuntimeException.java
index c54bd45..78dcc25 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/NotSerializableRuntimeException.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/NotSerializableRuntimeException.java
@@ -11,7 +11,8 @@
// 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.skyframe;
+
+package com.google.devtools.build.lib.skyframe.serialization;
import java.io.Serializable;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodec.java
new file mode 100644
index 0000000..b700923
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodec.java
@@ -0,0 +1,47 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+
+/**
+ * Generic object serialization/deserialization. Implementations should serialize values
+ * deterministically.
+ */
+public interface ObjectCodec<T> extends BaseCodec<T> {
+
+ /**
+ * Serializes {@code obj}, inverse of {@link #deserialize(CodedInputStream)}.
+ *
+ * @param obj the object to serialize
+ * @param codedOut the {@link CodedOutputStream} to write this object into. Implementations need
+ * not call {@link CodedOutputStream#flush()}, this should be handled by the caller.
+ * @throws SerializationException on failure to serialize
+ * @throws IOException on {@link IOException} during serialization
+ */
+ void serialize(T obj, CodedOutputStream codedOut) throws SerializationException, IOException;
+
+ /**
+ * Deserializes from {@code codedIn}, inverse of {@link #serialize(Object, CodedOutputStream)}.
+ *
+ * @param codedIn the {@link CodedInputStream} to read the serialized object from
+ * @return the object deserialized from {@code codedIn}
+ * @throws SerializationException on failure to deserialize
+ * @throws IOException on {@link IOException} during deserialization
+ */
+ T deserialize(CodedInputStream codedIn) throws SerializationException, IOException;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PackageIdentifierCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PackageIdentifierCodec.java
new file mode 100644
index 0000000..73aede6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PackageIdentifierCodec.java
@@ -0,0 +1,49 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+
+/** Custom serialization logic for {@link PackageIdentifier}s. */
+class PackageIdentifierCodec implements ObjectCodec<PackageIdentifier> {
+
+ private final RepositoryNameCodec repoNameCodec = new RepositoryNameCodec();
+ private final PathFragmentCodec pathFragmentCodec = new PathFragmentCodec();
+
+ @Override
+ public Class<PackageIdentifier> getEncodedClass() {
+ return PackageIdentifier.class;
+ }
+
+ @Override
+ public void serialize(PackageIdentifier pkgId, CodedOutputStream codedOut)
+ throws IOException, SerializationException {
+ repoNameCodec.serialize(pkgId.getRepository(), codedOut);
+ pathFragmentCodec.serialize(pkgId.getPackageFragment(), codedOut);
+ }
+
+ @Override
+ public PackageIdentifier deserialize(CodedInputStream codedIn)
+ throws IOException, SerializationException {
+ RepositoryName repoName = repoNameCodec.deserialize(codedIn);
+ PathFragment pathFragment = pathFragmentCodec.deserialize(codedIn);
+ return PackageIdentifier.create(repoName, pathFragment);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PathFragmentCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PathFragmentCodec.java
new file mode 100644
index 0000000..7438f6d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PathFragmentCodec.java
@@ -0,0 +1,55 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+
+/** Custom serialization for {@link PathFragment}s. */
+class PathFragmentCodec implements ObjectCodec<PathFragment> {
+
+ private final ObjectCodec<String> stringCodec = new FastStringCodec();
+
+ @Override
+ public Class<PathFragment> getEncodedClass() {
+ return PathFragment.class;
+ }
+
+ @Override
+ public void serialize(PathFragment pathFragment, CodedOutputStream codedOut)
+ throws IOException, SerializationException {
+ codedOut.writeInt32NoTag(pathFragment.getDriveLetter());
+ codedOut.writeBoolNoTag(pathFragment.isAbsolute());
+ codedOut.writeInt32NoTag(pathFragment.segmentCount());
+ for (int i = 0; i < pathFragment.segmentCount(); i++) {
+ stringCodec.serialize(pathFragment.getSegment(i), codedOut);
+ }
+ }
+
+ @Override
+ public PathFragment deserialize(CodedInputStream codedIn)
+ throws IOException, SerializationException {
+ char driveLetter = (char) codedIn.readInt32();
+ boolean isAbsolute = codedIn.readBool();
+ int segmentCount = codedIn.readInt32();
+ String[] segments = new String[segmentCount];
+ for (int i = 0; i < segmentCount; i++) {
+ segments[i] = stringCodec.deserialize(codedIn);
+ }
+ return PathFragment.create(driveLetter, isAbsolute, segments);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/RepositoryNameCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/RepositoryNameCodec.java
new file mode 100644
index 0000000..c9d0976
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/RepositoryNameCodec.java
@@ -0,0 +1,56 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+
+/** Custom serialization for {@link RepositoryName}. */
+class RepositoryNameCodec implements ObjectCodec<RepositoryName> {
+
+ @Override
+ public Class<RepositoryName> getEncodedClass() {
+ return RepositoryName.class;
+ }
+
+ @Override
+ public void serialize(RepositoryName repoName, CodedOutputStream codedOut) throws IOException {
+ boolean isMain = repoName.isMain();
+ // Main is by far the most common. Use boolean to short-circuit string encoding on
+ // serialization and byte[]/ByteString creation on deserialization.
+ codedOut.writeBoolNoTag(isMain);
+ if (!isMain) {
+ codedOut.writeStringNoTag(repoName.getName());
+ }
+ }
+
+ @Override
+ public RepositoryName deserialize(CodedInputStream codedIn)
+ throws SerializationException, IOException {
+ boolean isMain = codedIn.readBool();
+ if (isMain) {
+ return RepositoryName.MAIN;
+ }
+ try {
+ // We can read the string we wrote back as bytes to avoid string decoding/copying.
+ return SerializationCommonUtils.deserializeRepoName(codedIn.readBytes());
+ } catch (LabelSyntaxException e) {
+ throw new SerializationException("Failed to deserialize RepositoryName", e);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationCommonUtils.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationCommonUtils.java
new file mode 100644
index 0000000..50d81b4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationCommonUtils.java
@@ -0,0 +1,41 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.protobuf.ByteString;
+
+/** Common utilities for serialization. */
+public class SerializationCommonUtils {
+ public static final ImmutableListCodec<String> STRING_LIST_CODEC =
+ new ImmutableListCodec<>(FastStringCodec.INSTANCE);
+ private static final ByteString DEFAULT_REPOSITORY =
+ ByteString.copyFromUtf8(RepositoryName.DEFAULT.getName());
+ private static final ByteString MAIN_REPOSITORY =
+ ByteString.copyFromUtf8(RepositoryName.MAIN.getName());
+
+ public static RepositoryName deserializeRepoName(ByteString repoNameBytes)
+ throws LabelSyntaxException {
+ // We expect MAIN_REPOSITORY the vast majority of the time, so check for it first.
+ if (repoNameBytes.equals(MAIN_REPOSITORY)) {
+ return RepositoryName.MAIN;
+ } else if (repoNameBytes.equals(DEFAULT_REPOSITORY)) {
+ return RepositoryName.DEFAULT;
+ } else {
+ return RepositoryName.create(repoNameBytes.toStringUtf8());
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationException.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationException.java
new file mode 100644
index 0000000..1477e3a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationException.java
@@ -0,0 +1,50 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import java.io.NotSerializableException;
+
+/** Exception signaling a failure to Serialize or Deserialize an Object. */
+public class SerializationException extends Exception {
+
+ public SerializationException(String msg) {
+ super(msg);
+ }
+
+ public SerializationException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+ // No SerializationException(Throwable) overload because serialization errors should always
+ // provide as much context as possible.
+
+ /**
+ * {@link SerializationException} indicating that Blaze has no serialization schema for an object
+ * or type of object.
+ */
+ public static class NoCodecException extends SerializationException {
+ NoCodecException(String message) {
+ super(message);
+ }
+
+ NoCodecException(String message, NotSerializableException e) {
+ super(message, e);
+ }
+
+ NoCodecException(String message, NotSerializableRuntimeException e) {
+ super(message, e);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/StringCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/StringCodec.java
new file mode 100644
index 0000000..4b1bc70
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/StringCodec.java
@@ -0,0 +1,38 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+
+/** Dead-simple serialization for {@link String}s. */
+public class StringCodec implements ObjectCodec<String> {
+
+ @Override
+ public Class<String> getEncodedClass() {
+ return String.class;
+ }
+
+ @Override
+ public void serialize(String str, CodedOutputStream codedOut) throws IOException {
+ codedOut.writeStringNoTag(str);
+ }
+
+ @Override
+ public String deserialize(CodedInputStream codedIn) throws IOException {
+ return codedIn.readString();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/apple/AppleConfigurationSerializationTest.java b/src/test/java/com/google/devtools/build/lib/rules/apple/AppleConfigurationSerializationTest.java
new file mode 100644
index 0000000..532dac6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/apple/AppleConfigurationSerializationTest.java
@@ -0,0 +1,96 @@
+// Copyright 2017 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.rules.apple;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.skyframe.serialization.AbstractObjectCodecTest;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for serialization of {@link AppleConfiguration}. */
+@RunWith(JUnit4.class)
+public class AppleConfigurationSerializationTest
+ extends AbstractObjectCodecTest<AppleConfiguration> {
+ public AppleConfigurationSerializationTest() {
+ super(
+ new ObjectCodec<AppleConfiguration>() {
+ @Override
+ public void serialize(AppleConfiguration obj, CodedOutputStream codedOut)
+ throws SerializationException, IOException {
+ obj.serialize(codedOut);
+ }
+
+ @Override
+ public AppleConfiguration deserialize(CodedInputStream codedIn)
+ throws SerializationException, IOException {
+ return AppleConfiguration.deserialize(codedIn);
+ }
+
+ @Override
+ public Class<AppleConfiguration> getEncodedClass() {
+ return AppleConfiguration.class;
+ }
+ },
+ subject());
+ }
+
+ private static AppleConfiguration subject() {
+ AppleCommandLineOptions options = new AppleCommandLineOptions();
+ options.mandatoryMinimumVersion = false;
+ options.xcodeVersion = DottedVersion.fromString("1.0");
+ options.iosSdkVersion = DottedVersion.fromString("2.0");
+ options.watchOsSdkVersion = DottedVersion.fromString("3.0");
+ options.tvOsSdkVersion = DottedVersion.fromString("4.0");
+ options.macOsSdkVersion = DottedVersion.fromString("5.0");
+ options.iosMinimumOs = DottedVersion.fromString("6.1beta3.7");
+ options.watchosMinimumOs = DottedVersion.fromString("7.0");
+ options.tvosMinimumOs = DottedVersion.fromString("8.0");
+ options.macosMinimumOs = DottedVersion.fromString("9.0");
+ options.iosCpu = "ioscpu1";
+ options.appleCrosstoolTop = Label.parseAbsoluteUnchecked("//apple/crosstool:top");
+ options.applePlatformType = ApplePlatform.PlatformType.WATCHOS;
+ options.appleSplitCpu = "appleSplitCpu1";
+ options.configurationDistinguisher =
+ AppleConfiguration.ConfigurationDistinguisher.APPLEBIN_TVOS;
+ options.iosMultiCpus = ImmutableList.of("iosMultiCpu1", "iosMultiCpu2");
+ options.watchosCpus = ImmutableList.of("watchosCpu1", "watchosCpu2", "watchosCpu3");
+ options.tvosCpus = ImmutableList.of("tvosCpu1");
+ options.macosCpus = ImmutableList.of();
+ options.defaultProvisioningProfile = Label.parseAbsoluteUnchecked("//default/provisioning");
+ options.xcodeVersionConfig = Label.parseAbsoluteUnchecked("//xcode/version:config");
+ options.xcodeToolchain = "xcodeToolchain1";
+ options.appleBitcodeMode = AppleCommandLineOptions.AppleBitcodeMode.EMBEDDED_MARKERS;
+ options.enableAppleCrosstoolTransition = false;
+ options.targetUsesAppleCrosstool = true;
+ return new AppleConfiguration(
+ options,
+ "iosCpuArg",
+ DottedVersion.fromString("10.0"),
+ DottedVersion.fromString("11.0"),
+ DottedVersion.fromString("12.0"),
+ DottedVersion.fromString("13.0"),
+ DottedVersion.fromString("14.0"),
+ DottedVersion.fromString("15.0"),
+ DottedVersion.fromString("16.0"),
+ DottedVersion.fromString("17.0"),
+ DottedVersion.fromString("18.0"));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/apple/BUILD b/src/test/java/com/google/devtools/build/lib/rules/apple/BUILD
index 2b85ae5..89015b0 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/apple/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/apple/BUILD
@@ -4,9 +4,38 @@
visibility = ["//src/test/java/com/google/devtools/build/lib:__pkg__"],
)
+SERIALIZATION_TEST_SRCS = ["AppleConfigurationSerializationTest.java"]
+
+java_library(
+ name = "AppleConfigurationSerializationTest_lib",
+ testonly = 1,
+ srcs = SERIALIZATION_TEST_SRCS,
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/lib/rules/apple",
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
+ "//src/test/java/com/google/devtools/build/lib/skyframe/serialization:serialization-test-base",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party/protobuf:protobuf_java",
+ ],
+)
+
+java_test(
+ name = "AppleConfigurationSerializationTest",
+ test_class = "com.google.devtools.build.lib.AllTests",
+ runtime_deps = [
+ ":AppleConfigurationSerializationTest_lib",
+ "//src/test/java/com/google/devtools/build/lib:test_runner",
+ ],
+)
+
java_library(
name = "AppleRulesTests_lib",
- srcs = glob(["*.java"]),
+ srcs = glob(
+ ["*.java"],
+ exclude = SERIALIZATION_TEST_SRCS,
+ ),
deps = [
"//src/main/java/com/google/devtools/build/lib:build-base",
"//src/main/java/com/google/devtools/build/lib/rules/apple",
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
index fd6714e..8bc5a45 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -1,6 +1,9 @@
filegroup(
name = "srcs",
- srcs = glob(["**"]) + ["//src/test/java/com/google/devtools/build/lib/skyframe/packages:srcs"],
+ srcs = glob(["**"]) + [
+ "//src/test/java/com/google/devtools/build/lib/skyframe/packages:srcs",
+ "//src/test/java/com/google/devtools/build/lib/skyframe/serialization:srcs",
+ ],
visibility = ["//src/test/java/com/google/devtools/build/lib:__pkg__"],
)
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/AbstractObjectCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/AbstractObjectCodecTest.java
new file mode 100644
index 0000000..4fa00b3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/AbstractObjectCodecTest.java
@@ -0,0 +1,107 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.CodedInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Common ObjectCodec tests. */
+public abstract class AbstractObjectCodecTest<T> {
+
+ @Nullable protected ObjectCodec<T> underTest;
+ @Nullable protected ImmutableList<T> subjects;
+
+ /**
+ * Override to false to skip testDeserializeBadDataThrowsSerializationException(). Codecs that
+ * cannot distinguish good and bad data should do this.
+ */
+ protected boolean shouldTestDeserializeBadData = true;
+
+ /** Construct with the given codec and subjects. */
+ protected AbstractObjectCodecTest(
+ ObjectCodec<T> underTest, @SuppressWarnings("unchecked") T... subjects) {
+ this.underTest = underTest;
+ this.subjects = ImmutableList.copyOf(subjects);
+ }
+
+ /**
+ * Construct without a codec and subjects. They must be set in the subclass's constructor instead.
+ *
+ * <p>This is useful if the logic for creating the codec and/or subjects is non-trivial. Using
+ * this super constructor, the logic can be placed in the subclass's constructor; whereas if using
+ * the above super constructor, the logic must be factored into a static method.
+ */
+ protected AbstractObjectCodecTest() {}
+
+ @Before
+ public void checkInitialized() {
+ Preconditions.checkNotNull(underTest);
+ Preconditions.checkNotNull(subjects);
+ }
+
+ @Test
+ public void testSuccessfulSerializationDeserialization() throws Exception {
+ for (T subject : subjects) {
+ byte[] serialized = toBytes(subject);
+ Object deserialized = fromBytes(serialized);
+ verifyDeserialization(deserialized, subject);
+ }
+ }
+
+ @Test
+ public void testSerializationRoundTripBytes() throws Exception {
+ for (T subject : subjects) {
+ byte[] serialized = toBytes(subject);
+ T deserialized = fromBytes(serialized);
+ byte[] reserialized = toBytes(deserialized);
+ assertThat(reserialized).isEqualTo(serialized);
+ }
+ }
+
+ @Test
+ public void testDeserializeBadDataThrowsSerializationException() {
+ if (!shouldTestDeserializeBadData) {
+ return;
+ }
+ try {
+ underTest.deserialize(CodedInputStream.newInstance("junk".getBytes(StandardCharsets.UTF_8)));
+ fail("Expected exception");
+ } catch (SerializationException | IOException e) {
+ // Expected.
+ }
+ }
+
+ protected T fromBytes(byte[] bytes) throws SerializationException, IOException {
+ return TestUtils.fromBytes(underTest, bytes);
+ }
+
+ /** Serialize subject using the {@link ObjectCodec} under test. */
+ protected byte[] toBytes(T subject) throws IOException, SerializationException {
+ return TestUtils.toBytes(underTest, subject);
+ }
+
+ protected void verifyDeserialization(Object deserialized, T subject) {
+ assertThat(deserialized).isEqualTo(subject);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/BUILD b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
new file mode 100644
index 0000000..4111713
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
@@ -0,0 +1,63 @@
+TEST_BASE_FILES = [
+ "AbstractObjectCodecTest.java",
+ "TestUtils.java",
+]
+
+java_library(
+ name = "serialization-test-base",
+ testonly = 1,
+ srcs = TEST_BASE_FILES,
+ visibility = [
+ "//src/test/java/com/google/devtools/build/lib:__subpackages__",
+ ],
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ "//third_party:junit4",
+ "//third_party:truth",
+ "//third_party/protobuf:protobuf_java",
+ ],
+)
+
+java_library(
+ name = "serialization-tests",
+ testonly = 1,
+ srcs = glob(
+ [
+ "*.java",
+ ],
+ exclude = TEST_BASE_FILES,
+ ),
+ deps = [
+ ":serialization-test-base",
+ "//src/main/java/com/google/devtools/build/lib:build-base",
+ "//src/main/java/com/google/devtools/build/lib:packages-internal",
+ "//src/main/java/com/google/devtools/build/lib:util",
+ "//src/main/java/com/google/devtools/build/lib:vfs",
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
+ "//third_party:guava",
+ "//third_party:guava-testlib",
+ "//third_party:jsr305",
+ "//third_party:junit4",
+ "//third_party:mockito",
+ "//third_party:truth",
+ ],
+)
+
+java_test(
+ name = "SerializationTests",
+ test_class = "com.google.devtools.build.lib.AllTests",
+ runtime_deps = [
+ ":serialization-tests",
+ "//src/test/java/com/google/devtools/build/lib:test_runner",
+ ],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = [
+ "//src/test/java/com/google/devtools/build/lib/skyframe:__subpackages__",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/EnumCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/EnumCodecTest.java
new file mode 100644
index 0000000..c71d554
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/EnumCodecTest.java
@@ -0,0 +1,33 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link EnumCodec}. */
+@RunWith(JUnit4.class)
+public class EnumCodecTest extends AbstractObjectCodecTest<EnumCodecTest.DummyEnum> {
+
+ /** Test enum (dummy comment for lint). */
+ public enum DummyEnum {
+ DUMB_ONE,
+ DUMB_TWO
+ }
+
+ public EnumCodecTest() {
+ super(new EnumCodec<>(DummyEnum.class), DummyEnum.DUMB_ONE, DummyEnum.DUMB_TWO);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/FastStringCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/FastStringCodecTest.java
new file mode 100644
index 0000000..6aef73e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/FastStringCodecTest.java
@@ -0,0 +1,53 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link FastStringCodec}. */
+@RunWith(JUnit4.class)
+public class FastStringCodecTest extends AbstractObjectCodecTest<String> {
+
+ public FastStringCodecTest() {
+ super(
+ new FastStringCodec(),
+ "ow now brown cow. ow now brown cow",
+ "(╯°□°)╯︵┻━┻ string with utf8/ascii",
+ "string with ascii/utf8 (╯°□°)╯︵┻━┻",
+ "last character utf8 ╯",
+ "last char only non-ascii ƒ",
+ "ƒ",
+ "╯",
+ "",
+ Character.toString((char) 0xc3));;
+ }
+
+ // hashCode is stored in String. Because we're using Unsafe to bypass standard String
+ // constructors, make sure it still works.
+ @Test
+ public void testEqualsAndHashCodePreserved() throws Exception {
+ String original1 = "hello world";
+ String original2 = "dlrow olleh";
+
+ // Equals tester tests equals and hash code.
+ new EqualsTester()
+ .addEqualityGroup(original1, fromBytes(toBytes(original1)))
+ .addEqualityGroup(original2, fromBytes(toBytes(original2)))
+ .testEquals();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/ImmutableListCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/ImmutableListCodecTest.java
new file mode 100644
index 0000000..23f3855
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/ImmutableListCodecTest.java
@@ -0,0 +1,33 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link ImmutableListCodec}. */
+@RunWith(JUnit4.class)
+public class ImmutableListCodecTest extends AbstractObjectCodecTest<ImmutableList<String>> {
+
+ @SuppressWarnings("unchecked")
+ public ImmutableListCodecTest() {
+ super(
+ new ImmutableListCodec<>(new StringCodec()),
+ ImmutableList.of(),
+ ImmutableList.of("foo"),
+ ImmutableList.of("bar", "baz"));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/LabelCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/LabelCodecTest.java
new file mode 100644
index 0000000..4dc1b14
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/LabelCodecTest.java
@@ -0,0 +1,29 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Basic tests for {@link LabelCodec}. */
+@RunWith(JUnit4.class)
+public class LabelCodecTest extends AbstractObjectCodecTest<Label> {
+
+ public LabelCodecTest() throws LabelSyntaxException {
+ super(new LabelCodec(), Label.parseAbsolute("//foo/bar:baz"));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/PackageIdentifierCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/PackageIdentifierCodecTest.java
new file mode 100644
index 0000000..9bc860a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/PackageIdentifierCodecTest.java
@@ -0,0 +1,30 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Basic tests for {@link PackageIdentifierCodec}. */
+@RunWith(JUnit4.class)
+public class PackageIdentifierCodecTest extends AbstractObjectCodecTest<PackageIdentifier> {
+
+ public PackageIdentifierCodecTest() throws Exception {
+ super(
+ new PackageIdentifierCodec(), PackageIdentifier.create("@foo", PathFragment.create("bar")));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/PathFragmentCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/PathFragmentCodecTest.java
new file mode 100644
index 0000000..4ce91f8
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/PathFragmentCodecTest.java
@@ -0,0 +1,28 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.devtools.build.lib.vfs.PathFragment;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Basic tests for {@link PathFragmentCodec}. */
+@RunWith(JUnit4.class)
+public class PathFragmentCodecTest extends AbstractObjectCodecTest<PathFragment> {
+
+ public PathFragmentCodecTest() {
+ super(new PathFragmentCodec(), PathFragment.create("/a/path/fragment/with/lots/of/parts"));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/RepositoryNameCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/RepositoryNameCodecTest.java
new file mode 100644
index 0000000..97e38ed
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/RepositoryNameCodecTest.java
@@ -0,0 +1,54 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import static org.junit.Assert.fail;
+
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link RepositoryNameCodec}. */
+@RunWith(JUnit4.class)
+public class RepositoryNameCodecTest extends AbstractObjectCodecTest<RepositoryName> {
+
+ // Set 0th byte (isMain) false, so that we'll try to read a string from the rest of the
+ // data and fail.
+ public static final byte[] INVALID_ENCODED_REPOSITORY_NAME = new byte[] {0, 10, 9, 8, 7};
+
+ public RepositoryNameCodecTest() throws LabelSyntaxException {
+ super(
+ new RepositoryNameCodec(),
+ RepositoryName.create(RepositoryName.DEFAULT.getName()),
+ RepositoryName.create(RepositoryName.MAIN.getName()),
+ RepositoryName.create("@externalandshouldntexistinthisworkspace"));
+ }
+
+ // The default bad data test from AbstractObjectCodecTest doesn't play nice with boolean prefixed
+ // encodings.
+ @Override
+ @Test
+ public void testDeserializeBadDataThrowsSerializationException() {
+ try {
+ fromBytes(INVALID_ENCODED_REPOSITORY_NAME);
+ fail("Expected exception");
+ } catch (SerializationException | IOException e) {
+ // Expected.
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/StringCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/StringCodecTest.java
new file mode 100644
index 0000000..423ffdc
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/StringCodecTest.java
@@ -0,0 +1,27 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Basic tests for {@link StringCodec}. */
+@RunWith(JUnit4.class)
+public class StringCodecTest extends AbstractObjectCodecTest<String> {
+
+ public StringCodecTest() {
+ super(new StringCodec(), "usually precomputed and supports weird unicodes: (╯°□°)╯︵┻━┻ ");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/TestUtils.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/TestUtils.java
new file mode 100644
index 0000000..df84b38
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/TestUtils.java
@@ -0,0 +1,42 @@
+// Copyright 2017 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.skyframe.serialization;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/** Helpers for serialization tests. */
+class TestUtils {
+
+ private TestUtils() {}
+
+ /** Serialize a value to a new byte array. */
+ static <T> byte[] toBytes(ObjectCodec<T> codec, T value)
+ throws IOException, SerializationException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ CodedOutputStream codedOut = CodedOutputStream.newInstance(bytes);
+ codec.serialize(value, codedOut);
+ codedOut.flush();
+ return bytes.toByteArray();
+ }
+
+ /** Deserialize a value from a byte array. */
+ static <T> T fromBytes(ObjectCodec<T> codec, byte[] bytes)
+ throws SerializationException, IOException {
+ return codec.deserialize(CodedInputStream.newInstance(bytes));
+ }
+}