Experimental java annotation support with unpredictable action inputs/outputs in j2objc_library behind flag --experimental_j2objc_annotation_processing

Also added flag --experimental_zip_tree_artifact to switch on and off the zipping implementation of tree artifact generation.

--
MOS_MIGRATED_REVID=128586669
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
index bbea1f5..374f887 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
@@ -36,12 +36,10 @@
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
-
 import javax.annotation.Nullable;
 
 /**
@@ -155,6 +153,16 @@
     }
   };
 
+  /**
+   * A Predicate that evaluates to true if the Artifact <b>is</b> a tree artifact.
+   */
+  public static final Predicate<Artifact> IS_TREE_ARTIFACT = new Predicate<Artifact>() {
+    @Override
+    public boolean apply(Artifact input) {
+      return input.isTreeArtifact();
+    }
+  };
+
   private final int hashCode;
   private final Path path;
   private final Root root;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
index 793069e..d314388 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
@@ -27,13 +27,11 @@
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.lib.vfs.PathFragment;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
-
 import javax.annotation.Nullable;
 
 /**
@@ -136,7 +134,7 @@
     void eval(ImmutableList.Builder<String> builder, ArtifactExpander artifactExpander) {
       Set<Artifact> expandedArtifacts = new TreeSet<>();
       artifactExpander.expand(treeArtifact, expandedArtifacts);
-      
+
       if (!expandedArtifacts.isEmpty()) {
         builder.add(Artifact.joinExecPaths(delimiter, expandedArtifacts));
       }
@@ -151,6 +149,34 @@
     }
   }
 
+  private static final class ExpandedTreeArtifactExecPathsArg
+      extends TreeArtifactExpansionArgvFragment {
+    private final Artifact treeArtifact;
+
+    private ExpandedTreeArtifactExecPathsArg(Artifact treeArtifact) {
+      Preconditions.checkArgument(
+          treeArtifact.isTreeArtifact(), "%s is not a TreeArtifact", treeArtifact);
+      this.treeArtifact = treeArtifact;
+    }
+
+    @Override
+    void eval(ImmutableList.Builder<String> builder, ArtifactExpander artifactExpander) {
+      Set<Artifact> expandedArtifacts = new TreeSet<>();
+      artifactExpander.expand(treeArtifact, expandedArtifacts);
+
+      for (Artifact expandedArtifact : expandedArtifacts) {
+        builder.add(expandedArtifact.getExecPathString());
+      }
+    }
+
+    @Override
+    public String describe() {
+      return String.format(
+          "ExpandedTreeArtifactExecPathsArg{ treeArtifact: %s}",
+          treeArtifact.getExecPathString());
+    }
+  }
+
   private static final class PathWithTemplateArg extends ArgvFragment {
 
     private final String template;
@@ -515,6 +541,17 @@
       return this;
     }
 
+    /**
+     * Adds the exec paths (one argument per exec path) of all {@link TreeFileArtifact}s under
+     * {@code treeArtifact}.
+     *
+     * @param treeArtifact the TreeArtifact containing the {@link TreeFileArtifact}s to add.
+     */
+    public Builder addExpandedTreeArtifactExecPaths(Artifact treeArtifact) {
+      arguments.add(new ExpandedTreeArtifactExecPathsArg(treeArtifact));
+      return this;
+    }
+
     public Builder addBeforeEachPath(String repeated, Iterable<PathFragment> paths) {
       if (repeated != null && paths != null) {
         arguments.add(InterspersingArgs.fromStrings(paths, repeated, /*formatEach=*/ null));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMapAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMapAction.java
index 183da37..817ee99 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMapAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMapAction.java
@@ -16,15 +16,17 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.ActionExecutionContext;
 import com.google.devtools.build.lib.actions.ActionOwner;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
 import com.google.devtools.build.lib.actions.ResourceSet;
 import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction.DeterministicWriter;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.vfs.PathFragment;
-
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
@@ -72,7 +74,13 @@
       boolean moduleMapHomeIsCwd,
       boolean generateSubmodules,
       boolean externDependencies) {
-    super(owner, ImmutableList.<Artifact>of(), cppModuleMap.getArtifact(),
+    super(
+        owner,
+        ImmutableList.<Artifact>builder()
+            .addAll(Iterables.filter(privateHeaders, Artifact.IS_TREE_ARTIFACT))
+            .addAll(Iterables.filter(publicHeaders, Artifact.IS_TREE_ARTIFACT))
+            .build(),
+        cppModuleMap.getArtifact(),
         /*makeExecutable=*/false);
     this.cppModuleMap = cppModuleMap;
     this.moduleMapHomeIsCwd = moduleMapHomeIsCwd;
@@ -87,6 +95,7 @@
 
   @Override
   public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx)  {
+    final ArtifactExpander artifactExpander = ctx.getArtifactExpander();
     return new DeterministicWriter() {
       @Override
       public void writeOutputFile(OutputStream out) throws IOException {
@@ -101,11 +110,11 @@
         content.append("  export *\n");
 
         HashSet<PathFragment> deduper = new HashSet<>();
-        for (Artifact artifact : publicHeaders) {
+        for (Artifact artifact : expandedHeaders(artifactExpander, publicHeaders)) {
           appendHeader(
               content, "", artifact.getExecPath(), leadingPeriods, /*canCompile=*/ true, deduper);
         }
-        for (Artifact artifact : privateHeaders) {
+        for (Artifact artifact : expandedHeaders(artifactExpander, privateHeaders)) {
           appendHeader(
               content,
               "private",
@@ -137,7 +146,21 @@
       }
     };
   }
-  
+
+  private static Iterable<Artifact> expandedHeaders(ArtifactExpander artifactExpander,
+      Iterable<Artifact> unexpandedHeaders) {
+    List<Artifact> expandedHeaders = new ArrayList<>();
+    for (Artifact unexpandedHeader : unexpandedHeaders) {
+      if (unexpandedHeader.isTreeArtifact()) {
+        artifactExpander.expand(unexpandedHeader, expandedHeaders);
+      } else {
+        expandedHeaders.add(unexpandedHeader);
+      }
+    }
+
+    return ImmutableList.copyOf(expandedHeaders);
+  }
+
   private void appendHeader(StringBuilder content, String visibilitySpecifier, PathFragment path,
       String leadingPeriods, boolean canCompile, HashSet<PathFragment> deduper) {
     if (deduper.contains(path)) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
index 056dd97..97a4b01 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
@@ -55,9 +55,9 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
 import com.google.devtools.build.lib.actions.ParameterFile;
 import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
@@ -66,9 +66,10 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
-import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate;
+import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate.OutputPathMapper;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -180,10 +181,17 @@
       new Predicate<Artifact>() {
         @Override
         public boolean apply(Artifact artifact) {
-          // The current clang (clang-600.0.57) on Darwin doesn't support 'textual', so we can't
-          // have '.inc' files in the module map (since they're implictly textual).
-          // TODO(bazel-team): Use HEADERS file type once clang-700 is the base clang we support.
-          return artifact.getFilename().endsWith(".h");
+          if (artifact.isTreeArtifact()) {
+            // Tree artifact is basically a directory, which does not have any information about
+            // the contained files and their extensions. Here we assume the passed in tree artifact
+            // contains proper header files with .h extension.
+            return true;
+          } else {
+            // The current clang (clang-600.0.57) on Darwin doesn't support 'textual', so we can't
+            // have '.inc' files in the module map (since they're implictly textual).
+            // TODO(bazel-team): Use HEADERS file type once clang-700 is the base clang we support.
+            return artifact.getFilename().endsWith(".h");
+          }
         }
       };
 
@@ -234,6 +242,19 @@
   static final ImmutableList<String> DEFAULT_LINKER_FLAGS = ImmutableList.of("-ObjC");
 
   /**
+   * A mapper that maps input ObjC source {@link Artifact.TreeFileArtifact}s to output
+   * object file {@link Artifact.TreeFileArtifact}s.
+   */
+  private static final OutputPathMapper COMPILE_ACTION_TEMPLATE_OUTPUT_PATH_MAPPER =
+      new OutputPathMapper() {
+        @Override
+        public PathFragment parentRelativeOutputPath(TreeFileArtifact inputTreeFileArtifact) {
+          return FileSystemUtils.replaceExtension(
+              inputTreeFileArtifact.getParentRelativePath(), ".o");
+        }
+      };
+
+  /**
    * Returns information about the given rule's compilation artifacts.
    */
   // TODO(bazel-team): Remove this information from ObjcCommon and move it internal to this class.
@@ -398,8 +419,7 @@
           common.getObjcProvider(),
           extraCompileArgs,
           priorityHeaders,
-          moduleMap,
-          buildConfiguration.isCodeCoverageEnabled());
+          moduleMap);
     }
     return this;
   }
@@ -413,8 +433,7 @@
       ObjcProvider objcProvider,
       ExtraCompileArgs extraCompileArgs,
       Iterable<PathFragment> priorityHeaders,
-      Optional<CppModuleMap> moduleMap,
-      boolean isCodeCoverageEnabled) {
+      Optional<CppModuleMap> moduleMap) {
     ImmutableList.Builder<Artifact> objFiles = new ImmutableList.Builder<>();
     for (Artifact sourceFile : compilationArtifacts.getSrcs()) {
       Artifact objFile = intermediateArtifacts.objFile(sourceFile);
@@ -422,29 +441,49 @@
       if (ObjcRuleClasses.SWIFT_SOURCES.matches(sourceFile.getFilename())) {
         registerSwiftCompileAction(sourceFile, objFile, objcProvider);
       } else {
-        registerCompileAction(
-            sourceFile,
-            objFile,
-            objcProvider,
-            priorityHeaders,
-            moduleMap,
-            compilationArtifacts,
-            Iterables.concat(extraCompileArgs, ImmutableList.of("-fobjc-arc")),
-            isCodeCoverageEnabled);
+        if (objFile.isTreeArtifact()) {
+          registerCompileActionTemplate(
+              sourceFile,
+              objFile,
+              objcProvider,
+              priorityHeaders,
+              moduleMap,
+              compilationArtifacts,
+              Iterables.concat(extraCompileArgs, ImmutableList.of("-fobjc-arc")));
+        } else {
+          registerCompileAction(
+              sourceFile,
+              objFile,
+              objcProvider,
+              priorityHeaders,
+              moduleMap,
+              compilationArtifacts,
+              Iterables.concat(extraCompileArgs, ImmutableList.of("-fobjc-arc")));
+        }
       }
     }
     for (Artifact nonArcSourceFile : compilationArtifacts.getNonArcSrcs()) {
       Artifact objFile = intermediateArtifacts.objFile(nonArcSourceFile);
       objFiles.add(objFile);
-      registerCompileAction(
-          nonArcSourceFile,
-          objFile,
-          objcProvider,
-          priorityHeaders,
-          moduleMap,
-          compilationArtifacts,
-          Iterables.concat(extraCompileArgs, ImmutableList.of("-fno-objc-arc")),
-          isCodeCoverageEnabled);
+      if (objFile.isTreeArtifact()) {
+        registerCompileActionTemplate(
+            nonArcSourceFile,
+            objFile,
+            objcProvider,
+            priorityHeaders,
+            moduleMap,
+            compilationArtifacts,
+            Iterables.concat(extraCompileArgs, ImmutableList.of("-fno-objc-arc")));
+      } else {
+        registerCompileAction(
+            nonArcSourceFile,
+            objFile,
+            objcProvider,
+            priorityHeaders,
+            moduleMap,
+            compilationArtifacts,
+            Iterables.concat(extraCompileArgs, ImmutableList.of("-fno-objc-arc")));
+      }
     }
 
     objFiles.addAll(compilationArtifacts.getPrecompiledSrcs());
@@ -454,7 +493,7 @@
     }
 
     for (Artifact archive : compilationArtifacts.getArchive().asSet()) {
-      registerArchiveActions(objFiles, archive);
+      registerArchiveActions(objFiles.build(), archive);
     }
   }
 
@@ -478,35 +517,30 @@
     return addSource(commandLine, sourceFile);
   }
 
