aar_import creates res/values/empty.xml if it contains no resources.

To do this, add a new tool that is used instead of zipper to get the resources out of the AAR. This tool creates res/values/empty.xml if there are no resources in the AAR.

Also, some general cleaning of the code.

RELNOTES: None
PiperOrigin-RevId: 166768607
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java
index 19ff508..61295fc 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java
@@ -83,13 +83,9 @@
     ruleContext.registerAction(createSingleFileExtractorActions(
         ruleContext, aar, ANDROID_MANIFEST, androidManifestArtifact));
 
-    Artifact resourcesManifest = createAarArtifact(ruleContext, "resource_manifest");
-    ruleContext.registerAction(
-        createManifestExtractorActions(ruleContext, aar, "res/.*", resourcesManifest));
-
     Artifact resources = createAarTreeArtifact(ruleContext, "resources");
     ruleContext.registerAction(
-        createManifestFileEntriesExtractorActions(ruleContext, aar, resourcesManifest, resources));
+        createAarResourcesExtractorActions(ruleContext, aar, resources));
 
     ApplicationManifest androidManifest =
         ApplicationManifest.fromExplicitManifest(ruleContext, androidManifestArtifact);
@@ -110,8 +106,7 @@
             ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT),
             ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LOCAL_SYMBOLS),
             ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST),
-            resourcesZip,
-            /* alwaysExportManifest = */ true);
+            resourcesZip);
 
     // There isn't really any use case for building an aar_import target on its own, so the files to
     // build could be empty. The resources zip and merged jars are added here as a sanity check for
@@ -123,8 +118,8 @@
     Artifact nativeLibs = createAarArtifact(ruleContext, "native_libs.zip");
     ruleContext.registerAction(createAarNativeLibsFilterActions(ruleContext, aar, nativeLibs));
 
-    JavaRuleOutputJarsProvider.Builder jarProviderBuilder = new JavaRuleOutputJarsProvider.Builder()
-        .addOutputJar(mergedJar, null, ImmutableList.<Artifact>of());
+    JavaRuleOutputJarsProvider.Builder jarProviderBuilder =
+        new JavaRuleOutputJarsProvider.Builder().addOutputJar(mergedJar, null, ImmutableList.of());
     for (TransitiveInfoCollection export : ruleContext.getPrerequisites("exports", Mode.TARGET)) {
       for (OutputJar jar :
           JavaInfo.getProvider(JavaRuleOutputJarsProvider.class, export).getOutputJars()) {
@@ -134,13 +129,12 @@
     }
 
     ImmutableList<TransitiveInfoCollection> targets =
-        ImmutableList.<TransitiveInfoCollection>copyOf(
-            ruleContext.getPrerequisites("exports", Mode.TARGET));
+        ImmutableList.copyOf(ruleContext.getPrerequisites("exports", Mode.TARGET));
     JavaCommon common =
         new JavaCommon(
             ruleContext,
             javaSemantics,
-            /* sources = */ ImmutableList.<Artifact>of(),
+            /* sources = */ ImmutableList.of(),
             /* compileDeps = */ targets,
             /* runtimeDeps = */ targets,
             /* bothDeps = */ targets);
@@ -182,7 +176,7 @@
   private static Action[] createSingleFileExtractorActions(RuleContext ruleContext, Artifact aar,
       String filename, Artifact outputArtifact) {
     return new SpawnAction.Builder()
-        .setExecutable(ruleContext.getExecutablePrerequisite("$zipper", Mode.HOST))
+        .setExecutable(ruleContext.getExecutablePrerequisite(AarImportBaseRule.ZIPPER, Mode.HOST))
         .setMnemonic("AarFileExtractor")
         .setProgressMessage("Extracting %s from %s", filename, aar.getFilename())
         .addInput(aar)
@@ -196,20 +190,20 @@
         .build(ruleContext);
   }
 
-  private static Action[] createManifestFileEntriesExtractorActions(RuleContext ruleContext,
-      Artifact aar, Artifact manifest, Artifact outputTree) {
+  private static Action[] createAarResourcesExtractorActions(
+      RuleContext ruleContext, Artifact aar, Artifact outputTree) {
     return new SpawnAction.Builder()
-        .setExecutable(ruleContext.getExecutablePrerequisite("$zipper", Mode.HOST))
-        .setMnemonic("AarManifestFileEntriesExtractor")
+        .setExecutable(
+            ruleContext.getExecutablePrerequisite(
+                AarImportBaseRule.AAR_RESOURCES_EXTRACTOR, Mode.HOST))
+        .setMnemonic("AarResourcesExtractor")
         .addInput(aar)
         .addOutput(outputTree)
         .setCommandLine(
             CustomCommandLine.builder()
-                .addExecPath("x", aar)
-                .addExecPath("-d", outputTree)
-                .addPrefixedExecPath("@", manifest)
+                .addExecPath("--input_aar", aar)
+                .addExecPath("--output_res_dir", outputTree)
                 .build())
-        .addInput(manifest)
         .build(ruleContext);
   }
 
@@ -217,7 +211,8 @@
       Artifact aar, Artifact jarsTreeArtifact, Artifact singleJarParamFile) {
     return new SpawnAction.Builder()
         .setExecutable(
-            ruleContext.getExecutablePrerequisite("$aar_embedded_jars_extractor", Mode.HOST))
+            ruleContext.getExecutablePrerequisite(
+                AarImportBaseRule.AAR_EMBEDDED_JARS_EXTACTOR, Mode.HOST))
         .setMnemonic("AarEmbeddedJarsExtractor")
         .setProgressMessage("Extracting classes.jar and libs/*.jar from %s", aar.getFilename())
         .addInput(aar)
@@ -249,30 +244,13 @@
         .build(ruleContext);
   }
 
