| // 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.skyframe; | |
| import com.google.common.collect.Lists; | |
| import com.google.common.collect.Multimap; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.events.EventHandler; | |
| import com.google.devtools.build.lib.packages.Attribute; | |
| import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException; | |
| import com.google.devtools.build.lib.packages.DependencyFilter; | |
| 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.OutputFile; | |
| import com.google.devtools.build.lib.packages.Package; | |
| import com.google.devtools.build.lib.packages.PackageGroup; | |
| import com.google.devtools.build.lib.packages.Rule; | |
| import com.google.devtools.build.lib.packages.Target; | |
| import com.google.devtools.build.lib.util.Preconditions; | |
| 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 java.util.Collection; | |
| import java.util.HashMap; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Map.Entry; | |
| import java.util.Set; | |
| import javax.annotation.Nullable; | |
| /** | |
| * This class can be extended to define {@link SkyFunction}s that traverse a target and its | |
| * transitive dependencies and return values based on that traversal. | |
| * | |
| * <p>The {@code TProcessedTargets} type parameter represents the result of processing a target and | |
| * its transitive dependencies. | |
| * | |
| * <p>{@code TransitiveBaseTraversalFunction} asks for one to be constructed via {@link | |
| * #processTarget}, and then asks for it to be updated based on the current target's | |
| * attributes' dependencies via {@link #processDeps}, and then asks for it to be updated based | |
| * on the current target' aspects' dependencies via {@link #processDeps}. Finally, it calls | |
| * {@link #computeSkyValue} with the {#code ProcessedTargets} to get the {@link SkyValue} to | |
| * return. | |
| */ | |
| abstract class TransitiveBaseTraversalFunction<TProcessedTargets> implements SkyFunction { | |
| /** | |
| * Returns a {@link SkyKey} corresponding to the traversal of a target specified by {@code label} | |
| * and its transitive dependencies. | |
| * | |
| * <p>Extenders of this class should implement this function to return a key with their | |
| * specialized {@link SkyFunction}'s name. | |
| * | |
| * <p>{@link TransitiveBaseTraversalFunction} calls this for each dependency of a target, and | |
| * then gets their values from the environment. | |
| * | |
| * <p>The key's {@link SkyFunction} may throw at most {@link NoSuchPackageException} and | |
| * {@link NoSuchTargetException}. Other exception types are not handled by {@link | |
| * TransitiveBaseTraversalFunction}. | |
| */ | |
| abstract SkyKey getKey(Label label); | |
| abstract TProcessedTargets processTarget(Label label, TargetAndErrorIfAny targetAndErrorIfAny); | |
| abstract void processDeps( | |
| TProcessedTargets processedTargets, | |
| EventHandler eventHandler, | |
| TargetAndErrorIfAny targetAndErrorIfAny, | |
| Iterable<Entry<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>>> | |
| depEntries) | |
| throws InterruptedException; | |
| /** | |
| * Returns a {@link SkyValue} based on the target and any errors it has, and the values | |
| * accumulated across it and a traversal of its transitive dependencies. | |
| */ | |
| abstract SkyValue computeSkyValue(TargetAndErrorIfAny targetAndErrorIfAny, | |
| TProcessedTargets processedTargets); | |
| /** | |
| * Returns a {@link TargetMarkerValue} corresponding to the {@param targetMarkerKey} or {@code | |
| * null} if the value isn't ready. | |
| */ | |
| @Nullable | |
| abstract TargetMarkerValue getTargetMarkerValue(SkyKey targetMarkerKey, Environment env) | |
| throws NoSuchTargetException, NoSuchPackageException, InterruptedException; | |
| @Override | |
| public SkyValue compute(SkyKey key, Environment env) | |
| throws TransitiveBaseTraversalFunctionException, InterruptedException { | |
| Label label = (Label) key.argument(); | |
| LoadTargetResults loadTargetResults; | |
| try { | |
| loadTargetResults = loadTarget(env, label); | |
| } catch (NoSuchTargetException e) { | |
| throw new TransitiveBaseTraversalFunctionException(e); | |
| } catch (NoSuchPackageException e) { | |
| throw new TransitiveBaseTraversalFunctionException(e); | |
| } | |
| LoadTargetResultsType loadTargetResultsType = loadTargetResults.getType(); | |
| if (loadTargetResultsType.equals(LoadTargetResultsType.VALUES_MISSING)) { | |
| return null; | |
| } | |
| Preconditions.checkState( | |
| loadTargetResultsType.equals(LoadTargetResultsType.TARGET_AND_ERROR_IF_ANY), | |
| loadTargetResultsType); | |
| TargetAndErrorIfAny targetAndErrorIfAny = (TargetAndErrorIfAny) loadTargetResults; | |
| TProcessedTargets processedTargets = processTarget(label, targetAndErrorIfAny); | |
| // Process deps from attributes. | |
| Iterable<SkyKey> labelDepKeys = getLabelDepKeys(targetAndErrorIfAny.getTarget()); | |
| Map<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>> depMap = | |
| env.getValuesOrThrow(labelDepKeys, NoSuchPackageException.class, | |
| NoSuchTargetException.class); | |
| processDeps(processedTargets, env.getListener(), targetAndErrorIfAny, depMap.entrySet()); | |
| if (env.valuesMissing()) { | |
| return null; | |
| } | |
| // Process deps from aspects. | |
| Iterable<SkyKey> labelAspectKeys = | |
| getStrictLabelAspectKeys(targetAndErrorIfAny.getTarget(), depMap, env); | |
| Set<Entry<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>>> | |
| labelAspectEntries = env.getValuesOrThrow(labelAspectKeys, NoSuchPackageException.class, | |
| NoSuchTargetException.class).entrySet(); | |
| processDeps(processedTargets, env.getListener(), targetAndErrorIfAny, labelAspectEntries); | |
| if (env.valuesMissing()) { | |
| return null; | |
| } | |
| return computeSkyValue(targetAndErrorIfAny, processedTargets); | |
| } | |
| @Override | |
| public String extractTag(SkyKey skyKey) { | |
| return Label.print(((Label) skyKey.argument())); | |
| } | |
| /** | |
| * Return an Iterable of SkyKeys corresponding to the Aspect-related dependencies of target. | |
| * | |
| * <p>This method may return a precise set of aspect keys, but may need to request additional | |
| * dependencies from the env to do so. | |
| */ | |
| private Iterable<SkyKey> getStrictLabelAspectKeys( | |
| Target target, | |
| Map<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>> depMap, | |
| Environment env) | |
| throws InterruptedException { | |
| List<SkyKey> depKeys = Lists.newArrayList(); | |
| if (target instanceof Rule) { | |
| Map<Label, ValueOrException2<NoSuchPackageException, NoSuchTargetException>> labelDepMap = | |
| new HashMap<>(depMap.size()); | |
| for (Entry<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>> entry : | |
| depMap.entrySet()) { | |
| labelDepMap.put((Label) entry.getKey().argument(), entry.getValue()); | |
| } | |
| Multimap<Attribute, Label> transitions = | |
| ((Rule) target).getTransitions(DependencyFilter.NO_NODEP_ATTRIBUTES); | |
| for (Entry<Attribute, Label> entry : transitions.entries()) { | |
| ValueOrException2<NoSuchPackageException, NoSuchTargetException> value = | |
| labelDepMap.get(entry.getValue()); | |
| for (Label label : | |
| getAspectLabels((Rule) target, entry.getKey(), entry.getValue(), value, env)) { | |
| depKeys.add(getKey(label)); | |
| } | |
| } | |
| } | |
| return depKeys; | |
| } | |
| /** Get the Aspect-related Label deps for the given edge. */ | |
| protected abstract Collection<Label> getAspectLabels( | |
| Rule fromRule, | |
| Attribute attr, | |
| Label toLabel, | |
| ValueOrException2<NoSuchPackageException, NoSuchTargetException> toVal, | |
| Environment env) | |
| throws InterruptedException; | |
| private Iterable<SkyKey> getLabelDepKeys(Target target) throws InterruptedException { | |
| List<SkyKey> depKeys = Lists.newArrayList(); | |
| for (Label depLabel : getLabelDeps(target)) { | |
| depKeys.add(getKey(depLabel)); | |
| } | |
| return depKeys; | |
| } | |
| // TODO(bazel-team): Unify this logic with that in LabelVisitor, and possibly DependencyResolver. | |
| private static Iterable<Label> getLabelDeps(Target target) throws InterruptedException { | |
| final Set<Label> labels = new HashSet<>(); | |
| if (target instanceof OutputFile) { | |
| Rule rule = ((OutputFile) target).getGeneratingRule(); | |
| labels.add(rule.getLabel()); | |
| visitTargetVisibility(target, labels); | |
| } else if (target instanceof InputFile) { | |
| visitTargetVisibility(target, labels); | |
| } else if (target instanceof Rule) { | |
| visitTargetVisibility(target, labels); | |
| visitRule(target, labels); | |
| } else if (target instanceof PackageGroup) { | |
| visitPackageGroup((PackageGroup) target, labels); | |
| } | |
| return labels; | |
| } | |
| private static void visitRule(Target target, Set<Label> labels) throws InterruptedException { | |
| labels.addAll(((Rule) target).getTransitions(DependencyFilter.NO_NODEP_ATTRIBUTES).values()); | |
| } | |
| private static void visitTargetVisibility(Target target, Set<Label> labels) { | |
| labels.addAll(target.getVisibility().getDependencyLabels()); | |
| } | |
| private static void visitPackageGroup(PackageGroup packageGroup, Set<Label> labels) { | |
| labels.addAll(packageGroup.getIncludes()); | |
| } | |
| enum LoadTargetResultsType { | |
| VALUES_MISSING, | |
| TARGET_AND_ERROR_IF_ANY | |
| } | |
| interface LoadTargetResults { | |
| LoadTargetResultsType getType(); | |
| } | |
| private static class ValuesMissing implements LoadTargetResults { | |
| private static final ValuesMissing INSTANCE = new ValuesMissing(); | |
| private ValuesMissing() {} | |
| @Override | |
| public LoadTargetResultsType getType() { | |
| return LoadTargetResultsType.VALUES_MISSING; | |
| } | |
| } | |
| interface TargetAndErrorIfAny { | |
| boolean isPackageLoadedSuccessfully(); | |
| @Nullable NoSuchTargetException getErrorLoadingTarget(); | |
| Target getTarget(); | |
| } | |
| private static class TargetAndErrorIfAnyImpl implements TargetAndErrorIfAny, LoadTargetResults { | |
| private final boolean packageLoadedSuccessfully; | |
| @Nullable private final NoSuchTargetException errorLoadingTarget; | |
| private final Target target; | |
| private TargetAndErrorIfAnyImpl(boolean packageLoadedSuccessfully, | |
| @Nullable NoSuchTargetException errorLoadingTarget, Target target) { | |
| this.packageLoadedSuccessfully = packageLoadedSuccessfully; | |
| this.errorLoadingTarget = errorLoadingTarget; | |
| this.target = target; | |
| } | |
| @Override | |
| public LoadTargetResultsType getType() { | |
| return LoadTargetResultsType.TARGET_AND_ERROR_IF_ANY; | |
| } | |
| @Override | |
| public boolean isPackageLoadedSuccessfully() { | |
| return packageLoadedSuccessfully; | |
| } | |
| @Override | |
| @Nullable | |
| public NoSuchTargetException getErrorLoadingTarget() { | |
| return errorLoadingTarget; | |
| } | |
| @Override | |
| public Target getTarget() { | |
| return target; | |
| } | |
| } | |
| private LoadTargetResults loadTarget(Environment env, Label label) | |
| throws NoSuchTargetException, NoSuchPackageException, InterruptedException { | |
| SkyKey packageKey = PackageValue.key(label.getPackageIdentifier()); | |
| SkyKey targetKey = TargetMarkerValue.key(label); | |
| boolean packageLoadedSuccessfully; | |
| Target target; | |
| NoSuchTargetException errorLoadingTarget = null; | |
| try { | |
| TargetMarkerValue targetValue = getTargetMarkerValue(targetKey, env); | |
| boolean targetValueMissing = targetValue == null; | |
| Preconditions.checkState(targetValueMissing == env.valuesMissing(), targetKey); | |
| if (targetValueMissing) { | |
| return ValuesMissing.INSTANCE; | |
| } | |
| PackageValue packageValue = (PackageValue) env.getValueOrThrow(packageKey, | |
| NoSuchPackageException.class); | |
| if (packageValue == null) { | |
| return ValuesMissing.INSTANCE; | |
| } | |
| Package pkg = packageValue.getPackage(); | |
| if (pkg.containsErrors()) { | |
| throw new BuildFileContainsErrorsException(label.getPackageIdentifier()); | |
| } | |
| packageLoadedSuccessfully = true; | |
| try { | |
| target = pkg.getTarget(label.getName()); | |
| } catch (NoSuchTargetException unexpected) { | |
| // Not expected since the TargetMarkerFunction would have failed earlier if the Target | |
| // was not present. | |
| throw new IllegalStateException(unexpected); | |
| } | |
| } catch (NoSuchTargetException e) { | |
| if (!e.hasTarget()) { | |
| throw e; | |
| } | |
| // We know that a Target may be extracted, but we need to get it out of the Package | |
| // (which is known to be in error). | |
| PackageValue packageValue = | |
| (PackageValue) Preconditions.checkNotNull(env.getValue(packageKey), label); | |
| Package pkg = packageValue.getPackage(); | |
| try { | |
| target = pkg.getTarget(label.getName()); | |
| } catch (NoSuchTargetException nste) { | |
| throw new IllegalStateException("Expected target to exist", nste); | |
| } | |
| errorLoadingTarget = e; | |
| packageLoadedSuccessfully = false; | |
| } | |
| return new TargetAndErrorIfAnyImpl(packageLoadedSuccessfully, errorLoadingTarget, target); | |
| } | |
| /** | |
| * Used to declare all the exception types that can be wrapped in the exception thrown by | |
| * {@link TransitiveTraversalFunction#compute}. | |
| */ | |
| static class TransitiveBaseTraversalFunctionException extends SkyFunctionException { | |
| /** | |
| * Used to propagate an error from a direct target dependency to the target that depended on | |
| * it. | |
| */ | |
| public TransitiveBaseTraversalFunctionException(NoSuchPackageException e) { | |
| super(e, Transience.PERSISTENT); | |
| } | |
| /** | |
| * In nokeep_going mode, used to propagate an error from a direct target dependency to the | |
| * target that depended on it. | |
| * | |
| * <p>In keep_going mode, used the same way, but only for targets that could not be loaded at | |
| * all (we proceed with transitive loading on targets that contain errors).</p> | |
| */ | |
| public TransitiveBaseTraversalFunctionException(NoSuchTargetException e) { | |
| super(e, Transience.PERSISTENT); | |
| } | |
| } | |
| } |