blob: cc78d474ad414b1fd3e0b19013d256e4335bbe5d [file] [log] [blame]
// 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.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.extra.JavaCompileInfo;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.rules.java.JavaCompileAction;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for Bazel's Android data binding support.
*/
@RunWith(JUnit4.class)
public class AndroidDataBindingTest extends AndroidBuildViewTestCase {
private void writeDataBindingFiles() throws Exception {
scratch.file("java/android/library/BUILD",
"android_library(",
" name = 'lib_with_data_binding',",
" enable_data_binding = 1,",
" manifest = 'AndroidManifest.xml',",
" srcs = ['MyLib.java'],",
" resource_files = [],",
")");
scratch.file("java/android/library/MyLib.java",
"package android.library; public class MyLib {};");
scratch.file("java/android/binary/BUILD",
"android_binary(",
" name = 'app',",
" enable_data_binding = 1,",
" manifest = 'AndroidManifest.xml',",
" srcs = ['MyApp.java'],",
" deps = ['//java/android/library:lib_with_data_binding'],",
")");
scratch.file("java/android/binary/MyApp.java",
"package android.binary; public class MyApp {};");
}
private void writeDataBindingFilesWithNoResourcesDep() throws Exception {
scratch.file("java/android/lib_with_resource_files/BUILD",
"android_library(",
" name = 'lib_with_resource_files',",
" enable_data_binding = 1,",
" manifest = 'AndroidManifest.xml',",
" srcs = ['LibWithResourceFiles.java'],",
" resource_files = glob(['res/**']),",
")");
scratch.file("java/android/lib_with_resource_files/LibWithResourceFiles.java",
"package android.lib_with_resource_files; public class LibWithResourceFiles {};");
scratch.file("java/android/lib_no_resource_files/BUILD",
"android_library(",
" name = 'lib_no_resource_files',",
" enable_data_binding = 1,",
" srcs = ['LibNoResourceFiles.java'],",
" deps = ['//java/android/lib_with_resource_files'],",
")");
scratch.file("java/android/lib_no_resource_files/LibNoResourceFiles.java",
"package android.lib_no_resource_files; public class LibNoResourceFiles {};");
scratch.file("java/android/binary/BUILD",
"android_binary(",
" name = 'app',",
" enable_data_binding = 1,",
" manifest = 'AndroidManifest.xml',",
" srcs = ['MyApp.java'],",
" deps = ['//java/android/lib_no_resource_files'],",
")");
scratch.file("java/android/binary/MyApp.java",
"package android.binary; public class MyApp {};");
}
@Test
public void basicDataBindingIntegration() throws Exception {
writeDataBindingFiles();
ConfiguredTarget ctapp = getConfiguredTarget("//java/android/binary:app");
Set<Artifact> allArtifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ctapp));
// "Data binding"-enabled targets invoke resource processing with a request for data binding
// output:
Artifact libResourceInfoOutput = getFirstArtifactEndingWith(allArtifacts,
"databinding/lib_with_data_binding/layout-info.zip");
assertThat(getGeneratingSpawnActionArgs(libResourceInfoOutput))
.containsAllOf("--dataBindingInfoOut", libResourceInfoOutput.getExecPathString())
.inOrder();
Artifact binResourceInfoOutput = getFirstArtifactEndingWith(allArtifacts,
"databinding/app/layout-info.zip");
assertThat(getGeneratingSpawnActionArgs(binResourceInfoOutput))
.containsAllOf("--dataBindingInfoOut", binResourceInfoOutput.getExecPathString())
.inOrder();
// Java compilation includes the data binding annotation processor, the resource processor's
// output, and the auto-generated DataBindingInfo.java the annotation processor uses to figure
// out what to do:
JavaCompileAction libCompileAction = (JavaCompileAction) getGeneratingAction(
getFirstArtifactEndingWith(allArtifacts, "lib_with_data_binding.jar"));
assertThat(libCompileAction.getProcessorNames())
.contains("android.databinding.annotationprocessor.ProcessDataBinding");
assertThat(ActionsTestUtil.prettyArtifactNames(libCompileAction.getInputs()))
.containsAllOf(
"java/android/library/databinding/lib_with_data_binding/layout-info.zip",
"java/android/library/databinding/lib_with_data_binding/DataBindingInfo.java");
JavaCompileAction binCompileAction = (JavaCompileAction) getGeneratingAction(
getFirstArtifactEndingWith(allArtifacts, "app.jar"));
assertThat(binCompileAction.getProcessorNames())
.contains("android.databinding.annotationprocessor.ProcessDataBinding");
assertThat(ActionsTestUtil.prettyArtifactNames(binCompileAction.getInputs()))
.containsAllOf(
"java/android/binary/databinding/app/layout-info.zip",
"java/android/binary/databinding/app/DataBindingInfo.java");
}
@Test
public void dataBindingCompilationUsesMetadataFromDeps() throws Exception {
writeDataBindingFiles();
ConfiguredTarget ctapp = getConfiguredTarget("//java/android/binary:app");
Set<Artifact> allArtifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ctapp));
// The library's compilation doesn't include any of the -setter_store.bin, layoutinfo.bin, etc.
// files that store a dependency's data binding results (since the library has no deps).
// We check that they don't appear as compilation inputs.
JavaCompileAction libCompileAction = (JavaCompileAction)
getGeneratingAction(getFirstArtifactEndingWith(allArtifacts, "lib_with_data_binding.jar"));
assertThat(
Iterables.filter(libCompileAction.getInputs(),
ActionsTestUtil.getArtifactSuffixMatcher(".bin")))
.isEmpty();
// The binary's compilation includes the library's data binding results.
JavaCompileAction binCompileAction = (JavaCompileAction) getGeneratingAction(
getFirstArtifactEndingWith(allArtifacts, "app.jar"));
Iterable<Artifact> depMetadataInputs = Iterables.filter(
binCompileAction.getInputs(), ActionsTestUtil.getArtifactSuffixMatcher(".bin"));
final String depMetadataBaseDir = Iterables.getFirst(depMetadataInputs, null).getExecPath()
.getParentDirectory().toString();
ActionsTestUtil.execPaths(Iterables.filter(
binCompileAction.getInputs(), ActionsTestUtil.getArtifactSuffixMatcher(".bin")));
assertThat(ActionsTestUtil.execPaths(depMetadataInputs)).containsExactly(
depMetadataBaseDir + "/android.library-android.library-setter_store.bin",
depMetadataBaseDir + "/android.library-android.library-layoutinfo.bin",
depMetadataBaseDir + "/android.library-android.library-br.bin");
}
@Test
public void dataBindingAnnotationProcessorFlags() throws Exception {
writeDataBindingFiles();
ConfiguredTarget ctapp = getConfiguredTarget("//java/android/binary:app");
Set<Artifact> allArtifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ctapp));
JavaCompileAction binCompileAction = (JavaCompileAction) getGeneratingAction(
getFirstArtifactEndingWith(allArtifacts, "app.jar"));
String dataBindingFilesDir = targetConfig.getBinDirectory(RepositoryName.MAIN).getExecPath()
.getRelative("java/android/binary/databinding/app").getPathString();
ImmutableList<String> expectedJavacopts =
ImmutableList.of(
"-Aandroid.databinding.bindingBuildFolder=" + dataBindingFilesDir,
"-Aandroid.databinding.generationalFileOutDir=" + dataBindingFilesDir,
"-Aandroid.databinding.sdkDir=/not/used",
"-Aandroid.databinding.artifactType=APPLICATION",
"-Aandroid.databinding.xmlOutDir=" + dataBindingFilesDir,
"-Aandroid.databinding.exportClassListTo=/tmp/exported_classes",
"-Aandroid.databinding.modulePackage=android.binary",
"-Aandroid.databinding.minApi=14",
"-Aandroid.databinding.printEncodedErrors=0");
assertThat(paramFileArgsForAction(binCompileAction)).containsAllIn(expectedJavacopts);
// Regression test for b/63134122
JavaCompileInfo javaCompileInfo =
binCompileAction
.getExtraActionInfo(actionKeyContext)
.getExtension(JavaCompileInfo.javaCompileInfo);
assertThat(javaCompileInfo.getJavacOptList()).containsAllIn(expectedJavacopts);
}
@Test
public void dataBindingIncludesTransitiveDepsForLibsWithNoResources() throws Exception {
writeDataBindingFilesWithNoResourcesDep();
ConfiguredTarget ct = getConfiguredTarget("//java/android/binary:app");
Set<Artifact> allArtifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ct));
// Data binding resource processing outputs are expected for the app and libs with resources.
assertThat(getFirstArtifactEndingWith(allArtifacts,
"databinding/lib_with_resource_files/layout-info.zip")).isNotNull();
assertThat(getFirstArtifactEndingWith(allArtifacts, "databinding/app/layout-info.zip"))
.isNotNull();
// Compiling the app's Java source includes data binding metadata from the resource-equipped
// lib, but not the resource-empty one.
JavaCompileAction binCompileAction = (JavaCompileAction) getGeneratingAction(
getFirstArtifactEndingWith(allArtifacts, "app.jar"));
List<String> appJarInputs = ActionsTestUtil.prettyArtifactNames(binCompileAction.getInputs());
String libWithResourcesMetadataBaseDir = "java/android/binary/databinding/app/"
+ "dependent-lib-artifacts/java/android/lib_with_resource_files/databinding/"
+ "lib_with_resource_files/bin-files/android.lib_with_resource_files-";
assertThat(appJarInputs).containsAllOf(
"java/android/binary/databinding/app/layout-info.zip",
libWithResourcesMetadataBaseDir + "android.lib_with_resource_files-setter_store.bin",
libWithResourcesMetadataBaseDir + "android.lib_with_resource_files-layoutinfo.bin",
libWithResourcesMetadataBaseDir + "android.lib_with_resource_files-br.bin");
for (String compileInput : appJarInputs) {
assertThat(compileInput).doesNotMatch(".*lib_no_resource_files.*.bin");
}
}
@Test
public void libsWithNoResourcesOnlyRunAnnotationProcessor() throws Exception {
// Bazel skips resource processing because there are no new resources to process. But it still
// runs the annotation processor to ensure the Java compiler reads Java sources referenced by
// the deps' resources (e.g. "<variable type="some.package.SomeClass" />"). Without this,
// JavaBuilder's --reduce_classpath feature would strip out those sources as "unused" and fail
// the binary's compilation with unresolved symbol errors.
writeDataBindingFilesWithNoResourcesDep();
ConfiguredTarget ct = getConfiguredTarget("//java/android/lib_no_resource_files");
Iterable<Artifact> libArtifacts = getFilesToBuild(ct);
assertThat(getFirstArtifactEndingWith(libArtifacts, "_resources.jar")).isNull();
assertThat(getFirstArtifactEndingWith(libArtifacts, "layout-info.zip")).isNull();
JavaCompileAction libCompileAction = (JavaCompileAction) getGeneratingAction(
getFirstArtifactEndingWith(libArtifacts, "lib_no_resource_files.jar"));
// The annotation processor is attached to the Java compilation:
assertThat(paramFileArgsForAction(libCompileAction))
.containsAllOf(
"--processors", "android.databinding.annotationprocessor.ProcessDataBinding");
// The dummy .java file with annotations that trigger the annotation process is present:
assertThat(ActionsTestUtil.prettyArtifactNames(libCompileAction.getInputs()))
.contains("java/android/lib_no_resource_files/databinding/lib_no_resource_files/"
+ "DataBindingInfo.java");
}
@Test
public void missingDataBindingAttributeStillAnalyzes() throws Exception {
// When a library is missing enable_data_binding = 1, we expect it to fail in execution (because
// aapt doesn't know how to read the data binding expressions). But analysis should work.
scratch.file("java/android/library/BUILD",
"android_library(",
" name = 'lib_with_data_binding',",
" enable_data_binding = 1,",
" manifest = 'AndroidManifest.xml',",
" srcs = ['MyLib.java'],",
" resource_files = [],",
")");
scratch.file("java/android/library/MyLib.java",
"package android.library; public class MyLib {};");
scratch.file("java/android/binary/BUILD",
"android_binary(",
" name = 'app',",
" enable_data_binding = 0,",
" manifest = 'AndroidManifest.xml',",
" srcs = ['MyApp.java'],",
" deps = ['//java/android/library:lib_with_data_binding'],",
")");
scratch.file("java/android/binary/MyApp.java",
"package android.binary; public class MyApp {};");
assertThat(getConfiguredTarget("//java/android/binary:app")).isNotNull();
}
}