// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.rules.android;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.BuildType;
/**
* Represents a container for the {@link ResourceContainer}s for a given library. This is
* abstraction simplifies the process of managing and exporting the direct and transitive resource
* dependencies of an android rule, as well as providing type safety.
*
*
The transitive and direct dependencies are not guaranteed to be disjoint. If a
* library is included in both the transitive and direct dependencies, it will appear twice. This
* requires consumers to manage duplicated resources gracefully.
*/
@Immutable
public final class ResourceDependencies {
/**
* Contains all the transitive resources that are not generated by the direct ancestors of the
* current rule.
*/
private final NestedSet transitiveResources;
/**
* Contains all the direct dependencies of the current target. Since a given direct dependency can
* act as a "forwarding" library, collecting all the direct resource from it's dependencies
* and providing them as "direct" dependencies to maintain merge order, this uses a NestedSet to
* properly maintain ordering and ease of merging.
*/
private final NestedSet directResources;
private final NestedSet transitiveResourceRoots;
private final NestedSet transitiveManifests;
private final NestedSet transitiveAapt2RTxt;
private final NestedSet transitiveSymbolsBin;
private final NestedSet transitiveStaticLib;
private final NestedSet transitiveRTxt;
/** Whether the resources of the current rule should be treated as neverlink. */
private final boolean neverlink;
public static ResourceDependencies fromRuleResources(RuleContext ruleContext, boolean neverlink) {
if (!hasResourceAttribute(ruleContext)) {
return empty();
}
NestedSetBuilder transitiveDependencies = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder directDependencies = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveResourceRoots = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveManifests = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveAapt2RTxt = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveSymbolsBin = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveStaticLib = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveRTxt = NestedSetBuilder.naiveLinkOrder();
extractFromAttributes(
ImmutableList.of("resources"),
ruleContext,
transitiveDependencies,
directDependencies,
transitiveResourceRoots,
transitiveManifests,
transitiveAapt2RTxt,
transitiveSymbolsBin,
transitiveStaticLib,
transitiveRTxt);
return new ResourceDependencies(
neverlink,
transitiveDependencies.build(),
directDependencies.build(),
transitiveResourceRoots.build(),
transitiveManifests.build(),
transitiveAapt2RTxt.build(),
transitiveSymbolsBin.build(),
transitiveStaticLib.build(),
transitiveRTxt.build());
}
public static ResourceDependencies fromRuleDeps(RuleContext ruleContext, boolean neverlink) {
NestedSetBuilder transitiveDependencies = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder directDependencies = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveResourceRoots = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveManifests = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveAapt2RTxt = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveSymbolsBin = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveStaticLib = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveRTxt = NestedSetBuilder.naiveLinkOrder();
extractFromAttributes(
AndroidCommon.TRANSITIVE_ATTRIBUTES,
ruleContext,
transitiveDependencies,
directDependencies,
transitiveResourceRoots,
transitiveManifests,
transitiveAapt2RTxt,
transitiveSymbolsBin,
transitiveStaticLib,
transitiveRTxt);
return new ResourceDependencies(
neverlink,
transitiveDependencies.build(),
directDependencies.build(),
transitiveResourceRoots.build(),
transitiveManifests.build(),
transitiveAapt2RTxt.build(),
transitiveSymbolsBin.build(),
transitiveStaticLib.build(),
transitiveRTxt.build());
}
public static ResourceDependencies fromRuleResourceAndDeps(RuleContext ruleContext,
boolean neverlink) {
NestedSetBuilder transitiveDependencies = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder directDependencies = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveResourceRoots = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveManifests = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveAapt2RTxt = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveSymbolsBin = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveStaticLib = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder transitiveRTxt = NestedSetBuilder.naiveLinkOrder();
if (hasResourceAttribute(ruleContext)) {
extractFromAttributes(
ImmutableList.of("resources"),
ruleContext,
transitiveDependencies,
directDependencies,
transitiveResourceRoots,
transitiveManifests,
transitiveAapt2RTxt,
transitiveSymbolsBin,
transitiveStaticLib,
transitiveRTxt);
}
if (directDependencies.isEmpty()) {
// There are no resources, so this library will forward the direct and transitive dependencies
// without changes.
extractFromAttributes(
AndroidCommon.TRANSITIVE_ATTRIBUTES,
ruleContext,
transitiveDependencies,
directDependencies,
transitiveResourceRoots,
transitiveManifests,
transitiveAapt2RTxt,
transitiveSymbolsBin,
transitiveStaticLib,
transitiveRTxt);
} else {
// There are resources, so the direct dependencies and the transitive will be merged into
// the transitive dependencies. This maintains the relationship of the resources being
// directly on the rule.
extractFromAttributes(
AndroidCommon.TRANSITIVE_ATTRIBUTES,
ruleContext,
transitiveDependencies,
transitiveDependencies,
transitiveResourceRoots,
transitiveManifests,
transitiveAapt2RTxt,
transitiveSymbolsBin,
transitiveStaticLib,
transitiveRTxt);
}
return new ResourceDependencies(
neverlink,
transitiveDependencies.build(),
directDependencies.build(),
transitiveResourceRoots.build(),
transitiveManifests.build(),
transitiveAapt2RTxt.build(),
transitiveSymbolsBin.build(),
transitiveStaticLib.build(),
transitiveRTxt.build());
}
private static void extractFromAttributes(
Iterable attributeNames,
RuleContext ruleContext,
NestedSetBuilder builderForTransitive,
NestedSetBuilder builderForDirect,
NestedSetBuilder transitiveResourceRoots,
NestedSetBuilder transitiveManifests,
NestedSetBuilder transitiveAapt2RTxt,
NestedSetBuilder transitiveSymbolsBin,
NestedSetBuilder transitiveStaticLib,
NestedSetBuilder transitiveRTxt) {
AttributeMap attributes = ruleContext.attributes();
for (String attr : attributeNames) {
if (!attributes.has(attr, BuildType.LABEL_LIST) && !attributes.has(attr, BuildType.LABEL)) {
continue;
}
for (AndroidResourcesProvider resources :
ruleContext.getPrerequisites(attr, Mode.TARGET, AndroidResourcesProvider.class)) {
builderForTransitive.addTransitive(resources.getTransitiveAndroidResources());
builderForDirect.addTransitive(resources.getDirectAndroidResources());
transitiveResourceRoots.addTransitive(resources.getTransitiveResourceRoots());
transitiveManifests.addTransitive(resources.getTransitiveManifests());
transitiveAapt2RTxt.addTransitive(resources.getTransitiveAapt2RTxt());
transitiveSymbolsBin.addTransitive(resources.getTransitiveSymbolsBin());
transitiveStaticLib.addTransitive(resources.getTransitiveStaticLib());
transitiveRTxt.addTransitive(resources.getTransitiveRTxt());
}
}
}
/**
* Check for the existence of a "resources" attribute.
*
* The existence of the resources attribute is not guaranteed on for all android rules, so it
* is necessary to check for it.
*
* @param ruleContext The context to check.
* @return True if the ruleContext has resources, otherwise, false.
*/
private static boolean hasResourceAttribute(RuleContext ruleContext) {
return ruleContext.attributes().has("resources", BuildType.LABEL);
}
@Override
public String toString() {
return "ResourceDependencies [transitiveResources=" + transitiveResources + ", directResources="
+ directResources + "]";
}
/**
* Creates an empty ResourceDependencies instance. This is used when an AndroidResources rule
* is the only resource dependency. The most common case is the AndroidTest rule.
*/
public static ResourceDependencies empty() {
return new ResourceDependencies(
false,
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER));
}
private ResourceDependencies(
boolean neverlink,
NestedSet transitiveResources,
NestedSet directResources,
NestedSet transitiveResourceRoots,
NestedSet transitiveManifests,
NestedSet transitiveAapt2RTxt,
NestedSet transitiveSymbolsBin,
NestedSet transitiveStaticLib,
NestedSet transitiveRTxt) {
this.neverlink = neverlink;
this.transitiveResources = transitiveResources;
this.directResources = directResources;
this.transitiveResourceRoots = transitiveResourceRoots;
this.transitiveManifests = transitiveManifests;
this.transitiveAapt2RTxt = transitiveAapt2RTxt;
this.transitiveSymbolsBin = transitiveSymbolsBin;
this.transitiveStaticLib = transitiveStaticLib;
this.transitiveRTxt = transitiveRTxt;
}
/** Returns a copy of this instance with filtered resources. The original object is unchanged. */
public ResourceDependencies filter(RuleContext ruleContext, ResourceFilter filter) {
// Note that this doesn't filter any of the dependent artifacts. This
// means that if any resource changes, the corresponding actions will get
// re-executed
return new ResourceDependencies(
neverlink,
filter.filterDependencies(ruleContext, transitiveResources),
filter.filterDependencies(ruleContext, directResources),
transitiveResourceRoots,
transitiveManifests,
transitiveAapt2RTxt,
transitiveSymbolsBin,
transitiveStaticLib,
transitiveRTxt);
}
/**
* Creates a new AndroidResourcesProvider with the supplied ResourceContainer as the direct dep.
*
* When a library produces a new resource container the AndroidResourcesProvider should use
* that container as a the direct dependency for that library. This makes the consuming rule to
* identify the new container and merge appropriately. The previous direct dependencies are then
* added to the transitive dependencies.
*
* @param label The label of the library exporting this provider.
* @param newDirectResource The new direct dependency for AndroidResourcesProvider
* @param isResourcesOnly if the direct dependency is either an android_resources
* target or an android_library target with no fields that android_resources targets do not
* provide.
* @return A provider with the current resources and label.
*/
public AndroidResourcesProvider toProvider(
Label label, ResourceContainer newDirectResource, boolean isResourcesOnly) {
if (neverlink) {
return ResourceDependencies.empty().toProvider(label, isResourcesOnly);
}
NestedSetBuilder transitiveResourceRoots = NestedSetBuilder.naiveLinkOrder();
transitiveResourceRoots.addTransitive(this.transitiveResourceRoots);
transitiveResourceRoots.addAll(newDirectResource.getArtifacts());
NestedSetBuilder transitiveManifests = NestedSetBuilder.naiveLinkOrder();
transitiveManifests.addTransitive(this.transitiveManifests);
if (newDirectResource.getManifest() != null) {
transitiveManifests.add(newDirectResource.getManifest());
}
NestedSetBuilder transitiveAapt2RTxt = NestedSetBuilder.naiveLinkOrder();
transitiveAapt2RTxt.addTransitive(this.transitiveAapt2RTxt);
if (newDirectResource.getAapt2RTxt() != null) {
transitiveAapt2RTxt.add(newDirectResource.getAapt2RTxt());
}
NestedSetBuilder transitiveSymbolsBin = NestedSetBuilder.naiveLinkOrder();
transitiveSymbolsBin.addTransitive(this.transitiveSymbolsBin);
if (newDirectResource.getSymbols() != null) {
transitiveSymbolsBin.add(newDirectResource.getSymbols());
}
NestedSetBuilder transitiveStaticLib = NestedSetBuilder.naiveLinkOrder();
transitiveStaticLib.addTransitive(this.transitiveStaticLib);
if (newDirectResource.getStaticLibrary() != null) {
transitiveStaticLib.add(newDirectResource.getStaticLibrary());
}
NestedSetBuilder transitiveRTxt = NestedSetBuilder.naiveLinkOrder();
transitiveRTxt.addTransitive(this.transitiveRTxt);
if (newDirectResource.getRTxt() != null) {
transitiveRTxt.add(newDirectResource.getRTxt());
}
return AndroidResourcesProvider.create(
label,
NestedSetBuilder.naiveLinkOrder()
.addTransitive(transitiveResources)
.addTransitive(directResources)
.build(),
NestedSetBuilder.naiveLinkOrder().add(newDirectResource).build(),
transitiveResourceRoots.build(),
transitiveManifests.build(),
transitiveAapt2RTxt.build(),
transitiveSymbolsBin.build(),
transitiveStaticLib.build(),
transitiveRTxt.build(),
isResourcesOnly);
}
/**
* Create a new AndroidResourcesProvider from the dependencies of this library.
*
* When a library doesn't export resources it should simply forward the current transitive and
* direct resources to the consuming rule. This allows the consuming rule to make decisions about
* the resource merging as if this library didn't exist.
*
* @param label The label of the library exporting this provider.
* @param isResourcesOnly if the direct dependency is either an android_resources
* target or an android_library target with no fields that android_resources targets do not
* provide.
* @return A provider with the current resources and label.
*/
public AndroidResourcesProvider toProvider(Label label, boolean isResourcesOnly) {
if (neverlink) {
return ResourceDependencies.empty().toProvider(label, isResourcesOnly);
}
return AndroidResourcesProvider.create(
label,
transitiveResources,
directResources,
transitiveResourceRoots,
transitiveManifests,
transitiveAapt2RTxt,
transitiveSymbolsBin,
transitiveStaticLib,
transitiveRTxt,
isResourcesOnly);
}
/** Provides an NestedSet of the direct and transitive resources. */
public NestedSet getResources() {
return NestedSetBuilder.naiveLinkOrder()
.addTransitive(directResources)
.addTransitive(transitiveResources)
.build();
}
public NestedSet getTransitiveResources() {
return transitiveResources;
}
public NestedSet getDirectResources() {
return directResources;
}
public NestedSet getTransitiveResourceRoots() {
return transitiveResourceRoots;
}
public NestedSet getTransitiveManifests() {
return transitiveManifests;
}
public NestedSet getTransitiveAapt2RTxt() {
return transitiveAapt2RTxt;
}
public NestedSet getTransitiveSymbolsBin() {
return transitiveSymbolsBin;
}
public NestedSet getTransitiveStaticLib() {
return transitiveStaticLib;
}
public NestedSet getTransitiveRTxt() {
return transitiveRTxt;
}
}