// 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.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleContext; 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. * * <p>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<ResourceContainer> 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<ResourceContainer> directResources; /** * 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<ResourceContainer> transitiveDependencies = NestedSetBuilder.naiveLinkOrder(); NestedSetBuilder<ResourceContainer> directDependencies = NestedSetBuilder.naiveLinkOrder(); extractFromAttributes( ImmutableList.of("resources"), ruleContext, transitiveDependencies, directDependencies); return new ResourceDependencies(neverlink, transitiveDependencies.build(), directDependencies.build()); } public static ResourceDependencies fromRuleDeps(RuleContext ruleContext, boolean neverlink) { NestedSetBuilder<ResourceContainer> transitiveDependencies = NestedSetBuilder.naiveLinkOrder(); NestedSetBuilder<ResourceContainer> directDependencies = NestedSetBuilder.naiveLinkOrder(); extractFromAttributes(AndroidCommon.TRANSITIVE_ATTRIBUTES, ruleContext, transitiveDependencies, directDependencies); return new ResourceDependencies(neverlink, transitiveDependencies.build(), directDependencies.build()); } public static ResourceDependencies fromRuleResourceAndDeps(RuleContext ruleContext, boolean neverlink) { NestedSetBuilder<ResourceContainer> transitiveDependencies = NestedSetBuilder.naiveLinkOrder(); NestedSetBuilder<ResourceContainer> directDependencies = NestedSetBuilder.naiveLinkOrder(); if (hasResourceAttribute(ruleContext)) { extractFromAttributes( ImmutableList.of("resources"), ruleContext, transitiveDependencies, directDependencies); } 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); } 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); } return new ResourceDependencies(neverlink, transitiveDependencies.build(), directDependencies.build()); } private static void extractFromAttributes(Iterable<String> attributeNames, RuleContext ruleContext, NestedSetBuilder<ResourceContainer> builderForTransitive, NestedSetBuilder<ResourceContainer> builderForDirect) { 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()); } } } /** * Check for the existence of a "resources" attribute. * * <p>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.<ResourceContainer>emptySet(Order.NAIVE_LINK_ORDER), NestedSetBuilder.<ResourceContainer>emptySet(Order.NAIVE_LINK_ORDER)); } private ResourceDependencies( boolean neverlink, NestedSet<ResourceContainer> transitiveResources, NestedSet<ResourceContainer> directResources) { this.neverlink = neverlink; this.transitiveResources = transitiveResources; this.directResources = directResources; } /** Returns a copy of this instance with filtered resources. The original object is unchanged. */ public ResourceDependencies filter(RuleContext ruleContext, ResourceFilter filter) { return new ResourceDependencies( neverlink, filter.filterDependencies(ruleContext, transitiveResources), filter.filterDependencies(ruleContext, directResources)); } /** * Creates a new AndroidResourcesProvider with the supplied ResourceContainer as the direct dep. * * <p>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 * @return A provider with the current resources and label. */ public AndroidResourcesProvider toProvider(Label label, ResourceContainer newDirectResource) { if (neverlink) { return ResourceDependencies.empty().toProvider(label); } return AndroidResourcesProvider.create( label, NestedSetBuilder.<ResourceContainer>naiveLinkOrder() .addTransitive(transitiveResources) .addTransitive(directResources) .build(), NestedSetBuilder.<ResourceContainer>naiveLinkOrder().add(newDirectResource).build()); } /** * Create a new AndroidResourcesProvider from the dependencies of this library. * * <p>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. * @return A provider with the current resources and label. */ public AndroidResourcesProvider toProvider(Label label) { if (neverlink) { return ResourceDependencies.empty().toProvider(label); } return AndroidResourcesProvider.create(label, transitiveResources, directResources); } /** * Provides an NestedSet of the direct and transitive resources. */ public Iterable<ResourceContainer> getResources() { return NestedSetBuilder.<ResourceContainer>naiveLinkOrder() .addTransitive(directResources) .addTransitive(transitiveResources) .build(); } public NestedSet<ResourceContainer> getTransitiveResources() { return transitiveResources; } public NestedSet<ResourceContainer> getDirectResources() { return directResources; } }