blob: 9fc3df4578149ba59458dfd729fa580cbbd43218 [file] [log] [blame]
// 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.skyframe;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.devtools.build.lib.analysis.Aspect;
import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment;
import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.DependencyResolver.Dependency;
import com.google.devtools.build.lib.analysis.LabelAndConfiguration;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.AspectDefinition;
import com.google.devtools.build.lib.packages.AspectFactory;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.InputFile;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.PackageGroup;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.skyframe.AspectFunction.AspectCreationException;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor.BuildViewProvider;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.ValueOrException2;
import com.google.devtools.build.skyframe.ValueOrException3;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* SkyFunction for {@link ConfiguredTargetValue}s.
*/
final class ConfiguredTargetFunction implements SkyFunction {
/**
* Exception class that signals an error during the evaluation of a dependency.
*/
public static class DependencyEvaluationException extends Exception {
private final SkyKey rootCauseSkyKey;
public DependencyEvaluationException(Exception cause) {
super(cause);
this.rootCauseSkyKey = null;
}
public DependencyEvaluationException(SkyKey rootCauseSkyKey, Exception cause) {
super(cause);
this.rootCauseSkyKey = rootCauseSkyKey;
}
/**
* Returns the key of the root cause or null if the problem was with this target.
*/
public SkyKey getRootCauseSkyKey() {
return rootCauseSkyKey;
}
@Override
public Exception getCause() {
return (Exception) super.getCause();
}
}
private static final Function<Dependency, SkyKey> TO_KEYS =
new Function<Dependency, SkyKey>() {
@Override
public SkyKey apply(Dependency input) {
return ConfiguredTargetValue.key(input.getLabel(), input.getConfiguration());
}
};
private final BuildViewProvider buildViewProvider;
ConfiguredTargetFunction(BuildViewProvider buildViewProvider) {
this.buildViewProvider = buildViewProvider;
}
@Override
public SkyValue compute(SkyKey key, Environment env) throws ConfiguredTargetFunctionException,
InterruptedException {
SkyframeBuildView view = buildViewProvider.getSkyframeBuildView();
ConfiguredTargetKey configuredTargetKey = (ConfiguredTargetKey) key.argument();
LabelAndConfiguration lc = LabelAndConfiguration.of(
configuredTargetKey.getLabel(), configuredTargetKey.getConfiguration());
BuildConfiguration configuration = lc.getConfiguration();
PackageValue packageValue =
(PackageValue) env.getValue(PackageValue.key(lc.getLabel().getPackageIdentifier()));
if (packageValue == null) {
return null;
}
Target target;
try {
target = packageValue.getPackage().getTarget(lc.getLabel().getName());
} catch (NoSuchTargetException e1) {
throw new ConfiguredTargetFunctionException(new NoSuchTargetException(lc.getLabel(),
"No such target"));
}
// TODO(bazel-team): This is problematic - we create the right key, but then end up with a value
// that doesn't match; we can even have the same value multiple times. However, I think it's
// only triggered in tests (i.e., in normal operation, the configuration passed in is already
// null).
if (target instanceof InputFile) {
// InputFileConfiguredTarget expects its configuration to be null since it's not used.
configuration = null;
} else if (target instanceof PackageGroup) {
// Same for PackageGroupConfiguredTarget.
configuration = null;
}
TargetAndConfiguration ctgValue =
new TargetAndConfiguration(target, configuration);
SkyframeDependencyResolver resolver = view.createDependencyResolver(env);
if (resolver == null) {
return null;
}
try {
// Get the configuration targets that trigger this rule's configurable attributes.
Set<ConfigMatchingProvider> configConditions =
getConfigConditions(ctgValue.getTarget(), env, resolver, ctgValue);
if (configConditions == null) {
// Those targets haven't yet been resolved.
return null;
}
ListMultimap<Attribute, ConfiguredTarget> depValueMap =
computeDependencies(env, resolver, ctgValue, null, configConditions);
return createConfiguredTarget(
view, env, target, configuration, depValueMap, configConditions);
} catch (DependencyEvaluationException e) {
throw new ConfiguredTargetFunctionException(e.getRootCauseSkyKey(), e.getCause());
}
}
/**
* Computes the direct dependencies of a node in the configured target graph (a configured
* target or an aspect).
*
* <p>Returns null if Skyframe hasn't evaluated the required dependencies yet. In this case, the
* caller should also return null to Skyframe.
*
* @param env the Skyframe environment
* @param resolver The dependency resolver
* @param ctgValue The label and the configuration of the node
* @param aspectDefinition the aspect of the node (if null, the node is a configured target,
* otherwise it's an asect)
* @param configConditions the configuration conditions for evaluating the attributes of the node
* @return an attribute -&gt; direct dependency multimap
* @throws ConfiguredTargetFunctionException
*/
@Nullable
static ListMultimap<Attribute, ConfiguredTarget> computeDependencies(
Environment env, SkyframeDependencyResolver resolver, TargetAndConfiguration ctgValue,
AspectDefinition aspectDefinition, Set<ConfigMatchingProvider> configConditions)
throws DependencyEvaluationException {
// 1. Create the map from attributes to list of (target, configuration) pairs.
ListMultimap<Attribute, Dependency> depValueNames;
try {
depValueNames = resolver.dependentNodeMap(ctgValue, aspectDefinition, configConditions);
} catch (EvalException e) {
env.getListener().handle(Event.error(e.getLocation(), e.getMessage()));
throw new DependencyEvaluationException(new ConfiguredValueCreationException(e.print()));
}
// 2. Resolve configured target dependencies and handle errors.
Map<SkyKey, ConfiguredTarget> depValues =
resolveConfiguredTargetDependencies(env, depValueNames.values(), ctgValue.getTarget());
if (depValues == null) {
return null;
}
// 3. Resolve required aspects.
ListMultimap<SkyKey, Aspect> depAspects = resolveAspectDependencies(
env, depValues, depValueNames.values());
if (depAspects == null) {
return null;
}
// 3. Merge the dependent configured targets and aspects into a single map.
return mergeAspects(depValueNames, depValues, depAspects);
}
/**
* Merges the each direct dependency configured target with the aspects associated with it.
*
* <p>Note that the combination of a configured target and its associated aspects are not
* represented by a Skyframe node. This is because there can possibly be many different
* combinations of aspects for a particular configured target, so it would result in a
* combinatiorial explosion of Skyframe nodes.
*/
private static ListMultimap<Attribute, ConfiguredTarget> mergeAspects(
ListMultimap<Attribute, Dependency> depValueNames,
Map<SkyKey, ConfiguredTarget> depConfiguredTargetMap,
ListMultimap<SkyKey, Aspect> depAspectMap) {
ListMultimap<Attribute, ConfiguredTarget> result = ArrayListMultimap.create();
for (Map.Entry<Attribute, Dependency> entry : depValueNames.entries()) {
Dependency dep = entry.getValue();
SkyKey depKey = TO_KEYS.apply(dep);
ConfiguredTarget depConfiguredTarget = depConfiguredTargetMap.get(depKey);
result.put(entry.getKey(),
RuleConfiguredTarget.mergeAspects(depConfiguredTarget, depAspectMap.get(depKey)));
}
return result;
}
/**
* Given a list of {@link Dependency} objects, returns a multimap from the {@link SkyKey} of the
* dependency to the {@link Aspect} instances that should be merged into it.
*
* <p>Returns null if the required aspects are not computed yet.
*/
@Nullable
private static ListMultimap<SkyKey, Aspect> resolveAspectDependencies(Environment env,
Map<SkyKey, ConfiguredTarget> configuredTargetMap, Iterable<Dependency> deps)
throws DependencyEvaluationException {
ListMultimap<SkyKey, Aspect> result = ArrayListMultimap.create();
Set<SkyKey> aspectKeys = new HashSet<>();
for (Dependency dep : deps) {
for (Class<? extends ConfiguredAspectFactory> depAspect : dep.getAspects()) {
aspectKeys.add(AspectValue.key(dep.getLabel(), dep.getConfiguration(), depAspect));
}
}
Map<SkyKey, ValueOrException3<
AspectCreationException, NoSuchThingException, ConfiguredValueCreationException>>
depAspects = env.getValuesOrThrow(aspectKeys, AspectCreationException.class,
NoSuchThingException.class, ConfiguredValueCreationException.class);
for (Dependency dep : deps) {
SkyKey depKey = TO_KEYS.apply(dep);
ConfiguredTarget depConfiguredTarget = configuredTargetMap.get(depKey);
List<AspectValue> aspects = new ArrayList<>();
for (Class<? extends ConfiguredAspectFactory> depAspect : dep.getAspects()) {
if (!aspectMatchesConfiguredTarget(depConfiguredTarget, depAspect)) {
continue;
}
SkyKey aspectKey = AspectValue.key(dep.getLabel(), dep.getConfiguration(), depAspect);
AspectValue aspectValue = null;
try {
aspectValue = (AspectValue) depAspects.get(aspectKey).get();
} catch (ConfiguredValueCreationException e) {
// The configured target should have been created in resolveConfiguredTargetDependencies()
throw new IllegalStateException(e);
} catch (NoSuchThingException | AspectCreationException e) {
AspectFactory depAspectFactory = AspectFactory.Util.create(depAspect);
throw new DependencyEvaluationException(new ConfiguredValueCreationException(
String.format("Evaluation of aspect %s on %s failed: %s",
depAspectFactory.getDefinition().getName(), dep.getLabel(), e.toString())));
}
if (aspectValue == null) {
// Dependent aspect has either not been computed yet or is in error.
return null;
}
result.put(depKey, aspectValue.get());
}
}
return result;
}
private static boolean aspectMatchesConfiguredTarget(ConfiguredTarget dep,
Class<? extends ConfiguredAspectFactory> aspectFactory) {
AspectDefinition aspectDefinition = AspectFactory.Util.create(aspectFactory).getDefinition();
for (Class<?> provider : aspectDefinition.getRequiredProviders()) {
if (dep.getProvider((Class<? extends TransitiveInfoProvider>) provider) == null) {
return false;
}
}
return true;
}
/**
* Returns which aspects are computable based on the precise set of providers direct dependencies
* publish (and not the upper estimate in their rule definition).
*
* <p>An aspect is computable for a particular configured target if the configured target supplies
* all the providers the aspect requires.
*
* @param upperEstimate a multimap from attribute to the upper estimates computed by
* {@link com.google.devtools.build.lib.analysis.DependencyResolver}.
* @param configuredTargetDeps a multimap from attribute to the directly dependent configured
* targets
* @return a multimap from attribute to the more precise {@link Dependency} objects
*/
private static ListMultimap<Attribute, Dependency> getComputableAspects(
ListMultimap<Attribute, Dependency> upperEstimate,
Map<SkyKey, ConfiguredTarget> configuredTargetDeps) {
ListMultimap<Attribute, Dependency> result = ArrayListMultimap.create();
for (Map.Entry<Attribute, Dependency> entry : upperEstimate.entries()) {
ConfiguredTarget dep =
configuredTargetDeps.get(TO_KEYS.apply(entry.getValue()));
List<Class<? extends ConfiguredAspectFactory>> depAspects = new ArrayList<>();
for (Class<? extends ConfiguredAspectFactory> candidate : entry.getValue().getAspects()) {
boolean ok = true;
for (Class<?> requiredProvider :
AspectFactory.Util.create(candidate).getDefinition().getRequiredProviders()) {
if (dep.getProvider((Class<? extends TransitiveInfoProvider>) requiredProvider) == null) {
ok = false;
break;
}
}
if (ok) {
depAspects.add(candidate);
}
}
result.put(entry.getKey(), new Dependency(
entry.getValue().getLabel(), entry.getValue().getConfiguration(),
ImmutableSet.copyOf(depAspects)));
}
return result;
}
/**
* Returns the set of {@link ConfigMatchingProvider}s that key the configurable attributes
* used by this rule.
*
* <p>>If the configured targets supplying those providers aren't yet resolved by the
* dependency resolver, returns null.
*/
@Nullable
static Set<ConfigMatchingProvider> getConfigConditions(Target target, Environment env,
SkyframeDependencyResolver resolver, TargetAndConfiguration ctgValue)
throws DependencyEvaluationException {
if (!(target instanceof Rule)) {
return ImmutableSet.of();
}
ImmutableSet.Builder<ConfigMatchingProvider> configConditions = ImmutableSet.builder();
// Collect the labels of the configured targets we need to resolve.
ListMultimap<Attribute, LabelAndConfiguration> configLabelMap = ArrayListMultimap.create();
RawAttributeMapper attributeMap = RawAttributeMapper.of(((Rule) target));
for (Attribute a : ((Rule) target).getAttributes()) {
for (Label configLabel : attributeMap.getConfigurabilityKeys(a.getName(), a.getType())) {
if (!Type.Selector.isReservedLabel(configLabel)) {
configLabelMap.put(a, LabelAndConfiguration.of(
configLabel, ctgValue.getConfiguration()));
}
}
}
if (configLabelMap.isEmpty()) {
return ImmutableSet.of();
}
// Collect the corresponding Skyframe configured target values. Abort early if they haven't
// been computed yet.
Collection<Dependency> configValueNames =
resolver.resolveRuleLabels(ctgValue, null, configLabelMap);
Map<SkyKey, ConfiguredTarget> configValues =
resolveConfiguredTargetDependencies(env, configValueNames, target);
if (configValues == null) {
return null;
}
// Get the configured targets as ConfigMatchingProvider interfaces.
for (Dependency entry : configValueNames) {
ConfiguredTarget value = configValues.get(TO_KEYS.apply(entry));
// The code above guarantees that value is non-null here.
ConfigMatchingProvider provider = value.getProvider(ConfigMatchingProvider.class);
if (provider != null) {
configConditions.add(provider);
} else {
// Not a valid provider for configuration conditions.
String message =
entry.getLabel() + " is not a valid configuration key for " + target.getLabel();
env.getListener().handle(Event.error(TargetUtils.getLocationMaybe(target), message));
throw new DependencyEvaluationException(new ConfiguredValueCreationException(message));
}
}
return configConditions.build();
}
/***
* Resolves the targets referenced in depValueNames and returns their ConfiguredTarget
* instances.
*
* <p>Returns null if not all instances are available yet.
*
*/
@Nullable
private static Map<SkyKey, ConfiguredTarget> resolveConfiguredTargetDependencies(
Environment env, Collection<Dependency> deps, Target target)
throws DependencyEvaluationException {
boolean ok = !env.valuesMissing();
String message = null;
Iterable<SkyKey> depKeys = Iterables.transform(deps, TO_KEYS);
// TODO(bazel-team): maybe having a two-exception argument is better than typing a generic
// Exception here.
Map<SkyKey, ValueOrException2<NoSuchTargetException,
NoSuchPackageException>> depValuesOrExceptions = env.getValuesOrThrow(depKeys,
NoSuchTargetException.class, NoSuchPackageException.class);
Map<SkyKey, ConfiguredTarget> depValues = new HashMap<>(depValuesOrExceptions.size());
SkyKey childKey = null;
NoSuchThingException transitiveChildException = null;
for (Map.Entry<SkyKey, ValueOrException2<NoSuchTargetException, NoSuchPackageException>> entry
: depValuesOrExceptions.entrySet()) {
ConfiguredTargetKey depKey = (ConfiguredTargetKey) entry.getKey().argument();
LabelAndConfiguration depLabelAndConfiguration = LabelAndConfiguration.of(
depKey.getLabel(), depKey.getConfiguration());
Label depLabel = depLabelAndConfiguration.getLabel();
ConfiguredTargetValue depValue = null;
NoSuchThingException directChildException = null;
try {
depValue = (ConfiguredTargetValue) entry.getValue().get();
} catch (NoSuchTargetException e) {
if (depLabel.equals(e.getLabel())) {
directChildException = e;
} else {
childKey = entry.getKey();
transitiveChildException = e;
}
} catch (NoSuchPackageException e) {
if (depLabel.getPackageName().equals(e.getPackageName())) {
directChildException = e;
} else {
childKey = entry.getKey();
transitiveChildException = e;
}
}
// If an exception wasn't caused by a direct child target value, we'll treat it the same
// as any other missing dep by setting ok = false below, and returning null at the end.
if (directChildException != null) {
// Only update messages for missing targets we depend on directly.
message = TargetUtils.formatMissingEdge(target, depLabel, directChildException);
env.getListener().handle(Event.error(TargetUtils.getLocationMaybe(target), message));
}
if (depValue == null) {
ok = false;
} else {
depValues.put(entry.getKey(), depValue.getConfiguredTarget());
}
}
if (message != null) {
throw new DependencyEvaluationException(new NoSuchTargetException(message));
}
if (childKey != null) {
throw new DependencyEvaluationException(childKey, transitiveChildException);
}
if (!ok) {
return null;
} else {
return depValues;
}
}
@Override
public String extractTag(SkyKey skyKey) {
return Label.print(((ConfiguredTargetKey) skyKey.argument()).getLabel());
}
@Nullable
private ConfiguredTargetValue createConfiguredTarget(SkyframeBuildView view,
Environment env, Target target, BuildConfiguration configuration,
ListMultimap<Attribute, ConfiguredTarget> depValueMap,
Set<ConfigMatchingProvider> configConditions)
throws ConfiguredTargetFunctionException,
InterruptedException {
boolean extendedSanityChecks = configuration != null && configuration.extendedSanityChecks();
StoredEventHandler events = new StoredEventHandler();
BuildConfiguration ownerConfig = (configuration == null)
? null : configuration.getArtifactOwnerConfiguration();
boolean allowRegisteringActions = configuration == null || configuration.isActionsEnabled();
CachingAnalysisEnvironment analysisEnvironment = view.createAnalysisEnvironment(
new ConfiguredTargetKey(target.getLabel(), ownerConfig), false,
extendedSanityChecks, events, env, allowRegisteringActions);
if (env.valuesMissing()) {
return null;
}
ConfiguredTarget configuredTarget = view.createConfiguredTarget(target, configuration,
analysisEnvironment, depValueMap, configConditions);
events.replayOn(env.getListener());
if (events.hasErrors()) {
analysisEnvironment.disable(target);
throw new ConfiguredTargetFunctionException(new ConfiguredValueCreationException(
"Analysis of target '" + target.getLabel() + "' failed; build aborted"));
}
Preconditions.checkState(!analysisEnvironment.hasErrors(),
"Analysis environment hasError() but no errors reported");
if (env.valuesMissing()) {
return null;
}
analysisEnvironment.disable(target);
Preconditions.checkNotNull(configuredTarget, target);
return new ConfiguredTargetValue(configuredTarget,
ImmutableList.copyOf(analysisEnvironment.getRegisteredActions()));
}
/**
* An exception indicating that there was a problem during the construction of
* a ConfiguredTargetValue.
*/
public static final class ConfiguredValueCreationException extends Exception {
public ConfiguredValueCreationException(String message) {
super(message);
}
}
/**
* Used to declare all the exception types that can be wrapped in the exception thrown by
* {@link ConfiguredTargetFunction#compute}.
*/
public static final class ConfiguredTargetFunctionException extends SkyFunctionException {
public ConfiguredTargetFunctionException(NoSuchTargetException e) {
super(e, Transience.PERSISTENT);
}
private ConfiguredTargetFunctionException(ConfiguredValueCreationException error) {
super(error, Transience.PERSISTENT);
};
private ConfiguredTargetFunctionException(
@Nullable SkyKey childKey, Exception transitiveError) {
super(transitiveError, childKey);
}
}
}