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() {}
+}