-  private static Action[] createManifestExtractorActions(RuleContext ruleContext, Artifact aar,
-      String filenameRegexp, Artifact manifest) {
-    return new SpawnAction.Builder()
-        .setExecutable(ruleContext.getExecutablePrerequisite("$zip_manifest_creator", Mode.HOST))
-        .setMnemonic("ZipManifestCreator")
-        .setProgressMessage(
-            "Creating manifest for %s matching %s", aar.getFilename(), filenameRegexp)
-        .addInput(aar)
-        .addOutput(manifest)
-        .setCommandLine(
-            CustomCommandLine.builder()
-                .addDynamicString(filenameRegexp)
-                .addExecPath(aar)
-                .addExecPath(manifest)
-                .build())
-        .build(ruleContext);
-  }
-
   private static Action[] createAarNativeLibsFilterActions(RuleContext ruleContext, Artifact aar,
       Artifact outputZip) {
     SpawnAction.Builder actionBuilder =
         new SpawnAction.Builder()
             .setExecutable(
-                ruleContext.getExecutablePrerequisite("$aar_native_libs_zip_creator", Mode.HOST))
+                ruleContext.getExecutablePrerequisite(
+                    AarImportBaseRule.AAR_NATIVE_LIBS_ZIP_CREATOR, Mode.HOST))
             .setMnemonic("AarNativeLibsFilter")
             .setProgressMessage("Filtering AAR native libs by architecture")
             .addInput(aar)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarImportBaseRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarImportBaseRule.java
index 530a04c..52262de 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AarImportBaseRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarImportBaseRule.java
@@ -21,7 +21,6 @@
 
 import com.google.devtools.build.lib.analysis.RuleDefinition;
 import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
-import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClass.Builder;
 import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
