diff options
author | 2016-11-21 17:54:46 +0000 | |
---|---|---|
committer | 2016-11-21 20:04:46 +0000 | |
commit | 2aada22ebb0671a3ee772888bf52fe814a519197 (patch) | |
tree | b844a850254ad09a7f388d602a2e169e401539a0 /src/main/java | |
parent | 3b57ccfba7e67f85a0d3c31f9b813719e367fe9c (diff) |
Adds internal data binding support to Bazel, although this
is not yet exposed at the user level.
(https://developer.android.com/topic/libraries/data-binding/index.html).
See comments in DataBinding.java for the high-level overview of
how this works.
This does not yet work with
--experimental_use_parallel_android_resource_processing.
Exposing this at the user level additionally requires:
1) making the data binding support libraries available at an
expected place in the depot
2) Opting in android_binary / android_library rules through a new
"enable_data_binding" attribute.
--
MOS_MIGRATED_REVID=139797558
Diffstat (limited to 'src/main/java')
12 files changed, 556 insertions, 36 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java index 118228a740..834b1e1049 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java @@ -102,10 +102,8 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { } NestedSetBuilder<Artifact> filesBuilder = NestedSetBuilder.stableOrder(); - ImmutableList<TransitiveInfoCollection> deps = ImmutableList.<TransitiveInfoCollection>copyOf( - ruleContext.getPrerequisites("deps", Mode.TARGET)); - JavaCommon javaCommon = new JavaCommon( - ruleContext, javaSemantics, deps, deps, deps); + JavaCommon javaCommon = + AndroidCommon.createJavaCommonWithAndroidDataBinding(ruleContext, javaSemantics, false); javaSemantics.checkRule(ruleContext, javaCommon); javaSemantics.checkForProtoLibraryAndJavaProtoLibraryOnSameProto(ruleContext, javaCommon); @@ -225,7 +223,8 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { ProguardHelper.getProguardConfigArtifact(ruleContext, ""), createMainDexProguardSpec(ruleContext), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), - ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)); + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP), + DataBinding.isEnabled(ruleContext) ? DataBinding.getLayoutInfoFile(ruleContext) : null); ruleContext.assertNoErrors(); incrementalResourceApk = applicationManifest @@ -246,7 +245,8 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"), null, /* mainDexProguardCfg */ null, /* manifestOut */ - null /* mergedResourcesOut */); + null, /* mergedResourcesOut */ + null /* dataBindingInfoZip */); ruleContext.assertNoErrors(); instantRunResourceApk = applicationManifest @@ -266,7 +266,8 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { ProguardHelper.getProguardConfigArtifact(ruleContext, "instant_run"), null, /* mainDexProguardCfg */ null, /* manifestOut */ - null /* mergedResourcesOut */); + null /* mergedResourcesOut */, + null /* dataBindingInfoZip */); ruleContext.assertNoErrors(); splitResourceApk = applicationManifest @@ -286,7 +287,8 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"), null, /* mainDexProguardCfg */ null, /* manifestOut */ - null /* mergedResourcesOut */); + null /* mergedResourcesOut */, + null /* dataBindingInfoZip */); ruleContext.assertNoErrors(); } else { 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 455ee0229d..2105496744 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 @@ -22,6 +22,7 @@ import com.google.devtools.build.lib.analysis.AnalysisUtils; import com.google.devtools.build.lib.analysis.FileProvider; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.OutputGroupProvider; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; @@ -548,6 +549,9 @@ public class AndroidCommon { idlHelper.getIdlGeneratedJavaSources(), androidSemantics.getJavacArguments(ruleContext)) .setBootClassPath(bootclasspath); + if (DataBinding.isEnabled(ruleContext)) { + DataBinding.addAnnotationProcessor(ruleContext, attributes); + } JavaCompilationArtifacts.Builder artifactsBuilder = new JavaCompilationArtifacts.Builder(); NestedSetBuilder<Artifact> jarsProducedForRuntime = NestedSetBuilder.<Artifact>stableOrder(); @@ -607,8 +611,10 @@ public class AndroidCommon { private JavaCompilationHelper initAttributes( JavaTargetAttributes.Builder attributes, JavaSemantics semantics) { - JavaCompilationHelper helper = new JavaCompilationHelper( - ruleContext, semantics, javaCommon.getJavacOpts(), attributes); + JavaCompilationHelper helper = new JavaCompilationHelper(ruleContext, semantics, + javaCommon.getJavacOpts(), attributes, + DataBinding.isEnabled(ruleContext) + ? DataBinding.processDeps(ruleContext, attributes) : ImmutableList.<Artifact>of()); helper.addLibrariesToAttributes(javaCommon.targetsTreatedAsDeps(ClasspathType.COMPILE_ONLY)); attributes.setRuleKind(ruleContext.getRule().getRuleClass()); @@ -961,4 +967,48 @@ public class AndroidCommon { } return builder.build(); } + + /** + * Returns a {@link JavaCommon} instance with Android data binding support. + * + * <p>Binaries need both compile-time and runtime support, while libraries only need compile-time + * support. + * + * <p>No rule needs <i>any</i> support if data binding is disabled. + */ + static JavaCommon createJavaCommonWithAndroidDataBinding(RuleContext ruleContext, + JavaSemantics semantics, boolean isLibrary) { + boolean useDataBinding = DataBinding.isEnabled(ruleContext); + + ImmutableList<Artifact> srcs = + ruleContext.getPrerequisiteArtifacts("srcs", RuleConfiguredTarget.Mode.TARGET).list(); + if (useDataBinding) { + srcs = ImmutableList.<Artifact>builder().addAll(srcs) + .add(DataBinding.createAnnotationFile(ruleContext, isLibrary)).build(); + } + + ImmutableList<TransitiveInfoCollection> compileDeps; + ImmutableList<TransitiveInfoCollection> runtimeDeps; + ImmutableList<TransitiveInfoCollection> bothDeps; + + if (isLibrary) { + compileDeps = JavaCommon.defaultDeps(ruleContext, semantics, ClasspathType.COMPILE_ONLY); + if (useDataBinding) { + compileDeps = DataBinding.addSupportLibs(ruleContext, compileDeps); + } + runtimeDeps = JavaCommon.defaultDeps(ruleContext, semantics, ClasspathType.RUNTIME_ONLY); + bothDeps = JavaCommon.defaultDeps(ruleContext, semantics, ClasspathType.BOTH); + } else { + // Binary: + List<? extends TransitiveInfoCollection> ruleDeps = + ruleContext.getPrerequisites("deps", RuleConfiguredTarget.Mode.TARGET); + compileDeps = useDataBinding + ? DataBinding.addSupportLibs(ruleContext, ruleDeps) + : ImmutableList.<TransitiveInfoCollection>copyOf(ruleDeps); + runtimeDeps = compileDeps; + bothDeps = compileDeps; + } + + return new JavaCommon(ruleContext, semantics, srcs, compileDeps, runtimeDeps, bothDeps); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java index 9eb88a1a24..90ad509ed1 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java @@ -64,7 +64,8 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { AndroidCommon.collectTransitiveInfo(ruleContext, Mode.TARGET)); NestedSet<Artifact> transitiveProguardConfigs = new ProguardLibrary(ruleContext).collectProguardSpecs(); - JavaCommon javaCommon = new JavaCommon(ruleContext, javaSemantics); + JavaCommon javaCommon = + AndroidCommon.createJavaCommonWithAndroidDataBinding(ruleContext, javaSemantics, true); javaSemantics.checkRule(ruleContext, javaCommon); AndroidCommon androidCommon = new AndroidCommon(javaCommon); @@ -93,8 +94,8 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { null, /* proguardCfgOut */ null, /* mainDexProguardCfg */ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), - // This is just to communicate the results from the merge step to the validator step. - ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)); + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP), + DataBinding.isEnabled(ruleContext) ? DataBinding.getLayoutInfoFile(ruleContext) : null); if (ruleContext.hasErrors()) { return null; } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java index 45ee4685f5..bbbc9b052a 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java @@ -83,6 +83,7 @@ public class AndroidResourcesProcessorBuilder { private String applicationId; private String versionName; private Artifact symbolsTxt; + private Artifact dataBindingInfoZip; private Artifact manifestOut; private Artifact mergedResourcesOut; @@ -107,6 +108,16 @@ public class AndroidResourcesProcessorBuilder { return this; } + /** + * The output zip for resource-processed data binding expressions (i.e. a zip of .xml files). + * If null, data binding processing is skipped (and data binding expressions aren't allowed in + * layout resources). + */ + public AndroidResourcesProcessorBuilder setDataBindingInfoZip(Artifact zip) { + this.dataBindingInfoZip = zip; + return this; + } + public AndroidResourcesProcessorBuilder withDependencies(ResourceDependencies resourceDeps) { this.dependencies = resourceDeps; return this; @@ -287,6 +298,11 @@ public class AndroidResourcesProcessorBuilder { builder.add("--applicationId").add(applicationId); } + if (dataBindingInfoZip != null) { + builder.addExecPath("--dataBindingInfoOut", dataBindingInfoZip); + outs.add(dataBindingInfoZip); + } + if (!Strings.isNullOrEmpty(customJavaPackage)) { // Sets an alternative java package for the generated R.java // this allows android rules to generate resources outside of the java{,tests} tree. diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java index cd7ee9be5c..3a00d2adb8 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java @@ -313,7 +313,8 @@ public final class ApplicationManifest { proguardCfg, null, /* Artifact mainDexProguardCfg */ null, /* Artifact manifestOut */ - null /* Artifact mergedResources */); + null /* Artifact mergedResources */, + null /* Artifact dataBindingInfoZip */); } /** Packages up the manifest with resource and assets from the LocalResourceContainer. */ @@ -349,7 +350,8 @@ public final class ApplicationManifest { null, /* Artifact proguardCfg */ null, /* Artifact mainDexProguardCfg */ manifestOut, - mergedResources); + mergedResources, + null /* Artifact dataBindingInfoZip */); } /** Packages up the manifest with resource and assets from the rule and dependent resources. */ @@ -368,7 +370,8 @@ public final class ApplicationManifest { Artifact proguardCfg, @Nullable Artifact mainDexProguardCfg, Artifact manifestOut, - Artifact mergedResources) throws InterruptedException { + Artifact mergedResources, + Artifact dataBindingInfoZip) throws InterruptedException { LocalResourceContainer data = new LocalResourceContainer.Builder(ruleContext) .withAssets( AndroidCommon.getAssetDir(ruleContext), @@ -404,7 +407,7 @@ public final class ApplicationManifest { proguardCfg, mainDexProguardCfg, manifestOut, - mergedResources); + mergedResources, dataBindingInfoZip); } private ResourceApk createApk( @@ -421,7 +424,8 @@ public final class ApplicationManifest { Artifact proguardCfg, @Nullable Artifact mainDexProguardCfg, Artifact manifestOut, - Artifact mergedResources) throws InterruptedException { + Artifact mergedResources, + Artifact dataBindingInfoZip) throws InterruptedException { ResourceContainer resourceContainer = checkForInlinedResources( maybeInlinedResourceContainer, resourceDeps.getResources(), // TODO(bazel-team): Figure out if we really need to check @@ -484,6 +488,7 @@ public final class ApplicationManifest { .setDensities(densities) .setProguardOut(proguardCfg) .setMainDexProguardOut(mainDexProguardCfg) + .setDataBindingInfoZip(dataBindingInfoZip) .setApplicationId(manifestValues.get("applicationId")) .setVersionCode(manifestValues.get("versionCode")) .setVersionName(manifestValues.get("versionName")); 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 new file mode 100644 index 0000000000..7290259437 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java @@ -0,0 +1,276 @@ +// 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 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.analysis.RuleConfiguredTarget; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider; +import com.google.devtools.build.lib.rules.java.JavaTargetAttributes; +import com.google.devtools.build.lib.syntax.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * Support logic for Bazel's + * <a href="https://developer.android.com/topic/libraries/data-binding/index.html">data binding</a> + * integration. + * + * <p>In short, data binding in Bazel works as follows: + * <ol> + * <li>If a rule enables data binding and has layout resources with data binding expressions, + * resource processing invokes the data binding library to preprocess these expressions, then + * strips them out before feeding the resources into aapt. A separate "layout info" XML file + * gets produced that contains the bindings.</li> + * <li>The data binding annotation processor gets activated on Java compilation. This processor + * reads a custom-generated <code>DataBindingInfo.java</code> which specifies the path to the + * layout info file (as an annotation). The processor reads that file and produces the + * corresponding Java classes that end-user code uses to access the resources.</li> + * <li>The data binding compile-time and runtime support libraries get linked into the binary's + * deploy jar.</li> + * </ol> + * + * <p>For data binding to work, the corresponding support libraries must be checked into the depot + * via the implicit dependencies specified inside this class. + * + * <p>Unless otherwise specified, all methods in this class assume the current rule applies data + * binding. Callers can intelligently trigger this logic by checking {@link #isEnabled}. + * + */ +public final class DataBinding { + /** + * The rule attribute supplying the data binding runtime/compile-time support libraries. + */ + private static final String DATABINDING_RUNTIME_ATTR = "$databinding_runtime"; + + /** + * The rule attribute supplying the data binding annotation processor. + */ + private static final String DATABINDING_ANNOTATION_PROCESSOR_ATTR = + "$databinding_annotation_processor"; + + /** + * Should data binding support be enabled for this rule? + * + * <p>This is true if either the rule or any of its transitive dependencies declares data binding + * support in its attributes. + * + * <p>Data binding incurs additional resource processing and compilation work as well as + * additional compile/runtime dependencies. But rules with data binding disabled will fail if + * any data binding expressions appear in their layout resources. + */ + public static boolean isEnabled(RuleContext ruleContext) { + if (ruleContext.attributes().has("enable_data_binding", Type.BOOLEAN) + && ruleContext.attributes().get("enable_data_binding", Type.BOOLEAN)) { + return true; + } else { + return !Iterables.isEmpty(ruleContext.getPrerequisites("deps", + RuleConfiguredTarget.Mode.TARGET, UsesDataBindingProvider.class)); + } + } + + /** + * Returns the file where data binding's resource processing produces binding xml. For + * example, given: + * + * <pre>{@code + * <layout> + * <data> + * <variable name="foo" type="String" /> + * </data> + * </layout> + * <LinearLayout> + * ... + * </LinearLayout> + * } + * </pre> + * + * <p>data binding strips out and processes this part: + * + * <pre>{@code + * <data> + * <variable name="foo" type="String" /> + * </data> + * } + * </pre> + * + * for each layout file with data binding expressions. Since this may produce multiple + * files, outputs are zipped up into a single container. + */ + static Artifact getLayoutInfoFile(RuleContext ruleContext) { + // The data binding library expects this to be called "layout-info.zip". + return ruleContext.getUniqueDirectoryArtifact("databinding", "layout-info.zip", + ruleContext.getBinOrGenfilesDirectory()); + } + + /** + * Adds the support libraries needed to compile/run Java code with data binding. + * + * <p>This excludes the annotation processor, which is injected separately as a Java plugin + * (see {@link #addAnnotationProcessor}). + */ + static ImmutableList<TransitiveInfoCollection> addSupportLibs(RuleContext ruleContext, + List<? extends TransitiveInfoCollection> deps) { + RuleConfiguredTarget.Mode mode = RuleConfiguredTarget.Mode.TARGET; + return ImmutableList.<TransitiveInfoCollection>builder() + .addAll(deps) + .addAll(ruleContext.getPrerequisites(DATABINDING_RUNTIME_ATTR, mode)) + .build(); + } + + /** + * Adds data binding's annotation processor as a plugin to the given Java compilation context. + * + * <p>This, in conjunction with {@link #createAnnotationFile} extends the Java compilation to + * translate data binding .xml into corresponding classes. + */ + static void addAnnotationProcessor(RuleContext ruleContext, + JavaTargetAttributes.Builder attributes) { + JavaPluginInfoProvider plugin = ruleContext.getPrerequisite( + DATABINDING_ANNOTATION_PROCESSOR_ATTR, RuleConfiguredTarget.Mode.TARGET, + JavaPluginInfoProvider.class); + for (String name : plugin.getProcessorClasses()) { + // For header compilation (see JavaHeaderCompileAction): + attributes.addApiGeneratingProcessorName(name); + // For full compilation: + attributes.addProcessorName(name); + } + // For header compilation (see JavaHeaderCompileAction): + attributes.addApiGeneratingProcessorPath(plugin.getProcessorClasspath()); + // For full compilation: + attributes.addProcessorPath(plugin.getProcessorClasspath()); + attributes.addAdditionalOutputs(getMetadataOutputs(ruleContext)); + } + + /** + * Creates and returns the generated Java source that data binding's annotation processor + * reads to translate layout info xml (from {@link #getLayoutInfoFile} into the classes that + * end user code consumes. + */ + static Artifact createAnnotationFile(RuleContext ruleContext, boolean isLibrary) { + Template template = + Template.forResource(DataBinding.class, "databinding_annotation_template.txt"); + + List<Substitution> subs = new ArrayList<>(); + subs.add(Substitution.of("%module_package%", AndroidCommon.getJavaPackage(ruleContext))); + // TODO(gregce): clarify or remove the sdk root + subs.add(Substitution.of("%sdk_root%", "/not/used")); + subs.add(Substitution.of("%layout_info_dir%", + getLayoutInfoFile(ruleContext).getExecPath().getParentDirectory().toString())); + subs.add(Substitution.of("%export_class_list_to%", "/tmp/exported_classes")); // Unused. + subs.add(Substitution.of("%is_library%", Boolean.toString(isLibrary))); + subs.add(Substitution.of("%min_sdk%", "14")); // TODO(gregce): update this + + Artifact output = ruleContext.getPackageRelativeArtifact( + String.format("databinding/%s/DataBindingInfo.java", ruleContext.getLabel().getName()), + ruleContext.getConfiguration().getGenfilesDirectory()); + + ruleContext.registerAction + (new TemplateExpansionAction(ruleContext.getActionOwner(), output, template, subs, false)); + + return output; + } + + /** + * Adds the appropriate {@link UsesDataBindingProvider} for a rule if it should expose one. + * + * <p>A rule exposes {@link UsesDataBindingProvider} if either it or its deps set + * {@code enable_data_binding = 1}. + */ + public static void maybeAddProvider(RuleConfiguredTargetBuilder builder, + RuleContext ruleContext) { + // Expose the data binding provider if this rule either applies data binding or exports a dep + // that applies it. + List<Artifact> dataBindingMetadataOutputs = new ArrayList<>(); + if (DataBinding.isEnabled(ruleContext)) { + dataBindingMetadataOutputs.addAll(getMetadataOutputs(ruleContext)); + } + if (ruleContext.getAttribute("exports") != null) { + for (UsesDataBindingProvider provider : ruleContext.getPrerequisites("exports", + RuleConfiguredTarget.Mode.TARGET, UsesDataBindingProvider.class)) { + dataBindingMetadataOutputs.addAll(provider.getMetadataOutputs()); + } + } + if (!dataBindingMetadataOutputs.isEmpty()) { + // QUESTION(gregce): does a rule need to propagate the metadata outputs of its deps, or do + // they get integrated automatically into its own outputs? + builder.addProvider(UsesDataBindingProvider.class, + new UsesDataBindingProvider(dataBindingMetadataOutputs)); + } + } + + /** + * Annotation processing creates the following metadata files that describe how data binding is + * applied. The full file paths include prefixes as implemented in {@link #getMetadataOutputs}. + */ + private static final ImmutableList<String> METADATA_OUTPUT_SUFFIXES = ImmutableList.<String>of( + "setter_store.bin", "layoutinfo.bin", "br.bin"); + + /** + * Returns metadata outputs from this rule's annotation processing that describe what it did with + * data binding. This is used by parent rules to ensure consistent binding patterns. + * + * <p>>For example, if an {@code android_binary} depends on an {@code android_library} in a + * different package, the {@code android_library}'s version gets packaged with the application + * jar, even though (due to resource merging) both modules compile against their own instances. + */ + public static List<Artifact> getMetadataOutputs(RuleContext ruleContext) { + ImmutableList.Builder<Artifact> outputs = ImmutableList.<Artifact>builder(); + String javaPackage = AndroidCommon.getJavaPackage(ruleContext); + Label ruleLabel = ruleContext.getRule().getLabel(); + String pathPrefix = + String.format( + "_javac/%s/lib%s_classes/%s/%s-", + ruleLabel.getName(), + ruleLabel.getPackageIdentifier().getPackageFragment().getBaseName(), + javaPackage.replace('.', '/'), + javaPackage); + for (String suffix : METADATA_OUTPUT_SUFFIXES) { + outputs.add(ruleContext.getBinArtifact(pathPrefix + suffix)); + } + return outputs.build(); + } + + /** + * Processes deps that also apply data binding. + * + * @param ruleContext the current rule + * @param attributes java compilation attributes. The directories of the deps' metadata outputs + * (see {@link #getMetadataOutputs}) are added to this rule's annotation processor classpath. + * @return the deps' metadata outputs. These need to be staged as compilation inputs to the + * current rule. + */ + static ImmutableList<Artifact> processDeps(RuleContext ruleContext, + JavaTargetAttributes.Builder attributes) { + ImmutableList.Builder<Artifact> dataBindingJavaInputs = ImmutableList.<Artifact>builder(); + dataBindingJavaInputs.add(DataBinding.getLayoutInfoFile(ruleContext)); + for (UsesDataBindingProvider p : ruleContext.getPrerequisites("deps", + RuleConfiguredTarget.Mode.TARGET, UsesDataBindingProvider.class)) { + for (Artifact dataBindingDepMetadata : p.getMetadataOutputs()) { + attributes.addProcessorPathDir(dataBindingDepMetadata.getExecPath().getParentDirectory()); + dataBindingJavaInputs.add(dataBindingDepMetadata); + } + } + return dataBindingJavaInputs.build(); + } +} + diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/UsesDataBindingProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/UsesDataBindingProvider.java new file mode 100644 index 0000000000..7f69dac2bd --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/UsesDataBindingProvider.java @@ -0,0 +1,40 @@ +// 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 com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; +import java.util.Collection; + +/** + * An Android rule that exposes this enables + * <a href="https://developer.android.com/topic/libraries/data-binding/index.html">data binding</a> + * on its resource processing and Java compilation. + */ +public final class UsesDataBindingProvider implements TransitiveInfoProvider { + private final ImmutableList<Artifact> metadataOutputs; + + public UsesDataBindingProvider(Collection<Artifact> metadataOutputs) { + this.metadataOutputs = ImmutableList.copyOf(metadataOutputs); + } + + /** + * Returns the metadata outputs from this rule's annotation processing that describe how it + * applies data binding. See {@link DataBinding#getMetadataOutputs} for details. + */ + public ImmutableList<Artifact> getMetadataOutputs() { + return metadataOutputs; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding_annotation_template.txt b/src/main/java/com/google/devtools/build/lib/rules/android/databinding_annotation_template.txt new file mode 100644 index 0000000000..f847a20be4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding_annotation_template.txt @@ -0,0 +1,27 @@ +package android.databinding.layouts; + +import android.databinding.BindingBuildInfo; + +/** + * Template for the file that feeds data binding's annotation processor. The + * processor reads the values set here to generate .java files that link XML + * data binding declarations (from layoutInfoDir) to app code. + */ +@BindingBuildInfo( + // Setting a random build ID triggers incremental recompiling for the + // annotation processor. But Bazel is already incrementally correct, so + // this is unnecessary. + buildId="not_used_here", + modulePackage="%module_package%", + sdkRoot="%sdk_root%", + // The layout info file's *directory* (not the file itself): + layoutInfoDir="%layout_info_dir%", + exportClassListTo="%export_class_list_to%", + isLibrary=%is_library%, + minSdk=%min_sdk%, + enableDebugLogs=false, + printEncodedError=true +) +public class DataBindingInfo { + /* This only exists for annotation processing. */ +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java index aeb96b58e9..9f528c44de 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java @@ -608,6 +608,14 @@ public class JavaCommon { return targetsTreatedAsDeps.get(type); } + /** + * Returns the default dependencies for the given classpath context. + */ + public static ImmutableList<TransitiveInfoCollection> defaultDeps(RuleContext ruleContext, + JavaSemantics semantics, ClasspathType type) { + return collectTargetsTreatedAsDeps(ruleContext, semantics, type); + } + private static ImmutableList<TransitiveInfoCollection> collectTargetsTreatedAsDeps( RuleContext ruleContext, JavaSemantics semantics, ClasspathType type) { ImmutableList.Builder<TransitiveInfoCollection> builder = new Builder<>(); @@ -798,8 +806,8 @@ public class JavaCommon { * @return the value of the neverlink attribute. */ public static final boolean isNeverLink(RuleContext ruleContext) { - return ruleContext.getRule().isAttrDefined("neverlink", Type.BOOLEAN) && - ruleContext.attributes().get("neverlink", Type.BOOLEAN); + return ruleContext.getRule().isAttrDefined("neverlink", Type.BOOLEAN) + && ruleContext.attributes().get("neverlink", Type.BOOLEAN); } private static NestedSet<Artifact> getFilesToCompile(Artifact classJar) { diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java index 9ae2dbee79..77e2b02a13 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java @@ -66,6 +66,7 @@ public final class JavaCompilationHelper { private final List<Artifact> translations = new ArrayList<>(); private boolean translationsFrozen; private final JavaSemantics semantics; + private final ImmutableList<Artifact> additionalJavaBaseInputs; private static final String DEFAULT_ATTRIBUTES_SUFFIX = ""; @@ -73,7 +74,8 @@ public final class JavaCompilationHelper { ImmutableList<String> javacOpts, JavaTargetAttributes.Builder attributes, JavaToolchainProvider javaToolchainProvider, NestedSet<Artifact> hostJavabase, - Iterable<Artifact> jacocoInstrumentation) { + Iterable<Artifact> jacocoInstrumentation, + ImmutableList<Artifact> additionalJavaBaseInputs) { this.ruleContext = ruleContext; this.javaToolchain = javaToolchainProvider; this.hostJavabase = hostJavabase; @@ -82,6 +84,16 @@ public final class JavaCompilationHelper { this.customJavacOpts = javacOpts; this.customJavacJvmOpts = javaToolchain.getJvmOptions(); this.semantics = semantics; + this.additionalJavaBaseInputs = additionalJavaBaseInputs; + } + + public JavaCompilationHelper(RuleContext ruleContext, JavaSemantics semantics, + ImmutableList<String> javacOpts, JavaTargetAttributes.Builder attributes, + JavaToolchainProvider javaToolchainProvider, + NestedSet<Artifact> hostJavabase, + Iterable<Artifact> jacocoInstrumentation) { + this(ruleContext, semantics, javacOpts, attributes, javaToolchainProvider, hostJavabase, + jacocoInstrumentation, ImmutableList.<Artifact>of()); } public JavaCompilationHelper(RuleContext ruleContext, JavaSemantics semantics, @@ -93,6 +105,16 @@ public final class JavaCompilationHelper { } public JavaCompilationHelper(RuleContext ruleContext, JavaSemantics semantics, + ImmutableList<String> javacOpts, JavaTargetAttributes.Builder attributes, + ImmutableList<Artifact> additionalJavaBaseInputs) { + this(ruleContext, semantics, javacOpts, attributes, + getJavaToolchainProvider(ruleContext), + getHostJavabaseInputsNonStatic(ruleContext), + getInstrumentationJars(ruleContext), + additionalJavaBaseInputs); + } + + public JavaCompilationHelper(RuleContext ruleContext, JavaSemantics semantics, JavaTargetAttributes.Builder attributes) { this(ruleContext, semantics, getDefaultJavacOptsFromRule(ruleContext), attributes); } @@ -152,6 +174,7 @@ public final class JavaCompilationHelper { builder.setManifestProtoOutput(manifestProtoOutput); builder.setGensrcOutputJar(gensrcOutputJar); builder.setOutputDepsProto(outputDepsProto); + builder.setAdditionalOutputs(attributes.getAdditionalOutputs()); builder.setMetadata(outputMetadata); builder.setInstrumentationJars(jacocoInstrumentation); builder.addSourceFiles(attributes.getSourceFiles()); @@ -164,6 +187,7 @@ public final class JavaCompilationHelper { builder.setTempDirectory(tempDir(outputJar)); builder.setClassDirectory(classDir(outputJar)); builder.addProcessorPaths(attributes.getProcessorPath()); + builder.addProcessorPathDirs(attributes.getProcessorPathDirs()); builder.addProcessorNames(attributes.getProcessorNames()); builder.setStrictJavaDeps(attributes.getStrictJavaDeps()); builder.setDirectJars(attributes.getDirectJars()); @@ -240,8 +264,8 @@ public final class JavaCompilationHelper { private boolean shouldInstrumentJar() { // TODO(bazel-team): What about source jars? - return getConfiguration().isCodeCoverageEnabled() && attributes.hasSourceFiles() && - InstrumentedFilesCollector.shouldIncludeLocalSources(getRuleContext()); + return getConfiguration().isCodeCoverageEnabled() && attributes.hasSourceFiles() + && InstrumentedFilesCollector.shouldIncludeLocalSources(getRuleContext()); } private boolean shouldUseHeaderCompilation() { @@ -302,7 +326,11 @@ public final class JavaCompilationHelper { builder.setDirectJars(attributes.getDirectJars()); builder.setRuleKind(attributes.getRuleKind()); builder.setTargetLabel(attributes.getTargetLabel()); - builder.setJavaBaseInputs(hostJavabase); + builder.setJavaBaseInputs( + NestedSetBuilder + .fromNestedSet(hostJavabase) + .addAll(additionalJavaBaseInputs) + .build()); builder.setJavacJar(javaToolchain.getJavac()); builder.build(javaToolchain); @@ -425,8 +453,8 @@ public final class JavaCompilationHelper { * targets acting as aliases have to be filtered out. */ private boolean generatesOutputDeps() { - return getJavaConfiguration().getGenerateJavaDeps() && - (attributes.hasSourceFiles() || attributes.hasSourceJars()); + return getJavaConfiguration().getGenerateJavaDeps() + && (attributes.hasSourceFiles() || attributes.hasSourceJars()); } /** @@ -466,7 +494,11 @@ public final class JavaCompilationHelper { JavaCompileAction.Builder builder = new JavaCompileAction.Builder(ruleContext, semantics); builder.setJavaExecutable( ruleContext.getHostConfiguration().getFragment(Jvm.class).getJavaExecutable()); - builder.setJavaBaseInputs(hostJavabase); + builder.setJavaBaseInputs( + NestedSetBuilder + .fromNestedSet(hostJavabase) + .addAll(additionalJavaBaseInputs) + .build()); builder.setTargetLabel(ruleContext.getLabel()); return builder; } diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java index eac7e6e80b..d1cc48ec1e 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java @@ -594,7 +594,8 @@ public final class JavaCompileAction extends AbstractAction { * @param outputJar output jar * @param compressJar if true compress the output jar * @param outputDepsProto the proto file capturing dependency information - * @param processorPath the classpath where javac should search for annotation processors + * @param processorPath the classpath files where javac should search for annotation processors + * @param processorPathDirs the classpath dirs where javac should search for annotation processors * @param processorNames the classes that javac should use as annotation processors * @param messages the message files for translation * @param resources the set of resources to put into the jar @@ -614,6 +615,7 @@ public final class JavaCompileAction extends AbstractAction { final boolean compressJar, final Artifact outputDepsProto, final List<Artifact> processorPath, + final Set<PathFragment> processorPathDirs, final List<String> processorNames, final Collection<Artifact> messages, final Map<PathFragment, Artifact> resources, @@ -660,8 +662,13 @@ public final class JavaCompileAction extends AbstractAction { } result.add(Joiner.on(pathSeparator).join(extdirs)); } - if (!processorPath.isEmpty()) { - result.addJoinExecPaths("--processorpath", pathSeparator, processorPath); + if (!processorPath.isEmpty() || !processorPathDirs.isEmpty()) { + ImmutableList.Builder<String> execPathStrings = ImmutableList.<String>builder(); + execPathStrings.addAll(Artifact.toExecPaths(processorPath)); + for (PathFragment processorPathDir : processorPathDirs) { + execPathStrings.add(processorPathDir.toString()); + } + result.addJoinStrings("--processorpath", pathSeparator, execPathStrings.build()); } if (!processorNames.isEmpty()) { result.add("--processors", processorNames); @@ -927,6 +934,7 @@ public final class JavaCompileAction extends AbstractAction { private Artifact gensrcOutputJar; private Artifact manifestProtoOutput; private Artifact outputDepsProto; + private Collection<Artifact> additionalOutputs; private Artifact paramFile; private Artifact metadata; private final Collection<Artifact> sourceFiles = new ArrayList<>(); @@ -953,6 +961,7 @@ public final class JavaCompileAction extends AbstractAction { private PathFragment tempDirectory; private PathFragment classDirectory; private final List<Artifact> processorPath = new ArrayList<>(); + private final Set<PathFragment> processorPathDirs = new LinkedHashSet<>(); private final List<String> processorNames = new ArrayList<>(); private String ruleKind; private Label targetLabel; @@ -1039,12 +1048,18 @@ public final class JavaCompileAction extends AbstractAction { Preconditions.checkState(javaExecutable.isAbsolute() ^ !javabaseInputs.isEmpty(), javaExecutable); - ArrayList<Artifact> outputs = new ArrayList<>(Collections2.filter(Arrays.asList( - outputJar, - metadata, - gensrcOutputJar, - manifestProtoOutput, - outputDepsProto), Predicates.notNull())); + ImmutableList.Builder<Artifact> outputsBuilder = ImmutableList.<Artifact>builder() + .addAll( + new ArrayList<>(Collections2.filter(Arrays.asList( + outputJar, + metadata, + gensrcOutputJar, + manifestProtoOutput, + outputDepsProto), Predicates.notNull()))); + if (additionalOutputs != null) { + outputsBuilder.addAll(additionalOutputs); + } + ImmutableList<Artifact> outputs = outputsBuilder.build(); CustomMultiArgv commonJavaBuilderArgs = commonJavaBuilderArgs( semantics, @@ -1057,6 +1072,7 @@ public final class JavaCompileAction extends AbstractAction { compressJar, outputDepsProto, processorPath, + processorPathDirs, processorNames, translations, resources, @@ -1180,6 +1196,11 @@ public final class JavaCompileAction extends AbstractAction { return this; } + public Builder setAdditionalOutputs(Collection<Artifact> outputs) { + this.additionalOutputs = outputs; + return this; + } + public Builder setMetadata(Artifact metadata) { this.metadata = metadata; return this; @@ -1293,6 +1314,11 @@ public final class JavaCompileAction extends AbstractAction { return this; } + public Builder addProcessorPathDirs(Collection<PathFragment> processorPathDirs) { + this.processorPathDirs.addAll(processorPathDirs); + return this; + } + public Builder addProcessorNames(Collection<String> processorNames) { this.processorNames.addAll(processorNames); return this; diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java index 300475520d..4f996ad095 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java @@ -70,6 +70,9 @@ public class JavaTargetAttributes { private final List<Artifact> nativeLibraries = new ArrayList<>(); private final Set<Artifact> processorPath = new LinkedHashSet<>(); + // Classpath directories can't be represented as artifacts (TreeArtifact isn't appropriate + // here since all we need is a path string to apply to the command line). + private final Set<PathFragment> processorPathDirs = new LinkedHashSet<>(); private final Set<String> processorNames = new LinkedHashSet<>(); private final Set<Artifact> apiGeneratingProcessorPath = new LinkedHashSet<>(); @@ -82,6 +85,8 @@ public class JavaTargetAttributes { private final List<Artifact> classPathResources = new ArrayList<>(); + private final Set<Artifact> additionalOutputs = new LinkedHashSet<>(); + private BuildConfiguration.StrictDepsMode strictJavaDeps = BuildConfiguration.StrictDepsMode.OFF; private final NestedSetBuilder<Artifact> directJars = NestedSetBuilder.naiveLinkOrder(); @@ -302,6 +307,12 @@ public class JavaTargetAttributes { return this; } + public Builder addProcessorPathDir(PathFragment dir) { + Preconditions.checkArgument(!built); + processorPathDirs.add(dir); + return this; + } + public Builder addApiGeneratingProcessorName(String processor) { Preconditions.checkArgument(!built); apiGeneratingProcessorNames.add(processor); @@ -326,6 +337,15 @@ public class JavaTargetAttributes { return this; } + /** + * Adds additional outputs to this target's compile action. + */ + public Builder addAdditionalOutputs(Iterable<Artifact> outputs) { + Preconditions.checkArgument(!built); + Iterables.addAll(additionalOutputs, outputs); + return this; + } + public JavaTargetAttributes build() { built = true; return new JavaTargetAttributes( @@ -336,6 +356,7 @@ public class JavaTargetAttributes { bootClassPath, nativeLibraries, processorPath, + processorPathDirs, processorNames, apiGeneratingProcessorPath, apiGeneratingProcessorNames, @@ -343,6 +364,7 @@ public class JavaTargetAttributes { messages, sourceJars, classPathResources, + additionalOutputs, directJars.build(), compileTimeDependencyArtifacts, ruleKind, @@ -387,6 +409,7 @@ public class JavaTargetAttributes { private final ImmutableList<Artifact> nativeLibraries; private final ImmutableSet<Artifact> processorPath; + private final ImmutableSet<PathFragment> processorPathDirs; private final ImmutableSet<String> processorNames; private final ImmutableSet<Artifact> apiGeneratingProcessorPath; @@ -398,6 +421,8 @@ public class JavaTargetAttributes { private final ImmutableList<Artifact> classPathResources; + private final ImmutableSet<Artifact> additionalOutputs; + private final NestedSet<Artifact> directJars; private final ImmutableList<Artifact> compileTimeDependencyArtifacts; private final String ruleKind; @@ -415,6 +440,7 @@ public class JavaTargetAttributes { List<Artifact> bootClassPath, List<Artifact> nativeLibraries, Set<Artifact> processorPath, + Set<PathFragment> processorPathDirs, Set<String> processorNames, Set<Artifact> apiGeneratingProcessorPath, Set<String> apiGeneratingProcessorNames, @@ -422,6 +448,7 @@ public class JavaTargetAttributes { List<Artifact> messages, List<Artifact> sourceJars, List<Artifact> classPathResources, + Set<Artifact> additionalOutputs, NestedSet<Artifact> directJars, List<Artifact> compileTimeDependencyArtifacts, String ruleKind, @@ -440,6 +467,7 @@ public class JavaTargetAttributes { this.bootClassPath = ImmutableList.copyOf(bootClassPath); this.nativeLibraries = ImmutableList.copyOf(nativeLibraries); this.processorPath = ImmutableSet.copyOf(processorPath); + this.processorPathDirs = ImmutableSet.copyOf(processorPathDirs); this.processorNames = ImmutableSet.copyOf(processorNames); this.apiGeneratingProcessorPath = ImmutableSet.copyOf(apiGeneratingProcessorPath); this.apiGeneratingProcessorNames = ImmutableSet.copyOf(apiGeneratingProcessorNames); @@ -447,6 +475,7 @@ public class JavaTargetAttributes { this.messages = ImmutableList.copyOf(messages); this.sourceJars = ImmutableList.copyOf(sourceJars); this.classPathResources = ImmutableList.copyOf(classPathResources); + this.additionalOutputs = ImmutableSet.copyOf(additionalOutputs); this.compileTimeDependencyArtifacts = ImmutableList.copyOf(compileTimeDependencyArtifacts); this.ruleKind = ruleKind; this.targetLabel = targetLabel; @@ -478,6 +507,10 @@ public class JavaTargetAttributes { return classPathResources; } + public ImmutableSet<Artifact> getAdditionalOutputs() { + return additionalOutputs; + } + private NestedSet<Artifact> getExcludedArtifacts() { return excludedArtifacts; } @@ -520,6 +553,10 @@ public class JavaTargetAttributes { return processorPath; } + public ImmutableSet<PathFragment> getProcessorPathDirs() { + return processorPathDirs; + } + public Collection<Artifact> getApiGeneratingProcessorPath() { return apiGeneratingProcessorPath; } |