// 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.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
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 {};");
}
/**
* Returns the .params file contents of a {@link JavaCompileAction}
*/
private Iterable<String> getParamFileContents(JavaCompileAction action) {
Artifact paramFile = getFirstArtifactEndingWith(action.getInputs(), "-2.params");
return ((ParameterFileWriteAction) getGeneratingAction(paramFile)).getContents();
}
@Test
public void basicDataBindingIntegrationParallelResourceProcessing() throws Exception {
useConfiguration("--experimental_use_parallel_android_resource_processing");
basicDataBindingIntegration();
}
@Test
public void basicDataBindingIntegrationLegacyResourceProcessing() throws Exception {
useConfiguration("--noexperimental_use_parallel_android_resource_processing");
basicDataBindingIntegration();
}
private 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(getGeneratingSpawnAction(libResourceInfoOutput).getArguments())
.containsAllOf("--dataBindingInfoOut", libResourceInfoOutput.getExecPathString())
.inOrder();
Artifact binResourceInfoOutput = getFirstArtifactEndingWith(allArtifacts,
"databinding/app/layout-info.zip");
assertThat(getGeneratingSpawnAction(binResourceInfoOutput).getArguments())
.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 dataBindingCompilationUsesMetadataFromDepsParallelResourceProcessing()
throws Exception {
useConfiguration("--experimental_use_parallel_android_resource_processing");
dataBindingCompilationUsesMetadataFromDeps();
}
@Test
public void dataBindingCompilationUsesMetadataFromDepsLegacyResourceProcessing()
throws Exception {
useConfiguration("--noexperimental_use_parallel_android_resource_processing");
dataBindingCompilationUsesMetadataFromDeps();
}
private 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();
assertThat(getParamFileContents(binCompileAction))
.containsAllOf(
"-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");
}
@Test
public void dataBindingIncludesTransitiveDepsForLibsWithNoResources() 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 {};");
ConfiguredTarget ct = getConfiguredTarget("//java/android/binary:app");
Set<Artifact> allArtifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ct));
// Data binding resource processing outputs are expected only for libs with resources.
assertThat(getFirstArtifactEndingWith(allArtifacts,
"databinding/lib_no_resource_files/layout-info.zip")).isNull();
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");
}
}
}