Change WalkableGraphFactory#prepareAndGet to take multiple SkyKeys as graph roots

It also changes a few accessors of utility methods in Skyframe library. It
refactors the QueryExpressionMapper to use a general QueryExpressionVisitor.

RELNOTES: None
PiperOrigin-RevId: 165534908
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
index 43f77c8..92a6dbb 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
@@ -293,6 +293,11 @@
     throw new IllegalStateException();
   }
 
+  /** For patterns of type {@link Type#SINGLE_TARGET}, returns the target path. */
+  public String getSingleTargetPath() {
+    throw new IllegalStateException();
+  }
+
   /**
    * For patterns of type {@link Type#SINGLE_TARGET} and {@link Type#TARGETS_IN_PACKAGE}, returns
    * the {@link PackageIdentifier} corresponding to the package that would contain the target(s)
@@ -349,6 +354,11 @@
     }
 
     @Override
+    public String getSingleTargetPath() {
+      return targetName;
+    }
+
+    @Override
     public boolean equals(Object o) {
       if (this == o) {
         return true;
diff --git a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
index 0121e00..383a28f 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
@@ -163,7 +163,7 @@
   private GraphBackedRecursivePackageProvider graphBackedRecursivePackageProvider;
   private ListeningExecutorService executor;
   private RecursivePackageProviderBackedTargetPatternResolver resolver;
-  private final SkyKey universeKey;
+  protected final SkyKey universeKey;
   private final ImmutableList<TargetPatternKey> universeTargetPatternKeys;
 
   public SkyQueryEnvironment(
@@ -239,17 +239,27 @@
     }
   }
 
-  private void beforeEvaluateQuery() throws InterruptedException {
-    if (graph == null || !graphFactory.isUpToDate(universeKey)) {
+  /** Gets roots of graph which contains all nodes needed to evaluate {@code expr}. */
+  protected Set<SkyKey> getGraphRootsFromExpression(QueryExpression expr)
+      throws QueryException, InterruptedException {
+    return ImmutableSet.of(universeKey);
+  }
+
+  private void beforeEvaluateQuery(QueryExpression expr)
+      throws QueryException, InterruptedException {
+    Set<SkyKey> roots = getGraphRootsFromExpression(expr);
+    if (graph == null || !graphFactory.isUpToDate(roots)) {
       // If this environment is uninitialized or the graph factory needs to evaluate, do so. We
       // assume here that this environment cannot be initialized-but-stale if the factory is up
       // to date.
       EvaluationResult<SkyValue> result;
       try (AutoProfiler p = AutoProfiler.logged("evaluation and walkable graph", LOG)) {
-        result =
-            graphFactory.prepareAndGet(universeKey, loadingPhaseThreads, universeEvalEventHandler);
+        result = graphFactory.prepareAndGet(roots, loadingPhaseThreads, universeEvalEventHandler);
       }
-      checkEvaluationResult(result);
+
+      if (roots.size() == 1 && Iterables.getOnlyElement(roots).equals(universeKey)) {
+        checkEvaluationResult(result);
+      }
 
       packageSemaphore = makeFreshPackageMultisetSemaphore();
       graph = result.getWalkableGraph();
@@ -335,7 +345,7 @@
     }
 
     @Override
-    public QueryExpression map(FunctionExpression functionExpression) {
+    public QueryExpression visit(FunctionExpression functionExpression) {
       if (functionExpression.getFunction().getName().equals(new RdepsFunction().getName())) {
         List<Argument> args = functionExpression.getArgs();
         QueryExpression universeExpression = args.get(0).getExpression();
@@ -349,14 +359,14 @@
           }
         }
       }
-      return super.map(functionExpression);
+      return super.visit(functionExpression);
     }
   }
 
   @Override
   public final QueryExpression transformParsedQuery(QueryExpression queryExpression) {
     QueryExpressionMapper mapper = getQueryExpressionMapper();
-    QueryExpression transformedQueryExpression = queryExpression.getMapped(mapper);
+    QueryExpression transformedQueryExpression = queryExpression.accept(mapper);
     LOG.info(String.format(
         "transformed query [%s] to [%s]",
         Ascii.truncate(
@@ -406,7 +416,7 @@
   public QueryEvalResult evaluateQuery(
       QueryExpression expr, ThreadSafeOutputFormatterCallback<Target> callback)
           throws QueryException, InterruptedException, IOException {
-    beforeEvaluateQuery();
+    beforeEvaluateQuery(expr);
 
     // SkyQueryEnvironment batches callback invocations using a BatchStreamedCallback, created here
     // so that there's one per top-level evaluateQuery call. The batch size is large enough that
@@ -1153,7 +1163,7 @@
       implements InterruptibleSupplier<ImmutableSet<PathFragment>> {
     private final WalkableGraph graph;
 
-    BlacklistSupplier(WalkableGraph graph) {
+    private BlacklistSupplier(WalkableGraph graph) {
       this.graph = graph;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/AggregatingQueryExpressionVisitor.java b/src/main/java/com/google/devtools/build/lib/query2/engine/AggregatingQueryExpressionVisitor.java
new file mode 100644
index 0000000..cde80e0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/AggregatingQueryExpressionVisitor.java
@@ -0,0 +1,114 @@
+// Copyright 2017 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.engine;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.primitives.Booleans;
+import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Argument;
+import com.google.devtools.build.lib.query2.engine.QueryEnvironment.ArgumentType;
+import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction;
+
+/**
+ * An implementation of {@link QueryExpressionVisitor} which recursively visits all nested {@link
+ * QueryExpression}s
+ */
+public abstract class AggregatingQueryExpressionVisitor<T> implements QueryExpressionVisitor<T> {
+
+  @Override
+  public T visit(BinaryOperatorExpression binaryOperatorExpression) {
+    ImmutableMap.Builder<QueryExpression, T> builder = ImmutableMap.builder();
+    for (QueryExpression expr : binaryOperatorExpression.getOperands()) {
+      builder.put(expr, expr.accept(this));
+    }
+
+    return aggregate(builder.build());
+  }
+
+  @Override
+  public T visit(FunctionExpression functionExpression) {
+    ImmutableMap.Builder<QueryExpression, T> builder = ImmutableMap.builder();
+    for (Argument argument : functionExpression.getArgs()) {
+      if (argument.getType() == ArgumentType.EXPRESSION) {
+        builder.put(argument.getExpression(), argument.getExpression().accept(this));
+      }
+    }
+
+    return aggregate(builder.build());
+  }
+
+  @Override
+  public T visit(LetExpression letExpression) {
+    return aggregate(
+        ImmutableMap.of(
+            letExpression.getVarExpr(), letExpression.getVarExpr().accept(this),
+            letExpression.getBodyExpr(), letExpression.getBodyExpr().accept(this)));
+  }
+
+  @Override
+  public T visit(SetExpression setExpression) {
+    ImmutableMap.Builder<QueryExpression, T> builder = ImmutableMap.builder();
+    for (TargetLiteral targetLiteral : setExpression.getWords()) {
+      builder.put(targetLiteral, targetLiteral.accept(this));
+    }
+
+    return aggregate(builder.build());
+  }
+
+  /**
+   * Aggregates results from all sub-expression visitations. The map is guaranteed to include
+   * results for all direct sub-expressions.
+   */
+  protected abstract T aggregate(ImmutableMap<QueryExpression, T> resultMap);
+
+  /**
+   * Returns {@code true} when the query expression contains at least one {@link QueryFunction}
+   * whose name is in the set of {@code functionName}.
+   */
+  public static class ContainsFunctionQueryExpressionVisitor
+      extends AggregatingQueryExpressionVisitor<Boolean>
+      implements QueryExpressionVisitor<Boolean> {
+
+    private final ImmutableSet<String> functionNames;
+
+    public ContainsFunctionQueryExpressionVisitor(Iterable<String> functionNames) {
+      this.functionNames = ImmutableSet.copyOf(functionNames);
+    }
+
+    @Override
+    public Boolean visit(TargetLiteral targetLiteral) {
+      return false;
+    }
+
+    @Override
+    public Boolean visit(SetExpression setExpression) {
+      return false;
+    }
+
+    @Override
+    public Boolean visit(FunctionExpression functionExpression) {
+      QueryFunction function = functionExpression.getFunction();
+      if (functionNames.contains(function.getName())) {
+        return true;
+      } else {
+        return super.visit(functionExpression);
+      }
+    }
+
+    @Override
+    protected Boolean aggregate(ImmutableMap<QueryExpression, Boolean> resultMap) {
+      return Booleans.contains(Booleans.toArray(resultMap.values()), true);
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/BinaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/query2/engine/BinaryOperatorExpression.java
index 31fa785..fa5a4d8 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/BinaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/BinaryOperatorExpression.java
@@ -183,8 +183,8 @@
   }
 
   @Override
-  public QueryExpression getMapped(QueryExpressionMapper mapper) {
-    return mapper.map(this);
+  public <T> T accept(QueryExpressionVisitor<T> visitor) {
+    return visitor.visit(this);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/FunctionExpression.java b/src/main/java/com/google/devtools/build/lib/query2/engine/FunctionExpression.java
index a8c21d6..bd78b20 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/FunctionExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/FunctionExpression.java
@@ -60,8 +60,8 @@
   }
 
   @Override
-  public QueryExpression getMapped(QueryExpressionMapper mapper) {
-    return mapper.map(this);
+  public <T> T accept(QueryExpressionVisitor<T> visitor) {
+    return visitor.visit(this);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/LetExpression.java b/src/main/java/com/google/devtools/build/lib/query2/engine/LetExpression.java
index fd10135..eabf67c 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/LetExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/LetExpression.java
@@ -91,8 +91,8 @@
   }
 
   @Override
-  public QueryExpression getMapped(QueryExpressionMapper mapper) {
-    return mapper.map(this);
+  public <T> T accept(QueryExpressionVisitor<T> visitor) {
+    return visitor.visit(this);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpression.java b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpression.java
index 920722d..5398f4d 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpression.java
@@ -81,8 +81,8 @@
    */
   public abstract void collectTargetPatterns(Collection<String> literals);
 
-  /* Implementations should just be {@code return mapper.map(this)}. */
-  public abstract QueryExpression getMapped(QueryExpressionMapper mapper);
+  /* Implementations should just be {@code return visitor.visit(this)}. */
+  public abstract <T> T accept(QueryExpressionVisitor<T> visitor);
 
   /**
    * Returns this query expression pretty-printed.
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpressionMapper.java b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpressionMapper.java
index 8b4e3be..10e05eb 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpressionMapper.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpressionMapper.java
@@ -19,22 +19,25 @@
 /**
  * Performs an arbitrary transformation of a {@link QueryExpression}.
  *
- * <p>For each subclass of {@link QueryExpression}, there's a corresponding {@link #map} overload
- * that transforms a node of that type. By default, this method recursively applies this
- * {@link QueryExpressionMapper}'s transformation in a structure-preserving manner (trying to
- * maintain reference-equality, as an optimization). Subclasses of {@link QueryExpressionMapper} can
- * override these methods in order to implement an arbitrary transformation.
+ * <p>For each subclass of {@link QueryExpression}, there's a corresponding {@link #visit} overload
+ * that transforms a node of that type. By default, this method recursively applies this {@link
+ * QueryExpressionMapper}'s transformation in a structure-preserving manner (trying to maintain
+ * reference-equality, as an optimization). Subclasses of {@link QueryExpressionMapper} can override
+ * these methods in order to implement an arbitrary transformation.
  */
-public abstract class QueryExpressionMapper {
-  public QueryExpression map(TargetLiteral targetLiteral) {
+public abstract class QueryExpressionMapper implements QueryExpressionVisitor<QueryExpression> {
+
+  @Override
+  public QueryExpression visit(TargetLiteral targetLiteral) {
     return targetLiteral;
   }
 
-  public QueryExpression map(BinaryOperatorExpression binaryOperatorExpression) {
+  @Override
+  public QueryExpression visit(BinaryOperatorExpression binaryOperatorExpression) {
     boolean changed = false;
     ImmutableList.Builder<QueryExpression> mappedOperandsBuilder = ImmutableList.builder();
     for (QueryExpression operand : binaryOperatorExpression.getOperands()) {
-      QueryExpression mappedOperand = operand.getMapped(this);
+      QueryExpression mappedOperand = operand.accept(this);
       if (mappedOperand != operand) {
         changed = true;
       }
@@ -46,20 +49,22 @@
         : binaryOperatorExpression;
   }
 
-  public QueryExpression map(FunctionExpression functionExpression) {
+  @Override
+  public QueryExpression visit(FunctionExpression functionExpression) {
     boolean changed = false;
     ImmutableList.Builder<Argument> mappedArgumentBuilder = ImmutableList.builder();
     for (Argument argument : functionExpression.getArgs()) {
       switch (argument.getType()) {
-        case EXPRESSION: {
-          QueryExpression expr = argument.getExpression();
-          QueryExpression mappedExpression = expr.getMapped(this);
-          mappedArgumentBuilder.add(Argument.of(mappedExpression));
-          if (expr != mappedExpression) {
-            changed = true;
+        case EXPRESSION:
+          {
+            QueryExpression expr = argument.getExpression();
+            QueryExpression mappedExpression = expr.accept(this);
+            mappedArgumentBuilder.add(Argument.of(mappedExpression));
+            if (expr != mappedExpression) {
+              changed = true;
+            }
+            break;
           }
-          break;
-        }
         default:
           mappedArgumentBuilder.add(argument);
           break;
@@ -70,13 +75,14 @@
         : functionExpression;
   }
 
-  public QueryExpression map(LetExpression letExpression) {
+  @Override
+  public QueryExpression visit(LetExpression letExpression) {
     boolean changed = false;
-    QueryExpression mappedVarExpr = letExpression.getVarExpr().getMapped(this);
+    QueryExpression mappedVarExpr = letExpression.getVarExpr().accept(this);
     if (mappedVarExpr != letExpression.getVarExpr()) {
       changed = true;
     }
-    QueryExpression mappedBodyExpr = letExpression.getBodyExpr().getMapped(this);
+    QueryExpression mappedBodyExpr = letExpression.getBodyExpr().accept(this);
     if (mappedBodyExpr != letExpression.getBodyExpr()) {
       changed = true;
     }
@@ -85,7 +91,8 @@
         : letExpression;
   }
 
-  public QueryExpression map(SetExpression setExpression) {
+  @Override
+  public QueryExpression visit(SetExpression setExpression) {
     return setExpression;
   }
 
@@ -109,27 +116,27 @@
     }
 
     @Override
-    public QueryExpression map(TargetLiteral targetLiteral) {
+    public QueryExpression visit(TargetLiteral targetLiteral) {
       return mapAll(targetLiteral, mappers);
     }
 
     @Override
-    public QueryExpression map(BinaryOperatorExpression binaryOperatorExpression) {
+    public QueryExpression visit(BinaryOperatorExpression binaryOperatorExpression) {
       return mapAll(binaryOperatorExpression, mappers);
     }
 
     @Override
-    public QueryExpression map(FunctionExpression functionExpression) {
+    public QueryExpression visit(FunctionExpression functionExpression) {
       return mapAll(functionExpression, mappers);
     }
 
     @Override
-    public QueryExpression map(LetExpression letExpression) {
+    public QueryExpression visit(LetExpression letExpression) {
       return mapAll(letExpression, mappers);
     }
 
     @Override
-    public QueryExpression map(SetExpression setExpression) {
+    public QueryExpression visit(SetExpression setExpression) {
       return mapAll(setExpression, mappers);
     }
 
@@ -137,7 +144,7 @@
         QueryExpression expression, QueryExpressionMapper[] mappers) {
       QueryExpression expr = expression;
       for (int i = mappers.length - 1; i >= 0; i--) {
-        expr = expr.getMapped(mappers[i]);
+        expr = expr.accept(mappers[i]);
       }
 
       return expr;
@@ -151,27 +158,27 @@
     }
 
     @Override
-    public QueryExpression map(TargetLiteral targetLiteral) {
+    public QueryExpression visit(TargetLiteral targetLiteral) {
       return targetLiteral;
     }
 
     @Override
-    public QueryExpression map(BinaryOperatorExpression binaryOperatorExpression) {
+    public QueryExpression visit(BinaryOperatorExpression binaryOperatorExpression) {
       return binaryOperatorExpression;
     }
 
     @Override
-    public QueryExpression map(FunctionExpression functionExpression) {
+    public QueryExpression visit(FunctionExpression functionExpression) {
       return functionExpression;
     }
 
     @Override
-    public QueryExpression map(LetExpression letExpression) {
+    public QueryExpression visit(LetExpression letExpression) {
       return letExpression;
     }
 
     @Override
-    public QueryExpression map(SetExpression setExpression) {
+    public QueryExpression visit(SetExpression setExpression) {
       return setExpression;
     }
   }
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpressionVisitor.java b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpressionVisitor.java
new file mode 100644
index 0000000..3f233ee
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryExpressionVisitor.java
@@ -0,0 +1,27 @@
+// Copyright 2017 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.engine;
+
+/** Provides interfaces to visit all {@link QueryExpression}s. */
+public interface QueryExpressionVisitor<T> {
+  T visit(TargetLiteral targetLiteral);
+
+  T visit(BinaryOperatorExpression binaryOperatorExpression);
+
+  T visit(FunctionExpression functionExpression);
+
+  T visit(LetExpression letExpression);
+
+  T visit(SetExpression setExpression);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/SetExpression.java b/src/main/java/com/google/devtools/build/lib/query2/engine/SetExpression.java
index 57db015..eb12971 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/SetExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/SetExpression.java
@@ -64,8 +64,13 @@
   }
 
   @Override
-  public QueryExpression getMapped(QueryExpressionMapper mapper) {
-    return mapper.map(this);
+  public <T> T accept(QueryExpressionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  /** Gets the list of {@link TargetLiteral}s contained in the expression. */
+  List<TargetLiteral> getWords() {
+    return words;
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/TargetLiteral.java b/src/main/java/com/google/devtools/build/lib/query2/engine/TargetLiteral.java
index 733bffb..8cb1ab8 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/TargetLiteral.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/TargetLiteral.java
@@ -80,8 +80,8 @@
   }
 
   @Override
-  public QueryExpression getMapped(QueryExpressionMapper mapper) {
-    return mapper.map(this);
+  public <T> T accept(QueryExpressionVisitor<T> visitor) {
+    return visitor.visit(this);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java
index acc5a54..951a202 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java
@@ -38,15 +38,12 @@
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.packages.TriState;
 import com.google.devtools.build.lib.query2.AbstractBlazeQueryEnvironment;
-import com.google.devtools.build.lib.query2.engine.BuildFilesFunction;
-import com.google.devtools.build.lib.query2.engine.FunctionExpression;
-import com.google.devtools.build.lib.query2.engine.LoadFilesFunction;
+import com.google.devtools.build.lib.query2.engine.AggregatingQueryExpressionVisitor.ContainsFunctionQueryExpressionVisitor;
 import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback;
 import com.google.devtools.build.lib.query2.engine.QueryEnvironment;
-import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction;
 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.QueryExpressionMapper;
+import com.google.devtools.build.lib.query2.engine.QueryExpressionVisitor;
 import com.google.devtools.build.lib.query2.engine.SynchronizedDelegatingOutputFormatterCallback;
 import com.google.devtools.build.lib.query2.engine.ThreadSafeOutputFormatterCallback;
 import com.google.devtools.build.lib.query2.output.QueryOptions.OrderOutput;
@@ -67,7 +64,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 import javax.annotation.Nullable;
 
 /**
@@ -380,20 +376,11 @@
       if (!(env instanceof AbstractBlazeQueryEnvironment)) {
         return;
       }
-      final AtomicBoolean found = new AtomicBoolean(false);
-      QueryExpressionMapper noteBuildFilesAndLoadLilesMapper = new QueryExpressionMapper() {
-        @Override
-        public QueryExpression map(FunctionExpression functionExpression) {
-          QueryFunction queryFunction = functionExpression.getFunction();
-          if (queryFunction instanceof LoadFilesFunction
-              || queryFunction instanceof BuildFilesFunction) {
-            found.set(true);
-          }
-          return super.map(functionExpression);
-        }
-      };
-      expr.getMapped(noteBuildFilesAndLoadLilesMapper);
-      if (found.get()) {
+
+      QueryExpressionVisitor<Boolean> noteBuildFilesAndLoadLilesVisitor =
+          new ContainsFunctionQueryExpressionVisitor(ImmutableList.of("loadfiles", "buildfiles"));
+
+      if (expr.accept(noteBuildFilesAndLoadLilesVisitor)) {
         throw new QueryException(
             "Query expressions involving 'buildfiles' or 'loadfiles' cannot be used with "
             + "--output=location");
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/CollectPackagesUnderDirectoryValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/CollectPackagesUnderDirectoryValue.java
index 5f819e5..9e2f280 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/CollectPackagesUnderDirectoryValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/CollectPackagesUnderDirectoryValue.java
@@ -225,7 +225,7 @@
 
   /** Create a collect packages under directory request. */
   @ThreadSafe
-  static SkyKey key(
+  public static SkyKey key(
       RepositoryName repository, RootedPath rootedPath, ImmutableSet<PathFragment> excludedPaths) {
     return key(new RecursivePkgKey(repository, rootedPath, excludedPaths));
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/CollectTargetsInPackageValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/CollectTargetsInPackageValue.java
index 700d322..632a794 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/CollectTargetsInPackageValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/CollectTargetsInPackageValue.java
@@ -19,6 +19,7 @@
 import com.google.devtools.build.skyframe.LegacySkyKey;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import java.io.Serializable;
 
 /** Singleton result of {@link CollectTargetsInPackageFunction}. */
 public class CollectTargetsInPackageValue implements SkyValue {
@@ -30,7 +31,7 @@
    * Creates a key for evaluation of {@link CollectTargetsInPackageFunction}. See that class's
    * comment for what callers should have done beforehand.
    */
-  static SkyKey key(PackageIdentifier packageId, FilteringPolicy filteringPolicy) {
+  public static SkyKey key(PackageIdentifier packageId, FilteringPolicy filteringPolicy) {
     return LegacySkyKey.create(
         SkyFunctions.COLLECT_TARGETS_IN_PACKAGE,
         CollectTargetsInPackageKey.create(packageId, filteringPolicy));
@@ -38,7 +39,7 @@
 
   /** {@link SkyKey} argument. */
   @AutoValue
-  public abstract static class CollectTargetsInPackageKey {
+  public abstract static class CollectTargetsInPackageKey implements Serializable {
     public static CollectTargetsInPackageKey create(
         PackageIdentifier packageId, FilteringPolicy filteringPolicy) {
       return new AutoValue_CollectTargetsInPackageValue_CollectTargetsInPackageKey(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 96e89fa..7749443 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -1746,17 +1746,16 @@
    */
   @Override
   public EvaluationResult<SkyValue> prepareAndGet(
-      SkyKey universeKey, int numThreads, ExtendedEventHandler eventHandler)
+      Set<SkyKey> roots, int numThreads, ExtendedEventHandler eventHandler)
       throws InterruptedException {
     EvaluationResult<SkyValue> evaluationResult =
-        buildDriver.evaluate(ImmutableList.of(universeKey), true, numThreads, eventHandler);
-    Preconditions.checkNotNull(evaluationResult.getWalkableGraph(), universeKey);
+        buildDriver.evaluate(roots, true, numThreads, eventHandler);
     return evaluationResult;
   }
 
   @Override
-  public boolean isUpToDate(SkyKey universeKey) {
-    return buildDriver.alreadyEvaluated(ImmutableList.of(universeKey));
+  public boolean isUpToDate(Set<SkyKey> roots) {
+    return buildDriver.alreadyEvaluated(roots);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java b/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java
index 5b98079..b5b744f 100644
--- a/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java
@@ -18,6 +18,7 @@
 import com.google.devtools.build.skyframe.QueryableGraph.Reason;
 import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
 import javax.annotation.Nullable;
 
 /**
@@ -99,15 +100,15 @@
   /** Provides a WalkableGraph on demand after preparing it. */
   interface WalkableGraphFactory {
     EvaluationResult<SkyValue> prepareAndGet(
-        SkyKey universeKey, int numThreads, ExtendedEventHandler eventHandler)
+        Set<SkyKey> roots, int numThreads, ExtendedEventHandler eventHandler)
         throws InterruptedException;
 
     /**
-     * Returns true if this instance has already been used to {@link #prepareAndGet} {@code
-     * universeKey}. If so, cached results from {@link #prepareAndGet} can be re-used safely,
-     * potentially saving some processing time.
+     * Returns true if this instance has already been used to {@link #prepareAndGet} {@code roots}.
+     * If so, cached results from {@link #prepareAndGet} can be re-used safely, potentially saving
+     * some processing time.
      */
-    boolean isUpToDate(SkyKey universeKey);
+    boolean isUpToDate(Set<SkyKey> roots);
 
     /** Returns the {@link SkyKey} that defines this universe. */
     SkyKey getUniverseKey(Collection<String> roots, String offset);
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/IOExceptionsTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/IOExceptionsTest.java
index 3cbcf24..dcbff51 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/IOExceptionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/IOExceptionsTest.java
@@ -16,6 +16,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.packages.ConstantRuleVisibility;
 import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase;
@@ -64,7 +65,7 @@
   private boolean visitTransitively(Label label) throws InterruptedException {
     SkyKey key = TransitiveTargetValue.key(label);
     EvaluationResult<SkyValue> result =
-        skyframeExecutor.prepareAndGet(key, /*numThreads=*/5, reporter);
+        skyframeExecutor.prepareAndGet(ImmutableSet.of(key), /*numThreads=*/ 5, reporter);
     TransitiveTargetValue value = (TransitiveTargetValue) result.get(key);
     System.out.println(value);
     boolean hasTransitiveError = (value == null) || value.getTransitiveRootCauses() != null;