Initial @AutoCodec implementation.

PiperOrigin-RevId: 178994972
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
index 6bb0e5f..caa5c5e 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
@@ -61,15 +61,16 @@
    * Package names that aren't made relative to the current repository because they mean special
    * things to Bazel.
    */
-  public static final ImmutableSet<PathFragment> ABSOLUTE_PACKAGE_NAMES = ImmutableSet.of(
-      // Used for select
-      PathFragment.create("conditions"),
-      // dependencies that are a function of the configuration
-      PathFragment.create("tools/defaults"),
-      // Visibility is labels aren't actually targets
-      PathFragment.create("visibility"),
-      // There is only one //external package
-      Label.EXTERNAL_PACKAGE_NAME);
+  public static final ImmutableSet<PathFragment> ABSOLUTE_PACKAGE_NAMES =
+      ImmutableSet.of(
+          // Used for select
+          PathFragment.create("conditions"),
+          // dependencies that are a function of the configuration
+          PathFragment.create("tools/defaults"),
+          // Visibility is labels aren't actually targets
+          PathFragment.create("visibility"),
+          // There is only one //external package
+          Label.EXTERNAL_PACKAGE_NAME);
 
   public static final PackageIdentifier EXTERNAL_PACKAGE_IDENTIFIER =
       PackageIdentifier.createInMainRepo(EXTERNAL_PACKAGE_NAME);
@@ -78,10 +79,13 @@
   public static final SkyFunctionName TRANSITIVE_TRAVERSAL =
       SkyFunctionName.create("TRANSITIVE_TRAVERSAL");
 
