Enable using a parameter for large lists of resources to link.

RELNOTES: None
PiperOrigin-RevId: 166379334
diff --git a/src/test/java/com/google/devtools/build/android/AaptCommandBuilderTest.java b/src/test/java/com/google/devtools/build/android/AaptCommandBuilderTest.java
index c715d41..9dc1683 100644
--- a/src/test/java/com/google/devtools/build/android/AaptCommandBuilderTest.java
+++ b/src/test/java/com/google/devtools/build/android/AaptCommandBuilderTest.java
@@ -14,15 +14,21 @@
 package com.google.devtools.build.android;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toList;
 
 import com.android.builder.core.VariantType;
 import com.android.repository.Revision;
+import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
 import com.google.common.testing.NullPointerTester;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -33,12 +39,15 @@
 public class AaptCommandBuilderTest {
   private Path aapt;
   private Path manifest;
+  private Path workingDirectory;
 
   @Before
-  public void createPaths() {
-    FileSystem fs = FileSystems.getDefault();
-    aapt = fs.getPath("aapt");
-    manifest = fs.getPath("AndroidManifest.xml");
+  public void createPaths() throws IOException {
+    final Path tempDirectory =
+        Files.createTempDirectory(AaptCommandBuilderTest.class.getCanonicalName());
+    aapt = tempDirectory.resolve("aapt");
+    manifest = tempDirectory.resolve("AndroidManifest.xml");
+    workingDirectory = tempDirectory.resolve("working");
   }
 
   @Test
@@ -125,139 +134,167 @@
     list.add("gif");
     list.add(null);
     list.add("png");
-    assertThat(
-            new AaptCommandBuilder(aapt).addRepeated("-0", list).build())
+    assertThat(new AaptCommandBuilder(aapt).addRepeated("-0", list).build())
         .containsExactly(aapt.toString(), "-0", "gif", "-0", "png")
         .inOrder();
   }
 
-
   @Test
   public void testThenAddFlagForwardsCallAfterWhenTrue() {
-    assertThat(
-        new AaptCommandBuilder(aapt).when(true).thenAdd("--addthisflag").build())
+    assertThat(new AaptCommandBuilder(aapt).when(true).thenAdd("--addthisflag").build())
         .contains("--addthisflag");
   }
 
   @Test
   public void testThenAddFlagWithValueForwardsCallAfterWhenTrue() {
     assertThat(
-        new AaptCommandBuilder(aapt)
-            .when(true).thenAdd("--addthisflag", "andthisvalue").build())
+            new AaptCommandBuilder(aapt)
+                .when(true)
+                .thenAdd("--addthisflag", "andthisvalue")
+                .build())
         .contains("--addthisflag");
   }
 
   @Test
   public void testThenAddFlagWithPathForwardsCallAfterWhenTrue() {
-    assertThat(
-        new AaptCommandBuilder(aapt)
-            .when(true).thenAdd("--addthisflag", manifest).build())
+    assertThat(new AaptCommandBuilder(aapt).when(true).thenAdd("--addthisflag", manifest).build())
         .contains("--addthisflag");
   }
 
   @Test
   public void testThenAddRepeatedForwardsCallAfterWhenTrue() {
     assertThat(
-        new AaptCommandBuilder(aapt)
-            .when(true).thenAddRepeated("--addthisflag", ImmutableList.of("andthesevalues"))
-            .build())
+            new AaptCommandBuilder(aapt)
+                .when(true)
+                .thenAddRepeated("--addthisflag", ImmutableList.of("andthesevalues"))
+                .build())
         .contains("--addthisflag");
   }
 
   @Test
   public void testThenAddFlagDoesNothingAfterWhenFalse() {
-    assertThat(
-        new AaptCommandBuilder(aapt).when(false).thenAdd("--dontaddthisflag").build())
+    assertThat(new AaptCommandBuilder(aapt).when(false).thenAdd("--dontaddthisflag").build())
         .doesNotContain("--dontaddthisflag");
   }
 
   @Test
   public void testThenAddFlagWithValueDoesNothingAfterWhenFalse() {
     assertThat(
-        new AaptCommandBuilder(aapt)
-            .when(false).thenAdd("--dontaddthisflag", "orthisvalue").build())
+            new AaptCommandBuilder(aapt)
+                .when(false)
+                .thenAdd("--dontaddthisflag", "orthisvalue")
+                .build())
         .doesNotContain("--dontaddthisflag");
   }
 
   @Test
   public void testThenAddFlagWithPathDoesNothingAfterWhenFalse() {
     assertThat(
-        new AaptCommandBuilder(aapt)
-            .when(false).thenAdd("--dontaddthisflag", manifest).build())
+            new AaptCommandBuilder(aapt).when(false).thenAdd("--dontaddthisflag", manifest).build())
         .doesNotContain("--dontaddthisflag");
   }
 
   @Test
   public void testThenAddRepeatedDoesNothingAfterWhenFalse() {
     assertThat(
-        new AaptCommandBuilder(aapt)
-            .when(false).thenAddRepeated("--dontaddthisflag", ImmutableList.of("orthesevalues"))
-            .build())
+            new AaptCommandBuilder(aapt)
+                .when(false)
+                .thenAddRepeated("--dontaddthisflag", ImmutableList.of("orthesevalues"))
+                .build())
         .doesNotContain("--dontaddthisflag");
   }
 
   @Test
   public void testWhenVersionIsAtLeastAddsFlagsForEqualVersion() {
     assertThat(
-        new AaptCommandBuilder(aapt).forBuildToolsVersion(new Revision(23))
-            .whenVersionIsAtLeast(new Revision(23)).thenAdd("--addthisflag")
-            .build())
+            new AaptCommandBuilder(aapt)
+                .forBuildToolsVersion(new Revision(23))
+                .whenVersionIsAtLeast(new Revision(23))
+                .thenAdd("--addthisflag")
+                .build())
         .contains("--addthisflag");
   }
 
   @Test
   public void testWhenVersionIsAtLeastAddsFlagsForGreaterVersion() {
     assertThat(
-        new AaptCommandBuilder(aapt).forBuildToolsVersion(new Revision(24))
-            .whenVersionIsAtLeast(new Revision(23)).thenAdd("--addthisflag")
-            .build())
-
+            new AaptCommandBuilder(aapt)
+                .forBuildToolsVersion(new Revision(24))
+                .whenVersionIsAtLeast(new Revision(23))
+                .thenAdd("--addthisflag")
+                .build())
         .contains("--addthisflag");
   }
 
   @Test
   public void testWhenVersionIsAtLeastAddsFlagsForUnspecifiedVersion() {
     assertThat(
-        new AaptCommandBuilder(aapt)
-            .whenVersionIsAtLeast(new Revision(23)).thenAdd("--addthisflag")
-            .build())
+            new AaptCommandBuilder(aapt)
+                .whenVersionIsAtLeast(new Revision(23))
+                .thenAdd("--addthisflag")
+                .build())
         .contains("--addthisflag");
   }
 
   @Test
   public void testWhenVersionIsAtLeastDoesNotAddFlagsForLesserVersion() {
     assertThat(
-        new AaptCommandBuilder(aapt).forBuildToolsVersion(new Revision(22))
-            .whenVersionIsAtLeast(new Revision(23)).thenAdd("--dontaddthisflag")
-            .build())
+            new AaptCommandBuilder(aapt)
+                .forBuildToolsVersion(new Revision(22))
+                .whenVersionIsAtLeast(new Revision(23))
+                .thenAdd("--dontaddthisflag")
+                .build())
         .doesNotContain("--dontaddthisflag");
   }
 
   @Test
   public void testWhenVariantIsAddsFlagsForEqualVariantType() {
     assertThat(
-        new AaptCommandBuilder(aapt).forVariantType(VariantType.LIBRARY)
-            .whenVariantIs(VariantType.LIBRARY).thenAdd("--addthisflag")
-            .build())
+            new AaptCommandBuilder(aapt)
+                .forVariantType(VariantType.LIBRARY)
+                .whenVariantIs(VariantType.LIBRARY)
+                .thenAdd("--addthisflag")
+                .build())
         .contains("--addthisflag");
   }
 
   @Test
   public void testWhenVariantIsDoesNotAddFlagsForUnequalVariantType() {
     assertThat(
-        new AaptCommandBuilder(aapt).forVariantType(VariantType.DEFAULT)
-            .whenVariantIs(VariantType.LIBRARY).thenAdd("--dontaddthisflag")
-            .build())
+            new AaptCommandBuilder(aapt)
+                .forVariantType(VariantType.DEFAULT)
+                .whenVariantIs(VariantType.LIBRARY)
+                .thenAdd("--dontaddthisflag")
+                .build())
         .doesNotContain("--dontaddthisflag");
   }
 
   @Test
   public void testWhenVariantIsDoesNotAddFlagsForUnspecifiedVariantType() {
     assertThat(
-        new AaptCommandBuilder(aapt)
-            .whenVariantIs(VariantType.LIBRARY).thenAdd("--dontaddthisflag")
-            .build())
+            new AaptCommandBuilder(aapt)
+                .whenVariantIs(VariantType.LIBRARY)
+                .thenAdd("--dontaddthisflag")
+                .build())
         .doesNotContain("--dontaddthisflag");
   }
 
