Expose Bazel's Android data binding support to users.

The logic is already in Bazel but wasn't available to build rules.
This change makes it available, but still requires data binding's
{build|run}time libraries to be checked into appropriate depot
spots for everything to work.

Followup changes will make those libraries easily available.

Issue: #2694
PiperOrigin-RevId: 153359861
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 51b444d..a3707e7 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -979,6 +979,7 @@
     ),
     resources = [
         "rules/android/android_device_stub_template.txt",
+        "rules/android/databinding_annotation_template.txt",
     ],
     deps = [
         ":build-base",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
index 279f252..feafff5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
@@ -740,6 +740,8 @@
         builder, skylarkApiProvider, filesToBuild, classJar, ANDROID_COLLECTION_SPEC);
     javaCommon.addGenJarsProvider(builder, skylarkApiProvider, genClassJar, genSourceJar);
 
+    DataBinding.maybeAddProvider(builder, ruleContext);
+
     return builder
         .setFilesToBuild(filesToBuild)
         .addSkylarkTransitiveInfo(JavaSkylarkApiProvider.NAME, skylarkApiProvider.build())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
index 7c289e3..81c141d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -35,10 +35,12 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet;
 import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
 import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
 import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
 import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
 import com.google.devtools.build.lib.packages.Rule;
@@ -55,6 +57,7 @@
 import com.google.devtools.build.lib.rules.java.ProguardHelper;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
 import com.google.devtools.build.lib.syntax.Printer;
+import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.util.FileType;
 import java.util.List;
 
@@ -440,7 +443,7 @@
    */
   public static final class AndroidResourceSupportRule implements RuleDefinition {
     @Override
-    public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+    public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
       return builder
           /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(manifest) -->
           The name of the Android manifest file, normally <code>AndroidManifest.xml</code>.
@@ -491,6 +494,43 @@
           libraries that will only be detected at runtime.
           <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
           .add(attr("custom_package", STRING))
+          /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(enable_data_binding) -->
+          If true, this rule processes
+          <a href="https://developer.android.com/topic/libraries/data-binding/index.html">data
+          binding</a> expressions in layout resources included through the
+          <a href="${link android_binary.resource_files}">resource_files</a> attribute. Without this
+          setting, data binding expressions produce build failures.
+          <p>
+          To build an Android app with data binding, you must also do the following:
+          <ol>
+            <li>Set this attribute for all Android rules that transitively depend on this one.
+              This is because of resource merging: when a rule declares data binding XML expressions
+              its dependers implicitly inherit those expressions. So they also need to build with
+              data binding in order to parse those expressions correctly.
+            <li>Add a <code>deps =</code> entry for the data binding runtime library to all targets
+            that set this attribute. The location of this library depends on your depot setup.
+          </ol>
+          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+          .add(attr("enable_data_binding", Type.BOOLEAN))
+          // The javac annotation processor from Android's data binding library that turns
+          // processed XML expressions into Java code.
+          .add(attr(DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, BuildType.LABEL)
+              // This has to be a computed default because the annotation processor is a
+              // java_plugin, which means it needs the Jvm configuration fragment. That conflicts
+              // with Android builds that use --experimental_disable_jvm.
+              // TODO(gregce): The Jvm dependency is only needed for the host configuration.
+              //   --experimental_disable_jvm is really intended for target configurations without
+              //   a JDK. So this case isn't conceptually a conflict. Clean this up so we can remove
+              //   this computed default.
+              .value(new Attribute.ComputedDefault("enable_data_binding") {
+                @Override
+                public Object getDefault(AttributeMap rule) {
+                  return rule.get("enable_data_binding", Type.BOOLEAN)
+                      ? env.getToolsLabel("//tools/android:databinding_annotation_processor")
+                      : null;
+                }
+              }))
+
           .build();
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java b/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java
index 6b74a72..eae6870 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java
@@ -61,9 +61,9 @@
  */
 public final class DataBinding {
   /**
-   * The rule attribute supplying the data binding annotation processor.
+   * The rule attribute supplying data binding's annotation processor.
    */
-  private static final String DATABINDING_ANNOTATION_PROCESSOR_ATTR =
+  public static final String DATABINDING_ANNOTATION_PROCESSOR_ATTR =
       "$databinding_annotation_processor";
 
   /**
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index 73b07a9..f631e5d 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -228,7 +228,9 @@
         .add("java_import(name = 'idlclass_import',")
         .add("            jars = [ 'idlclass.jar' ])")
         .add("exports_files(['adb', 'adb_static'])")
-        .add("sh_binary(name = 'android_runtest', srcs = ['empty.sh'])");
+        .add("sh_binary(name = 'android_runtest', srcs = ['empty.sh'])")
+        .add("java_plugin(name = 'databinding_annotation_processor',")
+        .add("    processor_class = 'android.databinding.annotationprocessor.ProcessDataBinding')");
 
     return androidBuildContents.build();
   }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingTest.java
new file mode 100644
index 0000000..8569eae
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingTest.java
@@ -0,0 +1,249 @@
+// 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");
+    }
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/BUILD b/src/test/java/com/google/devtools/build/lib/rules/android/BUILD
index f7d7e10..3d8368d 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/android/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/BUILD
@@ -149,3 +149,21 @@
         "//third_party:truth",
     ],
 )
+
+java_test(
+    name = "AndroidDataBindingTest",
+    srcs = ["AndroidDataBindingTest.java"],
+    deps = [
+        ":AndroidBuildViewTestCase",
+        "//src/main/java/com/google/devtools/build/lib:android-rules",
+        "//src/main/java/com/google/devtools/build/lib:build-base",
+        "//src/main/java/com/google/devtools/build/lib:java-compilation",
+        "//src/main/java/com/google/devtools/build/lib/actions",
+        "//src/main/java/com/google/devtools/build/lib/cmdline",
+        "//src/test/java/com/google/devtools/build/lib:actions_testutil",
+        "//src/test/java/com/google/devtools/build/lib:testutil",
+        "//third_party:guava",
+        "//third_party:junit4",
+        "//third_party:truth",
+    ],
+)
diff --git a/tools/android/BUILD.tools b/tools/android/BUILD.tools
index 0d684d0..782fea9 100644
--- a/tools/android/BUILD.tools
+++ b/tools/android/BUILD.tools
@@ -205,3 +205,8 @@
     name = "debug_keystore",
     srcs = ["bazel_debug.keystore"],
 )
+
+alias(
+    name = "databinding_annotation_processor",
+    actual = "@android_databinding//:annotation_processor",
+)