Enable aapt2 support in library actions.

RELNOTES: None
PiperOrigin-RevId: 160445869
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java
index dc84ee5..e86cae2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java
@@ -118,17 +118,12 @@
 
   public ResourceContainer build(ActionConstructionContext context) {
     CustomCommandLine.Builder builder = new CustomCommandLine.Builder();
-    
+
     // Set the busybox tool.
     builder.add("--tool").add("MERGE").add("--");
 
     // Use a FluentIterable to avoid flattening the NestedSets
     NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder();
-    inputs.addAll(
-        ruleContext
-            .getExecutablePrerequisite("$android_resources_busybox", Mode.HOST)
-            .getRunfilesSupport()
-            .getRunfilesArtifactsWithoutMiddlemen());
 
     builder.addExecPath("--androidJar", sdk.getAndroidJar());
     inputs.add(sdk.getAndroidJar());
@@ -168,6 +163,8 @@
       builder.add("--packageForR").add(customJavaPackage);
     }
 
+    // TODO(corysmith): Move the data binding parsing out of the merging pass to enable faster
+    // aapt2 builds.
     if (dataBindingInfoZip != null) {
       builder.addExecPath("--dataBindingInfoOut", dataBindingInfoZip);
       outs.add(dataBindingInfoZip);
@@ -190,7 +187,8 @@
     // Return the full set of processed transitive dependencies.
     ResourceContainer.Builder result = primary.toBuilder();
     if (classJarOut != null) {
-      // ensure the classJar is propgated if it exists. Otherwise, AndroidCommon tries to make it.
+      // ensure the classJar is propagated if it exists. Otherwise, AndroidCommon tries to make it.
+      // TODO(corysmith): Centralize the class jar generation.
       result.setJavaClassJar(classJarOut);
     }
     if (manifestOut != null) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java
index 4a926fd..8a49985 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Functions;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
+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.Artifact;
@@ -31,6 +32,7 @@
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.ArrayList;
 import java.util.List;
+import javax.annotation.Nullable;
 
 /** Builder for creating $android_resource_parser action. */
 public class AndroidResourceParsingActionBuilder {
@@ -46,6 +48,8 @@
   private Artifact output;
 
   private ResourceContainer resourceContainer;
+  private Artifact compiledSymbols;
+  private Artifact dataBindingInfoZip;
 
   /**
    * @param ruleContext The RuleContext that was used to create the SpawnAction.Builder.
@@ -76,6 +80,17 @@
     return this;
   }
 
+  public AndroidResourceParsingActionBuilder setCompiledSymbolsOutput(
+      @Nullable Artifact compiledSymbols) {
+    this.compiledSymbols = compiledSymbols;
+    return this;
+  }
+
+  public AndroidResourceParsingActionBuilder setDataBindingInfoZip(Artifact dataBindingInfoZip) {
+    this.dataBindingInfoZip = dataBindingInfoZip;
+    return this;
+  }
+
   private static class ResourceContainerToArg implements Function<LocalResourceContainer, String> {
 
     public ResourceContainerToArg() {
@@ -112,40 +127,74 @@
 
   public ResourceContainer build(ActionConstructionContext context) {
     CustomCommandLine.Builder builder = new CustomCommandLine.Builder();
-    
+
     // Set the busybox tool.
     builder.add("--tool").add("PARSE").add("--");
 
     NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder();
-    inputs.addAll(
-        ruleContext
-            .getExecutablePrerequisite("$android_resources_busybox", Mode.HOST)
-            .getRunfilesSupport()
-            .getRunfilesArtifactsWithoutMiddlemen());
 
     Preconditions.checkNotNull(primary);
-    builder.add("--primaryData").add(RESOURCE_CONTAINER_TO_ARG.apply(primary));
+    String resourceDirectories = RESOURCE_CONTAINER_TO_ARG.apply(primary);
+    builder.add("--primaryData").add(resourceDirectories);
     inputs.addTransitive(RESOURCE_CONTAINER_TO_ARTIFACTS.apply(primary));
 
-    List<Artifact> outs = new ArrayList<>();
     Preconditions.checkNotNull(output);
     builder.addExecPath("--output", output);
-    outs.add(output);
 
-    SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder();
     // Create the spawn action.
     ruleContext.registerAction(
-        spawnActionBuilder
+        new SpawnAction.Builder()
             .useParameterFile(ParameterFileType.UNQUOTED)
             .addTransitiveInputs(inputs.build())
-            .addOutputs(ImmutableList.copyOf(outs))
+            .addOutputs(ImmutableList.of(output))
             .setCommandLine(builder.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Parsing Android resources for " + ruleContext.getLabel())
             .setMnemonic("AndroidResourceParser")
             .build(context));
-    
-    return resourceContainer.toBuilder().setSymbols(output).build();
+
+    if (compiledSymbols != null) {
+      List<Artifact> outs = new ArrayList<>();
+      CustomCommandLine.Builder flatFileBuilder = new CustomCommandLine.Builder();
+      flatFileBuilder
+          .add("--tool")
+          .add("COMPILE_LIBRARY_RESOURCES")
+          .add("--")
+          .add("--resources")
+          .add(resourceDirectories)
+          .addExecPath("--output", compiledSymbols);
+      outs.add(compiledSymbols);
+
+      // The databinding needs to be processed before compilation, so the stripping happens here.
+      if (dataBindingInfoZip != null) {
+        flatFileBuilder.addExecPath("--manifest", resourceContainer.getManifest());
+        inputs.add(resourceContainer.getManifest());
+        if (!Strings.isNullOrEmpty(resourceContainer.getJavaPackage())) {
+          flatFileBuilder.add("--packagePath").add(resourceContainer.getJavaPackage());
+        }
+        builder.addExecPath("--dataBindingInfoOut", dataBindingInfoZip);
+        outs.add(dataBindingInfoZip);
+      }
+      // Create the spawn action.
+      ruleContext.registerAction(
+          new SpawnAction.Builder()
+              .useParameterFile(ParameterFileType.UNQUOTED)
+              .addTransitiveInputs(inputs.build())
+              .addOutputs(ImmutableList.copyOf(outs))
+              .setCommandLine(flatFileBuilder.build())
+              .setExecutable(
+                  ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
+              .setProgressMessage("Compiling Android resources for " + ruleContext.getLabel())
+              .setMnemonic("AndroidResourceCompiler")
+              .build(context));
+      return resourceContainer
+          .toBuilder()
+          .setCompiledSymbols(compiledSymbols)
+          .setSymbols(output)
+          .build();
+    } else {
+      return resourceContainer.toBuilder().setSymbols(output).build();
+    }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceValidatorActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceValidatorActionBuilder.java
index 66e8b8b..ee77eaa 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceValidatorActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceValidatorActionBuilder.java
@@ -13,8 +13,11 @@
 // limitations under the License.
 package com.google.devtools.build.lib.rules.android;
 
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
@@ -23,9 +26,9 @@
 import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
-import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import java.util.ArrayList;
 import java.util.List;
+import javax.annotation.Nullable;
 
 /**
  * Builder for creating $android_resource_validator action. This action validates merged resources
@@ -50,6 +53,18 @@
   // Flags
   private String customJavaPackage;
   private boolean debug;
+  private Artifact staticLibraryOut;
+  private ResourceDependencies resourceDeps;
+  private Artifact aapt2SourceJarOut;
+  private Artifact aapt2RTxtOut;
+  private static final Function<ResourceContainer, Artifact> TO_STATIC_LIBRARY_ARTIFACT =
+      new Function<ResourceContainer, Artifact>() {
+        @Override
+        public Artifact apply(@Nullable ResourceContainer resourceContainer) {
+          return resourceContainer.getStaticLibrary();
+        }
+      };
+  private Artifact compiledSymbols;
 
   /** @param ruleContext The RuleContext that was used to create the SpawnAction.Builder. */
   public AndroidResourceValidatorActionBuilder(RuleContext ruleContext) {
@@ -57,6 +72,11 @@
     this.sdk = AndroidSdkProvider.fromRuleContext(ruleContext);
   }
 
+  public AndroidResourceValidatorActionBuilder setStaticLibraryOut(Artifact staticLibraryOut) {
+    this.staticLibraryOut = staticLibraryOut;
+    return this;
+  }
+
   /** The primary resource container. We mostly propagate its values, but update the R.txt. */
   public AndroidResourceValidatorActionBuilder withPrimary(ResourceContainer primary) {
     this.primary = primary;
@@ -88,7 +108,110 @@
     return this;
   }
 
+  /** Used to add the static library from the dependencies. */
+  public AndroidResourceValidatorActionBuilder withDependencies(ResourceDependencies resourceDeps) {
+    this.resourceDeps = resourceDeps;
+    return this;
+  }
+
+  public AndroidResourceValidatorActionBuilder setAapt2RTxtOut(Artifact aapt2RTxtOut) {
+    this.aapt2RTxtOut = aapt2RTxtOut;
+    return this;
+  }
+
+  public AndroidResourceValidatorActionBuilder setAapt2SourceJarOut(Artifact aapt2SourceJarOut) {
+    this.aapt2SourceJarOut = aapt2SourceJarOut;
+    return this;
+  }
+
   public ResourceContainer build(ActionConstructionContext context) {
+    ResourceContainer container = createValidateAction(context);
+    if (compiledSymbols == null) {
+      return container;
+    } else {
+      return createLinkStaticLibraryAction(container, context);
+    }
+  }
+
+  public AndroidResourceValidatorActionBuilder setCompiledSymbols(Artifact compiledSymbols) {
+    this.compiledSymbols = compiledSymbols;
+    return this;
+  }
+
+  /**
+   * This creates a static library using aapt2. It also generates a source jar and R.txt from aapt.
+   *
+   * <p>This allows the link action to replace the validate action for builds that use aapt2, as
+   * opposed to executing both actions.
+   */
+  private ResourceContainer createLinkStaticLibraryAction(
+      ResourceContainer validated, ActionConstructionContext context) {
+    Preconditions.checkNotNull(staticLibraryOut);
+    Preconditions.checkNotNull(aapt2SourceJarOut);
+    Preconditions.checkNotNull(aapt2RTxtOut);
+    Preconditions.checkNotNull(resourceDeps);
+
+    CustomCommandLine.Builder builder = new CustomCommandLine.Builder();
+    ImmutableList.Builder<Artifact> inputs = ImmutableList.builder();
+    ImmutableList.Builder<Artifact> outs = ImmutableList.builder();
+
+    // Set the busybox tool.
+    builder.add("--tool").add("LINK_STATIC_LIBRARY").add("--");
+
+    builder.addExecPath("--aapt2", sdk.getAapt2().getExecutable());
+
+    FluentIterable<Artifact> libraries =
+        FluentIterable.from(resourceDeps.getResources()).transform(TO_STATIC_LIBRARY_ARTIFACT);
+
+    builder
+        .add("--libraries")
+        .add(libraries.join(Joiner.on(context.getConfiguration().getHostPathSeparator())));
+    inputs.addAll(libraries);
+
+    builder.addExecPath("--compiled", compiledSymbols);
+    inputs.add(compiledSymbols);
+
+    builder.addExecPath("--manifest", primary.getManifest());
+    inputs.add(validated.getManifest());
+
+    if (!Strings.isNullOrEmpty(customJavaPackage)) {
+      // Sets an alternative java package for the generated R.java
+      // this allows android rules to generate resources outside of the java{,tests} tree.
+      builder.add("--packageForR").add(customJavaPackage);
+    }
+
+    builder.addExecPath("--sourceJarOut", aapt2SourceJarOut);
+    outs.add(aapt2SourceJarOut);
+
+    builder.addExecPath("--rTxtOut", aapt2RTxtOut);
+    outs.add(aapt2RTxtOut);
+
+    builder.addExecPath("--staticLibraryOut", staticLibraryOut);
+    outs.add(staticLibraryOut);
+
+    ruleContext.registerAction(
+        new SpawnAction.Builder()
+            .useParameterFile(ParameterFileType.UNQUOTED)
+            .addTool(sdk.getAapt2())
+            .addInputs(inputs.build())
+            .addOutputs(outs.build())
+            .setCommandLine(builder.build())
+            .setExecutable(
+                ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
+            .setProgressMessage(
+                "Linking static android resource library for " + ruleContext.getLabel())
+            .setMnemonic("AndroidResourceLink")
+            .build(context));
+
+    return validated
+        .toBuilder()
+        .setAapt2JavaSourceJar(aapt2SourceJarOut)
+        .setAapt2RTxt(aapt2RTxtOut)
+        .setStaticLibrary(staticLibraryOut)
+        .build();
+  }
+
+  private ResourceContainer createValidateAction(ActionConstructionContext context) {
     CustomCommandLine.Builder builder = new CustomCommandLine.Builder();
 
     // Set the busybox tool.
@@ -100,13 +223,7 @@
 
     builder.addExecPath("--aapt", sdk.getAapt().getExecutable());
 
-    // Use a FluentIterable to avoid flattening the NestedSets
-    NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder();
-    inputs.addAll(
-        ruleContext
-            .getExecutablePrerequisite("$android_resources_busybox", Mode.HOST)
-            .getRunfilesSupport()
-            .getRunfilesArtifactsWithoutMiddlemen());
+    ImmutableList.Builder<Artifact> inputs = ImmutableList.builder();
 
     builder.addExecPath("--annotationJar", sdk.getAnnotationsJar());
     inputs.add(sdk.getAnnotationsJar());
@@ -145,7 +262,7 @@
         spawnActionBuilder
             .useParameterFile(ParameterFileType.UNQUOTED)
             .addTool(sdk.getAapt())
-            .addTransitiveInputs(inputs.build())
+            .addInputs(inputs.build())
             .addOutputs(ImmutableList.copyOf(outs))
             .setCommandLine(builder.build())
             .setExecutable(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
index 152548b..81e9000 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
@@ -438,7 +438,7 @@
       @Nullable Artifact mainDexProguardCfg,
       Artifact manifestOut,
       Artifact mergedResources,
-      Artifact dataBindingInfoZip,
+      @Nullable Artifact dataBindingInfoZip,
       @Nullable Artifact featureOf,
       @Nullable Artifact featureAfter)
       throws InterruptedException {
@@ -511,7 +511,7 @@
         true /* isLibrary */,
         resourceDeps,
         resourceFilter,
-        ImmutableList.<String>of(),
+        ImmutableList.<String>of() /* uncompressedExtensions */,
         false /* crunchPng */,
         false /* incremental */,
         ResourceContainer.builderFromRule(ruleContext)
@@ -572,11 +572,24 @@
           AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR);
 
       if (resourceContainer.getSymbols() != null) {
-        new AndroidResourceParsingActionBuilder(ruleContext)
-            .withPrimary(resourceContainer)
-            .setParse(data)
-            .setOutput(resourceContainer.getSymbols())
-            .build(ruleContext);
+        AndroidResourceParsingActionBuilder parsingBuilder =
+            new AndroidResourceParsingActionBuilder(ruleContext)
+                .withPrimary(resourceContainer)
+                .setParse(data)
+                .setOutput(resourceContainer.getSymbols())
+                .setCompiledSymbolsOutput(resourceContainer.getCompiledSymbols());
+
+        if (dataBindingInfoZip != null && resourceContainer.getCompiledSymbols() != null) {
+          PathFragment unusedInfo = dataBindingInfoZip.getRootRelativePath();
+          // TODO(corysmith): Centralize the data binding processing and zipping into a single
+          // action. Data binding processing needs to be triggered here as well as the merger to
+          // avoid aapt2 from throwing an error during compilation.
+          parsingBuilder.setDataBindingInfoZip(
+              ruleContext.getDerivedArtifact(
+                  unusedInfo.replaceName(unusedInfo.getBaseName() + "_unused.zip"),
+                  dataBindingInfoZip.getRoot()));
+        }
+        resourceContainer = parsingBuilder.build(ruleContext);
       }
 
       AndroidResourceMergingActionBuilder resourcesMergerBuilder =
@@ -590,16 +603,21 @@
               .setDataBindingInfoZip(dataBindingInfoZip);
       ResourceContainer merged = resourcesMergerBuilder.build(ruleContext);
 
-      AndroidResourceValidatorActionBuilder validatorBuilder =
+      processed =
           new AndroidResourceValidatorActionBuilder(ruleContext)
               .setJavaPackage(merged.getJavaPackage())
-              .setDebug(
-                  ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT)
+              .setDebug(ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT)
               .setMergedResources(mergedResources)
               .withPrimary(merged)
+              .setRTxtOut(merged.getRTxt())
               .setSourceJarOut(merged.getJavaSourceJar())
-              .setRTxtOut(merged.getRTxt());
-      processed = validatorBuilder.build(ruleContext);
+              // aapt2 related artifacts. Will be generated if the targetAaptVersion is AAPT2.
+              .withDependencies(resourceDeps)
+              .setCompiledSymbols(merged.getCompiledSymbols())
+              .setAapt2RTxtOut(merged.getAapt2RTxt())
+              .setAapt2SourceJarOut(merged.getAapt2JavaSourceJar())
+              .setStaticLibraryOut(merged.getStaticLibrary())
+              .build(ruleContext);
     } else {
       AndroidResourcesProcessorBuilder builder =
           new AndroidResourcesProcessorBuilder(ruleContext)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java
index a97d52a..4d7e5b2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java
@@ -98,6 +98,18 @@
   @Nullable
   public abstract Artifact getSymbols();
 
+  @Nullable
+  public abstract Artifact getCompiledSymbols();
+
+  @Nullable
+  public abstract Artifact getStaticLibrary();
+
+  @Nullable
+  public abstract Artifact getAapt2RTxt();
+
+  @Nullable
+  public abstract Artifact getAapt2JavaSourceJar();
+
   // The limited hashCode and equals behavior is necessary to avoid duplication when building with
   // fat_apk_cpu set. Artifacts generated in different configurations will naturally be different
   // and non-equal objects, causing the ResourceContainer not to be automatically deduplicated at
@@ -284,6 +296,14 @@
 
     public abstract Builder setSymbols(@Nullable Artifact symbols);
 
+    public abstract Builder setCompiledSymbols(@Nullable Artifact compiledSymbols);
+
+    public abstract Builder setStaticLibrary(@Nullable Artifact staticLibrary);
+
+    public abstract Builder setAapt2JavaSourceJar(@Nullable Artifact javaSourceJar);
+
+    public abstract Builder setAapt2RTxt(@Nullable Artifact rTxt);
+
     abstract ResourceContainer autoBuild();
 
     /**