| // 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.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.FileProvider; |
| import com.google.devtools.build.lib.analysis.skylark.SkylarkRuleContext; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.events.Location; |
| import com.google.devtools.build.lib.packages.NativeInfo; |
| import com.google.devtools.build.lib.packages.NativeProvider; |
| import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; |
| import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion; |
| import com.google.devtools.build.lib.rules.android.AndroidLibraryAarInfo.Aar; |
| import com.google.devtools.build.lib.rules.java.JavaCompilationInfoProvider; |
| import com.google.devtools.build.lib.rules.java.JavaInfo; |
| import com.google.devtools.build.lib.skylarkinterface.Param; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; |
| import com.google.devtools.build.lib.syntax.EvalException; |
| import com.google.devtools.build.lib.syntax.Runtime; |
| import com.google.devtools.build.lib.syntax.SkylarkDict; |
| import com.google.devtools.build.lib.syntax.SkylarkList; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.util.List; |
| import java.util.Objects; |
| import javax.annotation.Nullable; |
| |
| /** Skylark-visible methods for working with Android data (manifests, resources, and assets). */ |
| @SkylarkModule( |
| name = "android_data", |
| doc = |
| "Utilities for working with Android data (manifests, resources, and assets). " |
| + "This API is non-final and subject to change without warning; do not rely on it.") |
| public abstract class AndroidSkylarkData { |
| |
| public abstract AndroidSemantics getAndroidSemantics(); |
| |
| /** |
| * Skylark API for getting a asset provider for android_library targets that don't specify assets. |
| * |
| * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here |
| * |
| * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext |
| * instead. See b/79159379 |
| */ |
| @SkylarkCallable( |
| name = "assets_from_deps", |
| mandatoryPositionals = 1, // context |
| parameters = { |
| @Param( |
| name = "deps", |
| defaultValue = "[]", |
| type = SkylarkList.class, |
| generic1 = AndroidAssetsInfo.class, |
| positional = false, |
| named = true, |
| doc = "Dependencies to inherit assets from"), |
| @Param( |
| name = "neverlink", |
| defaultValue = "False", |
| type = Boolean.class, |
| positional = false, |
| named = true, |
| doc = |
| "Defaults to False. If true, assets will not be exposed to targets that depend on" |
| + " them.") |
| }, |
| doc = |
| "Creates an AndroidAssetsInfo from this target's asset dependencies, ignoring local" |
| + " assets. No processing will be done. This method is deprecated and exposed only" |
| + " for backwards-compatibility with existing Native behavior.") |
| public static AndroidAssetsInfo assetsFromDeps( |
| SkylarkRuleContext ctx, SkylarkList<AndroidAssetsInfo> deps, boolean neverlink) |
| throws EvalException { |
| return AssetDependencies.fromProviders(deps, neverlink).toInfo(ctx.getLabel()); |
| } |
| |
| /** |
| * Skylark API for getting a resource provider for android_library targets that don't specify |
| * resources. |
| * |
| * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here |
| * |
| * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext |
| * instead. See b/79159379 |
| */ |
| @SkylarkCallable( |
| name = "resources_from_deps", |
| mandatoryPositionals = 1, // context |
| parameters = { |
| @Param( |
| name = "deps", |
| defaultValue = "[]", |
| type = SkylarkList.class, |
| generic1 = AndroidResourcesInfo.class, |
| positional = false, |
| named = true, |
| doc = "Dependencies to inherit resources from"), |
| @Param( |
| name = "neverlink", |
| defaultValue = "False", |
| type = Boolean.class, |
| positional = false, |
| named = true, |
| doc = |
| "Defaults to False. If true, resources will not be exposed to targets that depend" |
| + " on them."), |
| @Param( |
| name = "custom_package", |
| positional = false, |
| defaultValue = "None", |
| type = String.class, |
| noneable = true, |
| named = true, |
| doc = |
| "The Android application package to stamp the manifest with. If not provided, the" |
| + " current Java package, derived from the location of this target's BUILD" |
| + " file, will be used. For example, given a BUILD file in" |
| + " 'java/com/foo/bar/BUILD', the package would be 'com.foo.bar'."), |
| }, |
| doc = |
| "Creates an AndroidResourcesInfo from this target's resource dependencies, ignoring local" |
| + " resources. Only processing of deps will be done. This method is deprecated and" |
| + " exposed only for backwards-compatibility with existing Native behavior. An empty" |
| + " manifest will be generated and included in the provider - this path should not" |
| + " be used when an explicit manifest is specified.") |
| public static AndroidResourcesInfo resourcesFromDeps( |
| SkylarkRuleContext ctx, |
| SkylarkList<AndroidResourcesInfo> deps, |
| boolean neverlink, |
| Object customPackage) |
| throws EvalException, InterruptedException { |
| String pkg = fromNoneable(customPackage, String.class); |
| if (pkg == null) { |
| pkg = AndroidManifest.getDefaultPackage(ctx.getRuleContext()); |
| } |
| return ResourceApk.processFromTransitiveLibraryData( |
| ctx.getRuleContext(), |
| ResourceDependencies.fromProviders(deps, /* neverlink = */ neverlink), |
| AssetDependencies.empty(), |
| StampedAndroidManifest.createEmpty(ctx.getRuleContext(), pkg, /* exported = */ false)) |
| .toResourceInfo(ctx.getLabel()); |
| } |
| |
| /** |
| * Skylark API for stamping an Android manifest |
| * |
| * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here |
| * |
| * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext |
| * instead. See b/79159379 |
| */ |
| @SkylarkCallable( |
| name = "stamp_manifest", |
| mandatoryPositionals = 1, // SkylarkRuleContext ctx is mandatory |
| parameters = { |
| @Param( |
| name = "manifest", |
| positional = false, |
| defaultValue = "None", |
| type = Artifact.class, |
| noneable = true, |
| named = true, |
| doc = "The manifest to stamp. If not passed, a dummy manifest will be generated"), |
| @Param( |
| name = "custom_package", |
| positional = false, |
| defaultValue = "None", |
| type = String.class, |
| noneable = true, |
| named = true, |
| doc = |
| "The Android application package to stamp the manifest with. If not provided, the" |
| + " current Java package, derived from the location of this target's BUILD" |
| + " file, will be used. For example, given a BUILD file in" |
| + " 'java/com/foo/bar/BUILD', the package would be 'com.foo.bar'."), |
| @Param( |
| name = "exports_manifest", |
| positional = false, |
| defaultValue = "False", |
| type = Boolean.class, |
| named = true, |
| doc = |
| "Defaults to False. If passed as True, this manifest will be exported to and" |
| + " eventually merged into targets that depend on it. Otherwise, it won't be" |
| + " inherited."), |
| }, |
| doc = "Stamps a manifest with package information.") |
| public AndroidManifestInfo stampAndroidManifest( |
| SkylarkRuleContext ctx, Object manifest, Object customPackage, boolean exported) |
| throws InterruptedException { |
| String pkg = fromNoneable(customPackage, String.class); |
| if (pkg == null) { |
| pkg = AndroidManifest.getDefaultPackage(ctx.getRuleContext()); |
| } |
| |
| Artifact primaryManifest = fromNoneable(manifest, Artifact.class); |
| if (primaryManifest == null) { |
| return StampedAndroidManifest.createEmpty(ctx.getRuleContext(), pkg, exported).toProvider(); |
| } |
| |
| // If needed, rename the manifest to "AndroidManifest.xml", which aapt expects. |
| Artifact renamedManifest = |
| getAndroidSemantics().renameManifest(ctx.getRuleContext(), primaryManifest); |
| |
| return new AndroidManifest(renamedManifest, pkg, exported) |
| .stamp(ctx.getRuleContext()) |
| .toProvider(); |
| } |
| |
| /** |
| * Skylark API for merging android_library assets |
| * |
| * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here |
| * |
| * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext |
| * instead. See b/79159379 |
| */ |
| @SkylarkCallable( |
| name = "merge_assets", |
| mandatoryPositionals = 1, // context |
| parameters = { |
| @Param( |
| name = "assets", |
| positional = false, |
| defaultValue = "None", |
| type = SkylarkList.class, |
| generic1 = ConfiguredTarget.class, |
| noneable = true, |
| named = true, |
| doc = |
| "Targets containing raw assets for this target. If passed, 'assets_dir' must also" |
| + " be passed."), |
| @Param( |
| name = "assets_dir", |
| positional = false, |
| defaultValue = "None", |
| type = String.class, |
| noneable = true, |
| named = true, |
| doc = |
| "Directory the assets are contained in. Must be passed if and only if 'assets' is" |
| + " passed. This path will be split off of the asset paths on the device."), |
| @Param( |
| name = "deps", |
| positional = false, |
| defaultValue = "[]", |
| type = SkylarkList.class, |
| generic1 = AndroidAssetsInfo.class, |
| named = true, |
| doc = |
| "Providers containing assets from dependencies. These assets will be merged" |
| + " together with each other and this target's assets."), |
| @Param( |
| name = "neverlink", |
| positional = false, |
| defaultValue = "False", |
| type = Boolean.class, |
| named = true, |
| doc = |
| "Defaults to False. If passed as True, these assets will not be inherited by" |
| + " targets that depend on this one.") |
| }, |
| doc = |
| "Merges this target's assets together with assets inherited from dependencies. Note that," |
| + " by default, actions for validating the merge are created but may not be called." |
| + " You may want to force these actions to be called - see the 'validation_result'" |
| + " field in AndroidAssetsInfo") |
| public AndroidAssetsInfo mergeAssets( |
| SkylarkRuleContext ctx, |
| Object assets, |
| Object assetsDir, |
| SkylarkList<AndroidAssetsInfo> deps, |
| boolean neverlink) |
| throws EvalException, InterruptedException { |
| try { |
| return AndroidAssets.from( |
| ctx.getRuleContext(), |
| listFromNoneable(assets, ConfiguredTarget.class), |
| isNone(assetsDir) ? null : PathFragment.create(fromNoneable(assetsDir, String.class))) |
| .parse(ctx.getRuleContext()) |
| .merge( |
| ctx.getRuleContext(), |
| AssetDependencies.fromProviders(deps.getImmutableList(), neverlink)) |
| .toProvider(); |
| } catch (RuleErrorException e) { |
| throw new EvalException(Location.BUILTIN, e); |
| } |
| } |
| |
| /** |
| * Skylark API for merging android_library resources |
| * |
| * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here |
| * |
| * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext |
| * instead. See b/79159379 |
| */ |
| @SkylarkCallable( |
| name = "merge_resources", |
| mandatoryPositionals = 2, // context, manifest |
| parameters = { |
| @Param( |
| name = "resources", |
| positional = false, |
| defaultValue = "[]", |
| type = SkylarkList.class, |
| generic1 = FileProvider.class, |
| named = true, |
| doc = "Providers of this target's resources"), |
| @Param( |
| name = "deps", |
| positional = false, |
| defaultValue = "[]", |
| type = SkylarkList.class, |
| generic1 = AndroidResourcesInfo.class, |
| named = true, |
| doc = |
| "Targets containing raw resources from dependencies. These resources will be merged" |
| + " together with each other and this target's resources."), |
| @Param( |
| name = "neverlink", |
| positional = false, |
| defaultValue = "False", |
| type = Boolean.class, |
| named = true, |
| doc = |
| "Defaults to False. If passed as True, these resources will not be inherited by" |
| + " targets that depend on this one."), |
| @Param( |
| name = "enable_data_binding", |
| positional = false, |
| defaultValue = "False", |
| type = Boolean.class, |
| named = true, |
| doc = |
| "Defaults to False. If True, processes data binding expressions in layout" |
| + " resources."), |
| }, |
| doc = |
| "Merges this target's resources together with resources inherited from dependencies." |
| + " Returns a dict of provider type to actual info, with elements for" |
| + " AndroidResourcesInfo (various resource information) and JavaInfo (wrapping the" |
| + " R.class jar, for use in Java compilation). The passed manifest provider is used" |
| + " to get Android package information and to validate that all resources it refers" |
| + " to are available. Note that this method might do additional processing to this" |
| + " manifest, so in the future, you may want to use the manifest contained in this" |
| + " method's output instead of this one.") |
| public SkylarkDict<NativeProvider<?>, NativeInfo> mergeResources( |
| SkylarkRuleContext ctx, |
| AndroidManifestInfo manifest, |
| SkylarkList<ConfiguredTarget> resources, |
| SkylarkList<AndroidResourcesInfo> deps, |
| boolean neverlink, |
| boolean enableDataBinding) |
| throws EvalException, InterruptedException { |
| |
| ImmutableList<FileProvider> fileProviders = |
| resources |
| .stream() |
| .map(target -> target.getProvider(FileProvider.class)) |
| .filter(Objects::nonNull) |
| .collect(ImmutableList.toImmutableList()); |
| |
| try { |
| AndroidAaptVersion aaptVersion = |
| AndroidCommon.getAndroidConfig(ctx.getRuleContext()).getAndroidAaptVersion(); |
| |
| ValidatedAndroidResources validated = |
| AndroidResources.from(ctx.getRuleContext(), fileProviders, "resources") |
| .parse( |
| ctx.getRuleContext(), |
| manifest.asStampedManifest(), |
| enableDataBinding, |
| aaptVersion) |
| .merge( |
| ctx.getRuleContext(), |
| ResourceDependencies.fromProviders(deps, neverlink), |
| enableDataBinding, |
| aaptVersion) |
| .validate(ctx.getRuleContext(), aaptVersion); |
| |
| JavaInfo javaInfo = getJavaInfoForRClassJar(validated.getClassJar()); |
| |
| return SkylarkDict.of( |
| /* env = */ null, |
| AndroidResourcesInfo.PROVIDER, |
| validated.toProvider(), |
| JavaInfo.PROVIDER, |
| javaInfo); |
| |
| } catch (RuleErrorException e) { |
| throw new EvalException(Location.BUILTIN, e); |
| } |
| } |
| |
| /** |
| * Skylark API for building an Aar for an android_library |
| * |
| * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here |
| * |
| * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext |
| * instead. See b/79159379 |
| */ |
| @SkylarkCallable( |
| name = "make_aar", |
| mandatoryPositionals = 4, // context, resource info, asset info, and library class jar |
| parameters = { |
| @Param( |
| name = "proguard_specs", |
| type = SkylarkList.class, |
| generic1 = ConfiguredTarget.class, |
| defaultValue = "[]", |
| positional = false, |
| named = true, |
| doc = |
| "Files to be used as Proguard specification for this target, which will be" |
| + " inherited in the top-level target"), |
| @Param( |
| name = "deps", |
| type = SkylarkList.class, |
| generic1 = AndroidLibraryAarInfo.class, |
| defaultValue = "[]", |
| positional = false, |
| named = true, |
| doc = "Dependant AAR providers used to build this AAR."), |
| @Param( |
| name = "neverlink", |
| type = Boolean.class, |
| defaultValue = "False", |
| positional = false, |
| named = true, |
| doc = |
| "Defaults to False. If true, this target's Aar will not be generated or propagated" |
| + " to targets that depend upon it."), |
| }, |
| doc = |
| "Builds an AAR and corresponding provider for this target. The resource and asset" |
| + " providers from this same target must both be passed, as must the class JAR output" |
| + " of building the Android Java library.") |
| public AndroidLibraryAarInfo makeAar( |
| SkylarkRuleContext ctx, |
| AndroidResourcesInfo resourcesInfo, |
| AndroidAssetsInfo assetsInfo, |
| Artifact libraryClassJar, |
| SkylarkList<ConfiguredTarget> proguardSpecs, |
| SkylarkList<AndroidLibraryAarInfo> deps, |
| boolean neverlink) |
| throws EvalException, InterruptedException { |
| if (neverlink) { |
| return AndroidLibraryAarInfo.create( |
| null, |
| NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER), |
| NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER)); |
| } |
| |
| // Get the target's local resources, if defined, from the provider |
| boolean definesLocalResources = resourcesInfo.getDirectAndroidResources().isSingleton(); |
| AndroidResources resources = AndroidResources.empty(); |
| if (definesLocalResources) { |
| ValidatedAndroidData validatedAndroidData = |
| resourcesInfo.getDirectAndroidResources().toList().get(0); |
| if (validatedAndroidData.getLabel().equals(ctx.getLabel())) { |
| // TODO(b/77574966): Remove this cast once we get rid of ResourceContainer and can guarantee |
| // that only properly processed resources are passed into this object. |
| if (!(validatedAndroidData instanceof ValidatedAndroidResources)) { |
| throw new EvalException( |
| Location.BUILTIN, "Old data processing pipeline does not support the Skylark API"); |
| } |
| resources = (ValidatedAndroidResources) validatedAndroidData; |
| } else { |
| definesLocalResources = false; |
| } |
| } |
| |
| // Get the target's local assets, if defined, from the provider |
| boolean definesLocalAssets = assetsInfo.getDirectParsedAssets().isSingleton(); |
| AndroidAssets assets = AndroidAssets.empty(); |
| if (definesLocalAssets) { |
| ParsedAndroidAssets parsed = assetsInfo.getDirectParsedAssets().toList().get(0); |
| if (parsed.getLabel().equals(ctx.getLabel())) { |
| assets = parsed; |
| } else { |
| definesLocalAssets = false; |
| } |
| } |
| |
| if (definesLocalResources != definesLocalAssets) { |
| throw new EvalException( |
| Location.BUILTIN, |
| "Must define either both or none of assets and resources. Use the merge_assets and" |
| + " merge_resources methods to define them, or assets_from_deps and" |
| + " resources_from_deps to inherit without defining them."); |
| } |
| |
| ImmutableList.Builder<Artifact> proguardSpecBuilder = ImmutableList.builder(); |
| for (ConfiguredTarget target : proguardSpecs) { |
| FileProvider fileProvider = target.getProvider(FileProvider.class); |
| if (fileProvider != null) { |
| proguardSpecBuilder.addAll(fileProvider.getFilesToBuild()); |
| } |
| } |
| |
| return Aar.makeAar( |
| ctx.getRuleContext(), |
| resources, |
| assets, |
| resourcesInfo.getManifest(), |
| resourcesInfo.getRTxt(), |
| libraryClassJar, |
| proguardSpecBuilder.build()) |
| .toProvider(deps, definesLocalResources); |
| } |
| |
| /** |
| * Skylark API for doing all resource, asset, and manifest processing for an android_library |
| * |
| * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here |
| * |
| * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext |
| * instead. See b/79159379 |
| */ |
| @SkylarkCallable( |
| name = "process_library_data", |
| mandatoryPositionals = 2, // ctx and libraryClassJar are required |
| parameters = { |
| @Param( |
| name = "manifest", |
| positional = false, |
| type = Artifact.class, |
| defaultValue = "None", |
| named = true, |
| noneable = true, |
| doc = |
| "If passed, the manifest to use for this target. Otherwise, a dummy manifest will" |
| + " be generated."), |
| @Param( |
| name = "resources", |
| positional = false, |
| defaultValue = "None", |
| type = SkylarkList.class, |
| generic1 = FileProvider.class, |
| named = true, |
| noneable = true, |
| doc = "Providers of this target's resources"), |
| @Param( |
| name = "assets", |
| positional = false, |
| defaultValue = "None", |
| type = SkylarkList.class, |
| generic1 = ConfiguredTarget.class, |
| noneable = true, |
| named = true, |
| doc = |
| "Targets containing raw assets for this target. If passed, 'assets_dir' must also" |
| + " be passed."), |
| @Param( |
| name = "assets_dir", |
| positional = false, |
| defaultValue = "None", |
| type = String.class, |
| noneable = true, |
| named = true, |
| doc = |
| "Directory the assets are contained in. Must be passed if and only if 'assets' is" |
| + " passed. This path will be split off of the asset paths on the device."), |
| @Param( |
| name = "exports_manifest", |
| positional = false, |
| defaultValue = "None", |
| type = Boolean.class, |
| named = true, |
| noneable = true, |
| doc = |
| "Defaults to False. If passed as True, this manifest will be exported to and" |
| + " eventually merged into targets that depend on it. Otherwise, it won't be" |
| + " inherited."), |
| @Param( |
| name = "custom_package", |
| positional = false, |
| defaultValue = "None", |
| type = String.class, |
| noneable = true, |
| named = true, |
| doc = |
| "The Android application package to stamp the manifest with. If not provided, the" |
| + " current Java package, derived from the location of this target's BUILD" |
| + " file, will be used. For example, given a BUILD file in" |
| + " 'java/com/foo/bar/BUILD', the package would be 'com.foo.bar'."), |
| @Param( |
| name = "neverlink", |
| positional = false, |
| defaultValue = "False", |
| type = Boolean.class, |
| named = true, |
| doc = |
| "Defaults to False. If passed as True, these resources and assets will not be" |
| + " inherited by targets that depend on this one."), |
| @Param( |
| name = "enable_data_binding", |
| positional = false, |
| defaultValue = "False", |
| type = Boolean.class, |
| named = true, |
| doc = |
| "Defaults to False. If True, processes data binding expressions in layout" |
| + " resources."), |
| @Param( |
| name = "proguard_specs", |
| type = SkylarkList.class, |
| generic1 = ConfiguredTarget.class, |
| defaultValue = "[]", |
| positional = false, |
| named = true, |
| doc = |
| "Files to be used as Proguard specification for this target, which will be" |
| + " inherited in the top-level target"), |
| @Param( |
| name = "deps", |
| positional = false, |
| defaultValue = "[]", |
| type = SkylarkList.class, |
| generic1 = AndroidAssetsInfo.class, |
| named = true, |
| doc = |
| "Dependency targets. Providers will be extracted from these dependencies for each" |
| + " type of data."), |
| }, |
| doc = |
| "Performs full processing of data for android_library or similar rules. Returns a dict" |
| + " from provider type to providers for the target.") |
| public SkylarkDict<NativeProvider<?>, NativeInfo> processLibraryData( |
| SkylarkRuleContext ctx, |
| Artifact libraryClassJar, |
| Object manifest, |
| Object resources, |
| Object assets, |
| Object assetsDir, |
| Object exportsManifest, |
| Object customPackage, |
| boolean neverlink, |
| boolean enableDataBinding, |
| SkylarkList<ConfiguredTarget> proguardSpecs, |
| SkylarkList<ConfiguredTarget> deps) |
| throws InterruptedException, EvalException { |
| |
| SkylarkList<AndroidResourcesInfo> resourceDeps = |
| getProviders(deps, AndroidResourcesInfo.PROVIDER); |
| SkylarkList<AndroidAssetsInfo> assetDeps = getProviders(deps, AndroidAssetsInfo.PROVIDER); |
| |
| ImmutableMap.Builder<NativeProvider<?>, NativeInfo> infoBuilder = ImmutableMap.builder(); |
| |
| AndroidResourcesInfo resourcesInfo; |
| AndroidAssetsInfo assetsInfo; |
| if (isNone(manifest) |
| && isNone(resources) |
| && isNone(assets) |
| && isNone(assetsDir) |
| && isNone(exportsManifest)) { |
| |
| // If none of these parameters were specified, for backwards compatibility, do not trigger |
| // data processing. |
| resourcesInfo = resourcesFromDeps(ctx, resourceDeps, neverlink, customPackage); |
| assetsInfo = assetsFromDeps(ctx, assetDeps, neverlink); |
| |
| infoBuilder.put(AndroidResourcesInfo.PROVIDER, resourcesInfo); |
| } else { |
| |
| AndroidManifestInfo baseManifest = |
| stampAndroidManifest( |
| ctx, |
| manifest, |
| customPackage, |
| fromNoneableOrDefault(exportsManifest, Boolean.class, false)); |
| |
| SkylarkDict<NativeProvider<?>, NativeInfo> resourceOutput = |
| mergeResources( |
| ctx, |
| baseManifest, |
| listFromNoneableOrEmpty(resources, ConfiguredTarget.class), |
| resourceDeps, |
| neverlink, |
| enableDataBinding); |
| |
| resourcesInfo = (AndroidResourcesInfo) resourceOutput.get(AndroidResourcesInfo.PROVIDER); |
| assetsInfo = mergeAssets(ctx, assets, assetsDir, assetDeps, neverlink); |
| |
| infoBuilder.putAll(resourceOutput); |
| } |
| |
| |
| AndroidLibraryAarInfo aarInfo = |
| makeAar( |
| ctx, |
| resourcesInfo, |
| assetsInfo, |
| libraryClassJar, |
| proguardSpecs, |
| getProviders(deps, AndroidLibraryAarInfo.PROVIDER), |
| neverlink); |
| |
| // Only expose the aar provider in non-neverlinked actions |
| if (!neverlink) { |
| infoBuilder.put(AndroidLibraryAarInfo.PROVIDER, aarInfo); |
| } |
| |
| // Expose the updated manifest that was changed by resource processing |
| // TODO(b/30817309): Use the base manifest once manifests are no longer changed in resource |
| // processing |
| AndroidManifestInfo manifestInfo = resourcesInfo.getManifest().toProvider(); |
| |
| return SkylarkDict.copyOf( |
| /* env = */ null, |
| infoBuilder |
| .put(AndroidAssetsInfo.PROVIDER, assetsInfo) |
| .put(AndroidManifestInfo.PROVIDER, manifestInfo) |
| .build()); |
| } |
| |
| public static SkylarkDict<NativeProvider<?>, NativeInfo> getNativeInfosFrom( |
| ResourceApk resourceApk, Label label) { |
| ImmutableMap.Builder<NativeProvider<?>, NativeInfo> builder = ImmutableMap.builder(); |
| |
| builder.put(AndroidResourcesInfo.PROVIDER, resourceApk.toResourceInfo(label)); |
| |
| resourceApk |
| .toAssetsInfo(label) |
| .ifPresent(info -> builder.put(AndroidAssetsInfo.PROVIDER, info)); |
| resourceApk.toManifestInfo().ifPresent(info -> builder.put(AndroidManifestInfo.PROVIDER, info)); |
| |
| builder.put(JavaInfo.PROVIDER, getJavaInfoForRClassJar(resourceApk.getResourceJavaClassJar())); |
| |
| return SkylarkDict.copyOf(/* env = */ null, builder.build()); |
| } |
| |
| private static JavaInfo getJavaInfoForRClassJar(Artifact rClassJar) { |
| return JavaInfo.Builder.create() |
| .setNeverlink(true) |
| .addProvider( |
| JavaCompilationInfoProvider.class, |
| new JavaCompilationInfoProvider.Builder() |
| .setCompilationClasspath(NestedSetBuilder.create(Order.NAIVE_LINK_ORDER, rClassJar)) |
| .build()) |
| .build(); |
| } |
| |
| /** Checks if a "Noneable" object passed by Skylark is "None", which Java should treat as null. */ |
| public static boolean isNone(Object object) { |
| return object == Runtime.NONE; |
| } |
| |
| /** |
| * Converts a "Noneable" Object passed by Skylark to an nullable object of the appropriate type. |
| * |
| * <p>Skylark "Noneable" types are passed in as an Object that may be either the correct type or a |
| * Runtime.NONE object. Skylark will handle type checking, based on the appropriate @param |
| * annotation, but we still need to do the actual cast (or conversion to null) ourselves. |
| * |
| * @param object the Noneable object |
| * @param clazz the correct class, as defined in the @Param annotation |
| * @param <T> the type to cast to |
| * @return {@code null}, if the noneable argument was None, or the cast object, otherwise. |
| */ |
| @Nullable |
| public static <T> T fromNoneable(Object object, Class<T> clazz) { |
| if (isNone(object)) { |
| return null; |
| } |
| |
| return clazz.cast(object); |
| } |
| |
| public static <T> T fromNoneableOrDefault(Object object, Class<T> clazz, T defaultValue) { |
| T value = fromNoneable(object, clazz); |
| if (value == null) { |
| return defaultValue; |
| } |
| |
| return value; |
| } |
| |
| /** |
| * Converts a "Noneable" Object passed by Skylark to a List of the appropriate type. |
| * |
| * <p>This first calls {@link #fromNoneable(Object, Class)} to get a SkylarkList<?>, then safely |
| * casts it to a list with the appropriate generic. |
| */ |
| @Nullable |
| public static <T> List<T> listFromNoneable(Object object, Class<T> clazz) throws EvalException { |
| SkylarkList<?> asList = fromNoneable(object, SkylarkList.class); |
| if (asList == null) { |
| return null; |
| } |
| |
| return SkylarkList.castList(asList, clazz, null); |
| } |
| |
| private static <T> SkylarkList<T> listFromNoneableOrEmpty(Object object, Class<T> clazz) |
| throws EvalException { |
| List<T> value = listFromNoneable(object, clazz); |
| if (value == null) { |
| return SkylarkList.createImmutable(ImmutableList.of()); |
| } |
| |
| return SkylarkList.createImmutable(value); |
| } |
| |
| public static <T extends NativeInfo> SkylarkList<T> getProviders( |
| SkylarkList<ConfiguredTarget> targets, NativeProvider<T> provider) { |
| return SkylarkList.createImmutable( |
| targets |
| .stream() |
| .map(target -> target.get(provider)) |
| .filter(Objects::nonNull) |
| .collect(ImmutableList.toImmutableList())); |
| } |
| } |