// Copyright 2014 Google Inc. 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.analysis; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Verify; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap; import com.google.devtools.build.lib.packages.AspectDefinition; import com.google.devtools.build.lib.packages.AspectFactory; import com.google.devtools.build.lib.packages.AspectParameters; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.Attribute.LateBoundDefault; import com.google.devtools.build.lib.packages.Attribute.SplitTransition; import com.google.devtools.build.lib.packages.AttributeMap; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.EnvironmentGroup; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.NoSuchThingException; import com.google.devtools.build.lib.packages.OutputFile; import com.google.devtools.build.lib.packages.PackageGroup; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.syntax.Type; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.annotation.Nullable; /** * Resolver for dependencies between configured targets. * *

Includes logic to derive the right configurations depending on transition type. */ public abstract class DependencyResolver { /** * A dependency of a configured target through a label. * *

For static configurations: includes the target and the configuration of the dependency * configured target and any aspects that may be required. * *

For dynamic configurations: includes the target and the desired configuration transitions * that should be applied to produce the dependency's configuration. It's the caller's * responsibility to construct an actual configuration out of that. * *

Note that the presence of an aspect here does not necessarily mean that it will be created. * They will be filtered based on the {@link TransitiveInfoProvider} instances their associated * configured targets have (we cannot do that here because the configured targets are not * available yet). No error or warning is reported in this case, because it is expected that rules * sometimes over-approximate the providers they supply in their definitions. */ public static final class Dependency { /** * Returns the {@link ConfiguredTargetKey} for a direct dependency. * *

Essentially the same information as {@link Dependency} minus the aspects. */ public static final Function TO_CONFIGURED_TARGET_KEY = new Function() { @Override public ConfiguredTargetKey apply(Dependency input) { return new ConfiguredTargetKey(input.getLabel(), input.getConfiguration()); } }; private final Label label; // Only one of the two below fields is set. Use hasStaticConfiguration to determine which. @Nullable private final BuildConfiguration configuration; private final Attribute.Transition transition; private final boolean hasStaticConfiguration; private final ImmutableSet aspects; /** * Constructs a Dependency with a given configuration (suitable for static configuration * builds). */ public Dependency(Label label, @Nullable BuildConfiguration configuration, ImmutableSet aspects) { this.label = Preconditions.checkNotNull(label); this.configuration = configuration; this.transition = null; this.hasStaticConfiguration = true; this.aspects = Preconditions.checkNotNull(aspects); } /** * Constructs a Dependency with a given configuration (suitable for static configuration * builds). */ public Dependency(Label label, @Nullable BuildConfiguration configuration) { this(label, configuration, ImmutableSet.of()); } /** * Constructs a Dependency with a given transition (suitable for dynamic configuration builds). */ public Dependency(Label label, Attribute.Transition transition, ImmutableSet aspects) { this.label = Preconditions.checkNotNull(label); this.configuration = null; this.transition = Preconditions.checkNotNull(transition); this.hasStaticConfiguration = false; this.aspects = Preconditions.checkNotNull(aspects); } /** * Does this dependency represent a null configuration? */ public boolean isNull() { return configuration == null && transition == null; } /** * Does this dependency specify a static configuration (vs. a dynamic transition)? */ public boolean hasStaticConfiguration() { return hasStaticConfiguration; } public Label getLabel() { return label; } @Nullable public BuildConfiguration getConfiguration() { Verify.verify(hasStaticConfiguration); return configuration; } public Attribute.Transition getTransition() { Verify.verify(!hasStaticConfiguration); return transition; } public ImmutableSet getAspects() { return aspects; } @Override public int hashCode() { return Objects.hash(label, configuration, aspects); } @Override public boolean equals(Object other) { if (!(other instanceof Dependency)) { return false; } Dependency otherDep = (Dependency) other; return label.equals(otherDep.label) && (configuration == otherDep.configuration || (configuration != null && configuration.equals(otherDep.configuration))) && aspects.equals(otherDep.aspects); } @Override public String toString() { return String.format( "Dependency{label=%s, configuration=%s, aspects=%s}", label, configuration, aspects); } } protected DependencyResolver() { } /** * Returns ids for dependent nodes of a given node, sorted by attribute. Note that some * dependencies do not have a corresponding attribute here, and we use the null attribute to * represent those edges. Visibility attributes are only visited if {@code visitVisibility} is * {@code true}. * *

If {@code aspect} is null, returns the dependent nodes of the configured target node * representing the given target and configuration, otherwise that of the aspect node accompanying * the aforementioned configured target node for the specified aspect. * *

The values are not simply labels because this also implements the first step of applying * configuration transitions, namely, split transitions. This needs to be done before the labels * are resolved because late bound attributes depend on the configuration. A good example for this * is @{code :cc_toolchain}. * *

The long-term goal is that most configuration transitions be applied here. However, in order * to do that, we first have to eliminate transitions that depend on the rule class of the * dependency. */ public final ListMultimap dependentNodeMap( TargetAndConfiguration node, BuildConfiguration hostConfig, AspectDefinition aspect, AspectParameters aspectParameters, Set configConditions) throws EvalException, InterruptedException { Target target = node.getTarget(); BuildConfiguration config = node.getConfiguration(); ListMultimap outgoingEdges = ArrayListMultimap.create(); if (target instanceof OutputFile) { Preconditions.checkNotNull(config); visitTargetVisibility(node, outgoingEdges.get(null)); Rule rule = ((OutputFile) target).getGeneratingRule(); outgoingEdges.put(null, new Dependency(rule.getLabel(), config)); } else if (target instanceof InputFile) { visitTargetVisibility(node, outgoingEdges.get(null)); } else if (target instanceof EnvironmentGroup) { visitTargetVisibility(node, outgoingEdges.get(null)); } else if (target instanceof Rule) { Preconditions.checkNotNull(config); visitTargetVisibility(node, outgoingEdges.get(null)); Rule rule = (Rule) target; ListMultimap labelMap = resolveAttributes(rule, aspect, config, hostConfig, configConditions); visitRule(rule, aspect, aspectParameters, labelMap, outgoingEdges); } else if (target instanceof PackageGroup) { visitPackageGroup(node, (PackageGroup) target, outgoingEdges.get(null)); } else { throw new IllegalStateException(target.getLabel().toString()); } return outgoingEdges; } private ListMultimap resolveAttributes( Rule rule, AspectDefinition aspect, BuildConfiguration configuration, BuildConfiguration hostConfiguration, Set configConditions) throws EvalException, InterruptedException { ConfiguredAttributeMapper attributeMap = ConfiguredAttributeMapper.of(rule, configConditions); attributeMap.validateAttributes(); List attributes; if (aspect == null) { attributes = rule.getRuleClassObject().getAttributes(); } else { attributes = new ArrayList<>(); attributes.addAll(rule.getRuleClassObject().getAttributes()); if (aspect != null) { attributes.addAll(aspect.getAttributes().values()); } } ImmutableSortedKeyListMultimap.Builder result = ImmutableSortedKeyListMultimap.builder(); resolveExplicitAttributes(rule, configuration, attributeMap, result); resolveImplicitAttributes(rule, configuration, attributeMap, attributes, result); resolveLateBoundAttributes(rule, configuration, hostConfiguration, attributeMap, attributes, result); // Add the rule's visibility labels (which may come from the rule or from package defaults). addExplicitDeps(result, rule, "visibility", rule.getVisibility().getDependencyLabels(), configuration); // Add package default constraints when the rule doesn't explicitly declare them. // // Note that this can have subtle implications for constraint semantics. For example: say that // package defaults declare compatibility with ':foo' and rule R declares compatibility with // ':bar'. Does that mean that R is compatible with [':foo', ':bar'] or just [':bar']? In other // words, did R's author intend to add additional compatibility to the package defaults or to // override them? More severely, what if package defaults "restrict" support to just [':baz']? // Should R's declaration signify [':baz'] + ['bar'], [ORIGINAL_DEFAULTS] + ['bar'], or // something else? // // Rather than try to answer these questions with possibly confusing logic, we take the // simple approach of assigning the rule's "restriction" attribute to the rule-declared value if // it exists, else the package defaults value (and likewise for "compatibility"). This may not // always provide what users want, but it makes it easy for them to understand how rule // declarations and package defaults intermix (and how to refactor them to get what they want). // // An alternative model would be to apply the "rule declaration" / "rule class defaults" // relationship, i.e. the rule class' "compatibility" and "restriction" declarations are merged // to generate a set of default environments, then the rule's declarations are independently // processed on top of that. This protects against obscure coupling behavior between // declarations from wildly different places (e.g. it offers clear answers to the examples posed // above). But within the scope of a single package it seems better to keep the model simple and // make the user responsible for resolving ambiguities. if (!rule.isAttributeValueExplicitlySpecified(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR)) { addExplicitDeps(result, rule, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, rule.getPackage().getDefaultCompatibleWith(), configuration); } if (!rule.isAttributeValueExplicitlySpecified(RuleClass.RESTRICTED_ENVIRONMENT_ATTR)) { addExplicitDeps(result, rule, RuleClass.RESTRICTED_ENVIRONMENT_ATTR, rule.getPackage().getDefaultRestrictedTo(), configuration); } return result.build(); } /** * Adds new dependencies to the given rule under the given attribute name * * @param result the builder for the attribute --> dependency labels map * @param rule the rule being evaluated * @param attrName the name of the attribute to add dependency labels to * @param labels the dependencies to add * @param configuration the configuration to apply to those dependencies */ private void addExplicitDeps( ImmutableSortedKeyListMultimap.Builder result, Rule rule, String attrName, Iterable

Throws {@link NoSuchThingException} if the target is known not to exist. * *

Returns null if the target is not ready to be returned at this moment. If getTarget returns * null once or more during a {@link #dependentNodeMap} call, the results of that call will be * incomplete. For use within Skyframe, where several iterations may be needed to discover * all dependencies. */ @Nullable protected abstract Target getTarget(Label label) throws NoSuchThingException; }