// 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; } }