// 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.truth.Truth.assertThat; import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode; import java.util.Set; import org.junit.Before; /** * Base class for testing the multidex code of android_binary and android_test. */ public class AndroidMultidexBaseTest extends BuildViewTestCase { @Before public final void createFiles() throws Exception { scratch.file("java/android/BUILD", "android_binary(name = 'app',", " srcs = ['A.java'],", " manifest = 'AndroidManifest.xml',", " resource_files = glob(['res/**']),", " )"); scratch.file("java/android/res/values/strings.xml", "Hello Android!"); scratch.file("java/android/A.java", "package android; public class A {};"); } /** * Internal helper method: given an android_binary rule label, check that it builds in * multidex mode. Three multidex variations are possible: "legacy", "manual_main_dex" and * "native". * * @param ruleLabel the android_binary rule label to test against * @param multidexMode the multidex mode used in the rule */ protected void internalTestMultidexBuildStructure( String ruleLabel, MultidexMode multidexMode) throws Exception { ConfiguredTarget binary = getConfiguredTarget(ruleLabel); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); Artifact finalDexOutput = getFirstArtifactEndingWith(artifacts, "/classes.dex.zip"); assertThat(finalDexOutput).isNotNull(); // Only created in legacy mode: Artifact strippedJar = getFirstArtifactEndingWith(artifacts, "main_dex_intermediate.jar"); Artifact mainDexList = getFirstArtifactEndingWith(artifacts, "main_dex_list.txt"); String ruleName = Label.parseAbsolute(ruleLabel).getName(); Artifact mainDexProguardSpec = getFirstArtifactEndingWith( artifacts, "main_dex_" + ruleName + "_proguard.cfg"); if (multidexMode == MultidexMode.LEGACY) { // First action: check that the stripped jar is generated through Proguard. assertThat(mainDexProguardSpec).isNotNull(); AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(getRuleContext(binary)); assertThat(strippedJar).isNotNull(); SpawnAction stripAction = getGeneratingSpawnAction(strippedJar); assertThat(stripAction.getCommandFilename()) .isEqualTo(sdk.getProguard().getExecutable().getExecPathString()); assertThat(stripAction.getInputs()).contains(mainDexProguardSpec); // Second action: The dexer consumes the stripped jar to create the main dex class list. assertThat(mainDexList).isNotNull(); SpawnAction mainDexAction = getGeneratingSpawnAction(mainDexList); assertThat(mainDexAction.getArguments()).containsAllOf( mainDexList.getExecPathString(), strippedJar.getExecPathString()).inOrder(); } else if (multidexMode == MultidexMode.MANUAL_MAIN_DEX) { // Manual main dex mode: strippedJar shouldn't exist and mainDexList should be provided. assertThat(strippedJar).isNull(); assertThat(mainDexList).isNotNull(); } else { // Native mode: these shouldn't exist. assertThat(strippedJar).isNull(); assertThat(mainDexList).isNull(); } Artifact dexMergerInput = getFirstArtifactEndingWith(artifacts, "classes.jar"); SpawnAction dexMergerAction = getGeneratingSpawnAction(finalDexOutput); ImmutableList.Builder argsBuilder = ImmutableList.builder() .add( "--input", dexMergerInput.getExecPathString(), "--output", finalDexOutput.getExecPathString()); if (multidexMode != MultidexMode.OFF) { argsBuilder.add("--multidex=minimal"); } if (multidexMode == MultidexMode.LEGACY || multidexMode == MultidexMode.MANUAL_MAIN_DEX) { argsBuilder.add("--main-dex-list", mainDexList.getExecPathString()); } assertThat(dexMergerAction.getRemainingArguments()) .containsExactlyElementsIn(argsBuilder.build()) .inOrder(); } /** * Internal helper method: given an android_binary rule label, check that the dex merger * runs is invoked with {@code --multidex=off}. */ protected void internalTestNonMultidexBuildStructure(String ruleLabel) throws Exception { ConfiguredTarget binary = getConfiguredTarget(ruleLabel); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); Artifact dexInput = getFirstArtifactEndingWith(artifacts, "classes.jar"); Artifact dexOutput = getFirstArtifactEndingWith(artifacts, "classes.dex.zip"); SpawnAction dexAction = getGeneratingSpawnAction(dexOutput); assertThat(dexAction.getRemainingArguments()) .containsAllOf( "--input", dexInput.getExecPathString(), "--output", dexOutput.getExecPathString(), "--multidex=off") .inOrder(); } }