blob: a8a9191df65c6094632a2f2aa14ee777be4c2708 [file] [log] [blame]
// Copyright 2016 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 com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
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.CommandLineExpansionException;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.OutputGroupInfo;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.configuredtargets.FileConfiguredTarget;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
import com.google.devtools.build.lib.rules.java.JavaCompilationInfoProvider;
import com.google.devtools.build.lib.rules.java.JavaConfiguration.ImportDepsCheckingLevel;
import com.google.devtools.build.lib.rules.java.JavaInfo;
import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider;
import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider.OutputJar;
import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider;
import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link com.google.devtools.build.lib.rules.android.AarImport}. */
@RunWith(JUnit4.class)
public class AarImportTest extends BuildViewTestCase {
@Before
public void setup() throws Exception {
scratch.file(
"aapt2/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',",
" tags = ['__ANDROID_RULES_MIGRATION__'],",
")");
scratch.file(
"a/BUILD",
"java_import(",
" name = 'foo_src',",
" jars = ['foo-src.jar'],",
")",
"aar_import(",
" name = 'foo',",
" aar = 'foo.aar',",
" srcjar = ':foo_src',",
")",
"aar_import(",
" name = 'baz',",
" aar = 'baz.aar',",
")",
"java_import(",
" name = 'bar_src',",
" jars = ['bar-src.jar'],",
")",
"aar_import(",
" name = 'bar',",
" aar = 'bar.aar',",
" srcjar = ':bar_src',",
" deps = [':baz'],",
" exports = [':foo', '//java:baz'],",
")",
"aar_import(",
" name = 'intermediate',",
" aar = 'intermediate.aar',",
" deps = [':bar']",
")",
"aar_import(",
" name = 'last',",
" aar = 'last.aar',",
" deps = [':intermediate'],",
")",
"android_library(",
" name = 'library',",
" manifest = 'AndroidManifest.xml',",
" custom_package = 'com.google.arrimport',",
" resource_files = ['res/values/values.xml'],",
" srcs = ['App.java'],",
" deps = [':foo'],",
")");
scratch.file(
"java/BUILD",
"android_binary(",
" name = 'app',",
" manifest = 'AndroidManifest.xml',",
" srcs = ['App.java'],",
" deps = ['//a:bar'],",
")",
"android_library(",
" name = 'lib',",
" exports = ['//a:bar'],",
")",
"java_import(",
" name = 'baz',",
" jars = ['baz.jar'],",
" constraints = ['android'],",
")");
getAnalysisMock().ccSupport().setupCcToolchainConfigForCpu(mockToolsConfig, "armeabi-v7a");
}
@Test
public void aapt2RTxtProvided() throws Exception {
useConfiguration(
"--android_sdk=//aapt2/sdk:sdk",
"--experimental_skip_parsing_action",
"--android_aapt=aapt2");
ConfiguredTarget libTarget = getConfiguredTarget("//a:library");
NestedSet<Artifact> transitiveCompiledSymbols =
libTarget.get(AndroidResourcesInfo.PROVIDER).getTransitiveCompiledSymbols();
assertThat(transitiveCompiledSymbols).hasSize(2);
assertThat(
transitiveCompiledSymbols
.toSet()
.stream()
.map(Artifact::getRootRelativePathString)
.collect(Collectors.toSet()))
.containsExactly("a/foo_symbols/symbols.zip", "a/library_symbols/symbols.zip");
NestedSet<ValidatedAndroidResources> directResources =
libTarget.get(AndroidResourcesInfo.PROVIDER).getDirectAndroidResources();
assertThat(directResources).hasSize(1);
ValidatedAndroidResources resourceContainer = directResources.iterator().next();
Truth.assertThat(resourceContainer.getAapt2RTxt()).isNotNull();
}
@Test
public void testResourcesProvided() throws Exception {
ConfiguredTarget aarImportTarget = getConfiguredTarget("//a:foo");
NestedSet<ValidatedAndroidResources> directResources =
aarImportTarget.get(AndroidResourcesInfo.PROVIDER).getDirectAndroidResources();
assertThat(directResources).hasSize(1);
ValidatedAndroidResources resourceContainer = directResources.iterator().next();
assertThat(resourceContainer.getManifest()).isNotNull();
Artifact resourceTreeArtifact = Iterables.getOnlyElement(resourceContainer.getResources());
assertThat(resourceTreeArtifact.isTreeArtifact()).isTrue();
assertThat(resourceTreeArtifact.getExecPathString()).endsWith("_aar/unzipped/resources/foo");
NestedSet<ParsedAndroidAssets> directAssets =
aarImportTarget.get(AndroidAssetsInfo.PROVIDER).getDirectParsedAssets();
assertThat(directAssets).hasSize(1);
ParsedAndroidAssets assets = directAssets.iterator().next();
assertThat(assets.getSymbols()).isNotNull();
Artifact assetsTreeArtifact = Iterables.getOnlyElement(assets.getAssets());
assertThat(assetsTreeArtifact.isTreeArtifact()).isTrue();
assertThat(assetsTreeArtifact.getExecPathString()).endsWith("_aar/unzipped/assets/foo");
}
@Test
public void testSourceJarsProvided() throws Exception {
ConfiguredTarget aarImportTarget = getConfiguredTarget("//a:foo");
Iterable<Artifact> srcJars =
JavaInfo.getProvider(JavaSourceJarsProvider.class, aarImportTarget).getSourceJars();
assertThat(srcJars).hasSize(1);
Artifact srcJar = Iterables.getOnlyElement(srcJars);
assertThat(srcJar.getExecPathString()).endsWith("foo-src.jar");
Iterable<Artifact> srcInfoJars =
JavaInfo.getProvider(JavaSourceInfoProvider.class, aarImportTarget)
.getSourceJarsForJarFiles();
assertThat(srcInfoJars).hasSize(1);
Artifact srcInfoJar = Iterables.getOnlyElement(srcInfoJars);
assertThat(srcInfoJar.getExecPathString()).endsWith("foo-src.jar");
}
@Test
public void testSourceJarsCollectedTransitively() throws Exception {
ConfiguredTarget aarImportTarget = getConfiguredTarget("//a:bar");
Iterable<Artifact> srcJars =
JavaInfo.getProvider(JavaSourceJarsProvider.class, aarImportTarget)
.getTransitiveSourceJars();
assertThat(srcJars).hasSize(2);
assertThat(ActionsTestUtil.baseArtifactNames(srcJars))
.containsExactly("foo-src.jar", "bar-src.jar");
Iterable<Artifact> srcInfoJars =
JavaInfo.getProvider(JavaSourceInfoProvider.class, aarImportTarget)
.getSourceJarsForJarFiles();
assertThat(srcInfoJars).hasSize(1);
Artifact srcInfoJar = Iterables.getOnlyElement(srcInfoJars);
assertThat(srcInfoJar.getExecPathString()).endsWith("bar-src.jar");
}
@Test
public void testResourcesExtractor() throws Exception {
ValidatedAndroidResources resourceContainer =
getConfiguredTarget("//a:foo")
.get(AndroidResourcesInfo.PROVIDER)
.getDirectAndroidResources()
.toList()
.get(0);
Artifact resourceTreeArtifact = resourceContainer.getResources().get(0);
Artifact aarResourcesExtractor =
getHostConfiguredTarget(
ruleClassProvider.getToolsRepository() + "//tools/android:aar_resources_extractor")
.getProvider(FilesToRunProvider.class)
.getExecutable();
ParsedAndroidAssets assets =
getConfiguredTarget("//a:foo")
.get(AndroidAssetsInfo.PROVIDER)
.getDirectParsedAssets()
.toList()
.get(0);
Artifact assetsTreeArtifact = assets.getAssets().get(0);
assertThat(getGeneratingSpawnAction(resourceTreeArtifact).getArguments())
.containsExactly(
aarResourcesExtractor.getExecPathString(),
"--input_aar",
"a/foo.aar",
"--output_res_dir",
resourceTreeArtifact.getExecPathString(),
"--output_assets_dir",
assetsTreeArtifact.getExecPathString());
}
@Test
public void testDepsCheckerActionExistsForLevelError() throws Exception {
useConfiguration("--experimental_import_deps_checking=ERROR");
ConfiguredTarget aarImportTarget = getConfiguredTarget("//a:last");
OutputGroupInfo outputGroupInfo = aarImportTarget.get(OutputGroupInfo.SKYLARK_CONSTRUCTOR);
NestedSet<Artifact> outputGroup =
outputGroupInfo.getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
assertThat(outputGroup).hasSize(2);
// We should force asset merging to happen
Artifact mergedAssetsZip =
aarImportTarget.get(AndroidAssetsInfo.PROVIDER).getValidationResult();
assertThat(outputGroup).contains(mergedAssetsZip);
// Get the other artifact from the output group
Artifact artifact = ActionsTestUtil.getFirstArtifactEndingWith(outputGroup, "jdeps.proto");
assertThat(artifact.isTreeArtifact()).isFalse();
assertThat(artifact.getExecPathString()).endsWith("_aar/last/jdeps.proto");
SpawnAction checkerAction = getGeneratingSpawnAction(artifact);
List<String> arguments = checkerAction.getArguments();
assertThat(arguments)
.containsAtLeast(
"--bootclasspath_entry",
"--classpath_entry",
"--directdep",
"--input",
"--checking_mode=error",
"--rule_label",
"//a:last",
"--jdeps_output");
ensureArgumentsHaveClassEntryOptionWithSuffix(
arguments, "/intermediate/classes_and_libs_merged.jar");
assertThat(arguments.stream().filter(arg -> "--classpath_entry".equals(arg)).count())
.isEqualTo(9); // transitive classpath
assertThat(arguments.stream().filter(arg -> "--directdep".equals(arg)).count())
.isEqualTo(2); // 1 declared dep
}
@Test
public void testDepsCheckerActionExistsForLevelWarning() throws Exception {
checkDepsCheckerActionExistsForLevel(ImportDepsCheckingLevel.WARNING, "warning");
}
@Test
public void testDepsCheckerActionDoesNotExistsForLevelOff() throws Exception {
useConfiguration("--experimental_import_deps_checking=off");
ConfiguredTarget aarImportTarget = getConfiguredTarget("//a:bar");
OutputGroupInfo outputGroupInfo = aarImportTarget.get(OutputGroupInfo.SKYLARK_CONSTRUCTOR);
NestedSet<Artifact> outputGroup =
outputGroupInfo.getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
assertThat(outputGroup).hasSize(1);
assertThat(ActionsTestUtil.getFirstArtifactEndingWith(outputGroup, "jdeps.proto")).isNull();
}
private void checkDepsCheckerActionExistsForLevel(
ImportDepsCheckingLevel level, String expectedCheckingMode) throws Exception {
useConfiguration("--experimental_import_deps_checking=" + level.name());
ConfiguredTarget aarImportTarget = getConfiguredTarget("//a:bar");
OutputGroupInfo outputGroupInfo = aarImportTarget.get(OutputGroupInfo.SKYLARK_CONSTRUCTOR);
NestedSet<Artifact> outputGroup =
outputGroupInfo.getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
assertThat(outputGroup).hasSize(2);
// We should force asset merging to happen
Artifact mergedAssetsZip =
aarImportTarget.get(AndroidAssetsInfo.PROVIDER).getValidationResult();
assertThat(outputGroup).contains(mergedAssetsZip);
// Get the other artifact from the output group
Artifact artifact = ActionsTestUtil.getFirstArtifactEndingWith(outputGroup, "jdeps.proto");
checkDepsCheckerOutputArtifact(artifact, expectedCheckingMode);
}
private void checkDepsCheckerOutputArtifact(Artifact artifact, String expectedCheckingMode)
throws CommandLineExpansionException {
assertThat(artifact.isTreeArtifact()).isFalse();
assertThat(artifact.getExecPathString()).endsWith("_aar/bar/jdeps.proto");
SpawnAction checkerAction = getGeneratingSpawnAction(artifact);
List<String> arguments = checkerAction.getArguments();
assertThat(arguments)
.containsAtLeast(
"--bootclasspath_entry",
"--classpath_entry",
"--input",
"--rule_label",
"--jdeps_output",
"--checking_mode=" + expectedCheckingMode);
}
/**
* Tests whether the given argument list contains an argument nameds "--classpath_entry" with a
* value that ends with the given suffix.
*/
private static void ensureArgumentsHaveClassEntryOptionWithSuffix(
List<String> arguments, String suffix) {
assertThat(arguments).isNotEmpty();
Iterator<String> iterator = arguments.iterator();
assertThat(iterator.hasNext()).isTrue();
String prev = iterator.next();
while (iterator.hasNext()) {
String current = iterator.next();
if ("--classpath_entry".equals(prev) && current.endsWith(suffix)) {
return; // Success.
}
prev = current;
}
Assert.fail(
"The arguments does not have the expected --classpath_entry: The arguments are "
+ arguments
+ ", and the expected class entry suffix is "
+ suffix);
}
@Test
public void testNativeLibsProvided() throws Exception {
ConfiguredTarget androidLibraryTarget = getConfiguredTarget("//java:lib");
NestedSet<Artifact> nativeLibs =
androidLibraryTarget.get(AndroidNativeLibsInfo.PROVIDER).getNativeLibs();
assertThat(nativeLibs)
.containsExactly(
ActionsTestUtil.getFirstArtifactEndingWith(nativeLibs, "foo/native_libs.zip"),
ActionsTestUtil.getFirstArtifactEndingWith(nativeLibs, "bar/native_libs.zip"),
ActionsTestUtil.getFirstArtifactEndingWith(nativeLibs, "baz/native_libs.zip"));
}
@Test
public void testNativeLibsMakesItIntoApk() throws Exception {
scratch.file(
"java/com/google/android/hello/BUILD",
"aar_import(",
" name = 'my_aar',",
" aar = 'my_aar.aar',",
")",
"android_binary(",
" name = 'my_app',",
" srcs = ['HelloApp.java'],",
" deps = [':my_aar'],",
" manifest = 'AndroidManifest.xml',",
")");
ConfiguredTarget binary = getConfiguredTarget("//java/com/google/android/hello:my_app");
SpawnAction apkBuilderAction =
(SpawnAction)
actionsTestUtil()
.getActionForArtifactEndingWith(getFilesToBuild(binary), "my_app_unsigned.apk");
assertThat(
Iterables.find(
apkBuilderAction.getArguments(),
Predicates.containsPattern("_aar/my_aar/native_libs.zip$")))
.isNotEmpty();
}
@Test
public void testClassesJarProvided() throws Exception {
ConfiguredTarget aarImportTarget = getConfiguredTarget("//a:foo");
Iterable<OutputJar> outputJars =
JavaInfo.getProvider(JavaRuleOutputJarsProvider.class, aarImportTarget).getOutputJars();
assertThat(outputJars).hasSize(1);
Artifact classesJar = outputJars.iterator().next().getClassJar();
assertThat(classesJar.getFilename()).isEqualTo("classes_and_libs_merged.jar");
SpawnAction jarMergingAction = ((SpawnAction) getGeneratingAction(classesJar));
assertThat(jarMergingAction.getArguments()).contains("--dont_change_compression");
}
@Test
public void testNoCustomJavaPackage() throws Exception {
ValidatedAndroidResources resourceContainer =
getConfiguredTarget("//a:foo")
.get(AndroidResourcesInfo.PROVIDER)
.getDirectAndroidResources()
.iterator()
.next();
// aar_import should not set a custom java package. Instead aapt will read the
// java package from the manifest.
assertThat(resourceContainer.getJavaPackage()).isNull();
}
@Test
public void testDepsPropagatesMergedAarJars() throws Exception {
Action appCompileAction =
getGeneratingAction(
ActionsTestUtil.getFirstArtifactEndingWith(
actionsTestUtil()
.artifactClosureOf(getFileConfiguredTarget("//java:app.apk").getArtifact()),
"libapp.jar"));
assertThat(appCompileAction).isNotNull();
assertThat(ActionsTestUtil.prettyArtifactNames(appCompileAction.getInputs()))
.containsAtLeast(
"a/_aar/foo/classes_and_libs_merged.jar",
"a/_aar/bar/classes_and_libs_merged.jar",
"a/_aar/baz/classes_and_libs_merged.jar");
}
@Test
public void testExportsPropagatesMergedAarJars() throws Exception {
FileConfiguredTarget appTarget = getFileConfiguredTarget("//java:app.apk");
Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(appTarget.getArtifact());
ConfiguredTarget bar = getConfiguredTarget("//a:bar");
Artifact barClassesJar =
ActionsTestUtil.getFirstArtifactEndingWith(artifacts, "bar/classes_and_libs_merged.jar");
// Verify that bar/classes_and_libs_merged.jar was in the artifact closure.
assertThat(barClassesJar).isNotNull();
assertThat(barClassesJar.getArtifactOwner().getLabel()).isEqualTo(bar.getLabel());
ConfiguredTarget foo = getConfiguredTarget("//a:foo");
Artifact fooClassesJar =
ActionsTestUtil.getFirstArtifactEndingWith(artifacts, "foo/classes_and_libs_merged.jar");
// Verify that foo/classes_and_libs_merged.jar was in the artifact closure.
assertThat(fooClassesJar).isNotNull();
assertThat(fooClassesJar.getArtifactOwner().getLabel()).isEqualTo(foo.getLabel());
ConfiguredTarget baz = getConfiguredTarget("//java:baz.jar");
Artifact bazJar = ActionsTestUtil.getFirstArtifactEndingWith(artifacts, "baz.jar");
// Verify that baz.jar was in the artifact closure
assertThat(bazJar).isNotNull();
assertThat(bazJar.getArtifactOwner().getLabel()).isEqualTo(baz.getLabel());
}
@Test
public void testExportsPropagatesResources() throws Exception {
FileConfiguredTarget appTarget = getFileConfiguredTarget("//java:app.apk");
Set<Artifact> artifacts = actionsTestUtil().artifactClosureOf(appTarget.getArtifact());
ConfiguredTarget bar = getConfiguredTarget("//a:bar");
Artifact barResources =
ActionsTestUtil.getFirstArtifactEndingWith(artifacts, "_aar/unzipped/resources/bar");
assertThat(barResources).isNotNull();
assertThat(barResources.getArtifactOwner().getLabel()).isEqualTo(bar.getLabel());
ConfiguredTarget foo = getConfiguredTarget("//a:foo");
Artifact fooResources =
ActionsTestUtil.getFirstArtifactEndingWith(artifacts, "_aar/unzipped/resources/foo");
assertThat(fooResources).isNotNull();
assertThat(fooResources.getArtifactOwner().getLabel()).isEqualTo(foo.getLabel());
}
@Test
public void testJavaCompilationArgsProvider() throws Exception {
useConfiguration("--experimental_import_deps_checking=ERROR");
ConfiguredTarget aarImportTarget = getConfiguredTarget("//a:bar");
JavaCompilationArgsProvider provider =
JavaInfo.getProvider(JavaCompilationArgsProvider.class, aarImportTarget);
assertThat(provider).isNotNull();
assertThat(artifactsToStrings(provider.getRuntimeJars()))
.containsExactly(
"bin a/_aar/bar/classes_and_libs_merged.jar",
"bin a/_aar/foo/classes_and_libs_merged.jar",
"bin a/_aar/baz/classes_and_libs_merged.jar",
"src java/baz.jar");
List<Artifact> compileTimeJavaDependencyArtifacts =
provider.getCompileTimeJavaDependencyArtifacts().toList();
assertThat(compileTimeJavaDependencyArtifacts).hasSize(2);
assertThat(
compileTimeJavaDependencyArtifacts
.stream()
.filter(artifact -> artifact.getExecPathString().endsWith("/_aar/foo/jdeps.proto"))
.collect(Collectors.toList()))
.hasSize(1);
assertThat(
compileTimeJavaDependencyArtifacts
.stream()
.filter(artifact -> artifact.getExecPathString().endsWith("/_aar/bar/jdeps.proto"))
.collect(Collectors.toList()))
.hasSize(1);
}
@Test
public void testFailsWithoutAndroidSdk() throws Exception {
scratch.file("sdk/BUILD", "alias(", " name = 'sdk',", " actual = 'doesnotexist',", ")");
useConfiguration("--android_sdk=//sdk");
checkError(
"aar",
"aar",
"No Android SDK found. Use the --android_sdk command line option to specify one.",
"aar_import(",
" name = 'aar',",
" aar = 'a.aar',",
")");
}
@Test
public void testExportsManifest() throws Exception {
Artifact binaryMergedManifest =
getConfiguredTarget("//java:app").get(ApkInfo.PROVIDER).getMergedManifest();
// Compare root relative path strings instead of artifacts due to difference in configuration
// caused by the Android split transition.
assertThat(
Iterables.transform(
getGeneratingAction(binaryMergedManifest).getInputs(),
Artifact::getRootRelativePathString))
.containsAtLeast(getAndroidManifest("//a:foo"), getAndroidManifest("//a:bar"));
}
private String getAndroidManifest(String aarImport) throws Exception {
return getConfiguredTarget(aarImport)
.get(AndroidResourcesInfo.PROVIDER)
.getDirectAndroidResources()
.toList()
.get(0)
.getManifest()
.getRootRelativePathString();
}
@Test
public void testTransitiveExports() throws Exception {
assertThat(getConfiguredTarget("//a:bar").get(JavaInfo.PROVIDER).getTransitiveExports())
.containsExactly(
Label.parseAbsolute("//a:foo", ImmutableMap.of()),
Label.parseAbsolute("//java:baz", ImmutableMap.of()));
}
@Test
public void testRClassFromAarImportInCompileClasspath() throws Exception {
NestedSet<Artifact> compilationClasspath =
JavaInfo.getProvider(JavaCompilationInfoProvider.class, getConfiguredTarget("//a:library"))
.getCompilationClasspath();
assertThat(
compilationClasspath.toList().stream()
.filter(artifact -> artifact.getFilename().equalsIgnoreCase("foo_resources.jar"))
.count())
.isEqualTo(1);
}
}