Extract `aapt2 optimize` to a new action.

PiperOrigin-RevId: 268106770
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/Aapt2OptimizeActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/Aapt2OptimizeActionBuilder.java
new file mode 100644
index 0000000..77574f1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/Aapt2OptimizeActionBuilder.java
@@ -0,0 +1,62 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.rules.android;
+
+import com.google.auto.value.AutoValue;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion;
+import javax.annotation.Nullable;
+
+/** Helper for creating an {@code aapt2 optimize} action. */
+@AutoValue
+abstract class Aapt2OptimizeActionBuilder {
+
+  void registerAction(AndroidDataContext dataContext) {
+    BusyBoxActionBuilder builder =
+        BusyBoxActionBuilder.create(dataContext, "AAPT2_OPTIMIZE")
+            .addAapt(AndroidAaptVersion.AAPT2)
+            .addFlag("--");
+    if (resourcePathShorteningMapOut() != null) {
+      builder
+          .addFlag("--enable-resource-path-shortening")
+          .addOutput("--resource-path-shortening-map", resourcePathShorteningMapOut());
+    }
+    builder
+        .addOutput("-o", optimizedApkOut())
+        .addInput(resourceApk())
+        .buildAndRegister("Optimizing Android resources", "Aapt2Optimize");
+  }
+
+  abstract Artifact resourceApk();
+
+  abstract Artifact optimizedApkOut();
+
+  @Nullable
+  abstract Artifact resourcePathShorteningMapOut();
+
+  static Builder builder() {
+    return new AutoValue_Aapt2OptimizeActionBuilder.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+    abstract Builder setResourceApk(Artifact apk);
+
+    abstract Builder setOptimizedApkOut(Artifact apk);
+
+    abstract Builder setResourcePathShorteningMapOut(Artifact map);
+
+    abstract Aapt2OptimizeActionBuilder build();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
index c39ea97..03a3666 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -359,6 +359,10 @@
         AndroidBinaryMobileInstall.createMobileInstallResourceApks(
             ruleContext, dataContext, manifest);
 
+    boolean optimizeResources =
+        AndroidAaptVersion.chooseTargetAaptVersion(ruleContext) == AndroidAaptVersion.AAPT2
+            && dataContext.useResourcePathShortening();
+
     return createAndroidBinary(
         ruleContext,
         dataContext,
@@ -373,6 +377,7 @@
         resourceApk,
         mobileInstallResourceApks,
         shrinkResources,
+        optimizeResources,
         resourceClasses,
         ImmutableList.<Artifact>of(),
         ImmutableList.<Artifact>of(),
@@ -395,6 +400,7 @@
       ResourceApk resourceApk,
       @Nullable MobileInstallResourceApks mobileInstallResourceApks,
       boolean shrinkResources,
+      boolean optimizeResources,
       JavaTargetAttributes resourceClasses,
       ImmutableList<Artifact> apksUnderTest,
       ImmutableList<Artifact> additionalMergedManifests,
@@ -430,8 +436,7 @@
     // TODO(bazel-team): Verify that proguard spec files don't contain -printmapping directions
     // which this -printmapping command line flag will override.
     Artifact proguardOutputMap = null;
-    if (ProguardHelper.genProguardMapping(ruleContext.attributes())
-        || shrinkResources) {
+    if (ProguardHelper.genProguardMapping(ruleContext.attributes()) || shrinkResources) {
       proguardOutputMap = androidSemantics.getProguardOutputMap(ruleContext);
     }
 
@@ -457,6 +462,10 @@
               filesBuilder);
     }
 
+    if (optimizeResources) {
+      resourceApk = optimizeResources(dataContext, resourceApk);
+    }
+
     Artifact jarToDex = proguardOutput.getOutputJar();
     DexingOutput dexingOutput =
         dex(
@@ -954,6 +963,25 @@
             .build(dataContext));
   }
 
+  private static ResourceApk optimizeResources(
+      AndroidDataContext dataContext, ResourceApk resourceApk) throws InterruptedException {
+    Artifact optimizedApk =
+        dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_OPTIMIZED_APK);
+
+    Aapt2OptimizeActionBuilder.Builder builder =
+        Aapt2OptimizeActionBuilder.builder()
+            .setResourceApk(resourceApk.getArtifact())
+            .setOptimizedApkOut(optimizedApk);
+    if (dataContext.useResourcePathShortening()) {
+      builder.setResourcePathShorteningMapOut(
+          dataContext.createOutputArtifact(
+              AndroidRuleClasses.ANDROID_RESOURCE_PATH_SHORTENING_MAP));
+    }
+    builder.build().registerAction(dataContext);
+
+    return resourceApk.withApk(optimizedApk);
+  }
+
   @Immutable
   static final class DexingOutput {
     private final Artifact classesDexZip;
@@ -1255,12 +1283,7 @@
           ruleContext, multidex ? "minimal" : "off", dexArchives, classesDex, mainDexList, dexopts);
     } else {
       SpecialArtifact shardsToMerge =
-          createSharderAction(
-              ruleContext,
-              dexArchives,
-              mainDexList,
-              dexopts,
-              inclusionFilterJar);
+          createSharderAction(ruleContext, dexArchives, mainDexList, dexopts, inclusionFilterJar);
       Artifact multidexShards = createTemplatedMergerActions(ruleContext, shardsToMerge, dexopts);
       // TODO(b/69431301): avoid this action and give the files to apk build action directly
       createZipMergeAction(ruleContext, multidexShards, classesDex);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
index 6c0fab1..48db0b2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
@@ -42,7 +42,6 @@
 
   private Artifact proguardOut;
   private Artifact mainDexProguardOut;
-  private Artifact resourcePathShorteningMapOut;
   private boolean conditionalKeepRules;
   private Artifact rTxtOut;
   private Artifact sourceJarOut;
@@ -125,12 +124,6 @@
     return this;
   }
 
