blob: 9590d1d92cff93b5602fc0a28ddd69122917c86e [file] [log] [blame]
// 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.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
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.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.packages.RuleErrorConsumer;
import java.util.Optional;
import javax.annotation.Nullable;
/**
* Represents a container for the resource dependencies for a given target. This 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.
*
* <p>TODO(b/76418178): Once resource processing is fully decoupled from asset and manifest
* processing, remove asset and manifest fields from this class.
*/
@Immutable
public final class ResourceDependencies {
/**
* Contains all the transitive resources that are not generated by the direct ancestors of the
* current rule.
*
* @deprecated We are migrating towards storing each type of Artifact in a different NestedSet.
* This should allow greater efficiency since we don't need to unroll this NestedSet to get a
* particular input. TODO(b/67996945): Complete this migration (or better yet, remove
* transitive dependencies entirely).
*/
@Deprecated private final NestedSet<ValidatedAndroidResources> transitiveResourceContainers;
/**
* 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.
*
* <p>Unlike {@link transitiveResourceContainers} above, this isn't deprecated, since there isn't
* much to unroll.
*/
private final NestedSet<ValidatedAndroidResources> directResourceContainers;
/**
* Transitive resource files for this target.
*
* <p>We keep them separate from the {@code transitiveAssets} so that we can filter them. Note
* that these uses of "transitive" are different from the ones above---the ones below include
* direct dependencies.
*/
private final NestedSet<Artifact> transitiveResources;
private final NestedSet<Artifact> transitiveManifests;
private final NestedSet<Artifact> transitiveAapt2RTxt;
private final NestedSet<Artifact> transitiveAapt2ValidationArtifacts;
private final NestedSet<Artifact> transitiveSymbolsBin;
private final NestedSet<Artifact> transitiveCompiledSymbols;
private final NestedSet<Artifact> transitiveStaticLib;
private final NestedSet<Artifact> transitiveRTxt;
/** Whether the resources of the current rule should be treated as neverlink. */
private final boolean neverlink;
public static ResourceDependencies fromRuleDeps(RuleContext ruleContext, boolean neverlink) {
return fromProviders(
AndroidCommon.getTransitivePrerequisites(
ruleContext, Mode.TARGET, AndroidResourcesInfo.PROVIDER),
neverlink);
}
public static ResourceDependencies fromProviders(
Iterable<AndroidResourcesInfo> providers, boolean neverlink) {
NestedSetBuilder<ValidatedAndroidResources> transitiveDependencies =
NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<ValidatedAndroidResources> directDependencies =
NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<Artifact> transitiveResources = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<Artifact> transitiveManifests = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<Artifact> transitiveAapt2RTxt = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<Artifact> transitiveAapt2ValidationArtifacts =
NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<Artifact> transitiveSymbolsBin = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<Artifact> transitiveCompiledSymbols = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<Artifact> transitiveStaticLib = NestedSetBuilder.naiveLinkOrder();
NestedSetBuilder<Artifact> transitiveRTxt = NestedSetBuilder.naiveLinkOrder();
for (AndroidResourcesInfo resources : providers) {
transitiveDependencies.addTransitive(resources.getTransitiveAndroidResources());
directDependencies.addTransitive(resources.getDirectAndroidResources());
transitiveResources.addTransitive(resources.getTransitiveResources());
transitiveManifests.addTransitive(resources.getTransitiveManifests());
transitiveAapt2RTxt.addTransitive(resources.getTransitiveAapt2RTxt());
transitiveAapt2ValidationArtifacts.addTransitive(
resources.getTransitiveAapt2ValidationArtifacts());
transitiveSymbolsBin.addTransitive(resources.getTransitiveSymbolsBin());
transitiveCompiledSymbols.addTransitive(resources.getTransitiveCompiledSymbols());
transitiveStaticLib.addTransitive(resources.getTransitiveStaticLib());
transitiveRTxt.addTransitive(resources.getTransitiveRTxt());
}
return new ResourceDependencies(
neverlink,
transitiveDependencies.build(),
directDependencies.build(),
transitiveResources.build(),
transitiveManifests.build(),
transitiveAapt2RTxt.build(),
transitiveAapt2ValidationArtifacts.build(),
transitiveSymbolsBin.build(),
transitiveCompiledSymbols.build(),
transitiveStaticLib.build(),
transitiveRTxt.build());
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("transitiveResourceContainers", transitiveResourceContainers)
.add("directResourceContainers", directResourceContainers)
.add("transitiveResources", transitiveResources)
.add("transitiveManifests", transitiveManifests)
.add("transitiveAapt2RTxt", transitiveAapt2RTxt)
.add("transitiveAapt2ValidationArtifacts", transitiveAapt2ValidationArtifacts)
.add("transitiveSymbolsBin", transitiveSymbolsBin)
.add("transitiveCompiledSymbols", transitiveCompiledSymbols)
.add("transitiveStaticLib", transitiveStaticLib)
.add("transitiveRTxt", transitiveRTxt)
.add("neverlink", neverlink)
.toString();
}
/**
* 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),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER));
}
private ResourceDependencies(
boolean neverlink,
NestedSet<ValidatedAndroidResources> transitiveResourceContainers,
NestedSet<ValidatedAndroidResources> directResourceContainers,
NestedSet<Artifact> transitiveResources,
NestedSet<Artifact> transitiveManifests,
NestedSet<Artifact> transitiveAapt2RTxt,
NestedSet<Artifact> transitiveAapt2ValidationArtifacts,
NestedSet<Artifact> transitiveSymbolsBin,
NestedSet<Artifact> transitiveCompiledSymbols,
NestedSet<Artifact> transitiveStaticLib,
NestedSet<Artifact> transitiveRTxt) {
this.neverlink = neverlink;
this.transitiveResourceContainers = transitiveResourceContainers;
this.directResourceContainers = directResourceContainers;
this.transitiveResources = transitiveResources;
this.transitiveManifests = transitiveManifests;
this.transitiveAapt2RTxt = transitiveAapt2RTxt;
this.transitiveAapt2ValidationArtifacts = transitiveAapt2ValidationArtifacts;
this.transitiveSymbolsBin = transitiveSymbolsBin;
this.transitiveCompiledSymbols = transitiveCompiledSymbols;
this.transitiveStaticLib = transitiveStaticLib;
this.transitiveRTxt = transitiveRTxt;
}
/** Returns a copy of this instance with filtered resources. The original object is unchanged. */
public ResourceDependencies filter(RuleErrorConsumer errorConsumer, ResourceFilter resourceFilter)
throws RuleErrorException {
Optional<NestedSet<Artifact>> filteredResources =
resourceFilter.maybeFilterDependencies(transitiveResources);
if (!filteredResources.isPresent()) {
// No filtering was done.
return this;
}
// 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 withResources(
resourceFilter.filterDependencyContainers(errorConsumer, transitiveResourceContainers),
resourceFilter.filterDependencyContainers(errorConsumer, directResourceContainers),
filteredResources.get());
}
@VisibleForTesting
ResourceDependencies withResources(
NestedSet<ValidatedAndroidResources> transitiveResourceContainers,
NestedSet<ValidatedAndroidResources> directResourceContainers,
NestedSet<Artifact> transitiveResources) {
return new ResourceDependencies(
neverlink,
transitiveResourceContainers,
directResourceContainers,
transitiveResources,
transitiveManifests,
transitiveAapt2RTxt,
transitiveAapt2ValidationArtifacts,
transitiveSymbolsBin,
transitiveCompiledSymbols,
transitiveStaticLib,
transitiveRTxt);
}
/**
* Creates a new AndroidResourcesInfo with the supplied ResourceContainer as the direct dep.
*
* <p>When a library produces a new resource container the AndroidResourcesInfo 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 newDirectResource The new direct dependency for AndroidResourcesInfo
* @return A provider with the current resources and label.
*/
public AndroidResourcesInfo toInfo(ValidatedAndroidResources newDirectResource) {
if (neverlink) {
return ResourceDependencies.empty()
.toInfo(
newDirectResource.getLabel(),
newDirectResource.getProcessedManifest(),
newDirectResource.getRTxt());
}
return new AndroidResourcesInfo(
newDirectResource.getLabel(),
newDirectResource.getProcessedManifest().toProvider(),
newDirectResource.getRTxt(),
// TODO(b/117338320): This is incorrect; direct should come before transitive, and the
// order should be link order instead of naive link order. However, some applications may
// depend on this incorrect order.
NestedSetBuilder.<ValidatedAndroidResources>naiveLinkOrder()
.addTransitive(transitiveResourceContainers)
.addTransitive(directResourceContainers)
.build(),
NestedSetBuilder.<ValidatedAndroidResources>naiveLinkOrder()
.add(newDirectResource.export())
.build(),
NestedSetBuilder.<Artifact>naiveLinkOrder()
.addTransitive(transitiveResources)
.addAll(newDirectResource.getResources())
.build(),
withDirectAndTransitive(newDirectResource.getManifest(), transitiveManifests),
withDirectAndTransitive(newDirectResource.getAapt2RTxt(), transitiveAapt2RTxt),
withDirectAndTransitive(
newDirectResource.getAapt2ValidationArtifact(), transitiveAapt2ValidationArtifacts),
withDirectAndTransitive(newDirectResource.getSymbols(), transitiveSymbolsBin),
withDirectAndTransitive(newDirectResource.getCompiledSymbols(), transitiveCompiledSymbols),
withDirectAndTransitive(newDirectResource.getStaticLibrary(), transitiveStaticLib),
withDirectAndTransitive(newDirectResource.getRTxt(), transitiveRTxt));
}
/**
* Create a new AndroidResourcesInfo 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 AndroidResourcesInfo toInfo(
Label label, ProcessedAndroidManifest manifest, Artifact rTxt) {
if (neverlink) {
return ResourceDependencies.empty().toInfo(label, manifest, rTxt);
}
return new AndroidResourcesInfo(
label,
manifest.toProvider(),
rTxt,
transitiveResourceContainers,
directResourceContainers,
transitiveResources,
transitiveManifests,
transitiveAapt2RTxt,
transitiveAapt2ValidationArtifacts,
transitiveSymbolsBin,
transitiveCompiledSymbols,
transitiveStaticLib,
transitiveRTxt);
}
private static NestedSet<Artifact> withDirectAndTransitive(
@Nullable Artifact direct, NestedSet<Artifact> transitive) {
NestedSetBuilder<Artifact> builder = NestedSetBuilder.naiveLinkOrder();
builder.addTransitive(transitive);
if (direct != null) {
builder.add(direct);
}
return builder.build();
}
/**
* Provides an NestedSet of the direct and transitive resources.
*
* @deprecated Rather than accessing the ResourceContainers, use other methods in this class to
* get the specific Artifacts you need instead.
*/
@Deprecated
public NestedSet<ValidatedAndroidResources> getResourceContainers() {
return NestedSetBuilder.<ValidatedAndroidResources>naiveLinkOrder()
.addTransitive(directResourceContainers)
.addTransitive(transitiveResourceContainers)
.build();
}
/**
* @deprecated Rather than accessing the ResourceContainers, use other methods in this class to
* get the specific Artifacts you need instead.
*/
@Deprecated
public NestedSet<ValidatedAndroidResources> getTransitiveResourceContainers() {
return transitiveResourceContainers;
}
public NestedSet<ValidatedAndroidResources> getDirectResourceContainers() {
return directResourceContainers;
}
public NestedSet<Artifact> getTransitiveResources() {
return transitiveResources;
}
public NestedSet<Artifact> getTransitiveManifests() {
return transitiveManifests;
}
public NestedSet<Artifact> getTransitiveAapt2RTxt() {
return transitiveAapt2RTxt;
}
NestedSet<Artifact> getTransitiveAapt2ValidationArtifacts() {
return transitiveAapt2ValidationArtifacts;
}
public NestedSet<Artifact> getTransitiveSymbolsBin() {
return transitiveSymbolsBin;
}
/**
* @return The transitive closure of compiled symbols. Compiled symbols are zip files containing
* the compiled resource output of aapt2 compile
*/
public NestedSet<Artifact> getTransitiveCompiledSymbols() {
return transitiveCompiledSymbols;
}
public NestedSet<Artifact> getTransitiveStaticLib() {
return transitiveStaticLib;
}
public NestedSet<Artifact> getTransitiveRTxt() {
return transitiveRTxt;
}
}