Invoke aapt2 to generate resource table in tests
This will call aapt2 to generate the resource table based on values of a res folder
Currently the builder supports adding strings and drawables.
Bug: 287398085
Change-Id: I40ca6f28034ceecff7740ea325d071c5eb03eb49
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 258da3a..ccf07e8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -210,6 +210,8 @@
public static final Path DESUGARED_JDK_11_LIB_JAR =
Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar");
+ public static final Path AAPT2 = Paths.get(THIRD_PARTY_DIR, "aapt2", "aapt2");
+
public static Path getDesugarLibConversions(CustomConversionVersion legacy) {
return legacy == CustomConversionVersion.LEGACY
? Paths.get(LIBS_DIR, "library_desugar_conversions_legacy.jar")
@@ -1679,6 +1681,13 @@
return runProcess(builder);
}
+ public static ProcessResult runAapt2(String... args) throws IOException {
+ ArrayList<String> cmd = Lists.newArrayList(AAPT2.toString());
+ cmd.addAll(Lists.newArrayList(args));
+ ProcessBuilder builder = new ProcessBuilder(cmd);
+ return runProcess(builder);
+ }
+
public static ProcessResult forkD8(Path dir, String... args)
throws IOException, InterruptedException {
return forkJava(dir, D8.class, args);
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index b58587b..bf9ee64 100644
--- a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -3,47 +3,158 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.androidresources;
-import java.nio.charset.StandardCharsets;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.MoreCollectors;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import org.junit.rules.TemporaryFolder;
public class AndroidResourceTestingUtils {
- // Taken from default empty android studio activity template
- public static String TEST_MANIFEST =
+ public static String SIMPLE_MANIFEST_WITH_STRING =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:dataExtractionRules=\"@xml/data_extraction_rules\"\n"
- + " android:fullBackupContent=\"@xml/backup_rules\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:roundIcon=\"@mipmap/ic_launcher_round\"\n"
- + " android:supportsRtl=\"true\"\n"
- + " android:theme=\"@style/Theme.MyApplication\"\n"
- + " tools:targetApi=\"31\">\n"
- + " <activity\n"
- + " android:name=\".MainActivity\"\n"
- + " android:exported=\"true\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/Theme.MyApplication.NoActionBar\">\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\" />\n"
- + "\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
- + " </intent-filter>\n"
- + "\n"
- + " <meta-data\n"
- + " android:name=\"android.app.lib_name\"\n"
- + " android:value=\"\" />\n"
- + " </activity>\n"
+ + " package=\"com.android.tools.r8\">\n"
+ + " <application android:label=\"@string/app_name\">\n"
+ " </application>\n"
- + "\n"
- + "</manifest>";
+ + "</manifest>\n"
+ + "\n";
- // TODO(287399385): Add testing utils for generating/consuming resource tables.
- public static byte[] TEST_RESOURCE_TABLE = "RESOURCE_TABLE".getBytes(StandardCharsets.UTF_8);
+ public static class AndroidTestRClass {
+ private final Path javaFilePath;
+ private final Path rootDirectory;
+
+ AndroidTestRClass(Path rootDirectory) throws IOException {
+ this.rootDirectory = rootDirectory;
+ this.javaFilePath =
+ Files.walk(rootDirectory)
+ .filter(path -> path.endsWith("R.java"))
+ .collect(MoreCollectors.onlyElement());
+ }
+
+ public Path getJavaFilePath() {
+ return javaFilePath;
+ }
+
+ public Path getRootDirectory() {
+ return rootDirectory;
+ }
+ }
+
+ public static class AndroidTestResource {
+ private final AndroidTestRClass rClass;
+ private final Path resourceZip;
+
+ AndroidTestResource(AndroidTestRClass rClass, Path resourceZip) {
+ this.rClass = rClass;
+ this.resourceZip = resourceZip;
+ }
+
+ public AndroidTestRClass getRClass() {
+ return rClass;
+ }
+
+ public Path getResourceZip() {
+ return resourceZip;
+ }
+ }
+
+ public static class AndroidTestResourceBuilder {
+ private String manifest;
+ private Map<String, String> stringValues = new TreeMap<>();
+ private Map<String, byte[]> drawables = new TreeMap<>();
+
+ AndroidTestResourceBuilder withManifest(String manifest) {
+ this.manifest = manifest;
+ return this;
+ }
+
+ AndroidTestResourceBuilder withSimpleManifest() {
+ this.manifest = SIMPLE_MANIFEST_WITH_STRING;
+ return this;
+ }
+
+ AndroidTestResourceBuilder addStringValue(String name, String value) {
+ stringValues.put(name, value);
+ return this;
+ }
+
+ AndroidTestResourceBuilder addDrawable(String name, byte[] value) {
+ drawables.put(name, value);
+ return this;
+ }
+
+ AndroidTestResource build(TemporaryFolder temp) throws IOException {
+ Path manifestPath =
+ FileUtils.writeTextFile(temp.newFile("AndroidManifest.xml").toPath(), this.manifest);
+ Path resFolder = temp.newFolder("res").toPath();
+ if (stringValues.size() > 0) {
+ FileUtils.writeTextFile(
+ temp.newFolder("res", "values").toPath().resolve("strings.xml"),
+ createStringResourceXml());
+ }
+ if (drawables.size() > 0) {
+ for (Entry<String, byte[]> entry : drawables.entrySet()) {
+ FileUtils.writeToFile(
+ temp.newFolder("res", "drawable").toPath().resolve(entry.getKey()),
+ null,
+ entry.getValue());
+ }
+ }
+
+ Path output = temp.newFile("resources.zip").toPath();
+ Path rClassOutput = temp.newFolder("aapt_R_class").toPath();
+ compileWithAapt2(resFolder, manifestPath, rClassOutput, output, temp);
+ return new AndroidTestResource(new AndroidTestRClass(rClassOutput), output);
+ }
+
+ private String createStringResourceXml() {
+ StringBuilder stringBuilder = new StringBuilder("<resources>\n");
+ stringValues.forEach(
+ (name, value) ->
+ stringBuilder.append("<string name=\"" + name + "\">" + value + "</string>\n"));
+ stringBuilder.append("</resources>");
+ return stringBuilder.toString();
+ }
+ }
+
+ public static void compileWithAapt2(
+ Path resFolder, Path manifest, Path rClassFolder, Path resourceZip, TemporaryFolder temp)
+ throws IOException {
+ Path compileOutput = temp.newFile("compiled.zip").toPath();
+ ProcessResult compileProcessResult =
+ ToolHelper.runAapt2(
+ "compile", "-o", compileOutput.toString(), "--dir", resFolder.toString());
+ failOnError(compileProcessResult);
+
+ ProcessResult linkProcesResult =
+ ToolHelper.runAapt2(
+ "link",
+ "-I",
+ ToolHelper.getAndroidJar(AndroidApiLevel.S).toString(),
+ "-o",
+ resourceZip.toString(),
+ "--java",
+ rClassFolder.toString(),
+ "--manifest",
+ manifest.toString(),
+ "--proto-format",
+ compileOutput.toString());
+ failOnError(linkProcesResult);
+ }
+
+ private static void failOnError(ProcessResult processResult) {
+ if (processResult.exitCode != 0) {
+ throw new RuntimeException("Failed aapt2: \n" + processResult);
+ }
+ }
// The below byte arrays are lifted from the resource shrinkers DummyContent
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java
index f0bb82a..31359f3 100644
--- a/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java
@@ -3,19 +3,22 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.androidresources;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThat;
import com.android.tools.r8.ArchiveProtoAndroidResourceConsumer;
import com.android.tools.r8.ArchiveProtoAndroidResourceProvider;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ZipUtils;
-import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
-import java.nio.charset.StandardCharsets;
+import java.nio.charset.Charset;
import java.nio.file.Path;
-import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -38,14 +41,14 @@
String manifestPath = "AndroidManifest.xml";
String resourcePath = "resources.pb";
String pngPath = "res/drawable/foo.png";
- String xmlPath = "res/xml/bar.xml";
- Path resources =
- ZipBuilder.builder(temp.newFile("resources.zip").toPath())
- .addText(manifestPath, AndroidResourceTestingUtils.TEST_MANIFEST)
- .addBytes(resourcePath, AndroidResourceTestingUtils.TEST_RESOURCE_TABLE)
- .addBytes(pngPath, AndroidResourceTestingUtils.TINY_PNG)
- .addBytes(xmlPath, AndroidResourceTestingUtils.TINY_PROTO_XML)
- .build();
+
+ AndroidTestResource testResource =
+ new AndroidTestResourceBuilder()
+ .withSimpleManifest()
+ .addStringValue("app_name", "The App")
+ .addDrawable("foo.png", AndroidResourceTestingUtils.TINY_PNG)
+ .build(temp);
+ Path resources = testResource.getResourceZip();
Path output = temp.newFile("resources_out.zip").toPath();
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
@@ -59,20 +62,19 @@
.addKeepMainRule(FooBar.class)
.run(parameters.getRuntime(), FooBar.class)
.assertSuccessWithOutputLines("Hello World");
- assertTrue(
- Arrays.equals(
- ZipUtils.readSingleEntry(output, manifestPath),
- AndroidResourceTestingUtils.TEST_MANIFEST.getBytes(StandardCharsets.UTF_8)));
- assertTrue(
- Arrays.equals(
- ZipUtils.readSingleEntry(output, resourcePath),
- AndroidResourceTestingUtils.TEST_RESOURCE_TABLE));
- assertTrue(
- Arrays.equals(
- ZipUtils.readSingleEntry(output, pngPath), AndroidResourceTestingUtils.TINY_PNG));
- assertTrue(
- Arrays.equals(
- ZipUtils.readSingleEntry(output, xmlPath), AndroidResourceTestingUtils.TINY_PROTO_XML));
+ assertArrayEquals(
+ ZipUtils.readSingleEntry(output, manifestPath),
+ ZipUtils.readSingleEntry(resources, manifestPath));
+ assertArrayEquals(
+ ZipUtils.readSingleEntry(output, resourcePath),
+ ZipUtils.readSingleEntry(resources, resourcePath));
+ assertArrayEquals(
+ ZipUtils.readSingleEntry(output, pngPath), ZipUtils.readSingleEntry(resources, pngPath));
+ String rClassContent =
+ FileUtils.readTextFile(
+ testResource.getRClass().getJavaFilePath(), Charset.defaultCharset());
+ assertThat(rClassContent, containsString("app_name"));
+ assertThat(rClassContent, containsString("foo"));
}
public static class FooBar {