@@ -32,15 +31,18 @@
 /** Rule definition for the aar_import rule. */
 public class AarImportBaseRule implements RuleDefinition {
 
+  static final String AAR_EMBEDDED_JARS_EXTACTOR = "$aar_embedded_jars_extractor";
+  static final String AAR_NATIVE_LIBS_ZIP_CREATOR = "$aar_native_libs_zip_creator";
+  static final String AAR_RESOURCES_EXTRACTOR = "$aar_resources_extractor";
+  static final String ZIPPER = "$zipper";
+
   @Override
-  public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
+  public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
     return builder
         /* <!-- #BLAZE_RULE($aar_import_base).ATTRIBUTE(aar) -->
         The <code>.aar</code> file to provide to the Android targets that depend on this target.
         <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
-        .add(attr("aar", LABEL)
-            .mandatory()
-            .allowedFileTypes(FileType.of(".aar")))
+        .add(attr("aar", LABEL).mandatory().allowedFileTypes(FileType.of(".aar")))
         /* <!-- #BLAZE_RULE(aar_import).ATTRIBUTE(exports) -->
         Targets to export to rules that depend on this rule.
         See <a href="${link java_library.exports}">java_library.exports.
@@ -49,26 +51,22 @@
             .allowedRuleClasses("aar_import", "java_import")
             .allowedFileTypes()
             .validityPredicate(ANY_EDGE))
-        .add(attr("$aar_embedded_jars_extractor", LABEL)
+        .add(attr(AAR_EMBEDDED_JARS_EXTACTOR, LABEL)
             .cfg(HOST)
             .exec()
-            .value(Label.parseAbsoluteUnchecked(
-                environment.getToolsRepository() + "//tools/android:aar_embedded_jars_extractor")))
-        .add(attr("$aar_native_libs_zip_creator", LABEL)
+            .value(env.getToolsLabel("//tools/android:aar_embedded_jars_extractor")))
+        .add(attr(AAR_NATIVE_LIBS_ZIP_CREATOR, LABEL)
             .cfg(HOST)
             .exec()
-            .value(Label.parseAbsoluteUnchecked(
-                environment.getToolsRepository() + "//tools/android:aar_native_libs_zip_creator")))
-        .add(attr("$zip_manifest_creator", LABEL)
+            .value(env.getToolsLabel("//tools/android:aar_native_libs_zip_creator")))
+        .add(attr(AAR_RESOURCES_EXTRACTOR, LABEL)
             .cfg(HOST)
             .exec()
-            .value(Label.parseAbsoluteUnchecked(
-                environment.getToolsRepository() + "//tools/android:zip_manifest_creator")))
-        .add(attr("$zipper", LABEL)
+            .value(env.getToolsLabel("//tools/android:aar_resources_extractor")))
+        .add(attr(ZIPPER, LABEL)
             .cfg(HOST)
             .exec()
-            .value(Label.parseAbsoluteUnchecked(
-                environment.getToolsRepository() + "//tools/zip:zipper")))
+            .value(env.getToolsLabel("//tools/zip:zipper")))
         .advertiseProvider(JavaCompilationArgsProvider.class)
         .build();
   }
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 f15eba6..f291995 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
@@ -360,8 +360,7 @@
       Artifact rTxt,
       Artifact symbols,
       Artifact manifestOut,
-      Artifact mergedResources,
-      boolean alwaysExportManifest) throws InterruptedException, RuleErrorException {
+      Artifact mergedResources) throws InterruptedException, RuleErrorException {
     if (ruleContext.hasErrors()) {
       return null;
     }
@@ -369,16 +368,14 @@
         ResourceContainer.builderFromRule(ruleContext)
             .setRTxt(rTxt)
             .setSymbols(symbols)
-            .setJavaPackageFrom(JavaPackageSource.MANIFEST);
-    if (alwaysExportManifest) {
-      builder.setManifestExported(true);
-    }
+            .setJavaPackageFrom(JavaPackageSource.MANIFEST)
+            .setManifestExported(true);
 
     return createApk(
         ruleContext,
         true, /* isLibrary */
         resourceDeps,
-        ImmutableList.<String>of(), /* List<String> uncompressedExtensions */
+        ImmutableList.of(), /* List<String> uncompressedExtensions */
         false, /* crunchPng */
         false, /* incremental */
         builder,
@@ -788,7 +785,7 @@
     List<String> uncompressedExtensions =
         ruleContext.getTokenizedStringListAttr("nocompress_extensions");
 
-    ImmutableList.Builder<String> additionalAaptOpts = ImmutableList.<String>builder();
+    ImmutableList.Builder<String> additionalAaptOpts = ImmutableList.builder();
 
     for (String extension : uncompressedExtensions) {
       additionalAaptOpts.add("-0").add(extension);
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index bd6ce31..f3525b3 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -247,7 +247,7 @@
         .add("java_binary(name = 'IdlClass',")
         .add("            runtime_deps = [ ':idlclass_import' ],")
         .add("            main_class = 'com.google.devtools.build.android.idlclass.IdlClass')")
-        .add("sh_binary(name = 'zip_manifest_creator', srcs = ['empty.sh'])")
+        .add("sh_binary(name = 'aar_resources_extractor', srcs = ['empty.sh'])")
         .add("sh_binary(name = 'aar_embedded_jars_extractor', srcs = ['empty.sh'])")
         .add("java_import(name = 'idlclass_import',")
         .add("            jars = [ 'idlclass.jar' ])")
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AarImportTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AarImportTest.java
index 245698d..bb52347 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/android/AarImportTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/AarImportTest.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.rules.android;
 
+import static com.google.common.collect.Streams.stream;
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.base.Predicates;
@@ -21,6 +22,7 @@
 import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.FileConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -86,6 +88,31 @@
   }
 
   @Test
+  public void testResourcesExtractor() throws Exception {
+    AndroidResourcesProvider resourcesProvider =
+        getConfiguredTarget("//a:foo").getProvider(AndroidResourcesProvider.class);
+
+    Artifact resourceTreeArtifact =
+        stream(resourcesProvider.getDirectAndroidResources())
+            .flatMap(resourceContainer -> resourceContainer.getResources().stream())
+            .findFirst()
+            .get();
+    Artifact aarResourcesExtractor =
+        getHostConfiguredTarget(
+            ruleClassProvider.getToolsRepository() + "//tools/android:aar_resources_extractor")
+        .getProvider(FilesToRunProvider.class)
+        .getExecutable();
+
+    assertThat(getGeneratingSpawnAction(resourceTreeArtifact).getArguments())
+        .containsExactly(
+            aarResourcesExtractor.getExecPathString(),
+            "--input_aar",
+            "a/foo.aar",
+            "--output_res_dir",
+            resourceTreeArtifact.getExecPathString());
+  }
+
+  @Test
   public void testNativeLibsProvided() throws Exception {
     ConfiguredTarget androidLibraryTarget = getConfiguredTarget("//java:lib");
 
diff --git a/tools/android/BUILD b/tools/android/BUILD
index 19155c2..75a9956 100644
--- a/tools/android/BUILD
+++ b/tools/android/BUILD
@@ -120,23 +120,6 @@
     ],
 )
 
