| // 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(); |
| } |
| } |