-  private void registerCompileAction(
+  private CustomCommandLine compileActionCommandLine(
       Artifact sourceFile,
       Artifact objFile,
       ObjcProvider objcProvider,
       Iterable<PathFragment> priorityHeaders,
       Optional<CppModuleMap> moduleMap,
-      CompilationArtifacts compilationArtifacts,
+      Optional<Artifact> pchFile,
+      Optional<Artifact> dotdFile,
       Iterable<String> otherFlags,
-      boolean isCodeCoverageEnabled) {
-    ImmutableList.Builder<String> coverageFlags = new ImmutableList.Builder<>();
-    ImmutableList.Builder<Artifact> gcnoFiles = new ImmutableList.Builder<>();
-    ImmutableList.Builder<Artifact> additionalInputs = new ImmutableList.Builder<>();
-    if (isCodeCoverageEnabled && ObjcRuleClasses.isInstrumentable(sourceFile)) {
-      coverageFlags.addAll(CLANG_COVERAGE_FLAGS);
-      gcnoFiles.add(intermediateArtifacts.gcnoFile(sourceFile));
-    }
-    CustomCommandLine.Builder commandLine = new CustomCommandLine.Builder()
-        .add(CLANG);
-    if (ObjcRuleClasses.CPP_SOURCES.matches(sourceFile.getExecPath())) {
+      boolean collectCodeCoverage,
+      boolean isCPlusPlusSource,
+      boolean hasSwiftSources) {
+    CustomCommandLine.Builder commandLine = new CustomCommandLine.Builder().add(CLANG);
+
+    if (isCPlusPlusSource) {
       commandLine.add("-stdlib=libc++");
       commandLine.add("-std=gnu++11");
     }
 
-    if (compilationArtifacts.hasSwiftSources()) {
+    if (hasSwiftSources) {
       // Add the directory that contains merged TargetName-Swift.h header to search path, in case
       // any of ObjC files use it.
       commandLine.add("-I");
       commandLine.addPath(intermediateArtifacts.swiftHeader().getExecPath().getParentDirectory());
-      additionalInputs.add(intermediateArtifacts.swiftHeader());
     }
 
     // The linker needs full debug symbol information to perform binary dead-code stripping.
@@ -514,35 +548,67 @@
       commandLine.add("-g");
     }
 
-    Artifact dotdFile = intermediateArtifacts.dotdFile(sourceFile);
+    List<String> coverageFlags = ImmutableList.of();
+    if (collectCodeCoverage) {
+      coverageFlags = CLANG_COVERAGE_FLAGS;
+    }
+
     commandLine
         .add(compileFlagsForClang(appleConfiguration))
         .add(commonLinkAndCompileFlagsForClang(objcProvider, objcConfiguration, appleConfiguration))
         .add(objcConfiguration.getCoptsForCompilationMode())
         .addBeforeEachPath("-iquote", ObjcCommon.userHeaderSearchPaths(buildConfiguration))
-        .addBeforeEachExecPath("-include", compilationArtifacts.getPchFile().asSet())
+        .addBeforeEachExecPath("-include", pchFile.asSet())
         .addBeforeEachPath("-I", priorityHeaders)
         .addBeforeEachPath("-I", objcProvider.get(INCLUDE))
         .addBeforeEachPath("-isystem", objcProvider.get(INCLUDE_SYSTEM))
         .add(otherFlags)
         .addFormatEach("-D%s", objcProvider.get(DEFINE))
-        .add(coverageFlags.build())
+        .add(coverageFlags)
         .add(getCompileRuleCopts());
-    PathFragment sourceExecPathFragment = sourceFile.getExecPath();
-    String sourcePath = sourceExecPathFragment.getPathString();
-    if (!sourceExecPathFragment.isAbsolute() && objcConfiguration.getUseAbsolutePathsForActions()) {
-      sourcePath = objcConfiguration.getXcodeWorkspaceRoot() + "/" + sourcePath;
-    }
-    commandLine
-      .add("-c").add(sourcePath)
-      .addExecPath("-o", objFile)
-      .add("-MD")
-      .addExecPath("-MF", dotdFile);
 
-    if (objcConfiguration.moduleMapsEnabled()) {
-      additionalInputs.addAll(objcProvider.get(MODULE_MAP));
+    // Add input source file arguments
+    commandLine.add("-c");
+    if (!sourceFile.getExecPath().isAbsolute()
+        && objcConfiguration.getUseAbsolutePathsForActions()) {
+      String workspaceRoot = objcConfiguration.getXcodeWorkspaceRoot();
+
+      // If the source file is a tree artifact, it means the file is basically a directory that may
+      // contain multiple concrete source files at execution time. When constructing the command
+      // line, we insert the source tree artifact as a placeholder, which will be replaced with
+      // one of its contained source files of type {@link Artifact.TreeFileArtifact} at execution
+      // time.
+      //
+      // We also do something similar for the object file arguments below.
+      if (sourceFile.isTreeArtifact()) {
+        commandLine.addPlaceholderTreeArtifactFormattedExecPath(workspaceRoot + "/%s", sourceFile);
+      } else {
+        commandLine.addPaths(workspaceRoot + "/%s", sourceFile.getExecPath());
+      }
+    } else {
+      if (sourceFile.isTreeArtifact()) {
+        commandLine.addPlaceholderTreeArtifactExecPath(sourceFile);
+      } else {
+        commandLine.addPath(sourceFile.getExecPath());
+      }
     }
 
+    // Add output object file arguments.
+    commandLine.add("-o");
+    if (objFile.isTreeArtifact()) {
+      commandLine.addPlaceholderTreeArtifactExecPath(objFile);
+    } else {
+      commandLine.addPath(objFile.getExecPath());
+    }
+
+    // Add Dotd file arguments.
+    if (dotdFile.isPresent()) {
+      commandLine
+          .add("-MD")
+          .addExecPath("-MF", dotdFile.get());
+    }
+
+    // Add module map arguments.
     if (moduleMap.isPresent()) {
       // If modules are enabled for the rule, -fmodules is added to the copts already. (This implies
       // module map usage). Otherwise, we need to pass -fmodule-maps.
@@ -564,18 +630,63 @@
           .add("-fmodule-name=" + moduleMap.get().getName());
     }
 
+    return commandLine.build();
+  }
+
+  private void registerCompileAction(
+      Artifact sourceFile,
+      Artifact objFile,
+      ObjcProvider objcProvider,
+      Iterable<PathFragment> priorityHeaders,
+      Optional<CppModuleMap> moduleMap,
+      CompilationArtifacts compilationArtifacts,
+      Iterable<String> otherFlags) {
+    boolean isCPlusPlusSource = ObjcRuleClasses.CPP_SOURCES.matches(sourceFile.getExecPath());
+    boolean runCodeCoverage =
+        buildConfiguration.isCodeCoverageEnabled() && ObjcRuleClasses.isInstrumentable(sourceFile);
+    boolean hasSwiftSources = compilationArtifacts.hasSwiftSources();
+
+    CustomCommandLine commandLine = compileActionCommandLine(
+        sourceFile,
+        objFile,
+        objcProvider,
+        priorityHeaders,
+        moduleMap,
+        compilationArtifacts.getPchFile(),
+        Optional.of(intermediateArtifacts.dotdFile(sourceFile)),
+        otherFlags,
+        runCodeCoverage,
+        isCPlusPlusSource,
+        hasSwiftSources);
+
+    Optional<Artifact> gcnoFile = Optional.absent();
+    if (runCodeCoverage) {
+      gcnoFile = Optional.of(intermediateArtifacts.gcnoFile(sourceFile));
+    }
+
+    Optional<Artifact> swiftHeader = Optional.absent();
+    if (hasSwiftSources) {
+      swiftHeader = Optional.of(intermediateArtifacts.swiftHeader());
+    }
+
+    NestedSet<Artifact> moduleMapInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+    if (objcConfiguration.moduleMapsEnabled()) {
+      moduleMapInputs = objcProvider.get(MODULE_MAP);
+    }
+
     // TODO(bazel-team): Remote private headers from inputs once they're added to the provider.
     ruleContext.registerAction(
         ObjcRuleClasses.spawnAppleEnvActionBuilder(
                 appleConfiguration, appleConfiguration.getSingleArchPlatform())
             .setMnemonic("ObjcCompile")
             .setExecutable(xcrunwrapper(ruleContext))
-            .setCommandLine(commandLine.build())
+            .setCommandLine(commandLine)
             .addInput(sourceFile)
-            .addInputs(additionalInputs.build())
+            .addInputs(swiftHeader.asSet())
+            .addTransitiveInputs(moduleMapInputs)
             .addOutput(objFile)
-            .addOutputs(gcnoFiles.build())
-            .addOutput(dotdFile)
+            .addOutputs(gcnoFile.asSet())
+            .addOutput(intermediateArtifacts.dotdFile(sourceFile))
             .addTransitiveInputs(objcProvider.get(HEADER))
             .addInputs(compilationArtifacts.getPrivateHdrs())
             .addTransitiveInputs(objcProvider.get(STATIC_FRAMEWORK_FILE))
@@ -585,6 +696,69 @@
   }
 
   /**
+   * Registers a SpawnActionTemplate to compile the source file tree artifact, {@code sourceFiles},
+   * which can contain multiple concrete source files unknown at analysis time. At execution time,
+   * the SpawnActionTemplate will register one ObjcCompile action for each individual source file
+   * under {@code sourceFiles}.
+   *
+   * <p>Note that this method currently does not support code coverage and sources other than ObjC
+   * sources.
+   *
+   * @param sourceFiles tree artifact containing source files to compile
+   * @param objFiles tree artifact containing object files compiled from {@code sourceFiles}
+   * @param objcProvider ObjcProvider instance for this invocation
+   * @param priorityHeaders priority headers to be included before the dependency headers
+   * @param moduleMap the module map generated from the associated headers
+   * @param compilationArtifacts the CompilationArtifacts instance for this invocation
+   * @param otherFlags extra compilation flags to add to the compile action command line
+   */
+  private void registerCompileActionTemplate(
+      Artifact sourceFiles,
+      Artifact objFiles,
+      ObjcProvider objcProvider,
+      Iterable<PathFragment> priorityHeaders,
+      Optional<CppModuleMap> moduleMap,
+      CompilationArtifacts compilationArtifacts,
+      Iterable<String> otherFlags) {
+    CustomCommandLine commandLine = compileActionCommandLine(
+        sourceFiles,
+        objFiles,
+        objcProvider,
+        priorityHeaders,
+        moduleMap,
+        compilationArtifacts.getPchFile(),
+        Optional.<Artifact>absent(),
+        otherFlags,
+        /* runCodeCoverage=*/false,
+        /* isCPlusPlusSource=*/false,
+        /* hasSwiftSources=*/false);
+
+    AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class);
+    Platform platform = appleConfiguration.getSingleArchPlatform();
+
+    NestedSet<Artifact> moduleMapInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+    if (objcConfiguration.moduleMapsEnabled()) {
+      moduleMapInputs = objcProvider.get(MODULE_MAP);
+    }
+
+    ruleContext.registerAction(
+        new SpawnActionTemplate.Builder(sourceFiles, objFiles)
+            .setMnemonics("ObjcCompileActionTemplate", "ObjcCompile")
+            .setExecutable(xcrunwrapper(ruleContext))
+            .setCommandLineTemplate(commandLine)
+            .setEnvironment(ObjcRuleClasses.appleToolchainEnvironment(appleConfiguration, platform))
+            .setExecutionInfo(ObjcRuleClasses.darwinActionExecutionRequirement())
+            .setOutputPathMapper(COMPILE_ACTION_TEMPLATE_OUTPUT_PATH_MAPPER)
+            .addCommonTransitiveInputs(objcProvider.get(HEADER))
+            .addCommonTransitiveInputs(moduleMapInputs)
+            .addCommonInputs(compilationArtifacts.getPrivateHdrs())
+            .addCommonTransitiveInputs(objcProvider.get(STATIC_FRAMEWORK_FILE))
+            .addCommonTransitiveInputs(objcProvider.get(DYNAMIC_FRAMEWORK_FILE))
+            .addCommonInputs(compilationArtifacts.getPchFile().asSet())
+            .build(ruleContext.getActionOwner()));
+  }
+
+  /**
    * Returns the copts for the compile action in the current rule context (using a combination
    * of the rule's "copts" attribute as well as the current configuration copts).
    */
@@ -779,23 +953,17 @@
         .build(ruleContext));
   }
 
-  private void registerArchiveActions(ImmutableList.Builder<Artifact> objFiles, Artifact archive) {
-    for (Action action :
-        archiveActions(objFiles.build(), archive, intermediateArtifacts.archiveObjList())) {
-      ruleContext.registerAction(action);
-    }
+  private void registerArchiveActions(List<Artifact> objFiles, Artifact archive) {
+    Artifact objList = intermediateArtifacts.archiveObjList();
+    registerObjFilelistAction(objFiles, objList);
+    registerArchiveAction(objFiles, archive);
   }
 
-  private Iterable<Action> archiveActions(
+  private void registerArchiveAction(
       Iterable<Artifact> objFiles,
-      Artifact archive,
-      Artifact objList) {
-
-    ImmutableList.Builder<Action> actions = new ImmutableList.Builder<>();
-
-    actions.add(objFilelistAction(objFiles, objList));
-
-    actions.add(ObjcRuleClasses.spawnAppleEnvActionBuilder(
+      Artifact archive) {
+    Artifact objList = intermediateArtifacts.archiveObjList();
+    ruleContext.registerAction(ObjcRuleClasses.spawnAppleEnvActionBuilder(
             appleConfiguration, appleConfiguration.getSingleArchPlatform())
         .setMnemonic("ObjcLink")
         .setExecutable(libtool(ruleContext))
@@ -810,17 +978,31 @@
         .addInput(objList)
         .addOutput(archive)
         .build(ruleContext));
-
-    return actions.build();
   }
 
-  private Action objFilelistAction(Iterable<Artifact> objFiles, Artifact objList) {
+  private void registerObjFilelistAction(Iterable<Artifact> objFiles, Artifact objList) {
     ImmutableSet<Artifact> dedupedObjFiles = ImmutableSet.copyOf(objFiles);
-    return new FileWriteAction(
+    CustomCommandLine.Builder objFilesToLinkParam = new CustomCommandLine.Builder();
+    ImmutableList.Builder<Artifact> treeObjFiles = new ImmutableList.Builder<>();
+
+    for (Artifact objFile : dedupedObjFiles) {
+      // If the obj file is a tree artifact, we need to expand it into the contained individual
+      // files properly.
+      if (objFile.isTreeArtifact()) {
+        treeObjFiles.add(objFile);
+        objFilesToLinkParam.addExpandedTreeArtifactExecPaths(objFile);
+      } else {
+        objFilesToLinkParam.addPath(objFile.getExecPath());
+      }
+    }
+
+    ruleContext.registerAction(new ParameterFileWriteAction(
         ruleContext.getActionOwner(),
+        treeObjFiles.build(),
         objList,
-        Artifact.joinExecPaths("\n", dedupedObjFiles),
-        /*makeExecutable=*/ false);
+        objFilesToLinkParam.build(),
+        ParameterFile.ParameterFileType.UNQUOTED,
+        ISO_8859_1));
   }
 
   /**
@@ -1186,8 +1368,8 @@
     // resulting in duplicate symbol errors unless they are deduped.
     objFiles = Iterables.filter(objFiles, Predicates.not(Predicates.in(forceLinkArtifacts)));
 
-    ruleContext.registerAction(objFilelistAction(objFiles, inputFileList));
-    
+    registerObjFilelistAction(objFiles, inputFileList);
+
     if (objcConfiguration.shouldPrioritizeStaticLibs()) {
       commandLine.add("-filelist").add(inputFileList.getExecPathString());
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
index 8d8235d..7efef81 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
@@ -229,7 +229,12 @@
    * artifact.
    */
   public Artifact objFile(Artifact source) {
-     return inUniqueObjsDir(source, ".o");
+    if (source.isTreeArtifact()) {
+      PathFragment rootRelativePath = source.getRootRelativePath().replaceName("obj_files");
+      return ruleContext.getTreeArtifact(rootRelativePath, ruleContext.getBinOrGenfilesDirectory());
+    } else {
+      return inUniqueObjsDir(source, ".o");
+    }
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
index a0d3411..36aa8d3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.lib.rules.objc;
 
+import static com.google.devtools.build.lib.actions.Artifact.IS_TREE_ARTIFACT;
 import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
 import static com.google.devtools.build.lib.packages.Attribute.attr;
 import static com.google.devtools.build.lib.packages.BuildType.LABEL;
@@ -21,9 +22,11 @@
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
 
 import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.ParameterFile;
 import com.google.devtools.build.lib.analysis.ConfiguredAspect;
@@ -34,6 +37,7 @@
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.PopulateTreeArtifactAction;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.cmdline.Label;
@@ -49,6 +53,7 @@
 import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
 import com.google.devtools.build.lib.rules.apple.AppleToolchain;
 import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
+import com.google.devtools.build.lib.rules.java.JavaGenJarsProvider;
 import com.google.devtools.build.lib.rules.java.JavaHelper;
 import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider;
 import com.google.devtools.build.lib.rules.java.Jvm;
@@ -56,7 +61,6 @@
 import com.google.devtools.build.lib.rules.objc.J2ObjcSource.SourceType;
 import com.google.devtools.build.lib.util.FileType;
 import com.google.devtools.build.lib.vfs.PathFragment;
-
 import java.util.List;
 
 /**
@@ -142,6 +146,10 @@
             .direct_compile_time_input()
             .cfg(HOST)
             .value(new AppleToolchain.XcodeConfigLabel(toolsRepository)))
+        .add(attr("$zipper", LABEL)
+            .cfg(HOST)
+            .exec()
+            .value(Label.parseAbsoluteUnchecked(toolsRepository + "//tools/zip:zipper")))
         .build();
   }
 
@@ -154,17 +162,28 @@
         base.getProvider(JavaCompilationArgsProvider.class);
     JavaSourceInfoProvider sourceInfoProvider =
         base.getProvider(JavaSourceInfoProvider.class);
+    JavaGenJarsProvider genJarProvider =
+        base.getProvider(JavaGenJarsProvider.class);
     ImmutableSet<Artifact> javaInputFiles = ImmutableSet.<Artifact>builder()
         .addAll(sourceInfoProvider.getSourceFiles())
         .addAll(sourceInfoProvider.getSourceJars())
         .addAll(sourceInfoProvider.getSourceJarsForJarFiles())
         .build();
 
+    Optional<Artifact> genSrcJar;
+    boolean annotationProcessingEnabled = ruleContext.getFragment(J2ObjcConfiguration.class)
+        .annotationProcessingEnabled();
+    if (genJarProvider != null && annotationProcessingEnabled) {
+      genSrcJar = Optional.fromNullable(genJarProvider.getGenSourceJar());
+    } else {
+      genSrcJar = Optional.<Artifact>absent();
+    }
+
     XcodeProvider xcodeProvider;
     ObjcCommon common;
 
     if (!javaInputFiles.isEmpty()) {
-      J2ObjcSource j2ObjcSource = buildJ2ObjcSource(ruleContext, javaInputFiles);
+      J2ObjcSource j2ObjcSource = buildJ2ObjcSource(ruleContext, javaInputFiles, genSrcJar);
       J2ObjcMappingFileProvider depJ2ObjcMappingFileProvider =
           depJ2ObjcMappingFileProvider(ruleContext);
       createJ2ObjcTranspilationAction(
@@ -173,13 +192,23 @@
           depJ2ObjcMappingFileProvider.getClassMappingFiles(),
           javaInputFiles,
           compilationArgsProvider,
-          j2ObjcSource);
+          j2ObjcSource,
+          genSrcJar);
+
+      boolean zipTreeArtifact = ruleContext.getFragment(J2ObjcConfiguration.class)
+          .zipTreeArtifact();
+      if (genSrcJar.isPresent() && zipTreeArtifact) {
+        for (Action action : genJarTreeArtifactCreationActions(ruleContext)) {
+          ruleContext.registerAction(action);
+        }
+      }
       common = common(
           ruleContext,
           j2ObjcSource.getObjcSrcs(),
           j2ObjcSource.getObjcHdrs(),
           j2ObjcSource.getHeaderSearchPaths(),
           DEPENDENT_ATTRIBUTES);
+
       xcodeProvider = xcodeProvider(
           ruleContext,
           common,
@@ -244,13 +273,70 @@
     return j2ObjcMappingFileProvider;
   }
 
-  private static void createJ2ObjcTranspilationAction(
+  private List<Artifact> genJarOutputs(RuleContext ruleContext) {
+    boolean zipTreeArtifact = ruleContext
+        .getFragment(J2ObjcConfiguration.class)
+        .zipTreeArtifact();
+
+    if (zipTreeArtifact) {
+      return ImmutableList.of(
+          j2ObjcGenJarSourceZip(ruleContext),
+          j2ObjcGenJarSourceZipManifest(ruleContext),
+          j2ObjcGenJarHeaderZip(ruleContext),
+          j2ObjcGenJarHeaderZipManifest(ruleContext));
+    } else {
+      return ImmutableList.of(
+          j2ObjcGenJarTranslatedSourceFiles(ruleContext),
+          j2objcGenJarTranslatedHeaderFiles(ruleContext));
+    }
+  }
+
+  private List<String> genJarFlags(RuleContext ruleContext) {
+    boolean zipTreeArtifact = ruleContext.getFragment(J2ObjcConfiguration.class).zipTreeArtifact();
+
+    if (zipTreeArtifact) {
+      return ImmutableList.of(
+          "--output_gen_source_zip", j2ObjcGenJarSourceZip(ruleContext).getExecPathString(),
+          "--output_gen_header_zip", j2ObjcGenJarHeaderZip(ruleContext).getExecPathString());
+    } else {
+      return ImmutableList.of(
+          "--output_gen_source_dir",
+          j2ObjcGenJarTranslatedSourceFiles(ruleContext).getExecPathString(),
+          "--output_gen_header_dir",
+          j2objcGenJarTranslatedHeaderFiles(ruleContext).getExecPathString());
+    }
+  }
+
+  private List<Action> genJarTreeArtifactCreationActions(RuleContext ruleContext) {
+    Artifact sourceFiles = j2ObjcGenJarTranslatedSourceFiles(ruleContext);
+    Artifact headerFiles = j2objcGenJarTranslatedHeaderFiles(ruleContext);
+
+    ImmutableList.Builder<Action> actions = ImmutableList.builder();
+    actions.add(new PopulateTreeArtifactAction(
+        ruleContext.getActionOwner(),
+        j2ObjcGenJarSourceZip(ruleContext),
+        j2ObjcGenJarSourceZipManifest(ruleContext),
+        sourceFiles,
+        ruleContext.getExecutablePrerequisite("$zipper", Mode.HOST)));
+
+    actions.add(new PopulateTreeArtifactAction(
+        ruleContext.getActionOwner(),
+        j2ObjcGenJarHeaderZip(ruleContext),
+        j2ObjcGenJarHeaderZipManifest(ruleContext),
+        headerFiles,
+        ruleContext.getExecutablePrerequisite("$zipper", Mode.HOST)));
+
+    return actions.build();
+  }
+
+  private void createJ2ObjcTranspilationAction(
       RuleContext ruleContext,
       NestedSet<Artifact> depsHeaderMappingFiles,
       NestedSet<Artifact> depsClassMappingFiles,
       Iterable<Artifact> sources,
       JavaCompilationArgsProvider compArgsProvider,
-      J2ObjcSource j2ObjcSource) {
+      J2ObjcSource j2ObjcSource,
+      Optional<Artifact> genSrcJar) {
     CustomCommandLine.Builder argBuilder = CustomCommandLine.builder();
     PathFragment javaExecutable = ruleContext.getFragment(Jvm.class, HOST).getJavaExecutable();
     argBuilder.add("--java").add(javaExecutable.getPathString());
@@ -259,12 +345,22 @@
     argBuilder.addExecPath("--j2objc", j2ObjcDeployJar);
 
     argBuilder.add("--main_class").add("com.google.devtools.j2objc.J2ObjC");
-    argBuilder.addJoinExecPaths("--translated_source_files", ",", j2ObjcSource.getObjcSrcs());
+    argBuilder.addJoinExecPaths(
+        "--translated_source_files",
+        ",",
+        Iterables.filter(j2ObjcSource.getObjcSrcs(), Predicates.not(IS_TREE_ARTIFACT)));
     argBuilder.add("--objc_file_path").addPath(j2ObjcSource.getObjcFilePath());
 
     Artifact outputDependencyMappingFile = j2ObjcOutputDependencyMappingFile(ruleContext);
     argBuilder.addExecPath("--output_dependency_mapping_file", outputDependencyMappingFile);
 
+    ImmutableList.Builder<Artifact> genSrcOutputFiles = ImmutableList.builder();
+    if (genSrcJar.isPresent()) {
+      genSrcOutputFiles.addAll(genJarOutputs(ruleContext));
+      argBuilder.addExecPath("--gen_src_jar", genSrcJar.get());
+      argBuilder.add(genJarFlags(ruleContext));
+    }
+
     Iterable<String> translationFlags = ruleContext
         .getFragment(J2ObjcConfiguration.class)
         .getTranslationFlags();
@@ -307,7 +403,8 @@
         ruleContext.getActionOwner(),
         paramFile,
         argBuilder.build(),
-        ParameterFile.ParameterFileType.UNQUOTED, ISO_8859_1));
+        ParameterFile.ParameterFileType.UNQUOTED,
+        ISO_8859_1));
 
     SpawnAction.Builder builder = new SpawnAction.Builder()
         .setMnemonic("TranspilingJ2objc")
@@ -316,16 +413,18 @@
         .addInput(j2ObjcDeployJar)
         .addInput(bootclasspathJar)
         .addInputs(sources)
+        .addInputs(genSrcJar.asSet())
         .addTransitiveInputs(compileTimeJars)
-        .addInputs(JavaHelper.getHostJavabaseInputs(ruleContext))
+        .addTransitiveInputs(JavaHelper.getHostJavabaseInputs(ruleContext))
         .addTransitiveInputs(depsHeaderMappingFiles)
         .addTransitiveInputs(depsClassMappingFiles)
         .addInput(paramFile)
         .setCommandLine(CustomCommandLine.builder()
             .addPaths("@%s", paramFile.getExecPath())
             .build())
-        .addOutputs(j2ObjcSource.getObjcSrcs())
-        .addOutputs(j2ObjcSource.getObjcHdrs())
+        .addOutputs(Iterables.filter(j2ObjcSource.getObjcSrcs(), Predicates.not(IS_TREE_ARTIFACT)))
+        .addOutputs(Iterables.filter(j2ObjcSource.getObjcHdrs(), Predicates.not(IS_TREE_ARTIFACT)))
+        .addOutputs(genSrcOutputFiles.build())
         .addOutput(outputHeaderMappingFile)
         .addOutput(outputDependencyMappingFile)
         .addOutput(archiveSourceMappingFile);
@@ -394,8 +493,38 @@
         ruleContext, ".archive_source_mapping.j2objc");
   }
 
+  private static Artifact j2ObjcGenJarTranslatedSourceFiles(RuleContext ruleContext) {
+    PathFragment rootRelativePath = ruleContext
+        .getUniqueDirectory("_j2objc/gen_jar_files")
+        .getRelative("source_files");
+    return ruleContext.getTreeArtifact(rootRelativePath, ruleContext.getBinOrGenfilesDirectory());
+  }
+
+  private static Artifact j2objcGenJarTranslatedHeaderFiles(RuleContext ruleContext) {
+    PathFragment rootRelativePath = ruleContext
+        .getUniqueDirectory("_j2objc/gen_jar_files")
+        .getRelative("header_files");
+    return ruleContext.getTreeArtifact(rootRelativePath, ruleContext.getBinOrGenfilesDirectory());
+  }
+
+  private static Artifact j2ObjcGenJarSourceZip(RuleContext ruleContext) {
+    return ObjcRuleClasses.artifactByAppendingToBaseName(ruleContext, ".genjar_source.zip");
+  }
+
+  private static Artifact j2ObjcGenJarSourceZipManifest(RuleContext ruleContext) {
+    return ObjcRuleClasses.artifactByAppendingToBaseName(ruleContext, ".genjar_source.txt");
+  }
+
+  private static Artifact j2ObjcGenJarHeaderZip(RuleContext ruleContext) {
+    return ObjcRuleClasses.artifactByAppendingToBaseName(ruleContext, ".genjar_header.zip");
+  }
+
+  private static Artifact j2ObjcGenJarHeaderZipManifest(RuleContext ruleContext) {
+    return ObjcRuleClasses.artifactByAppendingToBaseName(ruleContext, ".genjar_header.txt");
+  }
+
   private J2ObjcSource buildJ2ObjcSource(RuleContext ruleContext,
-      Iterable<Artifact> javaInputSourceFiles) {
+      Iterable<Artifact> javaInputSourceFiles, Optional<Artifact> genSrcJar) {
     PathFragment objcFileRootRelativePath = ruleContext.getUniqueDirectory("_j2objc");
     PathFragment objcFileRootExecPath = ruleContext
         .getConfiguration()
@@ -408,13 +537,23 @@
     Iterable<PathFragment> headerSearchPaths = J2ObjcLibrary.j2objcSourceHeaderSearchPaths(
         ruleContext, objcFileRootExecPath, javaInputSourceFiles);
 
+    Optional<Artifact> genJarTranslatedSrcs = Optional.absent();
+    Optional<Artifact> genJarTranslatedHdrs = Optional.absent();
+    Optional<PathFragment> genJarFileHeaderSearchPaths = Optional.absent();
+
+    if (genSrcJar.isPresent()) {
+      genJarTranslatedSrcs = Optional.of(j2ObjcGenJarTranslatedSourceFiles(ruleContext));
+      genJarTranslatedHdrs = Optional.of(j2objcGenJarTranslatedHeaderFiles(ruleContext));
+      genJarFileHeaderSearchPaths = Optional.of(genJarTranslatedHdrs.get().getExecPath());
+    }
+
     return new J2ObjcSource(
         ruleContext.getRule().getLabel(),
-        objcSrcs,
-        objcHdrs,
+        Iterables.concat(objcSrcs, genJarTranslatedSrcs.asSet()),
+        Iterables.concat(objcHdrs, genJarTranslatedHdrs.asSet()),
         objcFileRootExecPath,
         SourceType.JAVA,
-        headerSearchPaths);
+        Iterables.concat(headerSearchPaths, genJarFileHeaderSearchPaths.asSet()));
   }
 
   private Iterable<Artifact> getOutputObjcFiles(RuleContext ruleContext,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcCommandLineOptions.java b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcCommandLineOptions.java
index 8f438d1..ad76187 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcCommandLineOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcCommandLineOptions.java
@@ -19,7 +19,6 @@
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.common.options.Converters;
 import com.google.devtools.common.options.Option;
-
 import java.util.List;
 
 /**
@@ -52,6 +51,20 @@
       )
   public boolean explicitJreDeps;
 
+  @Option(name = "experimental_j2objc_annotation_processing",
+      defaultValue = "false",
+      category = "undocumented",
+      help = "Whether to enable j2objc Java annotation processing."
+      )
+  public boolean annotationProcessingEnabled;
+
+  @Option(name = "experimental_zip_tree_artifact",
+      defaultValue = "true",
+      category = "undocumented",
+      help = "Whether to enable zipping/unzipping implementation of tree artifact creation"
+      )
+  public boolean zipTreeArtifact;
+
   @Override
   public void addAllLabels(Multimap<String, Label> labelMap) {}
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcConfiguration.java
index 0653d43..458e2bb 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcConfiguration.java
@@ -24,7 +24,6 @@
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.EventHandler;
-
 import java.util.Collections;
 import java.util.Set;
 
@@ -78,6 +77,8 @@
   private final Set<String> translationFlags;
   private final boolean removeDeadCode;
   private final boolean explicitJreDeps;
+  private final boolean annotationProcessingEnabled;
+  private final boolean zipTreeArtifact;
 
   J2ObjcConfiguration(J2ObjcCommandLineOptions j2ObjcOptions) {
     this.removeDeadCode = j2ObjcOptions.removeDeadCode;
@@ -86,6 +87,8 @@
         .addAll(j2ObjcOptions.translationFlags)
         .addAll(J2OBJC_ALWAYS_ON_TRANSLATION_FLAGS)
         .build();
+    this.annotationProcessingEnabled = j2ObjcOptions.annotationProcessingEnabled;
+    this.zipTreeArtifact = j2ObjcOptions.zipTreeArtifact;
   }
 
   /**
@@ -116,6 +119,20 @@
     return explicitJreDeps;
   }
 
+  /**
+   * Returns whether to enable J2ObjC support for Java annotation processing.
+   */
+  public boolean annotationProcessingEnabled() {
+    return annotationProcessingEnabled;
+  }
+
+  /**
+   * Returns whether to enable the zipping/unzipping implementation of tree artifact creation.
+   */
+  public boolean zipTreeArtifact() {
+    return zipTreeArtifact;
+  }
+
   @Override
   public void reportInvalidOptions(EventHandler reporter, BuildOptions buildOptions) {
     if (!Collections.disjoint(translationFlags, J2OBJC_BLACKLISTED_TRANSLATION_FLAGS)) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
index e6feaae..81c8d9b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
@@ -213,20 +213,33 @@
    */
   static SpawnAction.Builder spawnAppleEnvActionBuilder(AppleConfiguration appleConfiguration,
       Platform targetPlatform) {
-    ImmutableMap.Builder<String, String> envBuilder = ImmutableMap.<String, String>builder()
-        .putAll(appleConfiguration.getTargetAppleEnvironment(targetPlatform))
-        .putAll(appleConfiguration.getAppleHostSystemEnv());
-
     return spawnOnDarwinActionBuilder()
-        .setEnvironment(envBuilder.build());
+        .setEnvironment(appleToolchainEnvironment(appleConfiguration, targetPlatform));
+  }
+
+  /**
+   * Returns apple environment variables that are typically needed by the apple toolchain.
+   */
+  static ImmutableMap<String, String> appleToolchainEnvironment(
+      AppleConfiguration appleConfiguration, Platform targetPlatform) {
+    return ImmutableMap.<String, String>builder()
+        .putAll(appleConfiguration.getTargetAppleEnvironment(targetPlatform))
+        .putAll(appleConfiguration.getAppleHostSystemEnv())
+        .build();
   }
 
   /**
    * Creates a new spawn action builder that requires a darwin architecture to run.
    */
   static SpawnAction.Builder spawnOnDarwinActionBuilder() {
-    return new SpawnAction.Builder()
-        .setExecutionInfo(ImmutableMap.of(ExecutionRequirements.REQUIRES_DARWIN, ""));
+    return new SpawnAction.Builder().setExecutionInfo(darwinActionExecutionRequirement());
+  }
+
+  /**
+   * Returns action requirement information for darwin architecture.
+   */
+  static ImmutableMap<String, String> darwinActionExecutionRequirement() {
+    return ImmutableMap.of(ExecutionRequirements.REQUIRES_DARWIN, "");
   }
 
   /**