// Copyright 2018 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 com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.TargetParsingException; import com.google.devtools.build.lib.cmdline.TargetPattern; import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet; import com.google.devtools.build.lib.concurrent.MultisetSemaphore; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.ExtendedEventHandler; import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.packages.DependencyFilter; import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.RuleTransitionFactory; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.pkgcache.FilteringPolicies; import com.google.devtools.build.lib.pkgcache.PackageManager; import com.google.devtools.build.lib.pkgcache.PathPackageLocator; import com.google.devtools.build.lib.pkgcache.TargetPatternEvaluator; import com.google.devtools.build.lib.query2.engine.KeyExtractor; import com.google.devtools.build.lib.query2.engine.MinDepthUniquifier; import com.google.devtools.build.lib.query2.engine.QueryEnvironment; 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.UniquifierImpl; import com.google.devtools.build.lib.query2.engine.ThreadSafeOutputFormatterCallback; import com.google.devtools.build.lib.query2.engine.Uniquifier; import com.google.devtools.build.lib.rules.AliasConfiguredTarget; import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; import com.google.devtools.build.lib.skyframe.ConfiguredTargetValue; import com.google.devtools.build.lib.skyframe.GraphBackedRecursivePackageProvider; import com.google.devtools.build.lib.skyframe.PackageValue; import com.google.devtools.build.lib.skyframe.RecursivePackageProviderBackedTargetPatternResolver; import com.google.devtools.build.lib.skyframe.RecursivePkgValueRootPackageExtractor; import com.google.devtools.build.lib.skyframe.SkyFunctions; import com.google.devtools.build.lib.skyframe.SkyframeExecutor; import com.google.devtools.build.lib.skyframe.TargetPatternValue; import com.google.devtools.build.lib.skyframe.TargetPatternValue.TargetPatternKey; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.WalkableGraph; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.Nullable; /** * {@link QueryEnvironment} that runs queries based on results from the analysis phase. * *

This environment can theoretically be used for multiple queries, but currently is only ever * used for one over the course of its lifetime. If this ever changed to be used for multiple, the * {@link TargetAccessor} field should be initialized on a per-query basis not a per-environment * basis. * *

