Split common functionality out of ConfiguredTargetQueryEnvironment.
This will allow us to reuse the code for the action graph query in a later CL.
RELNOTES: None
PiperOrigin-RevId: 202500176
diff --git a/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java
new file mode 100644
index 0000000..956abf5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java
@@ -0,0 +1,438 @@
+// 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.ConfiguredTarget;
+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.
+ *
+ * <p>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.
+ *
+ * <p>Aspects are also not supported, but probably should be in some fashion.
+ */
+abstract class PostAnalysisQueryEnvironment<T> extends AbstractBlazeQueryEnvironment<T> {
+ protected final BuildConfiguration defaultTargetConfiguration;
+ protected final BuildConfiguration hostConfiguration;
+ private final String parserPrefix;
+ private final PathPackageLocator pkgPath;
+ private final Supplier<WalkableGraph> walkableGraphSupplier;
+ private final TargetAccessor<T> accessor;
+ protected WalkableGraph graph;
+
+ private static final Function<SkyKey, ConfiguredTargetKey> SKYKEY_TO_CTKEY =
+ skyKey -> (ConfiguredTargetKey) skyKey.argument();
+ private static final ImmutableList<TargetPatternKey> 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<QueryFunction> extraFunctions,
+ BuildConfiguration defaultTargetConfiguration,
+ BuildConfiguration hostConfiguration,
+ String parserPrefix,
+ PathPackageLocator pkgPath,
+ Supplier<WalkableGraph> walkableGraphSupplier,
+ Set<Setting> settings,
+ TargetAccessor<T> 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<CqueryThreadsafeCallback> getDefaultOutputFormatters(
+ TargetAccessor<ConfiguredTarget> accessor,
+ Reporter reporter,
+ SkyframeExecutor skyframeExecutor,
+ BuildConfiguration hostConfiguration,
+ @Nullable RuleTransitionFactory trimmingTransitionFactory,
+ PackageManager packageManager);
+
+ protected abstract KeyExtractor<T, ConfiguredTargetKey> getConfiguredTargetKeyExtractor();
+
+ @Override
+ public QueryEvalResult evaluateQuery(
+ QueryExpression expr, ThreadSafeOutputFormatterCallback<T> 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<Setting> 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<T> 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<T> getFwdDeps(Iterable<T> targets, QueryExpressionContext<T> context)
+ throws InterruptedException {
+ Map<SkyKey, T> targetsByKey = Maps.newHashMapWithExpectedSize(Iterables.size(targets));
+ for (T target : targets) {
+ targetsByKey.put(getSkyKey(target), target);
+ }
+ Map<SkyKey, Collection<T>> directDeps =
+ targetifyValues(graph.getDirectDeps(targetsByKey.keySet()));
+ if (targetsByKey.size() != directDeps.size()) {
+ Iterable<ConfiguredTargetKey> 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<T> result = createThreadSafeMutableSet();
+ for (Map.Entry<SkyKey, Collection<T>> entry : directDeps.entrySet()) {
+ result.addAll(filterFwdDeps(targetsByKey.get(entry.getKey()), entry.getValue()));
+ }
+ return result;
+ }
+
+ private Collection<T> filterFwdDeps(T configTarget, Collection<T> rawFwdDeps) {
+ if (settings.isEmpty()) {
+ return rawFwdDeps;
+ }
+ return getAllowedDeps(configTarget, rawFwdDeps);
+ }
+
+ @Override
+ public Collection<T> getReverseDeps(Iterable<T> targets, QueryExpressionContext<T> context)
+ throws InterruptedException {
+ Map<SkyKey, T> targetsByKey = Maps.newHashMapWithExpectedSize(Iterables.size(targets));
+ for (T target : targets) {
+ targetsByKey.put(getSkyKey(target), target);
+ }
+ Map<SkyKey, Collection<T>> reverseDepsByKey =
+ targetifyValues(graph.getReverseDeps(targetsByKey.keySet()));
+ if (targetsByKey.size() != reverseDepsByKey.size()) {
+ Iterable<ConfiguredTargetKey> 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<T, Collection<T>> reverseDepsByCT = new HashMap<>();
+ for (Map.Entry<SkyKey, Collection<T>> entry : reverseDepsByKey.entrySet()) {
+ reverseDepsByCT.put(targetsByKey.get(entry.getKey()), entry.getValue());
+ }
+ return reverseDepsByCT.isEmpty() ? Collections.emptyList() : filterReverseDeps(reverseDepsByCT);
+ }
+
+ private Collection<T> filterReverseDeps(Map<T, Collection<T>> rawReverseDeps) {
+ Set<T> result = CompactHashSet.create();
+ for (Map.Entry<T, Collection<T>> targetAndRdeps : rawReverseDeps.entrySet()) {
+ ImmutableSet.Builder<T> 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<T> getAllowedDeps(T target, Collection<T> 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) && target instanceof RuleConfiguredTarget) {
+ Set<ConfiguredTargetKey> implicitDeps = ((RuleConfiguredTarget) target).getImplicitDeps();
+ deps =
+ deps.stream()
+ .filter(
+ dep ->
+ !implicitDeps.contains(
+ ConfiguredTargetKey.of(getCorrectLabel(dep), getConfiguration(dep))))
+ .collect(Collectors.toList());
+ }
+ return deps;
+ }
+
+ protected Map<SkyKey, Collection<T>> targetifyValues(
+ Map<SkyKey, ? extends Iterable<SkyKey>> input) throws InterruptedException {
+ Map<SkyKey, Collection<T>> result = new HashMap<>();
+ for (Map.Entry<SkyKey, ? extends Iterable<SkyKey>> entry : input.entrySet()) {
+ Collection<T> 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<T> getTransitiveClosure(
+ ThreadSafeMutableSet<T> targets, QueryExpressionContext<T> context)
+ throws InterruptedException {
+ return SkyQueryUtils.getTransitiveClosure(
+ targets, targets1 -> getFwdDeps(targets1, context), createThreadSafeMutableSet());
+ }
+
+ @Override
+ public void buildTransitiveClosure(
+ QueryExpression caller, ThreadSafeMutableSet<T> targetNodes, int maxDepth) {
+ // TODO(bazel-team): implement this. Just needed for error-checking.
+ }
+
+ @Override
+ public ImmutableList<T> getNodesOnPath(T from, T to, QueryExpressionContext<T> context)
+ throws InterruptedException {
+ return SkyQueryUtils.getNodesOnPath(
+ from,
+ to,
+ targets -> getFwdDeps(targets, context),
+ getConfiguredTargetKeyExtractor()::extractKey);
+ }
+
+ @Override
+ public <V> MutableMap<T, V> createMutableMap() {
+ return new MutableKeyExtractorBackedMapImpl<>(getConfiguredTargetKeyExtractor());
+ }
+
+ @Override
+ public Uniquifier<T> createUniquifier() {
+ return new UniquifierImpl<>(
+ getConfiguredTargetKeyExtractor(), SkyQueryEnvironment.DEFAULT_THREAD_COUNT);
+ }
+
+ @Override
+ public MinDepthUniquifier<T> 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<String> patterns) {}
+
+ @Override
+ public ThreadSafeMutableSet<T> getBuildFiles(
+ QueryExpression caller,
+ ThreadSafeMutableSet<T> nodes,
+ boolean buildFiles,
+ boolean loads,
+ QueryExpressionContext<T> context)
+ throws QueryException {
+ throw new QueryException("buildfiles() doesn't make sense for the configured target graph");
+ }
+
+ @Override
+ public Collection<T> getSiblingTargetsInPackage(T target) {
+ throw new UnsupportedOperationException("siblings() not supported");
+ }
+
+ @Override
+ public void close() {}
+}