-sh_binary(
-    name = "zip_manifest_creator",
-    srcs = ["zip_manifest_creator.sh"],
-    data = ["//tools/zip:zipper"],
-)
-
-sh_test(
-    name = "zip_manifest_creator_test",
-    size = "small",
-    srcs = ["zip_manifest_creator_test.sh"],
-    args = ["$(location //tools/zip:zipper)"],
-    data = [
-        ":zip_manifest_creator",
-        "//tools/zip:zipper",
-    ],
-)
-
 py_binary(
     name = "aar_embedded_jars_extractor",
     srcs = ["aar_embedded_jars_extractor.py"],
@@ -150,6 +133,18 @@
 )
 
 py_binary(
+    name = "aar_resources_extractor",
+    srcs = ["aar_resources_extractor.py"],
+    deps = ["//third_party/py/gflags"],
+)
+
+py_test(
+    name = "aar_resources_extractor_test",
+    srcs = ["aar_resources_extractor_test.py"],
+    deps = [":aar_resources_extractor"],
+)
+
+py_binary(
     name = "resource_extractor",
     srcs = ["resource_extractor.py"],
 )
diff --git a/tools/android/BUILD.tools b/tools/android/BUILD.tools
index 866d6af..bc43e41 100644
--- a/tools/android/BUILD.tools
+++ b/tools/android/BUILD.tools
@@ -151,12 +151,6 @@
     srcs = ["fail.sh"],
 )
 
-sh_binary(
-    name = "zip_manifest_creator",
-    srcs = ["zip_manifest_creator.sh"],
-    data = ["//tools/zip:zipper"],
-)
-
 py_binary(
     name = "aar_embedded_jars_extractor",
     srcs = ["aar_embedded_jars_extractor.py"],
@@ -164,6 +158,12 @@
 )
 
 py_binary(
+    name = "aar_resources_extractor",
+    srcs = ["aar_resources_extractor.py"],
+    deps = ["//third_party/py/gflags"],
+)
+
+py_binary(
     name = "resource_extractor",
     srcs = ["resource_extractor.py"],
 )
