| // 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. | |
| package com.google.devtools.build.android; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import com.google.common.base.Joiner; | |
| import com.google.common.base.Splitter; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.io.PatternFilenameFilter; | |
| import java.io.BufferedReader; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.nio.charset.Charset; | |
| import java.nio.file.FileSystem; | |
| import java.nio.file.FileSystems; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import javax.annotation.Nullable; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for {@link ManifestMergerAction}. */ | |
| @RunWith(JUnit4.class) | |
| public class ManifestMergerActionTest { | |
| private Path working; | |
| /** | |
| * Returns a runfile's path. | |
| * | |
| * <p>The `path` must specify a valid runfile, meaning on Windows (where $RUNFILES_MANIFEST_ONLY | |
| * is 1) the `path` must exactly match a runfiles manifest entry, and on Linux/MacOS `path` must | |
| * point to a valid file in the runfiles directory. | |
| */ | |
| @Nullable | |
| private static Path rlocation(String path) throws IOException { | |
| FileSystem fs = FileSystems.getDefault(); | |
| if (fs.getPath(path).isAbsolute()) { | |
| return fs.getPath(path); | |
| } | |
| if ("1".equals(System.getenv("RUNFILES_MANIFEST_ONLY"))) { | |
| String manifest = System.getenv("RUNFILES_MANIFEST_FILE"); | |
| assertThat(manifest).isNotNull(); | |
| try (BufferedReader r = | |
| Files.newBufferedReader(Paths.get(manifest), Charset.defaultCharset())) { | |
| Splitter splitter = Splitter.on(' ').limit(2); | |
| String line = null; | |
| while ((line = r.readLine()) != null) { | |
| List<String> tokens = splitter.splitToList(line); | |
| if (tokens.size() == 2) { | |
| if (tokens.get(0).equals(path)) { | |
| return fs.getPath(tokens.get(1)); | |
| } | |
| } | |
| } | |
| } | |
| return null; | |
| } else { | |
| String runfiles = System.getenv("RUNFILES_DIR"); | |
| if (runfiles == null) { | |
| runfiles = System.getenv("JAVA_RUNFILES"); | |
| assertThat(runfiles).isNotNull(); | |
| } | |
| Path result = fs.getPath(runfiles).resolve(path); | |
| assertThat(result.toFile().exists()).isTrue(); // comply with function's contract | |
| return result; | |
| } | |
| } | |
| @Before public void setup() throws Exception { | |
| working = Files.createTempDirectory(toString()); | |
| working.toFile().deleteOnExit(); | |
| } | |
| @Test | |
| public void testMerge_GenerateDummyManifest() throws Exception { | |
| Files.createDirectories(working.resolve("output")); | |
| Path mergedManifest = working.resolve("output/mergedManifest.xml"); | |
| ManifestMergerAction.main( | |
| new String[] { | |
| "--customPackage", | |
| "foo.bar.baz", | |
| "--mergeType", | |
| "LIBRARY", | |
| "--manifestOutput", | |
| mergedManifest.toString() | |
| }); | |
| assertThat( | |
| Joiner.on(" ") | |
| .join(Files.readAllLines(mergedManifest, UTF_8)) | |
| .replaceAll("\\s+", " ") | |
| .trim()) | |
| .isEqualTo( | |
| "<?xml version=\"1.0\" encoding=\"utf-8\"?> " | |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" " | |
| + "package=\"foo.bar.baz\" > " | |
| + "<uses-sdk android:minSdkVersion=\"1\" /> " | |
| + "<application /> " | |
| + "</manifest>"); | |
| } | |
| @Test public void testMerge() throws Exception { | |
| String dataDir = | |
| Paths.get(System.getenv("TEST_WORKSPACE"), System.getenv("TEST_BINARY")) | |
| .resolveSibling("testing/manifestmerge") | |
| .toString() | |
| .replace("\\", "/"); | |
| final Path mergerManifest = rlocation(dataDir + "/merger/AndroidManifest.xml"); | |
| final Path mergeeManifestOne = rlocation(dataDir + "/mergeeOne/AndroidManifest.xml"); | |
| final Path mergeeManifestTwo = rlocation(dataDir + "/mergeeTwo/AndroidManifest.xml"); | |
| assertThat(mergerManifest.toFile().exists()).isTrue(); | |
| assertThat(mergeeManifestOne.toFile().exists()).isTrue(); | |
| assertThat(mergeeManifestTwo.toFile().exists()).isTrue(); | |
| // The following code retrieves the path of the only AndroidManifest.xml in the expected/ | |
| // manifests directory. Unfortunately, this test runs internally and externally and the files | |
| // have different names. | |
| final File expectedManifestDirectory = | |
| mergerManifest.getParent().resolveSibling("expected").toFile(); | |
| final String[] debug = | |
| expectedManifestDirectory.list(new PatternFilenameFilter(".*AndroidManifest\\.xml$")); | |
| assertThat(debug).isNotNull(); | |
| final File[] expectedManifestDirectoryManifests = | |
| expectedManifestDirectory.listFiles((File dir, String name) -> true); | |
| assertThat(expectedManifestDirectoryManifests).isNotNull(); | |
| assertThat(expectedManifestDirectoryManifests).hasLength(1); | |
| final Path expectedManifest = expectedManifestDirectoryManifests[0].toPath(); | |
| Files.createDirectories(working.resolve("output")); | |
| final Path mergedManifest = working.resolve("output/mergedManifest.xml"); | |
| List<String> args = | |
| generateArgs( | |
| mergerManifest, | |
| ImmutableMap.of(mergeeManifestOne, "mergeeOne", mergeeManifestTwo, "mergeeTwo"), | |
| false, /* isLibrary */ | |
| ImmutableMap.of("applicationId", "com.google.android.apps.testapp"), | |
| "", /* custom_package */ | |
| mergedManifest); | |
| ManifestMergerAction.main(args.toArray(new String[0])); | |
| assertThat( | |
| Joiner.on(" ") | |
| .join(Files.readAllLines(mergedManifest, UTF_8)) | |
| .replaceAll("\\s+", " ") | |
| .trim()) | |
| .isEqualTo( | |
| Joiner.on(" ") | |
| .join(Files.readAllLines(expectedManifest, UTF_8)) | |
| .replaceAll("\\s+", " ") | |
| .trim()); | |
| } | |
| @Test public void fullIntegration() throws Exception { | |
| Files.createDirectories(working.resolve("output")); | |
| final Path binaryOutput = working.resolve("output/binaryManifest.xml"); | |
| final Path libFooOutput = working.resolve("output/libFooManifest.xml"); | |
| final Path libBarOutput = working.resolve("output/libBarManifest.xml"); | |
| final Path binaryManifest = AndroidDataBuilder.of(working.resolve("binary")) | |
| .createManifest("AndroidManifest.xml", "com.google.app", "") | |
| .buildUnvalidated() | |
| .getManifest(); | |
| final Path libFooManifest = AndroidDataBuilder.of(working.resolve("libFoo")) | |
| .createManifest("AndroidManifest.xml", "com.google.foo", | |
| " <application android:name=\"${applicationId}\" />") | |
| .buildUnvalidated() | |
| .getManifest(); | |
| final Path libBarManifest = AndroidDataBuilder.of(working.resolve("libBar")) | |
| .createManifest("AndroidManifest.xml", "com.google.bar", | |
| "<application android:name=\"${applicationId}\">", | |
| "<activity android:name=\".activityFoo\" />", | |
| "</application>") | |
| .buildUnvalidated() | |
| .getManifest(); | |
| // libFoo manifest merging | |
| List<String> args = generateArgs(libFooManifest, ImmutableMap.<Path, String>of(), true, | |
| ImmutableMap.<String, String>of(), "", libFooOutput); | |
| ManifestMergerAction.main(args.toArray(new String[0])); | |
| assertThat(Joiner.on(" ") | |
| .join(Files.readAllLines(libFooOutput, UTF_8)) | |
| .replaceAll("\\s+", " ").trim()).contains( | |
| "<?xml version=\"1.0\" encoding=\"utf-8\"?>" | |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"" | |
| + " package=\"com.google.foo\">" | |
| + " <application android:name=\"${applicationId}\" />" | |
| + "</manifest>"); | |
| // libBar manifest merging | |
| args = generateArgs(libBarManifest, ImmutableMap.<Path, String>of(), true, | |
| ImmutableMap.<String, String>of(), "com.google.libbar", libBarOutput); | |
| ManifestMergerAction.main(args.toArray(new String[0])); | |
| assertThat(Joiner.on(" ") | |
| .join(Files.readAllLines(libBarOutput, UTF_8)) | |
| .replaceAll("\\s+", " ").trim()).contains( | |
| "<?xml version=\"1.0\" encoding=\"utf-8\"?>" | |
| + " <manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"" | |
| + " package=\"com.google.libbar\" >" | |
| + " <application android:name=\"${applicationId}\" >" | |
| + " <activity android:name=\"com.google.bar.activityFoo\" />" | |
| + " </application>" | |
| + " </manifest>"); | |
| // binary manifest merging | |
| args = | |
| generateArgs( | |
| binaryManifest, | |
| ImmutableMap.of(libFooOutput, "libFoo", libBarOutput, "libBar"), | |
| /* library= */ false, | |
| ImmutableMap.of( | |
| "applicationId", "com.google.android.app", | |
| "foo", "this \\\\: is \"a, \"bad string"), | |
| /* customPackage= */ "", | |
| binaryOutput); | |
| ManifestMergerAction.main(args.toArray(new String[0])); | |
| assertThat(Joiner.on(" ") | |
| .join(Files.readAllLines(binaryOutput, UTF_8)) | |
| .replaceAll("\\s+", " ").trim()).contains( | |
| "<?xml version=\"1.0\" encoding=\"utf-8\"?>" | |
| + " <manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"" | |
| + " package=\"com.google.android.app\" >" | |
| + " <application android:name=\"com.google.android.app\" >" | |
| + " <activity android:name=\"com.google.bar.activityFoo\" />" | |
| + " </application>" | |
| + " </manifest>"); | |
| } | |
| private List<String> generateArgs( | |
| Path manifest, | |
| Map<Path, String> mergeeManifests, | |
| boolean library, | |
| Map<String, String> manifestValues, | |
| String customPackage, | |
| Path manifestOutput) { | |
| return ImmutableList.of( | |
| "--manifest", manifest.toString(), | |
| "--mergeeManifests", mapToDictionaryString(mergeeManifests), | |
| "--mergeType", library ? "LIBRARY" : "APPLICATION", | |
| "--manifestValues", mapToDictionaryString(manifestValues), | |
| "--customPackage", customPackage, | |
| "--manifestOutput", manifestOutput.toString()); | |
| } | |
| private <K, V> String mapToDictionaryString(Map<K, V> map) { | |
| StringBuilder sb = new StringBuilder(); | |
| Iterator<Map.Entry<K, V>> iter = map.entrySet().iterator(); | |
| while (iter.hasNext()) { | |
| Map.Entry<K, V> entry = iter.next(); | |
| sb.append(entry.getKey().toString().replace(":", "\\:").replace(",", "\\,")); | |
| sb.append(':'); | |
| sb.append(entry.getValue().toString().replace(":", "\\:").replace(",", "\\,")); | |
| if (iter.hasNext()) { | |
| sb.append(','); | |
| } | |
| } | |
| return sb.toString(); | |
| } | |
| } |