diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java new file mode 100644 index 0000000000..9fc3df4578 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java @@ -0,0 +1,578 @@ +// 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 -> 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); + } + } +} |