diff --git a/tools/android/aar_resources_extractor.py b/tools/android/aar_resources_extractor.py
new file mode 100644
index 0000000..0e259b8
--- /dev/null
+++ b/tools/android/aar_resources_extractor.py
@@ -0,0 +1,58 @@
+# pylint: disable=g-direct-third-party-import
+# 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.
+
+"""A tool for extracting resource files from an AAR.
+
+An AAR may contain resources under the /res directory. This tool extracts all
+of the resources into a directory. If no resources exist, it creates an
+empty.xml file that defines no resources.
+
+In the future, this script may be extended to also extract assets.
+"""
+
+import os
+import sys
+import zipfile
+
+from third_party.py import gflags
+
+FLAGS = gflags.FLAGS
+
+gflags.DEFINE_string("input_aar", None, "Input AAR")
+gflags.MarkFlagAsRequired("input_aar")
+gflags.DEFINE_string("output_res_dir", None, "Output resources directory")
+gflags.MarkFlagAsRequired("output_res_dir")
+
+
+def ExtractResources(aar, output_res_dir):
+  aar_contains_no_resources = True
+  for name in aar.namelist():
+    if name.startswith("res/"):
+      aar.extract(name, output_res_dir)
+      aar_contains_no_resources = False
+  if aar_contains_no_resources:
+    empty_xml_filename = output_res_dir + "/res/values/empty.xml"
+    os.makedirs(os.path.dirname(empty_xml_filename))
+    with open(empty_xml_filename, "wb") as empty_xml:
+      empty_xml.write("<resources/>")
+
+
+def main():
+  with zipfile.ZipFile(FLAGS.input_aar, "r") as aar:
+    ExtractResources(aar, FLAGS.output_res_dir)
+
+if __name__ == "__main__":
+  FLAGS(sys.argv)
+  main()
diff --git a/tools/android/aar_resources_extractor_test.py b/tools/android/aar_resources_extractor_test.py
new file mode 100644
index 0000000..b59e416
--- /dev/null
+++ b/tools/android/aar_resources_extractor_test.py
@@ -0,0 +1,64 @@
+# 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.
+
+"""Tests for aar_resources_extractor."""
+
+import os
+import shutil
+import StringIO
+import unittest
+import zipfile
+
+from tools.android import aar_resources_extractor
+
+
+class AarResourcesExtractorTest(unittest.TestCase):
+  """Unit tests for aar_resources_extractor.py."""
+
+  def setUp(self):
+    os.chdir(os.environ["TEST_TMPDIR"])
+
+  def tearDown(self):
+    shutil.rmtree("out_dir")
+
+  def DirContents(self, d):
+    return [path + "/" + f for (path, _, files) in os.walk(d)
+            for f in files]
+
+  def testNoResources(self):
+    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    os.makedirs("out_dir")
+    aar_resources_extractor.ExtractResources(aar, "out_dir")
+    self.assertEqual(["out_dir/res/values/empty.xml"],
+                     self.DirContents("out_dir"))
+    with open("out_dir/res/values/empty.xml", "r") as empty_xml:
+      self.assertEqual("<resources/>", empty_xml.read())
+
+  def testContainsResources(self):
+    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    aar.writestr("res/values/values.xml", "some values")
+    aar.writestr("res/layouts/layout.xml", "some layout")
+    os.makedirs("out_dir")
+    aar_resources_extractor.ExtractResources(aar, "out_dir")
+    expected_resources = ["out_dir/res/values/values.xml",
+                          "out_dir/res/layouts/layout.xml"]
+    self.assertItemsEqual(expected_resources, self.DirContents("out_dir"))
+    with open("out_dir/res/values/values.xml", "r") as values_xml:
+      self.assertEqual("some values", values_xml.read())
+    with open("out_dir/res/layouts/layout.xml", "r") as layout_xml:
+      self.assertEqual("some layout", layout_xml.read())
+
+
+if __name__ == "__main__":
+  unittest.main()