aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib
diff options
context:
space:
mode:
authorGravatar Adam Michael <ajmichael@google.com>2017-03-17 21:27:29 +0000
committerGravatar Yue Gan <yueg@google.com>2017-03-20 11:42:35 +0000
commit42d313fa526398b76aa122106adf1518a784e12f (patch)
tree713d5ea136c9d805f75ca1e6363711e7d094d237 /src/test/java/com/google/devtools/build/lib
parentca8c1e39f814cf4059fe83b6479833d30ba62fde (diff)
Open source some tests for android_binary.
-- PiperOrigin-RevId: 150484382 MOS_MIGRATED_REVID=150484382
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib')
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java35
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java2342
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/android/AndroidBuildViewTestCase.java182
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/android/BUILD29
4 files changed, 2578 insertions, 10 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index 76de5f80c4..c2c75afad9 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -39,8 +39,6 @@ import com.google.devtools.build.lib.rules.objc.J2ObjcConfiguration;
import com.google.devtools.build.lib.rules.objc.ObjcConfigurationLoader;
import com.google.devtools.build.lib.rules.proto.ProtoConfiguration;
import com.google.devtools.build.lib.rules.python.PythonConfigurationLoader;
-import com.google.devtools.build.lib.testutil.BuildRuleBuilder;
-import com.google.devtools.build.lib.testutil.BuildRuleWithDefaultsBuilder;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
@@ -111,7 +109,8 @@ public final class BazelAnalysisMock extends AnalysisMock {
"filegroup(name='jacoco-blaze-agent', srcs = [])",
"exports_files(['JavaBuilder_deploy.jar','SingleJar_deploy.jar','TestRunner_deploy.jar',",
" 'JavaBuilderCanary_deploy.jar', 'ijar', 'GenClass_deploy.jar',",
- " 'turbine_deploy.jar','ExperimentalTestRunner_deploy.jar'])");
+ " 'turbine_deploy.jar','ExperimentalTestRunner_deploy.jar'])",
+ "sh_binary(name = 'proguard_whitelister', srcs = ['empty.sh'])");
ImmutableList<String> androidBuildContents = createAndroidBuildContents();
@@ -157,13 +156,29 @@ public final class BazelAnalysisMock extends AnalysisMock {
private ImmutableList<String> createAndroidBuildContents() {
ImmutableList.Builder<String> androidBuildContents = ImmutableList.builder();
- BuildRuleWithDefaultsBuilder ruleBuilder =
- new BuildRuleWithDefaultsBuilder("android_sdk", "sdk")
- .populateAttributes("", false);
- androidBuildContents.add(ruleBuilder.build());
- for (BuildRuleBuilder generatedRuleBuilder : ruleBuilder.getRulesToGenerate()) {
- androidBuildContents.add(generatedRuleBuilder.build());
- }
+ androidBuildContents.add(
+ "android_sdk(",
+ " name = 'sdk',",
+ " aapt = ':static_aapt_tool',",
+ " adb = ':static_adb_tool',",
+ " aidl = ':static_aidl_tool',",
+ " android_jar = ':android_runtime_jar',",
+ " annotations_jar = ':annotations_jar',",
+ " apkbuilder = ':ApkBuilderMainBinary',",
+ " apksigner = ':ApkSignerBinary',",
+ " dx = ':dx_binary',",
+ " framework_aidl = ':aidl_framework',",
+ " jack = ':jack',",
+ " jill = ':jill',",
+ " main_dex_classes = ':mainDexClasses.rules',",
+ " main_dex_list_creator = ':main_dex_list_creator',",
+ " proguard = ':ProGuard',",
+ " resource_extractor = ':resource_extractor',",
+ " shrinked_android_jar = ':shrinkedAndroid.jar',",
+ " zipalign = ':zipalign',",
+ ")",
+ "filegroup(name = 'android_runtime_jar', srcs = ['android.jar'])",
+ "filegroup(name = 'dx_binary', srcs = ['dx_binary.jar'])");
androidBuildContents
.add("sh_binary(name = 'aar_generator', srcs = ['empty.sh'])")
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java
new file mode 100644
index 0000000000..0ee11007e0
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java
@@ -0,0 +1,2342 @@
+// 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.lib.rules.android;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode;
+import com.google.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass.AndroidDeployInfo;
+import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
+import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
+import com.google.devtools.build.lib.rules.java.JavaCompileAction;
+import com.google.devtools.build.lib.rules.java.JavaSemantics;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * A test for {@link com.google.devtools.build.lib.rules.android.AndroidBinary}.
+ */
+@RunWith(JUnit4.class)
+public class AndroidBinaryTest extends AndroidBuildViewTestCase {
+
+ @Before
+ public void createFiles() throws Exception {
+ scratch.file("java/android/BUILD",
+ "android_binary(name = 'app',",
+ " srcs = ['A.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res/**']),",
+ " )");
+ scratch.file("java/android/res/values/strings.xml",
+ "<resources><string name = 'hello'>Hello Android!</string></resources>");
+ scratch.file("java/android/A.java",
+ "package android; public class A {};");
+ }
+
+ @Test
+ public void testAssetsInExternalRepository() throws Exception {
+ FileSystemUtils.appendIsoLatin1(
+ scratch.resolve("WORKSPACE"), "local_repository(name='r', path='/r')");
+ scratch.file("/r/p/BUILD", "filegroup(name='assets', srcs=['a/b'])");
+ scratch.file("/r/p/a/b");
+ invalidatePackages();
+ scratchConfiguredTarget("java/a", "a",
+ "android_binary(",
+ " name = 'a',",
+ " srcs = ['A.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " assets = ['@r//p:assets'],",
+ " assets_dir = '')");
+ }
+
+ @Test
+ public void testMultidexModeAndMainDexProguardSpecs() throws Exception {
+ checkError("java/a", "a", "only allowed if 'multidex' is set to 'legacy'",
+ "android_binary(",
+ " name = 'a',",
+ " srcs = ['A.java'],",
+ " main_dex_proguard_specs = ['foo'])");
+ }
+
+ @Test
+ public void testMainDexProguardSpecs() throws Exception {
+ useConfiguration("--experimental_android_use_singlejar_for_multidex");
+ ConfiguredTarget ct = scratchConfiguredTarget("java/a", "a",
+ "android_binary(",
+ " name = 'a',",
+ " srcs = ['A.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'legacy',",
+ " main_dex_proguard_specs = ['a.spec'])");
+
+ Artifact intermediateJar = artifactByPath(getFilesToBuild(ct),
+ ".apk", ".dex.zip", ".dex.zip", "main_dex_list.txt", "_intermediate.jar");
+ List<String> args = getGeneratingSpawnAction(intermediateJar).getArguments();
+ MoreAsserts.assertContainsSublist(args, "-include", "java/a/a.spec");
+ assertThat(Joiner.on(" ").join(args)).doesNotContain("mainDexClasses.rules");
+ }
+
+ @Test
+ public void testNonLegacyNativeDepsDoesNotPolluteDexSharding() throws Exception {
+ scratch.file("java/a/BUILD",
+ "android_binary(name = 'a',",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'native',",
+ " deps = [':cc'],",
+ " dex_shards = 2)",
+ "cc_library(name = 'cc',",
+ " srcs = ['cc.cc'])");
+
+ Artifact jarShard = artifactByPath(getFilesToBuild(getConfiguredTarget("//java/a:a")),
+ "a.apk", ".apk", ".apk", "classes.dex.zip", "shard1.dex.zip", "shard1.jar");
+ Iterable<Artifact> shardInputs = getGeneratingAction(jarShard).getInputs();
+ assertThat(getFirstArtifactEndingWith(shardInputs, ".txt")).isNull();
+ }
+
+ @Test
+ public void testJavaPluginProcessorPath() throws Exception {
+ scratch.file("java/test/BUILD",
+ "java_library(name = 'plugin_dep',",
+ " srcs = [ 'ProcessorDep.java'])",
+ "java_plugin(name = 'plugin',",
+ " srcs = ['AnnotationProcessor.java'],",
+ " processor_class = 'com.google.process.stuff',",
+ " deps = [ ':plugin_dep' ])",
+ "android_binary(name = 'to_be_processed',",
+ " manifest = 'AndroidManifest.xml',",
+ " plugins = [':plugin'],",
+ " srcs = ['ToBeProcessed.java'])");
+ ConfiguredTarget target = getConfiguredTarget("//java/test:to_be_processed");
+ JavaCompileAction javacAction = (JavaCompileAction) getGeneratingAction(
+ getBinArtifact("libto_be_processed.jar", target));
+
+ assertThat(javacAction.getProcessorNames()).contains("com.google.process.stuff");
+ assertThat(javacAction.getProcessorNames()).hasSize(1);
+
+ assertEquals("libplugin.jar libplugin_dep.jar", ActionsTestUtil.baseNamesOf(
+ javacAction.getProcessorpath()));
+ assertEquals("ToBeProcessed.java AnnotationProcessor.java ProcessorDep.java",
+ actionsTestUtil().predecessorClosureOf(getFilesToBuild(target),
+ JavaSemantics.JAVA_SOURCE));
+ }
+
+ // Same test as above, enabling the plugin through the command line.
+ @Test
+ public void testPluginCommandLine() throws Exception {
+ scratch.file("java/test/BUILD",
+ "java_library(name = 'plugin_dep',",
+ " srcs = [ 'ProcessorDep.java'])",
+ "java_plugin(name = 'plugin',",
+ " srcs = ['AnnotationProcessor.java'],",
+ " processor_class = 'com.google.process.stuff',",
+ " deps = [ ':plugin_dep' ])",
+ "android_binary(name = 'to_be_processed',",
+ " manifest = 'AndroidManifest.xml',",
+ " srcs = ['ToBeProcessed.java'])");
+
+ useConfiguration("--plugin=//java/test:plugin");
+ ConfiguredTarget target = getConfiguredTarget("//java/test:to_be_processed");
+ JavaCompileAction javacAction = (JavaCompileAction) getGeneratingAction(
+ getBinArtifact("libto_be_processed.jar", target));
+
+ assertThat(javacAction.getProcessorNames()).contains("com.google.process.stuff");
+ assertThat(javacAction.getProcessorNames()).hasSize(1);
+ assertEquals("libplugin.jar libplugin_dep.jar",
+ ActionsTestUtil.baseNamesOf(javacAction.getProcessorpath()));
+ assertEquals("ToBeProcessed.java AnnotationProcessor.java ProcessorDep.java",
+ actionsTestUtil().predecessorClosureOf(getFilesToBuild(target),
+ JavaSemantics.JAVA_SOURCE));
+ }
+
+ @Test
+ public void testInvalidPlugin() throws Exception {
+ checkError("java/test", "lib",
+ // error:
+ getErrorMsgMisplacedRules("plugins", "android_binary",
+ "//java/test:lib", "java_library", "//java/test:not_a_plugin"),
+ // BUILD file:
+ "java_library(name = 'not_a_plugin',",
+ " srcs = [ 'NotAPlugin.java'])",
+ "android_binary(name = 'lib',",
+ " plugins = [':not_a_plugin'],",
+ " manifest = 'AndroidManifest.xml',",
+ " srcs = ['Lib.java'])");
+ }
+
+ @Test
+ public void testBaselineCoverageArtifacts() throws Exception {
+ useConfiguration("--collect_code_coverage");
+ ConfiguredTarget target = scratchConfiguredTarget("java/com/google/a", "bin",
+ "android_binary(name='bin', srcs=['Main.java'], manifest='AndroidManifest.xml')");
+
+ assertThat(baselineCoverageArtifactBasenames(target)).containsExactly("Main.java");
+ }
+
+ @Test
+ public void testSameSoFromMultipleDeps() throws Exception {
+ scratch.file("java/d/BUILD",
+ "genrule(name='genrule', srcs=[], outs=['genrule.so'], cmd='')",
+ "cc_library(name='cc1', srcs=[':genrule.so'])",
+ "cc_library(name='cc2', srcs=[':genrule.so'])",
+ "android_binary(name='ab', deps=[':cc1', ':cc2'], manifest='AndroidManifest.xml')");
+ getConfiguredTarget("//java/d:ab");
+ }
+
+ @Test
+ public void testSimpleBinary_desugarJava8() throws Exception {
+ useConfiguration("--experimental_desugar_for_android");
+ ConfiguredTarget binary = getConfiguredTarget("//java/android:app");
+
+ SpawnAction action = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "_deploy.jar");
+ assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs()))
+ .contains("libapp.jar_desugared.jar");
+ assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs()))
+ .doesNotContain("libapp.jar");
+ }
+
+ // regression test for #3169099
+ @Test
+ public void testBinarySrcs() throws Exception {
+ scratch.file("java/srcs/a.foo", "foo");
+ scratch.file("java/srcs/BUILD",
+ "android_binary(name = 'valid', manifest = 'AndroidManifest.xml', "
+ + "srcs = ['a.java', 'b.srcjar', ':gvalid', ':gmix'])",
+ "android_binary(name = 'invalid', manifest = 'AndroidManifest.xml', "
+ + "srcs = ['a.foo', ':ginvalid'])",
+ "android_binary(name = 'mix', manifest = 'AndroidManifest.xml', "
+ + "srcs = ['a.java', 'a.foo'])",
+ "genrule(name = 'gvalid', srcs = ['a.java'], outs = ['b.java'], cmd = '')",
+ "genrule(name = 'ginvalid', srcs = ['a.java'], outs = ['b.foo'], cmd = '')",
+ "genrule(name = 'gmix', srcs = ['a.java'], outs = ['c.java', 'c.foo'], cmd = '')"
+ );
+ assertSrcsValidityForRuleType("//java/srcs", "android_binary", ".java or .srcjar");
+ }
+
+ // regression test for #3169095
+ @Test
+ public void testXmbInSrcs_NotPermittedButDoesNotThrow() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratchConfiguredTarget("java/xmb", "a",
+ "android_binary(name = 'a', manifest = 'AndroidManifest.xml', srcs = ['a.xmb'])");
+ // We expect there to be an error here because a.xmb is not a valid src,
+ // and more importantly, no exception to have been thrown.
+ assertContainsEvent("in srcs attribute of android_binary rule //java/xmb:a: "
+ + "target '//java/xmb:a.xmb' does not exist");
+ }
+
+ @Test
+ public void testNativeLibraryBasenameCollision() throws Exception {
+ reporter.removeHandler(failFastHandler); // expect errors
+ scratch.file("java/android/common/BUILD",
+ "cc_library(name = 'libcommon_armeabi',",
+ " srcs = ['armeabi/native.so'],)");
+ scratch.file("java/android/app/BUILD",
+ "cc_library(name = 'libnative',",
+ " srcs = ['native.so'],)",
+ "android_binary(name = 'b',",
+ " srcs = ['A.java'],",
+ " deps = [':libnative', '//java/android/common:libcommon_armeabi'],",
+ " manifest = 'AndroidManifest.xml',",
+ " )");
+ getConfiguredTarget("//java/android/app:b");
+ assertContainsEvent("Each library in the transitive closure must have a unique basename to "
+ + "avoid name collisions when packaged into an apk, but two libraries have the basename "
+ + "'native.so': java/android/common/armeabi/native.so and java/android/app/native.so");
+ }
+
+ private void setupNativeLibrariesForLinking() throws Exception {
+ scratch.file("java/android/common/BUILD",
+ "cc_library(name = 'common_native',",
+ " srcs = ['common.cc'],)",
+ "android_library(name = 'common',",
+ " deps = [':common_native'],)");
+ scratch.file("java/android/app/BUILD",
+ "cc_library(name = 'native',",
+ " srcs = ['native.cc'],)",
+ "android_binary(name = 'auto',",
+ " srcs = ['A.java'],",
+ " deps = [':native', '//java/android/common:common'],",
+ " manifest = 'AndroidManifest.xml',",
+ " )",
+ "android_binary(name = 'off',",
+ " srcs = ['A.java'],",
+ " deps = [':native', '//java/android/common:common'],",
+ " manifest = 'AndroidManifest.xml',",
+ " legacy_native_support = 0,",
+ " )");
+ }
+
+ private void assertNativeLibraryLinked(ConfiguredTarget target, String... srcNames) {
+ Artifact linkedLib = getOnlyElement(getNativeLibrariesInApk(target));
+ assertEquals(
+ "lib" + target.getLabel().toPathFragment().getBaseName() + ".so", linkedLib.getFilename());
+ assertFalse(linkedLib.isSourceArtifact());
+ assertEquals("Native libraries were not linked to produce " + linkedLib,
+ target.getLabel(), getGeneratingLabelForArtifact(linkedLib));
+ assertThat(artifactsToStrings(actionsTestUtil().artifactClosureOf(linkedLib)))
+ .containsAllIn(ImmutableSet.copyOf(Arrays.asList(srcNames)));
+ }
+
+ @Test
+ public void testNativeLibrary_LinksLibrariesWhenCodeIsPresent() throws Exception {
+ setupNativeLibrariesForLinking();
+ assertNativeLibraryLinked(getConfiguredTarget("//java/android/app:auto"),
+ "src java/android/common/common.cc", "src java/android/app/native.cc");
+ assertNativeLibraryLinked(getConfiguredTarget("//java/android/app:off"),
+ "src java/android/common/common.cc", "src java/android/app/native.cc");
+ }
+
+ @Test
+ public void testNativeLibrary_CopiesLibrariesDespiteExtraLayersOfIndirection() throws Exception {
+ scratch.file("java/android/app/BUILD",
+ "cc_library(name = 'native_dep',",
+ " srcs = ['dep.so'])",
+ "cc_library(name = 'native',",
+ " srcs = ['native_prebuilt.so'],",
+ " deps = [':native_dep'])",
+ "cc_library(name = 'native_wrapper',",
+ " deps = [':native'])",
+ "android_binary(name = 'app',",
+ " srcs = ['A.java'],",
+ " deps = [':native_wrapper'],",
+ " manifest = 'AndroidManifest.xml',",
+ " )");
+ assertNativeLibrariesCopiedNotLinked(getConfiguredTarget("//java/android/app:app"),
+ "src java/android/app/dep.so", "src java/android/app/native_prebuilt.so");
+ }
+
+ @Test
+ public void testNativeLibrary_CopiesLibrariesWrappedInCcLibraryWithSameName() throws Exception {
+ scratch.file("java/android/app/BUILD",
+ "cc_library(name = 'native',",
+ " srcs = ['libnative.so'])",
+ "android_binary(name = 'app',",
+ " srcs = ['A.java'],",
+ " deps = [':native'],",
+ " manifest = 'AndroidManifest.xml',",
+ " )");
+ assertNativeLibrariesCopiedNotLinked(getConfiguredTarget("//java/android/app:app"),
+ "src java/android/app/libnative.so");
+ }
+
+ @Test
+ public void testNativeLibrary_LinksWhenPrebuiltArchiveIsSupplied() throws Exception {
+ scratch.file("java/android/app/BUILD",
+ "cc_library(name = 'native_dep',",
+ " srcs = ['dep.lo'])",
+ "cc_library(name = 'native',",
+ " srcs = ['native_prebuilt.a'],",
+ " deps = [':native_dep'])",
+ "cc_library(name = 'native_wrapper',",
+ " deps = [':native'])",
+ "android_binary(name = 'app',",
+ " srcs = ['A.java'],",
+ " deps = [':native_wrapper'],",
+ " manifest = 'AndroidManifest.xml',",
+ " )");
+ assertNativeLibraryLinked(getConfiguredTarget("//java/android/app:app"),
+ "src java/android/app/native_prebuilt.a");
+ }
+
+ @Test
+ public void testNativeLibrary_CopiesFullLibrariesInIfsoMode() throws Exception {
+ useConfiguration("--interface_shared_objects");
+ scratch.file("java/android/app/BUILD",
+ "cc_library(name = 'native_dep',",
+ " srcs = ['dep.so'])",
+ "cc_library(name = 'native',",
+ " srcs = ['native.cc', 'native_prebuilt.so'],",
+ " deps = [':native_dep'])",
+ "android_binary(name = 'app',",
+ " srcs = ['A.java'],",
+ " deps = [':native'],",
+ " manifest = 'AndroidManifest.xml',",
+ " )");
+ ConfiguredTarget app = getConfiguredTarget("//java/android/app:app");
+ Iterable<Artifact> nativeLibraries = getNativeLibrariesInApk(app);
+ assertThat(artifactsToStrings(nativeLibraries))
+ .containsAllOf(
+ "src java/android/app/native_prebuilt.so",
+ "src java/android/app/dep.so");
+ assertThat(FileType.filter(nativeLibraries, CppFileTypes.INTERFACE_SHARED_LIBRARY))
+ .isEmpty();
+ }
+
+ @Test
+ public void testIncrementalDexingWithAidlRuntimeDependency() throws Exception {
+ useConfiguration(
+ "--incremental_dexing", "--incremental_dexing_binary_types=all", "--android_sdk=//sdk:sdk");
+
+ scratch.file("sdk/BUILD",
+ "android_sdk(",
+ " name = 'sdk',",
+ " aapt = 'aapt',",
+ " adb = 'adb',",
+ " aidl = 'aidl',",
+ " android_jar = 'android.jar',",
+ " annotations_jar = 'annotations_jar',",
+ " apkbuilder = 'apkbuilder',",
+ " apksigner = 'apksigner',",
+ " dx = 'dx',",
+ " framework_aidl = 'framework_aidl',",
+ // TODO(b/35630874): set aidl_lib in MockAndroidSupport once b/35630874 is fixed
+ " aidl_lib = ':aidl_runtime',",
+ " main_dex_classes = 'main_dex_classes',",
+ " main_dex_list_creator = 'main_dex_list_creator',",
+ " proguard = 'proguard',",
+ " shrinked_android_jar = 'shrinked_android_jar',",
+ " zipalign = 'zipalign',",
+ " jack = 'jack',",
+ " jill = 'jill',",
+ " resource_extractor = 'resource_extractor'",
+ ")",
+ "java_library(",
+ " name = 'aidl_runtime',",
+ " srcs = ['AidlRuntime.java'],",
+ ")");
+ scratch.file(
+ "java/com/google/android/BUILD",
+ "android_library(",
+ " name = 'dep',",
+ " srcs = ['dep.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res/**']),",
+ // " idl_srcs = ['dep.aidl'],", b/35630874 AIDL runtime linked in without IDL sources
+ ")",
+ "android_binary(",
+ " name = 'top',",
+ " srcs = ['foo.java', 'bar.srcjar'],",
+ " manifest = 'AndroidManifest.xml',",
+ " deps = [':dep'],",
+ ")");
+
+ ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top");
+ assertNoEvents();
+
+ Action shardAction =
+ getGeneratingAction(getBinArtifact("_dx/top/classes.jar", topTarget));
+ for (String basename : ActionsTestUtil.baseArtifactNames(shardAction.getInputs())) {
+ // all jars are converted to dex archives
+ assertThat(!basename.contains(".jar") || basename.endsWith(".jar.dex.zip"))
+ .named(basename).isTrue();
+ }
+ assertThat(ActionsTestUtil.baseArtifactNames(shardAction.getInputs()))
+ .contains("libaidl_runtime.jar.dex.zip");
+ }
+
+ /** Regression test for http://b/33173461. */
+ @Test
+ public void testIncrementalDexingUsesDexArchives_binaryDependingOnAliasTarget()
+ throws Exception {
+ useConfiguration("--incremental_dexing", "--incremental_dexing_binary_types=all",
+ "--experimental_desugar_for_android");
+ scratch.file(
+ "java/com/google/android/BUILD",
+ "android_library(",
+ " name = 'dep',",
+ " srcs = ['dep.java'],",
+ " resource_files = glob(['res/**']),",
+ " manifest = 'AndroidManifest.xml',",
+ ")",
+ "alias(",
+ " name = 'alt',",
+ " actual = ':dep',",
+ ")",
+ "android_binary(",
+ " name = 'top',",
+ " srcs = ['foo.java', 'bar.srcjar'],",
+ " multidex = 'native',",
+ " manifest = 'AndroidManifest.xml',",
+ " deps = [':alt',],",
+ ")");
+
+ ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top");
+ assertNoEvents();
+
+ Action shardAction =
+ getGeneratingAction(getBinArtifact("_dx/top/classes.jar", topTarget));
+ for (Artifact input : shardAction.getInputs()) {
+ String basename = input.getFilename();
+ // all jars are converted to dex archives
+ assertThat(!basename.contains(".jar") || basename.endsWith(".jar.dex.zip"))
+ .named(basename).isTrue();
+ // all jars are desugared before being converted
+ if (basename.endsWith(".jar.dex.zip")) {
+ assertThat(getGeneratingAction(input).getPrimaryInput().getFilename())
+ .isEqualTo(basename.substring(0, basename.length() - ".jar.dex.zip".length())
+ + ".jar_desugared.jar");
+ }
+ }
+ // Make sure exactly the dex archives generated for top and dependents appear. We also *don't*
+ // want neverlink and unused_dep to appear, and to be safe we do so by explicitly enumerating
+ // *all* expected input dex archives.
+ assertThat(
+ Iterables.filter(
+ ActionsTestUtil.baseArtifactNames(shardAction.getInputs()),
+ Predicates.containsPattern("\\.jar")))
+ .containsExactly(
+ // top's dex archives
+ "libtop.jar.dex.zip",
+ "top_resources.jar.dex.zip",
+ // dep's dex archives
+ "libdep.jar.dex.zip",
+ "dep_resources.jar.dex.zip");
+ }
+
+ @Test
+ public void testIncrementalDisabledWithBlacklistedDexopts_withDexShards() throws Exception {
+ // Even if we mark --no-locals, which is blacklisted by default, as an --incremental_dexopts
+ // incremental dexing isn't used
+ useConfiguration("--incremental_dexing",
+ "--dexopts_supported_in_incremental_dexing=--no-locals");
+ scratch.file(
+ "java/com/google/android/BUILD",
+ "android_binary(",
+ " name = 'top',",
+ " srcs = ['foo.java', 'bar.srcjar'],",
+ " manifest = 'AndroidManifest.xml',",
+ " dexopts = ['--no-locals'],",
+ " dex_shards = 2,",
+ " multidex = 'native',",
+ ")");
+
+ ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top");
+ assertNoEvents();
+ Action shardAction = getGeneratingAction(getBinArtifact("_dx/top/shard1.jar", topTarget));
+ assertThat(
+ Iterables.filter(
+ ActionsTestUtil.baseArtifactNames(shardAction.getInputs()),
+ Predicates.containsPattern("\\.jar\\.dex\\.zip")))
+ .isEmpty(); // no dex archives are used
+ }
+
+ @Test
+ public void testIncrementalDexingDisabledWithProguard() throws Exception {
+ useConfiguration("--incremental_dexing", "--incremental_dexing_binary_types=all");
+ scratch.file(
+ "java/com/google/android/BUILD",
+ "android_binary(",
+ " name = 'top',",
+ " srcs = ['foo.java', 'bar.srcjar'],",
+ " manifest = 'AndroidManifest.xml',",
+ " proguard_specs = ['proguard.cfg'],",
+ ")");
+
+ ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top");
+ assertNoEvents();
+ Action dexAction = getGeneratingAction(getBinArtifact("_dx/top/classes.dex", topTarget));
+ assertThat(
+ Iterables.filter(
+ ActionsTestUtil.baseArtifactNames(dexAction.getInputs()),
+ Predicates.containsPattern("\\.jar")))
+ .containsExactly("top_proguard.jar", "dx_binary.jar"); // proguard output is used directly
+ }
+
+ @Test
+ public void testIncrementalDexing_attributeIncompatibleWithProguard() throws Exception {
+ checkError("java/com/google/android", "top", "target cannot be incrementally dexed",
+ "android_binary(",
+ " name = 'top',",
+ " srcs = ['foo.java', 'bar.srcjar'],",
+ " manifest = 'AndroidManifest.xml',",
+ " proguard_specs = ['proguard.cfg'],",
+ " incremental_dexing = 1,",
+ ")");
+ }
+
+ @Test
+ public void testDexesWithJackWhenFlagEnabled() throws Exception {
+ useConfiguration("--experimental_android_use_jack_for_dexing");
+ scratch.file(
+ "java/com/google/android/BUILD",
+ "android_library(",
+ " name = 'dep',",
+ " srcs = ['dep.java']",
+ ")",
+ "android_library(",
+ " name = 'neverlink',",
+ " srcs = ['neverlink.java'],",
+ " neverlink = 1",
+ ")",
+ "java_plugin(",
+ " name = 'plugin',",
+ " srcs = ['plugin.java'],",
+ " processor_class = 'com.google.android.Plugin'",
+ ")",
+ "android_binary(",
+ " name = 'top',",
+ " srcs = ['foo.java', 'bar.srcjar'],",
+ " plugins = [':plugin'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res/**']),",
+ " deps = [':dep', ':neverlink'],",
+ ")");
+
+ ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top");
+ Action jackDexAction =
+ getGeneratingAction(
+ artifactByPath(
+ getFilesToBuild(topTarget), "top.apk", ".apk", ".apk", "classes.dex.zip"));
+ Iterable<String> jackDexInputs = ActionsTestUtil.baseArtifactNames(jackDexAction.getInputs());
+ assertThat(jackDexInputs).containsAllOf("libtop.jack", "libdep.jack");
+ assertThat(jackDexInputs).doesNotContain("libneverlink.jack");
+ Artifact jackLibrary = getBinArtifact("libtop.jack", topTarget);
+ assertThat(ActionsTestUtil.baseArtifactNames(actionsTestUtil().artifactClosureOf(jackLibrary)))
+ .containsAllOf("foo.java", "bar.srcjar", "libplugin.jar");
+ }
+
+ @Test
+ public void testJackDexingIncludesProguardSpecsFromLibraries() throws Exception {
+ useConfiguration("--experimental_android_use_jack_for_dexing");
+ scratch.file(
+ "java/com/google/android/BUILD",
+ "android_library(",
+ " name = 'dep',",
+ " srcs = ['dep.java'],",
+ " proguard_specs = ['transitive.pro'],",
+ ")",
+ "android_binary(",
+ " name = 'top',",
+ " srcs = ['foo.java', 'bar.srcjar'],",
+ " proguard_specs = ['direct.pro'],",
+ " manifest = 'AndroidManifest.xml',",
+ " deps = [':dep'],",
+ ")");
+
+ ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top");
+ Action jackDexAction =
+ getGeneratingAction(
+ artifactByPath(
+ getFilesToBuild(topTarget), "top.apk", ".apk", ".apk", "classes.dex.zip"));
+ Iterable<String> jackDexInputs = ActionsTestUtil.baseArtifactNames(jackDexAction.getInputs());
+ assertThat(jackDexInputs).containsAllOf("transitive.pro_valid", "direct.pro");
+ }
+
+ @Test
+ public void testJackDexingOnNonProguardTargetHasNoProguardSpecsFromLibraries() throws Exception {
+ useConfiguration("--experimental_android_use_jack_for_dexing");
+ scratch.file(
+ "java/com/google/android/BUILD",
+ "android_library(",
+ " name = 'dep',",
+ " srcs = ['dep.java'],",
+ " proguard_specs = ['transitive.pro'],",
+ ")",
+ "android_binary(",
+ " name = 'top',",
+ " srcs = ['foo.java', 'bar.srcjar'],",
+ " manifest = 'AndroidManifest.xml',",
+ " deps = [':dep'],",
+ ")");
+
+ ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top");
+
+ Action jackDexAction =
+ getGeneratingAction(
+ artifactByPath(
+ getFilesToBuild(topTarget), "top.apk", ".apk", ".apk", "classes.dex.zip"));
+ Iterable<String> jackDexInputs = ActionsTestUtil.baseArtifactNames(jackDexAction.getInputs());
+ assertThat(jackDexInputs).doesNotContain("transitive.pro_valid");
+ }
+
+ @Test
+ public void testV1SigningMethod() throws Exception {
+ actualSignerToolTests("v1", "true", "false");
+ }
+
+ @Test
+ public void testV2SigningMethod() throws Exception {
+ actualSignerToolTests("v2", "false", "true");
+ }
+
+ @Test
+ public void testV1V2SigningMethod() throws Exception {
+ actualSignerToolTests("v1_v2", "true", "true");
+ }
+
+ private void actualSignerToolTests(String apkSigningMethod, String signV1, String signV2)
+ throws Exception {
+ scratch.file("sdk/BUILD",
+ "android_sdk(",
+ " name = 'sdk',",
+ " aapt = 'aapt',",
+ " adb = 'adb',",
+ " aidl = 'aidl',",
+ " android_jar = 'android.jar',",
+ " annotations_jar = 'annotations_jar',",
+ " apkbuilder = 'apkbuilder',",
+ " apksigner = 'apksigner',",
+ " dx = 'dx',",
+ " framework_aidl = 'framework_aidl',",
+ " main_dex_classes = 'main_dex_classes',",
+ " main_dex_list_creator = 'main_dex_list_creator',",
+ " proguard = 'proguard',",
+ " shrinked_android_jar = 'shrinked_android_jar',",
+ " zipalign = 'zipalign',",
+ " jack = 'jack',",
+ " jill = 'jill',",
+ " resource_extractor = 'resource_extractor')");
+ scratch.file("java/com/google/android/hello/BUILD",
+ "android_binary(name = 'hello',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',)");
+ useConfiguration("--android_sdk=//sdk:sdk", "--apk_signing_method=" + apkSigningMethod);
+ ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:hello");
+
+ Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary));
+ assertThat(getFirstArtifactEndingWith(artifacts, "signed_hello.apk")).isNull();
+ SpawnAction unsignedApkAction = (SpawnAction) actionsTestUtil()
+ .getActionForArtifactEndingWith(artifacts, "hello_unsigned.apk");
+ assertThat(unsignedApkAction.getCommandFilename()).endsWith("sdk/apkbuilder");
+ SpawnAction zipalignAction = (SpawnAction) actionsTestUtil()
+ .getActionForArtifactEndingWith(artifacts, "zipaligned_hello.apk");
+ assertThat(zipalignAction.getCommandFilename()).endsWith("sdk/zipalign");
+ SpawnAction apkAction = (SpawnAction) actionsTestUtil()
+ .getActionForArtifactEndingWith(artifacts, "hello.apk");
+ assertThat(apkAction.getCommandFilename()).endsWith("sdk/apksigner");
+
+ assertThat(flagValue("--v1-signing-enabled", apkAction.getArguments())).isEqualTo(signV1);
+ assertThat(flagValue("--v2-signing-enabled", apkAction.getArguments())).isEqualTo(signV2);
+ }
+
+ @Test
+ public void testResourceShrinkingAction() throws Exception {
+ scratch.file("java/com/google/android/hello/BUILD",
+ "android_binary(name = 'hello',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " inline_constants = 0,",
+ " resource_files = ['res/values/strings.xml'],",
+ " shrink_resources = 1,",
+ " proguard_specs = ['proguard-spec.pro'],)");
+
+ ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:hello");
+
+ Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary));
+
+ assertThat(artifacts).containsAllOf(
+ getFirstArtifactEndingWith(artifacts, "resource_files.zip"),
+ getFirstArtifactEndingWith(artifacts, "proguard.jar"),
+ getFirstArtifactEndingWith(artifacts, "shrunk.ap_"));
+
+ final SpawnAction resourceProcessing =
+ getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "resource_files.zip"));
+ List<String> processingArgs = resourceProcessing.getArguments();
+
+ assertThat(flagValue("--resourcesOutput", processingArgs))
+ .endsWith("hello_files/resource_files.zip");
+
+ final SpawnAction proguard =
+ getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "proguard.jar"));
+ assertThat(proguard).isNotNull();
+ List<String> proguardArgs = proguard.getArguments();
+
+ assertThat(flagValue("-outjars", proguardArgs)).endsWith("hello_proguard.jar");
+
+ final SpawnAction resourceShrinking =
+ getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "shrunk.ap_"));
+ assertThat(resourceShrinking).isNotNull();
+
+ List<String> shrinkingArgs = resourceShrinking.getArguments();
+ assertThat(flagValue("--resources", shrinkingArgs))
+ .isEqualTo(flagValue("--resourcesOutput", processingArgs));
+ assertThat(flagValue("--shrunkJar", shrinkingArgs))
+ .isEqualTo(flagValue("-outjars", proguardArgs));
+ assertThat(flagValue("--proguardMapping", shrinkingArgs))
+ .isEqualTo(flagValue("-printmapping", proguardArgs));
+ assertThat(flagValue("--rTxt", shrinkingArgs))
+ .isEqualTo(flagValue("--rOutput", processingArgs));
+ assertThat(flagValue("--primaryManifest", shrinkingArgs))
+ .isEqualTo(flagValue("--manifestOutput", processingArgs));
+ }
+
+ @Test
+ public void testResourceShrinking_RequiresProguard() throws Exception {
+ scratch.file("java/com/google/android/hello/BUILD",
+ "android_binary(name = 'hello',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " inline_constants = 0,",
+ " resource_files = ['res/values/strings.xml'],",
+ " shrink_resources = 1,)");
+
+ ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:hello");
+
+ Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary));
+
+ assertThat(artifacts).containsNoneOf(
+ getFirstArtifactEndingWith(artifacts, "shrunk.jar"),
+ getFirstArtifactEndingWith(artifacts, "shrunk.ap_"));
+ }
+
+ @Test
+ public void testProguardExtraOutputs() throws Exception {
+ scratch.file(
+ "java/com/google/android/hello/BUILD",
+ "android_binary(name = 'b',",
+ " srcs = ['HelloApp.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " proguard_specs = ['proguard-spec.pro'])");
+ ConfiguredTarget output = getConfiguredTarget("//java/com/google/android/hello:b");
+
+ // Checks that ProGuard is called with the appropriate options.
+ SpawnAction action =
+ (SpawnAction)
+ actionsTestUtil()
+ .getActionForArtifactEndingWith(getFilesToBuild(output), "_proguard.jar");
+
+ // Assert that the ProGuard executable set in the android_sdk rule appeared in the command-line
+ // of the SpawnAction that generated the _proguard.jar.
+ assertTrue(
+ Iterables.any(
+ action.getArguments(),
+ new Predicate<String>() {
+ @Override
+ public boolean apply(String s) {
+ return s.endsWith("ProGuard");
+ }
+ }));
+ assertThat(action.getArguments())
+ .containsAllOf(
+ "-injars",
+ execPathEndingWith(action.getInputs(), "b_deploy.jar"),
+ "-printseeds",
+ execPathEndingWith(action.getOutputs(), "b_proguard.seeds"),
+ "-printusage",
+ execPathEndingWith(action.getOutputs(), "b_proguard.usage"))
+ .inOrder();
+
+ // Checks that the output files are produced.
+ assertProguardUsed(output);
+ assertNotNull(getBinArtifact("b_proguard.usage", output));
+ assertNotNull(getBinArtifact("b_proguard.seeds", output));
+ }
+
+ @Test
+ public void testProGuardExecutableMatchesConfiguration() throws Exception {
+ scratch.file("java/com/google/devtools/build/jkrunchy/BUILD",
+ "package(default_visibility=['//visibility:public'])",
+ "java_binary(name = 'jkrunchy',",
+ " srcs = glob(['*.java']),",
+ " main_class = 'com.google.devtools.build.jkrunchy.JKrunchyMain')");
+
+ useConfiguration("--proguard_top=//java/com/google/devtools/build/jkrunchy:jkrunchy");
+
+ scratch.file("java/com/google/android/hello/BUILD",
+ "android_binary(name = 'b',",
+ " srcs = ['HelloApp.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " proguard_specs = ['proguard-spec.pro'])");
+
+ ConfiguredTarget output = getConfiguredTarget("//java/com/google/android/hello:b_proguard.jar");
+ assertProguardUsed(output);
+
+ SpawnAction proguardAction = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith(
+ getFilesToBuild(output), "_proguard.jar");
+ assertEquals("ProGuard implementation was not correctly taken from the configuration",
+ getHostConfiguration().getBinDirectory(RepositoryName.MAIN).getExecPath()
+ .getRelative("java/com/google/devtools/build/jkrunchy/jkrunchy").toString(),
+ proguardAction.getCommandFilename());
+ }
+
+ @Test
+ public void testNeverlinkTransitivity() throws Exception {
+ scratch.file("java/com/google/android/neversayneveragain/BUILD",
+ "android_library(name = 'l1',",
+ " srcs = ['l1.java'])",
+ "android_library(name = 'l2',",
+ " srcs = ['l2.java'],",
+ " deps = [':l1'],",
+ " neverlink = 1)",
+ "android_library(name = 'l3',",
+ " srcs = ['l3.java'],",
+ " deps = [':l2'])",
+ "android_library(name = 'l4',",
+ " srcs = ['l4.java'],",
+ " deps = [':l1'])",
+ "android_binary(name = 'b1',",
+ " srcs = ['b1.java'],",
+ " deps = [':l2'],",
+ " manifest = 'AndroidManifest.xml')",
+ "android_binary(name = 'b2',",
+ " srcs = ['b2.java'],",
+ " deps = [':l3'],",
+ " manifest = 'AndroidManifest.xml')",
+ "android_binary(name = 'b3',",
+ " srcs = ['b3.java'],",
+ " deps = [':l3', ':l4'],",
+ " manifest = 'AndroidManifest.xml')");
+ ConfiguredTarget b1 = getConfiguredTarget("//java/com/google/android/neversayneveragain:b1");
+ Action b1DeployAction = actionsTestUtil().getActionForArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(getFilesToBuild(b1)), "b1_deploy.jar");
+ List<String> b1Inputs = ActionsTestUtil.prettyArtifactNames(b1DeployAction.getInputs());
+
+ assertThat(b1Inputs).containsNoneOf("java/com/google/android/neversayneveragain/libl1.jar",
+ "java/com/google/android/neversayneveragain/libl2.jar",
+ "java/com/google/android/neversayneveragain/libl3.jar",
+ "java/com/google/android/neversayneveragain/libl4.jar");
+ assertThat(b1Inputs).contains(
+ "java/com/google/android/neversayneveragain/libb1.jar");
+
+ ConfiguredTarget b2 = getConfiguredTarget("//java/com/google/android/neversayneveragain:b2");
+ Action b2DeployAction = actionsTestUtil().getActionForArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(getFilesToBuild(b2)), "b2_deploy.jar");
+ List<String> b2Inputs = ActionsTestUtil.prettyArtifactNames(b2DeployAction.getInputs());
+
+ assertThat(b2Inputs).containsNoneOf(
+ "java/com/google/android/neversayneveragain/libl1.jar",
+ "java/com/google/android/neversayneveragain/libl2.jar",
+ "java/com/google/android/neversayneveragain/libl4.jar");
+ assertThat(b2Inputs).containsAllOf(
+ "java/com/google/android/neversayneveragain/libl3.jar",
+ "java/com/google/android/neversayneveragain/libb2.jar");
+
+ ConfiguredTarget b3 = getConfiguredTarget("//java/com/google/android/neversayneveragain:b3");
+ Action b3DeployAction = actionsTestUtil().getActionForArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(getFilesToBuild(b3)), "b3_deploy.jar");
+ List<String> b3Inputs = ActionsTestUtil.prettyArtifactNames(b3DeployAction.getInputs());
+
+ assertThat(b3Inputs).containsAllOf("java/com/google/android/neversayneveragain/libl1.jar",
+ "java/com/google/android/neversayneveragain/libl3.jar",
+ "java/com/google/android/neversayneveragain/libl4.jar",
+ "java/com/google/android/neversayneveragain/libb3.jar");
+ assertThat(b3Inputs).doesNotContain("java/com/google/android/neversayneveragain/libl2.jar");
+ }
+
+ @Test
+ public void testDexopts() throws Exception {
+ checkDexopts("[ '--opt1', '--opt2' ]", ImmutableList.of("--opt1", "--opt2"));
+ }
+
+ @Test
+ public void testDexoptsTokenization() throws Exception {
+ checkDexopts("[ '--opt1', '--opt2 tokenized' ]",
+ ImmutableList.of("--opt1", "--opt2", "tokenized"));
+ }
+
+ @Test
+ public void testDexoptsMakeVariableSubstitution() throws Exception {
+ checkDexopts("[ '--opt1', '$(COMPILATION_MODE)' ]", ImmutableList.of("--opt1", "fastbuild"));
+ }
+
+ private void checkDexopts(String dexopts, List<String> expectedArgs) throws Exception {
+ scratch.file("java/com/google/android/BUILD",
+ "android_binary(name = 'b',",
+ " srcs = ['dummy1.java'],",
+ " dexopts = " + dexopts + ",",
+ " manifest = 'AndroidManifest.xml')");
+
+ // Include arguments that are always included.
+ List<String> fixedArgs = ImmutableList.of("--num-threads=5");
+ expectedArgs = new ImmutableList.Builder<String>()
+ .addAll(fixedArgs).addAll(expectedArgs).build();
+
+ // Ensure that the args that immediately follow "--dex" match the expectation.
+ ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android:b");
+ SpawnAction dexAction = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "classes.dex");
+ List<String> args = dexAction.getArguments();
+ int start = args.indexOf("--dex") + 1;
+ assertThat(start).isNotEqualTo(0);
+ int end = Math.min(args.size(), start + expectedArgs.size());
+ assertEquals(expectedArgs, args.subList(start, end));
+ }
+
+ @Test
+ public void testDexMainListOpts() throws Exception {
+ useConfiguration("--experimental_android_use_singlejar_for_multidex");
+ checkDexMainListOpts("[ '--opt1', '--opt2' ]", "--opt1", "--opt2");
+ }
+
+ @Test
+ public void testDexMainListOptsTokenization() throws Exception {
+ useConfiguration("--experimental_android_use_singlejar_for_multidex");
+ checkDexMainListOpts("[ '--opt1', '--opt2 tokenized' ]", "--opt1", "--opt2", "tokenized");
+ }
+
+ @Test
+ public void testDexMainListOptsMakeVariableSubstitution() throws Exception {
+ useConfiguration("--experimental_android_use_singlejar_for_multidex");
+ checkDexMainListOpts("[ '--opt1', '$(COMPILATION_MODE)' ]", "--opt1", "fastbuild");
+ }
+
+ private void checkDexMainListOpts(String mainDexListOpts, String... expectedArgs)
+ throws Exception {
+ scratch.file("java/com/google/android/BUILD",
+ "android_binary(name = 'b',",
+ " srcs = ['dummy1.java'],",
+ " multidex = \"legacy\",",
+ " main_dex_list_opts = " + mainDexListOpts + ",",
+ " manifest = 'AndroidManifest.xml')");
+
+ // Ensure that the args that immediately follow the main class in the shell command
+ // match the expectation.
+ ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android:b");
+ SpawnAction mainDexListAction = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "main_dex_list.txt");
+ List<String> args = mainDexListAction.getArguments();
+ // args: [ "bash", "-c", "java -cp dx.jar main opts other" ]
+ MoreAsserts.assertContainsSublist(args, expectedArgs);
+ }
+
+ @Test
+ public void testResourceConfigurationFilters() throws Exception {
+ scratch.file("java/com/google/android/BUILD",
+ "android_binary(name = 'b',",
+ " srcs = ['dummy1.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_configuration_filters = [ 'en', 'fr'],)");
+
+ // Ensure that the args are present
+ ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android:b");
+ List<String> args = resourceArguments(getResourceContainer(binary));
+ assertThat(flagValue("--resourceConfigs", args)).contains("en,fr");
+ }
+
+ @Test
+ public void testFilteredResourcesInvalidFilter() throws Exception {
+ String badQualifier = "invalid-qualifier";
+
+ useConfiguration("--experimental_android_use_resource_prefiltering");
+
+ checkError(
+ "java/r/android",
+ "r",
+ badQualifier,
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res/**']),",
+ " resource_configuration_filters = ['" + badQualifier + "'])");
+ }
+
+ @Test
+ public void testFilteredResourcesInvalidResourceDir() throws Exception {
+ String badQualifierDir = "values-invalid-qualifier";
+
+ useConfiguration("--noexperimental_android_use_resource_prefiltering");
+
+ checkError(
+ "java/r/android",
+ "r",
+ badQualifierDir,
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/" + badQualifierDir + "/foo.xml'],",
+ " prefilter_resources = 1,",
+ " resource_configuration_filters = ['en'])");
+ }
+
+ @Test
+ public void testFilteredResourcesFilteringDisabled() throws Exception {
+ List<String> resources =
+ ImmutableList.of("res/values/foo.xml", "res/values-en/foo.xml", "res/values-fr/foo.xml");
+ String dir = "java/r/android";
+
+ useConfiguration("--noexperimental_android_use_resource_prefiltering");
+
+ ConfiguredTarget binary =
+ scratchConfiguredTarget(
+ dir,
+ "r",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_configuration_filters = ['en'],",
+ " resource_files = ['" + Joiner.on("', '").join(resources) + "'])");
+ ResourceContainer directResources = getResourceContainer(binary, /* transitive= */ false);
+
+ // Validate that the AndroidResourceProvider for this binary contains all values.
+ assertThat(resourceContentsPaths(dir, directResources)).containsExactlyElementsIn(resources);
+
+ // Validate that the input to resource processing contains all values.
+ assertThat(resourceInputPaths(dir, directResources)).containsAllIn(resources);
+ }
+
+ @Test
+ public void testFilteredResourcesFilteringNotSpecified() throws Exception {
+ // TODO(asteinb): Once prefiltering is run by default, remove this test and remove the
+ // prefilter_resources flag from tests that currently explicitly specify to filter
+ List<String> resources =
+ ImmutableList.of("res/values/foo.xml", "res/values-en/foo.xml", "res/values-fr/foo.xml");
+ String dir = "java/r/android";
+
+ ConfiguredTarget binary =
+ scratchConfiguredTarget(
+ dir,
+ "r",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_configuration_filters = ['en'],",
+ " resource_files = ['" + Joiner.on("', '").join(resources) + "'])");
+
+ ResourceContainer directResources = getResourceContainer(binary, /* transitive= */ false);
+
+ // Validate that the AndroidResourceProvider for this binary contains all values.
+ assertThat(resourceContentsPaths(dir, directResources)).containsExactlyElementsIn(resources);
+
+ // Validate that the input to resource processing contains all values.
+ assertThat(resourceInputPaths(dir, directResources)).containsAllIn(resources);
+ }
+
+ @Test
+ public void testFilteredResourcesSimple() throws Exception {
+ testDirectResourceFiltering(
+ "en",
+ /* unexpectedQualifiers= */ ImmutableList.of("fr"),
+ /* expectedQualifiers= */ ImmutableList.of("en"));
+ }
+
+ @Test
+ public void testFilteredResourcesNoopFilter() throws Exception {
+ testDirectResourceFiltering(
+ /* filters= */ "",
+ /* unexpectedQualifiers= */ ImmutableList.<String>of(),
+ /* expectedQualifiers= */ ImmutableList.of("en", "fr"));
+ }
+
+ @Test
+ public void testFilteredResourcesMultipleFilters() throws Exception {
+ testDirectResourceFiltering(
+ "en,es",
+ /* unexpectedQualifiers= */ ImmutableList.of("fr"),
+ /* expectedQualifiers= */ ImmutableList.of("en", "es"));
+ }
+
+ @Test
+ public void testFilteredResourcesMultipleFilterStrings() throws Exception {
+ testDirectResourceFiltering(
+ "en', 'es",
+ /* unexpectedQualifiers= */ ImmutableList.of("fr"),
+ /* expectedQualifiers= */ ImmutableList.of("en", "es"));
+ }
+
+ @Test
+ public void testFilteredResourcesLocaleWithoutRegion() throws Exception {
+ testDirectResourceFiltering(
+ "en",
+ /* unexpectedQualifiers= */ ImmutableList.of("fr-rCA"),
+ /* expectedQualifiers= */ ImmutableList.of("en-rCA", "en-rUS", "en"));
+ }
+
+ @Test
+ public void testFilteredResourcesLocaleWithRegion() throws Exception {
+ testDirectResourceFiltering(
+ "en-rUS",
+ /* unexpectedQualifiers= */ ImmutableList.of("en-rGB"),
+ /* expectedQualifiers= */ ImmutableList.of("en-rUS", "en"));
+ }
+
+ @Test
+ public void testFilteredResourcesSmallestScreenWidth() throws Exception {
+ testDirectResourceFiltering(
+ "sw600dp",
+ /* unexpectedQualifiers= */ ImmutableList.of("sw700dp"),
+ /* expectedQualifiers= */ ImmutableList.of("sw500dp", "sw600dp"));
+ }
+
+ @Test
+ public void testFilteredResourcesScreenWidth() throws Exception {
+ testDirectResourceFiltering(
+ "w600dp",
+ /* unexpectedQualifiers= */ ImmutableList.of("w700dp"),
+ /* expectedQualifiers= */ ImmutableList.of("w500dp", "w600dp"));
+ }
+
+ @Test
+ public void testFilteredResourcesScreenHeight() throws Exception {
+ testDirectResourceFiltering(
+ "h600dp",
+ /* unexpectedQualifiers= */ ImmutableList.of("h700dp"),
+ /* expectedQualifiers= */ ImmutableList.of("h500dp", "h600dp"));
+ }
+
+ @Test
+ public void testFilteredResourcesScreenSize() throws Exception {
+ testDirectResourceFiltering(
+ "normal",
+ /* unexpectedQualifiers= */ ImmutableList.of("large", "xlarge"),
+ /* expectedQualifiers= */ ImmutableList.of("small", "normal"));
+ }
+
+ /** Tests that filtering on density is ignored to match aapt behavior. */
+ @Test
+ public void testFilteredResourcesDensity() throws Exception {
+
+ testDirectResourceFiltering(
+ "hdpi",
+ /* unexpectedQualifiers= */ ImmutableList.<String>of(),
+ /* expectedQualifiers= */ ImmutableList.of("ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi"));
+ }
+
+ /** Tests that filtering on API version is ignored to match aapt behavior. */
+ @Test
+ public void testFilteredResourcesApiVersion() throws Exception {
+ testDirectResourceFiltering(
+ "v4",
+ /* unexpectedQualifiers= */ ImmutableList.<String>of(),
+ /* expectedQualifiers= */ ImmutableList.of("v3", "v4", "v5"));
+ }
+
+ @Test
+ public void testFilteredResourcesRegularQualifiers() throws Exception {
+ // Include one value for each qualifier not tested above
+ String filters =
+ "mcc310-mnc004-ldrtl-long-round-port-car-night-notouch-keysexposed-nokeys-navexposed-nonav";
+
+ // In the qualifiers we expect to be removed, include one value that contradicts each qualifier
+ // of the filter
+ testDirectResourceFiltering(
+ filters,
+ /* unexpectedQualifiers= */ ImmutableList.of(
+ "mcc309",
+ "mnc03",
+ "ldltr",
+ "notlong",
+ "notround",
+ "land",
+ "watch",
+ "notnight",
+ "finger",
+ "keyshidden",
+ "qwerty",
+ "navhidden",
+ "dpad"),
+ /* expectedQualifiers= */ ImmutableList.of(filters));
+ }
+
+ private void testDirectResourceFiltering(
+ String resourceConfigurationFilters,
+ List<String> unexpectedQualifiers,
+ List<String> expectedQualifiers)
+ throws Exception {
+
+ List<String> unexpectedResources = new ArrayList<>();
+ for (String qualifier : unexpectedQualifiers) {
+ unexpectedResources.add("res/values-" + qualifier + "/foo.xml");
+ }
+
+ List<String> expectedResources = new ArrayList<>();
+ for (String qualifier : expectedQualifiers) {
+ expectedResources.add("res/values-" + qualifier + "/foo.xml");
+ }
+
+ // Default resources should never be filtered
+ expectedResources.add("res/values/foo.xml");
+
+ Iterable<String> allResources = Iterables.concat(unexpectedResources, expectedResources);
+
+ String dir = "java/r/android";
+
+ useConfiguration("--experimental_android_use_resource_prefiltering");
+
+ ConfiguredTarget binary =
+ scratchConfiguredTarget(
+ dir,
+ "r",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_configuration_filters = ['" + resourceConfigurationFilters + "'],",
+ " resource_files = ['" + Joiner.on("', '").join(allResources) + "'])");
+
+ ResourceContainer directResources = getResourceContainer(binary, /* transitive= */ false);
+
+ // Validate that the AndroidResourceProvider for this binary contains only the filtered values.
+ assertThat(resourceContentsPaths(dir, directResources))
+ .containsExactlyElementsIn(expectedResources);
+
+ // Validate that the input to resource processing contains only the filtered values.
+ assertThat(resourceInputPaths(dir, directResources)).containsAllIn(expectedResources);
+ assertThat(resourceInputPaths(dir, directResources)).containsNoneIn(unexpectedResources);
+ }
+
+ @Test
+ public void testFilteredTransitiveResources() throws Exception {
+ String matchingResource = "res/values-en/foo.xml";
+ String unqualifiedResource = "res/values/foo.xml";
+ String notMatchingResource = "res/values-fr/foo.xml";
+
+ String dir = "java/r/android";
+
+ useConfiguration("--experimental_android_use_resource_prefiltering");
+
+ ConfiguredTarget binary =
+ scratchConfiguredTarget(
+ dir,
+ "r",
+ "android_library(name = 'lib',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = [",
+ " '" + matchingResource + "',",
+ " '" + unqualifiedResource + "',",
+ " '" + notMatchingResource + "'",
+ " ])",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " deps = [':lib'],",
+ " resource_configuration_filters = ['en'])");
+
+ ResourceContainer directResources = getResourceContainer(binary, /* transitive= */ false);
+ ResourceContainer transitiveResources = getResourceContainer(binary, /* transitive= */ true);
+
+ assertThat(resourceContentsPaths(dir, directResources)).isEmpty();
+
+ assertThat(resourceContentsPaths(dir, transitiveResources))
+ .containsExactly(matchingResource, unqualifiedResource);
+
+ assertThat(resourceInputPaths(dir, directResources))
+ .containsAllOf(matchingResource, unqualifiedResource);
+ }
+
+ /**
+ * Gets the paths of matching artifacts contained within a resource container
+ *
+ * @param dir the directory to look for artifacts in
+ * @param resource the container that contains eligible artifacts
+ * @return the paths to all artifacts from the input that are contained within the given
+ * directory, relative to that directory.
+ */
+ private List<String> resourceContentsPaths(String dir, ResourceContainer resource) {
+ return pathsToArtifacts(dir, resource.getArtifacts());
+ }
+
+ /**
+ * Gets the paths of matching artifacts that are used as input to resource processing
+ *
+ * @param dir the directory to look for artifacts in
+ * @param resource the ResourceContainer output from the resource processing that uses these
+ * artifacts as inputs
+ * @return the paths to all artifacts used as inputs to resource processing that are contained
+ * within the given directory, relative to that directory.
+ */
+ private List<String> resourceInputPaths(String dir, ResourceContainer resource) {
+ return pathsToArtifacts(dir, resourceGeneratingAction(resource).getInputs());
+ }
+
+ /**
+ * Gets the paths of matching artifacts from an iterable
+ *
+ * @param dir the directory to look for artifacts in
+ * @param artifacts all available artifacts
+ * @return the paths to all artifacts from the input that are contained within the given
+ * directory, relative to that directory.
+ */
+ private List<String> pathsToArtifacts(String dir, Iterable<Artifact> artifacts) {
+ List<String> paths = new ArrayList<>();
+
+ Path containingDir = rootDirectory;
+ for (String part : dir.split("/")) {
+ containingDir = containingDir.getChild(part);
+ }
+
+ for (Artifact a : artifacts) {
+ if (a.getPath().startsWith(containingDir)) {
+ paths.add(a.getPath().relativeTo(containingDir).toString());
+ }
+ }
+
+ return paths;
+ }
+
+ @Test
+ public void testLocalResourcesUseRClassGenerator() throws Exception {
+ scratch.file("java/r/android/BUILD",
+ "android_library(name = 'lib',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res2/**']),",
+ " )",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res/**']),",
+ " deps = [':lib'],",
+ " )");
+ scratch.file("java/r/android/res2/values/strings.xml",
+ "<resources><string name = 'lib_string'>Libs!</string></resources>");
+ scratch.file("java/r/android/res/values/strings.xml",
+ "<resources><string name = 'hello'>Hello Android!</string></resources>");
+ ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r");
+ SpawnAction compilerAction = ((SpawnAction) getResourceClassJarAction(binary));
+ assertThat(compilerAction.getMnemonic()).isEqualTo("RClassGenerator");
+ List<String> args = compilerAction.getArguments();
+ assertThat(args)
+ .containsAllOf("--primaryRTxt", "--primaryManifest", "--libraries", "--classJarOutput");
+ }
+
+ @Test
+ public void testLocalResourcesUseRClassGeneratorNoLibraries() throws Exception {
+ scratch.file("java/r/android/BUILD",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res/**']),",
+ " )");
+ scratch.file("java/r/android/res/values/strings.xml",
+ "<resources><string name = 'hello'>Hello Android!</string></resources>");
+ ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r");
+ SpawnAction compilerAction = ((SpawnAction) getResourceClassJarAction(binary));
+ assertThat(compilerAction.getMnemonic()).isEqualTo("RClassGenerator");
+ List<String> args = compilerAction.getArguments();
+ assertThat(args).containsAllOf("--primaryRTxt", "--primaryManifest", "--classJarOutput");
+ assertThat(args).doesNotContain("--libraries");
+ }
+
+ @Test
+ public void testUseRClassGeneratorCustomPackage() throws Exception {
+ scratch.file("java/r/android/BUILD",
+ "android_library(name = 'lib',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res2/**']),",
+ " custom_package = 'com.lib.custom',",
+ " )",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res/**']),",
+ " custom_package = 'com.binary.custom',",
+ " deps = [':lib'],",
+ " )");
+ scratch.file("java/r/android/res2/values/strings.xml",
+ "<resources><string name = 'lib_string'>Libs!</string></resources>");
+ scratch.file("java/r/android/res/values/strings.xml",
+ "<resources><string name = 'hello'>Hello Android!</string></resources>");
+ ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r");
+ SpawnAction compilerAction = ((SpawnAction) getResourceClassJarAction(binary));
+ assertThat(compilerAction.getMnemonic()).isEqualTo("RClassGenerator");
+ List<String> args = compilerAction.getArguments();
+ assertThat(args)
+ .containsAllOf("--primaryRTxt", "--primaryManifest", "--libraries", "--classJarOutput",
+ "--packageForR", "com.binary.custom");
+ }
+
+ @Test
+ public void testNoCrunchBinaryOnly() throws Exception {
+ scratch.file("java/r/android/BUILD",
+ "android_binary(name = 'r',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/drawable-hdpi-v4/foo.png',",
+ " 'res/drawable-hdpi-v4/bar.9.png'],",
+ " crunch_png = 0,",
+ " )");
+ ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r");
+ List<String> args = getGeneratingSpawnAction(getResourceApk(binary)).getArguments();
+ assertThat(args).contains("--useAaptCruncher=no");
+ }
+
+ @Test
+ public void testDoCrunch() throws Exception {
+ scratch.file("java/r/android/BUILD",
+ "android_binary(name = 'r',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/drawable-hdpi-v4/foo.png',",
+ " 'res/drawable-hdpi-v4/bar.9.png'],",
+ " crunch_png = 1,",
+ " )");
+ ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r");
+ List<String> args = getGeneratingSpawnAction(getResourceApk(binary)).getArguments();
+ assertThat(args).doesNotContain("--useAaptCruncher=no");
+ }
+
+ @Test
+ public void testDoCrunchDefault() throws Exception {
+ scratch.file("java/r/android/BUILD",
+ "android_binary(name = 'r',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/drawable-hdpi-v4/foo.png',",
+ " 'res/drawable-hdpi-v4/bar.9.png'],",
+ " )");
+ ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r");
+ List<String> args = getGeneratingSpawnAction(getResourceApk(binary)).getArguments();
+ assertThat(args).doesNotContain("--useAaptCruncher=no");
+ }
+
+ @Test
+ public void testNoCrunchWithAndroidLibraryNoBinaryResources() throws Exception {
+ scratch.file("java/r/android/BUILD",
+ "android_library(name = 'resources',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/values/strings.xml',",
+ " 'res/drawable-hdpi-v4/foo.png',",
+ " 'res/drawable-hdpi-v4/bar.9.png'],",
+ " )",
+ "android_binary(name = 'r',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " deps = [':resources'],",
+ " crunch_png = 0,",
+ " )");
+ ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r");
+ List<String> args = getGeneratingSpawnAction(getResourceApk(binary)).getArguments();
+ assertThat(args).contains("--useAaptCruncher=no");
+ }
+
+ @Test
+ public void testNoCrunchWithMultidexNative() throws Exception {
+ useConfiguration("--experimental_android_use_singlejar_for_multidex");
+ scratch.file("java/r/android/BUILD",
+ "android_library(name = 'resources',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/values/strings.xml',",
+ " 'res/drawable-hdpi-v4/foo.png',",
+ " 'res/drawable-hdpi-v4/bar.9.png'],",
+ " )",
+ "android_binary(name = 'r',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " deps = [':resources'],",
+ " multidex = 'native',",
+ " crunch_png = 0,",
+ " )");
+ ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r");
+ List<String> args = getGeneratingSpawnAction(getResourceApk(binary)).getArguments();
+ assertThat(args).contains("--useAaptCruncher=no");
+ }
+
+ @Test
+ public void testZipaligned() throws Exception {
+ ConfiguredTarget binary = getConfiguredTarget("//java/android:app");
+ SpawnAction action = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "zipaligned_app.apk");
+ assertEquals("AndroidZipAlign", action.getMnemonic());
+
+ List<String> arguments = action.getArguments();
+ assertEquals(1, Iterables.frequency(arguments, "4"));
+
+ Artifact zipAlignTool =
+ getFirstArtifactEndingWith(action.getInputs(), "/zipalign");
+ assertEquals(1, Iterables.frequency(arguments, zipAlignTool.getExecPathString()));
+
+ Artifact unsignedApk =
+ getFirstArtifactEndingWith(action.getInputs(), "/app_unsigned.apk");
+ assertEquals(1, Iterables.frequency(arguments, unsignedApk.getExecPathString()));
+
+ Artifact zipalignedApk =
+ getFirstArtifactEndingWith(action.getOutputs(), "/zipaligned_app.apk");
+ assertEquals(1, Iterables.frequency(arguments, zipalignedApk.getExecPathString()));
+ }
+
+ @Test
+ public void testDeployInfo() throws Exception {
+ ConfiguredTarget binary = getConfiguredTarget("//java/android:app");
+ NestedSet<Artifact> outputGroup = getOutputGroup(binary, "android_deploy_info");
+ Artifact deployInfoArtifact = ActionsTestUtil
+ .getFirstArtifactEndingWith(outputGroup, "/deploy_info.deployinfo.pb");
+ assertThat(deployInfoArtifact).isNotNull();
+ AndroidDeployInfo deployInfo = getAndroidDeployInfo(deployInfoArtifact);
+ assertThat(deployInfo).isNotNull();
+ assertThat(deployInfo.getMergedManifest().getExecRootPath()).endsWith(
+ "/AndroidManifest.xml");
+ assertThat(deployInfo.getAdditionalMergedManifestsList()).isEmpty();
+ assertThat(deployInfo.getApksToDeploy(0).getExecRootPath()).endsWith("/app.apk");
+ assertThat(deployInfo.getDataToDeployList()).isEmpty();
+ }
+
+ /**
+ * Internal helper method: checks that dex sharding input and output is correct for
+ * different combinations of multidex mode and build with and without proguard.
+ */
+ private void internalTestDexShardStructure(MultidexMode multidexMode, boolean proguard,
+ String nonProguardSuffix) throws Exception {
+ ConfiguredTarget target = getConfiguredTarget("//java/a:a");
+ assertNoEvents();
+ Action shardAction = getGeneratingAction(getBinArtifact("_dx/a/shard1.jar", target));
+
+ // Verify command line arguments
+ List<String> arguments = ((SpawnAction) shardAction).getRemainingArguments();
+ List<String> expectedArguments = new ArrayList<>();
+ Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(target));
+ Artifact shard1 = getFirstArtifactEndingWith(artifacts, "shard1.jar");
+ Artifact shard2 = getFirstArtifactEndingWith(artifacts, "shard2.jar");
+ Artifact resourceJar = getFirstArtifactEndingWith(artifacts,
+ "java_resources.jar");
+ expectedArguments.add("--output_jar");
+ expectedArguments.add(shard1.getExecPathString());
+ expectedArguments.add("--output_jar");
+ expectedArguments.add(shard2.getExecPathString());
+ expectedArguments.add("--output_resources");
+ expectedArguments.add(resourceJar.getExecPathString());
+ if (multidexMode == MultidexMode.LEGACY) {
+ Artifact mainDexList = getFirstArtifactEndingWith(artifacts,
+ "main_dex_list.txt");
+ expectedArguments.add("--main_dex_filter");
+ expectedArguments.add(mainDexList.getExecPathString());
+ }
+ if (!proguard) {
+ expectedArguments.add("--input_jar");
+ expectedArguments.add(
+ getFirstArtifactEndingWith(artifacts, "a_resources.jar" + nonProguardSuffix)
+ .getExecPathString());
+ }
+ Artifact inputJar;
+ if (proguard) {
+ inputJar = getFirstArtifactEndingWith(artifacts, "a_proguard.jar");
+ } else {
+ inputJar = getFirstArtifactEndingWith(artifacts, "liba.jar" + nonProguardSuffix);
+ }
+ expectedArguments.add("--input_jar");
+ expectedArguments.add(inputJar.getExecPathString());
+ assertThat(arguments).containsExactlyElementsIn(expectedArguments).inOrder();
+
+ // Verify input and output artifacts
+ List<String> shardOutputs = ActionsTestUtil.baseArtifactNames(shardAction.getOutputs());
+ List<String> shardInputs = ActionsTestUtil.baseArtifactNames(shardAction.getInputs());
+ assertThat(shardOutputs)
+ .containsExactly("shard1.jar", "shard2.jar", "java_resources.jar");
+ if (multidexMode == MultidexMode.LEGACY) {
+ assertThat(shardInputs).contains("main_dex_list.txt");
+ } else {
+ assertThat(shardInputs).doesNotContain("main_dex_list.txt");
+ }
+ if (proguard) {
+ assertThat(shardInputs).contains("a_proguard.jar");
+ assertThat(shardInputs).doesNotContain("liba.jar" + nonProguardSuffix);
+ } else {
+ assertThat(shardInputs).contains("liba.jar" + nonProguardSuffix);
+ assertThat(shardInputs).doesNotContain("a_proguard.jar");
+ }
+ assertThat(shardInputs).doesNotContain("a_deploy.jar");
+
+ // Verify that dex compilation is followed by the correct merge operation
+ Action apkAction = getGeneratingAction(getFirstArtifactEndingWith(
+ getFilesToBuild(target), "a_unsigned.apk"));
+ Action mergeAction = getGeneratingAction(getFirstArtifactEndingWith(
+ apkAction.getInputs(), "classes.dex.zip"));
+ Iterable<Artifact> dexShards = Iterables.filter(
+ mergeAction.getInputs(), ActionsTestUtil.getArtifactSuffixMatcher(".dex.zip"));
+ assertThat(ActionsTestUtil.baseArtifactNames(dexShards))
+ .containsExactly("shard1.dex.zip", "shard2.dex.zip");
+ }
+
+ @Test
+ public void testDexShardingNeedsMultidex() throws Exception {
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " manifest='AndroidManifest.xml')");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//java/a:a");
+ assertContainsEvent(".dex sharding is only available in multidex mode");
+ }
+
+ @Test
+ public void testDexShardingDoesNotWorkWithManualMultidex() throws Exception {
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " multidex='manual_main_dex',",
+ " main_dex_list='main_dex_list.txt',",
+ " manifest='AndroidManifest.xml')");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//java/a:a");
+ assertContainsEvent(".dex sharding is not available in manual multidex mode");
+ }
+
+ @Test
+ public void testDexShardingLegacyStructure() throws Exception {
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " multidex='legacy',",
+ " manifest='AndroidManifest.xml')");
+
+ internalTestDexShardStructure(MultidexMode.LEGACY, false, "");
+ }
+
+ @Test
+ public void testDexShardingNativeStructure() throws Exception {
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " multidex='native',",
+ " manifest='AndroidManifest.xml')");
+
+ internalTestDexShardStructure(MultidexMode.NATIVE, false, "");
+ }
+
+ @Test
+ public void testDexShardingNativeStructure_withDesugaring() throws Exception {
+ useConfiguration("--experimental_desugar_for_android");
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " multidex='native',",
+ " manifest='AndroidManifest.xml')");
+
+ internalTestDexShardStructure(MultidexMode.NATIVE, false, "_desugared.jar");
+ }
+
+ @Test
+ public void testDexShardingLegacyAndProguardStructure() throws Exception {
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " multidex='legacy',",
+ " manifest='AndroidManifest.xml',",
+ " proguard_specs=['proguard.cfg'])");
+
+ internalTestDexShardStructure(MultidexMode.LEGACY, true, "");
+ }
+
+ @Test
+ public void testDexShardingLegacyAndProguardStructure_withDesugaring() throws Exception {
+ useConfiguration("--experimental_desugar_for_android");
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " multidex='legacy',",
+ " manifest='AndroidManifest.xml',",
+ " proguard_specs=['proguard.cfg'])");
+
+ internalTestDexShardStructure(MultidexMode.LEGACY, true, "_desugared.jar");
+ }
+
+ @Test
+ public void testDexShardingNativeAndProguardStructure() throws Exception {
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " multidex='native',",
+ " manifest='AndroidManifest.xml',",
+ " proguard_specs=['proguard.cfg'])");
+
+ internalTestDexShardStructure(MultidexMode.NATIVE, true, "");
+ }
+
+ @Test
+ public void testIncrementalApkAndProguardBuildStructure() throws Exception {
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name='a',",
+ " srcs=['A.java'],",
+ " dex_shards=2,",
+ " multidex='native',",
+ " manifest='AndroidManifest.xml',",
+ " proguard_specs=['proguard.cfg'])");
+
+ ConfiguredTarget target = getConfiguredTarget("//java/a:a");
+ Action shardAction = getGeneratingAction(getBinArtifact("_dx/a/shard1.jar", target));
+ List<String> shardOutputs = ActionsTestUtil.baseArtifactNames(shardAction.getOutputs());
+ assertThat(shardOutputs).contains("java_resources.jar");
+ assertThat(shardOutputs).doesNotContain("a_deploy.jar");
+ }
+
+ @Test
+ public void testManualMainDexBuildStructure() throws Exception {
+ checkError("java/foo",
+ "maindex_nomultidex",
+ "Both \"main_dex_list\" and \"multidex='manual_main_dex'\" must be specified",
+ "android_binary(",
+ " name = 'maindex_nomultidex',",
+ " srcs = ['a.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'manual_main_dex')");
+ }
+
+ @Test
+ public void testMainDexListLegacyMultidex() throws Exception {
+ checkError("java/foo",
+ "maindex_nomultidex",
+ "Both \"main_dex_list\" and \"multidex='manual_main_dex'\" must be specified",
+ "android_binary(",
+ " name = 'maindex_nomultidex',",
+ " srcs = ['a.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'legacy',",
+ " main_dex_list = 'main_dex_list.txt')");
+ }
+
+ @Test
+ public void testMainDexListNativeMultidex() throws Exception {
+ checkError("java/foo",
+ "maindex_nomultidex",
+ "Both \"main_dex_list\" and \"multidex='manual_main_dex'\" must be specified",
+ "android_binary(",
+ " name = 'maindex_nomultidex',",
+ " srcs = ['a.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'native',",
+ " main_dex_list = 'main_dex_list.txt')");
+ }
+
+ @Test
+ public void testMainDexListNoMultidex() throws Exception {
+ checkError("java/foo",
+ "maindex_nomultidex",
+ "Both \"main_dex_list\" and \"multidex='manual_main_dex'\" must be specified",
+ "android_binary(",
+ " name = 'maindex_nomultidex',",
+ " srcs = ['a.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " main_dex_list = 'main_dex_list.txt')");
+ }
+
+ @Test
+ public void testMainDexListWithAndroidSdk() throws Exception {
+ scratch.file("sdk/BUILD",
+ "android_sdk(",
+ " name = 'sdk',",
+ " aapt = 'aapt',",
+ " adb = 'adb',",
+ " aidl = 'aidl',",
+ " android_jar = 'android.jar',",
+ " annotations_jar = 'annotations_jar',",
+ " apkbuilder = 'apkbuilder',",
+ " apksigner = 'apksigner',",
+ " dx = 'dx',",
+ " framework_aidl = 'framework_aidl',",
+ " main_dex_classes = 'main_dex_classes',",
+ " main_dex_list_creator = 'main_dex_list_creator',",
+ " proguard = 'proguard',",
+ " shrinked_android_jar = 'shrinked_android_jar',",
+ " zipalign = 'zipalign',",
+ " jack = 'jack',",
+ " jill = 'jill',",
+ " resource_extractor = 'resource_extractor')");
+
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name = 'a',",
+ " srcs = ['A.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'legacy',",
+ " main_dex_list_opts = ['--hello', '--world'])");
+
+ useConfiguration(
+ "--android_sdk=//sdk:sdk", "--experimental_android_use_singlejar_for_multidex");
+ ConfiguredTarget a = getConfiguredTarget("//java/a:a");
+ Artifact mainDexList = ActionsTestUtil.getFirstArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(getFilesToBuild(a)), "main_dex_list.txt");
+ SpawnAction spawnAction = getGeneratingSpawnAction(mainDexList);
+ assertThat(spawnAction.getArguments()).containsAllOf("--hello", "--world");
+ }
+
+ @Test
+ public void testMainDexAaptGenerationSupported() throws Exception {
+ scratch.file("sdk/BUILD",
+ "android_sdk(",
+ " name = 'sdk',",
+ " build_tools_version = '24.0.0',",
+ " aapt = 'aapt',",
+ " adb = 'adb',",
+ " aidl = 'aidl',",
+ " android_jar = 'android.jar',",
+ " annotations_jar = 'annotations_jar',",
+ " apkbuilder = 'apkbuilder',",
+ " apksigner = 'apksigner',",
+ " dx = 'dx',",
+ " framework_aidl = 'framework_aidl',",
+ " main_dex_classes = 'main_dex_classes',",
+ " main_dex_list_creator = 'main_dex_list_creator',",
+ " proguard = 'proguard',",
+ " shrinked_android_jar = 'shrinked_android_jar',",
+ " zipalign = 'zipalign',",
+ " jack = 'jack',",
+ " jill = 'jill',",
+ " resource_extractor = 'resource_extractor')");
+
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name = 'a',",
+ " srcs = ['A.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'legacy')");
+
+ useConfiguration(
+ "--android_sdk=//sdk:sdk", "--experimental_android_use_singlejar_for_multidex");
+ ConfiguredTarget a = getConfiguredTarget("//java/a:a");
+ Artifact intermediateJar = artifactByPath(getFilesToBuild(a),
+ ".apk", ".dex.zip", ".dex.zip", "main_dex_list.txt", "_intermediate.jar");
+ List<String> args = getGeneratingSpawnAction(intermediateJar).getArguments();
+ assertContainsSublist(
+ args,
+ ImmutableList.of(
+ "-include",
+ targetConfig.getBinFragment() + "/java/a/proguard/a/main_dex_a_proguard.cfg"));
+ }
+
+ @Test
+ public void testMainDexAaptGenerationUnsupported() throws Exception {
+ scratch.file("sdk/BUILD",
+ "android_sdk(",
+ " name = 'sdk',",
+ " build_tools_version = '24.0.0-rc3',",
+ " aapt = 'aapt',",
+ " adb = 'adb',",
+ " aidl = 'aidl',",
+ " android_jar = 'android.jar',",
+ " annotations_jar = 'annotations_jar',",
+ " apkbuilder = 'apkbuilder',",
+ " apksigner = 'apksigner',",
+ " dx = 'dx',",
+ " framework_aidl = 'framework_aidl',",
+ " main_dex_classes = 'main_dex_classes',",
+ " main_dex_list_creator = 'main_dex_list_creator',",
+ " proguard = 'proguard',",
+ " shrinked_android_jar = 'shrinked_android_jar',",
+ " zipalign = 'zipalign',",
+ " jack = 'jack',",
+ " jill = 'jill',",
+ " resource_extractor = 'resource_extractor')");
+
+ scratch.file("java/a/BUILD",
+ "android_binary(",
+ " name = 'a',",
+ " srcs = ['A.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'legacy')");
+
+ useConfiguration(
+ "--android_sdk=//sdk:sdk", "--experimental_android_use_singlejar_for_multidex");
+ ConfiguredTarget a = getConfiguredTarget("//java/a:a");
+ Artifact intermediateJar = artifactByPath(getFilesToBuild(a),
+ ".apk", ".dex.zip", ".dex.zip", "main_dex_list.txt", "_intermediate.jar");
+ List<String> args = getGeneratingSpawnAction(intermediateJar).getArguments();
+ assertEquals(-1,
+ Collections.indexOfSubList(
+ args,
+ ImmutableList.of(
+ "-include",
+ targetConfig.getBinFragment() + "/java/a/proguard/a/main_dex_a_proguard.cfg")));
+ }
+
+ @Test
+ public void testMainDexGenerationWithoutProguardMap() throws Exception {
+ useConfiguration("--experimental_android_use_singlejar_for_multidex");
+ scratchConfiguredTarget("java/foo", "abin",
+ "android_binary(",
+ " name = 'abin',",
+ " srcs = ['a.java'],",
+ " proguard_specs = [],",
+ " manifest = 'AndroidManifest.xml',",
+ " multidex = 'legacy',)");
+ ConfiguredTarget a = getConfiguredTarget("//java/foo:abin");
+ Artifact intermediateJar = artifactByPath(getFilesToBuild(a),
+ ".apk", ".dex.zip", ".dex.zip", "main_dex_list.txt", "_intermediate.jar");
+ List<String> args = getGeneratingSpawnAction(intermediateJar).getArguments();
+ MoreAsserts.assertDoesNotContainSublist(
+ args,
+ "-previousobfuscationmap");
+ }
+
+ // regression test for b/14288948
+ @Test
+ public void testEmptyListAsProguardSpec() throws Exception {
+ scratchConfiguredTarget("java/foo", "abin",
+ "android_binary(",
+ " name = 'abin',",
+ " srcs = ['a.java'],",
+ " proguard_specs = [],",
+ " manifest = 'AndroidManifest.xml')");
+ }
+
+ @Test
+ public void testConfigurableProguardSpecsEmptyList() throws Exception {
+ scratchConfiguredTarget("java/foo", "abin",
+ "android_binary(",
+ " name = 'abin',",
+ " srcs = ['a.java'],",
+ " proguard_specs = select({",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [],",
+ " }),",
+ " manifest = 'AndroidManifest.xml')");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testConfigurableProguardSpecsEmptyListWithMapping() throws Exception {
+ scratchConfiguredTarget("java/foo", "abin",
+ "android_binary(",
+ " name = 'abin',",
+ " srcs = ['a.java'],",
+ " proguard_specs = select({",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [],",
+ " }),",
+ " proguard_generate_mapping = 1,",
+ " manifest = 'AndroidManifest.xml')");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testResourcesWithConfigurationQualifier_LocalResources() throws Exception {
+ scratch.file("java/android/resources/BUILD",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = glob(['res/**']),",
+ " )");
+ scratch.file("java/android/resources/res/values-en/strings.xml",
+ "<resources><string name = 'hello'>Hello Android!</string></resources>");
+ scratch.file("java/android/resources/res/values/strings.xml",
+ "<resources><string name = 'hello'>Hello Android!</string></resources>");
+ ConfiguredTarget resource = getConfiguredTarget("//java/android/resources:r");
+
+ List<String> args = getGeneratingSpawnAction(getResourceApk(resource)).getArguments();
+
+ assertPrimaryResourceDirs(ImmutableList.of("java/android/resources/res"), args);
+ }
+
+ @Test
+ public void testResourcesInOtherPackage_exported_LocalResources() throws Exception {
+ scratch.file("java/android/resources/BUILD",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['//java/resources/other:res/values/strings.xml'],",
+ " )");
+ scratch.file("java/resources/other/BUILD",
+ "exports_files(['res/values/strings.xml'])");
+ ConfiguredTarget resource = getConfiguredTarget("//java/android/resources:r");
+
+ List<String> args = getGeneratingSpawnAction(getResourceApk(resource)).getArguments();
+ assertPrimaryResourceDirs(ImmutableList.of("java/resources/other/res"), args);
+ assertNoEvents();
+ }
+
+ @Test
+ public void testResourcesInOtherPackage_filegroup_LocalResources() throws Exception {
+ scratch.file("java/android/resources/BUILD",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['//java/other/resources:fg'],",
+ " )");
+ scratch.file("java/other/resources/BUILD",
+ "filegroup(name = 'fg',",
+ " srcs = ['res/values/strings.xml'],",
+ ")");
+ ConfiguredTarget resource = getConfiguredTarget("//java/android/resources:r");
+
+ List<String> args = getGeneratingSpawnAction(getResourceApk(resource)).getArguments();
+ assertPrimaryResourceDirs(ImmutableList.of("java/other/resources/res"), args);
+ assertNoEvents();
+ }
+
+ @Test
+ public void testResourcesInOtherPackage_filegroupWithExternalSources_LocalResources()
+ throws Exception {
+ scratch.file("java/android/resources/BUILD",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = [':fg'],",
+ " )",
+ "filegroup(name = 'fg',",
+ " srcs = ['//java/other/resources:res/values/strings.xml'])");
+ scratch.file("java/other/resources/BUILD",
+ "exports_files(['res/values/strings.xml'])");
+ ConfiguredTarget resource = getConfiguredTarget("//java/android/resources:r");
+
+ List<String> args = getGeneratingSpawnAction(getResourceApk(resource)).getArguments();
+ assertPrimaryResourceDirs(ImmutableList.of("java/other/resources/res"), args);
+ assertNoEvents();
+ }
+
+ @Test
+ public void testMultipleDependentResourceDirectories_LocalResources()
+ throws Exception {
+ scratch.file("java/android/resources/d1/BUILD",
+ "android_library(name = 'd1',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['d1-res/values/strings.xml'],",
+ " )");
+ scratch.file("java/android/resources/d2/BUILD",
+ "android_library(name = 'd2',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['d2-res/values/strings.xml'],",
+ " )");
+ scratch.file("java/android/resources/BUILD",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['bin-res/values/strings.xml'],",
+ " deps = [",
+ " '//java/android/resources/d1:d1','//java/android/resources/d2:d2'",
+ " ])");
+ ConfiguredTarget resource = getConfiguredTarget("//java/android/resources:r");
+
+ List<String> args = getGeneratingSpawnAction(getResourceApk(resource)).getArguments();
+ assertPrimaryResourceDirs(ImmutableList.of("java/android/resources/bin-res"), args);
+ assertThat(getDirectDependentResourceDirs(args)).containsAllIn(ImmutableList.of(
+ "java/android/resources/d1/d1-res", "java/android/resources/d2/d2-res"));
+ assertNoEvents();
+ }
+
+ // Regression test for b/11924769
+ @Test
+ public void testResourcesInOtherPackage_doubleFilegroup_LocalResources() throws Exception {
+ scratch.file("java/android/resources/BUILD",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = [':fg'],",
+ " )",
+ "filegroup(name = 'fg',",
+ " srcs = ['//java/other/resources:fg'])");
+ scratch.file("java/other/resources/BUILD",
+ "filegroup(name = 'fg',",
+ " srcs = ['res/values/strings.xml'],",
+ ")");
+ ConfiguredTarget resource = getConfiguredTarget("//java/android/resources:r");
+
+ List<String> args = getGeneratingSpawnAction(getResourceApk(resource)).getArguments();
+ assertPrimaryResourceDirs(ImmutableList.of("java/other/resources/res"), args);
+ assertNoEvents();
+ }
+
+ @Test
+ public void testManifestMissingFails_LocalResources() throws Exception {
+ checkError("java/android/resources", "r",
+ "manifest attribute of android_library rule //java/android/resources:r: manifest is "
+ + "required when resource_files or assets are defined.",
+ "filegroup(name = 'b')",
+ "android_library(name = 'r',",
+ " resource_files = [':b'],",
+ " )");
+ }
+
+ @Test
+ public void testResourcesDoesNotMatchDirectoryLayout_BadFile_LocalResources() throws Exception {
+ checkError("java/android/resources", "r",
+ "'java/android/resources/res/somefile.xml' is not in the expected resource directory "
+ + "structure of <resource directory>/{"
+ + Joiner.on(',').join(LocalResourceContainer.Builder.RESOURCE_DIRECTORY_TYPES) + "}",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/somefile.xml', 'r/t/f/m/raw/fold']",
+ " )");
+ }
+
+ @Test
+ public void testResourcesDoesNotMatchDirectoryLayout_BadDirectory_LocalResources()
+ throws Exception {
+ checkError("java/android/resources",
+ "r",
+ "'java/android/resources/res/other/somefile.xml' is not in the expected resource directory "
+ + "structure of <resource directory>/{"
+ + Joiner.on(',').join(LocalResourceContainer.Builder.RESOURCE_DIRECTORY_TYPES) + "}",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/other/somefile.xml', 'r/t/f/m/raw/fold']",
+ " )");
+ }
+
+ @Test
+ public void testResourcesNotUnderCommonDirectoryFails_LocalResources() throws Exception {
+ checkError("java/android/resources", "r",
+ "'java/android/resources/r/t/f/m/raw/fold' (generated by '//java/android/resources:r/t/f/m/"
+ + "raw/fold') is not in the same directory 'res' "
+ + "(derived from java/android/resources/res/raw/speed). "
+ + "All resources must share a common directory",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/raw/speed', 'r/t/f/m/raw/fold']",
+ " )");
+ }
+
+ @Test
+ public void testAssetsAndNoAssetsDirFails_LocalResources() throws Exception {
+ scratch.file("java/android/resources/assets/values/strings.xml",
+ "<resources><string name = 'hello'>Hello Android!</string></resources>");
+ checkError("java/android/resources", "r",
+ "'assets' and 'assets_dir' should be either both empty or both non-empty",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " assets = glob(['assets/**']),",
+ " )");
+ }
+
+ @Test
+ public void testAssetsDirAndNoAssetsFails_LocalResources() throws Exception {
+ checkError("java/cpp/android", "r",
+ "'assets' and 'assets_dir' should be either both empty or both non-empty",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " assets_dir = 'assets',",
+ " )");
+ }
+
+ @Test
+ public void testAssetsNotUnderAssetsDirFails_LocalResources() throws Exception {
+ checkError("java/android/resources", "r",
+ "'java/android/resources/r/t/f/m' (generated by '//java/android/resources:r/t/f/m') "
+ + "is not beneath 'assets'",
+ "android_binary(name = 'r',",
+ " manifest = 'AndroidManifest.xml',",
+ " assets_dir = 'assets',",
+ " assets = ['assets/valuable', 'r/t/f/m']",
+ " )");
+ }
+
+ @Test
+ public void testFileLocation_LocalResources() throws Exception {
+ scratch.file("java/android/resources/BUILD",
+ "android_binary(name = 'r',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/values/strings.xml'],",
+ " )");
+ ConfiguredTarget r = getConfiguredTarget("//java/android/resources:r");
+ assertEquals(getTargetConfiguration().getBinDirectory(RepositoryName.MAIN),
+ getFirstArtifactEndingWith(getFilesToBuild(r), ".apk").getRoot());
+ }
+
+ @Test
+ public void testCustomPackage_LocalResources() throws Exception {
+ scratch.file("a/r/BUILD",
+ "android_binary(name = 'r',",
+ " srcs = ['Foo.java'],",
+ " custom_package = 'com.google.android.bar',",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/values/strings.xml'],",
+ " )");
+ ConfiguredTarget r = getConfiguredTarget("//a/r:r");
+ assertNoEvents();
+ List<String> args = getGeneratingSpawnAction(getResourceApk(r)).getArguments();
+ assertContainsSublist(args, ImmutableList.of("--packageForR", "com.google.android.bar"));
+ }
+
+ @Test
+ public void testCustomJavacopts() throws Exception {
+ scratch.file("java/foo/A.java", "foo");
+ scratch.file("java/foo/BUILD",
+ "android_binary(name = 'a', manifest = 'AndroidManifest.xml', ",
+ " srcs = ['A.java'], javacopts = ['-g:lines,source'])");
+
+
+ Artifact deployJar = getFileConfiguredTarget("//java/foo:a_deploy.jar").getArtifact();
+ Action deployAction = getGeneratingAction(deployJar);
+ JavaCompileAction javacAction = (JavaCompileAction)
+ actionsTestUtil().getActionForArtifactEndingWith(
+ actionsTestUtil().artifactClosureOf(deployAction.getInputs()), "liba.jar");
+
+ assertThat(javacAction.buildCommandLine()).contains("-g:lines,source");
+ }
+
+ @Test
+ public void testAndroidBinaryExportsJavaCompilationArgsProvider() throws Exception {
+
+ scratch.file("java/foo/A.java", "foo");
+ scratch.file("java/foo/BUILD",
+ "android_binary(name = 'a', manifest = 'AndroidManifest.xml', ",
+ " srcs = ['A.java'], javacopts = ['-g:lines,source'])");
+
+ final JavaCompilationArgsProvider provider =
+ getConfiguredTarget("//java/foo:a").getProvider(JavaCompilationArgsProvider.class);
+
+ assertThat(provider).isNotNull();
+ }
+
+ @Test
+ public void testNoApplicationId_LocalResources() throws Exception {
+ scratch.file("java/a/r/BUILD",
+ "android_binary(name = 'r',",
+ " srcs = ['Foo.java'],",
+ " manifest = 'AndroidManifest.xml',",
+ " resource_files = ['res/values/strings.xml'],",
+ " )");
+ ConfiguredTarget r = getConfiguredTarget("//java/a/r:r");
+ assertNoEvents();
+ List<String> args = getGeneratingSpawnAction(getResourceApk(r)).getArguments();
+ Truth.assertThat(args).doesNotContain("--applicationId");
+ }
+
+ @Test
+ public void testDisallowPrecompiledJars() throws Exception {
+ checkError("java/precompiled", "binary",
+ // messages:
+ "does not produce any android_binary srcs files (expected .java or .srcjar)",
+ // build file:
+ "android_binary(name = 'binary',",
+ " manifest='AndroidManifest.xml',",
+ " srcs = [':jar'])",
+ "filegroup(name = 'jar',",
+ " srcs = ['lib.jar'])");
+ }
+
+ @Test
+ public void testApplyProguardMapping() throws Exception {
+ scratch.file(
+ "java/com/google/android/BUILD",
+ "android_binary(",
+ " name = 'foo',",
+ " srcs = ['foo.java'],",
+ " proguard_apply_mapping = 'proguard.map',",
+ " proguard_specs = ['foo.pro'],",
+ " manifest = 'AndroidManifest.xml',",
+ ")");
+
+ ConfiguredTarget ct = getConfiguredTarget("//java/com/google/android:foo");
+ SpawnAction proguardAction =
+ getGeneratingSpawnAction(artifactByPath(getFilesToBuild(ct), "_proguard.jar"));
+ MoreAsserts.assertContainsSublist(
+ proguardAction.getArguments(),
+ "-applymapping", "java/com/google/android/proguard.map");
+ }
+
+ @Test
+ public void testApplyProguardMappingWithNoSpec() throws Exception {
+ checkError(
+ "java/com/google/android", "foo",
+ // messages:
+ "'proguard_apply_mapping' can only be used when 'proguard_specs' is also set",
+ // build file:
+ "android_binary(",
+ " name = 'foo',",
+ " srcs = ['foo.java'],",
+ " proguard_apply_mapping = 'proguard.map',",
+ " manifest = 'AndroidManifest.xml',",
+ ")");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBuildViewTestCase.java
new file mode 100644
index 0000000000..fbb900212f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBuildViewTestCase.java
@@ -0,0 +1,182 @@
+// 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.lib.rules.android;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass.AndroidDeployInfo;
+import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider;
+import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider.OutputJar;
+import com.google.devtools.build.lib.util.Preconditions;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Common methods shared between Android related {@link BuildViewTestCase}s. */
+public abstract class AndroidBuildViewTestCase extends BuildViewTestCase {
+ protected Iterable<Artifact> getNativeLibrariesInApk(ConfiguredTarget target) {
+ Action unsignedApkAction = actionsTestUtil().getActionForArtifactEndingWith(
+ getFilesToBuild(target), "_unsigned.apk");
+ ImmutableList.Builder<Artifact> result = ImmutableList.builder();
+ for (Artifact output : unsignedApkAction.getInputs()) {
+ if (!output.getExecPathString().endsWith(".so")) {
+ continue;
+ }
+
+ result.add(output);
+ }
+
+ return result.build();
+ }
+
+ protected Label getGeneratingLabelForArtifact(Artifact artifact) {
+ Action generatingAction = getGeneratingAction(artifact);
+ return generatingAction != null
+ ? getGeneratingAction(artifact).getOwner().getLabel()
+ : null;
+ }
+
+ protected void assertNativeLibrariesCopiedNotLinked(
+ ConfiguredTarget target, String... expectedLibNames) {
+ Iterable<Artifact> copiedLibs = getNativeLibrariesInApk(target);
+ for (Artifact copiedLib : copiedLibs) {
+ assertWithMessage("Native libraries were linked to produce " + copiedLib)
+ .that(getGeneratingLabelForArtifact(copiedLib))
+ .isNotEqualTo(target.getLabel());
+ }
+ assertThat(artifactsToStrings(copiedLibs))
+ .containsAllIn(ImmutableSet.copyOf(Arrays.asList(expectedLibNames)));
+ }
+
+ protected String flagValue(String flag, List<String> args) {
+ assertThat(args).contains(flag);
+ return args.get(args.indexOf(flag) + 1);
+ }
+
+ protected Artifact getResourceApk(ConfiguredTarget target) {
+ Action unsignedApkAction = getGeneratingAction(
+ getFirstArtifactEndingWith(getFilesToBuild(target), "_unsigned.apk"));
+ Artifact binaryApk =
+ getFirstArtifactEndingWith(unsignedApkAction.getInputs(), ".apk");
+ Artifact resourceApk =
+ getFirstArtifactEndingWith(unsignedApkAction.getInputs(), ".ap_");
+ assertTrue((binaryApk == null && resourceApk != null)
+ || (binaryApk != null && resourceApk == null));
+ return binaryApk == null ? resourceApk : binaryApk;
+ }
+
+ protected void assertProguardUsed(ConfiguredTarget binary) {
+ assertNotNull("proguard.jar is not in the rule output",
+ actionsTestUtil().getActionForArtifactEndingWith(
+ getFilesToBuild(binary), "_proguard.jar"));
+ }
+
+ protected List<String> resourceArguments(ResourceContainer resource) {
+ return resourceGeneratingAction(resource).getArguments();
+ }
+
+ protected SpawnAction resourceGeneratingAction(ResourceContainer resource) {
+ return getGeneratingSpawnAction(resource.getApk());
+ }
+
+ protected static ResourceContainer getResourceContainer(ConfiguredTarget target) {
+ return getResourceContainer(target, /* transitive= */ false);
+ }
+
+ protected static ResourceContainer getResourceContainer(
+ ConfiguredTarget target, boolean transitive) {
+
+ Preconditions.checkNotNull(target);
+ final AndroidResourcesProvider provider = target.getProvider(AndroidResourcesProvider.class);
+ assertThat(provider).named("No android resources exported from the target.").isNotNull();
+ return getOnlyElement(
+ transitive
+ ? provider.getTransitiveAndroidResources()
+ : provider.getDirectAndroidResources());
+ }
+
+ protected ActionAnalysisMetadata getResourceClassJarAction(final ConfiguredTarget target) {
+ JavaRuleOutputJarsProvider jarProvider = target.getProvider(JavaRuleOutputJarsProvider.class);
+ assertThat(jarProvider).isNotNull();
+ return getGeneratingAction(
+ Iterables.find(jarProvider.getOutputJars(), new Predicate<OutputJar>() {
+ @Override
+ public boolean apply(@Nullable OutputJar outputJar) {
+ assertThat(outputJar).isNotNull();
+ assertThat(outputJar.getClassJar()).isNotNull();
+ return outputJar.getClassJar().getFilename().equals(
+ target.getTarget().getName() + "_resources.jar");
+ }
+ }
+ ).getClassJar()
+ );
+ }
+
+ // android resources related tests
+ protected void assertPrimaryResourceDirs(List<String> expectedPaths, List<String> actualArgs) {
+ assertThat(actualArgs).contains("--primaryData");
+
+ String actualFlagValue = actualArgs.get(actualArgs.indexOf("--primaryData") + 1);
+ assertThat(actualFlagValue).matches("[^:]*:[^:]*:[^:]*");
+ ImmutableList.Builder<String> actualPaths = ImmutableList.builder();
+ actualPaths.add(actualFlagValue.split(":")[0].split("#"));
+ assertThat(actualPaths.build()).containsAllIn(expectedPaths);
+ }
+
+ protected List<String> getDirectDependentResourceDirs(List<String> actualArgs) {
+ assertThat(actualArgs).contains("--directData");
+ String actualFlagValue = actualArgs.get(actualArgs.indexOf("--directData") + 1);
+ return getDependentResourceDirs(actualFlagValue);
+ }
+
+ protected List<String> getDependentResourceDirs(String actualFlagValue) {
+ ImmutableList.Builder<String> actualPaths = ImmutableList.builder();
+ for (String resourceDependency : actualFlagValue.split(",")) {
+ assertThat(actualFlagValue).matches("[^:]*:[^:]*:[^:]*:.*");
+ actualPaths.add(resourceDependency.split(":")[0].split("#"));
+ }
+ return actualPaths.build();
+ }
+
+ protected String execPathEndingWith(Iterable<Artifact> inputs, String suffix) {
+ return getFirstArtifactEndingWith(inputs, suffix).getExecPathString();
+ }
+
+ @Nullable
+ protected AndroidDeployInfo getAndroidDeployInfo(Artifact artifact) throws IOException {
+ Action generatingAction = getGeneratingAction(artifact);
+ if (generatingAction instanceof AndroidDeployInfoAction) {
+ AndroidDeployInfoAction writeAction = (AndroidDeployInfoAction) generatingAction;
+ return writeAction.getDeployInfo();
+ }
+ return null;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/BUILD b/src/test/java/com/google/devtools/build/lib/rules/android/BUILD
index 2cb61f507e..467294cf4d 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/android/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/BUILD
@@ -48,3 +48,32 @@ java_test(
"//third_party:truth",
],
)
+
+java_test(
+ name = "AndroidBinaryTest",
+ srcs = [
+ "AndroidBinaryTest.java",
+ "AndroidBuildViewTestCase.java",
+ ],
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:android-rules",
+ "//src/main/java/com/google/devtools/build/lib:build-base",
+ "//src/main/java/com/google/devtools/build/lib:collect",
+ "//src/main/java/com/google/devtools/build/lib:java-compilation",
+ "//src/main/java/com/google/devtools/build/lib:packages-internal",
+ "//src/main/java/com/google/devtools/build/lib:preconditions",
+ "//src/main/java/com/google/devtools/build/lib:syntax",
+ "//src/main/java/com/google/devtools/build/lib:util",
+ "//src/main/java/com/google/devtools/build/lib:vfs",
+ "//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/lib/rules/cpp",
+ "//src/main/protobuf:android_deploy_info_java_proto",
+ "//src/test/java/com/google/devtools/build/lib:actions_testutil",
+ "//src/test/java/com/google/devtools/build/lib:analysis_testutil",
+ "//src/test/java/com/google/devtools/build/lib:testutil",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)