/*
* Copyright 2013-present Facebook, Inc.
*
* 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.facebook.buck.android;
import static com.facebook.buck.android.AndroidNdkHelper.SymbolGetter;
import static com.facebook.buck.android.AndroidNdkHelper.SymbolsAndDtNeeded;
import static com.facebook.buck.testutil.RegexMatcher.containsRegex;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.android.relinker.Symbols;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.jvm.java.testutil.AbiCompilationModeTest;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.DefaultOnDiskBuildInfo;
import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.FilesystemBuildInfoStore;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.testutil.TestConsole;
import com.facebook.buck.testutil.integration.BuckBuildLog;
import com.facebook.buck.testutil.integration.ProjectWorkspace;
import com.facebook.buck.testutil.integration.TemporaryPaths;
import com.facebook.buck.testutil.integration.TestDataHelper;
import com.facebook.buck.testutil.integration.ZipInspector;
import com.facebook.buck.util.DefaultProcessExecutor;
import com.facebook.buck.util.ObjectMappers;
import com.facebook.buck.zip.ZipConstants;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.Hashing;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import org.apache.commons.compress.archivers.zip.ZipUtil;
import org.hamcrest.Matchers;
import org.hamcrest.collection.IsIn;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class AndroidBinaryIntegrationTest extends AbiCompilationModeTest {
@Rule public TemporaryPaths tmpFolder = new TemporaryPaths();
private ProjectWorkspace workspace;
private ProjectFilesystem filesystem;
private static final String SIMPLE_TARGET = "//apps/multidex:app";
private static final String RAW_DEX_TARGET = "//apps/multidex:app-art";
private static final String APP_REDEX_TARGET = "//apps/sample:app_redex";
@Before
public void setUp() throws InterruptedException, IOException {
AssumeAndroidPlatform.assumeSdkIsAvailable();
AssumeAndroidPlatform.assumeNdkIsAvailable();
workspace =
TestDataHelper.createProjectWorkspaceForScenario(
new AndroidBinaryIntegrationTest(), "android_project", tmpFolder);
workspace.setUp();
setWorkspaceCompilationMode(workspace);
filesystem = new ProjectFilesystem(workspace.getDestPath());
}
@Test
public void testNonExopackageHasSecondary() throws IOException {
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(SIMPLE_TARGET), "%s.apk")));
zipInspector.assertFileExists("assets/secondary-program-dex-jars/metadata.txt");
zipInspector.assertFileExists("assets/secondary-program-dex-jars/secondary-1.dex.jar");
zipInspector.assertFileDoesNotExist("classes2.dex");
zipInspector.assertFileExists("classes.dex");
zipInspector.assertFileExists("lib/armeabi/libnative_cxx_lib.so");
}
@Test
public void testAppHasAssets() throws IOException {
Path apkPath = workspace.buildAndReturnOutput(SIMPLE_TARGET);
ZipInspector zipInspector = new ZipInspector(workspace.getPath(apkPath));
zipInspector.assertFileExists("assets/asset_file.txt");
zipInspector.assertFileExists("assets/hilarity.txt");
zipInspector.assertFileContents(
"assets/hilarity.txt",
workspace.getFileContents("res/com/sample/base/buck-assets/hilarity.txt"));
// Test that after changing an asset, the new asset is in the apk.
String newContents = "some new contents";
workspace.writeContentsToPath(newContents, "res/com/sample/base/buck-assets/hilarity.txt");
workspace.buildAndReturnOutput(SIMPLE_TARGET);
zipInspector = new ZipInspector(workspace.getPath(apkPath));
zipInspector.assertFileContents("assets/hilarity.txt", newContents);
}
@Test
public void testAppAssetsAreCompressed() throws IOException {
// Small files don't get compressed. Make something a bit bigger.
String largeContents =
Joiner.on("\n").join(Collections.nCopies(100, "A boring line of content."));
workspace.writeContentsToPath(largeContents, "res/com/sample/base/buck-assets/hilarity.txt");
Path apkPath = workspace.buildAndReturnOutput(SIMPLE_TARGET);
ZipInspector zipInspector = new ZipInspector(workspace.getPath(apkPath));
zipInspector.assertFileExists("assets/asset_file.txt");
zipInspector.assertFileExists("assets/hilarity.txt");
zipInspector.assertFileContents(
"assets/hilarity.txt",
workspace.getFileContents("res/com/sample/base/buck-assets/hilarity.txt"));
zipInspector.assertFileIsCompressed("assets/hilarity.txt");
}
@Test
public void testAppUncompressableAssetsAreNotCompressed() throws IOException {
// Small files don't get compressed. Make something a bit bigger.
String largeContents =
Joiner.on("\n").join(Collections.nCopies(100, "A boring line of content."));
workspace.writeContentsToPath(largeContents, "res/com/sample/base/buck-assets/movie.mp4");
Path apkPath = workspace.buildAndReturnOutput(SIMPLE_TARGET);
ZipInspector zipInspector = new ZipInspector(workspace.getPath(apkPath));
zipInspector.assertFileExists("assets/asset_file.txt");
zipInspector.assertFileExists("assets/movie.mp4");
zipInspector.assertFileContents(
"assets/movie.mp4", workspace.getFileContents("res/com/sample/base/buck-assets/movie.mp4"));
zipInspector.assertFileIsNotCompressed("assets/movie.mp4");
}
@Test
public void testGzAssetsAreRejected() throws IOException {
workspace.writeContentsToPath("some contents", "res/com/sample/base/buck-assets/zipped.gz");
ProjectWorkspace.ProcessResult result = workspace.runBuckBuild(SIMPLE_TARGET);
result.assertFailure();
assertTrue(result.getStderr().contains("zipped.gz"));
}
@Test
public void testRawSplitDexHasSecondary() throws IOException {
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", RAW_DEX_TARGET);
result.assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(RAW_DEX_TARGET), "%s.apk")));
zipInspector.assertFileDoesNotExist("assets/secondary-program-dex-jars/metadata.txt");
zipInspector.assertFileDoesNotExist("assets/secondary-program-dex-jars/secondary-1.dex.jar");
zipInspector.assertFileExists("classes2.dex");
zipInspector.assertFileExists("classes.dex");
zipInspector.assertFileExists("lib/armeabi/libnative_cxx_lib.so");
}
@Test
public void testDisguisedExecutableIsRenamed() throws IOException {
Path output = workspace.buildAndReturnOutput("//apps/sample:app_with_disguised_exe");
ZipInspector zipInspector = new ZipInspector(output);
zipInspector.assertFileExists("lib/armeabi/libmybinary.so");
}
@Test
public void testNdkLibraryIsIncluded() throws IOException {
Path output = workspace.buildAndReturnOutput("//apps/sample:app_with_ndk_library");
ZipInspector zipInspector = new ZipInspector(output);
zipInspector.assertFileExists("lib/armeabi/libfakenative.so");
}
@Test
public void testEditingNdkLibraryForcesRebuild() throws IOException, InterruptedException {
String apkWithNdkLibrary = "//apps/sample:app_with_ndk_library";
Path output = workspace.buildAndReturnOutput(apkWithNdkLibrary);
ZipInspector zipInspector = new ZipInspector(output);
zipInspector.assertFileExists("lib/armeabi/libfakenative.so");
// Sleep 1 second (plus another half to be super duper safe) to make sure that
// fakesystem.c gets a later timestamp than the fakesystem.o that was produced
// during the build in setUp. If we don't do this, there's a chance that the
// ndk-build we run during the upcoming build will not rebuild it (on filesystems
// that have 1-second granularity for last modified).
// To verify this, create a Makefile with the following rule (don't forget to use a tab):
// out: in
// cat $< > $@
// Run: echo foo > in ; make ; cat out ; echo bar > in ; make ; cat out
// On a filesystem with 1-second mtime granularity, the last "cat" should print "foo"
// (with very high probability).
Thread.sleep(1500);
workspace.replaceFileContents(
"native/fakenative/jni/fakesystem.c", "exit(status)", "exit(1+status)");
workspace.resetBuildLogFile();
workspace.buildAndReturnOutput(apkWithNdkLibrary);
workspace.getBuildLog().assertTargetBuiltLocally(apkWithNdkLibrary);
}
@Test
public void testEditingPrimaryDexClassForcesRebuildForSimplePackage() throws IOException {
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
workspace.replaceFileContents(
"java/com/sample/app/MyApplication.java", "package com", "package\ncom");
workspace.resetBuildLogFile();
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", SIMPLE_TARGET);
result.assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally(SIMPLE_TARGET);
}
@Test
public void testEditingSecondaryDexClassForcesRebuildForSimplePackage() throws IOException {
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
workspace.replaceFileContents("java/com/sample/lib/Sample.java", "package com", "package\ncom");
workspace.resetBuildLogFile();
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", SIMPLE_TARGET);
result.assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally(SIMPLE_TARGET);
}
@Test
public void testNotAllJavaLibrariesFetched() throws IOException {
String target = "//apps/multidex:app_with_deeper_deps";
workspace.runBuckCommand("build", target).assertSuccess();
workspace.replaceFileContents(
"java/com/sample/app/MyApplication.java", "package com", "package\ncom");
workspace.resetBuildLogFile();
workspace.runBuckCommand("build", target).assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally(target);
buildLog.assertTargetIsAbsent("//java/com/sample/lib:lib");
}
@Test
public void testPreprocessorForcesReDex() throws IOException {
String target = "//java/com/preprocess:disassemble";
Path outputFile = workspace.buildAndReturnOutput(target);
String output = new String(Files.readAllBytes(outputFile), UTF_8);
assertThat(output, containsString("content=2"));
workspace.replaceFileContents("java/com/preprocess/convert.py", "content=2", "content=3");
outputFile = workspace.buildAndReturnOutput(target);
output = new String(Files.readAllBytes(outputFile), UTF_8);
assertThat(output, containsString("content=3"));
}
@Test
public void testDxFindsReferencedResources() throws InterruptedException, IOException {
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
ProjectFilesystem filesystem = new ProjectFilesystem(tmpFolder.getRoot());
DefaultOnDiskBuildInfo buildInfo =
new DefaultOnDiskBuildInfo(
BuildTargetFactory.newInstance("//java/com/sample/lib:lib#dex"),
filesystem,
new FilesystemBuildInfoStore(filesystem));
Optional<ImmutableList<String>> resourcesFromMetadata =
buildInfo.getValues(DexProducedFromJavaLibrary.REFERENCED_RESOURCES);
assertTrue(resourcesFromMetadata.isPresent());
assertEquals(
ImmutableSet.of("com.sample.top_layout", "com.sample2.title"),
ImmutableSet.copyOf(resourcesFromMetadata.get()));
}
@Test
public void testDexingIsInputBased() throws IOException {
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally("//java/com/sample/lib:lib#dex");
workspace.replaceFileContents(
"java/com/sample/lib/Sample.java", "import", "import /* no output change */");
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
buildLog = workspace.getBuildLog();
buildLog.assertNotTargetBuiltLocally("//java/com/sample/lib:lib#dex");
buildLog.assertTargetHadMatchingInputRuleKey("//java/com/sample/lib:lib#dex");
workspace.replaceFileContents(
"java/com/sample/lib/Sample.java", "import", "import /* \n some output change */");
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally("//java/com/sample/lib:lib#dex");
}
@Test
public void testCxxLibraryDep() throws IOException {
String target = "//apps/sample:app_cxx_lib_dep";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("lib/armeabi/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/armeabi/libgnustl_shared.so");
zipInspector.assertFileExists("lib/armeabi-v7a/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/armeabi-v7a/libgnustl_shared.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/x86/libgnustl_shared.so");
}
@Test
public void testCxxLibraryDepModular() throws IOException {
String target = "//apps/sample:app_cxx_lib_dep_modular";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileDoesNotExist("lib/armeabi/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/armeabi/libgnustl_shared.so");
zipInspector.assertFileDoesNotExist("lib/armeabi-v7a/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/armeabi-v7a/libgnustl_shared.so");
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/x86/libgnustl_shared.so");
zipInspector.assertFileExists("assets/native.cxx.lib/libs.txt");
zipInspector.assertFileExists("assets/native.cxx.lib/libs.xzs");
}
@Test
public void testCxxLibraryDepClang() throws IOException {
String target = "//apps/sample:app_cxx_lib_dep";
ProjectWorkspace.ProcessResult result =
workspace.runBuckCommand(
"build", "-c", "ndk.compiler=clang", "-c", "ndk.cxx_runtime=libcxx", target);
result.assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("lib/armeabi/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/armeabi/libc++_shared.so");
zipInspector.assertFileExists("lib/armeabi-v7a/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/armeabi-v7a/libc++_shared.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/x86/libc++_shared.so");
}
@Test
public void testCxxLibraryDepWithNoFilters() throws IOException {
String target = "//apps/sample:app_cxx_lib_dep_no_filters";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("lib/armeabi/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/armeabi-v7a/libnative_cxx_lib.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_lib.so");
}
@Test
public void testNoCxxDepsDoesNotIncludeNdkRuntime() throws IOException {
String target = "//apps/sample:app_no_cxx_deps";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileDoesNotExist("lib/armeabi/libgnustl_shared.so");
zipInspector.assertFileDoesNotExist("lib/armeabi-v7a/libgnustl_shared.so");
zipInspector.assertFileDoesNotExist("lib/x86/libgnustl_shared.so");
}
@Test
public void testProguardDontObfuscateGeneratesMappingFile() throws IOException {
String target = "//apps/sample:app_proguard_dontobfuscate";
workspace.runBuckCommand("build", target).assertSuccess();
Path mapping =
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s/proguard/mapping.txt"));
assertTrue(Files.exists(mapping));
}
@Test
public void testStaticCxxLibraryDep() throws IOException {
String target = "//apps/sample:app_static_cxx_lib_dep";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo2.so");
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_bar.so");
}
private static Path unzip(Path tmpDir, Path zipPath, String name) throws IOException {
Path outPath = tmpDir.resolve(zipPath.getFileName());
try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
Files.copy(
zipFile.getInputStream(zipFile.getEntry(name)),
outPath,
StandardCopyOption.REPLACE_EXISTING);
return outPath;
}
}
@Test
public void testNativeLibraryMerging() throws IOException, InterruptedException {
NdkCxxPlatform platform = AndroidNdkHelper.getNdkCxxPlatform(workspace, filesystem);
SourcePathResolver pathResolver =
new SourcePathResolver(
new SourcePathRuleFinder(
new BuildRuleResolver(
TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())));
Path tmpDir = tmpFolder.newFolder("merging_tmp");
SymbolGetter syms =
new SymbolGetter(
new DefaultProcessExecutor(new TestConsole()),
tmpDir,
platform.getObjdump(),
pathResolver);
SymbolsAndDtNeeded info;
workspace.replaceFileContents(".buckconfig", "#cpu_abis", "cpu_abis = x86");
ImmutableMap<String, Path> paths =
workspace.buildMultipleAndReturnOutputs(
"//apps/sample:app_with_merged_libs",
"//apps/sample:app_with_alternate_merge_glue",
"//apps/sample:app_with_merged_libs_modular");
Path apkPath = paths.get("//apps/sample:app_with_merged_libs");
ZipInspector zipInspector = new ZipInspector(apkPath);
zipInspector.assertFileDoesNotExist("lib/x86/lib1a.so");
zipInspector.assertFileDoesNotExist("lib/x86/lib1b.so");
zipInspector.assertFileDoesNotExist("lib/x86/lib2e.so");
zipInspector.assertFileDoesNotExist("lib/x86/lib2f.so");
info = syms.getSymbolsAndDtNeeded(apkPath, "lib/x86/lib1.so");
assertThat(info.symbols.global, Matchers.hasItem("A"));
assertThat(info.symbols.global, Matchers.hasItem("B"));
assertThat(info.symbols.global, Matchers.hasItem("glue_1"));
assertThat(info.symbols.global, not(Matchers.hasItem("glue_2")));
assertThat(info.dtNeeded, Matchers.hasItem("libnative_merge_C.so"));
assertThat(info.dtNeeded, Matchers.hasItem("libnative_merge_D.so"));
assertThat(info.dtNeeded, not(Matchers.hasItem("libnative_merge_B.so")));
info = syms.getSymbolsAndDtNeeded(apkPath, "lib/x86/libnative_merge_C.so");
assertThat(info.symbols.global, Matchers.hasItem("C"));
assertThat(info.symbols.global, Matchers.hasItem("static_func_C"));
assertThat(info.symbols.global, not(Matchers.hasItem("glue_1")));
assertThat(info.symbols.global, not(Matchers.hasItem("glue_2")));
assertThat(info.dtNeeded, Matchers.hasItem("libnative_merge_D.so"));
assertThat(info.dtNeeded, Matchers.hasItem("libprebuilt_for_C.so"));
info = syms.getSymbolsAndDtNeeded(apkPath, "lib/x86/libnative_merge_D.so");
assertThat(info.symbols.global, Matchers.hasItem("D"));
assertThat(info.symbols.global, not(Matchers.hasItem("glue_1")));
assertThat(info.symbols.global, not(Matchers.hasItem("glue_2")));
assertThat(info.dtNeeded, Matchers.hasItem("lib2.so"));
assertThat(info.dtNeeded, not(Matchers.hasItem("libnative_merge_E.so")));
assertThat(info.dtNeeded, not(Matchers.hasItem("libnative_merge_F.so")));
info = syms.getSymbolsAndDtNeeded(apkPath, "lib/x86/lib2.so");
assertThat(info.symbols.global, Matchers.hasItem("E"));
assertThat(info.symbols.global, Matchers.hasItem("F"));
assertThat(info.symbols.global, Matchers.hasItem("static_func_F"));
assertThat(info.symbols.global, Matchers.hasItem("glue_1"));
assertThat(info.symbols.global, not(Matchers.hasItem("glue_2")));
assertThat(info.dtNeeded, Matchers.hasItem("libprebuilt_for_F.so"));
Path otherPath = paths.get("//apps/sample:app_with_alternate_merge_glue");
info = syms.getSymbolsAndDtNeeded(otherPath, "lib/x86/lib2.so");
assertThat(info.symbols.global, not(Matchers.hasItem("glue_1")));
assertThat(info.symbols.global, Matchers.hasItem("glue_2"));
assertThat(info.dtNeeded, Matchers.hasItem("libprebuilt_for_F.so"));
Path modularPath = paths.get("//apps/sample:app_with_merged_libs_modular");
ZipInspector modularZipInspector = new ZipInspector(modularPath);
modularZipInspector.assertFileDoesNotExist("lib/x86/lib1a.so");
modularZipInspector.assertFileDoesNotExist("lib/x86/lib1b.so");
modularZipInspector.assertFileDoesNotExist("lib/x86/lib2e.so");
modularZipInspector.assertFileDoesNotExist("lib/x86/lib2f.so");
modularZipInspector.assertFileExists("assets/native.merge.A/libs.txt");
modularZipInspector.assertFileExists("assets/native.merge.A/libs.xzs");
modularZipInspector.assertFileDoesNotExist("lib/x86/lib1.so");
modularZipInspector.assertFileDoesNotExist("lib/x86/lib2.so");
Path disassembly =
workspace.buildAndReturnOutput("//apps/sample:disassemble_app_with_merged_libs_gencode");
List<String> disassembledLines = filesystem.readLines(disassembly);
Pattern fieldPattern =
Pattern.compile("^\\.field public static final ([^:]+):Ljava/lang/String; = \"([^\"]+)\"$");
ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder();
for (String line : disassembledLines) {
Matcher m = fieldPattern.matcher(line);
if (!m.matches()) {
continue;
}
mapBuilder.put(m.group(1), m.group(2));
}
assertThat(
mapBuilder.build(),
Matchers.equalTo(
ImmutableMap.of(
"lib1a_so", "lib1_so",
"lib1b_so", "lib1_so",
"lib2e_so", "lib2_so",
"lib2f_so", "lib2_so")));
}
@Test
public void testNativeLibraryMergeErrors() throws IOException, InterruptedException {
try {
workspace.runBuckBuild("//apps/sample:app_with_merge_lib_into_two_targets");
Assert.fail("No exception from trying to merge lib into two targets.");
} catch (RuntimeException e) {
assertThat(e.getMessage(), Matchers.containsString("into both"));
}
try {
workspace.runBuckBuild("//apps/sample:app_with_cross_asset_merged_libs");
Assert.fail("No exception from trying to merge between asset and non-asset.");
} catch (RuntimeException e) {
assertThat(e.getMessage(), Matchers.containsString("contains both asset and non-asset"));
}
// An older version of the code made this illegal.
// Keep the test around in case we want to restore this behavior.
// try {
// workspace.runBuckBuild("//apps/sample:app_with_merge_into_existing_lib");
// Assert.fail("No exception from trying to merge into existing name.");
// } catch (RuntimeException e) {
// assertThat(e.getMessage(), Matchers.containsString("already a library name"));
// }
try {
workspace.runBuckBuild("//apps/sample:app_with_circular_merged_libs");
Assert.fail("No exception from trying circular merged dep.");
} catch (RuntimeException e) {
assertThat(e.getMessage(), Matchers.containsString("Dependency cycle"));
}
try {
workspace.runBuckBuild("//apps/sample:app_with_circular_merged_libs_including_root");
Assert.fail("No exception from trying circular merged dep.");
} catch (RuntimeException e) {
assertThat(e.getMessage(), Matchers.containsString("Dependency cycle"));
}
try {
workspace.runBuckBuild("//apps/sample:app_with_invalid_native_lib_merge_glue");
Assert.fail("No exception from trying invalid glue.");
} catch (RuntimeException e) {
assertThat(e.getMessage(), Matchers.matchesPattern(".*glue.*is not linkable.*"));
}
}
@Test
public void testNativeRelinker() throws IOException, InterruptedException {
NdkCxxPlatform platform = AndroidNdkHelper.getNdkCxxPlatform(workspace, filesystem);
SourcePathResolver pathResolver =
new SourcePathResolver(
new SourcePathRuleFinder(
new BuildRuleResolver(
TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())));
Path tmpDir = tmpFolder.newFolder("xdso");
SymbolGetter syms =
new SymbolGetter(
new DefaultProcessExecutor(new TestConsole()),
tmpDir,
platform.getObjdump(),
pathResolver);
Symbols sym;
Path apkPath = workspace.buildAndReturnOutput("//apps/sample:app_xdso_dce");
sym = syms.getSymbols(apkPath, "lib/x86/libnative_xdsodce_top.so");
assertTrue(sym.global.contains("_Z10JNI_OnLoadii"));
assertTrue(sym.undefined.contains("_Z10midFromTopi"));
assertTrue(sym.undefined.contains("_Z10botFromTopi"));
assertFalse(sym.all.contains("_Z6unusedi"));
sym = syms.getSymbols(apkPath, "lib/x86/libnative_xdsodce_mid.so");
assertTrue(sym.global.contains("_Z10midFromTopi"));
assertTrue(sym.undefined.contains("_Z10botFromMidi"));
assertFalse(sym.all.contains("_Z6unusedi"));
sym = syms.getSymbols(apkPath, "lib/x86/libnative_xdsodce_bot.so");
assertTrue(sym.global.contains("_Z10botFromTopi"));
assertTrue(sym.global.contains("_Z10botFromMidi"));
assertFalse(sym.all.contains("_Z6unusedi"));
// Run some verification on the same apk with native_relinker disabled.
apkPath = workspace.buildAndReturnOutput("//apps/sample:app_no_xdso_dce");
sym = syms.getSymbols(apkPath, "lib/x86/libnative_xdsodce_top.so");
assertTrue(sym.all.contains("_Z6unusedi"));
sym = syms.getSymbols(apkPath, "lib/x86/libnative_xdsodce_mid.so");
assertTrue(sym.all.contains("_Z6unusedi"));
sym = syms.getSymbols(apkPath, "lib/x86/libnative_xdsodce_bot.so");
assertTrue(sym.all.contains("_Z6unusedi"));
}
@Test
public void testHeaderOnlyCxxLibrary() throws IOException {
String target = "//apps/sample:app_header_only_cxx_lib_dep";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_headeronly.so");
}
@Test
public void testX86OnlyCxxLibrary() throws IOException {
String target = "//apps/sample:app_with_x86_lib";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileDoesNotExist("lib/armeabi-v7a/libnative_cxx_x86-only.so");
zipInspector.assertFileDoesNotExist("lib/armeabi-v7a/libgnustl_shared.so");
zipInspector.assertFileDoesNotExist("lib/armeabi/libnative_cxx_x86-only.so");
zipInspector.assertFileDoesNotExist("lib/armeabi/libgnustl_shared.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_x86-only.so");
zipInspector.assertFileExists("lib/x86/libgnustl_shared.so");
}
@Test
public void testApksHaveDeterministicTimestamps() throws IOException {
String target = "//apps/sample:app";
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", target);
result.assertSuccess();
// Iterate over each of the entries, expecting to see all zeros in the time fields.
Path apk =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
Date dosEpoch = new Date(ZipUtil.dosToJavaTime(ZipConstants.DOS_FAKE_TIME));
try (ZipInputStream is = new ZipInputStream(Files.newInputStream(apk))) {
for (ZipEntry entry = is.getNextEntry(); entry != null; entry = is.getNextEntry()) {
assertThat(entry.getName(), new Date(entry.getTime()), Matchers.equalTo(dosEpoch));
}
}
}
@Test
public void testCxxLibraryAsAsset() throws IOException {
String target = "//apps/sample:app_cxx_lib_asset";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("assets/lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo2.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo2.so");
}
@Test
public void testCxxLibraryAsAssetWithoutPackaging() throws IOException {
String target = "//apps/sample:app_cxx_lib_asset_no_package";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_libasset.so");
}
@Test
public void testCompressAssetLibs() throws IOException {
String target = "//apps/sample:app_compress_lib_asset";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("assets/lib/libs.xzs");
zipInspector.assertFileExists("assets/lib/metadata.txt");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo2.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo2.so");
}
@Test
public void testCompressAssetLibsModular() throws IOException {
String target = "//apps/sample:app_compress_lib_asset_modular";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("assets/lib/libs.xzs");
zipInspector.assertFileExists("assets/lib/metadata.txt");
zipInspector.assertFileExists("assets/native.cxx.libasset/libs.xzs");
zipInspector.assertFileExists("assets/native.cxx.libasset/libs.txt");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo2.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo2.so");
}
@Test
public void testCompressAssetLibsNoPackageModular() throws IOException {
String target = "//apps/sample:app_cxx_lib_asset_no_package_modular";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("assets/native.cxx.libasset/libs.xzs");
zipInspector.assertFileExists("assets/native.cxx.libasset/libs.txt");
zipInspector.assertFileExists("lib/x86/libnative_cxx_libasset2.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo2.so");
zipInspector.assertFileDoesNotExist("assets/lib/libs.xzs");
zipInspector.assertFileDoesNotExist("assets/lib/metadata.txt");
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_libasset2.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo2.so");
}
@Test
public void testCompressLibsNoPackageModular() throws IOException {
String target = "//apps/sample:app_cxx_lib_no_package_modular";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
zipInspector.assertFileExists("assets/native.cxx.foo1/libs.xzs");
zipInspector.assertFileExists("assets/native.cxx.foo1/libs.txt");
zipInspector.assertFileExists("assets/native.cxx.libasset/libs.xzs");
zipInspector.assertFileExists("assets/native.cxx.libasset/libs.txt");
zipInspector.assertFileExists("lib/x86/libnative_cxx_libasset2.so");
zipInspector.assertFileExists("lib/x86/libnative_cxx_foo2.so");
zipInspector.assertFileDoesNotExist("assets/lib/libs.xzs");
zipInspector.assertFileDoesNotExist("assets/lib/metadata.txt");
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileDoesNotExist("lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_libasset.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_libasset2.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo1.so");
zipInspector.assertFileDoesNotExist("assets/lib/x86/libnative_cxx_foo2.so");
}
/* Disable @Test */
public void testMultidexProguardModular() throws IOException {
String target = "//apps/multidex:app_modular_proguard_dontobfuscate";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
String module = "java.com.sample.small.small_with_no_resource_deps";
zipInspector.assertFileExists("assets/" + module + "/" + module + "2.dex");
}
/* Disable @Test */
public void testMultidexProguardModularWithObfuscation() throws IOException {
String target = "//apps/multidex:app_modular_proguard_obfuscate";
workspace.runBuckCommand("build", target).assertSuccess();
ZipInspector zipInspector =
new ZipInspector(
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s.apk")));
String module = "java.com.sample.small.small_with_no_resource_deps";
zipInspector.assertFileExists("assets/" + module + "/" + module + "2.dex");
}
@Test
public void testLibraryMetadataChecksum() throws IOException {
String target = "//apps/sample:app_cxx_lib_asset";
workspace.runBuckCommand("build", target).assertSuccess();
Path pathToZip =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
ZipFile file = new ZipFile(pathToZip.toFile());
ZipEntry metadata = file.getEntry("assets/lib/metadata.txt");
assertNotNull(metadata);
BufferedReader contents =
new BufferedReader(new InputStreamReader(file.getInputStream(metadata)));
String line = contents.readLine();
byte[] buffer = new byte[512];
while (line != null) {
// Each line is of the form <filename> <filesize> <SHA256 checksum>
String[] tokens = line.split(" ");
assertSame(tokens.length, 3);
String filename = tokens[0];
int filesize = Integer.parseInt(tokens[1]);
String checksum = tokens[2];
ZipEntry lib = file.getEntry("assets/lib/" + filename);
assertNotNull(lib);
InputStream is = file.getInputStream(lib);
ByteArrayOutputStream out = new ByteArrayOutputStream();
while (filesize > 0) {
int read = is.read(buffer, 0, Math.min(buffer.length, filesize));
assertTrue(read >= 0);
out.write(buffer, 0, read);
filesize -= read;
}
String actualChecksum = Hashing.sha256().hashBytes(out.toByteArray()).toString();
assertEquals(checksum, actualChecksum);
is.close();
out.close();
line = contents.readLine();
}
file.close();
contents.close();
}
@Test
public void testStripRulesAreShared() throws IOException {
workspace.runBuckCommand("build", "//apps/sample:app_cxx_lib_asset").assertSuccess();
workspace.resetBuildLogFile();
workspace.runBuckCommand("build", "//apps/sample:app_cxx_different_rule_name").assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
for (BuildTarget target : buildLog.getAllTargets()) {
String rawTarget = target.toString();
if (rawTarget.contains("libgnustl_shared.so")) {
// Stripping the C++ runtime is currently not shared.
continue;
}
if (rawTarget.contains("strip")) {
buildLog.assertNotTargetBuiltLocally(rawTarget);
}
}
}
@Test
public void testApkWithNoResourcesBuildsCorrectly() throws IOException {
workspace.runBuckBuild("//apps/sample:app_with_no_res").assertSuccess();
workspace.runBuckBuild("//apps/sample:app_with_no_res_or_predex").assertSuccess();
}
@Test
public void testSimpleAapt2App() throws IOException {
// TODO(dreiss): Remove this when aapt2 is everywhere.
ProjectWorkspace.ProcessResult foundAapt2 =
workspace.runBuckBuild("//apps/sample:check_for_aapt2");
Assume.assumeTrue(foundAapt2.getExitCode() == 0);
ImmutableMap<String, Path> outputs =
workspace.buildMultipleAndReturnOutputs(
"//apps/sample:app_with_aapt2",
"//apps/sample:disassemble_app_with_aapt2",
"//apps/sample:resource_dump_app_with_aapt2");
ZipInspector zipInspector = new ZipInspector(outputs.get("//apps/sample:app_with_aapt2"));
zipInspector.assertFileExists("res/drawable/tiny_black.png");
zipInspector.assertFileExists("res/layout/top_layout.xml");
zipInspector.assertFileExists("assets/asset_file.txt");
zipInspector.assertFileIsNotCompressed("res/drawable/tiny_black.png");
Map<String, String> rDotJavaContents =
parseRDotJavaSmali(outputs.get("//apps/sample:disassemble_app_with_aapt2"));
Map<String, String> resourceBundleContents =
parseResourceDump(outputs.get("//apps/sample:resource_dump_app_with_aapt2"));
assertEquals(
resourceBundleContents.get("string/title"),
rDotJavaContents.get("com/sample2/R$string:title"));
assertEquals(
resourceBundleContents.get("layout/top_layout"),
rDotJavaContents.get("com/sample/R$layout:top_layout"));
assertEquals(
resourceBundleContents.get("drawable/app_icon"),
rDotJavaContents.get("com/sample/R$drawable:app_icon"));
}
@Test
public void testApkEmptyResDirectoriesBuildsCorrectly() throws IOException {
workspace.runBuckBuild("//apps/sample:app_with_aar_and_no_res").assertSuccess();
}
@Test
public void testNativeLibGeneratedProguardConfigIsUsedByProguard() throws IOException {
String target = "//apps/sample:app_with_native_lib_proguard";
workspace.runBuckBuild(target).assertSuccess();
Path generatedConfig =
workspace.getPath(
BuildTargets.getGenPath(
filesystem,
BuildTargetFactory.newInstance(target)
.withFlavors(AndroidBinaryGraphEnhancer.NATIVE_LIBRARY_PROGUARD_FLAVOR),
NativeLibraryProguardGenerator.OUTPUT_FORMAT));
Path proguardDir =
workspace.getPath(
BuildTargets.getGenPath(
filesystem, BuildTargetFactory.newInstance(target), "%s/proguard"));
Path proguardCommandLine = proguardDir.resolve("command-line.txt");
// Check that the proguard command line references the native lib proguard config.
assertTrue(workspace.getFileContents(proguardCommandLine).contains(generatedConfig.toString()));
assertEquals(
workspace.getFileContents("native/proguard_gen/expected.pro"),
workspace.getFileContents(generatedConfig));
}
@Test
public void testReDexIsCalledAppropriatelyFromAndroidBinary() throws IOException {
Path apk = workspace.buildAndReturnOutput(APP_REDEX_TARGET);
Path unzippedApk = unzip(apk.getParent(), apk, "app_redex");
// We use a fake ReDex binary that writes out the arguments it received as JSON so that we can
// verify that it was called in the right way.
@SuppressWarnings("unchecked")
Map<String, Object> userData = ObjectMappers.readValue(unzippedApk.toFile(), Map.class);
String androidSdk = (String) userData.get("ANDROID_SDK");
assertTrue(
"ANDROID_SDK environment variable must be set so ReDex runs with zipalign",
androidSdk != null && !androidSdk.isEmpty());
assertEquals(workspace.getDestPath().toString(), userData.get("PWD"));
assertTrue(userData.get("config").toString().endsWith("apps/sample/redex-config.json"));
assertEquals("buck-out/gen/apps/sample/app_redex/proguard/seeds.txt", userData.get("keep"));
assertEquals("my_alias", userData.get("keyalias"));
assertEquals("android", userData.get("keypass"));
assertEquals(
workspace.resolve("keystores/debug.keystore").toString(), userData.get("keystore"));
assertEquals(
"buck-out/gen/apps/sample/app_redex__redex/app_redex.redex.apk", userData.get("out"));
assertEquals("buck-out/gen/apps/sample/app_redex/proguard/command-line.txt", userData.get("P"));
assertEquals(
"buck-out/gen/apps/sample/app_redex/proguard/mapping.txt", userData.get("proguard-map"));
assertTrue((Boolean) userData.get("sign"));
assertEquals("my_param_name={\"foo\": true}", userData.get("J"));
assertTrue(
"redex_extra_args: -j $(location ...) is not properly expanded!",
userData.get("j").toString().endsWith(".jar"));
assertTrue(
"redex_extra_args: -S $(location ...) is not properly expanded!",
userData.get("S").toString().contains("coldstart_classes=")
&& !userData.get("S").toString().contains("location"));
}
@Test
public void testEditingRedexToolForcesRebuild() throws IOException {
workspace.runBuckBuild(APP_REDEX_TARGET).assertSuccess();
workspace.replaceFileContents("tools/redex/fake_redex.py", "main()\n", "main() \n");
workspace.resetBuildLogFile();
workspace.runBuckBuild(APP_REDEX_TARGET).assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally(APP_REDEX_TARGET);
}
@Test
public void testEditingSecondaryDexHeadListForcesRebuild() throws IOException {
workspace.runBuckBuild(APP_REDEX_TARGET).assertSuccess();
workspace.replaceFileContents("tools/redex/secondary_dex_head.list", "", " ");
workspace.resetBuildLogFile();
workspace.runBuckBuild(APP_REDEX_TARGET).assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally(APP_REDEX_TARGET);
}
@Test
public void testInstrumentationApkWithEmptyResDepBuildsCorrectly() throws IOException {
workspace.runBuckBuild("//apps/sample:instrumentation_apk").assertSuccess();
}
@Test
public void testInvalidKeystoreKeyAlias() throws IOException {
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
workspace.replaceFileContents(
"keystores/debug.keystore.properties", "key.alias=my_alias", "key.alias=invalid_alias");
workspace.resetBuildLogFile();
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", SIMPLE_TARGET);
result.assertFailure("Invalid keystore key alias should fail.");
assertThat(
"error message for invalid keystore key alias is incorrect.",
result.getStderr(),
containsRegex("The keystore \\[.*\\] key\\.alias \\[.*\\].*does not exist"));
}
@Test
public void testResourcesTrimming() throws IOException {
workspace.runBuckBuild(SIMPLE_TARGET).assertSuccess();
// Enable trimming.
workspace.replaceFileContents(
"apps/multidex/BUCK", "# ARGS_FOR_APP", "trim_resource_ids = True, # ARGS_FOR_APP");
workspace.runBuckCommand("build", "//apps/multidex:disassemble_app_r_dot_java").assertSuccess();
// Make sure we only see what we expect.
verifyTrimmedRDotJava(ImmutableSet.of("top_layout", "title"));
// Make a change.
workspace.replaceFileContents(
"java/com/sample/lib/Sample.java", "R.layout.top_layout", "0 /* NO RESOURCE HERE */");
// Make sure everything gets rebuilt, and we only see what we expect.
workspace.resetBuildLogFile();
workspace.runBuckCommand("build", "//apps/multidex:disassemble_app_r_dot_java").assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally("//apps/multidex:app#compile_uber_r_dot_java");
buildLog.assertTargetBuiltLocally("//apps/multidex:app#dex_uber_r_dot_java");
verifyTrimmedRDotJava(ImmutableSet.of("title"));
// Turn off trimming and turn on exopackage, and rebuilt.
workspace.replaceFileContents(
"apps/multidex/BUCK",
"trim_resource_ids = True, # ARGS_FOR_APP",
"exopackage_modes = ['secondary_dex'], # ARGS_FOR_APP");
workspace.runBuckCommand("build", SIMPLE_TARGET).assertSuccess();
// Make a change.
workspace.replaceFileContents(
"java/com/sample/lib/Sample.java", "0 /* NO RESOURCE HERE */", "R.layout.top_layout");
// rebuilt and verify that we get an ABI hit.
workspace.resetBuildLogFile();
workspace.runBuckCommand("build", SIMPLE_TARGET).assertSuccess();
buildLog = workspace.getBuildLog();
buildLog.assertTargetHadMatchingInputRuleKey(SIMPLE_TARGET);
}
@Test
public void testResourcesTrimmingWithPattern() throws IOException {
// Enable trimming.
workspace.replaceFileContents(
"apps/multidex/BUCK",
"# ARGS_FOR_APP",
"keep_resource_pattern = '^app_.*', trim_resource_ids = True, # ARGS_FOR_APP");
workspace.runBuckCommand("build", "//apps/multidex:disassemble_app_r_dot_java").assertSuccess();
// Make sure we only see what we expect.
verifyTrimmedRDotJava(ImmutableSet.of("app_icon", "app_name", "top_layout", "title"));
// Make a change.
workspace.replaceFileContents(
"java/com/sample/lib/Sample.java", "R.layout.top_layout", "0 /* NO RESOURCE HERE */");
// Make sure everything gets rebuilt, and we only see what we expect.
workspace.resetBuildLogFile();
workspace.runBuckCommand("build", "//apps/multidex:disassemble_app_r_dot_java").assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally("//apps/multidex:app#compile_uber_r_dot_java");
buildLog.assertTargetBuiltLocally("//apps/multidex:app#dex_uber_r_dot_java");
verifyTrimmedRDotJava(ImmutableSet.of("app_icon", "app_name", "title"));
}
private static final Pattern SMALI_PUBLIC_CLASS_PATTERN =
Pattern.compile("\\.class public L([\\w/$]+);");
private static final Pattern SMALI_STATIC_FINAL_INT_PATTERN =
Pattern.compile("\\.field public static final (\\w+):I = (0x[0-9A-fa-f]+)");
private void verifyTrimmedRDotJava(ImmutableSet<String> expected) throws IOException {
List<String> lines =
filesystem.readLines(
Paths.get("buck-out/gen/apps/multidex/disassemble_app_r_dot_java/all_r_fields.smali"));
ImmutableSet.Builder<String> found = ImmutableSet.builder();
for (String line : lines) {
Matcher m = SMALI_STATIC_FINAL_INT_PATTERN.matcher(line);
assertTrue("Could not match line: " + line, m.matches());
assertThat(m.group(1), IsIn.in(expected));
found.add(m.group(1));
}
assertEquals(expected, found.build());
}
private Map<String, String> parseRDotJavaSmali(Path smaliPath) throws IOException {
List<String> lines = filesystem.readLines(smaliPath);
ImmutableMap.Builder<String, String> output = ImmutableMap.builder();
String currentClass = null;
for (String line : lines) {
Matcher m;
m = SMALI_PUBLIC_CLASS_PATTERN.matcher(line);
if (m.matches()) {
currentClass = m.group(1);
continue;
}
m = SMALI_STATIC_FINAL_INT_PATTERN.matcher(line);
if (m.matches()) {
output.put(currentClass + ":" + m.group(1), m.group(2));
continue;
}
}
return output.build();
}
private static final Pattern RESOURCE_DUMP_SPEC_PATTERN =
Pattern.compile(" *spec resource (0x[0-9A-fa-f]+) [\\w.]+:(\\w+/\\w+):.*");
private Map<String, String> parseResourceDump(Path dumpPath) throws IOException {
List<String> lines = filesystem.readLines(dumpPath);
ImmutableMap.Builder<String, String> output = ImmutableMap.builder();
for (String line : lines) {
Matcher m = RESOURCE_DUMP_SPEC_PATTERN.matcher(line);
if (m.matches()) {
output.put(m.group(2), m.group(1));
}
}
return output.build();
}
}