From 4b00ab1b97e5711c2fd65eb8150923effac0dae7 Mon Sep 17 00:00:00 2001 From: ahumesky Date: Thu, 15 Nov 2018 16:23:46 -0800 Subject: [PATCH] Update Android rules for Databinding V2. RELNOTES[NEW]: Android Databinding v2 can be enabled with --experimental_android_databinding_v2. PiperOrigin-RevId: 221710069 --- .../build/lib/analysis/RuleContext.java | 5 + .../actions/ActionConstructionContext.java | 24 + .../lib/rules/android/AndroidCommon.java | 15 +- .../AndroidDataBindingProcessorBuilder.java | 98 ++++ .../lib/rules/android/AndroidDataContext.java | 40 +- .../AndroidResourcesProcessorBuilder.java | 26 +- .../lib/rules/android/AndroidRuleClasses.java | 5 + .../lib/rules/android/AndroidSkylarkData.java | 6 +- .../rules/android/BusyBoxActionBuilder.java | 12 + .../rules/android/ParsedAndroidResources.java | 6 +- .../android/databinding/DataBinding.java | 182 +++--- .../databinding/DataBindingContext.java | 23 +- .../databinding/DataBindingV1Context.java | 110 ++-- .../databinding/DataBindingV2Context.java | 264 ++++++++- .../databinding/DataBindingV2Provider.java | 87 +++ .../DisabledDataBindingV1Context.java | 25 +- .../DisabledDataBindingV2Context.java | 92 +++ .../databinding/UsesDataBindingProvider.java | 2 +- .../android/DataBindingV2ProviderApi.java | 99 ++++ .../android/AndroidDataBindingV2Test.java | 550 ++++++++++++++++++ .../rules/android/AndroidResourcesTest.java | 5 +- .../lib/rules/android/ResourceTestBase.java | 2 +- tools/android/BUILD.tools | 8 + 23 files changed, 1503 insertions(+), 183 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingProcessorBuilder.java create mode 100644 src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Provider.java create mode 100644 src/main/java/com/google/devtools/build/lib/rules/android/databinding/DisabledDataBindingV2Context.java create mode 100644 src/main/java/com/google/devtools/build/lib/skylarkbuildapi/android/DataBindingV2ProviderApi.java create mode 100644 src/test/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingV2Test.java diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java index 113d995e90140a..f1306a11a32bf7 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java @@ -731,6 +731,11 @@ public Artifact getUniqueDirectoryArtifact(String uniqueDirectorySuffix, String return getUniqueDirectoryArtifact(uniqueDirectorySuffix, relative, getBinOrGenfilesDirectory()); } + @Override + public Artifact getUniqueDirectoryArtifact(String uniqueDirectorySuffix, PathFragment relative) { + return getUniqueDirectoryArtifact(uniqueDirectorySuffix, relative, getBinOrGenfilesDirectory()); + } + /** * Creates an artifact in a directory that is unique to the rule, thus guaranteeing that it never * clashes with artifacts created by other rules. diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java index 2ce75c5c1b1353..ce9c3279a26edb 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java @@ -80,6 +80,30 @@ public interface ActionConstructionContext { */ Artifact getUniqueDirectoryArtifact(String uniqueDirectorySuffix, String relative); + /** + * Creates an artifact in a directory that is unique to the rule, thus guaranteeing that it never + * clashes with artifacts created by other rules. + * + * @param uniqueDirectorySuffix suffix of the directory - it will be prepended + */ + Artifact getUniqueDirectoryArtifact(String uniqueDirectorySuffix, PathFragment relative); + + /** + * Returns a path fragment qualified by the rule name and unique fragment to + * disambiguate artifacts produced from the source file appearing in + * multiple rules. + * + *

For example "pkg/dir/name" -> "pkg/<fragment>/rule/dir/name. + */ + public PathFragment getUniqueDirectory(PathFragment fragment); + + /** + * Returns the root of either the "bin" or "genfiles" tree, based on this target and the current + * configuration. The choice of which tree to use is based on the rule with which this target + * (which must be an OutputFile or a Rule) is associated. + */ + public ArtifactRoot getBinOrGenfilesDirectory(); + /** * Returns the root-relative path fragment under which output artifacts of this rule should go. * 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 0ac3bfa1c8a470..5a978421bd6a5b 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 @@ -878,10 +878,17 @@ static JavaCommon createJavaCommonWithAndroidDataBinding( JavaSemantics semantics, DataBindingContext dataBindingContext, boolean isLibrary) { - ImmutableList srcs = - dataBindingContext.addAnnotationFileToSrcs( - ruleContext.getPrerequisiteArtifacts("srcs", RuleConfiguredTarget.Mode.TARGET).list(), - ruleContext); + + ImmutableList ruleSources = + ruleContext.getPrerequisiteArtifacts("srcs", RuleConfiguredTarget.Mode.TARGET).list(); + + ImmutableList dataBindingSources = + dataBindingContext.getAnnotationSourceFiles(ruleContext); + + ImmutableList srcs = ImmutableList.builder() + .addAll(ruleSources) + .addAll(dataBindingSources) + .build(); ImmutableList compileDeps; ImmutableList runtimeDeps; diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingProcessorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingProcessorBuilder.java new file mode 100644 index 00000000000000..af31cf9d1722d8 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingProcessorBuilder.java @@ -0,0 +1,98 @@ +// Copyright 2018 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.actions.ArtifactRoot; +import com.google.devtools.build.lib.vfs.PathFragment; + +/** Builder for creating databinding processing action. */ +public class AndroidDataBindingProcessorBuilder { + + /** + * Creates and registers an action to strip databinding from layout xml and generate the layout + * info file. + * + * @param dataContext The android data context. + * @param androidResources The resources to process. + * @param appId The app id (the app's java package). + * @param dataBindingLayoutInfoOut The output layout info file to write. + * @return The new AndroidResources that has been processed by databinding. + */ + public static AndroidResources create( + AndroidDataContext dataContext, + AndroidResources androidResources, + String appId, + Artifact dataBindingLayoutInfoOut) { + + ImmutableList.Builder databindingProcessedResourcesBuilder = ImmutableList.builder(); + for (Artifact resource : androidResources.getResources()) { + + // Create resources that will be processed by databinding under paths that look like: + // + // /databinding-processed-resources/// + + Artifact databindingProcessedResource = + dataContext.getUniqueDirectoryArtifact("databinding-processed-resources", + resource.getRootRelativePath()); + + databindingProcessedResourcesBuilder.add(databindingProcessedResource); + } + ImmutableList databindingProcessedResources = + databindingProcessedResourcesBuilder.build(); + + BusyBoxActionBuilder builder = BusyBoxActionBuilder.create(dataContext, "PROCESS_DATABINDING"); + + // Create output resource roots that correspond to the paths of the resources created above: + // + // /databinding-processed-resources// + // + // AndroidDataBindingProcessingAction will append each value of --resource_root to its + // corresponding --output_resource_root, so the only part that needs to be constructed here is + // + // /databinding-processed-resources/ + ArtifactRoot binOrGenfiles = dataContext.getBinOrGenfilesDirectory(); + PathFragment uniqueDir = + dataContext.getUniqueDirectory(PathFragment.create("databinding-processed-resources")); + PathFragment outputResourceRoot = binOrGenfiles.getExecPath().getRelative(uniqueDir); + + ImmutableList.Builder outputResourceRootsBuilder = ImmutableList.builder(); + for (PathFragment resourceRoot : androidResources.getResourceRoots()) { + + outputResourceRootsBuilder.add(outputResourceRoot); + + // The order of these matter, the input root and the output root have to be matched up + // because the resource processor will iterate over them in pairs. + builder.addFlag("--resource_root", resourceRoot.toString()); + builder.addFlag("--output_resource_root", outputResourceRoot.toString()); + } + + // Even though the databinding processor really only cares about layout files, we send + // all the resources so that the new resource root that is created for databinding processing + // can be used for later processing (e.g. aapt). It would be nice to send only the layout + // files, but then we'd have to mix roots and rely on sandboxing to "hide" the + // old unprocessed files, which might not work if, for example, the actions run locally. + builder.addInputs(androidResources.getResources()); + + builder.addOutputs(databindingProcessedResources); + + builder.addOutput("--dataBindingInfoOut", dataBindingLayoutInfoOut); + builder.addFlag("--appId", appId); + + builder.buildAndRegister("Processing data binding", "ProcessDatabinding"); + + return new AndroidResources(databindingProcessedResources, outputResourceRootsBuilder.build()); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDataContext.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDataContext.java index 45463ac8e2460d..aa65bc2a25e46e 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDataContext.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDataContext.java @@ -15,6 +15,7 @@ import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactRoot; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; @@ -24,6 +25,7 @@ import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction; import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidDataContextApi; +import com.google.devtools.build.lib.vfs.PathFragment; /** * Wraps common tools and settings used for working with Android assets, resources, and manifests. @@ -37,26 +39,30 @@ * are used in BusyBox actions. */ public class AndroidDataContext implements AndroidDataContextApi { + private final Label label; private final ActionConstructionContext actionConstructionContext; private final FilesToRunProvider busybox; private final AndroidSdkProvider sdk; private final boolean persistentBusyboxToolsEnabled; + private final boolean useDataBindingV2; public static AndroidDataContext forNative(RuleContext ruleContext) { return makeContext(ruleContext); } public static AndroidDataContext makeContext(RuleContext ruleContext) { + AndroidConfiguration androidConfig = ruleContext + .getConfiguration() + .getFragment(AndroidConfiguration.class); + return new AndroidDataContext( ruleContext.getLabel(), ruleContext, ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST), - ruleContext - .getConfiguration() - .getFragment(AndroidConfiguration.class) - .persistentBusyboxTools(), - AndroidSdkProvider.fromRuleContext(ruleContext)); + androidConfig.persistentBusyboxTools(), + AndroidSdkProvider.fromRuleContext(ruleContext), + androidConfig.useDataBindingV2()); } protected AndroidDataContext( @@ -64,12 +70,14 @@ protected AndroidDataContext( ActionConstructionContext actionConstructionContext, FilesToRunProvider busybox, boolean persistentBusyboxToolsEnabled, - AndroidSdkProvider sdk) { + AndroidSdkProvider sdk, + boolean useDataBindingV2) { this.label = label; this.persistentBusyboxToolsEnabled = persistentBusyboxToolsEnabled; this.actionConstructionContext = actionConstructionContext; this.busybox = busybox; this.sdk = sdk; + this.useDataBindingV2 = useDataBindingV2; } public Label getLabel() { @@ -111,6 +119,22 @@ public Artifact getUniqueDirectoryArtifact(String uniqueDirectorySuffix, String return actionConstructionContext.getUniqueDirectoryArtifact(uniqueDirectorySuffix, relative); } + public Artifact getUniqueDirectoryArtifact(String uniqueDirectorySuffix, PathFragment relative) { + return actionConstructionContext.getUniqueDirectoryArtifact(uniqueDirectorySuffix, relative); + } + + public PathFragment getUniqueDirectory(PathFragment fragment) { + return actionConstructionContext.getUniqueDirectory(fragment); + } + + public ArtifactRoot getBinOrGenfilesDirectory() { + return actionConstructionContext.getBinOrGenfilesDirectory(); + } + + public PathFragment getPackageDirectory() { + return actionConstructionContext.getPackageDirectory(); + } + public AndroidConfiguration getAndroidConfig() { return actionConstructionContext.getConfiguration().getFragment(AndroidConfiguration.class); } @@ -124,4 +148,8 @@ public boolean useDebug() { public boolean isPersistentBusyboxToolsEnabled() { return persistentBusyboxToolsEnabled; } + + public boolean useDataBindingV2() { + return useDataBindingV2; + } } 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 724f9893cbb008..f781d0507fe9b3 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 @@ -214,12 +214,6 @@ public ProcessedAndroidData build( StampedAndroidManifest primaryManifest, DataBindingContext dataBindingContext) { - if (aaptVersion == AndroidAaptVersion.AAPT2) { - createAapt2ApkAction(dataContext, primaryResources, primaryAssets, primaryManifest); - } else { - createAaptAction(dataContext, primaryResources, primaryAssets, primaryManifest); - } - // Wrap the new manifest, if any ProcessedAndroidManifest processedManifest = new ProcessedAndroidManifest( @@ -227,10 +221,28 @@ public ProcessedAndroidData build( primaryManifest.getPackage(), primaryManifest.isExported()); + // In databinding v2, this strips out the databinding and generates the layout info file. + AndroidResources databindingProcessedResources = dataBindingContext.processResources( + dataContext, primaryResources, processedManifest.getPackage()); + + if (aaptVersion == AndroidAaptVersion.AAPT2) { + createAapt2ApkAction( + dataContext, + databindingProcessedResources, + primaryAssets, + primaryManifest); + } else { + createAaptAction( + dataContext, + databindingProcessedResources, + primaryAssets, + primaryManifest); + } + // Wrap the parsed resources ParsedAndroidResources parsedResources = ParsedAndroidResources.of( - primaryResources, + databindingProcessedResources, symbols, /* compiledSymbols = */ null, dataContext.getLabel(), 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 407f73763bb304..df9fb65077fd06 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 @@ -442,6 +442,11 @@ Generated files (from genrules) can be referenced by attr(DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, LABEL) .cfg(HostTransition.INSTANCE) .value(env.getToolsLabel("//tools/android:databinding_annotation_processor"))) + .add( + attr(DataBinding.DATABINDING_EXEC_PROCESSOR_ATTR, LABEL) + .cfg(HostTransition.INSTANCE) + .exec() + .value(env.getToolsLabel("//tools/android:databinding_exec"))) .advertiseSkylarkProvider( SkylarkProviderIdentifier.forKey(AndroidResourcesInfo.PROVIDER.getKey())) .advertiseSkylarkProvider( diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java index d816107afab44c..f07d87e249e7ae 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java @@ -93,7 +93,7 @@ public AndroidResourcesInfo resourcesFromDeps( } return ResourceApk.processFromTransitiveLibraryData( ctx, - DataBinding.asDisabledDataBindingContext(), + DataBinding.getDisabledDataBindingContext(ctx), ResourceDependencies.fromProviders(deps, /* neverlink = */ neverlink), AssetDependencies.empty(), StampedAndroidManifest.createEmpty( @@ -374,7 +374,7 @@ public SkylarkDict processAarImportData( AndroidManifest.forAarImport(androidManifestArtifact), ResourceDependencies.fromProviders( getProviders(deps, AndroidResourcesInfo.PROVIDER), /* neverlink = */ false), - DataBinding.asDisabledDataBindingContext(), + DataBinding.getDisabledDataBindingContext(ctx), aaptVersion); MergedAndroidAssets mergedAssets = @@ -420,7 +420,7 @@ public SkylarkDict processLocalTestData( ctx, getAndroidSemantics(), errorReporter, - DataBinding.asDisabledDataBindingContext(), + DataBinding.getDisabledDataBindingContext(ctx), rawManifest, AndroidResources.from(errorReporter, getFileProviders(resources), "resource_files"), AndroidAssets.from( diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java index c1cd19fe3bc0c8..eb61855b672152 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java @@ -98,6 +98,12 @@ public BusyBoxActionBuilder addInput( return this; } + /** Adds the given input artifacts without any command line options. */ + public BusyBoxActionBuilder addInputs(Iterable inputs) { + this.inputs.addAll(inputs); + return this; + } + /** Adds an input artifact if it is non-null */ public BusyBoxActionBuilder maybeAddInput( @CompileTimeConstant String arg, @Nullable Artifact value) { @@ -150,6 +156,12 @@ public BusyBoxActionBuilder addOutput(@CompileTimeConstant String arg, Artifact return this; } + /** Adds the given output artifacts without adding any command line options. */ + public BusyBoxActionBuilder addOutputs(Iterable outputs) { + this.outputs.addAll(outputs); + return this; + } + /** Adds an output artifact if it is non-null */ public BusyBoxActionBuilder maybeAddOutput( @CompileTimeConstant String arg, @Nullable Artifact value) { diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ParsedAndroidResources.java b/src/main/java/com/google/devtools/build/lib/rules/android/ParsedAndroidResources.java index 256d6f3329d2ee..9dcf277c6e7a2b 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ParsedAndroidResources.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ParsedAndroidResources.java @@ -56,6 +56,10 @@ public static ParsedAndroidResources parseFrom( getDummyDataBindingArtifact(dataContext.getActionConstructionContext()))); } + // In databinding v2, this strips out the databinding and generates the layout info file. + AndroidResources databindingProcessedResources = + dataBindingContext.processResources(dataContext, resources, manifest.getPackage()); + return builder .setOutput(dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_MERGED_SYMBOLS)) .setCompiledSymbolsOutput( @@ -64,7 +68,7 @@ public static ParsedAndroidResources parseFrom( : null) .build( dataContext, - dataBindingContext.processResources(resources), + databindingProcessedResources, manifest, dataBindingContext); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBinding.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBinding.java index 6285a1a56ec11f..d21e344edc6ab2 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBinding.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBinding.java @@ -13,21 +13,26 @@ // limitations under the License. package com.google.devtools.build.lib.rules.android.databinding; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.Artifact; -import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; +import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.SymlinkAction; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.rules.android.AndroidCommon; import com.google.devtools.build.lib.rules.android.AndroidConfiguration; +import com.google.devtools.build.lib.rules.android.AndroidDataContext; import com.google.devtools.build.lib.rules.android.AndroidResources; import com.google.devtools.build.lib.syntax.Type; +import com.google.devtools.build.lib.util.ResourceFileLoader; import com.google.devtools.build.lib.vfs.PathFragment; +import java.io.IOException; import java.util.List; +import javax.annotation.Nullable; /** * Support logic for Bazel's METADATA_OUTPUT_SUFFIXES = - ImmutableList.of("setter_store.bin", "layoutinfo.bin", "br.bin"); - - /** The directory where the annotation processor looks for dep metadata. */ - private static final String DEP_METADATA_INPUT_DIR = "dependent-lib-artifacts"; - - /** The directory where the annotation processor write metadata output for the current rule. */ - private static final String METADATA_OUTPUT_DIR = "bin-files"; - /** * Should data binding support be enabled for this rule? * @@ -127,6 +118,15 @@ private static boolean isEnabled(RuleContext ruleContext) { ruleContext.attributes().get(ENABLE_DATA_BINDING_ATTR, Type.BOOLEAN)); } + /** Supplies a disabled (no-op) DataBindingContext. */ + public static DataBindingContext getDisabledDataBindingContext(AndroidDataContext ctx) { + if (ctx.useDataBindingV2()) { + return DISABLED_V2_CONTEXT; + } else { + return DISABLED_V1_CONTEXT; + } + } + /** Returns this rule's data binding base output dir (as an execroot-relative path). */ static PathFragment getDataBindingExecPath(RuleContext ruleContext) { return ruleContext @@ -135,6 +135,10 @@ static PathFragment getDataBindingExecPath(RuleContext ruleContext) { .getRelative(ruleContext.getUniqueDirectory("databinding")); } + static Artifact getLayoutInfoFile(ActionConstructionContext actionConstructionContext) { + return actionConstructionContext.getUniqueDirectoryArtifact("databinding", "layout-info.zip"); + } + /** Returns an artifact for the specified output under a standardized data binding base dir. */ static Artifact getDataBindingArtifact(RuleContext ruleContext, String relativePath) { PathFragment binRelativeBasePath = @@ -149,26 +153,31 @@ static String createProcessorFlag(String flag, String value) { return String.format("-Aandroid.databinding.%s=%s", flag, value); } - /** - * Adds the appropriate {@link UsesDataBindingProvider} for a rule if it should expose one. - * - *

A rule exposes {@link UsesDataBindingProvider} if either it or its deps set {@code - * enable_data_binding = 1}. - */ - static void maybeAddProvider( - List dataBindingMetadataOutputs, - RuleConfiguredTargetBuilder builder, - RuleContext ruleContext) { - // Expose the data binding provider if there are outputs. - dataBindingMetadataOutputs.addAll(getTransitiveMetadata(ruleContext, "exports")); - if (!AndroidResources.definesAndroidResources(ruleContext.attributes())) { - // If this rule doesn't declare direct resources, no resource processing is run so no data - // binding outputs are produced. In that case, we need to explicitly propagate data binding - // outputs from the deps to make sure they continue up the build graph. - dataBindingMetadataOutputs.addAll(getTransitiveMetadata(ruleContext, "deps")); - } - if (!dataBindingMetadataOutputs.isEmpty()) { - builder.addNativeDeclaredProvider(new UsesDataBindingProvider(dataBindingMetadataOutputs)); + /** Turns a key/value pair into a javac annotation processor flag received by data binding. */ + static String createProcessorFlag(String flag, Artifact value) { + return createProcessorFlag(flag, value.getExecPathString()); + } + + static ImmutableList getAnnotationFile(RuleContext ruleContext) { + // Add this rule's annotation processor input. If the rule doesn't have direct resources, + // there's no direct data binding info, so there's strictly no need for annotation processing. + // But it's still important to process the deps' .bin files so any Java class references get + // re-referenced so they don't get filtered out of the compilation classpath by JavaBuilder + // (which filters out classpath .jars that "aren't used": see --reduce_classpath). If data + // binding didn't reprocess a library's data binding expressions redundantly up the dependency + // chain (meaning each depender processes them again as if they were its own), this problem + // wouldn't happen. + try { + String contents = + ResourceFileLoader.loadResource( + DataBinding.class, "databinding_annotation_template.txt"); + Artifact annotationFile = getDataBindingArtifact(ruleContext, "DataBindingInfo.java"); + ruleContext.registerAction( + FileWriteAction.create(ruleContext, annotationFile, contents, false)); + return ImmutableList.of(annotationFile); + } catch (IOException e) { + ruleContext.ruleError("Cannot load annotation processor template: " + e.getMessage()); + return ImmutableList.of(); } } @@ -196,7 +205,10 @@ static List getTransitiveMetadata(RuleContext ruleContext, String attr * would be a class redefinition conflict. But by feeding the library's metadata outputs into the * binary's compilation, enough information is available to only use the first version. */ - static List getMetadataOutputs(RuleContext ruleContext) { + static ImmutableList getMetadataOutputs( + RuleContext ruleContext, + List metadataOutputSuffixes) { + if (!AndroidResources.definesAndroidResources(ruleContext.attributes())) { // If this rule doesn't define local resources, no resource processing was done, so it // doesn't produce data binding output. @@ -204,7 +216,7 @@ static List getMetadataOutputs(RuleContext ruleContext) { } ImmutableList.Builder outputs = ImmutableList.builder(); String javaPackage = AndroidCommon.getJavaPackage(ruleContext); - for (String suffix : METADATA_OUTPUT_SUFFIXES) { + for (String suffix : metadataOutputSuffixes) { // The annotation processor automatically creates files with this naming pattern under the // {@code -Aandroid.databinding.generationalFileOutDir} base directory. outputs.add( @@ -215,28 +227,50 @@ static List getMetadataOutputs(RuleContext ruleContext) { return outputs.build(); } + @Nullable + static Artifact getMetadataOutput( + RuleContext ruleContext, + String metadataOutputSuffix) { + + if (!AndroidResources.definesAndroidResources(ruleContext.attributes())) { + // If this rule doesn't define local resources, no resource processing was done, so it + // doesn't produce data binding output. + return null; + } + String javaPackage = AndroidCommon.getJavaPackage(ruleContext); + + // The annotation processor automatically creates files with this naming pattern under the + // {@code -Aandroid.databinding.generationalFileOutDir} base directory. + return getDataBindingArtifact( + ruleContext, + String.format("%s/%s-%s-%s", + METADATA_OUTPUT_DIR, javaPackage, javaPackage, metadataOutputSuffix)); + } + /** * Data binding's annotation processor reads the transitive metadata outputs of the target's deps - * (see {@link #getMetadataOutputs(RuleContext)}) in the directory specified by the processor flag - * {@code -Aandroid.databinding.bindingBuildFolder}. Since dependencies don't generate their - * outputs under a common directory, we symlink them into a common place here. + * (see {@link #getMetadataOutputs(RuleContext, List)}) in the directory specified by the + * processor flag {@code -Aandroid.databinding.bindingBuildFolder}. Since dependencies don't + * generate their outputs under a common directory, we symlink them into a common place here. * * @return the symlink paths of the transitive dep metadata outputs for this rule */ - static Artifact symlinkDepsMetadataIntoOutputTree( - RuleContext ruleContext, Artifact depMetadata) { + static Artifact symlinkDepsMetadataIntoOutputTree(RuleContext ruleContext, Artifact depMetadata) { + Label ruleLabel = ruleContext.getRule().getLabel(); Artifact symlink = getDataBindingArtifact( ruleContext, String.format( "%s/%s", DEP_METADATA_INPUT_DIR, depMetadata.getRootRelativePathString())); - ruleContext.registerAction(SymlinkAction.toArtifact( - ruleContext.getActionOwner(), - depMetadata, - symlink, - String.format( - "Symlinking dep metadata output %s for %s", depMetadata.getFilename(), ruleLabel))); + ruleContext.registerAction( + SymlinkAction.toArtifact( + ruleContext.getActionOwner(), + depMetadata, + symlink, + String.format( + "Symlinking dep metadata output %s for %s", depMetadata.getFilename(), ruleLabel))); return symlink; } + } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingContext.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingContext.java index 95802c298165c3..726a163c35503e 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingContext.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingContext.java @@ -17,6 +17,7 @@ import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.rules.android.AndroidDataContext; import com.google.devtools.build.lib.rules.android.AndroidResources; import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider; import java.util.function.BiConsumer; @@ -55,16 +56,22 @@ public interface DataBindingContext { /** The javac flags that are needed to configure data binding's annotation processor. */ void supplyJavaCoptsUsing( - RuleContext ruleContext, boolean isBinary, Consumer> consumer); + RuleContext ruleContext, + boolean isBinary, + Consumer> consumer); /** * Adds data binding's annotation processor as a plugin to the given Java compilation context. * *

This extends the Java compilation to translate data binding .xml into corresponding * classes. + * + * The BiConsumer accepts as its first argument the JavaPluginInfoProvider, and the list of + * outputs of the processor as the second argument. */ void supplyAnnotationProcessor( - RuleContext ruleContext, BiConsumer> consumer); + RuleContext ruleContext, + BiConsumer> consumer); /** * Processes deps that also apply data binding. @@ -83,8 +90,7 @@ void supplyAnnotationProcessor( *

This triggers the annotation processor. Annotation processor settings are configured * separately in {@link #supplyJavaCoptsUsing(RuleContext, boolean, Consumer)}. */ - ImmutableList addAnnotationFileToSrcs( - ImmutableList srcs, RuleContext ruleContext); + ImmutableList getAnnotationSourceFiles(RuleContext ruleContext); /** * Adds the appropriate {@link UsesDataBindingProvider} for a rule if it should expose one. @@ -94,5 +100,12 @@ ImmutableList addAnnotationFileToSrcs( */ void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext); - AndroidResources processResources(AndroidResources resources); + /** + * Process the given Android Resources for databinding. In databinding v2, this strips out the + * databinding and generates the layout info file. + */ + AndroidResources processResources( + AndroidDataContext dataContext, + AndroidResources resources, + String appId); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV1Context.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV1Context.java index bea5df8d045473..f136ec9fd7254a 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV1Context.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV1Context.java @@ -14,7 +14,6 @@ package com.google.devtools.build.lib.rules.android.databinding; import static com.google.devtools.build.lib.rules.android.databinding.DataBinding.createProcessorFlag; -import static com.google.devtools.build.lib.rules.android.databinding.DataBinding.getDataBindingExecPath; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -22,21 +21,25 @@ import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; -import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; import com.google.devtools.build.lib.rules.android.AndroidCommon; +import com.google.devtools.build.lib.rules.android.AndroidDataContext; import com.google.devtools.build.lib.rules.android.AndroidResources; import com.google.devtools.build.lib.rules.java.JavaInfo; import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider; -import com.google.devtools.build.lib.util.ResourceFileLoader; -import java.io.IOException; import java.util.List; -import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Consumer; final class DataBindingV1Context implements DataBindingContext { + /** + * 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 METADATA_OUTPUT_SUFFIXES_V1 = + ImmutableList.of("setter_store.bin", "layoutinfo.bin", "br.bin"); + private final ActionConstructionContext actionConstructionContext; DataBindingV1Context(ActionConstructionContext actionConstructionContext) { @@ -45,18 +48,15 @@ final class DataBindingV1Context implements DataBindingContext { @Override public void supplyLayoutInfo(Consumer consumer) { - consumer.accept(layoutInfoFile()); - } - - Artifact layoutInfoFile() { - return actionConstructionContext.getUniqueDirectoryArtifact("databinding", "layout-info.zip"); + consumer.accept(DataBinding.getLayoutInfoFile(actionConstructionContext)); } @Override public void supplyJavaCoptsUsing( RuleContext ruleContext, boolean isBinary, Consumer> consumer) { + ImmutableList.Builder flags = ImmutableList.builder(); - String metadataOutputDir = getDataBindingExecPath(ruleContext).getPathString(); + String metadataOutputDir = DataBinding.getDataBindingExecPath(ruleContext).getPathString(); // Directory where the annotation processor looks for deps metadata output. The annotation // processor automatically appends {@link DEP_METADATA_INPUT_DIR} to this path. Individual @@ -75,7 +75,7 @@ public void supplyJavaCoptsUsing( // The path where data binding's resource processor wrote its output (the data binding XML // expressions). The annotation processor reads this file to translate that XML into Java. - flags.add(createProcessorFlag("xmlOutDir", getDataBindingExecPath(ruleContext).toString())); + flags.add(createProcessorFlag("xmlOutDir", metadataOutputDir)); // Unused. flags.add(createProcessorFlag("exportClassListTo", "/tmp/exported_classes")); @@ -84,7 +84,9 @@ public void supplyJavaCoptsUsing( flags.add(createProcessorFlag("modulePackage", AndroidCommon.getJavaPackage(ruleContext))); // The minimum Android SDK compatible with this rule. - flags.add(createProcessorFlag("minApi", "14")); // TODO(gregce): update this + // TODO(bazel-team): This probably should be based on the actual min-sdk from the manifest, + // or an appropriate rule attribute. + flags.add(createProcessorFlag("minApi", "14")); // If enabled, produces cleaner output for Android Studio. flags.add(createProcessorFlag("printEncodedErrors", "0")); @@ -94,80 +96,64 @@ public void supplyJavaCoptsUsing( @Override public void supplyAnnotationProcessor( - RuleContext ruleContext, BiConsumer> consumer) { - consumer.accept( - JavaInfo.getProvider( - JavaPluginInfoProvider.class, - ruleContext.getPrerequisite( - DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, RuleConfiguredTarget.Mode.HOST)), - DataBinding.getMetadataOutputs(ruleContext)); + RuleContext ruleContext, + BiConsumer> consumer) { + + JavaPluginInfoProvider javaPluginInfoProvider = JavaInfo.getProvider( + JavaPluginInfoProvider.class, + ruleContext.getPrerequisite( + DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, RuleConfiguredTarget.Mode.HOST)); + + ImmutableList annotationProcessorOutputs = + DataBinding.getMetadataOutputs(ruleContext, METADATA_OUTPUT_SUFFIXES_V1); + + consumer.accept(javaPluginInfoProvider, annotationProcessorOutputs); } @Override public ImmutableList processDeps(RuleContext ruleContext) { + ImmutableList.Builder dataBindingJavaInputs = ImmutableList.builder(); if (AndroidResources.definesAndroidResources(ruleContext.attributes())) { - dataBindingJavaInputs.add(layoutInfoFile()); + dataBindingJavaInputs.add(DataBinding.getLayoutInfoFile(actionConstructionContext)); } + for (Artifact dataBindingDepMetadata : DataBinding.getTransitiveMetadata(ruleContext, "deps")) { dataBindingJavaInputs.add( DataBinding.symlinkDepsMetadataIntoOutputTree(ruleContext, dataBindingDepMetadata)); } + return dataBindingJavaInputs.build(); } @Override - public ImmutableList addAnnotationFileToSrcs( - ImmutableList srcs, RuleContext ruleContext) { - // Add this rule's annotation processor input. If the rule doesn't have direct resources, - // there's no direct data binding info, so there's strictly no need for annotation processing. - // But it's still important to process the deps' .bin files so any Java class references get - // re-referenced so they don't get filtered out of the compilation classpath by JavaBuilder - // (which filters out classpath .jars that "aren't used": see --reduce_classpath). If data - // binding didn't reprocess a library's data binding expressions redundantly up the dependency - // chain (meaning each depender processes them again as if they were its own), this problem - // wouldn't happen. - try { - String contents = - ResourceFileLoader.loadResource( - DataBinding.class, "databinding_annotation_template.txt"); - Artifact annotationFile = DataBinding - .getDataBindingArtifact(ruleContext, "DataBindingInfo.java"); - ruleContext.registerAction( - FileWriteAction.create(ruleContext, annotationFile, contents, false)); - return ImmutableList.builder().addAll(srcs).add(annotationFile).build(); - } catch (IOException e) { - ruleContext.ruleError("Cannot load annotation processor template: " + e.getMessage()); - return ImmutableList.of(); - } + public ImmutableList getAnnotationSourceFiles(RuleContext ruleContext) { + return DataBinding.getAnnotationFile(ruleContext); } @Override public void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext) { - List dataBindingMetadataOutputs = - Lists.newArrayList(DataBinding.getMetadataOutputs(ruleContext)); - DataBinding.maybeAddProvider(dataBindingMetadataOutputs, builder, ruleContext); - } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + List dataBindingMetadataOutputs = Lists.newArrayList( + DataBinding.getMetadataOutputs(ruleContext, METADATA_OUTPUT_SUFFIXES_V1)); + + // Expose the data binding provider if there are outputs. + dataBindingMetadataOutputs.addAll(DataBinding.getTransitiveMetadata(ruleContext, "exports")); + if (!AndroidResources.definesAndroidResources(ruleContext.attributes())) { + // If this rule doesn't declare direct resources, no resource processing is run so no data + // binding outputs are produced. In that case, we need to explicitly propagate data binding + // outputs from the deps to make sure they continue up the build graph. + dataBindingMetadataOutputs.addAll(DataBinding.getTransitiveMetadata(ruleContext, "deps")); } - if (o == null || getClass() != o.getClass()) { - return false; + if (!dataBindingMetadataOutputs.isEmpty()) { + builder.addNativeDeclaredProvider( + new UsesDataBindingProvider(dataBindingMetadataOutputs)); } - DataBindingV1Context that = (DataBindingV1Context) o; - return Objects.equals(actionConstructionContext, that.actionConstructionContext); - } - - @Override - public int hashCode() { - return actionConstructionContext.hashCode(); } @Override - public AndroidResources processResources(AndroidResources resources) { + public AndroidResources processResources( + AndroidDataContext dataContext, AndroidResources resources, String appId) { return resources; } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Context.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Context.java index 28661d572c5945..642a8f86774e4f 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Context.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Context.java @@ -13,63 +13,303 @@ // limitations under the License. package com.google.devtools.build.lib.rules.android.databinding; +import static com.google.devtools.build.lib.rules.android.databinding.DataBinding.createProcessorFlag; + import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; +import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; +import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.packages.BuildType; +import com.google.devtools.build.lib.rules.android.AndroidCommon; +import com.google.devtools.build.lib.rules.android.AndroidDataBindingProcessorBuilder; +import com.google.devtools.build.lib.rules.android.AndroidDataContext; import com.google.devtools.build.lib.rules.android.AndroidResources; +import com.google.devtools.build.lib.rules.java.JavaInfo; import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; class DataBindingV2Context implements DataBindingContext { - // TODO(b/112038432): Enable databinding v2. - @SuppressWarnings("unused") + /** + * 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 METADATA_OUTPUT_SUFFIXES_V2 = + ImmutableList.of("setter_store.bin", "br.bin"); + private final ActionConstructionContext actionContext; DataBindingV2Context(ActionConstructionContext actionContext) { this.actionContext = actionContext; - // TODO(b/112038432): Enable databinding v2. - throw new UnsupportedOperationException("V2 not implemented yet."); } @Override public void supplyLayoutInfo(Consumer consumer) { - + // In v2, The layout info file is generated in processResources below. } @Override public void supplyJavaCoptsUsing(RuleContext ruleContext, boolean isBinary, Consumer> consumer) { + ImmutableList.Builder flags = ImmutableList.builder(); + String metadataOutputDir = DataBinding.getDataBindingExecPath(ruleContext).getPathString(); + + // Directory where the annotation processor looks for deps metadata output. The annotation + // processor automatically appends {@link DEP_METADATA_INPUT_DIR} to this path. Individual + // files can be anywhere under this directory, recursively. + flags.add(createProcessorFlag("bindingBuildFolder", metadataOutputDir)); + + // Directory where the annotation processor should write this rule's metadata output. The + // annotation processor automatically appends {@link METADATA_OUTPUT_DIR} to this path. + flags.add(createProcessorFlag("generationalFileOutDir", metadataOutputDir)); + + // Path to the Android SDK installation (if available). + flags.add(createProcessorFlag("sdkDir", "/not/used")); + + // Whether the current rule is a library or binary. + flags.add(createProcessorFlag("artifactType", isBinary ? "APPLICATION" : "LIBRARY")); + + // Unused. + flags.add(createProcessorFlag("exportClassListTo", "/tmp/exported_classes")); + + // The Java package for the current rule. + flags.add(createProcessorFlag("modulePackage", AndroidCommon.getJavaPackage(ruleContext))); + + // The minimum Android SDK compatible with this rule. + // TODO(bazel-team): This probably should be based on the actual min-sdk from the manifest, + // or an appropriate rule attribute. + flags.add(createProcessorFlag("minApi", "14")); + + // If enabled, produces cleaner output for Android Studio. + flags.add(createProcessorFlag("printEncodedErrors", "0")); + + // V2 flags + flags.add(createProcessorFlag("enableV2", "1")); + + if (AndroidResources.definesAndroidResources(ruleContext.attributes())) { + flags.add(createProcessorFlag("classLogDir", getClassInfoFile(ruleContext))); + // The path where data binding's resource processor wrote its output (the data binding XML + // expressions). The annotation processor reads this file to translate that XML into Java. + flags.add(createProcessorFlag("xmlOutDir", DataBinding.getLayoutInfoFile(ruleContext))); + } else { + // send dummy files + flags.add(createProcessorFlag("classLogDir", "/tmp/no_resources")); + flags.add(createProcessorFlag("xmlOutDir", "/tmp/no_resources")); + } + + consumer.accept(flags.build()); } @Override - public void supplyAnnotationProcessor(RuleContext ruleContext, + public void supplyAnnotationProcessor( + RuleContext ruleContext, BiConsumer> consumer) { + JavaPluginInfoProvider javaPluginInfoProvider = JavaInfo.getProvider( + JavaPluginInfoProvider.class, + ruleContext.getPrerequisite( + DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, RuleConfiguredTarget.Mode.HOST)); + + ImmutableList annotationProcessorOutputs = + DataBinding.getMetadataOutputs(ruleContext, METADATA_OUTPUT_SUFFIXES_V2); + + consumer.accept(javaPluginInfoProvider, annotationProcessorOutputs); } @Override public ImmutableList processDeps(RuleContext ruleContext) { - return null; + + ImmutableList.Builder dataBindingJavaInputs = ImmutableList.builder(); + if (AndroidResources.definesAndroidResources(ruleContext.attributes())) { + dataBindingJavaInputs.add(DataBinding.getLayoutInfoFile(ruleContext)); + dataBindingJavaInputs.add(getClassInfoFile(ruleContext)); + } + + for (Artifact transitiveBRFile : getTransitiveBRFiles(ruleContext)) { + dataBindingJavaInputs.add( + DataBinding.symlinkDepsMetadataIntoOutputTree(ruleContext, transitiveBRFile)); + } + + for (Artifact directSetterStoreFile : getDirectSetterStoreFiles(ruleContext)) { + dataBindingJavaInputs.add( + DataBinding.symlinkDepsMetadataIntoOutputTree(ruleContext, directSetterStoreFile)); + } + + for (Artifact classInfo : getDirectClassInfo(ruleContext)) { + dataBindingJavaInputs.add( + DataBinding.symlinkDepsMetadataIntoOutputTree(ruleContext, classInfo)); + } + + return dataBindingJavaInputs.build(); } + private static ImmutableList getTransitiveBRFiles(RuleContext context) { + ImmutableList.Builder brFiles = ImmutableList.builder(); + if (context.attributes().has("deps", BuildType.LABEL_LIST)) { + + Iterable providers = context.getPrerequisites( + "deps", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + + for (DataBindingV2Provider provider : providers) { + brFiles.addAll(provider.getTransitiveBRFiles()); + } + } + return brFiles.build(); + } + + private static List getDirectSetterStoreFiles(RuleContext context) { + ImmutableList.Builder setterStoreFiles = ImmutableList.builder(); + if (context.attributes().has("deps", BuildType.LABEL_LIST)) { + + Iterable providers = context.getPrerequisites( + "deps", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + + for (DataBindingV2Provider provider : providers) { + setterStoreFiles.addAll(provider.getSetterStores()); + } + } + return setterStoreFiles.build(); + } + @Override - public ImmutableList addAnnotationFileToSrcs(ImmutableList srcs, - RuleContext ruleContext) { - return null; + public ImmutableList getAnnotationSourceFiles(RuleContext ruleContext) { + ImmutableList.Builder srcs = ImmutableList.builder(); + + srcs.addAll(DataBinding.getAnnotationFile(ruleContext)); + srcs.addAll(createBaseClasses(ruleContext)); + + return srcs.build(); + } + + private ImmutableList createBaseClasses(RuleContext ruleContext) { + + if (!AndroidResources.definesAndroidResources(ruleContext.attributes())) { + return ImmutableList.of(); // no resource, no base classes or class info + } + + Artifact layoutInfo = DataBinding.getLayoutInfoFile(ruleContext); + Artifact classInfoFile = getClassInfoFile(ruleContext); + Artifact srcOutFile = DataBinding.getDataBindingArtifact(ruleContext, "baseClassSrc.srcjar"); + + FilesToRunProvider exec = ruleContext + .getExecutablePrerequisite(DataBinding.DATABINDING_EXEC_PROCESSOR_ATTR, Mode.HOST); + + CustomCommandLine.Builder commandLineBuilder = CustomCommandLine.builder() + .add("GEN_BASE_CLASSES") + .addExecPath("-layoutInfoFiles", layoutInfo) + .add("-package", AndroidCommon.getJavaPackage(ruleContext)) + .addExecPath("-classInfoOut", classInfoFile) + .addExecPath("-sourceOut", srcOutFile) + .add("-zipSourceOutput", "true") + .add("-useAndroidX", "false"); + + List dependencyClientInfos = getDirectClassInfo(ruleContext); + for (Artifact artifact : dependencyClientInfos) { + commandLineBuilder.addExecPath("-dependencyClassInfoList", artifact); + } + + Action[] action = new SpawnAction.Builder() + .setExecutable(exec) + .setMnemonic("GenerateDataBindingBaseClasses") + .addInput(layoutInfo) + .addInputs(dependencyClientInfos) + .addOutput(classInfoFile) + .addOutput(srcOutFile) + .addCommandLine(commandLineBuilder.build()) + .build(ruleContext); + ruleContext.registerAction(action); + + return ImmutableList.of(srcOutFile); + } + + private static List getDirectClassInfo(RuleContext context) { + ImmutableList.Builder clientInfoFiles = ImmutableList.builder(); + if (context.attributes().has("deps", BuildType.LABEL_LIST)) { + + Iterable providers = context.getPrerequisites( + "deps", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + + for (DataBindingV2Provider provider : providers) { + clientInfoFiles.addAll(provider.getClassInfos()); + } + } + return clientInfoFiles.build(); } @Override public void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext) { + Artifact setterStore = DataBinding.getMetadataOutput(ruleContext, "setter_store.bin"); + Artifact br = DataBinding.getMetadataOutput(ruleContext, "br.bin"); + + ImmutableList.Builder setterStores = ImmutableList.builder(); + if (setterStore != null) { + setterStores.add(setterStore); + } + + ImmutableList.Builder classInfos = ImmutableList.builder(); + if (AndroidResources.definesAndroidResources(ruleContext.attributes())) { + Artifact classInfo = getClassInfoFile(ruleContext); + classInfos.add(classInfo); + } + + // android_binary doesn't have "exports" + if (ruleContext.attributes().has("exports", BuildType.LABEL_LIST)) { + Iterable exportsProviders = + ruleContext.getPrerequisites( + "exports", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + for (DataBindingV2Provider provider : exportsProviders) { + setterStores.addAll(provider.getSetterStores()); + classInfos.addAll(provider.getClassInfos()); + } + } + + NestedSetBuilder brFiles = new NestedSetBuilder<>(Order.STABLE_ORDER); + if (br != null) { + brFiles.add(br); + } + + Iterable depsProviders = ruleContext.getPrerequisites( + "deps", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + + for (DataBindingV2Provider provider : depsProviders) { + brFiles.addTransitive(provider.getTransitiveBRFiles()); + } + + builder.addNativeDeclaredProvider( + new DataBindingV2Provider( + classInfos.build(), + setterStores.build(), + brFiles.build())); } @Override - public AndroidResources processResources(AndroidResources resources) { - return null; + public AndroidResources processResources( + AndroidDataContext dataContext, AndroidResources resources, String appId) { + + AndroidResources databindingProcessedResources = AndroidDataBindingProcessorBuilder.create( + dataContext, + resources, + appId, + DataBinding.getLayoutInfoFile(actionContext)); + + return databindingProcessedResources; + + } + + private static Artifact getClassInfoFile(ActionConstructionContext context) { + return context.getUniqueDirectoryArtifact("databinding", "class-info.zip"); } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Provider.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Provider.java new file mode 100644 index 00000000000000..0ffd18054b8564 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Provider.java @@ -0,0 +1,87 @@ +// Copyright 2018 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.databinding; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.packages.BuiltinProvider; +import com.google.devtools.build.lib.packages.NativeInfo; +import com.google.devtools.build.lib.skylarkbuildapi.android.DataBindingV2ProviderApi; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.SkylarkList; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; + +/** + * A provider that exposes this enables data binding + * version 2 on its resource processing and Java compilation. + */ +public final class DataBindingV2Provider extends NativeInfo + implements DataBindingV2ProviderApi { + + public static final Provider PROVIDER = new Provider(); + + private final ImmutableList classInfos; + + private final ImmutableList setterStores; + + private final NestedSet transitiveBRFiles; + + public DataBindingV2Provider( + ImmutableList classInfos, + ImmutableList setterStores, + NestedSet transitiveBRFiles) { + super(PROVIDER); + this.classInfos = classInfos; + this.setterStores = setterStores; + this.transitiveBRFiles = transitiveBRFiles; + } + + @Override + public ImmutableList getClassInfos() { + return classInfos; + } + + @Override + public ImmutableList getSetterStores() { + return setterStores; + } + + @Override + public NestedSet getTransitiveBRFiles() { + return transitiveBRFiles; + } + + /** The provider can construct the DataBindingV2Provider provider. */ + public static class Provider extends BuiltinProvider + implements DataBindingV2ProviderApi.Provider { + + private Provider() { + super(NAME, DataBindingV2Provider.class); + } + + @Override + public DataBindingV2ProviderApi createInfo( + SkylarkList setterStores, + SkylarkList clientInfos, + SkylarkNestedSet transitiveBrFiles) throws EvalException { + + return new DataBindingV2Provider( + setterStores.getImmutableList(), + clientInfos.getImmutableList(), + transitiveBrFiles.getSet(Artifact.class)); + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DisabledDataBindingV1Context.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DisabledDataBindingV1Context.java index cae89c4fb27aa5..7b5990053228af 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DisabledDataBindingV1Context.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DisabledDataBindingV1Context.java @@ -17,6 +17,7 @@ import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.rules.android.AndroidDataContext; import com.google.devtools.build.lib.rules.android.AndroidResources; import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider; import java.util.ArrayList; @@ -39,18 +40,30 @@ public ImmutableList processDeps(RuleContext ruleContext) { } @Override - public ImmutableList addAnnotationFileToSrcs( - ImmutableList srcs, RuleContext ruleContext) { - return srcs; - }; + public ImmutableList getAnnotationSourceFiles(RuleContext ruleContext) { + return ImmutableList.of(); + } @Override public void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext) { - DataBinding.maybeAddProvider(new ArrayList<>(), builder, ruleContext); + + ArrayList dataBindingMetadataOutputs = new ArrayList<>(); + // Expose the data binding provider if there are outputs. + dataBindingMetadataOutputs.addAll(DataBinding.getTransitiveMetadata(ruleContext, "exports")); + if (!AndroidResources.definesAndroidResources(ruleContext.attributes())) { + // If this rule doesn't declare direct resources, no resource processing is run so no data + // binding outputs are produced. In that case, we need to explicitly propagate data binding + // outputs from the deps to make sure they continue up the build graph. + dataBindingMetadataOutputs.addAll(DataBinding.getTransitiveMetadata(ruleContext, "deps")); + } + if (!dataBindingMetadataOutputs.isEmpty()) { + builder.addNativeDeclaredProvider(new UsesDataBindingProvider(dataBindingMetadataOutputs)); + } } @Override - public AndroidResources processResources(AndroidResources resources) { + public AndroidResources processResources( + AndroidDataContext dataContext, AndroidResources resources, String appId) { return resources; } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DisabledDataBindingV2Context.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DisabledDataBindingV2Context.java new file mode 100644 index 00000000000000..5bbef27a87f769 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DisabledDataBindingV2Context.java @@ -0,0 +1,92 @@ +// Copyright 2018 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.databinding; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.packages.BuildType; +import com.google.devtools.build.lib.rules.android.AndroidDataContext; +import com.google.devtools.build.lib.rules.android.AndroidResources; +import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +class DisabledDataBindingV2Context implements DataBindingContext { + + @Override + public void supplyJavaCoptsUsing(RuleContext ruleContext, boolean isBinary, + Consumer> consumer) { } + + @Override + public void supplyAnnotationProcessor(RuleContext ruleContext, + BiConsumer> consumer) { } + + @Override + public ImmutableList processDeps(RuleContext ruleContext) { + return ImmutableList.of(); + } + + @Override + public ImmutableList getAnnotationSourceFiles(RuleContext ruleContext) { + return ImmutableList.of(); + } + + @Override + public void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext) { + + ImmutableList.Builder setterStores = ImmutableList.builder(); + ImmutableList.Builder classInfos = ImmutableList.builder(); + NestedSetBuilder brFiles = new NestedSetBuilder<>(Order.STABLE_ORDER); + + // android_binary doesn't have "exports" + if (ruleContext.attributes().has("exports", BuildType.LABEL_LIST)) { + Iterable exportsProviders = + ruleContext.getPrerequisites( + "exports", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + for (DataBindingV2Provider provider : exportsProviders) { + setterStores.addAll(provider.getSetterStores()); + classInfos.addAll(provider.getClassInfos()); + brFiles.addTransitive(provider.getTransitiveBRFiles()); + } + } + + + Iterable depsProviders = ruleContext.getPrerequisites( + "deps", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + + for (DataBindingV2Provider provider : depsProviders) { + brFiles.addTransitive(provider.getTransitiveBRFiles()); + } + + builder.addNativeDeclaredProvider( + new DataBindingV2Provider( + classInfos.build(), + setterStores.build(), + brFiles.build())); + } + + @Override + public AndroidResources processResources( + AndroidDataContext dataContext, AndroidResources resources, String appId) { + return resources; + } + + @Override + public void supplyLayoutInfo(Consumer consumer) { } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/UsesDataBindingProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/UsesDataBindingProvider.java index e6a4aaa22c96a8..62ce14f45eb74c 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/UsesDataBindingProvider.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/UsesDataBindingProvider.java @@ -58,4 +58,4 @@ public UsesDataBindingProvider createInfo(SkylarkList metadataOutputs) return new UsesDataBindingProvider(metadataOutputs.getImmutableList()); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/android/DataBindingV2ProviderApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/android/DataBindingV2ProviderApi.java new file mode 100644 index 00000000000000..2c0f28ef9619bd --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/android/DataBindingV2ProviderApi.java @@ -0,0 +1,99 @@ +// Copyright 2018 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.skylarkbuildapi.android; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.skylarkbuildapi.FileApi; +import com.google.devtools.build.lib.skylarkbuildapi.ProviderApi; +import com.google.devtools.build.lib.skylarkbuildapi.StructApi; +import com.google.devtools.build.lib.skylarkinterface.Param; +import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; +import com.google.devtools.build.lib.skylarkinterface.SkylarkConstructor; +import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.SkylarkList; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; + +/** + * An interface for a provider that exposes the use of data binding. + */ +@SkylarkModule( + name = "DataBindingV2Info", + doc = + "Do not use this module. It is intended for migration purposes only. If you depend on it, " + + "you will be broken when it is removed.", + documented = false) +public interface DataBindingV2ProviderApi extends StructApi { + + /** Name of this info object. */ + public static final String NAME = "DataBindingV2Info"; + + /** Returns the setter store files from this rule. */ + @SkylarkCallable(name = "setter_stores", structField = true, doc = "", documented = false) + ImmutableList getSetterStores(); + + /** Returns the client info files from this rule. */ + @SkylarkCallable(name = "client_infos", structField = true, doc = "", documented = false) + ImmutableList getClassInfos(); + + /** Returns the BR files from this rule and its dependencies. */ + @SkylarkCallable(name = "transitive_br_files", structField = true, doc = "", documented = false) + NestedSet getTransitiveBRFiles(); + + /** The provider implementing this can construct the DataBindingV2Info provider. */ + @SkylarkModule( + name = "Provider", + doc = + "Do not use this module. It is intended for migration purposes only. If you depend on " + + "it, you will be broken when it is removed.", + documented = false) + public interface Provider extends ProviderApi { + + @SkylarkCallable( + name = NAME, + doc = "The DataBindingV2Info constructor.", + documented = false, + parameters = { + @Param( + name = "setter_stores", + doc = "A list of artifacts of setter_stores.bin.", + positional = true, + named = false, + type = SkylarkList.class, + generic1 = FileApi.class), + @Param( + name = "client_infos", + doc = "A list of artifacts of client_infos.bin.", + positional = true, + named = false, + type = SkylarkList.class, + generic1 = FileApi.class), + @Param( + name = "transitive_br_files", + doc = "A list of artifacts of br.bin.", + positional = true, + named = false, + type = SkylarkNestedSet.class, + generic1 = FileApi.class), + }, + selfCall = true) + @SkylarkConstructor(objectType = DataBindingV2ProviderApi.class) + DataBindingV2ProviderApi createInfo( + SkylarkList setterStores, + SkylarkList clientInfos, + SkylarkNestedSet transitiveBrFiles) throws EvalException; + } +} \ No newline at end of file diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingV2Test.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingV2Test.java new file mode 100644 index 00000000000000..e62b1cfc7e7821 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidDataBindingV2Test.java @@ -0,0 +1,550 @@ +// 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 static com.google.devtools.build.lib.actions.util.ActionsTestUtil.prettyArtifactNames; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.truth.Truth; +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.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.cmdline.RepositoryName; +import com.google.devtools.build.lib.rules.android.databinding.DataBindingV2Provider; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for Bazel's Android data binding v2 support. */ +@RunWith(JUnit4.class) +public class AndroidDataBindingV2Test extends AndroidBuildViewTestCase { + + @Before + public void setDataBindingV2Flag() throws Exception { + useConfiguration("--experimental_android_databinding_v2"); + } + + 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 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: + SpawnAction libCompileAction = + (SpawnAction) + getGeneratingAction( + getFirstArtifactEndingWith(allArtifacts, "lib_with_data_binding.jar")); + assertThat(getProcessorNames(libCompileAction)) + .contains("android.databinding.annotationprocessor.ProcessDataBinding"); + assertThat(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"); + + SpawnAction binCompileAction = + (SpawnAction) getGeneratingAction(getFirstArtifactEndingWith(allArtifacts, "app.jar")); + assertThat(getProcessorNames(binCompileAction)) + .contains("android.databinding.annotationprocessor.ProcessDataBinding"); + assertThat(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 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. + SpawnAction libCompileAction = + (SpawnAction) + 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. + SpawnAction binCompileAction = + (SpawnAction) getGeneratingAction(getFirstArtifactEndingWith(allArtifacts, "app.jar")); + Iterable 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-br.bin"); + } + + @Test + public void dataBindingAnnotationProcessorFlags() throws Exception { + + writeDataBindingFiles(); + + ConfiguredTarget ctapp = getConfiguredTarget("//java/android/binary:app"); + Set allArtifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ctapp)); + SpawnAction binCompileAction = + (SpawnAction) getGeneratingAction(getFirstArtifactEndingWith(allArtifacts, "app.jar")); + String dataBindingFilesDir = + targetConfig + .getBinDirectory(RepositoryName.MAIN) + .getExecPath() + .getRelative("java/android/binary/databinding/app") + .getPathString(); + ImmutableList expectedJavacopts = + ImmutableList.of( + "-Aandroid.databinding.bindingBuildFolder=" + dataBindingFilesDir, + "-Aandroid.databinding.generationalFileOutDir=" + dataBindingFilesDir, + "-Aandroid.databinding.sdkDir=/not/used", + "-Aandroid.databinding.artifactType=APPLICATION", + "-Aandroid.databinding.exportClassListTo=/tmp/exported_classes", + "-Aandroid.databinding.modulePackage=android.binary", + "-Aandroid.databinding.minApi=14", + "-Aandroid.databinding.printEncodedErrors=0", + "-Aandroid.databinding.enableV2=1"); + 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 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. + SpawnAction binCompileAction = + (SpawnAction) getGeneratingAction(getFirstArtifactEndingWith(allArtifacts, "app.jar")); + + List appJarInputs = 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-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. ""). 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 libArtifacts = getFilesToBuild(ct); + + assertThat(getFirstArtifactEndingWith(libArtifacts, "_resources.jar")).isNull(); + assertThat(getFirstArtifactEndingWith(libArtifacts, "layout-info.zip")).isNull(); + + SpawnAction libCompileAction = + (SpawnAction) + 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(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(); + } + + @Test + public void dataBindingProviderIsProvided() throws Exception { + + useConfiguration("--android_sdk=//sdk:sdk", "--experimental_android_databinding_v2"); + + scratch.file( + "sdk/BUILD", + "android_sdk(", + " name = 'sdk',", + " aapt = 'aapt',", + " aapt2 = 'aapt2',", + " adb = 'adb',", + " aidl = 'aidl',", + " android_jar = 'android.jar',", + " apksigner = 'apksigner',", + " dx = 'dx',", + " framework_aidl = 'framework_aidl',", + " main_dex_classes = 'main_dex_classes',", + " main_dex_list_creator = 'main_dex_list_creator',", + " proguard = 'proguard',", + " shrinked_android_jar = 'shrinked_android_jar',", + " zipalign = 'zipalign',", + " tags = ['__ANDROID_RULES_MIGRATION__'],", + ")"); + + scratch.file( + "java/a/BUILD", + "android_library(", + " name = 'a', ", + " srcs = ['A.java'],", + " enable_data_binding = 1,", + " manifest = 'a/AndroidManifest.xml',", + " resource_files = ['res/values/a.xml'],", + ")"); + + scratch.file( + "java/b/BUILD", + "android_library(", + " name = 'b', ", + " srcs = ['B.java'],", + " enable_data_binding = 1,", + " manifest = 'AndroidManifest.xml',", + " resource_files = ['res/values/a.xml'],", + ")"); + + ConfiguredTarget a = getConfiguredTarget("//java/a:a"); + final DataBindingV2Provider dataBindingV2Provider = a.get(DataBindingV2Provider.PROVIDER); + + assertThat(dataBindingV2Provider) + .named(DataBindingV2Provider.NAME) + .isNotNull(); + + assertThat( + dataBindingV2Provider + .getSetterStores() + .stream() + .map(Artifact::getRootRelativePathString) + .collect(Collectors.toList())) + .containsExactly("java/a/databinding/a/bin-files/a-a-setter_store.bin"); + + assertThat( + dataBindingV2Provider + .getClassInfos() + .stream() + .map(Artifact::getRootRelativePathString) + .collect(Collectors.toList())) + .containsExactly("java/a/databinding/a/class-info.zip"); + + assertThat( + dataBindingV2Provider + .getTransitiveBRFiles() + .toCollection() + .stream() + .map(Artifact::getRootRelativePathString) + .collect(Collectors.toList())) + .containsExactly("java/a/databinding/a/bin-files/a-a-br.bin"); + } + + @Test + public void ensureDataBindingProviderIsPropagatedThroughNonDataBindingLibs() throws Exception { + + useConfiguration("--android_sdk=//sdk:sdk", "--experimental_android_databinding_v2"); + + scratch.file( + "sdk/BUILD", + "android_sdk(", + " name = 'sdk',", + " aapt = 'aapt',", + " aapt2 = 'aapt2',", + " adb = 'adb',", + " aidl = 'aidl',", + " android_jar = 'android.jar',", + " apksigner = 'apksigner',", + " dx = 'dx',", + " framework_aidl = 'framework_aidl',", + " main_dex_classes = 'main_dex_classes',", + " main_dex_list_creator = 'main_dex_list_creator',", + " proguard = 'proguard',", + " shrinked_android_jar = 'shrinked_android_jar',", + " zipalign = 'zipalign',", + " tags = ['__ANDROID_RULES_MIGRATION__'],", + ")"); + scratch.file( + "java/a/BUILD", + "android_library(", + " name = 'a', ", + " srcs = ['A.java'],", + " enable_data_binding = 1,", + " manifest = 'AndroidManifest.xml',", + " resource_files = ['res/values/a.xml'],", + ")"); + scratch.file( + "java/b/BUILD", + "android_library(", + " name = 'b', ", + " srcs = ['B.java'],", + " deps = ['//java/a:a'],", + ")"); + + ConfiguredTarget b = getConfiguredTarget("//java/b:b"); + Truth.assertThat(b.get(DataBindingV2Provider.PROVIDER)) + .named("DataBindingV2Info") + .isNotNull(); + } + + @Test + public void testDataBindingCollectedThroughExports() throws Exception { + + useConfiguration("--android_sdk=//sdk:sdk", "--experimental_android_databinding_v2"); + + scratch.file( + "sdk/BUILD", + "android_sdk(", + " name = 'sdk',", + " aapt = 'aapt',", + " aapt2 = 'aapt2',", + " adb = 'adb',", + " aidl = 'aidl',", + " android_jar = 'android.jar',", + " apksigner = 'apksigner',", + " dx = 'dx',", + " framework_aidl = 'framework_aidl',", + " main_dex_classes = 'main_dex_classes',", + " main_dex_list_creator = 'main_dex_list_creator',", + " proguard = 'proguard',", + " shrinked_android_jar = 'shrinked_android_jar',", + " zipalign = 'zipalign',", + " tags = ['__ANDROID_RULES_MIGRATION__'],", + ")"); + + scratch.file( + "java/a/BUILD", + "android_library(", + " name = 'a', ", + " srcs = ['A.java'],", + " enable_data_binding = 1,", + " manifest = 'AndroidManifest.xml',", + " resource_files = ['res/values/a.xml'],", + ")"); + + scratch.file( + "java/b/BUILD", + "android_library(", + " name = 'b', ", + " srcs = ['B.java'],", + " enable_data_binding = 1,", + " manifest = 'AndroidManifest.xml',", + " resource_files = ['res/values/a.xml'],", + ")"); + + scratch.file( + "java/c/BUILD", + "android_library(", + " name = 'c', ", + " exports = ['//java/a:a', '//java/b:b']", + ")"); + + ConfiguredTarget c = getConfiguredTarget("//java/c:c"); + DataBindingV2Provider provider = c.get(DataBindingV2Provider.PROVIDER); + + assertThat( + provider + .getClassInfos() + .stream() + .map(Artifact::getRootRelativePathString) + .collect(Collectors.toList())) + .containsExactly( + "java/a/databinding/a/class-info.zip", + "java/b/databinding/b/class-info.zip"); + + assertThat( + provider + .getSetterStores() + .stream() + .map(Artifact::getRootRelativePathString) + .collect(Collectors.toList())) + .containsExactly( + "java/a/databinding/a/bin-files/a-a-setter_store.bin", + "java/b/databinding/b/bin-files/b-b-setter_store.bin"); + + assertThat( + provider + .getTransitiveBRFiles() + .toCollection() + .stream() + .map(Artifact::getRootRelativePathString) + .collect(Collectors.toList())) + .containsExactly( + "java/a/databinding/a/bin-files/a-a-br.bin", + "java/b/databinding/b/bin-files/b-b-br.bin"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidResourcesTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidResourcesTest.java index fcdef8b2fe0014..6cb9ea088eb97d 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidResourcesTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidResourcesTest.java @@ -557,7 +557,10 @@ private MergedAndroidResources makeMergedResources(RuleContext ruleContext) private ParsedAndroidResources makeParsedResources(RuleContext ruleContext) throws RuleErrorException, InterruptedException { - return makeParsedResources(ruleContext, DataBinding.asDisabledDataBindingContext()); + DataBindingContext dataBindingContext = + DataBinding.contextFrom(ruleContext, + ruleContext.getConfiguration().getFragment(AndroidConfiguration.class)); + return makeParsedResources(ruleContext, dataBindingContext); } private ParsedAndroidResources makeParsedResources( diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java b/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java index 0d48adba5d64a5..f46fd11feca72e 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java +++ b/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java @@ -258,7 +258,7 @@ public ValidatedAndroidResources makeValidatedResourcesFor( includeAapt2Outs ? getOutput("symbols.zip") : null, manifest.getManifest().getOwnerLabel(), manifest, - DataBinding.asDisabledDataBindingContext()), + DataBinding.DISABLED_V1_CONTEXT), getOutput("merged/resources.zip"), getOutput("class.jar"), /* dataBindingInfoZip = */ null, diff --git a/tools/android/BUILD.tools b/tools/android/BUILD.tools index 427b33ebdf9e54..c5c3a5ea6acce5 100644 --- a/tools/android/BUILD.tools +++ b/tools/android/BUILD.tools @@ -321,6 +321,14 @@ filegroup( srcs = ["bazel_debug.keystore"], ) +java_binary( + name = "exec_binary", + main_class = "android.databinding.AndroidDataBinding", + runtime_deps = [ + "//third_party/java/android_databinding:exec", + ], +) + alias( name = "databinding_annotation_processor", actual = "//external:databinding_annotation_processor",