blob: 389115afd1528a947adb06802c516b53df2aa5d5 [file] [log] [blame]
// 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()));
}
}