-  public AndroidResourcesProcessorBuilder setResourcePathShorteningMapOut(
-      Artifact resourcePathShorteningMapOut) {
-    this.resourcePathShorteningMapOut = resourcePathShorteningMapOut;
-    return this;
-  }
-
   public AndroidResourcesProcessorBuilder setRTxtOut(Artifact rTxtOut) {
     this.rTxtOut = rTxtOut;
     return this;
@@ -333,12 +326,6 @@
           .addTransitiveInputValues(assetDependencies.getTransitiveCompiledSymbols());
     }
 
-    if (resourcePathShorteningMapOut != null) {
-      builder
-          .addFlag("--resourcePathShortening")
-          .maybeAddOutput("--resourcePathShorteningMapOutput", resourcePathShorteningMapOut);
-    }
-
     builder.maybeAddFlag("--conditionalKeepRules", conditionalKeepRules);
 
     configureCommonFlags(dataContext, primaryResources, primaryAssets, primaryManifest, builder)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
index d0a1f94..cfafdc1 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -108,6 +108,8 @@
       fromTemplates("%{name}_files/%{name}_resources_aapt2-src.jar");
   public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_SHRUNK_APK =
       fromTemplates("%{name}_shrunk.ap_");
+  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_OPTIMIZED_APK =
+      fromTemplates("%{name}_optimized.ap_");
   public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_ZIP =
       fromTemplates("%{name}_files/resource_files.zip");
   public static final SafeImplicitOutputsFunction ANDROID_ASSETS_ZIP =
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java
index d93b9f4..4d6ad92 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java
@@ -71,6 +71,14 @@
   }
 
   /** Adds a direct input artifact. */
+  public BusyBoxActionBuilder addInput(Artifact value) {
+    Preconditions.checkNotNull(value);
+    commandLine.addExecPath(value);
+    inputs.add(value);
+    return this;
+  }
+
+  /** Adds a direct input artifact. */
   public BusyBoxActionBuilder addInput(@CompileTimeConstant String arg, Artifact value) {
     Preconditions.checkNotNull(value);
     commandLine.addExecPath(arg, value);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ProcessedAndroidData.java b/src/main/java/com/google/devtools/build/lib/rules/android/ProcessedAndroidData.java
index c33220e..f5a2e44 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ProcessedAndroidData.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ProcessedAndroidData.java
@@ -73,10 +73,6 @@
       throw errorConsumer.throwWithRuleError(
           "resource cycle shrinking can only be enabled for builds with aapt2");
     }
-    if (dataContext.useResourcePathShortening() && aaptVersion != AndroidAaptVersion.AAPT2) {
-      throw errorConsumer.throwWithRuleError(
-          "resource path shortening can only be enabled for builds with aapt2");
-    }
 
     AndroidResourcesProcessorBuilder builder =
         builderForNonIncrementalTopLevelTarget(dataContext, manifest, manifestValues, aaptVersion)
@@ -88,11 +84,6 @@
                 AndroidBinary.createMainDexProguardSpec(
                     dataContext.getLabel(), dataContext.getActionConstructionContext()))
             .conditionalKeepRules(conditionalKeepRules);
-    if (dataContext.useResourcePathShortening()) {
-      builder.setResourcePathShorteningMapOut(
-          dataContext.createOutputArtifact(
-              AndroidRuleClasses.ANDROID_RESOURCE_PATH_SHORTENING_MAP));
-    }
     dataBindingContext.supplyLayoutInfo(builder::setDataBindingInfoZip);
     return buildActionForBinary(
         dataContext,
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java
index 34097a8..f4468df 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getArtifactsEndingWith;
 import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith;
+import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.hasInput;
 import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.prettyArtifactNames;
 import static com.google.devtools.build.lib.rules.java.JavaCompileActionTestHelper.getJavacArguments;
 import static com.google.devtools.build.lib.rules.java.JavaCompileActionTestHelper.getProcessorpath;
@@ -937,7 +938,7 @@
   }
 
   @Test