+  @Test
+  public void testParamFile() throws IOException {
+
+    final List<String> resources =
+        IntStream.range(0, 201).mapToObj(i -> "res" + i).collect(toList());
+    assertThat(
+            new AaptCommandBuilder(aapt)
+                .addParameterableRepeated("-R", resources, workingDirectory)
+                .build())
+        .containsAllOf("-R", "@" + workingDirectory.resolve("params-R"));
+    assertThat(
+            Files.readAllLines(workingDirectory.resolve("params-R"), StandardCharsets.UTF_8)
+                .stream()
+                .map(Splitter.on(' ')::split)
+                .flatMap(Streams::stream)
+                .collect(toList()))
+        .containsAllIn(resources);
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java b/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java
index 6e9c9a4..ad645a6 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java
@@ -23,9 +23,11 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 
 /**
@@ -110,6 +112,29 @@
     return this;
   }
 
+  /**
+   * Adds a flag to the builder multiple times, once for each value in the given collection. {@code
+   * null} values will be skipped. If the collection is empty, nothing will be added. The values
+   * will be added in the source collection's iteration order. See {@link
+   * AaptCommandBuilder#addRepeated(String, Collection)} for more information. If the collection
+   * exceed 200 items, the values will be written to a file and passed as &lt;flag&gt @&lt;file&gt;.
+   */
+  public AaptCommandBuilder addParameterableRepeated(
+      final String flag, Collection<String> values, Path workingDirectory) throws IOException {
+    Preconditions.checkNotNull(flag);
+    Preconditions.checkNotNull(workingDirectory);
+    if (values.size() > 200) {
+      add(
+          flag,
+          "@" + Files.write(
+              Files.createDirectories(workingDirectory).resolve("params" + flag),
+              ImmutableList.of(values.stream().collect(Collectors.joining(" ")))));
+    } else {
+      addRepeated(flag, values);
+    }
+    return this;
+  }
+
   /** Adds the next flag to the builder only if the condition is true. */
   public ConditionalAaptCommandBuilder when(boolean condition) {
     if (condition) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java
index dc6ab34..8efbc5c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java
@@ -63,7 +63,6 @@
     return new ResourceLinker(aapt2, workingDirectory);
   }
 
-
   /** Dependent static libraries to be linked to. */
   public ResourceLinker dependencies(List<StaticLibrary> libraries) {
     this.linkAgainst = libraries;
@@ -158,7 +157,8 @@
               .addRepeated("-A", compiled.getAssetsStrings())
               .addRepeated("-I", StaticLibrary.toPathStrings(linkAgainst))
               .addRepeated("-R", StaticLibrary.toPathStrings(include))
-              .addRepeated("-R", unzipCompiledResources(compiled.getZip()))
+              .addParameterableRepeated(
+                  "-R", unzipCompiledResources(compiled.getZip()), workingDirectory)
               // Never compress apks.
               .add("-0", "apk")
               // Add custom no-compress extensions.