| // Copyright 2014 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.query2; |
| |
| import static com.google.common.collect.ImmutableSet.toImmutableSet; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Maps; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.cmdline.ResolvedTargets; |
| import com.google.devtools.build.lib.cmdline.TargetParsingException; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; |
| import com.google.devtools.build.lib.events.ExtendedEventHandler; |
| import com.google.devtools.build.lib.graph.Digraph; |
| import com.google.devtools.build.lib.graph.Node; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.CachingPackageLocator; |
| import com.google.devtools.build.lib.packages.NoSuchThingException; |
| import com.google.devtools.build.lib.packages.OutputFile; |
| 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.TargetEdgeObserver; |
| import com.google.devtools.build.lib.pkgcache.TargetPatternPreloader; |
| import com.google.devtools.build.lib.pkgcache.TargetProvider; |
| import com.google.devtools.build.lib.pkgcache.TransitivePackageLoader; |
| import com.google.devtools.build.lib.query2.engine.Callback; |
| import com.google.devtools.build.lib.query2.engine.DigraphQueryEvalResult; |
| import com.google.devtools.build.lib.query2.engine.MinDepthUniquifier; |
| 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.query2.engine.QueryExpressionContext; |
| import com.google.devtools.build.lib.query2.engine.QueryUtil.MinDepthUniquifierImpl; |
| import com.google.devtools.build.lib.query2.engine.QueryUtil.MutableKeyExtractorBackedMapImpl; |
| import com.google.devtools.build.lib.query2.engine.QueryUtil.ThreadSafeMutableKeyExtractorBackedSetImpl; |
| import com.google.devtools.build.lib.query2.engine.QueryUtil.UniquifierImpl; |
| import com.google.devtools.build.lib.query2.engine.SkyframeRestartQueryException; |
| import com.google.devtools.build.lib.query2.engine.ThreadSafeOutputFormatterCallback; |
| import com.google.devtools.build.lib.query2.engine.Uniquifier; |
| import com.google.devtools.build.lib.skyframe.SkyframeLabelVisitor; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| 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; |
| |
| /** |
| * The environment of a Blaze query. Not thread-safe. |
| */ |
| public class BlazeQueryEnvironment extends AbstractBlazeQueryEnvironment<Target> { |
| private static final int MAX_DEPTH_FULL_SCAN_LIMIT = 20; |
| private final Map<String, Set<Target>> resolvedTargetPatterns = new HashMap<>(); |
| private final TargetPatternPreloader targetPatternPreloader; |
| private final PathFragment relativeWorkingDirectory; |
| private final TransitivePackageLoader transitivePackageLoader; |
| private final TargetProvider targetProvider; |
| private final CachingPackageLocator cachingPackageLocator; |
| private final Digraph<Target> graph = new Digraph<>(); |
| private final ErrorPrintingTargetEdgeErrorObserver errorObserver; |
| private final LabelVisitor labelVisitor; |
| protected final int loadingPhaseThreads; |
| |
| private final BlazeTargetAccessor accessor = new BlazeTargetAccessor(this); |
| |
| /** |
| * Note that the correct operation of this class critically depends on the Reporter being a |
| * singleton object, shared by all cooperating classes contributing to Query. |
| * |
| * @param strictScope if true, fail the whole query if a label goes out of scope. |
| * @param loadingPhaseThreads the number of threads to use during loading the packages for the |
| * query. |
| * @param labelFilter a predicate that determines if a specific label is allowed to be visited |
| * during query execution. If it returns false, the query execution is stopped with an error |
| * message. |
| * @param settings a set of enabled settings |
| */ |
| BlazeQueryEnvironment( |
| TransitivePackageLoader transitivePackageLoader, |
| TargetProvider targetProvider, |
| CachingPackageLocator cachingPackageLocator, |
| TargetPatternPreloader targetPatternPreloader, |
| PathFragment relativeWorkingDirectory, |
| boolean keepGoing, |
| boolean strictScope, |
| int loadingPhaseThreads, |
| Predicate<Label> labelFilter, |
| ExtendedEventHandler eventHandler, |
| Set<Setting> settings, |
| Iterable<QueryFunction> extraFunctions) { |
| super(keepGoing, strictScope, labelFilter, eventHandler, settings, extraFunctions); |
| this.targetPatternPreloader = targetPatternPreloader; |
| this.relativeWorkingDirectory = relativeWorkingDirectory; |
| this.transitivePackageLoader = transitivePackageLoader; |
| this.targetProvider = targetProvider; |
| this.cachingPackageLocator = cachingPackageLocator; |
| this.errorObserver = new ErrorPrintingTargetEdgeErrorObserver(this.eventHandler); |
| this.loadingPhaseThreads = loadingPhaseThreads; |
| this.labelVisitor = new LabelVisitor(targetProvider, dependencyFilter); |
| } |
| |
| @Override |
| public void close() { |
| // BlazeQueryEnvironment has no resources that need to be cleaned up. |
| } |
| |
| @Override |
| public DigraphQueryEvalResult<Target> evaluateQuery( |
| QueryExpression expr, |
| ThreadSafeOutputFormatterCallback<Target> callback) |
| throws QueryException, InterruptedException, IOException { |
| resolvedTargetPatterns.clear(); |
| QueryEvalResult queryEvalResult = super.evaluateQuery(expr, callback); |
| return new DigraphQueryEvalResult<>( |
| queryEvalResult.getSuccess(), queryEvalResult.isEmpty(), graph); |
| } |
| |
| @Override |
| public Collection<Target> getSiblingTargetsInPackage(Target target) { |
| Collection<Target> siblings = target.getPackage().getTargets().values(); |
| // Ensure that the sibling targets are in the graph being built-up. |
| siblings.forEach(this::getNode); |
| return siblings; |
| } |
| |
| @Override |
| public QueryTaskFuture<Void> getTargetsMatchingPattern( |
| QueryExpression owner, String pattern, Callback<Target> callback) { |
| try { |
| getTargetsMatchingPatternImpl(pattern, callback); |
| return immediateSuccessfulFuture(null); |
| } catch (QueryException e) { |
| return immediateFailedFuture(e); |
| } catch (InterruptedException e) { |
| return immediateCancelledFuture(); |
| } |
| } |
| |
| private void getTargetsMatchingPatternImpl(String pattern, Callback<Target> callback) |
| throws QueryException, InterruptedException { |
| // We can safely ignore the boolean error flag. The evaluateQuery() method above wraps the |
| // entire query computation in an error sensor. |
| |
| 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(); |
| } |
| } |
| |
| Set<PathFragment> packages = new HashSet<>(); |
| for (Target target : targets) { |
| packages.add(target.getLabel().getPackageFragment()); |
| } |
| |
| Set<Target> result = new LinkedHashSet<>(); |
| for (Target target : targets) { |
| result.add(getOrCreate(target)); |
| |
| // Preservation of graph order: it is important that targets obtained via |
| // a wildcard such as p:* are correctly ordered w.r.t. each other, so to |
| // ensure this, we add edges between any pair of directly connected |
| // targets in this set. |
| if (target instanceof OutputFile) { |
| OutputFile outputFile = (OutputFile) target; |
| if (targets.contains(outputFile.getGeneratingRule())) { |
| makeEdge(outputFile, outputFile.getGeneratingRule()); |
| } |
| } else if (target instanceof Rule) { |
| Rule rule = (Rule) target; |
| for (Label label : rule.getLabels(dependencyFilter)) { |
| if (!packages.contains(label.getPackageFragment())) { |
| continue; // don't cause additional package loading |
| } |
| try { |
| if (!validateScope(label, strictScope)) { |
| continue; // Don't create edges to targets which are out of scope. |
| } |
| Target to = getTargetOrThrow(label); |
| if (targets.contains(to)) { |
| makeEdge(rule, to); |
| } |
| } catch (NoSuchThingException e) { |
| /* ignore */ |
| } |
| } |
| } |
| } |
| callback.process(result); |
| } |
| |
| @Override |
| public Target getTarget(Label label) |
| throws TargetNotFoundException, QueryException, InterruptedException { |
| // Can't use strictScope here because we are expecting a target back. |
| validateScope(label, true); |
| try { |
| return getNode(getTargetOrThrow(label)).getLabel(); |
| } catch (NoSuchThingException e) { |
| throw new TargetNotFoundException(e); |
| } |
| } |
| |
| private Node<Target> getNode(Target target) { |
| return graph.createNode(target); |
| } |
| |
| private Collection<Node<Target>> getNodes(Iterable<Target> target) { |
| Set<Node<Target>> result = new LinkedHashSet<>(); |
| for (Target t : target) { |
| result.add(getNode(t)); |
| } |
| return result; |
| } |
| |
| @Override |
| public Target getOrCreate(Target target) { |
| return getNode(target).getLabel(); |
| } |
| |
| @Override |
| public Collection<Target> getFwdDeps( |
| Iterable<Target> targets, QueryExpressionContext<Target> context) { |
| ThreadSafeMutableSet<Target> result = createThreadSafeMutableSet(); |
| for (Target target : targets) { |
| result.addAll(getTargetsFromNodes(getNode(target).getSuccessors())); |
| } |
| return result; |
| } |
| |
| @Override |
| public Collection<Target> getReverseDeps( |
| Iterable<Target> targets, QueryExpressionContext<Target> context) { |
| ThreadSafeMutableSet<Target> result = createThreadSafeMutableSet(); |
| for (Target target : targets) { |
| result.addAll(getTargetsFromNodes(getNode(target).getPredecessors())); |
| } |
| return result; |
| } |
| |
| @Override |
| public ThreadSafeMutableSet<Target> getTransitiveClosure( |
| ThreadSafeMutableSet<Target> targetNodes, QueryExpressionContext<Target> context) { |
| for (Target node : targetNodes) { |
| checkBuilt(node); |
| } |
| return getTargetsFromNodes(graph.getFwdReachable(getNodes(targetNodes))); |
| } |
| |
| /** |
| * Checks that the graph rooted at 'targetNode' has been completely built; |
| * fails if not. Callers of {@link #getTransitiveClosure} must ensure that |
| * {@link #buildTransitiveClosure} has been called before. |
| * |
| * <p>It would be inefficient and failure-prone to make getTransitiveClosure |
| * call buildTransitiveClosure directly. Also, it would cause |
| * nondeterministic behavior of the operators, since the set of packages |
| * loaded (and hence errors reported) would depend on the ordering details of |
| * the query operators' implementations. |
| */ |
| private void checkBuilt(Target targetNode) { |
| Preconditions.checkState( |
| labelVisitor.hasVisited(targetNode.getLabel()), |
| "getTransitiveClosure(%s) called without prior call to buildTransitiveClosure()", |
| targetNode); |
| } |
| |
| @Override |
| public void buildTransitiveClosure(QueryExpression caller, |
| ThreadSafeMutableSet<Target> targetNodes, |
| int maxDepth) throws QueryException, InterruptedException { |
| preloadTransitiveClosure(targetNodes, maxDepth); |
| labelVisitor.syncWithVisitor(eventHandler, targetNodes, keepGoing, |
| loadingPhaseThreads, maxDepth, errorObserver, new GraphBuildingObserver()); |
| |
| if (errorObserver.hasErrors()) { |
| reportBuildFileError(caller, "errors were encountered while computing transitive closure"); |
| } |
| } |
| |
| @Override |
| public Iterable<Target> getNodesOnPath( |
| Target from, Target to, QueryExpressionContext<Target> context) { |
| ImmutableList.Builder<Target> builder = ImmutableList.builder(); |
| for (Node<Target> node : graph.getShortestPath(getNode(from), getNode(to))) { |
| builder.add(node.getLabel()); |
| } |
| return builder.build(); |
| } |
| |
| @ThreadSafe |
| @Override |
| public ThreadSafeMutableSet<Target> createThreadSafeMutableSet() { |
| return new ThreadSafeMutableKeyExtractorBackedSetImpl<>( |
| TargetKeyExtractor.INSTANCE, Target.class); |
| } |
| |
| @Override |
| public <V> MutableMap<Target, V> createMutableMap() { |
| return new MutableKeyExtractorBackedMapImpl<>(TargetKeyExtractor.INSTANCE); |
| } |
| |
| @Override |
| public Uniquifier<Target> createUniquifier() { |
| return new UniquifierImpl<>(TargetKeyExtractor.INSTANCE); |
| } |
| |
| @Override |
| public MinDepthUniquifier<Target> createMinDepthUniquifier() { |
| return new MinDepthUniquifierImpl<>(TargetKeyExtractor.INSTANCE, /*concurrencyLevel=*/ 1); |
| } |
| |
| private void preloadTransitiveClosure(ThreadSafeMutableSet<Target> targets, int maxDepth) |
| throws InterruptedException { |
| if (maxDepth >= MAX_DEPTH_FULL_SCAN_LIMIT && transitivePackageLoader != null) { |
| // Only do the full visitation if "maxDepth" is large enough. Otherwise, the benefits of |
| // preloading will be outweighed by the cost of doing more work than necessary. |
| Set<Label> labels = targets.stream().map(Target::getLabel).collect(toImmutableSet()); |
| ((SkyframeLabelVisitor) transitivePackageLoader) |
| .sync(eventHandler, labels, keepGoing, loadingPhaseThreads, false); |
| } |
| } |
| |
| /** |
| * It suffices to synchronize the modifications of this.graph from within the |
| * GraphBuildingObserver, because that's the only concurrent part. |
| * Concurrency is always encapsulated within the evaluation of a single query |
| * operator (e.g. deps(), somepath(), etc). |
| */ |
| private class GraphBuildingObserver implements TargetEdgeObserver { |
| |
| @Override |
| public void edge(Target from, Attribute attribute, Target to) { |
| Preconditions.checkState(attribute == null || |
| dependencyFilter.apply(((Rule) from), attribute), |
| "Disallowed edge from LabelVisitor: %s --> %s", from, to); |
| makeEdge(from, to); |
| } |
| |
| @Override |
| public void node(Target node) { |
| graph.createNode(node); |
| } |
| |
| @Override |
| public void missingEdge(Target target, Label to, NoSuchThingException e) { |
| // No - op. |
| } |
| } |
| |
| private void makeEdge(Target from, Target to) { |
| graph.addEdge(from, to); |
| } |
| |
| private Target getTargetOrThrow(Label label) |
| throws NoSuchThingException, SkyframeRestartQueryException, InterruptedException { |
| Target target = targetProvider.getTarget(eventHandler, label); |
| if (target == null) { |
| throw new SkyframeRestartQueryException(); |
| } |
| return target; |
| } |
| |
| // TODO(bazel-team): rename this to getDependentFiles when all implementations |
| // of QueryEnvironment is fixed. |
| @Override |
| public ThreadSafeMutableSet<Target> getBuildFiles( |
| final QueryExpression caller, |
| ThreadSafeMutableSet<Target> nodes, |
| boolean buildFiles, |
| boolean loads, |
| QueryExpressionContext<Target> context) |
| throws QueryException { |
| ThreadSafeMutableSet<Target> dependentFiles = createThreadSafeMutableSet(); |
| Set<PackageIdentifier> 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.getPackageIdentifier())) { |
| if (buildFiles) { |
| addIfUniqueLabel(getNode(pkg.getBuildFile()), seenLabels, dependentFiles); |
| } |
| |
| List<Label> extensions = new ArrayList<>(); |
| if (loads) { |
| extensions.addAll(pkg.getSkylarkFileDependencies()); |
| } |
| |
| for (Label extension : extensions) { |
| |
| Node<Target> loadTarget = getLoadTarget(extension, pkg); |
| addIfUniqueLabel(loadTarget, seenLabels, dependentFiles); |
| |
| // Also add the BUILD file of the extension. |
| if (buildFiles) { |
| Path buildFileForLoad = |
| cachingPackageLocator.getBuildFileForPackage( |
| loadTarget.getLabel().getLabel().getPackageIdentifier()); |
| if (buildFileForLoad != null) { |
| Label buildFileLabel = |
| Label.createUnvalidated( |
| loadTarget.getLabel().getLabel().getPackageIdentifier(), |
| buildFileForLoad.getBaseName()); |
| addIfUniqueLabel( |
| getNode(new FakeLoadTarget(buildFileLabel, pkg)), seenLabels, dependentFiles); |
| } |
| } |
| } |
| } |
| } |
| return dependentFiles; |
| } |
| |
| @Override |
| protected void preloadOrThrow(QueryExpression caller, Collection<String> patterns) |
| throws TargetParsingException, InterruptedException { |
| if (!resolvedTargetPatterns.keySet().containsAll(patterns)) { |
| // Note that this may throw a RuntimeException if deps are missing in Skyframe and this is |
| // being called from within a SkyFunction. |
| resolvedTargetPatterns.putAll( |
| Maps.transformValues( |
| targetPatternPreloader.preloadTargetPatterns( |
| eventHandler, relativeWorkingDirectory, patterns, keepGoing), |
| ResolvedTargets::getTargets)); |
| } |
| } |
| |
| private static void addIfUniqueLabel(Node<Target> node, Set<Label> labels, Set<Target> nodes) { |
| if (labels.add(node.getLabel().getLabel())) { |
| nodes.add(node.getLabel()); |
| } |
| } |
| |
| private Node<Target> getLoadTarget(Label label, Package pkg) { |
| return getNode(new FakeLoadTarget(label, pkg)); |
| } |
| |
| @Override |
| public TargetAccessor<Target> getAccessor() { |
| return accessor; |
| } |
| |
| /** Given a set of target nodes, returns the targets. */ |
| private ThreadSafeMutableSet<Target> getTargetsFromNodes(Iterable<Node<Target>> input) { |
| ThreadSafeMutableSet<Target> result = createThreadSafeMutableSet(); |
| for (Node<Target> node : input) { |
| result.add(node.getLabel()); |
| } |
| return result; |
| } |
| } |