Update Android rules for Databinding V2. RELNOTES[NEW]: Android Databinding v2 can be enabled with --experimental_android_databinding_v2. PiperOrigin-RevId: 221710069
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 113d995..f1306a1 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 @@ 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 2ce75c5..ce9c327 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
@@ -81,6 +81,30 @@ 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. + * + * <p>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. * * <p>Note that:
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 0ac3bfa..5a97842 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 @@ JavaSemantics semantics, DataBindingContext dataBindingContext, boolean isLibrary) { - ImmutableList<Artifact> srcs = - dataBindingContext.addAnnotationFileToSrcs( - ruleContext.getPrerequisiteArtifacts("srcs", RuleConfiguredTarget.Mode.TARGET).list(), - ruleContext); + + ImmutableList<Artifact> ruleSources = + ruleContext.getPrerequisiteArtifacts("srcs", RuleConfiguredTarget.Mode.TARGET).list(); + + ImmutableList<Artifact> dataBindingSources = + dataBindingContext.getAnnotationSourceFiles(ruleContext); + + ImmutableList<Artifact> srcs = ImmutableList.<Artifact>builder() + .addAll(ruleSources) + .addAll(dataBindingSources) + .build(); ImmutableList<TransitiveInfoCollection> compileDeps; ImmutableList<TransitiveInfoCollection> 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 0000000..af31cf9 --- /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<Artifact> databindingProcessedResourcesBuilder = ImmutableList.builder(); + for (Artifact resource : androidResources.getResources()) { + + // Create resources that will be processed by databinding under paths that look like: + // + // <bazel-pkg>/databinding-processed-resources/<rule-name>/<bazal-pkg>/<resource-dir> + + Artifact databindingProcessedResource = + dataContext.getUniqueDirectoryArtifact("databinding-processed-resources", + resource.getRootRelativePath()); + + databindingProcessedResourcesBuilder.add(databindingProcessedResource); + } + ImmutableList<Artifact> databindingProcessedResources = + databindingProcessedResourcesBuilder.build(); + + BusyBoxActionBuilder builder = BusyBoxActionBuilder.create(dataContext, "PROCESS_DATABINDING"); + + // Create output resource roots that correspond to the paths of the resources created above: + // + // <bazel-pkg>/databinding-processed-resources/<rule-name>/<resource-root> + // + // 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 + // + // <bazel-pkg>/databinding-processed-resources/<rule-name> + ArtifactRoot binOrGenfiles = dataContext.getBinOrGenfilesDirectory(); + PathFragment uniqueDir = + dataContext.getUniqueDirectory(PathFragment.create("databinding-processed-resources")); + PathFragment outputResourceRoot = binOrGenfiles.getExecPath().getRelative(uniqueDir); + + ImmutableList.Builder<PathFragment> 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 45463ac..aa65bc2 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 @@ 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 @@ 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 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 724f989..f781d05 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 @@ 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 @@ 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 407f737..df9fb65 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 @@ 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 d816107..f07d87e 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 @@ } return ResourceApk.processFromTransitiveLibraryData( ctx, - DataBinding.asDisabledDataBindingContext(), + DataBinding.getDisabledDataBindingContext(ctx), ResourceDependencies.fromProviders(deps, /* neverlink = */ neverlink), AssetDependencies.empty(), StampedAndroidManifest.createEmpty( @@ -374,7 +374,7 @@ AndroidManifest.forAarImport(androidManifestArtifact), ResourceDependencies.fromProviders( getProviders(deps, AndroidResourcesInfo.PROVIDER), /* neverlink = */ false), - DataBinding.asDisabledDataBindingContext(), + DataBinding.getDisabledDataBindingContext(ctx), aaptVersion); MergedAndroidAssets mergedAssets = @@ -420,7 +420,7 @@ 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 c1cd19f..eb61855 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 @@ return this; } + /** Adds the given input artifacts without any command line options. */ + public BusyBoxActionBuilder addInputs(Iterable<Artifact> 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 @@ return this; } + /** Adds the given output artifacts without adding any command line options. */ + public BusyBoxActionBuilder addOutputs(Iterable<Artifact> 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 256d6f3..9dcf277 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 @@ 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 @@ : 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 6285a1a..d21e344 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 <a @@ -53,67 +58,53 @@ * via the implicit dependencies specified inside this class. */ public final class DataBinding { + /** The rule attribute supplying data binding's annotation processor. */ public static final String DATABINDING_ANNOTATION_PROCESSOR_ATTR = "$databinding_annotation_processor"; + /** The rule attribute supplying data binding's build helper (exec). */ + public static final String DATABINDING_EXEC_PROCESSOR_ATTR = "$databinding_exec"; + private static final String ENABLE_DATA_BINDING_ATTR = "enable_data_binding"; - private static final DataBindingContext DISABLED_CONTEXT = new DisabledDataBindingV1Context(); + /** 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 writes metadata output for the current rule. */ + private static final String METADATA_OUTPUT_DIR = "bin-files"; + + @VisibleForTesting + public static final DataBindingContext DISABLED_V1_CONTEXT = new DisabledDataBindingV1Context(); + + private static final DataBindingContext DISABLED_V2_CONTEXT = new DisabledDataBindingV2Context(); /** Supplies a databinding context from a rulecontext. */ public static DataBindingContext contextFrom( RuleContext ruleContext, AndroidConfiguration androidConfig) { - if (isEnabled(ruleContext)) { - if (androidConfig.useDataBindingV2()) { - return asEnabledDataBindingV2ContextFrom(ruleContext); - } - return asEnabledDataBindingV1ContextFrom(ruleContext); - } - return asDisabledDataBindingContext(); + + return contextFrom(isEnabled(ruleContext), ruleContext, androidConfig); } /** Supplies a databinding context from an action context. */ public static DataBindingContext contextFrom( boolean enabled, ActionConstructionContext context, AndroidConfiguration androidConfig) { + if (enabled) { if (androidConfig.useDataBindingV2()) { - return asEnabledDataBindingV2ContextFrom(context); + return new DataBindingV2Context(context); + } else { + return new DataBindingV1Context(context); } - return asEnabledDataBindingV1ContextFrom(context); + } else { + if (androidConfig.useDataBindingV2()) { + return DISABLED_V2_CONTEXT; + } else { + return DISABLED_V1_CONTEXT; + } } - return asDisabledDataBindingContext(); } - /** Supplies an enabled DataBindingContext from the action context. */ - private static DataBindingContext asEnabledDataBindingV1ContextFrom( - ActionConstructionContext actionContext) { - return new DataBindingV1Context(actionContext); - } - - private static DataBindingContext asEnabledDataBindingV2ContextFrom( - ActionConstructionContext actionContext) { - return new DataBindingV2Context(actionContext); - } - - /** Supplies a disabled (no-op) DataBindingContext. */ - public static DataBindingContext asDisabledDataBindingContext() { - return DISABLED_CONTEXT; - } - - /** - * 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.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 @@ 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 @@ .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 @@ return String.format("-Aandroid.databinding.%s=%s", flag, value); } - /** - * 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}. - */ - static void maybeAddProvider( - List<Artifact> 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<Artifact> 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 @@ * 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<Artifact> getMetadataOutputs(RuleContext ruleContext) { + static ImmutableList<Artifact> getMetadataOutputs( + RuleContext ruleContext, + List<String> 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 @@ } ImmutableList.Builder<Artifact> outputs = ImmutableList.<Artifact>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 @@ 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<String>)}) 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 95802c2..726a163 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 @@ /** The javac flags that are needed to configure data binding's annotation processor. */ void supplyJavaCoptsUsing( - RuleContext ruleContext, boolean isBinary, Consumer<Iterable<String>> consumer); + RuleContext ruleContext, + boolean isBinary, + Consumer<Iterable<String>> consumer); /** * Adds data binding's annotation processor as a plugin to the given Java compilation context. * * <p>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<JavaPluginInfoProvider, Iterable<Artifact>> consumer); + RuleContext ruleContext, + BiConsumer<JavaPluginInfoProvider, Iterable<Artifact>> consumer); /** * Processes deps that also apply data binding. @@ -83,8 +90,7 @@ * <p>This triggers the annotation processor. Annotation processor settings are configured * separately in {@link #supplyJavaCoptsUsing(RuleContext, boolean, Consumer)}. */ - ImmutableList<Artifact> addAnnotationFileToSrcs( - ImmutableList<Artifact> srcs, RuleContext ruleContext); + ImmutableList<Artifact> getAnnotationSourceFiles(RuleContext ruleContext); /** * Adds the appropriate {@link UsesDataBindingProvider} for a rule if it should expose one. @@ -94,5 +100,12 @@ */ 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 bea5df8..f136ec9 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<String> METADATA_OUTPUT_SUFFIXES_V1 = + ImmutableList.of("setter_store.bin", "layoutinfo.bin", "br.bin"); + private final ActionConstructionContext actionConstructionContext; DataBindingV1Context(ActionConstructionContext actionConstructionContext) { @@ -45,18 +48,15 @@ @Override public void supplyLayoutInfo(Consumer<Artifact> 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<Iterable<String>> consumer) { + ImmutableList.Builder<String> 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 @@ // 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 @@ 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 @@ @Override public void supplyAnnotationProcessor( - RuleContext ruleContext, BiConsumer<JavaPluginInfoProvider, Iterable<Artifact>> consumer) { - consumer.accept( - JavaInfo.getProvider( - JavaPluginInfoProvider.class, - ruleContext.getPrerequisite( - DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, RuleConfiguredTarget.Mode.HOST)), - DataBinding.getMetadataOutputs(ruleContext)); + RuleContext ruleContext, + BiConsumer<JavaPluginInfoProvider, Iterable<Artifact>> consumer) { + + JavaPluginInfoProvider javaPluginInfoProvider = JavaInfo.getProvider( + JavaPluginInfoProvider.class, + ruleContext.getPrerequisite( + DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, RuleConfiguredTarget.Mode.HOST)); + + ImmutableList<Artifact> annotationProcessorOutputs = + DataBinding.getMetadataOutputs(ruleContext, METADATA_OUTPUT_SUFFIXES_V1); + + consumer.accept(javaPluginInfoProvider, annotationProcessorOutputs); } @Override public ImmutableList<Artifact> processDeps(RuleContext ruleContext) { + ImmutableList.Builder<Artifact> 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<Artifact> addAnnotationFileToSrcs( - ImmutableList<Artifact> 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.<Artifact>builder().addAll(srcs).add(annotationFile).build(); - } catch (IOException e) { - ruleContext.ruleError("Cannot load annotation processor template: " + e.getMessage()); - return ImmutableList.of(); - } + public ImmutableList<Artifact> getAnnotationSourceFiles(RuleContext ruleContext) { + return DataBinding.getAnnotationFile(ruleContext); } @Override public void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext) { - List<Artifact> dataBindingMetadataOutputs = - Lists.newArrayList(DataBinding.getMetadataOutputs(ruleContext)); - DataBinding.maybeAddProvider(dataBindingMetadataOutputs, builder, ruleContext); - } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + List<Artifact> 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 28661d5..642a8f8 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<String> 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<Artifact> consumer) { - + // In v2, The layout info file is generated in processResources below. } @Override public void supplyJavaCoptsUsing(RuleContext ruleContext, boolean isBinary, Consumer<Iterable<String>> consumer) { + ImmutableList.Builder<String> 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<JavaPluginInfoProvider, Iterable<Artifact>> consumer) { + JavaPluginInfoProvider javaPluginInfoProvider = JavaInfo.getProvider( + JavaPluginInfoProvider.class, + ruleContext.getPrerequisite( + DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, RuleConfiguredTarget.Mode.HOST)); + + ImmutableList<Artifact> annotationProcessorOutputs = + DataBinding.getMetadataOutputs(ruleContext, METADATA_OUTPUT_SUFFIXES_V2); + + consumer.accept(javaPluginInfoProvider, annotationProcessorOutputs); } @Override public ImmutableList<Artifact> processDeps(RuleContext ruleContext) { - return null; + + ImmutableList.Builder<Artifact> 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<Artifact> getTransitiveBRFiles(RuleContext context) { + ImmutableList.Builder<Artifact> brFiles = ImmutableList.builder(); + if (context.attributes().has("deps", BuildType.LABEL_LIST)) { + + Iterable<DataBindingV2Provider> providers = context.getPrerequisites( + "deps", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + + for (DataBindingV2Provider provider : providers) { + brFiles.addAll(provider.getTransitiveBRFiles()); + } + } + return brFiles.build(); + } + + private static List<Artifact> getDirectSetterStoreFiles(RuleContext context) { + ImmutableList.Builder<Artifact> setterStoreFiles = ImmutableList.builder(); + if (context.attributes().has("deps", BuildType.LABEL_LIST)) { + + Iterable<DataBindingV2Provider> providers = context.getPrerequisites( + "deps", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + + for (DataBindingV2Provider provider : providers) { + setterStoreFiles.addAll(provider.getSetterStores()); + } + } + return setterStoreFiles.build(); + } + @Override - public ImmutableList<Artifact> addAnnotationFileToSrcs(ImmutableList<Artifact> srcs, - RuleContext ruleContext) { - return null; + public ImmutableList<Artifact> getAnnotationSourceFiles(RuleContext ruleContext) { + ImmutableList.Builder<Artifact> srcs = ImmutableList.builder(); + + srcs.addAll(DataBinding.getAnnotationFile(ruleContext)); + srcs.addAll(createBaseClasses(ruleContext)); + + return srcs.build(); + } + + private ImmutableList<Artifact> 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<Artifact> 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<Artifact> getDirectClassInfo(RuleContext context) { + ImmutableList.Builder<Artifact> clientInfoFiles = ImmutableList.builder(); + if (context.attributes().has("deps", BuildType.LABEL_LIST)) { + + Iterable<DataBindingV2Provider> 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<Artifact> setterStores = ImmutableList.builder(); + if (setterStore != null) { + setterStores.add(setterStore); + } + + ImmutableList.Builder<Artifact> 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<DataBindingV2Provider> exportsProviders = + ruleContext.getPrerequisites( + "exports", RuleConfiguredTarget.Mode.TARGET, DataBindingV2Provider.PROVIDER); + for (DataBindingV2Provider provider : exportsProviders) { + setterStores.addAll(provider.getSetterStores()); + classInfos.addAll(provider.getClassInfos()); + } + } + + NestedSetBuilder<Artifact> brFiles = new NestedSetBuilder<>(Order.STABLE_ORDER); + if (br != null) { + brFiles.add(br); + } + + Iterable<DataBindingV2Provider> 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 0000000..0ffd180 --- /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 <a + * href="https://developer.android.com/topic/libraries/data-binding/index.html">data binding</a> + * version 2 on its resource processing and Java compilation. + */ +public final class DataBindingV2Provider extends NativeInfo + implements DataBindingV2ProviderApi<Artifact> { + + public static final Provider PROVIDER = new Provider(); + + private final ImmutableList<Artifact> classInfos; + + private final ImmutableList<Artifact> setterStores; + + private final NestedSet<Artifact> transitiveBRFiles; + + public DataBindingV2Provider( + ImmutableList<Artifact> classInfos, + ImmutableList<Artifact> setterStores, + NestedSet<Artifact> transitiveBRFiles) { + super(PROVIDER); + this.classInfos = classInfos; + this.setterStores = setterStores; + this.transitiveBRFiles = transitiveBRFiles; + } + + @Override + public ImmutableList<Artifact> getClassInfos() { + return classInfos; + } + + @Override + public ImmutableList<Artifact> getSetterStores() { + return setterStores; + } + + @Override + public NestedSet<Artifact> getTransitiveBRFiles() { + return transitiveBRFiles; + } + + /** The provider can construct the DataBindingV2Provider provider. */ + public static class Provider extends BuiltinProvider<DataBindingV2Provider> + implements DataBindingV2ProviderApi.Provider<Artifact> { + + private Provider() { + super(NAME, DataBindingV2Provider.class); + } + + @Override + public DataBindingV2ProviderApi<Artifact> createInfo( + SkylarkList<Artifact> setterStores, + SkylarkList<Artifact> 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 cae89c4..7b59900 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 @@ } @Override - public ImmutableList<Artifact> addAnnotationFileToSrcs( - ImmutableList<Artifact> srcs, RuleContext ruleContext) { - return srcs; - }; - - @Override - public void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext) { - DataBinding.maybeAddProvider(new ArrayList<>(), builder, ruleContext); + public ImmutableList<Artifact> getAnnotationSourceFiles(RuleContext ruleContext) { + return ImmutableList.of(); } @Override - public AndroidResources processResources(AndroidResources resources) { + public void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext) { + + ArrayList<Artifact> 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( + 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 0000000..5bbef27 --- /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<Iterable<String>> consumer) { } + + @Override + public void supplyAnnotationProcessor(RuleContext ruleContext, + BiConsumer<JavaPluginInfoProvider, Iterable<Artifact>> consumer) { } + + @Override + public ImmutableList<Artifact> processDeps(RuleContext ruleContext) { + return ImmutableList.of(); + } + + @Override + public ImmutableList<Artifact> getAnnotationSourceFiles(RuleContext ruleContext) { + return ImmutableList.of(); + } + + @Override + public void addProvider(RuleConfiguredTargetBuilder builder, RuleContext ruleContext) { + + ImmutableList.Builder<Artifact> setterStores = ImmutableList.builder(); + ImmutableList.Builder<Artifact> classInfos = ImmutableList.builder(); + NestedSetBuilder<Artifact> brFiles = new NestedSetBuilder<>(Order.STABLE_ORDER); + + // android_binary doesn't have "exports" + if (ruleContext.attributes().has("exports", BuildType.LABEL_LIST)) { + Iterable<DataBindingV2Provider> 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<DataBindingV2Provider> 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<Artifact> 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 e6a4aaa..62ce14f 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 @@ 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 0000000..2c0f28e --- /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 <a + * href="https://developer.android.com/topic/libraries/data-binding/index.html">data binding</a>. + */ +@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<T extends FileApi> 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<T> getSetterStores(); + + /** Returns the client info files from this rule. */ + @SkylarkCallable(name = "client_infos", structField = true, doc = "", documented = false) + ImmutableList<T> getClassInfos(); + + /** Returns the BR files from this rule and its dependencies. */ + @SkylarkCallable(name = "transitive_br_files", structField = true, doc = "", documented = false) + NestedSet<T> 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<F extends FileApi> extends ProviderApi { + + @SkylarkCallable( + name = NAME, + doc = "The <code>DataBindingV2Info</code> 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<F> createInfo( + SkylarkList<F> setterStores, + SkylarkList<F> clientInfos, + SkylarkNestedSet transitiveBrFiles) throws EvalException; + } +} \ No newline at end of file