+  public static final LabelCodec CODEC = LabelCodec.INSTANCE;
+
   private static final Interner<Label> LABEL_INTERNER = BlazeInterners.newWeakInterner();
 
   /**
    * Factory for Labels from absolute string form. e.g.
+   *
    * <pre>
    * //foo/bar
    * //foo/bar:quux
@@ -98,6 +102,7 @@
 
   /**
    * Factory for Labels from absolute string form. e.g.
+   *
    * <pre>
    * //foo/bar
    * //foo/bar:quux
@@ -106,8 +111,7 @@
    * {@literal @}foo//bar:baz
    * </pre>
    *
-   * @param defaultToMain Treat labels in the default repository as being in the main
-   *   one instead.
+   * @param defaultToMain Treat labels in the default repository as being in the main one instead.
    */
   public static Label parseAbsolute(String absName, boolean defaultToMain)
       throws LabelSyntaxException {
@@ -156,11 +160,10 @@
   /**
    * Factory for Labels from separate components.
    *
-   * @param packageName The name of the package.  The package name does
-   *   <b>not</b> include {@code //}.  Must be valid according to
-   *   {@link LabelValidator#validatePackageName}.
-   * @param targetName The name of the target within the package.  Must be
-   *   valid according to {@link LabelValidator#validateTargetName}.
+   * @param packageName The name of the package. The package name does <b>not</b> include {@code
+   *     //}. Must be valid according to {@link LabelValidator#validatePackageName}.
+   * @param targetName The name of the target within the package. Must be valid according to {@link
+   *     LabelValidator#validateTargetName}.
    * @throws LabelSyntaxException if either of the arguments was invalid.
    */
   public static Label create(String packageName, String targetName) throws LabelSyntaxException {
@@ -168,8 +171,8 @@
   }
 
   /**
-   * Similar factory to above, but takes a package identifier to allow external repository labels
-   * to be created.
+   * Similar factory to above, but takes a package identifier to allow external repository labels to
+   * be created.
    */
   public static Label create(PackageIdentifier packageId, String targetName)
       throws LabelSyntaxException {
@@ -182,7 +185,6 @@
    * <p>Only call this method if you know what you're doing; in particular, don't call it on
    * arbitrary {@code targetName} inputs
    */
-
   public static Label createUnvalidated(PackageIdentifier packageId, String targetName) {
     return LABEL_INTERNER.intern(new Label(packageId, StringCanonicalizer.intern(targetName)));
   }
@@ -190,6 +192,7 @@
   /**
    * Resolves a relative label using a workspace-relative path to the current working directory. The
    * method handles these cases:
+   *
    * <ul>
    *   <li>The label is absolute.
    *   <li>The label starts with a colon.
@@ -246,8 +249,8 @@
   }
 
   /**
-   * Validates the given package name and returns a canonical {@link PackageIdentifier} instance
-   * if it is valid. Otherwise it throws a SyntaxException.
+   * Validates the given package name and returns a canonical {@link PackageIdentifier} instance if
+   * it is valid. Otherwise it throws a SyntaxException.
    */
   private static PackageIdentifier validatePackageName(String packageIdentifier, String name)
       throws LabelSyntaxException {
@@ -285,9 +288,7 @@
     this.hashCode = hashCode(this.name, this.packageIdentifier);
   }
 
-  /**
-   * A specialization of Arrays.HashCode() that does not require constructing a 2-element array.
-   */
+  /** A specialization of Arrays.HashCode() that does not require constructing a 2-element array. */
   private static final int hashCode(Object obj1, Object obj2) {
     int result = 31 + (obj1 == null ? 0 : obj1.hashCode());
     return 31 * result + (obj2 == null ? 0 : obj2.hashCode());
@@ -309,10 +310,14 @@
    * Returns the name of the package in which this rule was declared (e.g. {@code
    * //file/base:fileutils_test} returns {@code file/base}).
    */
-  @SkylarkCallable(name = "package", structField = true,
-      doc = "The package part of this label. "
-      + "For instance:<br>"
-      + "<pre class=language-python>Label(\"//pkg/foo:abc\").package == \"pkg/foo\"</pre>")
+  @SkylarkCallable(
+    name = "package",
+    structField = true,
+    doc =
+        "The package part of this label. "
+            + "For instance:<br>"
+            + "<pre class=language-python>Label(\"//pkg/foo:abc\").package == \"pkg/foo\"</pre>"
+  )
   public String getPackageName() {
     return packageIdentifier.getPackageFragment().getPathString();
   }
@@ -322,11 +327,15 @@
    * {@code @repo//pkg:b}, it will returns {@code external/repo/pkg} and for label {@code //pkg:a},
    * it will returns an empty string.
    */
-  @SkylarkCallable(name = "workspace_root", structField = true,
-      doc = "Returns the execution root for the workspace of this label, relative to the execroot. "
-      + "For instance:<br>"
-      + "<pre class=language-python>Label(\"@repo//pkg/foo:abc\").workspace_root =="
-      + " \"external/repo\"</pre>")
+  @SkylarkCallable(
+    name = "workspace_root",
+    structField = true,
+    doc =
+        "Returns the execution root for the workspace of this label, relative to the execroot. "
+            + "For instance:<br>"
+            + "<pre class=language-python>Label(\"@repo//pkg/foo:abc\").workspace_root =="
+            + " \"external/repo\"</pre>"
+  )
   public String getWorkspaceRoot() {
     return packageIdentifier.getRepository().getSourceRoot().toString();
   }
@@ -343,21 +352,23 @@
     return packageIdentifier.getPackageFragment();
   }
 
-  /**
-   * Returns the label as a path fragment, using the package and the label name.
-   */
+  /** Returns the label as a path fragment, using the package and the label name. */
   public PathFragment toPathFragment() {
     return packageIdentifier.getPackageFragment().getRelative(name);
   }
 
   /**
-   * Returns the name by which this rule was declared (e.g. {@code //foo/bar:baz}
-   * returns {@code baz}).
+   * Returns the name by which this rule was declared (e.g. {@code //foo/bar:baz} returns {@code
+   * baz}).
    */
-  @SkylarkCallable(name = "name", structField = true,
-      doc = "The name of this label within the package. "
-      + "For instance:<br>"
-      + "<pre class=language-python>Label(\"//pkg/foo:abc\").name == \"abc\"</pre>")
+  @SkylarkCallable(
+    name = "name",
+    structField = true,
+    doc =
+        "The name of this label within the package. "
+            + "For instance:<br>"
+            + "<pre class=language-python>Label(\"//pkg/foo:abc\").name == \"abc\"</pre>"
+  )
   public String getName() {
     return name;
   }
@@ -382,13 +393,16 @@
   }
 
   public String getUnambiguousCanonicalForm() {
-    return packageIdentifier.getRepository() + "//" + packageIdentifier.getPackageFragment()
-        + ":" + name;
+    return packageIdentifier.getRepository()
+        + "//"
+        + packageIdentifier.getPackageFragment()
+        + ":"
+        + name;
   }
 
   /**
-   * Renders this label in canonical form, except with labels in the main and default
-   * repositories conflated.
+   * Renders this label in canonical form, except with labels in the main and default repositories
+   * conflated.
    */
   public String getDefaultCanonicalForm() {
     String repository;
@@ -397,8 +411,7 @@
     } else {
       repository = packageIdentifier.getRepository().getName();
     }
-    return repository + "//" + packageIdentifier.getPackageFragment()
-        + ":" + name;
+    return repository + "//" + packageIdentifier.getPackageFragment() + ":" + name;
   }
 
   /**
@@ -517,9 +530,7 @@
     return hashCode;
   }
 
-  /**
-   * Two labels are equal iff both their name and their package name are equal.
-   */
+  /** Two labels are equal iff both their name and their package name are equal. */
   @Override
   public boolean equals(Object other) {
     if (!(other instanceof Label)) {
@@ -527,7 +538,8 @@
     }
     Label otherLabel = (Label) other;
     // Perform the equality comparisons in order from least likely to most likely.
-    return hashCode == otherLabel.hashCode && name.equals(otherLabel.name)
+    return hashCode == otherLabel.hashCode
+        && name.equals(otherLabel.name)
         && packageIdentifier.equals(otherLabel.packageIdentifier);
   }
 
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 c03e091..10291a6 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
@@ -23,6 +23,7 @@
 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.skyframe.serialization.ObjectCodec;
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
@@ -76,6 +77,9 @@
   /** A list of targets of which all test suites should be expanded. */
   @ThreadSafe
   static final class TestSuiteExpansionKey implements SkyKey {
+    public static final ObjectCodec<TestSuiteExpansionKey> CODEC =
+        TestSuiteExpansionKeyCodec.INSTANCE;
+
     private final ImmutableSortedSet<Label> targets;
 
     public TestSuiteExpansionKey(ImmutableSortedSet<Label> targets) {
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
index bb25cbc..af255e7 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
@@ -3,6 +3,7 @@
 filegroup(
     name = "srcs",
     srcs = glob(["**"]) + [
+        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:srcs",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils:srcs",
     ],
 )
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java
new file mode 100644
index 0000000..17e3224
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.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.autocodec;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies that AutoCodec should generate a codec implementation for the annotated abstract class.
+ *
+ * <p>Example:
+ *
+ * <pre>{@code
+ * @AutoCodec
+ * abstract class Codec implements ObjectCodec<Target> {
+ *   static Codec create() {
+ *     return new AutoCodec_Target();
+ *   }
+ * }
+ * }</pre>
+ *
+ * The {@code AutoCodec_} prefix is added to the {@Target} to obtain the generated class name.
+ */
+@Target(ElementType.TYPE)
+public @interface AutoCodec {
+  /**
+   * AutoCodec recursively derives a codec using the public interfaces of the class.
+   *
+   * <p>Specific strategies are described below.
+   */
+  public static enum Strategy {
+    /**
+     * Uses the constructor to infer serialization code.
+     *
+     * <p>Each constructor parameter is expected to have a corresponding getter. These pairs are
+     * used for serialization and deserialization.
+     */
+    CONSTRUCTOR,
+    // TODO(shahan): Add a strategy that serializes from public members.
+  }
+
+  Strategy strategy() default Strategy.CONSTRUCTOR;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java
new file mode 100644
index 0000000..29df3cb
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java
@@ -0,0 +1,323 @@
+// 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.autocodec;
+
+import com.google.auto.service.AutoService;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+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 com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.Diagnostic;
+
+/**
+ * Javac annotation processor (compiler plugin) for generating {@link ObjectCodec} implementations.
+ *
+ * <p>User code must never reference this class.
+ */
+@AutoService(Processor.class)
+public class AutoCodecProcessor extends AbstractProcessor {
+  // Synthesized classes will be prefixed with AutoCodec_.
+  public static final String GENERATED_CLASS_NAME_PREFIX = "AutoCodec";
+  private static final Class<AutoCodec> ANNOTATION = AutoCodec.class;
+
+  /**
+   * Passing {@code --javacopt=-Aautocodec_print_generated} to {@code blaze build} tells AutoCodec
+   * to print the generated code.
+   */
+  private static final String PRINT_GENERATED_OPTION = "autocodec_print_generated";
+
+  private ProcessingEnvironment env; // Captured from `init` method.
+
+  @Override
+  public Set<String> getSupportedOptions() {
+    return ImmutableSet.of(PRINT_GENERATED_OPTION);
+  }
+
+  @Override
+  public Set<String> getSupportedAnnotationTypes() {
+    return ImmutableSet.of(ANNOTATION.getCanonicalName());
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported(); // Supports all versions of Java.
+  }
+
+  @Override
+  public synchronized void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+    this.env = processingEnv;
+  }
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    for (Element element : roundEnv.getElementsAnnotatedWith(ANNOTATION)) {
+      AutoCodec annotation = element.getAnnotation(ANNOTATION);
+      switch (annotation.strategy()) {
+        case CONSTRUCTOR:
+          buildCodecUsingConstructor((TypeElement) element);
+          break;
+        default:
+          throw new IllegalArgumentException("Unknown strategy: " + annotation.strategy());
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Uses the first constructor of the class to synthesize a codec.
+   *
+   * <p>This strategy depends on
+   *
+   * <ul>
+   *   <li>the class constructor taking all serialized fields as parameters
+   *   <li>and each serialized field having a corresponding getter.
+   * </ul>
+   *
+   * For example, a constructor having parameter, {@code target}, should having a matching getter,
+   * {@code getTarget()}.
+   *
+   * <p>The first constructor is the first ocurring in the source code.
+   */
+  private void buildCodecUsingConstructor(TypeElement classElement) {
+    TypeSpec.Builder codecClassBuilder =
+        TypeSpec.classBuilder(getCodecName(classElement))
+            .superclass(TypeName.get(classElement.asType()));
+
+    TypeElement encodedType = getEncodedType(classElement);
+
+    // Generates the getEncodedClass method.
+    codecClassBuilder.addMethod(
+        MethodSpec.methodBuilder("getEncodedClass")
+            .addModifiers(Modifier.PUBLIC)
+            .addAnnotation(Override.class)
+            .returns(
+                ParameterizedTypeName.get(
+                    ClassName.get(Class.class), TypeName.get(encodedType.asType())))
+            .addStatement("return $T.class", TypeName.get(encodedType.asType()))
+            .build());
+
+    // In Java, every class has a constructor, so this always succeeds.
+    ExecutableElement constructor =
+        ElementFilter.constructorsIn(encodedType.getEnclosedElements()).get(0);
+    List<? extends VariableElement> constructorParameters = constructor.getParameters();
+    addSerializeMethodUsingConstructor(codecClassBuilder, encodedType, constructorParameters);
+    addDeserializeMethodUsingConstructor(codecClassBuilder, encodedType, constructorParameters);
+
+    String packageName =
+        env.getElementUtils().getPackageOf(classElement).getQualifiedName().toString();
+    try {
+      JavaFile file = JavaFile.builder(packageName, codecClassBuilder.build()).build();
+      file.writeTo(env.getFiler());
+      if (env.getOptions().containsKey("autocodec_print_generated")) {
+        note("AutoCodec generated codec for " + classElement + ":\n" + file);
+      }
+    } catch (IOException e) {
+      env.getMessager()
+          .printMessage(Diagnostic.Kind.ERROR, "Failed to generate output file: " + e.getMessage());
+    }
+  }
+
+  /**
+   * Heuristic that converts a constructor parameter to a getter.
+   *
+   * <p>For example, a parameter called {@code target} results in {@code getTarget()}.
+   */
+  private static String paramNameAsAccessor(String name) {
+    return "get" + name.substring(0, 1).toUpperCase() + name.substring(1) + "()";
+  }
+
+  /**
+   * Name of the generated codec class.
+   *
+   * <p>For {@code Foo.Bar.Codec} this is {@code AutoCodec_Foo_Bar_Codec}.
+   */
+  private static String getCodecName(Element element) {
+    ImmutableList.Builder<String> classNamesBuilder = new ImmutableList.Builder<>();
+    do {
+      classNamesBuilder.add(element.getSimpleName().toString());
+      element = element.getEnclosingElement();
+    } while (element instanceof TypeElement);
+    classNamesBuilder.add(GENERATED_CLASS_NAME_PREFIX);
+    return classNamesBuilder.build().reverse().stream().collect(Collectors.joining("_"));
+  }
+
+  private void addSerializeMethodUsingConstructor(
+      TypeSpec.Builder codecClassBuilder,
+      TypeElement encodedType,
+      List<? extends VariableElement> constructorParameters) {
+    MethodSpec.Builder serializeBuilder =
+        MethodSpec.methodBuilder("serialize")
+            .addModifiers(Modifier.PUBLIC)
+            .returns(void.class)
+            .addParameter(TypeName.get(encodedType.asType()), "input")
+            .addParameter(CodedOutputStream.class, "codedOut")
+            .addAnnotation(Override.class)
+            .addException(SerializationException.class)
+            .addException(IOException.class);
+    for (VariableElement parameter : constructorParameters) {
+      buildSerializeBody(
+          serializeBuilder,
+          (DeclaredType) parameter.asType(),
+          "input." + paramNameAsAccessor(parameter.getSimpleName().toString()));
+    }
+    codecClassBuilder.addMethod(serializeBuilder.build());
+  }
+
+  private void addDeserializeMethodUsingConstructor(
+      TypeSpec.Builder codecClassBuilder,
+      TypeElement encodedType,
+      List<? extends VariableElement> constructorParameters) {
+    MethodSpec.Builder deserializeBuilder =
+        MethodSpec.methodBuilder("deserialize")
+            .addModifiers(Modifier.PUBLIC)
+            .returns(TypeName.get(encodedType.asType()))
+            .addParameter(CodedInputStream.class, "codedIn")
+            .addAnnotation(Override.class)
+            .addException(SerializationException.class)
+            .addException(IOException.class);
+    for (VariableElement parameter : constructorParameters) {
+      buildDeserializeBody(
+          deserializeBuilder,
+          (DeclaredType) parameter.asType(),
+          parameter.getSimpleName().toString());
+    }
+    // Invokes the constructor and returns the value.
+    deserializeBuilder.addStatement(
+        "return new $T($L)",
+        TypeName.get(encodedType.asType()),
+        constructorParameters
+            .stream()
+            .map(p -> p.getSimpleName().toString())
+            .collect(Collectors.joining(", ")));
+    codecClassBuilder.addMethod(deserializeBuilder.build());
+  }
+
+  /**
+   * Appends code statements to {@code builder} to serialize a pre-declared variable named {@code
+   * accessor}.
+   *
+   * @param type the type of {@code accessor}
+   */
+  private void buildSerializeBody(MethodSpec.Builder builder, DeclaredType type, String accessor) {
+    builder.beginControlFlow("if ($L != null)", accessor); // Begin if not null block.
+    builder.addStatement("codedOut.writeBoolNoTag(true)");
+    // TODO(shahan): Add support for more types.
+    if (matchesErased(type, ImmutableSortedSet.class)) {
+      // Writes the target count to the stream so deserialization knows when to stop.
+      builder.addStatement("codedOut.writeInt32NoTag($L.size())", accessor);
+      DeclaredType repeatedType = (DeclaredType) type.getTypeArguments().get(0);
+      // TODO(shahan): consider introducing a depth parameter to avoid shadowing here.
+      builder.beginControlFlow("for ($T repeated : $L)", TypeName.get(repeatedType), accessor);
+      buildSerializeBody(builder, repeatedType, "repeated");
+      builder.endControlFlow();
+    } else {
+      // Otherwise use the type's CODEC.
+      builder.addStatement("$T.CODEC.serialize($L, codedOut)", TypeName.get(type), accessor);
+    }
+    builder.nextControlFlow("else");
+    builder.addStatement("codedOut.writeBoolNoTag(false)");
+    builder.endControlFlow(); // End if not null.
+  }
+
+  /**
+   * Appends code statements to {@code builder} declaring a variable called {@code name} and
+   * initializing it by deserialization.
+   *
+   * @param type the type of {@code name}
+   */
+  private void buildDeserializeBody(MethodSpec.Builder builder, DeclaredType type, String name) {
+    builder.addStatement("$T $L = null", TypeName.get(type), name);
+    builder.beginControlFlow("if (codedIn.readBool())"); // Begin null-handling block.
+    // TODO(shahan): Add support for more types.
+    if (matchesErased(type, ImmutableSortedSet.class)) {
+      DeclaredType repeatedType = (DeclaredType) type.getTypeArguments().get(0);
+      builder.addStatement(
+          "$T<$T> builder = new $T<>($T.naturalOrder())",
+          ImmutableSortedSet.Builder.class,
+          TypeName.get(repeatedType),
+          ImmutableSortedSet.Builder.class,
+          Comparator.class);
+      builder.addStatement("int length = codedIn.readInt32()");
+      builder.beginControlFlow("for (int i = 0; i < length; ++i)");
+      buildDeserializeBody(builder, repeatedType, "repeated");
+      builder.addStatement("builder.add(repeated)");
+      builder.endControlFlow();
+      builder.addStatement("$L = builder.build()", name);
+    } else {
+      // Otherwise, use the type's CODEC value.
+      builder.addStatement("$L = $T.CODEC.deserialize(codedIn)", name, TypeName.get(type));
+    }
+    builder.endControlFlow(); // End null-handling block.
+  }
+
+  /**
+   * Gets the type parameter of ObjectCodec, i.e., the type being encoded.
+   *
+   * <p>{@code element} must implement ObjectCodec.
+   */
+  private TypeElement getEncodedType(TypeElement element) {
+    for (TypeMirror implementedInterface : element.getInterfaces()) {
+      if (matchesErased(implementedInterface, ObjectCodec.class)) {
+        return (TypeElement)
+            env.getTypeUtils()
+                .asElement(((DeclaredType) implementedInterface).getTypeArguments().get(0));
+      }
+    }
+    throw new IllegalArgumentException(element + " does not implement ObjectCodec!");
+  }
+
+  /** True when erasure of {@code type} matches erasure of {@code clazz}. */
+  private boolean matchesErased(TypeMirror type, Class<?> clazz) {
+    return env.getTypeUtils()
+        .isSameType(
+            env.getTypeUtils().erasure(type),
+            env.getTypeUtils()
+                .erasure(
+                    env.getElementUtils().getTypeElement((clazz.getCanonicalName())).asType()));
+  }
+
+  /** Emits a note to BUILD log during annotation processing for debugging. */
+  private void note(String note) {
+    env.getMessager().printMessage(Diagnostic.Kind.NOTE, note);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD
new file mode 100644
index 0000000..5807597
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD
@@ -0,0 +1,44 @@
+package(default_visibility = ["//src:__subpackages__"])
+
+filegroup(
+    name = "srcs",
+    srcs = glob(
+        ["**"],
+    ),
+)
+
+# @AutoCodec annotation and plugin. Used by clients.
+java_library(
+    name = "autocodec",
+    exported_plugins = [":autocodec-plugin"],
+    exports = [":autocodec-annotation"],
+)
+
+# @AutoCodec annotation only. Used by clients and the processor.
+java_library(
+    name = "autocodec-annotation",
+    srcs = ["AutoCodec.java"],
+)
+
+# Installs the @AutoCodec annotation processor as a compiler plugin.
+java_plugin(
+    name = "autocodec-plugin",
+    processor_class = "com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodecProcessor",
+    deps = [
+        ":autocodec-processor",
+    ],
+)
+
+# @AutoCodec annotation processor implementation.
+java_library(
+    name = "autocodec-processor",
+    srcs = ["AutoCodecProcessor.java"],
+    deps = [
+        ":autocodec-annotation",
+        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
+        "//third_party:auto_service",
+        "//third_party:guava",
+        "//third_party/java/javapoet",
+        "//third_party/protobuf:protobuf_java",
+    ],
+)