| // 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.ImmutableList.toImmutableList; |
| import static com.google.common.collect.ImmutableMap.toImmutableMap; |
| 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 com.google.devtools.build.lib.actions.util.ActionsTestUtil.hasInput; |
| import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.prettyArtifactNames; |
| import static com.google.devtools.build.lib.rules.java.JavaCompileActionTestHelper.getJavacArguments; |
| import static com.google.devtools.build.lib.rules.java.JavaCompileActionTestHelper.getProcessorpath; |
| import static org.junit.Assert.assertThrows; |
| |
| import com.google.common.base.Ascii; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| 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.FailAction; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.analysis.AnalysisResult; |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.FileProvider; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.OutputGroupInfo; |
| import com.google.devtools.build.lib.analysis.RequiredConfigFragmentsProvider; |
| import com.google.devtools.build.lib.analysis.actions.FileWriteAction; |
| import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction; |
| import com.google.devtools.build.lib.analysis.actions.SpawnAction; |
| import com.google.devtools.build.lib.analysis.util.DummyTestFragment; |
| import com.google.devtools.build.lib.cmdline.Label; |
| 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.packages.FileTarget; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.util.BazelMockAndroidSupport; |
| 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.cpp.CppLinkAction; |
| 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.JavaInfo; |
| import com.google.devtools.build.lib.rules.java.JavaSemantics; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; |
| import com.google.devtools.build.lib.testutil.MoreAsserts; |
| import com.google.devtools.build.lib.testutil.TestConstants; |
| import com.google.devtools.build.lib.testutil.TestRuleClassProvider; |
| 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.List; |
| import java.util.Set; |
| import java.util.function.Function; |
| 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 { |
| |
| /** Allow use of --foo as a dummy flag */ |
| @Override |
| protected ConfiguredRuleClassProvider createRuleClassProvider() { |
| ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); |
| TestRuleClassProvider.addStandardRules(builder); |
| builder.addConfigurationFragment(DummyTestFragment.class); |
| return builder.build(); |
| } |
| |
| @Override |
| protected String defaultPlatformFlag() { |
| return String.format( |
| "--android_platforms=%sandroid:armeabi-v7a", TestConstants.CONSTRAINTS_PACKAGE_ROOT); |
| } |
| |
| @Before |
| public void setupCcToolchain() throws Exception { |
| getAnalysisMock().ccSupport().setupCcToolchainConfigForCpu(mockToolsConfig, "armeabi-v7a"); |
| } |
| |
| @Before |
| public void setup() 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 {};"); |
| scratch.file( |
| "java/android/platforms/BUILD", |
| "platform(", |
| " name = 'x86',", |
| " parents = ['" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + "android:armeabi-v7a'],", |
| " constraint_values = ['" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + "cpu:x86_32'],", |
| ")", |
| "platform(", |
| " name = 'armeabi-v7a',", |
| " parents = ['" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + "android:armeabi-v7a'],", |
| " constraint_values = ['" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + "cpu:armv7'],", |
| ")"); |
| scratch.file( |
| "/workspace/platform_mappings", |
| "platforms:", |
| " //java/android/platforms:armeabi-v7a", |
| " --cpu=armeabi-v7a", |
| " //java/android/platforms:x86", |
| " --cpu=x86", |
| "flags:", |
| " --cpu=armeabi-v7a", |
| " //java/android/platforms:armv7", |
| " --cpu=x86", |
| " //java/android/platforms:x86"); |
| setBuildLanguageOptions("--experimental_google_legacy_api"); |
| } |
| |
| @Test |
| public void testFatApk_androidPlatformsFlag() throws Exception { |
| BazelMockAndroidSupport.setupNdk(mockToolsConfig); |
| scratch.file( |
| "java/foo/BUILD", |
| "config_setting(", |
| " name = 'is_x86',", |
| " constraint_values = ['" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + "cpu:x86_32'],", |
| ")", |
| "config_setting(", |
| " name = 'is_arm',", |
| " constraint_values = ['" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + "cpu:armv7'],", |
| ")", |
| "android_library(", |
| " name = 'lib',", |
| " srcs = ['MyLibrary.java'])", |
| "cc_library(", |
| " name = 'arm-native-lib',", |
| " srcs = ['armnative.so'])", |
| "cc_library(", |
| " name = 'x86-native-lib',", |
| " srcs = ['x86native.so'])", |
| "cc_library(", |
| " name = 'native-lib',", |
| " deps = select({", |
| " ':is_x86': [':x86-native-lib'],", |
| " ':is_arm': [':arm-native-lib'],", |
| " '//conditions:default': [],", |
| " }))", |
| "android_binary(", |
| " name = 'abin',", |
| " srcs = ['a.java'],", |
| " proguard_specs = [],", |
| " deps = ['lib', 'native-lib'],", |
| " manifest = 'AndroidManifest.xml',", |
| ")"); |
| useConfiguration( |
| "--android_platforms=//java/android/platforms:x86,//java/android/platforms:armeabi-v7a", |
| "--dynamic_mode=off"); |
| ConfiguredTarget apk = getConfiguredTarget("//java/foo:abin"); |
| List<String> args = getGeneratingSpawnActionArgs(getCompressedUnsignedApk(apk)); |
| assertContainsSublist( |
| args, |
| ImmutableList.of("--resources", "java/foo/armnative.so:lib/armeabi-v7a/armnative.so")); |
| assertContainsSublist( |
| args, ImmutableList.of("--resources", "java/foo/x86native.so:lib/x86/x86native.so")); |
| } |
| |
| @Test |
| public void testAssetsInExternalRepository() throws Exception { |
| FileSystemUtils.appendIsoLatin1( |
| scratch.resolve("WORKSPACE"), "local_repository(name='r', path='/r')"); |
| scratch.file("/r/WORKSPACE"); |
| 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'],", |
| " manifest = 'AndroidManifest.xml',", |
| " main_dex_proguard_specs = ['foo'])"); |
| } |
| |
| @Test |
| public void testAndroidManifestWithCustomName() throws Exception { |
| scratchConfiguredTarget( |
| "java/a", |
| "a", |
| "android_binary(", |
| " name = 'a',", |
| " srcs = ['A.java'],", |
| " manifest = 'SomeOtherAndroidManifest.xml')"); |
| assertNoEvents(); |
| } |
| |
| @Test |
| public void testMainDexProguardSpecs() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| 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( |
| ImmutableList.of(getCompressedUnsignedApk(ct)), |
| ".apk", |
| ".dex.zip", |
| ".dex.zip", |
| "main_dex_list.txt", |
| "_intermediate.jar"); |
| List<String> args = getGeneratingSpawnActionArgs(intermediateJar); |
| MoreAsserts.assertContainsSublist(args, "-include", "java/a/a.spec"); |
| assertThat(Joiner.on(" ").join(args)).doesNotContain("mainDexClasses.rules"); |
| } |
| |
| @Test |
| public void testLegacyMainDexListGenerator() throws Exception { |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| ) |
| """); |
| scratch.file( |
| "tools/fake/BUILD", |
| """ |
| cc_binary( |
| name = "generate_main_dex_list", |
| srcs = ["main.cc"], |
| ) |
| """); |
| useConfiguration("--legacy_main_dex_list_generator=//tools/fake:generate_main_dex_list"); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/a:a"); |
| Artifact mainDexList = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "main_dex_list.txt"); |
| List<String> args = getGeneratingSpawnActionArgs(mainDexList); |
| NestedSet<Artifact> mainDexInputs = getGeneratingAction(mainDexList).getInputs(); |
| |
| MoreAsserts.assertContainsSublist(args, "--lib", getAndroidJarPath()); |
| MoreAsserts.assertContainsSublist(args, "--main-dex-rules", getMainDexClassesPath()); |
| |
| assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains("generate_main_dex_list"); |
| assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains("a_deploy.jar"); |
| assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains(getAndroidJarFilename()); |
| assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)) |
| .contains(getMainDexClassesFilename()); |
| assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)) |
| .contains("main_dex_a_proguard.cfg"); |
| assertThat(getFirstArtifactEndingWith(mainDexInputs, "main_dex_list_creator")).isNull(); |
| } |
| |
| @Test |
| public void testOptimizedDexingWithLegacyMultidex() throws Exception { |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| proguard_generate_mapping = True, |
| proguard_specs = ["specs.pgcfg"], |
| ) |
| |
| android_binary( |
| name = "b", |
| srcs = ["B.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| proguard_generate_mapping = False, |
| proguard_specs = ["specs.pgcfg"], |
| shrink_resources = 0, |
| ) |
| """); |
| scratch.file( |
| "tools/fake/BUILD", |
| """ |
| cc_binary( |
| name = "optimizing_dexer", |
| srcs = ["main.cc"], |
| ) |
| """); |
| useConfiguration("--optimizing_dexer=//tools/fake:optimizing_dexer"); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/a:a"); |
| Artifact proguardMap = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "a_proguard.map"); |
| |
| Artifact dexedZip = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "/_dx/a/classes.dex.zip"); |
| SpawnAction dexer = getGeneratingSpawnAction(dexedZip); |
| assertThat(dexer).isEqualTo(getGeneratingSpawnAction(proguardMap)); |
| List<String> dexerArgs = dexer.getArguments(); |
| |
| assertThat(dexerArgs).contains("--release"); |
| assertThat(dexerArgs).contains("--no-desugaring"); |
| |
| Artifact paramFile = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "/_dx/a/classes.dex.zip-2.params"); |
| assertThat(dexerArgs).contains("@" + paramFile.getExecPath().getPathString()); |
| |
| ParameterFileWriteAction paramFileAction = |
| (ParameterFileWriteAction) getGeneratingAction(paramFile); |
| ImmutableList<String> paramFileArgs = ImmutableList.copyOf(paramFileAction.getArguments()); |
| MoreAsserts.assertContainsSublist(paramFileArgs, "--lib", getAndroidJarPath()); |
| Artifact mainDexList = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "/_dx/a/main_dex_list.txt"); |
| MoreAsserts.assertContainsSublist( |
| paramFileArgs, "--main-dex-list", mainDexList.getExecPath().getPathString()); |
| Artifact proguardMapInput = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "/proguard/a/legacy_a_pre_dexing.map"); |
| MoreAsserts.assertContainsSublist( |
| paramFileArgs, "--pg-map", proguardMapInput.getExecPath().getPathString()); |
| |
| List<String> shardArgs = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "_dx/a/shard1.zip")); |
| assertThat(shardArgs).contains("--intermediate"); |
| assertThat(shardArgs).doesNotContain("--dex"); |
| |
| // We are only testing that :b builds successfully here as a regression test for b/311406385. |
| ConfiguredTarget unusedBinaryB = getConfiguredTarget("//java/a:b"); |
| } |
| |
| @Test |
| public void testOptimizedDexingWithNativeMultidex() throws Exception { |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| proguard_generate_mapping = True, |
| proguard_specs = ["specs.pgcfg"], |
| ) |
| """); |
| scratch.file( |
| "tools/fake/BUILD", |
| """ |
| cc_binary( |
| name = "optimizing_dexer", |
| srcs = ["main.cc"], |
| ) |
| """); |
| useConfiguration("--optimizing_dexer=//tools/fake:optimizing_dexer"); |
| |
| // The behavior should match legacy multidex except for the main dex list and min sdk, so we |
| // only check those flags. |
| ConfiguredTarget binary = getConfiguredTarget("//java/a:a"); |
| Artifact dexedZip = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "/_dx/a/classes.dex.zip"); |
| SpawnAction dexer = getGeneratingSpawnAction(dexedZip); |
| List<String> dexerArgs = dexer.getArguments(); |
| |
| Artifact paramFile = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "/_dx/a/classes.dex.zip-2.params"); |
| assertThat(dexerArgs).contains("@" + paramFile.getExecPath().getPathString()); |
| |
| ParameterFileWriteAction paramFileAction = |
| (ParameterFileWriteAction) getGeneratingAction(paramFile); |
| ImmutableList<String> paramFileArgs = ImmutableList.copyOf(paramFileAction.getArguments()); |
| |
| assertThat(dexerArgs).doesNotContain("--main-dex-list"); |
| assertThat(paramFileArgs).doesNotContain("--main-dex-list"); |
| MoreAsserts.assertContainsSublist(paramFileArgs, "--min-api", "21"); |
| |
| List<String> shardArgs = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "_dx/a/shard1.zip")); |
| assertThat(shardArgs).contains("--intermediate"); |
| assertThat(shardArgs).doesNotContain("--dex"); |
| } |
| |
| @Test |
| public void testMainDexListObfuscation() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| scratch.file("/java/a/list.txt"); |
| ConfiguredTarget ct = |
| scratchConfiguredTarget( |
| "java/a", |
| "a", |
| "android_binary(", |
| " name = 'a',", |
| " srcs = ['A.java'],", |
| " manifest = 'AndroidManifest.xml',", |
| " multidex = 'manual_main_dex',", |
| " proguard_generate_mapping = 1,", |
| " main_dex_list = 'list.txt')"); |
| |
| Artifact obfuscatedDexList = |
| artifactByPath( |
| ImmutableList.of(getCompressedUnsignedApk(ct)), |
| ".apk", |
| ".dex.zip", |
| ".dex.zip", |
| "main_dex_list_obfuscated.txt"); |
| List<String> args = getGeneratingSpawnActionArgs(obfuscatedDexList); |
| assertThat(args.get(0)).contains("dex_list_obfuscator"); |
| MoreAsserts.assertContainsSublist(args, "--input", "java/a/list.txt"); |
| } |
| |
| @Test |
| public void testNonLegacyNativeDepsDoesNotPolluteDexSharding() throws Exception { |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| dex_shards = 2, |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| deps = [":cc"], |
| ) |
| |
| cc_library( |
| name = "cc", |
| srcs = ["cc.cc"], |
| ) |
| """); |
| |
| Artifact jarShard = |
| artifactByPath( |
| ImmutableList.of(getCompressedUnsignedApk(getConfiguredTarget("//java/a:a"))), |
| ".apk", |
| "classes.dex.zip", |
| "shard1.dex.zip", |
| "shard1.jar.dex.zip"); |
| NestedSet<Artifact> shardInputs = getGeneratingAction(jarShard).getInputs(); |
| assertThat(getFirstArtifactEndingWith(shardInputs, ".txt")).isNull(); |
| } |
| |
| @Test |
| public void testCcInfoDeps() throws Exception { |
| scratch.file( |
| "java/a/cc_info.bzl", |
| """ |
| def _impl(ctx): |
| cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo] |
| feature_configuration = cc_common.configure_features( |
| ctx = ctx, |
| cc_toolchain = cc_toolchain, |
| requested_features = ctx.features, |
| unsupported_features = ctx.disabled_features, |
| ) |
| library_to_link = cc_common.create_library_to_link( |
| actions = ctx.actions, |
| feature_configuration = feature_configuration, |
| cc_toolchain = cc_toolchain, |
| static_library = ctx.file.static_library, |
| ) |
| linker_input = cc_common.create_linker_input( |
| libraries = depset([library_to_link]), |
| user_link_flags = depset(ctx.attr.user_link_flags), |
| owner = ctx.label, |
| ) |
| linking_context = cc_common.create_linking_context( |
| linker_inputs = depset([linker_input]), |
| ) |
| return [CcInfo(linking_context = linking_context)] |
| |
| cc_info = rule( |
| implementation = _impl, |
| fragments = ["cpp"], |
| attrs = { |
| "srcs": attr.label_list(allow_files = True), |
| "user_link_flags": attr.string_list(), |
| "static_library": attr.label(allow_single_file = True), |
| "_cc_toolchain": attr.label(default = Label("//java/a:alias")), |
| }, |
| ) |
| """); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| load("//java/a:cc_info.bzl", "cc_info") |
| |
| cc_toolchain_alias(name = "alias") |
| |
| android_binary( |
| name = "a", |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| deps = [":cc_info"], |
| ) |
| |
| cc_info( |
| name = "cc_info", |
| static_library = "cc_info.a", |
| user_link_flags = [ |
| "-first_flag", |
| "-second_flag", |
| ], |
| ) |
| """); |
| |
| ConfiguredTarget app = getConfiguredTarget("//java/a:a"); |
| assertNoEvents(); |
| |
| Artifact copiedLib = getOnlyElement(getNativeLibrariesInApk(app)); |
| Artifact linkedLib = getGeneratingAction(copiedLib).getInputs().getSingleton(); |
| CppLinkAction action = (CppLinkAction) getGeneratingAction(linkedLib); |
| |
| assertThat(action.getArguments()).containsAtLeast("-first_flag", "-second_flag"); |
| |
| NestedSet<Artifact> linkInputs = action.getInputs(); |
| assertThat(ActionsTestUtil.baseArtifactNames(linkInputs)).contains("cc_info.a"); |
| } |
| |
| @Test |
| public void testCcInfoDepsViaAndroidLibrary() throws Exception { |
| scratch.file( |
| "java/a/cc_info.bzl", |
| """ |
| def _impl(ctx): |
| cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo] |
| feature_configuration = cc_common.configure_features( |
| ctx = ctx, |
| cc_toolchain = cc_toolchain, |
| requested_features = ctx.features, |
| unsupported_features = ctx.disabled_features, |
| ) |
| library_to_link = cc_common.create_library_to_link( |
| actions = ctx.actions, |
| feature_configuration = feature_configuration, |
| cc_toolchain = cc_toolchain, |
| static_library = ctx.file.static_library, |
| ) |
| linker_input = cc_common.create_linker_input( |
| libraries = depset([library_to_link]), |
| user_link_flags = depset(ctx.attr.user_link_flags), |
| owner = ctx.label, |
| ) |
| linking_context = cc_common.create_linking_context( |
| linker_inputs = depset([linker_input]), |
| ) |
| return [CcInfo(linking_context = linking_context)] |
| |
| cc_info = rule( |
| implementation = _impl, |
| fragments = ["cpp"], |
| attrs = { |
| "srcs": attr.label_list(allow_files = True), |
| "user_link_flags": attr.string_list(), |
| "static_library": attr.label(allow_single_file = True), |
| "_cc_toolchain": attr.label(default = Label("//java/a:alias")), |
| }, |
| ) |
| """); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| load("//java/a:cc_info.bzl", "cc_info") |
| |
| cc_toolchain_alias(name = "alias") |
| |
| android_binary( |
| name = "a", |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| deps = [":liba"], |
| ) |
| |
| android_library( |
| name = "liba", |
| srcs = ["a.java"], |
| deps = [":cc_info"], |
| ) |
| |
| cc_info( |
| name = "cc_info", |
| static_library = "cc_info.a", |
| user_link_flags = [ |
| "-first_flag", |
| "-second_flag", |
| ], |
| ) |
| """); |
| |
| ConfiguredTarget app = getConfiguredTarget("//java/a:a"); |
| |
| Artifact copiedLib = getOnlyElement(getNativeLibrariesInApk(app)); |
| Artifact linkedLib = getGeneratingAction(copiedLib).getInputs().getSingleton(); |
| CppLinkAction action = (CppLinkAction) getGeneratingAction(linkedLib); |
| |
| assertThat(action.getArguments()).containsAtLeast("-first_flag", "-second_flag"); |
| |
| NestedSet<Artifact> linkInputs = action.getInputs(); |
| assertThat(ActionsTestUtil.baseArtifactNames(linkInputs)).contains("cc_info.a"); |
| } |
| |
| @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", |
| srcs = ["ToBeProcessed.java"], |
| manifest = "AndroidManifest.xml", |
| plugins = [":plugin"], |
| ) |
| """); |
| ConfiguredTarget target = getConfiguredTarget("//java/test:to_be_processed"); |
| JavaCompileAction javacAction = |
| (JavaCompileAction) getGeneratingAction(getBinArtifact("libto_be_processed.jar", target)); |
| |
| assertThat(getProcessorNames(javacAction)).contains("com.google.process.stuff"); |
| assertThat(getProcessorNames(javacAction)).hasSize(1); |
| |
| assertThat( |
| ActionsTestUtil.baseArtifactNames( |
| getInputs(javacAction, getProcessorpath(javacAction)))) |
| .containsExactly("libplugin.jar", "libplugin_dep.jar"); |
| assertThat( |
| actionsTestUtil() |
| .predecessorClosureOf(getFilesToBuild(target), JavaSemantics.JAVA_SOURCE)) |
| .isEqualTo("ToBeProcessed.java AnnotationProcessor.java ProcessorDep.java"); |
| } |
| |
| // 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", |
| srcs = ["ToBeProcessed.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| |
| useConfiguration("--plugin=//java/test:plugin"); |
| ConfiguredTarget target = getConfiguredTarget("//java/test:to_be_processed"); |
| JavaCompileAction javacAction = |
| (JavaCompileAction) getGeneratingAction(getBinArtifact("libto_be_processed.jar", target)); |
| |
| assertThat(getProcessorNames(javacAction)).contains("com.google.process.stuff"); |
| assertThat(getProcessorNames(javacAction)).hasSize(1); |
| assertThat( |
| ActionsTestUtil.baseArtifactNames( |
| getInputs(javacAction, getProcessorpath(javacAction)))) |
| .containsExactly("libplugin.jar", "libplugin_dep.jar"); |
| assertThat( |
| actionsTestUtil() |
| .predecessorClosureOf(getFilesToBuild(target), JavaSemantics.JAVA_SOURCE)) |
| .isEqualTo("ToBeProcessed.java AnnotationProcessor.java ProcessorDep.java"); |
| } |
| |
| @Test |
| public void testInvalidPlugin() throws Exception { |
| checkError( |
| "java/test", |
| "lib", |
| // error: |
| getErrorMsgMandatoryProviderMissing("//java/test:not_a_plugin", "JavaPluginInfo"), |
| // 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", |
| manifest = "AndroidManifest.xml", |
| deps = [ |
| ":cc1", |
| ":cc2", |
| ], |
| ) |
| """); |
| 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"); |
| } |
| |
| /** |
| * Tests that --experimental_check_desugar_deps causes the relevant flags to be set on desugaring |
| * and singlejar actions, and makes sure the deploy jar is built even when just building an APK. |
| */ |
| @Test |
| public void testSimpleBinary_checkDesugarDepsAlwaysHappens() throws Exception { |
| useConfiguration("--experimental_check_desugar_deps"); |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| assertNoEvents(); |
| |
| // 1. Find app's deploy jar and make sure checking flags are set for it and its inputs |
| SpawnAction singlejar = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith(getFilesToBuild(binary), "/app_deploy.jar"); |
| assertThat(getGeneratingSpawnActionArgs(singlejar.getPrimaryOutput())) |
| .contains("--check_desugar_deps"); |
| |
| SpawnAction desugar = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith(singlejar.getInputs(), "/libapp.jar_desugared.jar"); |
| assertThat(desugar).isNotNull(); |
| assertThat(getGeneratingSpawnActionArgs(desugar.getPrimaryOutput())) |
| .contains("--emit_dependency_metadata_as_needed"); |
| |
| // 2. Make sure all APK outputs depend on the deploy Jar. |
| int found = 0; |
| for (Artifact built : getFilesToBuild(binary).toList()) { |
| if (built.getExtension().equals("apk")) { |
| // If this assertion breaks then APK artifacts have stopped depending on deploy jars. |
| // If that's desired then we'll need to make sure dependency checking is done in another |
| // action that APK artifacts depend on, in addition to the check that happens when building |
| // deploy.jars, which we assert above. |
| assertWithMessage("%s dependency on deploy.jar", built.getFilename()) |
| .that(actionsTestUtil().artifactClosureOf(built)) |
| .contains(singlejar.getPrimaryOutput()); |
| ++found; |
| } |
| } |
| assertThat(found).isEqualTo(2 /* signed and unsigned apks */); |
| } |
| |
| @Test |
| public void testSimpleBinary_dexNoMinSdkVersion() throws Exception { |
| scratch.overwriteFile( |
| "java/android/BUILD", |
| """ |
| android_binary( |
| name = "app", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| resource_files = glob(["res/**"]), |
| ) |
| """); |
| useConfiguration("--experimental_desugar_java8_libs"); |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| |
| List<String> args = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "/libapp.jar.dex.zip")); |
| assertThat(args).doesNotContain("--min_sdk_version"); |
| } |
| |
| @Test |
| public void testSimpleBinary_dexMinSdkVersion() throws Exception { |
| scratch.overwriteFile( |
| "java/android/BUILD", |
| """ |
| android_binary( |
| name = "app", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| min_sdk_version = 28, |
| multidex = "legacy", |
| resource_files = glob(["res/**"]), |
| ) |
| """); |
| useConfiguration("--experimental_desugar_java8_libs"); |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| |
| List<String> args = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "/libapp.jar.dex.zip")); |
| assertThat(args).contains("--min_sdk_version"); |
| assertThat(args).contains("28"); |
| } |
| |
| @Test |
| public void testSimpleBinary_desugarNoMinSdkVersion() throws Exception { |
| scratch.overwriteFile( |
| "java/android/BUILD", |
| """ |
| android_binary( |
| name = "app", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| resource_files = glob(["res/**"]), |
| ) |
| """); |
| useConfiguration("--experimental_desugar_java8_libs"); |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| |
| List<String> args = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "/libapp.jar_desugared.jar")); |
| assertThat(args).doesNotContain("--min_sdk_version"); |
| } |
| |
| @Test |
| public void testSimpleBinary_desugarMinSdkVersion() throws Exception { |
| scratch.overwriteFile( |
| "java/android/BUILD", |
| """ |
| android_binary( |
| name = "app", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| min_sdk_version = 28, |
| multidex = "legacy", |
| resource_files = glob(["res/**"]), |
| ) |
| """); |
| useConfiguration("--experimental_desugar_java8_libs"); |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| |
| List<String> args = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "/libapp.jar_desugared.jar")); |
| assertThat(args).contains("--min_sdk_version"); |
| assertThat(args).contains("28"); |
| } |
| |
| @Test |
| public void testSimpleAndroidBinary_multipleMinSdkVersionOnSameLibrary() throws Exception { |
| scratch.overwriteFile( |
| "java/android/BUILD", |
| """ |
| android_binary( |
| name = "app1", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = glob(["res/**"]), |
| deps = ["lib"], |
| ) |
| |
| android_binary( |
| name = "app2", |
| srcs = ["B.java"], |
| manifest = "AndroidManifest.xml", |
| min_sdk_version = 28, |
| resource_files = glob(["res/**"]), |
| deps = ["lib"], |
| ) |
| |
| android_library( |
| name = "lib", |
| srcs = ["C.java"], |
| ) |
| """); |
| AnalysisResult result = |
| update( |
| ImmutableList.of("//java/android:app1", "//java/android:app2"), |
| /* keepGoing= */ true, |
| /* loadingPhaseThreads= */ 1, |
| /* doAnalysis= */ true, |
| eventBus); |
| |
| ImmutableMap<String, ConfiguredTarget> targets = |
| result.getTargetsToBuild().stream() |
| .collect(toImmutableMap(ct -> ct.getLabel().toString(), Function.identity())); |
| |
| ConfiguredTarget app1 = targets.get("//java/android:app1"); |
| ConfiguredTarget app2 = targets.get("//java/android:app2"); |
| |
| // The "lib" target is built twice: once with the default minsdk from app1, and again with |
| // min_sdk_version = 28 from app2. |
| |
| List<String> libDesugarNoMinSdkArgs = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(app1)), |
| "/liblib.jar_desugared.jar")); |
| assertThat(libDesugarNoMinSdkArgs).doesNotContain("--min_sdk_version"); |
| |
| List<String> libDexNoMinSdkArgs = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(app1)), "/liblib.jar.dex.zip")); |
| assertThat(libDexNoMinSdkArgs).doesNotContain("--min_sdk_version"); |
| |
| List<String> libDesugarWithMinSdkArgs = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(app2)), |
| "/liblib.jar_minsdk=28_desugared.jar")); |
| assertThat(libDesugarWithMinSdkArgs).containsAtLeast("--min_sdk_version", "28").inOrder(); |
| |
| List<String> libDexWithMinSdkArgs = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(app2)), |
| "liblib.jar--min_sdk_version=28.dex.zip")); |
| assertThat(libDexWithMinSdkArgs).containsAtLeast("--min_sdk_version", "28").inOrder(); |
| } |
| |
| // 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"], |
| manifest = "AndroidManifest.xml", |
| deps = [ |
| ":libnative", |
| "//java/android/common:libcommon_armeabi", |
| ], |
| ) |
| """); |
| 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", |
| exports = [":common_native"], |
| ) |
| """); |
| scratch.file( |
| "java/android/app/BUILD", |
| """ |
| cc_library( |
| name = "native", |
| srcs = ["native.cc"], |
| ) |
| |
| android_binary( |
| name = "auto", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| deps = [ |
| ":native", |
| "//java/android/common", |
| ], |
| ) |
| |
| android_binary( |
| name = "off", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| deps = [ |
| ":native", |
| "//java/android/common", |
| ], |
| ) |
| """); |
| } |
| |
| private void assertNativeLibraryLinked(ConfiguredTarget target, String... srcNames) { |
| Artifact linkedLib = getOnlyElement(getNativeLibrariesInApk(target)); |
| assertThat(linkedLib.getFilename()) |
| .isEqualTo("lib" + target.getLabel().toPathFragment().getBaseName() + ".so"); |
| assertThat(linkedLib.isSourceArtifact()).isFalse(); |
| assertWithMessage("Native libraries were not linked to produce " + linkedLib) |
| .that(getGeneratingLabelForArtifact(linkedLib)) |
| .isEqualTo(target.getLabel()); |
| assertThat(artifactsToStrings(actionsTestUtil().artifactClosureOf(linkedLib))) |
| .containsAtLeastElementsIn(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 { |
| setBuildLanguageOptions( |
| "--experimental_builtins_injection_override=+cc_library", |
| "--experimental_google_legacy_api"); |
| 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"], |
| manifest = "AndroidManifest.xml", |
| deps = [":native_wrapper"], |
| ) |
| """); |
| ConfiguredTarget app = getConfiguredTarget("//java/android/app:app"); |
| assertNativeLibrariesCopiedNotLinked( |
| app, |
| getConfiguration(getDirectPrerequisite(app, "//java/android/app:native_wrapper")), |
| "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"], |
| manifest = "AndroidManifest.xml", |
| deps = [":native"], |
| ) |
| """); |
| ConfiguredTarget app = getConfiguredTarget("//java/android/app:app"); |
| assertNativeLibrariesCopiedNotLinked( |
| app, |
| getConfiguration(getDirectPrerequisite(app, "//java/android/app:native")), |
| "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"], |
| manifest = "AndroidManifest.xml", |
| deps = [":native_wrapper"], |
| ) |
| """); |
| 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"], |
| manifest = "AndroidManifest.xml", |
| deps = [":native"], |
| ) |
| """); |
| ConfiguredTarget app = getConfiguredTarget("//java/android/app:app"); |
| Iterable<Artifact> nativeLibraries = getNativeLibrariesInApk(app); |
| assertThat(artifactsToStrings(nativeLibraries)) |
| .containsAtLeast("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 testNativeLibrary_providesLinkerScriptToLinkAction() throws Exception { |
| scratch.file( |
| "java/android/app/BUILD", |
| """ |
| cc_library( |
| name = "native", |
| srcs = ["native.cc"], |
| linkopts = [ |
| "-Wl,-version-script", |
| "$(location jni.lds)", |
| ], |
| deps = ["jni.lds"], |
| ) |
| |
| android_binary( |
| name = "app", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| deps = [":native"], |
| ) |
| """); |
| |
| ConfiguredTarget app = getConfiguredTarget("//java/android/app:app"); |
| Artifact copiedLib = getOnlyElement(getNativeLibrariesInApk(app)); |
| Artifact linkedLib = getGeneratingAction(copiedLib).getInputs().getSingleton(); |
| NestedSet<Artifact> linkInputs = getGeneratingAction(linkedLib).getInputs(); |
| assertThat(ActionsTestUtil.baseArtifactNames(linkInputs)).contains("jni.lds"); |
| } |
| |
| /** Regression test for http://b/33173461. */ |
| @Test |
| public void testIncrementalDexingUsesDexArchives_binaryDependingOnAliasTarget() throws Exception { |
| useConfiguration( |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false", |
| "--experimental_incremental_dexing_after_proguard=1"); |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_library( |
| name = "dep", |
| srcs = ["dep.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| alias( |
| name = "alt", |
| actual = ":dep", |
| ) |
| |
| android_binary( |
| name = "top", |
| srcs = [ |
| "bar.srcjar", |
| "foo.java", |
| ], |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| deps = [":alt"], |
| ) |
| """); |
| |
| ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top"); |
| assertNoEvents(); |
| |
| Action shardAction = getGeneratingAction(getBinArtifact("_dx/top/classes.jar", topTarget)); |
| for (Artifact input : getNonToolInputs(shardAction)) { |
| String basename = input.getFilename(); |
| // all jars are converted to dex archives |
| assertWithMessage(basename) |
| .that(!basename.contains(".jar") || basename.endsWith(".jar.dex.zip")) |
| .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(getNonToolInputs(shardAction)), |
| 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"); |
| } |
| |
| @Test |
| public void testIncrementalDexingDisabledWithBlacklistedDexopts() throws Exception { |
| // Even if we mark a dx flag as supported, incremental dexing isn't used with disallowlisted |
| // dexopts (unless incremental_dexing attribute is set, which a different test covers) |
| useConfiguration( |
| "--incremental_dexing", |
| "--non_incremental_per_target_dexopts=--no-locals", |
| "--dexopts_supported_in_incremental_dexing=--no-locals"); |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "top", |
| srcs = [ |
| "bar.srcjar", |
| "foo.java", |
| ], |
| dex_shards = 2, |
| dexopts = ["--no-locals"], |
| manifest = "AndroidManifest.xml", |
| 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(getNonToolInputs(shardAction)), |
| Predicates.containsPattern("\\.jar\\.dex\\.zip"))) |
| .isEmpty(); // no dex archives are used |
| } |
| |
| @Test |
| public void testIncrementalDexingDisabledWithProguard() throws Exception { |
| useConfiguration( |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false", |
| "--experimental_incremental_dexing_after_proguard=1"); |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "top", |
| srcs = [ |
| "bar.srcjar", |
| "foo.java", |
| ], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard.cfg"], |
| ) |
| """); |
| |
| ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top"); |
| assertNoEvents(); |
| Action dexAction = |
| getGeneratingAction(getBinArtifact("_dx/top/intermediate_classes.dex.zip", 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_incompatibleWithProguardWhenDisabled() throws Exception { |
| useConfiguration( |
| "--experimental_incremental_dexing_after_proguard=0", // disable with Proguard |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false"); |
| 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 testIncrementalDexingAfterProguard_unsharded() throws Exception { |
| useConfiguration("--experimental_incremental_dexing_after_proguard=1"); |
| // Use "legacy" multidex mode so we get a main dex list file and can test that it's passed to |
| // the splitter action (similar to _withDexShards below), unlike without the dex splitter where |
| // the main dex list goes to the merging action. |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "top", |
| srcs = [ |
| "bar.srcjar", |
| "foo.java", |
| ], |
| dexopts = [ |
| "--minimal-main-dex", |
| "--positions=none", |
| ], |
| incremental_dexing = 1, |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| proguard_specs = ["b.pro"], |
| ) |
| """); |
| |
| ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top"); |
| assertNoEvents(); |
| |
| SpawnAction shardAction = |
| getGeneratingSpawnAction(getBinArtifact("_dx/top/classes.dex.zip", topTarget)); |
| assertThat(shardAction.getArguments()).contains("--main-dex-list"); |
| assertThat(shardAction.getArguments()).contains("--minimal-main-dex"); |
| assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(shardAction))) |
| .containsExactly("classes.jar", "main_dex_list.txt"); |
| |
| // --positions dexopt is supported after Proguard, even though not normally otherwise |
| assertThat( |
| paramFileArgsForAction( |
| getGeneratingSpawnAction(getBinArtifact("_dx/top/classes.jar", topTarget)))) |
| .contains("--positions=none"); |
| } |
| |
| @Test |
| public void testIncrementalDexingAfterProguard_autoShardedMultidexAutoOptIn() throws Exception { |
| useConfiguration( |
| "--experimental_incremental_dexing_after_proguard=3", |
| "--experimental_incremental_dexing_after_proguard_by_default"); |
| // Use "legacy" multidex mode so we get a main dex list file and can test that it's passed to |
| // the splitter action (similar to _withDexShards below), unlike without the dex splitter where |
| // the main dex list goes to the merging action. |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "top", |
| srcs = [ |
| "bar.srcjar", |
| "foo.java", |
| ], |
| dexopts = [ |
| "--minimal-main-dex", |
| "--positions=none", |
| ], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| proguard_specs = ["b.pro"], |
| ) |
| """); // incremental_dexing = 1 attribute not needed |
| |
| ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top"); |
| assertNoEvents(); |
| |
| SpawnAction splitAction = getGeneratingSpawnAction(getTreeArtifact("dexsplits/top", topTarget)); |
| assertThat(splitAction.getArguments()).contains("--main-dex-list"); |
| assertThat(splitAction.getArguments()).contains("--minimal-main-dex"); |
| assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(splitAction))) |
| .containsExactly( |
| "shard1.jar.dex.zip", "shard2.jar.dex.zip", "shard3.jar.dex.zip", "main_dex_list.txt"); |
| |
| SpawnAction shuffleAction = |
| getGeneratingSpawnAction(getBinArtifact("_dx/top/shard1.jar", topTarget)); |
| assertThat(shuffleAction.getArguments()).doesNotContain("--main-dex-list"); |
| assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(shuffleAction))) |
| .containsExactly("top_proguard.jar"); |
| |
| // --positions dexopt is supported after Proguard, even though not normally otherwise |
| assertThat( |
| paramFileArgsForAction( |
| getGeneratingSpawnAction(getBinArtifact("_dx/top/shard3.jar.dex.zip", topTarget)))) |
| .contains("--positions=none"); |
| } |
| |
| @Test |
| public void testIncrementalDexingAfterProguard_explicitDexShards() throws Exception { |
| useConfiguration("--experimental_incremental_dexing_after_proguard=2"); |
| // Use "legacy" multidex mode so we get a main dex list file and can test that it's passed to |
| // the shardAction, not to the subsequent dexMerger action. Without dex_shards, main dex list |
| // file goes to the dexMerger instead (see _multidex test). |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "top", |
| srcs = [ |
| "bar.srcjar", |
| "foo.java", |
| ], |
| dex_shards = 25, |
| incremental_dexing = 1, |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| proguard_specs = ["b.pro"], |
| ) |
| """); |
| |
| ConfiguredTarget topTarget = getConfiguredTarget("//java/com/google/android:top"); |
| assertNoEvents(); |
| SpawnAction shardAction = |
| getGeneratingSpawnAction(getBinArtifact("_dx/top/shard25.jar", topTarget)); |
| assertThat(shardAction.getArguments()).contains("--main_dex_filter"); |
| assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(shardAction))) |
| .containsExactly("top_proguard.jar", "main_dex_list.txt"); |
| SpawnAction mergeAction = |
| getGeneratingSpawnAction(getBinArtifact("_dx/top/shard1.jar.dex.zip", topTarget)); |
| assertThat(mergeAction.getArguments()).doesNotContain("--main-dex-list"); |
| assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(mergeAction))) |
| .contains("shard1.jar"); |
| } |
| |
| @Test |
| public void testV1SigningMethod() throws Exception { |
| actualSignerToolTests("v1", "true", "false", null); |
| } |
| |
| @Test |
| public void testV2SigningMethod() throws Exception { |
| actualSignerToolTests("v2", "false", "true", null); |
| } |
| |
| @Test |
| public void testV1V2SigningMethod() throws Exception { |
| actualSignerToolTests("v1_v2", "true", "true", null); |
| } |
| |
| @Test |
| public void testV4SigningMethod() throws Exception { |
| actualSignerToolTests("v4", "false", "false", "true"); |
| } |
| |
| private void actualSignerToolTests( |
| String apkSigningMethod, String signV1, String signV2, String signV4) throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| useConfiguration("--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.getInputs().toList().stream() |
| .map(Artifact::getFilename) |
| .anyMatch(filename -> Ascii.toLowerCase(filename).contains("singlejar"))) |
| .isTrue(); |
| SpawnAction compressedUnsignedApkAction = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith(artifacts, "compressed_hello_unsigned.apk"); |
| assertThat( |
| compressedUnsignedApkAction.getInputs().toList().stream() |
| .map(Artifact::getFilename) |
| .anyMatch(filename -> Ascii.toLowerCase(filename).contains("singlejar"))) |
| .isTrue(); |
| SpawnAction zipalignAction = |
| (SpawnAction) |
| actionsTestUtil().getActionForArtifactEndingWith(artifacts, "zipaligned_hello.apk"); |
| assertThat(zipalignAction.getCommandFilename()).endsWith("zipalign"); |
| Artifact a = ActionsTestUtil.getFirstArtifactEndingWith(artifacts, "hello.apk"); |
| assertThat(getGeneratingSpawnAction(a).getCommandFilename()).endsWith("ApkSignerBinary"); |
| List<String> args = getGeneratingSpawnActionArgs(a); |
| |
| assertThat(flagValue("--v1-signing-enabled", args)).isEqualTo(signV1); |
| assertThat(flagValue("--v2-signing-enabled", args)).isEqualTo(signV2); |
| if (signV4 != null) { |
| assertThat(flagValue("--v4-signing-enabled", args)).isEqualTo(signV4); |
| if (signV4.equals("true")) { |
| assertThat(getFirstArtifactEndingWith(artifacts, "hello.apk.idsig")).isNotNull(); |
| } |
| } else { |
| assertThat(args).doesNotContain("--v4-signing-enabled"); |
| } |
| } |
| |
| @Test |
| public void testResourcePathShortening_flagEnabledAndCOpt_optimizedApkIsInputToApkBuilderAction() |
| throws Exception { |
| useConfiguration("--experimental_android_resource_path_shortening", "-c", "opt"); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| SpawnAction optimizeAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "app_optimized.ap_")); |
| assertThat(optimizeAction.getMnemonic()).isEqualTo("Aapt2Optimize"); |
| assertThat(getGeneratingAction(getFirstArtifactEndingWith(artifacts, "app_resource_paths.map"))) |
| .isEqualTo(optimizeAction); |
| |
| List<String> processingArgs = optimizeAction.getArguments(); |
| assertThat(processingArgs).contains("--shorten-resource-paths"); |
| assertThat(flagValue("--resource-path-shortening-map", processingArgs)) |
| .endsWith("app_resource_paths.map"); |
| |
| // Verify that the optimized APK is an input to build the unsigned APK. |
| SpawnAction apkAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "app_unsigned.apk")); |
| assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder"); |
| assertThat(hasInput(apkAction, "app_optimized.ap_")).isTrue(); |
| } |
| |
| @Test |
| public void testResourcePathShortening_flagEnabledAndCDefault_optimizeArtifactsAbsent() |
| throws Exception { |
| useConfiguration("--experimental_android_resource_path_shortening"); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| Artifact resourcePathShorteningMapArtifact = |
| getFirstArtifactEndingWith(artifacts, "app_resource_paths.map"); |
| assertThat(resourcePathShorteningMapArtifact).isNull(); |
| |
| Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, "app_optimized.ap_"); |
| assertThat(optimizedResourceApk).isNull(); |
| |
| // Verify that the optimized APK is not an input to build the unsigned APK. |
| SpawnAction apkAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "app_unsigned.apk")); |
| assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder"); |
| assertThat(hasInput(apkAction, "app_optimized.ap_")).isFalse(); |
| } |
| |
| @Test |
| public void testResourcePathShortening_flagNotEnabledAndCOpt_optimizeArtifactsAbsent() |
| throws Exception { |
| useConfiguration("-c", "opt"); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| Artifact resourcePathShorteningMapArtifact = |
| getFirstArtifactEndingWith(artifacts, "app_resource_paths.map"); |
| assertThat(resourcePathShorteningMapArtifact).isNull(); |
| |
| Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, "app_optimized.ap_"); |
| assertThat(optimizedResourceApk).isNull(); |
| |
| // Verify that the optimized APK is not an input to build the unsigned APK. |
| SpawnAction apkAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "app_unsigned.apk")); |
| assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder"); |
| assertThat(hasInput(apkAction, "app_optimized.ap_")).isFalse(); |
| } |
| |
| @Test |
| public void resourceNameCollapse_flagAndProguardSpecsPresent_optimizedApkIsInputToApkBuilder() |
| throws Exception { |
| useConfiguration("--experimental_android_resource_name_obfuscation"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:hello"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| SpawnAction shrinkerAction = |
| getGeneratingSpawnAction( |
| getFirstArtifactEndingWith(artifacts, "resource_optimization.cfg")); |
| assertThat(shrinkerAction.getMnemonic()).isEqualTo("ResourceShrinker"); |
| SpawnAction optimizeAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "hello_optimized.ap_")); |
| assertThat(optimizeAction.getMnemonic()).isEqualTo("Aapt2Optimize"); |
| |
| List<String> processingArgs = optimizeAction.getArguments(); |
| assertThat(processingArgs).contains("--collapse-resource-names"); |
| assertThat(flagValue("--resources-config-path", processingArgs)) |
| .endsWith("resource_optimization.cfg"); |
| |
| // Verify that the optimized APK is an input to build the unsigned APK. |
| SpawnAction apkAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "hello_unsigned.apk")); |
| assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder"); |
| assertThat(hasInput(apkAction, "hello_optimized.ap_")).isTrue(); |
| } |
| |
| @Test |
| public void resourceNameCollapse_featureAndProguardSpecsPresent_optimizedApkIsInputToApkBuilder() |
| throws Exception { |
| useConfiguration("--features=resource_name_obfuscation"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:hello"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| SpawnAction shrinkerAction = |
| getGeneratingSpawnAction( |
| getFirstArtifactEndingWith(artifacts, "resource_optimization.cfg")); |
| assertThat(shrinkerAction.getMnemonic()).isEqualTo("ResourceShrinker"); |
| SpawnAction optimizeAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "hello_optimized.ap_")); |
| assertThat(optimizeAction.getMnemonic()).isEqualTo("Aapt2Optimize"); |
| |
| List<String> processingArgs = optimizeAction.getArguments(); |
| assertThat(processingArgs).contains("--collapse-resource-names"); |
| assertThat(flagValue("--resources-config-path", processingArgs)) |
| .endsWith("resource_optimization.cfg"); |
| |
| // Verify that the optimized APK is an input to build the unsigned APK. |
| SpawnAction apkAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "hello_unsigned.apk")); |
| assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder"); |
| assertThat(hasInput(apkAction, "hello_optimized.ap_")).isTrue(); |
| } |
| |
| @Test |
| public void resourceNameCollapse_flagPresentProguardSpecsAbsent_optimizeArtifactsAbsent() |
| throws Exception { |
| useConfiguration("--experimental_android_resource_name_obfuscation"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:hello"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, "hello_optimized.ap_"); |
| assertThat(optimizedResourceApk).isNull(); |
| |
| // Verify that the optimized APK is not an input to build the unsigned APK. |
| SpawnAction apkAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "hello_unsigned.apk")); |
| assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder"); |
| assertThat(hasInput(apkAction, "hello_optimized.ap_")).isFalse(); |
| } |
| |
| @Test |
| public void resourceNameCollapse_flagAbsentProguardSpecsPresent_optimizeArtifactsAbsent() |
| throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:hello"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, "hello_optimized.ap_"); |
| assertThat(optimizedResourceApk).isNull(); |
| |
| // Verify that the optimized APK is not an input to build the unsigned APK. |
| SpawnAction apkAction = |
| getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, "hello_unsigned.apk")); |
| assertThat(apkAction.getMnemonic()).isEqualTo("ApkBuilder"); |
| assertThat(hasInput(apkAction, "hello_optimized.ap_")).isFalse(); |
| } |
| |
| @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", |
| 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. |
| Artifact a = getFirstArtifactEndingWith(getFilesToBuild(output), "_proguard.jar"); |
| SpawnAction action = getGeneratingSpawnAction(a); |
| List<String> args = getGeneratingSpawnActionArgs(a); |
| |
| // Assert that the ProGuard executable set in the android_sdk rule appeared in the command-line |
| // of the SpawnAction that generated the _proguard.jar. |
| assertThat(args) |
| .containsAtLeast( |
| getProguardBinary().getExecPathString(), |
| "-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); |
| assertThat(getBinArtifact("b_proguard.usage", output)).isNotNull(); |
| assertThat(getBinArtifact("b_proguard.seeds", output)).isNotNull(); |
| } |
| |
| @Test |
| public void testProGuardExecutableMatchesConfiguration() throws Exception { |
| scratch.file( |
| "java/com/google/devtools/build/jkrunchy/BUILD", |
| """ |
| package(default_visibility = ["//visibility:public"]) |
| |
| java_binary( |
| name = "jkrunchy", |
| 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"); |
| Artifact jkrunchyExecutable = |
| getExecConfiguredTarget("//java/com/google/devtools/build/jkrunchy") |
| .getProvider(FilesToRunProvider.class) |
| .getExecutable(); |
| assertWithMessage("ProGuard implementation was not correctly taken from the configuration") |
| .that(proguardAction.getCommandFilename()) |
| .endsWith(jkrunchyExecutable.getRepositoryRelativePathString()); |
| } |
| |
| @Test |
| public void enforceProguardFileExtension_disabled_allowsOtherExtensions() throws Exception { |
| useConfiguration("--noenforce_proguard_file_extension"); |
| scratch.file( |
| "java/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| ) |
| """); |
| |
| ConfiguredTarget unused = getConfiguredTarget("//java/android/hello:b"); |
| } |
| |
| @Test |
| public void enforceProguardFileExtension_enabled_disallowsOtherExtensions() throws Exception { |
| useConfiguration("--enforce_proguard_file_extension"); |
| scratch.file( |
| "java/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| ) |
| """); |
| |
| AssertionError assertionError = |
| assertThrows(AssertionError.class, () -> getConfiguredTarget("//java/android/hello:b")); |
| |
| assertThat(assertionError).hasMessageThat().contains("These files do not end in .pgcfg"); |
| } |
| |
| @Test |
| public void enforceProguardFileExtension_enabled_allowsPgcfg() throws Exception { |
| useConfiguration("--enforce_proguard_file_extension"); |
| scratch.file( |
| "java/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = [":proguard.pgcfg"], |
| ) |
| """); |
| |
| ConfiguredTarget unused = getConfiguredTarget("//java/android/hello:b"); |
| } |
| |
| @Test |
| public void enforceProguardFileExtension_enabled_ignoresThirdParty() throws Exception { |
| useConfiguration("--enforce_proguard_file_extension"); |
| scratch.file( |
| "third_party/bar/BUILD", |
| """ |
| licenses(["unencumbered"]) |
| |
| exports_files(["proguard.pro"]) |
| """); |
| scratch.file( |
| "java/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["//third_party/bar:proguard.pro"], |
| ) |
| """); |
| |
| ConfiguredTarget unused = getConfiguredTarget("//java/android/hello:b"); |
| } |
| |
| @Test |
| public void testNeverlinkTransitivity() throws Exception { |
| useConfiguration("--android_fixed_resource_neverlinking"); |
| |
| scratch.file( |
| "java/com/google/android/neversayneveragain/BUILD", |
| """ |
| android_library( |
| name = "l1", |
| srcs = ["l1.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/resource.xml"], |
| ) |
| |
| android_library( |
| name = "l2", |
| srcs = ["l2.java"], |
| neverlink = 1, |
| deps = [":l1"], |
| ) |
| |
| 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"], |
| manifest = "AndroidManifest.xml", |
| deps = [":l2"], |
| ) |
| |
| android_binary( |
| name = "b2", |
| srcs = ["b2.java"], |
| manifest = "AndroidManifest.xml", |
| deps = [":l3"], |
| ) |
| |
| android_binary( |
| name = "b3", |
| srcs = ["b3.java"], |
| manifest = "AndroidManifest.xml", |
| deps = [ |
| ":l3", |
| ":l4", |
| ], |
| ) |
| """); |
| ConfiguredTarget b1 = getConfiguredTarget("//java/com/google/android/neversayneveragain:b1"); |
| Action b1DeployAction = |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(b1)), "b1_deploy.jar"); |
| List<String> b1Inputs = prettyArtifactNames(b1DeployAction.getInputs()); |
| |
| assertThat(b1Inputs) |
| .containsNoneOf( |
| "java/com/google/android/neversayneveragain/libl1.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/libl2.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/libl3.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/libl4.jar_desugared.jar"); |
| assertThat(b1Inputs) |
| .contains("java/com/google/android/neversayneveragain/libb1.jar_desugared.jar"); |
| assertThat( |
| resourceInputPaths( |
| "java/com/google/android/neversayneveragain", getValidatedResources(b1))) |
| .doesNotContain("res/values/resource.xml"); |
| |
| ConfiguredTarget b2 = getConfiguredTarget("//java/com/google/android/neversayneveragain:b2"); |
| Action b2DeployAction = |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(b2)), "b2_deploy.jar"); |
| List<String> b2Inputs = prettyArtifactNames(b2DeployAction.getInputs()); |
| |
| assertThat(b2Inputs) |
| .containsNoneOf( |
| "java/com/google/android/neversayneveragain/libl1.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/libl2.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/libl4.jar_desugared.jar"); |
| assertThat(b2Inputs) |
| .containsAtLeast( |
| "java/com/google/android/neversayneveragain/_dx/l3/libl3.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/libb2.jar_desugared.jar"); |
| assertThat( |
| resourceInputPaths( |
| "java/com/google/android/neversayneveragain", getValidatedResources(b2))) |
| .doesNotContain("res/values/resource.xml"); |
| |
| ConfiguredTarget b3 = getConfiguredTarget("//java/com/google/android/neversayneveragain:b3"); |
| Action b3DeployAction = |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(b3)), "b3_deploy.jar"); |
| List<String> b3Inputs = prettyArtifactNames(b3DeployAction.getInputs()); |
| |
| assertThat(b3Inputs) |
| .containsAtLeast( |
| "java/com/google/android/neversayneveragain/_dx/l1/libl1.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/_dx/l3/libl3.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/_dx/l4/libl4.jar_desugared.jar", |
| "java/com/google/android/neversayneveragain/libb3.jar_desugared.jar"); |
| assertThat(b3Inputs) |
| .doesNotContain("java/com/google/android/neversayneveragain/libl2.jar_desugared.jar"); |
| assertThat( |
| resourceInputPaths( |
| "java/com/google/android/neversayneveragain", getValidatedResources(b3))) |
| .contains("res/values/resource.xml"); |
| } |
| |
| @Test |
| public void testDexopts() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| checkDexopts("[ '--opt1', '--opt2' ]", ImmutableList.of("--opt1", "--opt2")); |
| } |
| |
| @Test |
| public void testDexoptsTokenization() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| checkDexopts( |
| "[ '--opt1', '--opt2 tokenized' ]", ImmutableList.of("--opt1", "--opt2", "tokenized")); |
| } |
| |
| @Test |
| public void testDexoptsMakeVariableSubstitution() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| 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. |
| ImmutableList<String> fixedArgs = ImmutableList.of("--multi-dex"); |
| expectedArgs = |
| new ImmutableList.Builder<String>().addAll(expectedArgs).addAll(fixedArgs).build(); |
| |
| // Ensure that the args that immediately follow "--dex" match the expectation. |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android:b"); |
| List<String> args = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), |
| "intermediate_classes.dex.zip")); |
| int start = args.indexOf("--dex") + 1; |
| assertThat(start).isNotEqualTo(0); |
| int end = Math.min(args.size(), start + expectedArgs.size()); |
| assertThat(args.subList(start, end)).isEqualTo(expectedArgs); |
| } |
| |
| @Test |
| public void testDexMainListOpts() throws Exception { |
| checkDexMainListOpts("[ '--opt1', '--opt2' ]", "--opt1", "--opt2"); |
| } |
| |
| @Test |
| public void testDexMainListOptsTokenization() throws Exception { |
| checkDexMainListOpts("[ '--opt1', '--opt2 tokenized' ]", "--opt1", "--opt2", "tokenized"); |
| } |
| |
| @Test |
| public void testDexMainListOptsMakeVariableSubstitution() throws Exception { |
| 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"); |
| List<String> args = |
| getGeneratingSpawnActionArgs( |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "main_dex_list.txt")); |
| |
| // args: [ "bash", "-c", "java -cp dx.jar main opts other" ] |
| MoreAsserts.assertContainsSublist(args, expectedArgs); |
| } |
| |
| @Test |
| public void omitResourcesInfoProviderFromAndroidBinary_enabled() throws Exception { |
| useConfiguration("--experimental_omit_resources_info_provider_from_android_binary"); |
| ConfiguredTarget binary = |
| scratchConfiguredTarget( |
| "java/com/pkg/myapp", |
| "myapp", |
| "android_binary(", |
| " name = 'myapp',", |
| " manifest = 'AndroidManifest.xml',", |
| ")"); |
| |
| assertThat(binary.get(AndroidResourcesInfo.PROVIDER)).isNull(); |
| } |
| |
| @Test |
| public void omitResourcesInfoProviderFromAndroidBinary_disabled() throws Exception { |
| useConfiguration("--noexperimental_omit_resources_info_provider_from_android_binary"); |
| ConfiguredTarget binary = |
| scratchConfiguredTarget( |
| "java/com/pkg/myapp", |
| "myapp", |
| "android_binary(", |
| " name = 'myapp',", |
| " manifest = 'AndroidManifest.xml',", |
| ")"); |
| |
| assertThat(binary.get(AndroidResourcesInfo.PROVIDER)).isNotNull(); |
| } |
| |
| @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(getValidatedResources(binary)); |
| assertThat(flagValue("--resourceConfigs", args)).contains("en,fr"); |
| } |
| |
| /** Test that resources are not filtered in analysis under aapt2. */ |
| @Test |
| public void testFilteredResourcesFilteringAapt2() 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"; |
| |
| ConfiguredTarget binary = |
| scratchConfiguredTarget( |
| dir, |
| "r", |
| "android_binary(name = 'r',", |
| " manifest = 'AndroidManifest.xml',", |
| " resource_configuration_filters = ['', 'en, es, '],", |
| " densities = ['hdpi, , ', 'xhdpi'],", |
| " resource_files = ['" + Joiner.on("', '").join(resources) + "'])"); |
| ValidatedAndroidResources directResources = |
| getValidatedResources(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)).containsAtLeastElementsIn(resources); |
| |
| // Validate that the filters are correctly passed to the resource processing action |
| // This includes trimming whitespace and ignoring empty filters. |
| assertThat(resourceArguments(directResources)).contains("en,es"); |
| assertThat(resourceArguments(directResources)).contains("hdpi,xhdpi"); |
| } |
| |
| @Test |
| public void testFilterResourcesPseudolocalesPropagated() throws Exception { |
| String dir = "java/r/android"; |
| ConfiguredTarget binary = |
| scratchConfiguredTarget( |
| dir, |
| "bin", |
| "android_binary(name = 'bin',", |
| " resource_configuration_filters = ['en', 'en-rXA', 'ar-rXB'],", |
| " manifest = 'AndroidManifest.xml')"); |
| |
| List<String> resourceProcessingArgs = |
| getGeneratingSpawnActionArgs(getValidatedResources(binary).getRTxt()); |
| |
| assertThat(resourceProcessingArgs).containsAtLeast("--resourceConfigs", "ar-rXB,en,en-rXA"); |
| } |
| |
| /** |
| * 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, ValidatedAndroidResources 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 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, ValidatedAndroidResources resource) { |
| return pathsToArtifacts(dir, resourceGeneratingAction(resource).getInputs().toList()); |
| } |
| |
| /** |
| * 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 testInheritedRNotInRuntimeJars() throws Exception { |
| String dir = "java/r/android/"; |
| scratch.file( |
| dir + "BUILD", |
| "android_library(name = 'sublib',", |
| " manifest = 'AndroidManifest.xml',", |
| " srcs =['sublib.java'],", |
| " )", |
| "android_library(name = 'lib',", |
| " manifest = 'AndroidManifest.xml',", |
| " deps = [':sublib'],", |
| " srcs =['lib.java'],", |
| " )", |
| "android_binary(name = 'bin',", |
| " manifest = 'AndroidManifest.xml',", |
| " deps = [':lib'],", |
| " srcs =['bin.java'],", |
| " )"); |
| |
| Action deployJarAction = |
| getGeneratingAction( |
| getFileConfiguredTarget("//java/r/android:bin_deploy.jar").getArtifact()); |
| List<String> inputs = ActionsTestUtil.baseArtifactNames(deployJarAction.getInputs()); |
| |
| assertThat(inputs) |
| .containsAtLeast( |
| "libsublib.jar_desugared.jar", |
| "liblib.jar_desugared.jar", |
| "libbin.jar_desugared.jar", |
| "bin_resources.jar_desugared.jar"); |
| assertThat(inputs) |
| .containsNoneOf("lib_resources.jar_desugared.jar", "sublib_resources.jar_desugared.jar"); |
| } |
| |
| @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>"); |
| Artifact jar = getResourceClassJar(getConfiguredTargetAndData("//java/r/android:r")); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| assertThat(getGeneratingSpawnActionArgs(jar)) |
| .containsAtLeast("--primaryRTxt", "--primaryManifest", "--library", "--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>"); |
| Artifact jar = getResourceClassJar(getConfiguredTargetAndData("//java/r/android:r")); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| List<String> args = getGeneratingSpawnActionArgs(jar); |
| assertThat(args).containsAtLeast("--primaryRTxt", "--primaryManifest", "--classJarOutput"); |
| assertThat(args).doesNotContain("--libraries"); |
| } |
| |
| @Test |
| public void testUseRClassGeneratorCustomPackage() throws Exception { |
| scratch.file( |
| "java/r/android/BUILD", |
| """ |
| android_library( |
| name = "lib", |
| custom_package = "com.lib.custom", |
| manifest = "AndroidManifest.xml", |
| resource_files = glob(["res2/**"]), |
| ) |
| |
| android_binary( |
| name = "r", |
| custom_package = "com.binary.custom", |
| 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>"); |
| ConfiguredTargetAndData binary = getConfiguredTargetAndData("//java/r/android:r"); |
| Artifact jar = getResourceClassJar(binary); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| List<String> args = getGeneratingSpawnActionArgs(jar); |
| assertThat(args) |
| .containsAtLeast( |
| "--primaryRTxt", |
| "--primaryManifest", |
| "--library", |
| "--classJarOutput", |
| "--packageForR", |
| "com.binary.custom"); |
| } |
| |
| @Test |
| public void testUseRClassGeneratorMultipleDeps() throws Exception { |
| scratch.file( |
| "java/r/android/BUILD", |
| """ |
| android_library( |
| name = "lib1", |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| android_library( |
| name = "lib2", |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| android_binary( |
| name = "r", |
| manifest = "AndroidManifest.xml", |
| deps = [ |
| ":lib1", |
| ":lib2", |
| ], |
| ) |
| """); |
| ConfiguredTargetAndData binary = getConfiguredTargetAndData("//java/r/android:r"); |
| Artifact jar = getResourceClassJar(binary); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| List<String> args = getGeneratingSpawnActionArgs(jar); |
| |
| AndroidResourcesInfo resourcesInfo = |
| binary.getConfiguredTarget().get(AndroidResourcesInfo.PROVIDER); |
| assertThat(resourcesInfo.getTransitiveAndroidResources().toList()).hasSize(2); |
| ValidatedAndroidResources firstDep = |
| resourcesInfo.getTransitiveAndroidResources().toList().get(0); |
| ValidatedAndroidResources secondDep = |
| resourcesInfo.getTransitiveAndroidResources().toList().get(1); |
| |
| assertThat(args) |
| .containsAtLeast( |
| "--primaryRTxt", |
| "--primaryManifest", |
| "--library", |
| firstDep.getAapt2RTxt().getExecPathString() |
| + "," |
| + firstDep.getManifest().getExecPathString(), |
| "--library", |
| secondDep.getAapt2RTxt().getExecPathString() |
| + "," |
| + secondDep.getManifest().getExecPathString(), |
| "--classJarOutput") |
| .inOrder(); |
| } |
| |
| @Test |
| public void useRTxtFromMergedResourcesForFinalRClasses() throws Exception { |
| useConfiguration("--experimental_use_rtxt_from_merged_resources"); |
| scratch.file( |
| "java/pkg/BUILD", |
| """ |
| android_library( |
| name = "lib", |
| srcs = ["B.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/values.xml"], |
| ) |
| |
| android_binary( |
| name = "bin", |
| manifest = "AndroidManifest.xml", |
| deps = [":lib"], |
| ) |
| """); |
| |
| ConfiguredTargetAndData bin = getConfiguredTargetAndData("//java/pkg:bin"); |
| ConfiguredTarget lib = getDirectPrerequisite(bin.getConfiguredTarget(), "//java/pkg:lib"); |
| ValidatedAndroidResources libResources = |
| lib.get(AndroidResourcesInfo.PROVIDER).getDirectAndroidResources().toList().get(0); |
| SpawnAction topLevelResourceClassAction = getGeneratingSpawnAction(getResourceClassJar(bin)); |
| |
| // verify that the R.txt from creating the library-level resources.jar is also used for creating |
| // the top-level resources.jar |
| assertThat(getGeneratingSpawnAction(libResources.getClassJar()).getOutputs()) |
| .contains(libResources.getAapt2RTxt()); |
| MoreAsserts.assertContainsSublist( |
| topLevelResourceClassAction.getArguments(), |
| "--library", |
| libResources.getAapt2RTxt().getExecPathString() |
| + "," |
| + libResources.getManifest().getExecPathString()); |
| |
| // the "validation artifact" shouldn't be used for creating the top-level resources.jar, |
| // but it's still fed as a pseudo-input to trigger validation. |
| MoreAsserts.assertDoesNotContainSublist( |
| topLevelResourceClassAction.getArguments(), |
| "--library", |
| libResources.getAapt2ValidationArtifact().getExecPathString() |
| + "," |
| + libResources.getManifest().getExecPathString()); |
| assertThat(topLevelResourceClassAction.getInputs().toList()) |
| .contains(libResources.getAapt2ValidationArtifact()); |
| } |
| |
| // (test for undesired legacy behavior) |
| @Test |
| public void doNotUseRTxtFromMergedResourcesForFinalRClasses() throws Exception { |
| scratch.file( |
| "java/pkg/BUILD", |
| """ |
| android_library( |
| name = "lib", |
| srcs = ["B.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/values.xml"], |
| ) |
| |
| android_binary( |
| name = "bin", |
| manifest = "AndroidManifest.xml", |
| deps = [":lib"], |
| ) |
| """); |
| |
| ConfiguredTargetAndData bin = getConfiguredTargetAndData("//java/pkg:bin"); |
| ConfiguredTarget lib = getDirectPrerequisite(bin.getConfiguredTarget(), "//java/pkg:lib"); |
| ValidatedAndroidResources libResources = |
| lib.get(AndroidResourcesInfo.PROVIDER).getDirectAndroidResources().toList().get(0); |
| |
| // use the "validation artifact" for creating the top-level resources.jar |
| MoreAsserts.assertContainsSublist( |
| getGeneratingSpawnActionArgs(getResourceClassJar(bin)), |
| "--library", |
| libResources.getAapt2ValidationArtifact().getExecPathString() |
| + "," |
| + libResources.getManifest().getExecPathString()); |
| } |
| |
| @Test |
| public void testNoCrunchBinaryOnly() throws Exception { |
| scratch.file( |
| "java/r/android/BUILD", |
| """ |
| android_binary( |
| name = "r", |
| srcs = ["Foo.java"], |
| crunch_png = 0, |
| 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 = getGeneratingSpawnActionArgs(getResourceApk(binary)); |
| assertThat(args).contains("--useAaptCruncher=no"); |
| } |
| |
| @Test |
| public void testDoCrunch() throws Exception { |
| scratch.file( |
| "java/r/android/BUILD", |
| """ |
| android_binary( |
| name = "r", |
| srcs = ["Foo.java"], |
| crunch_png = 1, |
| 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 = getGeneratingSpawnActionArgs(getResourceApk(binary)); |
| 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 = getGeneratingSpawnActionArgs(getResourceApk(binary)); |
| 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"], |
| crunch_png = 0, |
| manifest = "AndroidManifest.xml", |
| deps = [":resources"], |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r"); |
| List<String> args = getGeneratingSpawnActionArgs(getResourceApk(binary)); |
| assertThat(args).contains("--useAaptCruncher=no"); |
| } |
| |
| @Test |
| public void testNoCrunchWithMultidexNative() 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"], |
| crunch_png = 0, |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| deps = [":resources"], |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r"); |
| List<String> args = getGeneratingSpawnActionArgs(getResourceApk(binary)); |
| assertThat(args).contains("--useAaptCruncher=no"); |
| } |
| |
| @Test |
| public void testZipaligned() throws Exception { |
| ConfiguredTarget binary = getConfiguredTarget("//java/android:app"); |
| Artifact a = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "zipaligned_app.apk"); |
| SpawnAction action = getGeneratingSpawnAction(a); |
| |
| assertThat(action.getMnemonic()).isEqualTo("AndroidZipAlign"); |
| |
| List<String> arguments = getGeneratingSpawnActionArgs(a); |
| assertThat(arguments).contains("-p"); |
| assertThat(arguments).contains("4"); |
| |
| Artifact zipAlignTool = getFirstArtifactEndingWith(action.getInputs(), "/zipalign"); |
| assertThat(arguments).contains(zipAlignTool.getExecPathString()); |
| |
| Artifact unsignedApk = getFirstArtifactEndingWith(action.getInputs(), "/app_unsigned.apk"); |
| assertThat(arguments).contains(unsignedApk.getExecPathString()); |
| |
| Artifact zipalignedApk = getFirstArtifactEndingWith(action.getOutputs(), "/zipaligned_app.apk"); |
| assertThat(arguments).contains(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"); |
| Artifact mergedManifest = |
| ActionsTestUtil.getFirstArtifactEndingWith(outputGroup, "/AndroidManifest.xml"); |
| assertThat(mergedManifest).isNotNull(); |
| } |
| |
| /** |
| * 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( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), |
| "compressed_a_unsigned.apk")); |
| Action mergeAction = |
| getGeneratingAction(getFirstArtifactEndingWith(apkAction.getInputs(), "classes.dex.zip")); |
| Iterable<Artifact> dexShards = |
| Iterables.filter( |
| mergeAction.getInputs().toList(), ActionsTestUtil.getArtifactSuffixMatcher(".dex.zip")); |
| assertThat(ActionsTestUtil.baseArtifactNames(dexShards)) |
| .containsExactly("shard1.dex.zip", "shard2.dex.zip"); |
| } |
| |
| @Test |
| public void testDexShardingDoesNotWorkWithManualMultidex() throws Exception { |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| dex_shards = 2, |
| main_dex_list = "main_dex_list.txt", |
| manifest = "AndroidManifest.xml", |
| multidex = "manual_main_dex", |
| ) |
| """); |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//java/a:a"); |
| assertContainsEvent(".dex sharding is not available in manual multidex mode"); |
| } |
| |
| @Test |
| public void testDexShardingLegacyStructure() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| dex_shards = 2, |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| ) |
| """); |
| |
| internalTestDexShardStructure(MultidexMode.LEGACY, false, "_desugared.jar"); |
| } |
| |
| @Test |
| public void testDexShardingNativeStructure_withNoDesugaring() throws Exception { |
| useConfiguration("--noexperimental_desugar_for_android", "--noincremental_dexing"); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| dex_shards = 2, |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| ) |
| """); |
| |
| internalTestDexShardStructure(MultidexMode.NATIVE, false, ""); |
| } |
| |
| @Test |
| public void testDexShardingNativeStructure() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| dex_shards = 2, |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| ) |
| """); |
| |
| internalTestDexShardStructure(MultidexMode.NATIVE, false, "_desugared.jar"); |
| } |
| |
| @Test |
| public void testDexShardingLegacyAndProguardStructure_withNoDesugaring() throws Exception { |
| useConfiguration( |
| "--noexperimental_desugar_for_android", |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false", |
| "--experimental_incremental_dexing_after_proguard=1"); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| dex_shards = 2, |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| proguard_specs = ["proguard.cfg"], |
| ) |
| """); |
| |
| internalTestDexShardStructure(MultidexMode.LEGACY, true, ""); |
| } |
| |
| @Test |
| public void testDexShardingLegacyAndProguardStructure() throws Exception { |
| useConfiguration( |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false", |
| "--experimental_incremental_dexing_after_proguard=1"); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| dex_shards = 2, |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| proguard_specs = ["proguard.cfg"], |
| ) |
| """); |
| |
| internalTestDexShardStructure(MultidexMode.LEGACY, true, "_desugared.jar"); |
| } |
| |
| @Test |
| public void testDexShardingNativeAndProguardStructure() throws Exception { |
| useConfiguration( |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false", |
| "--experimental_incremental_dexing_after_proguard=1"); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| dex_shards = 2, |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| 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, |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| 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( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| main_dex_list_opts = [ |
| "--hello", |
| "--world", |
| ], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| ) |
| """); |
| |
| ConfiguredTarget a = getConfiguredTarget("//java/a:a"); |
| Artifact mainDexList = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(a)), "main_dex_list.txt"); |
| List<String> args = getGeneratingSpawnActionArgs(mainDexList); |
| assertThat(args).containsAtLeast("--hello", "--world"); |
| } |
| |
| @Test |
| public void testLegacyMainDexListWithAndroidSdk() throws Exception { |
| scratch.file( |
| "tools/fake/BUILD", |
| """ |
| cc_binary( |
| name = "generate_main_dex_list", |
| srcs = ["main.cc"], |
| ) |
| """); |
| |
| scratch.file( |
| "sdk/BUILD", |
| "android_sdk(", |
| " name = 'sdk',", |
| " aapt = 'aapt',", |
| " aapt2 = 'aapt2',", |
| " adb = 'adb',", |
| " aidl = 'aidl',", |
| " android_jar = 'android.jar',", |
| " 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',", |
| " legacy_main_dex_list_generator = '//tools/fake:generate_main_dex_list',", |
| " tags = ['__ANDROID_RULES_MIGRATION__'])", |
| "toolchain(", |
| " name = 'sdk_toolchain',", |
| String.format(" toolchain_type = '%s',", TestConstants.ANDROID_TOOLCHAIN_TYPE_LABEL), |
| " toolchain = ':sdk',", |
| ")"); |
| |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| ) |
| """); |
| |
| useConfiguration("--extra_toolchains=//sdk:sdk_toolchain"); |
| ConfiguredTarget a = getConfiguredTarget("//java/a:a"); |
| Artifact mainDexList = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(a)), "main_dex_list.txt"); |
| List<String> args = getGeneratingSpawnActionArgs(mainDexList); |
| NestedSet<Artifact> mainDexInputs = getGeneratingAction(mainDexList).getInputs(); |
| |
| MoreAsserts.assertContainsSublist(args, "--lib", "sdk/android.jar"); |
| assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains("generate_main_dex_list"); |
| assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains("a_deploy.jar"); |
| assertThat(getFirstArtifactEndingWith(mainDexInputs, "main_dex_list_creator")).isNull(); |
| } |
| |
| @Test |
| public void testMainDexAaptGenerationSupported() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "legacy", |
| ) |
| """); |
| |
| ConfiguredTargetAndData a = getConfiguredTargetAndData("//java/a:a"); |
| Artifact intermediateJar = |
| artifactByPath( |
| ImmutableList.of(getCompressedUnsignedApk(a.getConfiguredTarget())), |
| ".apk", |
| ".dex.zip", |
| ".dex.zip", |
| "main_dex_list.txt", |
| "_intermediate.jar"); |
| List<String> args = getGeneratingSpawnActionArgs(intermediateJar); |
| assertContainsSublist( |
| args, |
| ImmutableList.of( |
| "-include", |
| a.getConfiguration().getBinFragment(RepositoryName.MAIN) |
| + "/java/a/proguard/a/main_dex_a_proguard.cfg")); |
| } |
| |
| @Test |
| public void testMainDexGenerationWithoutProguardMap() throws Exception { |
| useConfiguration("--noincremental_dexing"); |
| 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( |
| ImmutableList.of(getCompressedUnsignedApk(a)), |
| ".apk", |
| ".dex.zip", |
| ".dex.zip", |
| "main_dex_list.txt", |
| "_intermediate.jar"); |
| List<String> args = getGeneratingSpawnActionArgs(intermediateJar); |
| MoreAsserts.assertDoesNotContainSublist(args, "-previousobfuscationmap"); |
| } |
| |
| // regression test for b/14288948 |
| @Test |
| public void testEmptyListAsProguardSpec() throws Exception { |
| scratch.file( |
| "java/foo/BUILD", |
| """ |
| android_binary( |
| name = "abin", |
| srcs = ["a.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = [], |
| ) |
| """); |
| Rule rule = getTarget("//java/foo:abin").getAssociatedRule(); |
| assertNoEvents(); |
| ImmutableList<String> implicitOutputFilenames = |
| rule.getOutputFiles().stream().map(FileTarget::getName).collect(toImmutableList()); |
| assertThat(implicitOutputFilenames).doesNotContain("abin_proguard.jar"); |
| } |
| |
| @Test |
| public void testConfigurableProguardSpecsEmptyList() throws Exception { |
| scratch.file( |
| "java/foo/BUILD", |
| "android_binary(", |
| " name = 'abin',", |
| " srcs = ['a.java'],", |
| " proguard_specs = select({", |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [],", |
| " }),", |
| " manifest = 'AndroidManifest.xml')"); |
| Rule rule = getTarget("//java/foo:abin").getAssociatedRule(); |
| assertNoEvents(); |
| ImmutableList<String> implicitOutputFilenames = |
| rule.getOutputFiles().stream().map(FileTarget::getName).collect(toImmutableList()); |
| assertThat(implicitOutputFilenames).contains("abin_proguard.jar"); |
| } |
| |
| @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 = getGeneratingSpawnActionArgs(getResourceApk(resource)); |
| |
| 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 = getGeneratingSpawnActionArgs(getResourceApk(resource)); |
| 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 = getGeneratingSpawnActionArgs(getResourceApk(resource)); |
| 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 = getGeneratingSpawnActionArgs(getResourceApk(resource)); |
| 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", |
| "//java/android/resources/d2", |
| ], |
| ) |
| """); |
| ConfiguredTarget resource = getConfiguredTarget("//java/android/resources:r"); |
| |
| List<String> args = getGeneratingSpawnActionArgs(getResourceApk(resource)); |
| assertPrimaryResourceDirs(ImmutableList.of("java/android/resources/bin-res"), args); |
| assertThat(getDirectDependentResourceDirs(args)) |
| .containsAtLeast("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 = getGeneratingSpawnActionArgs(getResourceApk(resource)); |
| assertPrimaryResourceDirs(ImmutableList.of("java/other/resources/res"), args); |
| assertNoEvents(); |
| } |
| |
| @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(AndroidResources.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(AndroidResources.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"], |
| ) |
| """); |
| ConfiguredTargetAndData r = getConfiguredTargetAndData("//java/android/resources:r"); |
| assertThat( |
| getFirstArtifactEndingWith(getFilesToBuild(r.getConfiguredTarget()), ".apk").getRoot()) |
| .isEqualTo(r.getConfiguration().getBinDirectory(RepositoryName.MAIN)); |
| } |
| |
| @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 = getGeneratingSpawnActionArgs(getResourceApk(r)); |
| 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", |
| srcs = ["A.java"], |
| javacopts = ["-g:lines,source"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| |
| 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(getJavacArguments(javacAction)).contains("-g:lines,source"); |
| } |
| |
| @Test |
| public void testFixDepsToolFlag() throws Exception { |
| useConfiguration("--experimental_fix_deps_tool=autofixer"); |
| |
| scratch.file("java/foo/A.java", "foo"); |
| scratch.file( |
| "java/foo/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| |
| Iterable<String> commandLine = |
| getJavacArguments( |
| ((JavaCompileAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| actionsTestUtil() |
| .artifactClosureOf( |
| getGeneratingAction( |
| getFileConfiguredTarget("//java/foo:a_deploy.jar") |
| .getArtifact()) |
| .getInputs()), |
| "liba.jar"))); |
| |
| assertThat(commandLine).containsAtLeast("--experimental_fix_deps_tool", "autofixer").inOrder(); |
| } |
| |
| @Test |
| public void testFixDepsToolFlagEmpty() throws Exception { |
| scratch.file("java/foo/A.java", "foo"); |
| scratch.file( |
| "java/foo/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| |
| Iterable<String> commandLine = |
| getJavacArguments( |
| ((JavaCompileAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| actionsTestUtil() |
| .artifactClosureOf( |
| getGeneratingAction( |
| getFileConfiguredTarget("//java/foo:a_deploy.jar") |
| .getArtifact()) |
| .getInputs()), |
| "liba.jar"))); |
| |
| assertThat(commandLine).containsAtLeast("--experimental_fix_deps_tool", "add_dep").inOrder(); |
| } |
| |
| @Test |
| public void testAndroidBinaryExportsJavaCompilationArgsProvider() throws Exception { |
| |
| scratch.file("java/foo/A.java", "foo"); |
| scratch.file( |
| "java/foo/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| javacopts = ["-g:lines,source"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| |
| final JavaCompilationArgsProvider provider = |
| JavaInfo.getProvider( |
| JavaCompilationArgsProvider.class, getConfiguredTarget("//java/foo:a")); |
| |
| 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 = getGeneratingSpawnActionArgs(getResourceApk(r)); |
| 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 testDesugarJava8Libs_noProguard() throws Exception { |
| useConfiguration("--experimental_desugar_java8_libs"); |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = ["foo.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| ) |
| """); |
| |
| ConfiguredTarget top = getConfiguredTarget("//java/com/google/android:foo"); |
| Artifact artifact = getBinArtifact("_dx/foo/_final_classes.dex.zip", top); |
| assertWithMessage("_final_classes.dex.zip").that(artifact).isNotNull(); |
| Action generatingAction = getGeneratingAction(artifact); |
| assertThat(ActionsTestUtil.baseArtifactNames(generatingAction.getInputs())) |
| .containsAtLeast("classes.dex.zip", /*canned*/ "java8_legacy.dex.zip"); |
| } |
| |
| @Test |
| public void testDesugarJava8Libs_withProguard() throws Exception { |
| useConfiguration("--experimental_desugar_java8_libs"); |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = ["foo.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| proguard_specs = ["foo.cfg"], |
| ) |
| """); |
| |
| ConfiguredTarget top = getConfiguredTarget("//java/com/google/android:foo"); |
| Artifact artifact = getBinArtifact("_dx/foo/_final_classes.dex.zip", top); |
| assertWithMessage("_final_classes.dex.zip").that(artifact).isNotNull(); |
| Action generatingAction = getGeneratingAction(artifact); |
| assertThat(ActionsTestUtil.baseArtifactNames(generatingAction.getInputs())) |
| .containsAtLeast("classes.dex.zip", /*built*/ "_java8_legacy.dex.zip"); |
| } |
| |
| private List<String> generatingActionArgs(Artifact artifact) throws Exception { |
| Action action = getGeneratingAction(artifact); |
| return ((SpawnAction) action).getArguments(); |
| } |
| |
| @Test |
| public void testDesugarJava8Libs_withProguardWithMap() throws Exception { |
| useConfiguration("--experimental_desugar_java8_libs"); |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = ["foo.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| proguard_generate_mapping = 1, |
| proguard_specs = ["foo.cfg"], |
| ) |
| """); |
| |
| ConfiguredTarget top = getConfiguredTarget("//java/com/google/android:foo"); |
| |
| Artifact desugaredLibraryDex = getBinArtifact("_dx/foo/_java8_legacy.dex.zip", top); |
| List<String> desugaredLibraryActionArgs = generatingActionArgs(desugaredLibraryDex); |
| assertThat(desugaredLibraryActionArgs.get(0)).contains("build_java8_legacy_dex"); |
| Artifact desugaredLibraryDexProguardMap = getBinArtifact("_dx/foo/_java8_legacy.dex.map", top); |
| MoreAsserts.assertContainsSublist( |
| desugaredLibraryActionArgs, |
| "--rules", |
| getBinArtifact("_dx/foo/_java8_legacy.dex.pgcfg", top).getExecPathString()); |
| MoreAsserts.assertContainsSublist( |
| desugaredLibraryActionArgs, "--output", desugaredLibraryDex.getExecPathString()); |
| MoreAsserts.assertContainsSublist( |
| desugaredLibraryActionArgs, |
| "--output_map", |
| desugaredLibraryDexProguardMap.getExecPathString()); |
| |
| Artifact proguardMap = getBinArtifact("foo_proguard.map", top); |
| List<String> mergeProguardMapsActionArgs = |
| ((SpawnAction) getGeneratingAction(proguardMap)).getArguments(); |
| assertThat(mergeProguardMapsActionArgs.get(0)).contains("merge_proguard_maps"); |
| assertThat(flagValues("--pg-map", mergeProguardMapsActionArgs)) |
| .isEqualTo( |
| ImmutableSet.of( |
| desugaredLibraryDexProguardMap.getExecPathString(), |
| getBinArtifact("_dx/foo/_proguard_output_for_desugared_library.map", top) |
| .getExecPathString())); |
| assertThat(flagValue("--pg-map-output", mergeProguardMapsActionArgs)) |
| .isEqualTo(proguardMap.getExecPathString()); |
| |
| Artifact artifact = getBinArtifact("_dx/foo/_final_classes.dex.zip", top); |
| assertWithMessage("_final_classes.dex.zip").that(artifact).isNotNull(); |
| Action generatingAction = getGeneratingAction(artifact); |
| assertThat(ActionsTestUtil.baseArtifactNames(generatingAction.getInputs())) |
| .containsAtLeast("classes.dex.zip", /*built*/ "_java8_legacy.dex.zip"); |
| } |
| |
| @Test |
| public void testDesugarJava8Libs_withProguardWithoutSpecsWithMap() throws Exception { |
| useConfiguration("--experimental_desugar_java8_libs"); |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = ["foo.java"], |
| manifest = "AndroidManifest.xml", |
| multidex = "native", |
| proguard_generate_mapping = 1, |
| ) |
| """); |
| |
| ConfiguredTarget top = getConfiguredTarget("//java/com/google/android:foo"); |
| |
| Artifact desugaredLibraryDex = getBinArtifact("_dx/foo/_java8_legacy.dex.zip", top); |
| assertThat(getGeneratingAction(desugaredLibraryDex)).isNull(); |
| |
| Artifact proguardMap = getBinArtifact("foo_proguard.map", top); |
| assertThat(getGeneratingAction(proguardMap)).isInstanceOf(FailAction.class); |
| |
| Artifact artifact = getBinArtifact("_dx/foo/_final_classes.dex.zip", top); |
| assertWithMessage("_final_classes.dex.zip").that(artifact).isNotNull(); |
| Action generatingAction = getGeneratingAction(artifact); |
| assertThat(ActionsTestUtil.baseArtifactNames(generatingAction.getInputs())) |
| .containsAtLeast("classes.dex.zip", /*canned*/ "java8_legacy.dex.zip"); |
| } |
| |
| @Test |
| public void testApplyProguardMapping() throws Exception { |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = ["foo.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_apply_mapping = "proguard.map", |
| proguard_specs = ["foo.pro"], |
| ) |
| """); |
| |
| ConfiguredTarget ct = getConfiguredTarget("//java/com/google/android:foo"); |
| |
| Artifact artifact = artifactByPath(getFilesToBuild(ct), "_proguard.jar"); |
| Action generatingAction = getGeneratingAction(artifact); |
| assertThat(Artifact.asExecPaths(generatingAction.getInputs())) |
| .contains("java/com/google/android/proguard.map"); |
| // Cannot use assertThat().containsAllOf().inOrder() as that does not assert that the elements |
| // are consecutive. |
| MoreAsserts.assertContainsSublist( |
| getGeneratingSpawnActionArgs(artifact), |
| "-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',", |
| ")"); |
| } |
| |
| @Test |
| public void testApplyProguardDictionary() throws Exception { |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = ["foo.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_apply_dictionary = "dictionary.txt", |
| proguard_specs = ["foo.pro"], |
| ) |
| """); |
| |
| ConfiguredTarget ct = getConfiguredTarget("//java/com/google/android:foo"); |
| |
| Artifact artifact = artifactByPath(getFilesToBuild(ct), "_proguard.jar"); |
| Action generatingAction = getGeneratingAction(artifact); |
| assertThat(Artifact.asExecPaths(generatingAction.getInputs())) |
| .contains("java/com/google/android/dictionary.txt"); |
| // Cannot use assertThat().containsAllOf().inOrder() as that does not assert that the elements |
| // are consecutive. |
| MoreAsserts.assertContainsSublist( |
| getGeneratingSpawnActionArgs(artifact), |
| "-obfuscationdictionary", |
| "java/com/google/android/dictionary.txt"); |
| MoreAsserts.assertContainsSublist( |
| getGeneratingSpawnActionArgs(artifact), |
| "-classobfuscationdictionary", |
| "java/com/google/android/dictionary.txt"); |
| MoreAsserts.assertContainsSublist( |
| getGeneratingSpawnActionArgs(artifact), |
| "-packageobfuscationdictionary", |
| "java/com/google/android/dictionary.txt"); |
| } |
| |
| @Test |
| public void testApplyProguardDictionaryWithNoSpec() throws Exception { |
| checkError( |
| "java/com/google/android", |
| "foo", |
| // messages: |
| "'proguard_apply_dictionary' can only be used when 'proguard_specs' is also set", |
| // build file: |
| "android_binary(", |
| " name = 'foo',", |
| " srcs = ['foo.java'],", |
| " proguard_apply_dictionary = 'dictionary.txt',", |
| " manifest = 'AndroidManifest.xml',", |
| ")"); |
| } |
| |
| @Test |
| public void testFeatureFlagsAttributeSetsSelectInDependency() throws Exception { |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "java/com/foo/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag1", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_setting( |
| name = "flag1@on", |
| flag_values = {":flag1": "on"}, |
| transitive_configs = [":flag1"], |
| ) |
| |
| config_feature_flag( |
| name = "flag2", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_setting( |
| name = "flag2@on", |
| flag_values = {":flag2": "on"}, |
| transitive_configs = [":flag2"], |
| ) |
| |
| android_library( |
| name = "lib", |
| srcs = select({ |
| ":flag1@on": ["Flag1On.java"], |
| "//conditions:default": ["Flag1Off.java"], |
| }) + select({ |
| ":flag2@on": ["Flag2On.java"], |
| "//conditions:default": ["Flag2Off.java"], |
| }), |
| transitive_configs = [ |
| ":flag1", |
| ":flag2", |
| ], |
| ) |
| |
| android_binary( |
| name = "foo", |
| feature_flags = { |
| "flag1": "on", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = [ |
| ":flag1", |
| ":flag2", |
| ], |
| deps = [":lib"], |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/foo"); |
| List<String> inputs = |
| prettyArtifactNames(actionsTestUtil().artifactClosureOf(getFinalUnsignedApk(binary))); |
| |
| assertThat(inputs).containsAtLeast("java/com/foo/Flag1On.java", "java/com/foo/Flag2Off.java"); |
| assertThat(inputs).containsNoneOf("java/com/foo/Flag1Off.java", "java/com/foo/Flag2On.java"); |
| } |
| |
| @Test |
| public void testFeatureFlagsAttributeSetsSelectInBinary() throws Exception { |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "java/com/foo/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag1", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_setting( |
| name = "flag1@on", |
| flag_values = {":flag1": "on"}, |
| transitive_configs = [":flag1"], |
| ) |
| |
| config_feature_flag( |
| name = "flag2", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_setting( |
| name = "flag2@on", |
| flag_values = {":flag2": "on"}, |
| transitive_configs = [":flag2"], |
| ) |
| |
| android_binary( |
| name = "foo", |
| srcs = select({ |
| ":flag1@on": ["Flag1On.java"], |
| "//conditions:default": ["Flag1Off.java"], |
| }) + select({ |
| ":flag2@on": ["Flag2On.java"], |
| "//conditions:default": ["Flag2Off.java"], |
| }), |
| feature_flags = { |
| "flag1": "on", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = [ |
| ":flag1", |
| ":flag2", |
| ], |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/foo"); |
| List<String> inputs = |
| prettyArtifactNames(actionsTestUtil().artifactClosureOf(getFinalUnsignedApk(binary))); |
| |
| assertThat(inputs).containsAtLeast("java/com/foo/Flag1On.java", "java/com/foo/Flag2Off.java"); |
| assertThat(inputs).containsNoneOf("java/com/foo/Flag1Off.java", "java/com/foo/Flag2On.java"); |
| } |
| |
| @Test |
| public void testFeatureFlagsAttributeSetsSelectInBinaryAlias() throws Exception { |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "java/com/foo/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag1", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_setting( |
| name = "flag1@on", |
| flag_values = {":flag1": "on"}, |
| transitive_configs = [":flag1"], |
| ) |
| |
| android_binary( |
| name = "foo", |
| srcs = select({ |
| ":flag1@on": ["Flag1On.java"], |
| "//conditions:default": ["Flag1Off.java"], |
| }), |
| feature_flags = { |
| "flag1": "on", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = [":flag1"], |
| ) |
| |
| alias( |
| name = "alias", |
| actual = ":foo", |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/foo:alias"); |
| List<String> inputs = |
| prettyArtifactNames(actionsTestUtil().artifactClosureOf(getFinalUnsignedApk(binary))); |
| |
| assertThat(inputs).contains("java/com/foo/Flag1On.java"); |
| assertThat(inputs).doesNotContain("java/com/foo/Flag1Off.java"); |
| } |
| |
| @Test |
| public void testFeatureFlagsAttributeFailsAnalysisIfFlagValueIsInvalid() throws Exception { |
| reporter.removeHandler(failFastHandler); |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "java/com/foo/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag1", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_setting( |
| name = "flag1@on", |
| flag_values = {":flag1": "on"}, |
| transitive_configs = [":flag1"], |
| ) |
| |
| android_library( |
| name = "lib", |
| srcs = select({ |
| ":flag1@on": ["Flag1On.java"], |
| "//conditions:default": ["Flag1Off.java"], |
| }), |
| transitive_configs = [":flag1"], |
| ) |
| |
| android_binary( |
| name = "foo", |
| feature_flags = { |
| "flag1": "invalid", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = [":flag1"], |
| deps = [":lib"], |
| ) |
| """); |
| assertThat(getConfiguredTarget("//java/com/foo")).isNull(); |
| assertContainsEvent( |
| "in config_feature_flag rule //java/com/foo:flag1: " |
| + "value must be one of [\"off\", \"on\"], but was \"invalid\""); |
| } |
| |
| @Test |
| public void testFeatureFlagsAttributeFailsAnalysisIfFlagValueIsInvalidEvenIfNotUsed() |
| throws Exception { |
| reporter.removeHandler(failFastHandler); |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "java/com/foo/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag1", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_setting( |
| name = "flag1@on", |
| flag_values = {":flag1": "on"}, |
| transitive_configs = [":flag1"], |
| ) |
| |
| android_binary( |
| name = "foo", |
| feature_flags = { |
| "flag1": "invalid", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = [":flag1"], |
| ) |
| """); |
| assertThat(getConfiguredTarget("//java/com/foo")).isNull(); |
| assertContainsEvent( |
| "in config_feature_flag rule //java/com/foo:flag1: " |
| + "value must be one of [\"off\", \"on\"], but was \"invalid\""); |
| } |
| |
| @Test |
| public void testFeatureFlagsAttributeFailsAnalysisIfFlagIsAliased() throws Exception { |
| reporter.removeHandler(failFastHandler); |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "java/com/foo/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag1", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| alias( |
| name = "alias", |
| actual = "flag1", |
| transitive_configs = [":flag1"], |
| ) |
| |
| android_binary( |
| name = "foo", |
| feature_flags = { |
| "alias": "on", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = [":flag1"], |
| ) |
| """); |
| assertThat(getConfiguredTarget("//java/com/foo")).isNull(); |
| assertContainsEvent( |
| "in feature_flags attribute of android_binary rule //java/com/foo:foo: " |
| + "Feature flags must be named directly, not through aliases; " |
| + "use '//java/com/foo:flag1', not '//java/com/foo:alias'"); |
| } |
| |
| @Test |
| public void testFeatureFlagsAttributeSetsFeatureFlagProviderValues() throws Exception { |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "java/com/foo/reader.bzl", |
| """ |
| def _impl(ctx): |
| ctx.actions.write( |
| ctx.outputs.java, |
| "\\n".join([ |
| str(target.label) + ": " + target[config_common.FeatureFlagInfo].value |
| for target in ctx.attr.flags |
| ]), |
| ) |
| return [DefaultInfo(files = depset([ctx.outputs.java]))] |
| |
| flag_reader = rule( |
| implementation = _impl, |
| attrs = {"flags": attr.label_list(providers = [config_common.FeatureFlagInfo])}, |
| outputs = {"java": "%{name}.java"}, |
| ) |
| """); |
| scratch.file( |
| "java/com/foo/BUILD", |
| """ |
| load("//java/com/foo:reader.bzl", "flag_reader") |
| |
| config_feature_flag( |
| name = "flag1", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_feature_flag( |
| name = "flag2", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| flag_reader( |
| name = "FooFlags", |
| flags = [ |
| ":flag1", |
| ":flag2", |
| ], |
| transitive_configs = [ |
| ":flag1", |
| ":flag2", |
| ], |
| ) |
| |
| android_binary( |
| name = "foo", |
| srcs = [":FooFlags.java"], |
| feature_flags = { |
| "flag1": "on", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = [ |
| ":flag1", |
| ":flag2", |
| ], |
| ) |
| """); |
| Artifact flagList = |
| getFirstArtifactEndingWith( |
| actionsTestUtil() |
| .artifactClosureOf(getFinalUnsignedApk(getConfiguredTarget("//java/com/foo"))), |
| "/FooFlags.java"); |
| FileWriteAction action = (FileWriteAction) getGeneratingAction(flagList); |
| assertThat(action.getFileContents()) |
| .isEqualTo("@@//java/com/foo:flag1: on\n@@//java/com/foo:flag2: off"); |
| } |
| |
| @Test |
| public void featureFlagsSetByAndroidBinaryAreInRequiredFragments() throws Exception { |
| useConfiguration("--include_config_fragments_provider=direct"); |
| scratch.file( |
| "java/com/foo/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag1", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| android_binary( |
| name = "foo", |
| srcs = [":FooFlags.java"], |
| feature_flags = { |
| "flag1": "on", |
| }, |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| ConfiguredTarget ct = getConfiguredTarget("//java/com/foo:foo"); |
| assertThat(ct.getProvider(RequiredConfigFragmentsProvider.class).getStarlarkOptions()) |
| .containsExactly(Label.parseCanonicalUnchecked("//java/com/foo:flag1")); |
| } |
| |
| @Test |
| public void testNocompressExtensions() throws Exception { |
| scratch.file( |
| "java/r/android/BUILD", |
| """ |
| android_binary( |
| name = "r", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| nocompress_extensions = [ |
| ".apk", |
| ".so", |
| ], |
| resource_files = ["res/raw/foo.apk"], |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r"); |
| ValidatedAndroidResources resource = getValidatedResources(binary); |
| List<String> args = resourceArguments(resource); |
| Artifact inputManifest = |
| getFirstArtifactEndingWith( |
| getGeneratingSpawnAction(resource.getManifest()).getInputs(), "AndroidManifest.xml"); |
| assertContainsSublist( |
| args, |
| ImmutableList.of( |
| "--primaryData", "java/r/android/res::" + inputManifest.getExecPathString())); |
| assertThat(args).contains("--uncompressedExtensions"); |
| assertThat(args.get(args.indexOf("--uncompressedExtensions") + 1)).isEqualTo(".apk,.so"); |
| assertThat(getGeneratingSpawnActionArgs(getCompressedUnsignedApk(binary))) |
| .containsAtLeast("--nocompress_suffixes", ".apk", ".so") |
| .inOrder(); |
| assertThat(getGeneratingSpawnActionArgs(getFinalUnsignedApk(binary))) |
| .containsAtLeast("--nocompress_suffixes", ".apk", ".so") |
| .inOrder(); |
| } |
| |
| @Test |
| public void testFeatureFlagPolicyMustContainRuleToUseFeatureFlags() throws Exception { |
| reporter.removeHandler(failFastHandler); // expecting an error |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.overwriteFile( |
| "tools/allowlists/config_feature_flag/BUILD", |
| """ |
| package_group( |
| name = "config_feature_flag", |
| packages = ["//flag"], |
| ) |
| """); |
| scratch.file( |
| "flag/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag", |
| allowed_values = [ |
| "right", |
| "wrong", |
| ], |
| default_value = "right", |
| visibility = ["//java/com/google/android/foo:__pkg__"], |
| ) |
| """); |
| scratch.file( |
| "java/com/google/android/foo/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = [":FooFlags.java"], |
| feature_flags = { |
| "//flag:flag": "right", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = ["//flag:flag"], |
| ) |
| """); |
| assertThat(getConfiguredTarget("//java/com/google/android/foo:foo")).isNull(); |
| assertContainsEvent( |
| "in android_binary rule //java/com/google/android/foo:foo: " |
| + "the attribute feature_flags is not available in this package"); |
| } |
| |
| @Test |
| public void testFeatureFlagPolicyDoesNotBlockRuleIfInPolicy() throws Exception { |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.overwriteFile( |
| "tools/allowlists/config_feature_flag/BUILD", |
| """ |
| package_group( |
| name = "config_feature_flag", |
| packages = [ |
| "//flag", |
| "//java/com/google/android/foo", |
| ], |
| ) |
| """); |
| scratch.file( |
| "flag/BUILD", |
| """ |
| config_feature_flag( |
| name = "flag", |
| allowed_values = [ |
| "right", |
| "wrong", |
| ], |
| default_value = "right", |
| visibility = ["//java/com/google/android/foo:__pkg__"], |
| ) |
| """); |
| scratch.file( |
| "java/com/google/android/foo/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = [":FooFlags.java"], |
| feature_flags = { |
| "//flag:flag": "right", |
| }, |
| manifest = "AndroidManifest.xml", |
| transitive_configs = ["//flag:flag"], |
| ) |
| """); |
| assertThat(getConfiguredTarget("//java/com/google/android/foo:foo")).isNotNull(); |
| assertNoEvents(); |
| } |
| |
| @Test |
| public void testFeatureFlagPolicyIsNotUsedIfFlagValuesNotUsed() throws Exception { |
| scratch.overwriteFile( |
| "tools/allowlists/config_feature_flag/BUILD", |
| """ |
| package_group( |
| name = "config_feature_flag", |
| packages = ["*super* busted package group"], |
| ) |
| """); |
| scratch.file( |
| "java/com/google/android/foo/BUILD", |
| """ |
| android_binary( |
| name = "foo", |
| srcs = [":FooFlags.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| assertThat(getConfiguredTarget("//java/com/google/android/foo:foo")).isNotNull(); |
| // the package_group is busted, so we would have failed to get this far if we depended on it |
| assertNoEvents(); |
| // Check time: does this test actually test what we're testing for? |
| reporter.removeHandler(failFastHandler); |
| assertThat(getConfiguredTarget("//tools/allowlists/config_feature_flag:config_feature_flag")) |
| .isNull(); |
| assertContainsEvent("*super* busted package group"); |
| } |
| |
| @Test |
| public void testAapt2WithAndroidSdk() throws Exception { |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/values.xml"], |
| ) |
| """); |
| |
| ConfiguredTarget a = getConfiguredTarget("//java/a:a"); |
| Artifact apk = getImplicitOutputArtifact(a, AndroidRuleClasses.ANDROID_RESOURCES_APK); |
| |
| assertThat(getGeneratingSpawnActionArgs(apk)) |
| .containsAtLeast( |
| "--aapt2", |
| // The path to aapt2 is different between Blaze and Bazel, so we omit it here. |
| // It's safe to do so as we've already checked for the `--aapt2` flag. |
| "--tool", |
| "AAPT2_PACKAGE"); |
| } |
| |
| @Test |
| public void testAapt2WithAndroidSdkAndDependencies() throws Exception { |
| scratch.file( |
| "java/b/BUILD", |
| """ |
| android_library( |
| name = "b", |
| srcs = ["B.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/values.xml"], |
| ) |
| """); |
| |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/values.xml"], |
| deps = ["//java/b"], |
| ) |
| """); |
| |
| ConfiguredTarget a = getConfiguredTarget("//java/a:a"); |
| ConfiguredTarget b = getDirectPrerequisite(a, "//java/b:b"); |
| |
| Artifact classJar = |
| getImplicitOutputArtifact(a, AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR); |
| Artifact apk = getImplicitOutputArtifact(a, AndroidRuleClasses.ANDROID_RESOURCES_APK); |
| |
| SpawnAction apkAction = getGeneratingSpawnAction(apk); |
| assertThat(getGeneratingSpawnActionArgs(apk)) |
| .containsAtLeast( |
| "--aapt2", |
| // The path to aapt2 is different between Blaze and Bazel, so we omit it here. |
| // It's safe to do so as we've already checked for the `--aapt2` flag. |
| "--tool", |
| "AAPT2_PACKAGE"); |
| |
| assertThat(apkAction.getInputs().toList()) |
| .contains(getImplicitOutputArtifact(b, AndroidRuleClasses.ANDROID_COMPILED_SYMBOLS)); |
| |
| SpawnAction classAction = getGeneratingSpawnAction(classJar); |
| assertThat(classAction.getInputs().toList()) |
| .containsAtLeast( |
| getImplicitOutputArtifact(a, AndroidRuleClasses.ANDROID_R_TXT), |
| getImplicitOutputArtifact(b, AndroidRuleClasses.ANDROID_R_TXT)); |
| } |
| |
| @Test |
| public void testAapt2ResourceShrinkingAction() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| resource_files = ["res/values/strings.xml"], |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTargetAndData targetAndData = |
| getConfiguredTargetAndData("//java/com/google/android/hello:hello"); |
| ConfiguredTarget binary = targetAndData.getConfiguredTarget(); |
| |
| Artifact jar = getResourceClassJar(targetAndData); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| assertThat(getGeneratingSpawnActionArgs(jar)).contains("--finalFields"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| assertThat(artifacts) |
| .containsAtLeast( |
| getFirstArtifactEndingWith(artifacts, "resource_files.zip"), |
| getFirstArtifactEndingWith(artifacts, "proguard.jar"), |
| getFirstArtifactEndingWith(artifacts, "shrunk.ap_")); |
| |
| List<String> processingArgs = |
| getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, "resource_files.zip")); |
| |
| assertThat(flagValue("--resourcesOutput", processingArgs)) |
| .endsWith("hello_files/resource_files.zip"); |
| |
| List<String> proguardArgs = |
| getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, "proguard.jar")); |
| |
| assertThat(flagValue("-outjars", proguardArgs)).endsWith("hello_proguard.jar"); |
| |
| List<String> shrinkingArgs = |
| getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, "shrunk.ap_")); |
| |
| assertThat(flagValue("--tool", shrinkingArgs)).isEqualTo("SHRINK_AAPT2"); |
| |
| assertThat(flagValue("--aapt2", shrinkingArgs)).isEqualTo(flagValue("--aapt2", processingArgs)); |
| 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("--resourcesConfigOutput", shrinkingArgs)) |
| .endsWith("resource_optimization.cfg"); |
| |
| List<String> packageArgs = |
| getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, "_hello_proguard.cfg")); |
| |
| assertThat(flagValue("--tool", packageArgs)).isEqualTo("AAPT2_PACKAGE"); |
| assertThat(packageArgs).doesNotContain("--conditionalKeepRules"); |
| } |
| |
| @Test |
| public void testAapt2ResourceShrinking_proguardSpecsAbsent_noShrunkApk() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/strings.xml"], |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTargetAndData targetAndData = |
| getConfiguredTargetAndData("//java/com/google/android/hello:hello"); |
| ConfiguredTarget binary = targetAndData.getConfiguredTarget(); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| assertThat(getFirstArtifactEndingWith(artifacts, "shrunk.ap_")).isNull(); |
| } |
| |
| @Test |
| public void testAapt2ResourceCycleShrinking() throws Exception { |
| useConfiguration("--experimental_android_resource_cycle_shrinking=true"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| resource_files = ["res/values/strings.xml"], |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTargetAndData targetAndData = |
| getConfiguredTargetAndData("//java/com/google/android/hello:hello"); |
| ConfiguredTarget binary = targetAndData.getConfiguredTarget(); |
| |
| Artifact jar = getResourceClassJar(targetAndData); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| assertThat(getGeneratingSpawnActionArgs(jar)).contains("--nofinalFields"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| List<String> packageArgs = |
| getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, "_hello_proguard.cfg")); |
| |
| assertThat(flagValue("--tool", packageArgs)).isEqualTo("AAPT2_PACKAGE"); |
| assertThat(packageArgs).contains("--conditionalKeepRules"); |
| } |
| |
| @Test |
| public void testAapt2ResourceCycleShinkingWithoutResourceShrinking() throws Exception { |
| useConfiguration("--experimental_android_resource_cycle_shrinking=true"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| resource_files = ["res/values/values.xml"], |
| shrink_resources = 0, |
| ) |
| """); |
| |
| ConfiguredTargetAndData targetAndData = |
| getConfiguredTargetAndData("//java/com/google/android/hello:a"); |
| ConfiguredTarget binary = targetAndData.getConfiguredTarget(); |
| |
| Artifact jar = getResourceClassJar(targetAndData); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| assertThat(getGeneratingSpawnActionArgs(jar)).doesNotContain("--nofinalFields"); |
| |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); |
| |
| List<String> packageArgs = |
| getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, "_a_proguard.cfg")); |
| |
| assertThat(flagValue("--tool", packageArgs)).isEqualTo("AAPT2_PACKAGE"); |
| assertThat(packageArgs).doesNotContain("--conditionalKeepRules"); |
| } |
| |
| @Test |
| public void testAapt2ResourceCycleShrinkingDisabledNoProguardSpecs() throws Exception { |
| useConfiguration("--experimental_android_resource_cycle_shrinking=true"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/strings.xml"], |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTargetAndData targetAndData = |
| getConfiguredTargetAndData("//java/com/google/android/hello:hello"); |
| |
| Artifact jar = getResourceClassJar(targetAndData); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| // Final fields should still be generated for non-ProGuarded builds. |
| assertThat(getGeneratingSpawnActionArgs(jar)).contains("--finalFields"); |
| } |
| |
| @Test |
| public void testAapt2ResourceCycleShrinkingDisabledNoProguardSpecsApplicationResources() |
| throws Exception { |
| useConfiguration("--experimental_android_resource_cycle_shrinking=true"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "hello", |
| srcs = ["Foo.java"], |
| manifest = "AndroidManifest.xml", |
| shrink_resources = 1, |
| ) |
| """); |
| |
| ConfiguredTargetAndData targetAndData = |
| getConfiguredTargetAndData("//java/com/google/android/hello:hello"); |
| |
| Artifact jar = getResourceClassJar(targetAndData); |
| assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo("RClassGenerator"); |
| // Final fields should still be generated for non-ProGuarded builds. |
| assertThat(getGeneratingSpawnActionArgs(jar)).contains("--finalFields"); |
| } |
| |
| @Test |
| public void testOnlyProguardSpecs() throws Exception { |
| useConfiguration( |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false", |
| "--experimental_incremental_dexing_after_proguard=1"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_library( |
| name = "l2", |
| srcs = ["MoreMaps.java"], |
| neverlink = 1, |
| ) |
| |
| android_library( |
| name = "l3", |
| idl_srcs = ["A.aidl"], |
| deps = [":l2"], |
| ) |
| |
| android_library( |
| name = "l4", |
| srcs = ["SubMoreMaps.java"], |
| neverlink = 1, |
| ) |
| |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = [ |
| "proguard-spec.pro", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| deps = [ |
| ":l3", |
| ":l4", |
| ], |
| ) |
| """); |
| ConfiguredTargetAndData binary = |
| getConfiguredTargetAndData("//java/com/google/android/hello:b"); |
| checkProguardUse( |
| binary.getConfiguredTarget(), |
| "b_proguard.jar", |
| false, |
| /*passes=*/ null, |
| // no-op since passes is null. |
| /*bytecodeOptimizationPassActions=*/ 0, |
| binary.getConfiguration().getBinFragment(RepositoryName.MAIN) |
| + "/java/com/google/android/hello/proguard/b/legacy_b_combined_library_jars.jar"); |
| } |
| |
| @Test |
| public void testOnlyProguardSpecsProguardJar() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_library( |
| name = "l2", |
| srcs = ["MoreMaps.java"], |
| neverlink = 1, |
| ) |
| |
| android_library( |
| name = "l3", |
| idl_srcs = ["A.aidl"], |
| deps = [":l2"], |
| ) |
| |
| android_library( |
| name = "l4", |
| srcs = ["SubMoreMaps.java"], |
| neverlink = 1, |
| ) |
| |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_generate_mapping = 1, |
| proguard_specs = [ |
| "proguard-spec.pro", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| deps = [ |
| ":l3", |
| ":l4", |
| ], |
| ) |
| """); |
| |
| ConfiguredTarget output = getConfiguredTarget("//java/com/google/android/hello:b_proguard.jar"); |
| assertProguardUsed(output); |
| |
| output = getConfiguredTarget("//java/com/google/android/hello:b_proguard.map"); |
| assertWithMessage("proguard.map is not in the rule output") |
| .that( |
| actionsTestUtil() |
| .getActionForArtifactEndingWith(getFilesToBuild(output), "_proguard.map")) |
| .isNotNull(); |
| } |
| |
| @Test |
| public void testCommandLineForMultipleProguardSpecs() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_library( |
| name = "l1", |
| srcs = ["Maps.java"], |
| neverlink = 1, |
| ) |
| |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = [ |
| "proguard-spec.pro", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| deps = [":l1"], |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:b"); |
| SpawnAction action = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith(getFilesToBuild(binary), "_proguard.jar"); |
| |
| assertWithMessage("Proguard action does not contain expected inputs.") |
| .that(prettyArtifactNames(action.getInputs())) |
| .containsAtLeast( |
| "java/com/google/android/hello/proguard-spec.pro", |
| "java/com/google/android/hello/proguard-spec1.pro", |
| "java/com/google/android/hello/proguard-spec2.pro"); |
| |
| assertThat(action.getArguments()) |
| .containsExactly( |
| getProguardBinary().getExecPathString(), |
| "-forceprocessing", |
| "-injars", |
| execPathEndingWith(action.getInputs(), "b_deploy.jar"), |
| "-outjars", |
| execPathEndingWith(action.getOutputs(), "b_proguard.jar"), |
| // Only one combined library jar |
| "-libraryjars", |
| execPathEndingWith(action.getInputs(), "legacy_b_combined_library_jars.jar"), |
| "@" + execPathEndingWith(action.getInputs(), "b_proguard.cfg"), |
| "@java/com/google/android/hello/proguard-spec.pro", |
| "@java/com/google/android/hello/proguard-spec1.pro", |
| "@java/com/google/android/hello/proguard-spec2.pro", |
| "-printseeds", |
| execPathEndingWith(action.getOutputs(), "_proguard.seeds"), |
| "-printusage", |
| execPathEndingWith(action.getOutputs(), "_proguard.usage"), |
| "-printconfiguration", |
| execPathEndingWith(action.getOutputs(), "_proguard.config")) |
| .inOrder(); |
| } |
| |
| /** Regression test for b/17790639 */ |
| @Test |
| public void testNoDuplicatesInProguardCommand() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_library( |
| name = "l1", |
| srcs = ["Maps.java"], |
| neverlink = 1, |
| ) |
| |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = [ |
| "proguard-spec.pro", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| deps = [":l1"], |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:b"); |
| SpawnAction action = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith(getFilesToBuild(binary), "_proguard.jar"); |
| assertThat(action.getArguments()) |
| .containsExactly( |
| getProguardBinary().getExecPathString(), |
| "-forceprocessing", |
| "-injars", |
| execPathEndingWith(action.getInputs(), "b_deploy.jar"), |
| "-outjars", |
| execPathEndingWith(action.getOutputs(), "b_proguard.jar"), |
| // Only one combined library jar |
| "-libraryjars", |
| execPathEndingWith(action.getInputs(), "legacy_b_combined_library_jars.jar"), |
| "@" + execPathEndingWith(action.getInputs(), "b_proguard.cfg"), |
| "@java/com/google/android/hello/proguard-spec.pro", |
| "@java/com/google/android/hello/proguard-spec1.pro", |
| "@java/com/google/android/hello/proguard-spec2.pro", |
| "-printseeds", |
| execPathEndingWith(action.getOutputs(), "_proguard.seeds"), |
| "-printusage", |
| execPathEndingWith(action.getOutputs(), "_proguard.usage"), |
| "-printconfiguration", |
| execPathEndingWith(action.getOutputs(), "_proguard.config")) |
| .inOrder(); |
| } |
| |
| @Test |
| public void testProguardMapping() throws Exception { |
| useConfiguration( |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false", |
| "--experimental_incremental_dexing_after_proguard=1"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_generate_mapping = 1, |
| proguard_specs = ["proguard-spec.pro"], |
| ) |
| """); |
| checkProguardUse( |
| getConfiguredTarget("//java/com/google/android/hello:b"), |
| "b_proguard.jar", |
| true, |
| /*passes=*/ null, |
| // no-op since passes is null. |
| /*bytecodeOptimizationPassActions=*/ 0, |
| getAndroidJarPath()); |
| } |
| |
| @Test |
| public void testProguardMappingProvider() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_library( |
| name = "l2", |
| srcs = ["MoreMaps.java"], |
| neverlink = 1, |
| ) |
| |
| android_library( |
| name = "l3", |
| idl_srcs = ["A.aidl"], |
| deps = [":l2"], |
| ) |
| |
| android_library( |
| name = "l4", |
| srcs = ["SubMoreMaps.java"], |
| neverlink = 1, |
| ) |
| |
| android_binary( |
| name = "b1", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_generate_mapping = 1, |
| proguard_specs = [ |
| "proguard-spec.pro", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| deps = [ |
| ":l3", |
| ":l4", |
| ], |
| ) |
| |
| android_binary( |
| name = "b2", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = [ |
| "proguard-spec.pro", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| deps = [ |
| ":l3", |
| ":l4", |
| ], |
| ) |
| """); |
| |
| ConfiguredTarget output = getConfiguredTarget("//java/com/google/android/hello:b1"); |
| assertProguardUsed(output); |
| Artifact mappingArtifact = getBinArtifact("b1_proguard.map", output); |
| ProguardMappingProvider mappingProvider = output.get(ProguardMappingProvider.PROVIDER); |
| assertThat(mappingProvider.getProguardMapping()).isEqualTo(mappingArtifact); |
| |
| output = getConfiguredTarget("//java/com/google/android/hello:b2"); |
| assertProguardUsed(output); |
| assertThat(output.get(ProguardMappingProvider.PROVIDER)).isNull(); |
| } |
| |
| @Test |
| public void testProguardSpecFromLibraryUsedInBinary() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_library( |
| name = "l2", |
| srcs = ["MoreMaps.java"], |
| proguard_specs = ["library_spec.cfg"], |
| ) |
| |
| android_library( |
| name = "l3", |
| idl_srcs = ["A.aidl"], |
| proguard_specs = ["library_spec.cfg"], |
| deps = [":l2"], |
| ) |
| |
| android_library( |
| name = "l4", |
| srcs = ["SubMoreMaps.java"], |
| neverlink = 1, |
| ) |
| |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = ["proguard-spec.pro"], |
| deps = [ |
| ":l3", |
| ":l4", |
| ], |
| ) |
| """); |
| assertProguardUsed(getConfiguredTarget("//java/com/google/android/hello:b")); |
| assertProguardGenerated(getConfiguredTarget("//java/com/google/android/hello:b")); |
| SpawnAction action = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| getFilesToBuild(getConfiguredTarget("//java/com/google/android/hello:b")), |
| "_proguard.jar"); |
| assertThat(prettyArtifactNames(action.getInputs())) |
| .contains("java/com/google/android/hello/proguard-spec.pro"); |
| assertThat(prettyArtifactNames(action.getInputs())) |
| .contains( |
| "java/com/google/android/hello/validated_proguard/l2/java/com/google/android/hello/library_spec.cfg_valid"); |
| assertThat(prettyArtifactNames(action.getInputs())).containsNoDuplicates(); |
| } |
| |
| @Test |
| public void testResourcesUsedInProguardGenerate() 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", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| resource_files = ["res/values/strings.xml"], |
| ) |
| """); |
| scratch.file( |
| "java/com/google/android/hello/res/values/strings.xml", |
| "<resources><string name = 'hello'>Hello Android!</string></resources>"); |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:b"); |
| SpawnAction action = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "_proguard.cfg"); |
| |
| assertProguardGenerated(binary); |
| assertWithMessage("Generate proguard action does not contain expected input.") |
| .that(prettyArtifactNames(action.getInputs())) |
| .contains("java/com/google/android/hello/res/values/strings.xml"); |
| } |
| |
| @Test |
| public void testUseSingleJarForLibraryJars() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_library( |
| name = "l1", |
| srcs = ["Maps.java"], |
| neverlink = 1, |
| ) |
| |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = [ |
| "proguard-spec.pro", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| deps = [":l1"], |
| ) |
| """); |
| ConfiguredTargetAndData binary = |
| getConfiguredTargetAndData("//java/com/google/android/hello:b"); |
| SpawnAction action = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| getFilesToBuild(binary.getConfiguredTarget()), "_proguard.jar"); |
| |
| checkProguardLibJars( |
| action, |
| binary.getConfiguration().getBinFragment(RepositoryName.MAIN) |
| + "/java/com/google/android/hello/proguard/b/legacy_b_combined_library_jars.jar"); |
| } |
| |
| @Test |
| public void testUseSingleJarForFilteredLibraryJars() throws Exception { |
| useConfiguration("--experimental_filter_library_jar_with_program_jar=true"); |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_library( |
| name = "l1", |
| srcs = ["Maps.java"], |
| neverlink = 1, |
| ) |
| |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_specs = [ |
| "proguard-spec.pro", |
| "proguard-spec1.pro", |
| "proguard-spec2.pro", |
| ], |
| deps = [":l1"], |
| ) |
| """); |
| ConfiguredTargetAndData binary = |
| getConfiguredTargetAndData("//java/com/google/android/hello:b"); |
| SpawnAction action = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith( |
| getFilesToBuild(binary.getConfiguredTarget()), "_proguard.jar"); |
| |
| checkProguardLibJars( |
| action, |
| binary.getConfiguration().getBinFragment(RepositoryName.MAIN) |
| + "/java/com/google/android/hello/proguard/b/legacy_b_combined_library_jars_filtered.jar"); |
| } |
| |
| @Test |
| public void testOnlyOneLibraryJar() throws Exception { |
| scratch.file( |
| "java/com/google/android/hello/BUILD", |
| """ |
| android_binary( |
| name = "b", |
| srcs = ["HelloApp.java"], |
| manifest = "AndroidManifest.xml", |
| proguard_generate_mapping = 1, |
| proguard_specs = ["proguard-spec.pro"], |
| ) |
| """); |
| ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:b"); |
| SpawnAction action = |
| (SpawnAction) |
| actionsTestUtil() |
| .getActionForArtifactEndingWith(getFilesToBuild(binary), "_proguard.jar"); |
| |
| checkProguardLibJars(action, getAndroidJarPath()); |
| } |
| |
| @Test |
| public void testApkInfoAccessibleFromStarlark() throws Exception { |
| scratch.file( |
| "java/com/google/android/BUILD", |
| """ |
| load(":postprocess.bzl", "postprocess") |
| |
| android_binary( |
| name = "b1", |
| srcs = ["b1.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| postprocess( |
| name = "postprocess", |
| dep = ":b1", |
| ) |
| """); |
| scratch.file( |
| "java/com/google/android/postprocess.bzl", |
| """ |
| def _impl(ctx): |
| return [DefaultInfo(files = depset( |
| [ctx.attr.dep[ApkInfo].signed_apk, ctx.attr.dep[ApkInfo].deploy_jar], |
| ))] |
| |
| postprocess = rule( |
| implementation = _impl, |
| attrs = {"dep": attr.label(providers = [ApkInfo])}, |
| ) |
| """); |
| ConfiguredTarget postprocess = getConfiguredTarget("//java/com/google/android:postprocess"); |
| assertThat(postprocess).isNotNull(); |
| assertThat( |
| prettyArtifactNames(postprocess.getProvider(FilesToRunProvider.class).getFilesToRun())) |
| .containsExactly("java/com/google/android/b1.apk", "java/com/google/android/b1_deploy.jar"); |
| } |
| |
| @Test |
| public void testInstrumentationInfoAccessibleFromStarlark() throws Exception { |
| scratch.file( |
| "java/com/google/android/instr/BUILD", |
| """ |
| load(":instr.bzl", "instr") |
| |
| android_binary( |
| name = "b1", |
| srcs = ["b1.java"], |
| instruments = ":b2", |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| android_binary( |
| name = "b2", |
| srcs = ["b2.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| instr( |
| name = "instr", |
| dep = ":b1", |
| ) |
| """); |
| scratch.file( |
| "java/com/google/android/instr/instr.bzl", |
| """ |
| def _impl(ctx): |
| target = ctx.attr.dep[AndroidInstrumentationInfo].target.signed_apk |
| return [DefaultInfo(files = depset([target]))] |
| |
| instr = rule( |
| implementation = _impl, |
| attrs = {"dep": attr.label(providers = [AndroidInstrumentationInfo])}, |
| ) |
| """); |
| ConfiguredTarget instr = getConfiguredTarget("//java/com/google/android/instr"); |
| assertThat(instr).isNotNull(); |
| assertThat(prettyArtifactNames(instr.getProvider(FilesToRunProvider.class).getFilesToRun())) |
| .containsExactly("java/com/google/android/instr/b2.apk"); |
| } |
| |
| @Test |
| public void testInstrumentationInfoCreatableFromStarlark() throws Exception { |
| scratch.file( |
| "java/com/google/android/instr/BUILD", |
| """ |
| load(":instr.bzl", "instr") |
| |
| android_binary( |
| name = "b1", |
| srcs = ["b1.java"], |
| instruments = ":b2", |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| android_binary( |
| name = "b2", |
| srcs = ["b2.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| instr( |
| name = "instr", |
| dep = ":b1", |
| ) |
| """); |
| scratch.file( |
| "java/com/google/android/instr/instr.bzl", |
| """ |
| def _impl(ctx): |
| target = ctx.attr.dep[AndroidInstrumentationInfo].target |
| return [AndroidInstrumentationInfo(target = target)] |
| |
| instr = rule( |
| implementation = _impl, |
| attrs = {"dep": attr.label(providers = [AndroidInstrumentationInfo])}, |
| ) |
| """); |
| ConfiguredTarget instr = getConfiguredTarget("//java/com/google/android/instr"); |
| assertThat(instr).isNotNull(); |
| assertThat(instr.get(AndroidInstrumentationInfo.PROVIDER).getTarget().getApk().prettyPrint()) |
| .isEqualTo("java/com/google/android/instr/b2.apk"); |
| } |
| |
| @Test |
| public void testInstrumentationInfoProviderHasApks() throws Exception { |
| scratch.file( |
| "java/com/google/android/instr/BUILD", |
| """ |
| android_binary( |
| name = "b1", |
| srcs = ["b1.java"], |
| instruments = ":b2", |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| android_binary( |
| name = "b2", |
| srcs = ["b2.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| ConfiguredTarget b1 = getConfiguredTarget("//java/com/google/android/instr:b1"); |
| AndroidInstrumentationInfo provider = b1.get(AndroidInstrumentationInfo.PROVIDER); |
| assertThat(provider.getTarget()).isNotNull(); |
| assertThat(provider.getTarget().getApk().prettyPrint()) |
| .isEqualTo("java/com/google/android/instr/b2.apk"); |
| } |
| |
| @Test |
| public void testNoInstrumentationInfoProviderIfNotInstrumenting() throws Exception { |
| scratch.file( |
| "java/com/google/android/instr/BUILD", |
| """ |
| android_binary( |
| name = "b1", |
| srcs = ["b1.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| ConfiguredTarget b1 = getConfiguredTarget("//java/com/google/android/instr:b1"); |
| AndroidInstrumentationInfo provider = b1.get(AndroidInstrumentationInfo.PROVIDER); |
| assertThat(provider).isNull(); |
| } |
| |
| @Test |
| public void testFilterActionWithInstrumentedBinary() throws Exception { |
| scratch.file( |
| "java/com/google/android/instr/BUILD", |
| """ |
| android_binary( |
| name = "b1", |
| srcs = ["b1.java"], |
| instruments = ":b2", |
| manifest = "AndroidManifest.xml", |
| ) |
| |
| android_binary( |
| name = "b2", |
| srcs = ["b2.java"], |
| manifest = "AndroidManifest.xml", |
| ) |
| """); |
| ConfiguredTarget b1 = getConfiguredTarget("//java/com/google/android/instr:b1"); |
| SpawnAction action = |
| (SpawnAction) |
| actionsTestUtil().getActionForArtifactEndingWith(getFilesToBuild(b1), "_filtered.jar"); |
| assertThat(action.getArguments()) |
| .containsAtLeast( |
| "--inputZip", |
| getFirstArtifactEndingWith(action.getInputs(), "b1_deploy.jar").getExecPathString(), |
| "--filterZips", |
| getFirstArtifactEndingWith(action.getInputs(), "b2_deploy.jar").getExecPathString(), |
| "--outputZip", |
| getFirstArtifactEndingWith(action.getOutputs(), "b1_filtered.jar").getExecPathString(), |
| "--filterTypes", |
| ".class", |
| "--checkHashMismatch", |
| "IGNORE", |
| "--explicitFilters", |
| "/BR\\.class$,/databinding/[^/]+Binding\\.class$,(^|/)R\\.class,(^|/)R\\$.*\\.class", |
| "--outputMode", |
| "DONT_CARE"); |
| } |
| |
| /** |
| * 'proguard_specs' attribute gets read by an implicit outputs function: the current heuristic is |
| * that if this attribute is configurable, we assume its contents are non-empty and thus create |
| * the mybinary_proguard.jar output. Test that here. |
| */ |
| @Test |
| public void testConfigurableProguardSpecs() throws Exception { |
| useConfiguration( |
| "--experimental_use_dex_splitter_for_incremental_dexing=false", |
| "--experimental_incremental_dexing_after_proguard_by_default=false", |
| "--experimental_incremental_dexing_after_proguard=1"); |
| scratch.file( |
| "conditions/BUILD", |
| """ |
| config_setting( |
| name = "a", |
| values = {"foo": "a"}, |
| ) |
| |
| config_setting( |
| name = "b", |
| values = {"foo": "b"}, |
| ) |
| """); |
| scratchConfiguredTarget( |
| "java/foo", |
| "abin", |
| "android_binary(", |
| " name = 'abin',", |
| " srcs = ['a.java'],", |
| " proguard_specs = select({", |
| " '//conditions:a': [':file1.pro'],", |
| " '//conditions:b': [],", |
| " '//conditions:default': [':file3.pro'],", |
| " }) + [", |
| // Add a long list here as a regression test for b/68238721 |
| " 'file4.pro',", |
| " 'file5.pro',", |
| " 'file6.pro',", |
| " 'file7.pro',", |
| " 'file8.pro',", |
| " ],", |
| " manifest = 'AndroidManifest.xml')"); |
| checkProguardUse( |
| getConfiguredTarget("//java/foo:abin"), |
| "abin_proguard.jar", |
| /*expectMapping=*/ false, |
| /*passes=*/ null, |
| // no-op since passes is null. |
| /*bytecodeOptimizationPassActions=*/ 0, |
| getAndroidJarPath()); |
| } |
| |
| @Test |
| public void alwaysSkipParsingActionWithAapt2() throws Exception { |
| scratch.file( |
| "java/b/BUILD", |
| """ |
| android_library( |
| name = "b", |
| srcs = ["B.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/values.xml"], |
| ) |
| """); |
| |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| android_binary( |
| name = "a", |
| srcs = ["A.java"], |
| manifest = "AndroidManifest.xml", |
| resource_files = ["res/values/values.xml"], |
| deps = ["//java/b"], |
| ) |
| """); |
| |
| ConfiguredTarget a = getConfiguredTarget("//java/a:a"); |
| ConfiguredTarget b = getDirectPrerequisite(a, "//java/b:b"); |
| |
| List<String> resourceProcessingArgs = |
| getGeneratingSpawnActionArgs(getValidatedResources(a).getApk()); |
| |
| assertThat(resourceProcessingArgs).contains("AAPT2_PACKAGE"); |
| String directData = |
| resourceProcessingArgs.get(resourceProcessingArgs.indexOf("--directData") + 1); |
| assertThat(directData).contains("symbols.zip"); |
| assertThat(directData).doesNotContain("merged.bin"); |
| |
| List<String> resourceMergingArgs = |
| getGeneratingSpawnActionArgs(getValidatedResources(b).getJavaClassJar()); |
| |
| assertThat(resourceMergingArgs).contains("MERGE_COMPILED"); |
| } |
| |
| @Test |
| public void starlarkJavaInfoToAndroidBinaryAttributes() throws Exception { |
| scratch.file( |
| "java/r/android/extension.bzl", |
| """ |
| def _impl(ctx): |
| dep_params = ctx.attr.dep[JavaInfo] |
| return [dep_params] |
| |
| my_rule = rule( |
| _impl, |
| attrs = { |
| "dep": attr.label(), |
| }, |
| ) |
| """); |
| scratch.file( |
| "java/r/android/BUILD", |
| """ |
| load(":extension.bzl", "my_rule") |
| |
| android_library( |
| name = "al_bottom_for_deps", |
| srcs = ["java/A.java"], |
| ) |
| |
| my_rule( |
| name = "mya", |
| dep = ":al_bottom_for_deps", |
| ) |
| |
| android_binary( |
| name = "foo_app", |
| srcs = ["java/B.java"], |
| # TODO(b/75051107): Remove the following line when fixed. |
| incremental_dexing = 0, |
| manifest = "AndroidManifest.xml", |
| deps = [":mya"], |
| ) |
| """); |
| |
| // Test that all bottom jars are on the runtime classpath of the app. |
| ConfiguredTarget target = getConfiguredTarget("//java/r/android:foo_app"); |
| |
| ImmutableList<Artifact> transitiveSrcJars = |
| OutputGroupInfo.get(target).getOutputGroup(JavaSemantics.SOURCE_JARS_OUTPUT_GROUP).toList(); |
| assertThat(ActionsTestUtil.baseArtifactNames(transitiveSrcJars)) |
| .containsExactly("libal_bottom_for_deps-src.jar", "libfoo_app-src.jar"); |
| ImmutableList<Artifact> directSrcJar = |
| OutputGroupInfo.get(target) |
| .getOutputGroup(JavaSemantics.DIRECT_SOURCE_JARS_OUTPUT_GROUP) |
| .toList(); |
| assertThat(ActionsTestUtil.baseArtifactNames(directSrcJar)) |
| .containsExactly("libfoo_app-src.jar"); |
| } |
| |
| @Test |
| public void androidManifestMergerOrderAlphabetical_MergeesSortedByExecPath() throws Exception { |
| useConfiguration("--android_manifest_merger_order=alphabetical"); |
| /* |
| * Dependency hierarchy: |
| * - //java/binary:application |
| * - //java/binary:library |
| * - //java/common:theme |
| * - //java/android:utility |
| * - //java/common:common |
| * - //java/android:core |
| */ |
| scratch.overwriteFile( |
| "java/android/BUILD", |
| """ |
| android_library( |
| name = "core", |
| exports_manifest = 1, |
| manifest = "core/AndroidManifest.xml", |
| resource_files = ["core/res/values/strings.xml"], |
| ) |
| |
| android_library( |
| name = "utility", |
| exports_manifest = 1, |
| manifest = "utility/AndroidManifest.xml", |
| resource_files = ["utility/res/values/values.xml"], |
| deps = ["//java/common"], |
| ) |
| """); |
| scratch.file( |
| "java/binary/BUILD", |
| """ |
| android_binary( |
| name = "application", |
| srcs = ["App.java"], |
| manifest = "app/AndroidManifest.xml", |
| deps = [":library"], |
| ) |
| |
| android_library( |
| name = "library", |
| exports_manifest = 1, |
| manifest = "library/AndroidManifest.xml", |
| deps = [ |
| "//java/android:utility", |
| "//java/common:theme", |
| ], |
| ) |
| """); |
| scratch.file( |
| "java/common/BUILD", |
| """ |
| android_library( |
| name = "common", |
| exports_manifest = 1, |
| manifest = "common/AndroidManifest.xml", |
| resource_files = ["common/res/values/common.xml"], |
| deps = ["//java/android:core"], |
| ) |
| |
| android_library( |
| name = "theme", |
| exports_manifest = 1, |
| manifest = "theme/AndroidManifest.xml", |
| resource_files = ["theme/res/values/values.xml"], |
| ) |
| """); |
| |
| // These have to be found via the same inheritance hierarchy, because getDirectPrerequsite can |
| // only traverse one level. |
| ConfiguredTarget application = getConfiguredTarget("//java/binary:application"); |
| ConfiguredTarget library = getDirectPrerequisite(application, "//java/binary:library"); |
| ConfiguredTarget theme = getDirectPrerequisite(library, "//java/common:theme"); |
| ConfiguredTarget utility = getDirectPrerequisite(library, "//java/android:utility"); |
| ConfiguredTarget common = getDirectPrerequisite(utility, "//java/common:common"); |
| ConfiguredTarget core = getDirectPrerequisite(common, "//java/android:core"); |
| |
| Artifact androidCoreManifest = getLibraryManifest(core); |
| Artifact androidUtilityManifest = getLibraryManifest(utility); |
| Artifact binaryLibraryManifest = getLibraryManifest(library); |
| Artifact commonManifest = getLibraryManifest(common); |
| Artifact commonThemeManifest = getLibraryManifest(theme); |
| |
| assertThat(getBinaryMergeeManifests(application)) |
| .containsExactlyEntriesIn( |
| ImmutableMap.of( |
| androidCoreManifest.getExecPath().toString(), "//java/android:core", |
| androidUtilityManifest.getExecPath().toString(), "//java/android:utility", |
| binaryLibraryManifest.getExecPath().toString(), "//java/binary:library", |
| commonManifest.getExecPath().toString(), "//java/common:common", |
| commonThemeManifest.getExecPath().toString(), "//java/common:theme")) |
| .inOrder(); |
| } |
| |
| @Test |
| public void androidManifestMergerOrderAlphabeticalByConfiguration_MergeesSortedByPathInBinOrGen() |
| throws Exception { |
| useConfiguration( |
| "--android_manifest_merger_order=alphabetical_by_configuration"); |
| scratch.overwriteFile( |
| "java/android/BUILD", |
| """ |
| android_library( |
| name = "core", |
| exports_manifest = 1, |
| manifest = "core/AndroidManifest.xml", |
| resource_files = ["core/res/values/strings.xml"], |
| ) |
| |
| android_library( |
| name = "utility", |
| exports_manifest = 1, |
| manifest = "utility/AndroidManifest.xml", |
| resource_files = ["utility/res/values/values.xml"], |
| transitive_configs = [ |
| "//flags:a", |
| "//flags:b", |
| ], |
| deps = ["//java/common"], |
| ) |
| """); |
| scratch.file( |
| "java/binary/BUILD", |
| """ |
| android_binary( |
| name = "application", |
| srcs = ["App.java"], |
| feature_flags = { |
| "//flags:a": "on", |
| "//flags:b": "on", |
| "//flags:c": "on", |
| }, |
| manifest = "app/AndroidManifest.xml", |
| transitive_configs = [ |
| "//flags:a", |
| "//flags:b", |
| "//flags:c", |
| ], |
| deps = [":library"], |
| ) |
| |
| android_library( |
| name = "library", |
| exports_manifest = 1, |
| manifest = "library/AndroidManifest.xml", |
| transitive_configs = [ |
| "//flags:a", |
| "//flags:b", |
| "//flags:c", |
| ], |
| deps = [ |
| "//java/android:utility", |
| "//java/common:theme", |
| ], |
| ) |
| """); |
| scratch.file( |
| "java/common/BUILD", |
| """ |
| android_library( |
| name = "common", |
| exports_manifest = 1, |
| manifest = "common/AndroidManifest.xml", |
| resource_files = ["common/res/values/common.xml"], |
| transitive_configs = ["//flags:a"], |
| deps = ["//java/android:core"], |
| ) |
| |
| android_library( |
| name = "theme", |
| exports_manifest = 1, |
| manifest = "theme/AndroidManifest.xml", |
| resource_files = ["theme/res/values/values.xml"], |
| transitive_configs = [ |
| "//flags:a", |
| "//flags:b", |
| "//flags:c", |
| ], |
| ) |
| """); |
| scratch.file( |
| "flags/BUILD", |
| """ |
| config_feature_flag( |
| name = "a", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_feature_flag( |
| name = "b", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| |
| config_feature_flag( |
| name = "c", |
| allowed_values = [ |
| "on", |
| "off", |
| ], |
| default_value = "off", |
| ) |
| """); |
| |
| assertThat(getBinaryMergeeManifests(getConfiguredTarget("//java/binary:application")).values()) |
| .containsExactly( |
| "//java/android:core", |
| "//java/android:utility", |
| "//java/binary:library", |
| "//java/common:common", |
| "//java/common:theme") |
| .inOrder(); |
| } |
| |
| @Test |
| public void testAndroidStarlarkApiNativeLibs() throws Exception { |
| scratch.file( |
| "java/a/fetch_native_libs.bzl", |
| """ |
| def _impl(ctx): |
| libs = ctx.attr.android_binary.android.native_libs |
| return [DefaultInfo(files = libs.values()[0])] |
| |
| fetch_native_libs = rule( |
| implementation = _impl, |
| attrs = { |
| "android_binary": attr.label(), |
| }, |
| ) |
| """); |
| scratch.file( |
| "java/a/BUILD", |
| """ |
| load("//java/a:fetch_native_libs.bzl", "fetch_native_libs") |
| |
| android_binary( |
| name = "app", |
| srcs = ["Main.java"], |
| manifest = "AndroidManifest.xml", |
| deps = [":cc"], |
| ) |
| |
| cc_library( |
| name = "cc", |
| srcs = ["cc.cc"], |
| ) |
| |
| fetch_native_libs( |
| name = "clibs", |
| android_binary = "app", |
| ) |
| """); |
| ConfiguredTarget clibs = getConfiguredTarget("//java/a:clibs"); |
| |
| assertThat( |
| ActionsTestUtil.baseArtifactNames( |
| clibs.getProvider(FileProvider.class).getFilesToBuild())) |
| .containsExactly("libapp.so"); |
| } |
| |
| @Test |
| public void testInstrumentsManifestMergeEnabled() throws Exception { |
| // This is the incorrect behavior where dependency manifests are merged into the test apk. |
| useConfiguration("--noexperimental_disable_instrumentation_manifest_merge"); |
| scratch.file( |
| "java/com/google/android/instr/BUILD", |
| """ |
| android_binary( |
| name = "b1", |
| srcs = ["b1.java"], |
| instruments = ":b2", |
| manifest = "test/AndroidManifest.xml", |
| deps = [":lib"], |
| ) |
| |
| android_library( |
| name = "lib", |
| exports_manifest = 1, |
| manifest = "lib/AndroidManifest.xml", |
| resource_files = ["lib/res/values/strings.xml"], |
| ) |
| |
| android_binary( |
| name = "b2", |
| srcs = ["b2.java"], |
| manifest = "bin/AndroidManifest.xml", |
| deps = [":lib"], |
| ) |
| """); |
| assertThat( |
| getBinaryMergeeManifests(getConfiguredTarget("//java/com/google/android/instr:b1")) |
| .values()) |
| .containsExactly("//java/com/google/android/instr:lib"); |
| } |
| |
| @Test |
| public void testInstrumentsManifestMergeDisabled() throws Exception { |
| // This is the correct behavior where dependency manifests are not merged into the test apk. |
| useConfiguration("--experimental_disable_instrumentation_manifest_merge"); |
| scratch.file( |
| "java/com/google/android/instr/BUILD", |
| """ |
| android_binary( |
| name = "b1", |
| srcs = ["b1.java"], |
| instruments = ":b2", |
| manifest = "test/AndroidManifest.xml", |
| deps = [":lib"], |
| ) |
| |
| android_library( |
| name = "lib", |
| exports_manifest = 1, |
| manifest = "lib/AndroidManifest.xml", |
| resource_files = ["lib/res/values/strings.xml"], |
| ) |
| |
| android_binary( |
| name = "b2", |
| srcs = ["b2.java"], |
| manifest = "bin/AndroidManifest.xml", |
| deps = [":lib"], |
| ) |
| """); |
| assertThat( |
| getBinaryMergeeManifests(getConfiguredTarget("//java/com/google/android/instr:b1")) |
| .values()) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void testOptimizedJavaResourcesDisabled() throws Exception { |
| useConfiguration("--noexperimental_get_android_java_resources_from_optimized_jar"); |
| ConfiguredTarget ct = |
| scratchConfiguredTarget( |
| "java/a", |
| "a", |
| "android_binary(", |
| " name = 'a',", |
| " srcs = ['A.java'],", |
| " manifest = 'AndroidManifest.xml',", |
| " proguard_specs = ['proguard.cfg'],", |
| ")"); |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ct)); |
| Artifact extractedResources = getFirstArtifactEndingWith(artifacts, "extracted_a_deploy.jar"); |
| String args = Joiner.on(" ").join(getGeneratingSpawnActionArgs(extractedResources)); |
| assertThat(args).contains("/a_deploy.jar"); |
| assertThat(args).doesNotContain("a_proguard.jar"); |
| } |
| |
| @Test |
| public void testOptimizedJavaResourcesEnabled() throws Exception { |
| useConfiguration("--experimental_get_android_java_resources_from_optimized_jar"); |
| ConfiguredTarget ct = |
| scratchConfiguredTarget( |
| "java/a", |
| "a", |
| "android_binary(", |
| " name = 'a',", |
| " srcs = ['A.java'],", |
| " manifest = 'AndroidManifest.xml',", |
| " proguard_specs = ['proguard.cfg'],", |
| ")"); |
| Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ct)); |
| Artifact extractedResources = getFirstArtifactEndingWith(artifacts, "extracted_a_proguard.jar"); |
| String args = Joiner.on(" ").join(getGeneratingSpawnActionArgs(extractedResources)); |
| assertThat(args).doesNotContain("a_deploy.jar"); |
| assertThat(args).contains("/a_proguard.jar"); |
| } |
| } |