// Copyright 2014 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.analysis;
import com.google.common.base.Function;
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.cmdline.Label;
import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.AspectClass;
import com.google.devtools.build.lib.packages.AspectDefinition;
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.Type;
import com.google.devtools.build.lib.util.Preconditions;
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,
Aspect aspect,
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 != null ? aspect.getDefinition() : null,
config,
hostConfig,
configConditions);
visitRule(rule, aspect, 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());
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