| // Copyright 2015 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.query2; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Functions; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ArrayListMultimap; |
| import com.google.common.collect.Collections2; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Maps; |
| 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.cmdline.LabelSyntaxException; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.cmdline.TargetParsingException; |
| import com.google.devtools.build.lib.cmdline.TargetPattern; |
| import com.google.devtools.build.lib.collect.CompactHashSet; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.graph.Digraph; |
| import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException; |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; |
| import com.google.devtools.build.lib.packages.NoSuchThingException; |
| import com.google.devtools.build.lib.packages.Package; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.pkgcache.PathPackageLocator; |
| import com.google.devtools.build.lib.pkgcache.TargetPatternEvaluator; |
| import com.google.devtools.build.lib.profiler.Profiler; |
| import com.google.devtools.build.lib.query2.engine.AllRdepsFunction; |
| import com.google.devtools.build.lib.query2.engine.QueryEvalResult; |
| import com.google.devtools.build.lib.query2.engine.QueryException; |
| import com.google.devtools.build.lib.query2.engine.QueryExpression; |
| import com.google.devtools.build.lib.skyframe.FileValue; |
| import com.google.devtools.build.lib.skyframe.GraphBackedRecursivePackageProvider; |
| import com.google.devtools.build.lib.skyframe.PackageLookupValue; |
| import com.google.devtools.build.lib.skyframe.PackageValue; |
| import com.google.devtools.build.lib.skyframe.PrepareDepsOfPatternsValue; |
| import com.google.devtools.build.lib.skyframe.RecursivePackageProviderBackedTargetPatternResolver; |
| import com.google.devtools.build.lib.skyframe.SkyFunctions; |
| import com.google.devtools.build.lib.skyframe.TargetPatternValue; |
| import com.google.devtools.build.lib.skyframe.TargetPatternValue.TargetPatternKey; |
| import com.google.devtools.build.lib.skyframe.TransitiveTraversalValue; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.RootedPath; |
| import com.google.devtools.build.skyframe.EvaluationResult; |
| import com.google.devtools.build.skyframe.SkyFunctionName; |
| import com.google.devtools.build.skyframe.SkyKey; |
| import com.google.devtools.build.skyframe.SkyValue; |
| import com.google.devtools.build.skyframe.WalkableGraph; |
| import com.google.devtools.build.skyframe.WalkableGraph.WalkableGraphFactory; |
| |
| import java.util.ArrayDeque; |
| import java.util.Collection; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Logger; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * {@link AbstractBlazeQueryEnvironment} that introspects the Skyframe graph to find forward and |
| * reverse edges. Results obtained by calling {@link #evaluateQuery} are not guaranteed to be in |
| * any particular order. As well, this class eagerly loads the full transitive closure of targets, |
| * even if the full closure isn't needed. |
| */ |
| public class SkyQueryEnvironment extends AbstractBlazeQueryEnvironment<Target> { |
| |
| private WalkableGraph graph; |
| |
| private ImmutableList<TargetPatternKey> universeTargetPatternKeys; |
| |
| private final BlazeTargetAccessor accessor = new BlazeTargetAccessor(this); |
| private final int loadingPhaseThreads; |
| private final WalkableGraphFactory graphFactory; |
| private final List<String> universeScope; |
| private final String parserPrefix; |
| private final PathPackageLocator pkgPath; |
| |
| private static final Logger LOG = Logger.getLogger(SkyQueryEnvironment.class.getName()); |
| |
| private static final Function<Target, Label> TARGET_LABEL_FUNCTION = |
| new Function<Target, Label>() { |
| |
| @Override |
| public Label apply(Target target) { |
| return target.getLabel(); |
| } |
| }; |
| |
| public SkyQueryEnvironment(boolean keepGoing, boolean strictScope, int loadingPhaseThreads, |
| Predicate<Label> labelFilter, |
| EventHandler eventHandler, |
| Set<Setting> settings, |
| Iterable<QueryFunction> extraFunctions, String parserPrefix, |
| WalkableGraphFactory graphFactory, |
| List<String> universeScope, PathPackageLocator pkgPath) { |
| super(keepGoing, strictScope, labelFilter, |
| eventHandler, |
| settings, |
| extraFunctions); |
| this.loadingPhaseThreads = loadingPhaseThreads; |
| this.graphFactory = graphFactory; |
| this.pkgPath = pkgPath; |
| this.universeScope = Preconditions.checkNotNull(universeScope); |
| this.parserPrefix = parserPrefix; |
| Preconditions.checkState(!universeScope.isEmpty(), |
| "No queries can be performed with an empty universe"); |
| } |
| |
| private void init() throws InterruptedException { |
| long startTime = Profiler.nanoTimeMaybe(); |
| EvaluationResult<SkyValue> result = |
| graphFactory.prepareAndGet(universeScope, loadingPhaseThreads, eventHandler); |
| graph = result.getWalkableGraph(); |
| long duration = Profiler.nanoTimeMaybe() - startTime; |
| if (duration > 0) { |
| LOG.info("Spent " + (duration / 1000 / 1000) + " ms on evaluation and walkable graph"); |
| } |
| |
| // The prepareAndGet call above evaluates a single PrepareDepsOfPatterns SkyKey. |
| // We expect to see either a single successfully evaluated value or a cycle in the result. |
| Collection<SkyValue> values = result.values(); |
| if (!values.isEmpty()) { |
| Preconditions.checkState(values.size() == 1, "Universe query \"%s\" returned multiple" |
| + " values unexpectedly (%s values in result)", universeScope, values.size()); |
| PrepareDepsOfPatternsValue prepareDepsOfPatternsValue = |
| (PrepareDepsOfPatternsValue) Iterables.getOnlyElement(values); |
| universeTargetPatternKeys = prepareDepsOfPatternsValue.getTargetPatternKeys(); |
| } else { |
| // No values in the result, so there must be an error. We expect the error to be a cycle. |
| boolean foundCycle = !Iterables.isEmpty(result.getError().getCycleInfo()); |
| Preconditions.checkState(foundCycle, "Universe query \"%s\" failed with non-cycle error: %s", |
| universeScope, result.getError()); |
| universeTargetPatternKeys = ImmutableList.of(); |
| } |
| } |
| |
| @Override |
| public QueryEvalResult<Target> evaluateQuery(QueryExpression expr) |
| throws QueryException, InterruptedException { |
| // Some errors are reported as QueryExceptions and others as ERROR events (if --keep_going). The |
| // result is set to have an error iff there were errors emitted during the query, so we reset |
| // errors here. |
| eventHandler.resetErrors(); |
| init(); |
| return super.evaluateQuery(expr); |
| } |
| |
| private Map<Target, Collection<Target>> makeTargetsMap(Map<SkyKey, Iterable<SkyKey>> input) { |
| ImmutableMap.Builder<Target, Collection<Target>> result = ImmutableMap.builder(); |
| |
| Map<SkyKey, Target> allTargets = makeTargetsWithAssociations( |
| Sets.newHashSet(Iterables.concat(input.values()))); |
| |
| for (Map.Entry<SkyKey, Target> entry : makeTargetsWithAssociations(input.keySet()).entrySet()) { |
| Iterable<SkyKey> skyKeys = input.get(entry.getKey()); |
| Set<Target> targets = CompactHashSet.createWithExpectedSize(Iterables.size(skyKeys)); |
| for (SkyKey key : skyKeys) { |
| Target target = allTargets.get(key); |
| if (target != null) { |
| targets.add(target); |
| } |
| } |
| result.put(entry.getValue(), targets); |
| } |
| return result.build(); |
| } |
| |
| private Map<Target, Collection<Target>> getRawFwdDeps(Iterable<Target> targets) { |
| return makeTargetsMap(graph.getDirectDeps(makeKeys(targets))); |
| } |
| |
| private Map<Target, Collection<Target>> getRawReverseDeps(Iterable<Target> targets) { |
| return makeTargetsMap(graph.getReverseDeps(makeKeys(targets))); |
| } |
| |
| private Set<Label> getAllowedDeps(Rule rule) { |
| Set<Label> allowedLabels = new HashSet<>(rule.getTransitions(dependencyFilter).values()); |
| allowedLabels.addAll(rule.getVisibility().getDependencyLabels()); |
| // We should add deps from aspects, otherwise they are going to be filtered out. |
| allowedLabels.addAll(rule.getAspectLabelsSuperset(dependencyFilter)); |
| return allowedLabels; |
| } |
| |
| private Collection<Target> filterFwdDeps(Target target, Collection<Target> rawFwdDeps) { |
| if (!(target instanceof Rule)) { |
| return rawFwdDeps; |
| } |
| final Set<Label> allowedLabels = getAllowedDeps((Rule) target); |
| return Collections2.filter(rawFwdDeps, |
| new Predicate<Target>() { |
| @Override |
| public boolean apply(Target target) { |
| return allowedLabels.contains(target.getLabel()); |
| } |
| }); |
| } |
| |
| @Override |
| public Collection<Target> getFwdDeps(Iterable<Target> targets) { |
| Set<Target> result = new HashSet<>(); |
| for (Map.Entry<Target, Collection<Target>> entry : getRawFwdDeps(targets).entrySet()) { |
| result.addAll(filterFwdDeps(entry.getKey(), entry.getValue())); |
| } |
| return result; |
| } |
| |
| @Override |
| public Collection<Target> getReverseDeps(Iterable<Target> targets) { |
| Set<Target> result = CompactHashSet.create(); |
| Map<Target, Collection<Target>> rawReverseDeps = getRawReverseDeps(targets); |
| |
| CompactHashSet<Target> visited = CompactHashSet.create(); |
| |
| Set<Label> keys = CompactHashSet.create(Collections2.transform(rawReverseDeps.keySet(), |
| TARGET_LABEL_FUNCTION)); |
| for (Collection<Target> parentCollection : rawReverseDeps.values()) { |
| for (Target parent : parentCollection) { |
| if (visited.add(parent)) { |
| if (parent instanceof Rule) { |
| for (Label label : getAllowedDeps((Rule) parent)) { |
| if (keys.contains(label)) { |
| result.add(parent); |
| } |
| } |
| } else { |
| result.add(parent); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public Set<Target> getTransitiveClosure(Set<Target> targets) { |
| Set<Target> visited = new HashSet<>(); |
| Collection<Target> current = targets; |
| while (!current.isEmpty()) { |
| Collection<Target> toVisit = Collections2.filter(current, |
| Predicates.not(Predicates.in(visited))); |
| current = getFwdDeps(toVisit); |
| visited.addAll(toVisit); |
| } |
| return ImmutableSet.copyOf(visited); |
| } |
| |
| // Implemented with a breadth-first search. |
| @Override |
| public Set<Target> getNodesOnPath(Target from, Target to) { |
| // Tree of nodes visited so far. |
| Map<Target, Target> nodeToParent = new HashMap<>(); |
| // Contains all nodes left to visit in a (LIFO) stack. |
| Deque<Target> toVisit = new ArrayDeque<>(); |
| toVisit.add(from); |
| nodeToParent.put(from, null); |
| while (!toVisit.isEmpty()) { |
| Target current = toVisit.removeFirst(); |
| if (to.equals(current)) { |
| return ImmutableSet.copyOf(Digraph.getPathToTreeNode(nodeToParent, to)); |
| } |
| for (Target dep : getFwdDeps(ImmutableList.of(current))) { |
| if (!nodeToParent.containsKey(dep)) { |
| nodeToParent.put(dep, current); |
| toVisit.addFirst(dep); |
| } |
| } |
| } |
| // Note that the only current caller of this method checks first to see if there is a path |
| // before calling this method. It is not clear what the return value should be here. |
| return null; |
| } |
| |
| @Override |
| public Set<Target> getTargetsMatchingPattern(QueryExpression owner, String pattern) |
| throws QueryException { |
| Set<Target> targets = new LinkedHashSet<>(resolvedTargetPatterns.get(pattern)); |
| |
| // Sets.filter would be more convenient here, but can't deal with exceptions. |
| Iterator<Target> targetIterator = targets.iterator(); |
| while (targetIterator.hasNext()) { |
| Target target = targetIterator.next(); |
| if (!validateScope(target.getLabel(), strictScope)) { |
| targetIterator.remove(); |
| } |
| } |
| return targets; |
| } |
| |
| @Override |
| public Set<Target> getBuildFiles(QueryExpression caller, Set<Target> nodes) |
| throws QueryException { |
| Set<Target> dependentFiles = new LinkedHashSet<>(); |
| Set<Package> seenPackages = new HashSet<>(); |
| // Keep track of seen labels, to avoid adding a fake subinclude label that also exists as a |
| // real target. |
| Set<Label> seenLabels = new HashSet<>(); |
| |
| // Adds all the package definition files (BUILD files and build |
| // extensions) for package "pkg", to "buildfiles". |
| for (Target x : nodes) { |
| Package pkg = x.getPackage(); |
| if (seenPackages.add(pkg)) { |
| addIfUniqueLabel(pkg.getBuildFile(), seenLabels, dependentFiles); |
| for (Label subinclude |
| : Iterables.concat(pkg.getSubincludeLabels(), pkg.getSkylarkFileDependencies())) { |
| addIfUniqueLabel(getSubincludeTarget(subinclude, pkg), seenLabels, dependentFiles); |
| |
| // Also add the BUILD file of the subinclude. |
| try { |
| addIfUniqueLabel(getSubincludeTarget( |
| subinclude.getLocalTargetLabel("BUILD"), pkg), seenLabels, dependentFiles); |
| } catch (LabelSyntaxException e) { |
| throw new AssertionError("BUILD should always parse as a target name", e); |
| } |
| } |
| } |
| } |
| return dependentFiles; |
| } |
| |
| private static void addIfUniqueLabel(Target node, Set<Label> labels, Set<Target> nodes) { |
| if (labels.add(node.getLabel())) { |
| nodes.add(node); |
| } |
| } |
| |
| private static Target getSubincludeTarget(final Label label, Package pkg) { |
| return new FakeSubincludeTarget(label, pkg); |
| } |
| |
| @Override |
| public TargetAccessor<Target> getAccessor() { |
| return accessor; |
| } |
| |
| private SkyKey getPackageKeyAndValidateLabel(Label label) throws QueryException { |
| // Can't use strictScope here because we are expecting a target back. |
| validateScope(label, true); |
| return PackageValue.key(label.getPackageIdentifier()); |
| } |
| |
| @Override |
| public Target getTarget(Label label) throws TargetNotFoundException, QueryException { |
| SkyKey packageKey = getPackageKeyAndValidateLabel(label); |
| if (!graph.exists(packageKey)) { |
| throw new QueryException(packageKey + " does not exist in graph"); |
| } |
| try { |
| PackageValue packageValue = (PackageValue) graph.getValue(packageKey); |
| if (packageValue != null) { |
| Package pkg = packageValue.getPackage(); |
| if (pkg.containsErrors()) { |
| throw new BuildFileContainsErrorsException(label.getPackageIdentifier()); |
| } |
| return packageValue.getPackage().getTarget(label.getName()); |
| } else { |
| throw (NoSuchThingException) Preconditions.checkNotNull( |
| graph.getException(packageKey), label); |
| } |
| } catch (NoSuchThingException e) { |
| throw new TargetNotFoundException(e); |
| } |
| } |
| |
| @Override |
| public void buildTransitiveClosure(QueryExpression caller, Set<Target> targets, int maxDepth) |
| throws QueryException { |
| // Everything has already been loaded, so here we just check for errors so that we can |
| // pre-emptively throw/report if needed. |
| for (Map.Entry<SkyKey, Exception> entry : |
| graph.getMissingAndExceptions(makeKeys(targets)).entrySet()) { |
| if (entry.getValue() == null) { |
| throw new QueryException(entry.getKey().argument() + " does not exist in graph"); |
| } |
| reportBuildFileError(caller, entry.getValue().getMessage()); |
| } |
| } |
| |
| private Set<Target> filterTargetsNotInGraph(Set<Target> targets) { |
| Map<Target, SkyKey> map = Maps.toMap(targets, TARGET_TO_SKY_KEY); |
| Set<SkyKey> present = graph.getSuccessfulValues(map.values()).keySet(); |
| if (present.size() == targets.size()) { |
| // Optimize for case of all targets being in graph. |
| return targets; |
| } |
| return Maps.filterValues(map, Predicates.in(present)).keySet(); |
| } |
| |
| @Override |
| protected Map<String, Set<Target>> preloadOrThrow( |
| QueryExpression caller, Collection<String> patterns) |
| throws QueryException, TargetParsingException { |
| GraphBackedRecursivePackageProvider provider = |
| new GraphBackedRecursivePackageProvider(graph, universeTargetPatternKeys); |
| Map<String, Set<Target>> result = Maps.newHashMapWithExpectedSize(patterns.size()); |
| for (String pattern : patterns) { |
| SkyKey patternKey = TargetPatternValue.key(pattern, |
| TargetPatternEvaluator.DEFAULT_FILTERING_POLICY, parserPrefix); |
| |
| TargetPatternValue.TargetPatternKey targetPatternKey = |
| ((TargetPatternValue.TargetPatternKey) patternKey.argument()); |
| |
| TargetParsingException targetParsingException = null; |
| if (graph.exists(patternKey)) { |
| // The graph already contains a value or exception for this target pattern, so we use it. |
| TargetPatternValue value = (TargetPatternValue) graph.getValue(patternKey); |
| if (value != null) { |
| result.put( |
| pattern, ImmutableSet.copyOf(makeTargetsFromLabels(value.getTargets().getTargets()))); |
| } else { |
| // Because the graph was always initialized via a keep_going build, we know that the |
| // exception stored here must be a TargetParsingException. Thus the comment in |
| // SkyframeTargetPatternEvaluator#parseTargetPatternKeys describing the situation in which |
| // the exception acceptance must be looser does not apply here. |
| targetParsingException = |
| (TargetParsingException) |
| Preconditions.checkNotNull(graph.getException(patternKey), pattern); |
| } |
| } else { |
| // If the graph doesn't contain a value for this target pattern, try to directly evaluate |
| // it, by making use of packages already present in the graph. |
| RecursivePackageProviderBackedTargetPatternResolver resolver = |
| new RecursivePackageProviderBackedTargetPatternResolver(provider, eventHandler, |
| targetPatternKey.getPolicy(), pkgPath); |
| TargetPattern parsedPattern = targetPatternKey.getParsedPattern(); |
| try { |
| result.put(pattern, filterTargetsNotInGraph(parsedPattern.eval(resolver).getTargets())); |
| } catch (TargetParsingException e) { |
| targetParsingException = e; |
| } catch (InterruptedException e) { |
| throw new QueryException(e.getMessage()); |
| } |
| } |
| |
| if (targetParsingException != null) { |
| if (!keepGoing) { |
| throw targetParsingException; |
| } else { |
| eventHandler.handle(Event.error("Evaluation of query \"" + caller + "\" failed: " |
| + targetParsingException.getMessage())); |
| result.put(pattern, ImmutableSet.<Target>of()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private static final Function<SkyKey, Label> SKYKEY_TO_LABEL = new Function<SkyKey, Label>() { |
| @Nullable |
| @Override |
| public Label apply(SkyKey skyKey) { |
| SkyFunctionName functionName = skyKey.functionName(); |
| if (!functionName.equals(SkyFunctions.TRANSITIVE_TRAVERSAL)) { |
| // Skip non-targets. |
| return null; |
| } |
| return (Label) skyKey.argument(); |
| } |
| }; |
| |
| private Map<SkyKey, Target> makeTargetsWithAssociations(Iterable<SkyKey> keys) { |
| return makeTargetsWithAssociations(keys, SKYKEY_TO_LABEL); |
| } |
| |
| private Collection<Target> makeTargetsFromLabels(Iterable<Label> labels) { |
| return makeTargetsWithAssociations(labels, Functions.<Label>identity()).values(); |
| } |
| |
| private <E> Map<E, Target> makeTargetsWithAssociations(Iterable<E> keys, |
| Function<E, Label> toLabel) { |
| Multimap<SkyKey, E> packageKeyToTargetKeyMap = ArrayListMultimap.create(); |
| for (E key : keys) { |
| Label label = toLabel.apply(key); |
| if (label == null) { |
| continue; |
| } |
| try { |
| packageKeyToTargetKeyMap.put(getPackageKeyAndValidateLabel(label), key); |
| } catch (QueryException e) { |
| // Skip disallowed labels. |
| } |
| } |
| ImmutableMap.Builder<E, Target> result = ImmutableMap.builder(); |
| Map<SkyKey, SkyValue> packageMap = graph.getSuccessfulValues(packageKeyToTargetKeyMap.keySet()); |
| for (Map.Entry<SkyKey, SkyValue> entry : packageMap.entrySet()) { |
| for (E targetKey : packageKeyToTargetKeyMap.get(entry.getKey())) { |
| try { |
| result.put(targetKey, ((PackageValue) entry.getValue()).getPackage() |
| .getTarget((toLabel.apply(targetKey)).getName())); |
| } catch (NoSuchTargetException e) { |
| // Skip missing target. |
| } |
| } |
| } |
| return result.build(); |
| } |
| |
| private static final Function<Target, SkyKey> TARGET_TO_SKY_KEY = |
| new Function<Target, SkyKey>() { |
| @Override |
| public SkyKey apply(Target target) { |
| return TransitiveTraversalValue.key(target.getLabel()); |
| } |
| }; |
| |
| private static Iterable<SkyKey> makeKeys(Iterable<Target> targets) { |
| return Iterables.transform(targets, TARGET_TO_SKY_KEY); |
| } |
| |
| @Override |
| public Target getOrCreate(Target target) { |
| return target; |
| } |
| |
| /** |
| * Get SkyKeys for the FileValues for the given {@code pathFragments}. To do this, we look for a |
| * package lookup node for each path fragment, since package lookup nodes contain the "root" of a |
| * package. The returned SkyKeys correspond to FileValues that may not exist in the graph. |
| */ |
| private Collection<SkyKey> getSkyKeysForFileFragments(Iterable<PathFragment> pathFragments) { |
| Set<SkyKey> result = new HashSet<>(); |
| Multimap<PathFragment, PathFragment> currentToOriginal = ArrayListMultimap.create(); |
| for (PathFragment pathFragment : pathFragments) { |
| currentToOriginal.put(pathFragment, pathFragment); |
| } |
| while (!currentToOriginal.isEmpty()) { |
| Map<SkyKey, PathFragment> keys = new HashMap<>(); |
| for (PathFragment pathFragment : currentToOriginal.keySet()) { |
| keys.put( |
| PackageLookupValue.key(PackageIdentifier.createInDefaultRepo(pathFragment)), |
| pathFragment); |
| } |
| Map<SkyKey, SkyValue> lookupValues = graph.getSuccessfulValues(keys.keySet()); |
| for (Map.Entry<SkyKey, SkyValue> entry : lookupValues.entrySet()) { |
| PackageLookupValue packageLookupValue = (PackageLookupValue) entry.getValue(); |
| if (packageLookupValue.packageExists()) { |
| PathFragment dir = keys.get(entry.getKey()); |
| Collection<PathFragment> originalFiles = currentToOriginal.get(dir); |
| Preconditions.checkState(!originalFiles.isEmpty(), entry); |
| for (PathFragment fileName : originalFiles) { |
| result.add( |
| FileValue.key(RootedPath.toRootedPath(packageLookupValue.getRoot(), fileName))); |
| } |
| currentToOriginal.removeAll(dir); |
| } |
| } |
| Multimap<PathFragment, PathFragment> newCurrentToOriginal = ArrayListMultimap.create(); |
| for (PathFragment pathFragment : currentToOriginal.keySet()) { |
| PathFragment parent = pathFragment.getParentDirectory(); |
| if (parent != null) { |
| newCurrentToOriginal.putAll(parent, currentToOriginal.get(pathFragment)); |
| } |
| } |
| currentToOriginal = newCurrentToOriginal; |
| } |
| return result; |
| } |
| |
| /** |
| * Calculates the set of {@link Package} objects, represented as source file targets, that depend |
| * on the given list of BUILD files and subincludes (other files are filtered out). |
| */ |
| @Nullable |
| Set<Target> getRBuildFiles(Collection<PathFragment> fileIdentifiers) { |
| Collection<SkyKey> files = getSkyKeysForFileFragments(fileIdentifiers); |
| Collection<SkyKey> current = graph.getSuccessfulValues(files).keySet(); |
| Set<SkyKey> resultKeys = CompactHashSet.create(); |
| while (!current.isEmpty()) { |
| Collection<Iterable<SkyKey>> reverseDeps = graph.getReverseDeps(current).values(); |
| current = new HashSet<>(); |
| for (SkyKey rdep : Iterables.concat(reverseDeps)) { |
| if (rdep.functionName().equals(SkyFunctions.PACKAGE)) { |
| resultKeys.add(rdep); |
| } else if (!rdep.functionName().equals(SkyFunctions.PACKAGE_LOOKUP)) { |
| // Packages may depend on subpackages for existence, but we don't report them as rdeps. |
| current.add(rdep); |
| } |
| } |
| } |
| Map<SkyKey, SkyValue> packageValues = graph.getSuccessfulValues(resultKeys); |
| ImmutableSet.Builder<Target> result = ImmutableSet.builder(); |
| for (SkyValue value : packageValues.values()) { |
| Package pkg = ((PackageValue) value).getPackage(); |
| if (!pkg.containsErrors()) { |
| result.add(pkg.getBuildFile()); |
| } |
| } |
| return result.build(); |
| } |
| |
| @Override |
| public Iterable<QueryFunction> getFunctions() { |
| return ImmutableList.<QueryFunction>builder() |
| .addAll(super.getFunctions()) |
| .add(new AllRdepsFunction()) |
| .add(new RBuildFilesFunction()) |
| .build(); |
| } |
| } |