-  public void testResourcePathShortening_flagEnabledAndCOpt_flagsGetPassedToAction()
+  public void testResourcePathShortening_flagEnabledAndCOpt_optimizedApkIsInputToApkBuilderAction()
       throws Exception {
     useConfiguration("--experimental_android_resource_path_shortening", "-c", "opt");
 
@@ -945,23 +946,26 @@
 
     Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary));
 
-    Artifact resourcePathShorteningMapArtifact =
-        getFirstArtifactEndingWith(artifacts, "app_resource_paths.map");
-    assertThat(resourcePathShorteningMapArtifact).isNotNull();
-    assertThat(getGeneratingAction(resourcePathShorteningMapArtifact).getMnemonic())
-        .isEqualTo("AndroidAapt2");
+    SpawnAction optimizeAction =
+        getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "app_optimized.ap_"));
+    assertThat(optimizeAction.getMnemonic()).isEqualTo("Aapt2Optimize");
+    assertThat(getGeneratingAction(getFirstArtifactEndingWith(artifacts, "app_resource_paths.map")))
+        .isEqualTo(optimizeAction);
 
-    Artifact androidResourcesApk = getFirstArtifactEndingWith(artifacts, ".ap_");
-    assertThat(getGeneratingAction(androidResourcesApk).getMnemonic()).isEqualTo("AndroidAapt2");
-
-    List<String> processingArgs = getGeneratingSpawnActionArgs(androidResourcesApk);
-    assertThat(processingArgs).contains("--resourcePathShortening");
-    assertThat(flagValue("--resourcePathShorteningMapOutput", processingArgs))
+    List<String> processingArgs = optimizeAction.getArguments();
+    assertThat(processingArgs).contains("--enable-resource-path-shortening");
+    assertThat(flagValue("--resource-path-shortening-map", processingArgs))
         .endsWith("app_resource_paths.map");
+
+    // Verify that the optimized APK is an input to build the unsigned APK.
+    SpawnAction apkAction =
+        getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "app_unsigned.apk"));
+    assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder");
+    assertThat(hasInput(apkAction, "app_optimized.ap_")).isTrue();
   }
 
   @Test
-  public void testResourcePathShortening_flagEnabledAndCDefault_flagsNotPassedToAction()
+  public void testResourcePathShortening_flagEnabledAndCDefault_optimizeArtifactsAbsent()
       throws Exception {
     useConfiguration("--experimental_android_resource_path_shortening");
 
@@ -973,16 +977,18 @@
         getFirstArtifactEndingWith(artifacts, "app_resource_paths.map");
     assertThat(resourcePathShorteningMapArtifact).isNull();
 
-    Artifact androidResourcesApk = getFirstArtifactEndingWith(artifacts, ".ap_");
-    assertThat(getGeneratingAction(androidResourcesApk).getMnemonic()).isEqualTo("AndroidAapt2");
+    Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, "app_optimized.ap_");
+    assertThat(optimizedResourceApk).isNull();
 
-    List<String> processingArgs = getGeneratingSpawnActionArgs(androidResourcesApk);
-    assertThat(processingArgs).doesNotContain("--resourcePathShortening");
-    assertThat(processingArgs).doesNotContain("--resourcePathShorteningMapOutput");
+    // Verify that the optimized APK is not an input to build the unsigned APK.
+    SpawnAction apkAction =
+        getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "app_unsigned.apk"));
+    assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder");
+    assertThat(hasInput(apkAction, "app_optimized.ap_")).isFalse();
   }
 
   @Test
-  public void testResourcePathShorteningMap_flagNotEnabledAndCOpt_flagsNotPassedToAction()
+  public void testResourcePathShorteningMap_flagNotEnabledAndCOpt_optimizeArtifactsAbsent()
       throws Exception {
     useConfiguration("-c", "opt");
 
@@ -994,12 +1000,14 @@
         getFirstArtifactEndingWith(artifacts, "app_resource_paths.map");
     assertThat(resourcePathShorteningMapArtifact).isNull();
 
-    Artifact androidResourcesApk = getFirstArtifactEndingWith(artifacts, ".ap_");
-    assertThat(getGeneratingAction(androidResourcesApk).getMnemonic()).isEqualTo("AndroidAapt2");
+    Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, "app_optimized.ap_");
+    assertThat(optimizedResourceApk).isNull();
 
-    List<String> processingArgs = getGeneratingSpawnActionArgs(androidResourcesApk);
-    assertThat(processingArgs).doesNotContain("--resourcePathShortening");
-    assertThat(processingArgs).doesNotContain("--resourcePathShorteningMapOutput");
+    // Verify that the optimized APK is not an input to build the unsigned APK.
+    SpawnAction apkAction =
+        getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "app_unsigned.apk"));
+    assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder");
+    assertThat(hasInput(apkAction, "app_optimized.ap_")).isFalse();
   }
 
   @Test