| // 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.Collections2; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.Sets; |
| 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. |
| Collection<SkyKey> labelDepKeys = |
| Collections2.transform(getLabelDeps(targetAndErrorIfAny.getTarget()), this::getKey); |
| |
| 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; |
| |
| // TODO(bazel-team): Unify this logic with that in LabelVisitor, and possibly DependencyResolver. |
| private static Collection<Label> getLabelDeps(Target target) throws InterruptedException { |
| if (target instanceof OutputFile) { |
| Rule rule = ((OutputFile) target).getGeneratingRule(); |
| List<Label> visibilityLabels = visitTargetVisibility(target); |
| HashSet<Label> result = Sets.newHashSetWithExpectedSize(visibilityLabels.size() + 1); |
| result.add(rule.getLabel()); |
| result.addAll(visibilityLabels); |
| return result; |
| } else if (target instanceof InputFile) { |
| return new HashSet<>(visitTargetVisibility(target)); |
| } else if (target instanceof Rule) { |
| List<Label> visibilityLabels = visitTargetVisibility(target); |
| Collection<Label> ruleLabels = visitRule(target); |
| HashSet<Label> result = |
| Sets.newHashSetWithExpectedSize(visibilityLabels.size() + ruleLabels.size()); |
| result.addAll(visibilityLabels); |
| result.addAll(ruleLabels); |
| return result; |
| } else if (target instanceof PackageGroup) { |
| return new HashSet<>(visitPackageGroup((PackageGroup) target)); |
| } else { |
| return ImmutableSet.of(); |
| } |
| } |
| |
| private static Collection<Label> visitRule(Target target) throws InterruptedException { |
| return ((Rule) target).getTransitions(DependencyFilter.NO_NODEP_ATTRIBUTES).values(); |
| } |
| |
| private static List<Label> visitTargetVisibility(Target target) { |
| return target.getVisibility().getDependencyLabels(); |
| } |
| |
| private static List<Label> visitPackageGroup(PackageGroup packageGroup) { |
| return 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); |
| } |
| } |
| } |