diff options
author | 2017-03-17 21:27:29 +0000 | |
---|---|---|
committer | 2017-03-20 11:42:35 +0000 | |
commit | 42d313fa526398b76aa122106adf1518a784e12f (patch) | |
tree | 713d5ea136c9d805f75ca1e6363711e7d094d237 /src/test/java/com/google/devtools | |
parent | ca8c1e39f814cf4059fe83b6479833d30ba62fde (diff) |
Open source some tests for android_binary.
--
PiperOrigin-RevId: 150484382
MOS_MIGRATED_REVID=150484382
Diffstat (limited to 'src/test/java/com/google/devtools')
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", + ], +) |