| // Copyright 2018 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.android; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import static org.hamcrest.CoreMatchers.containsString; | |
| import static org.junit.Assert.assertNotNull; | |
| import com.android.builder.core.VariantType; | |
| import com.google.common.base.Joiner; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.devtools.build.android.AarGeneratorAction.AarGeneratorOptions; | |
| import com.google.devtools.build.zip.ZipReader; | |
| import com.google.devtools.common.options.OptionsParser; | |
| import com.google.devtools.common.options.OptionsParsingException; | |
| import java.io.BufferedReader; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.FileOutputStream; | |
| 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.HashMap; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.stream.Collectors; | |
| import java.util.zip.ZipEntry; | |
| import java.util.zip.ZipInputStream; | |
| import java.util.zip.ZipOutputStream; | |
| import org.junit.Before; | |
| import org.junit.Rule; | |
| import org.junit.Test; | |
| import org.junit.rules.ExpectedException; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for {@link AarGeneratorAction}. */ | |
| @RunWith(JUnit4.class) | |
| public class AarGeneratorActionTest { | |
| private static class AarData { | |
| /** Templates for resource files generation. */ | |
| enum ResourceType { | |
| VALUE { | |
| @Override public String create(String... lines) { | |
| return String.format( | |
| "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>%s</resources>", | |
| Joiner.on("\n").join(lines)); | |
| } | |
| }, | |
| LAYOUT { | |
| @Override public String create(String... lines) { | |
| return String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?>" | |
| + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"" | |
| + " android:layout_width=\"fill_parent\"" | |
| + " android:layout_height=\"fill_parent\">%s</LinearLayout>", | |
| Joiner.on("\n").join(lines)); | |
| } | |
| }, | |
| UNFORMATTED { | |
| @Override public String create(String... lines) { | |
| return String.format(Joiner.on("\n").join(lines)); | |
| } | |
| }; | |
| public abstract String create(String... lines); | |
| } | |
| private static class Builder { | |
| private final Path root; | |
| private final Path assetDir; | |
| private final Path resourceDir; | |
| private Path manifest; | |
| private Path rtxt; | |
| private Path classes; | |
| private Map<Path, String> filesToWrite = new HashMap<>(); | |
| private Map<String, String> classesToWrite = new HashMap<>(); | |
| private ImmutableList.Builder<Path> proguardSpecs = ImmutableList.builder(); | |
| private boolean withEmptyRes = false; | |
| private boolean withEmptyAssets = false; | |
| public Builder(Path root) { | |
| this(root, "res", "assets"); | |
| } | |
| public Builder(Path root, String resourceRoot, String assetRoot) { | |
| this.root = root; | |
| assetDir = root.resolve(assetRoot); | |
| resourceDir = root.resolve(resourceRoot); | |
| manifest = root.resolve("fake-manifest-path"); | |
| rtxt = root.resolve("fake-rtxt-path"); | |
| classes = root.resolve("fake-classes-path"); | |
| } | |
| public Builder addResource(String path, ResourceType template, String... lines) { | |
| filesToWrite.put(resourceDir.resolve(path), template.create(lines)); | |
| return this; | |
| } | |
| public Builder withEmptyResources(boolean isEmpty) { | |
| this.withEmptyRes = isEmpty; | |
| return this; | |
| } | |
| public Builder addAsset(String path, String... lines) { | |
| filesToWrite.put(assetDir.resolve(path), Joiner.on("\n").join(lines)); | |
| return this; | |
| } | |
| public Builder withEmptyAssets(boolean isEmpty) { | |
| this.withEmptyAssets = isEmpty; | |
| return this; | |
| } | |
| public Builder createManifest(String path, String manifestPackage, String... lines) { | |
| this.manifest = root.resolve(path); | |
| filesToWrite.put(manifest, String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?>" | |
| + "<manifest xmlns:android='http://schemas.android.com/apk/res/android' package='%s'>" | |
| + "%s</manifest>", manifestPackage, Joiner.on("\n").join(lines))); | |
| return this; | |
| } | |
| public Builder createRtxt(String path, String... lines) { | |
| this.rtxt = root.resolve(path); | |
| filesToWrite.put(rtxt, String.format("%s", Joiner.on("\n").join(lines))); | |
| return this; | |
| } | |
| public Builder createClassesJar(String path) { | |
| this.classes = root.resolve(path); | |
| classesToWrite.put("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"); | |
| return this; | |
| } | |
| public Builder addClassesFile(String filePackage, String filename, String... lines) { | |
| classesToWrite.put(filePackage.replace(".", "/") + "/" + filename, | |
| String.format("%s", Joiner.on("\n").join(lines))); | |
| return this; | |
| } | |
| public Builder addProguardSpec(String path, String... lines) { | |
| Path proguardSpecPath = root.resolve(path); | |
| proguardSpecs.add(proguardSpecPath); | |
| filesToWrite.put(proguardSpecPath, String.format("%s", Joiner.on("\n").join(lines))); | |
| return this; | |
| } | |
| public AarData build() throws IOException { | |
| writeFiles(); | |
| return new AarData(buildMerged(), manifest, rtxt, classes, proguardSpecs.build()); | |
| } | |
| private MergedAndroidData buildMerged() { | |
| return new MergedAndroidData( | |
| resourceDir, | |
| assetDir, | |
| manifest); | |
| } | |
| private void writeFiles() throws IOException { | |
| assertNotNull("A manifest is required.", manifest); | |
| assertNotNull("A resource file is required.", rtxt); | |
| assertNotNull("A classes jar is required.", classes); | |
| if (withEmptyRes) { | |
| Files.createDirectories(resourceDir); | |
| } | |
| if (withEmptyAssets) { | |
| Files.createDirectories(assetDir); | |
| } | |
| for (Map.Entry<Path, String> entry : filesToWrite.entrySet()) { | |
| Path file = entry.getKey(); | |
| // only write files in assets if assets has not been set to empty and same for resources | |
| if (!((file.startsWith(assetDir) && withEmptyAssets) | |
| || (file.startsWith(resourceDir) && withEmptyRes))) { | |
| Files.createDirectories(file.getParent()); | |
| Files.write(file, entry.getValue().getBytes(StandardCharsets.UTF_8)); | |
| assertThat(Files.exists(file)).isTrue(); | |
| } | |
| } | |
| if (!classesToWrite.isEmpty()) { | |
| writeClassesJar(); | |
| } | |
| } | |
| private void writeClassesJar() throws IOException { | |
| final ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(classes.toFile())); | |
| for (Map.Entry<String, String> file : classesToWrite.entrySet()) { | |
| ZipEntry entry = new ZipEntry(file.getKey()); | |
| zout.putNextEntry(entry); | |
| zout.write(file.getValue().getBytes(UTF_8)); | |
| zout.closeEntry(); | |
| } | |
| zout.close(); | |
| classes.toFile().setLastModified(AarGeneratorAction.DEFAULT_TIMESTAMP); | |
| } | |
| } | |
| final MergedAndroidData data; | |
| final Path manifest; | |
| final Path rtxt; | |
| final Path classes; | |
| final ImmutableList<Path> proguardSpecs; | |
| private AarData( | |
| MergedAndroidData data, | |
| Path manifest, | |
| Path rtxt, | |
| Path classes, | |
| ImmutableList<Path> proguardSpecs) { | |
| this.data = data; | |
| this.manifest = manifest; | |
| this.rtxt = rtxt; | |
| this.classes = classes; | |
| this.proguardSpecs = proguardSpecs; | |
| } | |
| } | |
| /** | |
| * Operation to perform on a file. | |
| */ | |
| private interface FileOperation { | |
| /** | |
| * Performs the operation on a file, given its name, modificationTime and contents. | |
| */ | |
| void perform(String name, long modificationTime, String contents); | |
| } | |
| /** | |
| * Runs a {@link FileOperation} on every entry in a zip file. | |
| * | |
| * @param zip {@link Path} of the zip file to traverse. | |
| * @param operation {@link FileOperation} to be run on every entry of the zip file. | |
| * @throws IOException if there is an error reading the zip file. | |
| */ | |
| private void traverseZipFile(Path zip, FileOperation operation) throws IOException { | |
| ZipInputStream zis = new ZipInputStream(Files.newInputStream(zip)); | |
| ZipEntry z = zis.getNextEntry(); | |
| while (z != null) { | |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
| byte[] buffer = new byte[1024]; | |
| for (int count = 0; count != -1; count = zis.read(buffer)) { | |
| baos.write(buffer); | |
| } | |
| // Replace Windows path separators so that test cases are consistent across platforms. | |
| String name = z.getName().replace('\\', '/'); | |
| operation.perform( | |
| name, z.getTime(), new String(baos.toByteArray(), StandardCharsets.UTF_8)); | |
| z = zis.getNextEntry(); | |
| } | |
| } | |
| private Set<String> getZipEntries(Path zip) throws IOException { | |
| final Set<String> zipEntries = new HashSet<>(); | |
| traverseZipFile(zip, new FileOperation() { | |
| @Override public void perform(String name, long modificationTime, String contents) { | |
| zipEntries.add(name); | |
| } | |
| }); | |
| return zipEntries; | |
| } | |
| private Set<Long> getZipEntryTimestamps(Path zip) throws IOException { | |
| final Set<Long> timestamps = new HashSet<>(); | |
| traverseZipFile(zip, new FileOperation() { | |
| @Override public void perform(String name, long modificationTime, String contents) { | |
| timestamps.add(modificationTime); | |
| } | |
| }); | |
| return timestamps; | |
| } | |
| private Path tempDir; | |
| @Rule public ExpectedException thrown = ExpectedException.none(); | |
| @Before public void setUp() throws IOException { | |
| tempDir = Files.createTempDirectory(toString()); | |
| tempDir.toFile().deleteOnExit(); | |
| } | |
| @Test public void testCheckFlags() throws IOException, OptionsParsingException { | |
| Path manifest = tempDir.resolve("AndroidManifest.xml"); | |
| Files.createFile(manifest); | |
| Path rtxt = tempDir.resolve("R.txt"); | |
| Files.createFile(rtxt); | |
| Path classes = tempDir.resolve("classes.jar"); | |
| Files.createFile(classes); | |
| String[] args = new String[] {"--manifest", manifest.toString(), "--rtxt", rtxt.toString(), | |
| "--classes", classes.toString()}; | |
| OptionsParser optionsParser = | |
| OptionsParser.builder().optionsClasses(AarGeneratorOptions.class).build(); | |
| optionsParser.parse(args); | |
| AarGeneratorOptions options = optionsParser.getOptions(AarGeneratorOptions.class); | |
| AarGeneratorAction.checkFlags(options); | |
| } | |
| @Test public void testCheckFlags_MissingClasses() throws IOException, OptionsParsingException { | |
| Path manifest = tempDir.resolve("AndroidManifest.xml"); | |
| Files.createFile(manifest); | |
| Path rtxt = tempDir.resolve("R.txt"); | |
| Files.createFile(rtxt); | |
| String[] args = new String[] {"--manifest", manifest.toString(), "--rtxt", rtxt.toString()}; | |
| OptionsParser optionsParser = | |
| OptionsParser.builder().optionsClasses(AarGeneratorOptions.class).build(); | |
| optionsParser.parse(args); | |
| AarGeneratorOptions options = optionsParser.getOptions(AarGeneratorOptions.class); | |
| thrown.expect(IllegalArgumentException.class); | |
| thrown.expectMessage("classes must be specified. Building an .aar without" | |
| + " classes is unsupported."); | |
| AarGeneratorAction.checkFlags(options); | |
| } | |
| @Test public void testCheckFlags_MissingMultiple() throws IOException, OptionsParsingException { | |
| Path manifest = tempDir.resolve("AndroidManifest.xml"); | |
| Files.createFile(manifest); | |
| String[] args = new String[] {"--manifest", manifest.toString()}; | |
| OptionsParser optionsParser = | |
| OptionsParser.builder().optionsClasses(AarGeneratorOptions.class).build(); | |
| optionsParser.parse(args); | |
| AarGeneratorOptions options = optionsParser.getOptions(AarGeneratorOptions.class); | |
| thrown.expect(IllegalArgumentException.class); | |
| thrown.expectMessage("rtxt, classes must be specified. Building an .aar without" | |
| + " rtxt, classes is unsupported."); | |
| AarGeneratorAction.checkFlags(options); | |
| } | |
| @Test public void testWriteAar() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .addAsset("some/other/ft/data.txt", "bar") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| } | |
| @Test public void testWriteAar_DefaultTimestamps() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .addAsset("some/other/ft/data.txt", "bar") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| assertThat(getZipEntryTimestamps(aar)).containsExactly(AarGeneratorAction.DEFAULT_TIMESTAMP); | |
| assertThat(aar.toFile().lastModified()).isEqualTo(AarGeneratorAction.DEFAULT_TIMESTAMP); | |
| } | |
| @Test public void testAssetResourceSubdirs() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data"), "xyz", "assets") | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .addAsset("some/other/ft/data.txt", "bar") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| // verify aar archive | |
| Set<String> zipEntries = getZipEntries(aar); | |
| assertThat(zipEntries).contains("res/"); | |
| assertThat(zipEntries).contains("assets/"); | |
| } | |
| @Test public void testMissingManifest() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .addAsset("some/other/ft/data.txt", "bar") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| thrown.expect(IOException.class); | |
| thrown.expectMessage(containsString("fake-manifest-path")); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| } | |
| @Test public void testMissingRtxt() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .addAsset("some/other/ft/data.txt", "bar") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| thrown.expect(IOException.class); | |
| thrown.expectMessage(containsString("fake-rtxt-path")); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| } | |
| @Test public void testMissingClasses() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .addAsset("some/other/ft/data.txt", "bar") | |
| .build(); | |
| thrown.expect(IOException.class); | |
| thrown.expectMessage(containsString("fake-classes-path")); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| } | |
| @Test public void testMissingResources() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addAsset("some/other/ft/data.txt", "bar") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| thrown.expect(IOException.class); | |
| thrown.expectMessage(containsString("res")); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| } | |
| @Test public void testEmptyResources() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .withEmptyResources(true) | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .addAsset("some/other/ft/data.txt", "bar") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| } | |
| @Test public void testMissingAssets() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| } | |
| @Test public void testEmptyAssets() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id_name\" type=\"id\"/>") | |
| .withEmptyAssets(true) | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| AarGeneratorAction.writeAar(aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| } | |
| @Test public void testFullIntegration() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo", "") | |
| .createRtxt("R.txt", | |
| "int string app_name 0x7f050001", | |
| "int string hello_world 0x7f050002") | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id\" type=\"id\"/>") | |
| .addResource("layout/layout.xml", | |
| AarData.ResourceType.LAYOUT, | |
| "<TextView android:id=\"@+id/text2\"" | |
| + " android:layout_width=\"wrap_content\"" | |
| + " android:layout_height=\"wrap_content\"" | |
| + " android:text=\"Hello, I am a TextView\" />") | |
| .addAsset("some/other/ft/data.txt", | |
| "foo") | |
| .createClassesJar("classes.jar") | |
| .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents") | |
| .build(); | |
| MergedAndroidData md1 = new AarData.Builder(tempDir.resolve("d1")) | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id\" type=\"id\"/>") | |
| .addResource("layout/foo.xml", | |
| AarData.ResourceType.LAYOUT, | |
| "<TextView android:id=\"@+id/text\"" | |
| + " android:layout_width=\"wrap_content\"" | |
| + " android:layout_height=\"wrap_content\"" | |
| + " android:text=\"Hello, I am a TextView\" />") | |
| .addAsset("some/other/ft/data1.txt", | |
| "bar") | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .build().data; | |
| MergedAndroidData md2 = new AarData.Builder(tempDir.resolve("d2")) | |
| .addResource("values/ids.xml", | |
| AarData.ResourceType.VALUE, | |
| "<item name=\"id2\" type=\"id\"/>") | |
| .addResource("layout/bar.xml", | |
| AarData.ResourceType.LAYOUT, | |
| "<TextView android:id=\"@+id/textbar\"" | |
| + " android:layout_width=\"wrap_content\"" | |
| + " android:layout_height=\"wrap_content\"" | |
| + " android:text=\"Hello, I am a TextView\" />") | |
| .addResource("drawable-mdpi/icon.png", | |
| AarData.ResourceType.UNFORMATTED, | |
| "Thttpt.") | |
| .addResource("drawable-xxxhdpi/icon.png", | |
| AarData.ResourceType.UNFORMATTED, | |
| "Double Thttpt.") | |
| .addAsset("some/other/ft/data2.txt", | |
| "foo") | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d2", "") | |
| .build().data; | |
| UnvalidatedAndroidData primary = new UnvalidatedAndroidData( | |
| ImmutableList.of(aarData.data.getResourceDir()), | |
| ImmutableList.of(aarData.data.getAssetDir()), | |
| aarData.data.getManifest()); | |
| DependencyAndroidData d1 = | |
| new DependencyAndroidData( | |
| ImmutableList.of(md1.getResourceDir()), | |
| ImmutableList.of(md1.getAssetDir()), | |
| md1.getManifest(), | |
| null, | |
| null, | |
| null); | |
| DependencyAndroidData d2 = | |
| new DependencyAndroidData( | |
| ImmutableList.of(md2.getResourceDir()), | |
| ImmutableList.of(md2.getAssetDir()), | |
| md2.getManifest(), | |
| null, | |
| null, | |
| null); | |
| Path working = tempDir; | |
| Path resourcesOut = working.resolve("resources"); | |
| Path assetsOut = working.resolve("assets"); | |
| MergedAndroidData mergedData = | |
| AndroidResourceMerger.mergeDataAndWrite( | |
| primary, | |
| ImmutableList.of(d1, d2), | |
| ImmutableList.<DependencyAndroidData>of(), | |
| resourcesOut, | |
| assetsOut, | |
| null, | |
| VariantType.LIBRARY, | |
| null, | |
| /* filteredResources= */ ImmutableList.of(), | |
| true); | |
| AarGeneratorAction.writeAar( | |
| aar, mergedData, aarData.manifest, aarData.rtxt, aarData.classes, aarData.proguardSpecs); | |
| // verify aar archive | |
| Set<String> zipEntries = getZipEntries(aar); | |
| assertThat(zipEntries).containsExactly( | |
| "AndroidManifest.xml", | |
| "R.txt", | |
| "classes.jar", | |
| "res/", | |
| "res/values/", | |
| "res/values/values.xml", | |
| "res/layout/", | |
| "res/layout/layout.xml", | |
| "res/layout/foo.xml", | |
| "res/layout/bar.xml", | |
| "res/drawable-mdpi-v4/", | |
| "res/drawable-mdpi-v4/icon.png", | |
| "res/drawable-xxxhdpi-v4/", | |
| "res/drawable-xxxhdpi-v4/icon.png", | |
| "assets/", | |
| "assets/some/", | |
| "assets/some/other/", | |
| "assets/some/other/ft/", | |
| "assets/some/other/ft/data.txt", | |
| "assets/some/other/ft/data1.txt", | |
| "assets/some/other/ft/data2.txt"); | |
| } | |
| @Test public void testProguardSpecs() throws Exception { | |
| Path aar = tempDir.resolve("foo.aar"); | |
| AarData aarData = | |
| new AarData.Builder(tempDir.resolve("data")) | |
| .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "") | |
| .createRtxt("R.txt", "") | |
| .withEmptyResources(true) | |
| .withEmptyAssets(true) | |
| .createClassesJar("classes.jar") | |
| .addProguardSpec("spec1", "foo", "bar") | |
| .addProguardSpec("spec2", "baz") | |
| .build(); | |
| AarGeneratorAction.writeAar( | |
| aar, | |
| aarData.data, | |
| aarData.manifest, | |
| aarData.rtxt, | |
| aarData.classes, | |
| aarData.proguardSpecs); | |
| Set<String> zipEntries = getZipEntries(aar); | |
| assertThat(zipEntries).contains("proguard.txt"); | |
| List<String> proguardTxtContents = null; | |
| try (ZipReader aarReader = new ZipReader(aar.toFile())) { | |
| try (BufferedReader entryReader = | |
| new BufferedReader( | |
| new InputStreamReader( | |
| aarReader.getInputStream(aarReader.getEntry("proguard.txt")), | |
| StandardCharsets.UTF_8))) { | |
| proguardTxtContents = entryReader.lines().collect(Collectors.toList()); | |
| } | |
| } | |
| assertThat(proguardTxtContents).containsExactly("foo", "bar", "baz").inOrder(); | |
| } | |
| } |