Aspects are also not supported, but probably should be in some fashion. */ public abstract class PostAnalysisQueryEnvironment extends AbstractBlazeQueryEnvironment { protected final BuildConfiguration defaultTargetConfiguration; protected final BuildConfiguration hostConfiguration; private final String parserPrefix; private final PathPackageLocator pkgPath; private final Supplier walkableGraphSupplier; private final TargetAccessor accessor; protected WalkableGraph graph; private static final Function SKYKEY_TO_CTKEY = skyKey -> (ConfiguredTargetKey) skyKey.argument(); private static final ImmutableList ALL_PATTERNS; static { TargetPattern targetPattern; try { targetPattern = TargetPattern.defaultParser().parse("//..."); } catch (TargetParsingException e) { throw new IllegalStateException(e); } ALL_PATTERNS = ImmutableList.of( new TargetPatternKey( targetPattern, FilteringPolicies.NO_FILTER, false, "", ImmutableSet.of())); } protected RecursivePackageProviderBackedTargetPatternResolver resolver; public PostAnalysisQueryEnvironment( boolean keepGoing, ExtendedEventHandler eventHandler, Iterable extraFunctions, BuildConfiguration defaultTargetConfiguration, BuildConfiguration hostConfiguration, String parserPrefix, PathPackageLocator pkgPath, Supplier walkableGraphSupplier, Set settings, TargetAccessor targetAccessor) { super(keepGoing, true, Rule.ALL_LABELS, eventHandler, settings, extraFunctions); this.defaultTargetConfiguration = defaultTargetConfiguration; this.hostConfiguration = hostConfiguration; this.parserPrefix = parserPrefix; this.pkgPath = pkgPath; this.walkableGraphSupplier = walkableGraphSupplier; this.accessor = targetAccessor; } public abstract ImmutableList> getDefaultOutputFormatters( TargetAccessor accessor, Reporter reporter, SkyframeExecutor skyframeExecutor, BuildConfiguration hostConfiguration, @Nullable RuleTransitionFactory trimmingTransitionFactory, PackageManager packageManager); public abstract String getOutputFormat(); protected abstract KeyExtractor getConfiguredTargetKeyExtractor(); @Override public QueryEvalResult evaluateQuery( QueryExpression expr, ThreadSafeOutputFormatterCallback callback) throws QueryException, InterruptedException, IOException { beforeEvaluateQuery(); return super.evaluateQuery(expr, callback); } private void beforeEvaluateQuery() throws QueryException { graph = walkableGraphSupplier.get(); GraphBackedRecursivePackageProvider graphBackedRecursivePackageProvider = new GraphBackedRecursivePackageProvider( graph, ALL_PATTERNS, pkgPath, new RecursivePkgValueRootPackageExtractor()); resolver = new RecursivePackageProviderBackedTargetPatternResolver( graphBackedRecursivePackageProvider, eventHandler, FilteringPolicies.NO_FILTER, MultisetSemaphore.unbounded()); checkSettings(settings); } // Check to make sure the settings requested are currently supported by this class private void checkSettings(Set settings) throws QueryException { if (settings.contains(Setting.NO_NODEP_DEPS) || settings.contains(Setting.TESTS_EXPRESSION_STRICT)) { settings = Sets.difference( settings, ImmutableSet.of(Setting.NO_HOST_DEPS, Setting.NO_IMPLICIT_DEPS)); throw new QueryException( String.format( "The following filter(s) are not currently supported by configured query: %s", settings.toString())); } } public BuildConfiguration getHostConfiguration() { return hostConfiguration; } @Override public TargetAccessor getAccessor() { return accessor; } // TODO(bazel-team): It's weird that this untemplated function exists. Fix? Or don't implement? @Override public Target getTarget(Label label) throws TargetNotFoundException, InterruptedException { try { return ((PackageValue) walkableGraphSupplier.get().getValue(PackageValue.key(label.getPackageIdentifier()))) .getPackage() .getTarget(label.getName()); } catch (NoSuchTargetException e) { throw new TargetNotFoundException(e); } } @Override public T getOrCreate(T target) { return target; } /** * This method has to exist because {@link AliasConfiguredTarget#getLabel()} returns the label of * the "actual" target instead of the alias target. Grr. */ public abstract Label getCorrectLabel(T target); @Nullable protected abstract T getHostConfiguredTarget(Label label) throws InterruptedException; @Nullable protected abstract T getTargetConfiguredTarget(Label label) throws InterruptedException; @Nullable protected abstract T getNullConfiguredTarget(Label label) throws InterruptedException; @Nullable protected ConfiguredTargetValue getConfiguredTargetValue(SkyKey key) throws InterruptedException { return (ConfiguredTargetValue) walkableGraphSupplier.get().getValue(key); } @Nullable protected abstract T getValueFromKey(SkyKey key) throws InterruptedException; protected TargetPattern getPattern(String pattern) throws TargetParsingException { TargetPatternKey targetPatternKey = ((TargetPatternKey) TargetPatternValue.key( pattern, TargetPatternEvaluator.DEFAULT_FILTERING_POLICY, parserPrefix) .argument()); return targetPatternKey.getParsedPattern(); } @Override public ThreadSafeMutableSet getFwdDeps(Iterable targets, QueryExpressionContext context) throws InterruptedException { Map targetsByKey = Maps.newHashMapWithExpectedSize(Iterables.size(targets)); for (T target : targets) { targetsByKey.put(getSkyKey(target), target); } Map> directDeps = targetifyValues(graph.getDirectDeps(targetsByKey.keySet())); if (targetsByKey.size() != directDeps.size()) { Iterable missingTargets = Sets.difference(targetsByKey.keySet(), directDeps.keySet()) .stream() .map(SKYKEY_TO_CTKEY) .collect(Collectors.toList()); eventHandler.handle(Event.warn("Targets were missing from graph: " + missingTargets)); } ThreadSafeMutableSet result = createThreadSafeMutableSet(); for (Map.Entry> entry : directDeps.entrySet()) { result.addAll(filterFwdDeps(targetsByKey.get(entry.getKey()), entry.getValue())); } return result; } private Collection filterFwdDeps(T configTarget, Collection rawFwdDeps) { if (settings.isEmpty()) { return rawFwdDeps; } return getAllowedDeps(configTarget, rawFwdDeps); } @Override public Collection getReverseDeps(Iterable targets, QueryExpressionContext context) throws InterruptedException { Map targetsByKey = Maps.newHashMapWithExpectedSize(Iterables.size(targets)); for (T target : targets) { targetsByKey.put(getSkyKey(target), target); } Map> reverseDepsByKey = targetifyValues(graph.getReverseDeps(targetsByKey.keySet())); if (targetsByKey.size() != reverseDepsByKey.size()) { Iterable missingTargets = Sets.difference(targetsByKey.keySet(), reverseDepsByKey.keySet()) .stream() .map(SKYKEY_TO_CTKEY) .collect(Collectors.toList()); eventHandler.handle(Event.warn("Targets were missing from graph: " + missingTargets)); } Map> reverseDepsByCT = new HashMap<>(); for (Map.Entry> entry : reverseDepsByKey.entrySet()) { reverseDepsByCT.put(targetsByKey.get(entry.getKey()), entry.getValue()); } return reverseDepsByCT.isEmpty() ? Collections.emptyList() : filterReverseDeps(reverseDepsByCT); } private Collection filterReverseDeps(Map> rawReverseDeps) { Set result = CompactHashSet.create(); for (Map.Entry> targetAndRdeps : rawReverseDeps.entrySet()) { ImmutableSet.Builder ruleDeps = ImmutableSet.builder(); for (T parent : targetAndRdeps.getValue()) { if (parent instanceof RuleConfiguredTarget && dependencyFilter != DependencyFilter.ALL_DEPS) { ruleDeps.add(parent); } else { result.add(parent); } } result.addAll(getAllowedDeps((targetAndRdeps.getKey()), ruleDeps.build())); } return result; } /** * @param target source target * @param deps next level of deps to filter */ protected Collection getAllowedDeps(T target, Collection deps) { // It's possible to query on a target that's configured in the host configuration. In those // cases if --nohost_deps is turned on, we only allow reachable targets that are ALSO in the // host config. This is somewhat counterintuitive and subject to change in the future but seems // like the best option right now. if (settings.contains(Setting.NO_HOST_DEPS)) { BuildConfiguration currentConfig = getConfiguration(target); if (currentConfig != null && currentConfig.isHostConfiguration()) { deps = deps.stream() .filter( dep -> getConfiguration(dep) != null && getConfiguration(dep).isHostConfiguration()) .collect(Collectors.toList()); } else { deps = deps.stream() .filter( dep -> getConfiguration(dep) != null && !getConfiguration(dep).isHostConfiguration()) .collect(Collectors.toList()); } } if (settings.contains(Setting.NO_IMPLICIT_DEPS)) { RuleConfiguredTarget ruleConfiguredTarget = getRuleConfiguredTarget(target); if (ruleConfiguredTarget != null) { Set implicitDeps = ruleConfiguredTarget.getImplicitDeps(); deps = deps.stream() .filter( dep -> !implicitDeps.contains( ConfiguredTargetKey.of(getCorrectLabel(dep), getConfiguration(dep)))) .collect(Collectors.toList()); } } return deps; } protected abstract RuleConfiguredTarget getRuleConfiguredTarget(T target); protected Map> targetifyValues( Map> input) throws InterruptedException { Map> result = new HashMap<>(); for (Map.Entry> entry : input.entrySet()) { Collection value = new ArrayList<>(); for (SkyKey key : entry.getValue()) { if (key.functionName().equals(SkyFunctions.CONFIGURED_TARGET)) { value.add(getValueFromKey(key)); } } result.put(entry.getKey(), value); } return result; } @Nullable protected abstract BuildConfiguration getConfiguration(T target); protected abstract ConfiguredTargetKey getSkyKey(T target); @Override public ThreadSafeMutableSet getTransitiveClosure( ThreadSafeMutableSet targets, QueryExpressionContext context) throws InterruptedException { return SkyQueryUtils.getTransitiveClosure( targets, targets1 -> getFwdDeps(targets1, context), createThreadSafeMutableSet()); } @Override public void buildTransitiveClosure( QueryExpression caller, ThreadSafeMutableSet targetNodes, int maxDepth) { // TODO(bazel-team): implement this. Just needed for error-checking. } @Override public ImmutableList getNodesOnPath(T from, T to, QueryExpressionContext context) throws InterruptedException { return SkyQueryUtils.getNodesOnPath( from, to, targets -> getFwdDeps(targets, context), getConfiguredTargetKeyExtractor()::extractKey); } @Override public MutableMap createMutableMap() { return new MutableKeyExtractorBackedMapImpl<>(getConfiguredTargetKeyExtractor()); } @Override public Uniquifier createUniquifier() { return new UniquifierImpl<>(getConfiguredTargetKeyExtractor()); } @Override public MinDepthUniquifier createMinDepthUniquifier() { return new MinDepthUniquifierImpl<>( getConfiguredTargetKeyExtractor(), SkyQueryEnvironment.DEFAULT_THREAD_COUNT); } /** Target patterns are resolved on the fly so no pre-work to be done here. */ @Override protected void preloadOrThrow(QueryExpression caller, Collection patterns) {} @Override public ThreadSafeMutableSet getBuildFiles( QueryExpression caller, ThreadSafeMutableSet nodes, boolean buildFiles, boolean loads, QueryExpressionContext context) throws QueryException { throw new QueryException("buildfiles() doesn't make sense for the configured target graph"); } @Override public Collection getSiblingTargetsInPackage(T target) { throw new UnsupportedOperationException("siblings() not supported"); } @Override public void close() {} }