Allow globs to be used outside of the skyframe package.

--
MOS_MIGRATED_REVID=93647914
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContext.java b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContext.java
index d6d08fa..48714f2 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContext.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContext.java
@@ -14,9 +14,14 @@
 
 package com.google.devtools.build.lib.actions;
 
+import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.actions.Artifact.MiddlemanExpander;
 import com.google.devtools.build.lib.actions.cache.MetadataHandler;
 import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunction.Environment;
+
+import javax.annotation.Nullable;
 
 /**
  * A class that groups services in the scope of the action. Like the FileOutErr object.
@@ -28,14 +33,38 @@
   private final MetadataHandler metadataHandler;
   private final FileOutErr fileOutErr;
   private final MiddlemanExpander middlemanExpander;
+  @Nullable
+  private final Environment env;
 
-  public ActionExecutionContext(Executor executor, ActionInputFileCache actionInputFileCache,
-      MetadataHandler metadataHandler, FileOutErr fileOutErr, MiddlemanExpander middlemanExpander) {
+  private ActionExecutionContext(Executor executor, ActionInputFileCache actionInputFileCache,
+      MetadataHandler metadataHandler, FileOutErr fileOutErr,
+      @Nullable MiddlemanExpander middlemanExpander,
+      @Nullable SkyFunction.Environment env) {
     this.actionInputFileCache = actionInputFileCache;
     this.metadataHandler = metadataHandler;
     this.fileOutErr = fileOutErr;
     this.executor = executor;
     this.middlemanExpander = middlemanExpander;
+    this.env = env;
+  }
+
+  public ActionExecutionContext(Executor executor, ActionInputFileCache actionInputFileCache,
+      MetadataHandler metadataHandler, FileOutErr fileOutErr, MiddlemanExpander middlemanExpander) {
+    this(executor, actionInputFileCache, metadataHandler, fileOutErr, middlemanExpander, null);
+  }
+
+  public static ActionExecutionContext normal(Executor executor,
+      ActionInputFileCache actionInputFileCache, MetadataHandler metadataHandler,
+      FileOutErr fileOutErr, MiddlemanExpander middlemanExpander) {
+    return new ActionExecutionContext(executor, actionInputFileCache, metadataHandler, fileOutErr,
+        middlemanExpander, null);
+  }
+
+  public static ActionExecutionContext forInputDiscovery(Executor executor,
+      ActionInputFileCache actionInputFileCache, MetadataHandler metadataHandler,
+      FileOutErr fileOutErr, Environment env) {
+    return new ActionExecutionContext(executor, actionInputFileCache, metadataHandler, fileOutErr,
+        null, env);
   }
 
   public ActionInputFileCache getActionInputFileCache() {
@@ -63,11 +92,18 @@
   }
 
   /**
+   * Provides a mechanism for the action to request values from Skyframe while it discovers inputs.
+   */
+  public Environment getEnvironmentForDiscoveringInputs() {
+    return Preconditions.checkNotNull(env);
+  }
+
+  /**
    * Allows us to create a new context that overrides the FileOutErr with another one. This is
    * useful for muting the output for example.
    */
   public ActionExecutionContext withFileOutErr(FileOutErr fileOutErr) {
     return new ActionExecutionContext(executor, actionInputFileCache, metadataHandler, fileOutErr,
-        middlemanExpander);
+        middlemanExpander, env);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
index d0ac469..770899e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
@@ -296,16 +296,19 @@
 
     // This may be recreated if we discover inputs.
     PerActionFileCache perActionFileCache = new PerActionFileCache(state.inputArtifactData);
-    ActionExecutionContext actionExecutionContext =
-        skyframeActionExecutor.constructActionExecutionContext(perActionFileCache,
-            metadataHandler, state.expandedMiddlemen);
+    ActionExecutionContext actionExecutionContext = null;
     boolean inputsDiscoveredDuringActionExecution = false;
     Map<Artifact, FileArtifactValue> metadataFoundDuringActionExecution = null;
     try {
       if (action.discoversInputs()) {
         if (!state.hasDiscoveredInputs()) {
-          state.discoveredInputs =
-              skyframeActionExecutor.discoverInputs(action, actionExecutionContext);
+          try {
+            state.discoveredInputs = skyframeActionExecutor.discoverInputs(action,
+                perActionFileCache, metadataHandler, env);
+          } catch (MissingDepException e) {
+            Preconditions.checkState(env.valuesMissing(), action);
+            return null;
+          }
           if (state.discoveredInputs == null) {
             // Action had nothing to tell us about discovered inputs before execution. We'll have to
             // add them afterwards.
@@ -325,20 +328,22 @@
           perActionFileCache = new PerActionFileCache(state.inputArtifactData);
           metadataHandler =
               new ActionMetadataHandler(state.inputArtifactData, action.getOutputs(), tsgm);
-          actionExecutionContext =
-              skyframeActionExecutor.constructActionExecutionContext(perActionFileCache,
-                  metadataHandler, state.expandedMiddlemen);
         }
       }
+      actionExecutionContext =
+          skyframeActionExecutor.constructActionExecutionContext(perActionFileCache,
+              metadataHandler, state.expandedMiddlemen);
       if (!state.hasExecutedAction()) {
         state.value = skyframeActionExecutor.executeAction(action,
             metadataHandler, actionStartTime, actionExecutionContext);
       }
     } finally {
-      try {
-        actionExecutionContext.getFileOutErr().close();
-      } catch (IOException e) {
-        // Nothing we can do here.
+      if (actionExecutionContext != null) {
+        try {
+          actionExecutionContext.getFileOutErr().close();
+        } catch (IOException e) {
+          // Nothing we can do here.
+        }
       }
       if (inputsDiscoveredDuringActionExecution) {
         metadataFoundDuringActionExecution =
@@ -524,6 +529,12 @@
   }
 
   /**
+   * Exception to be thrown if an action is missing Skyframe dependencies that it finds are missing
+   * during execution/input discovery.
+   */
+  public static class MissingDepException extends RuntimeException {}
+
+  /**
    * Should be called once execution is over, and the intra-build cache of in-progress computations
    * should be discarded. If the cache is non-empty (due to an interrupted/failed build), failure to
    * call complete() can both cause a memory leak and incorrect results on the subsequent build.
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupValue.java
index 16516b5..0383534 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupValue.java
@@ -29,7 +29,7 @@
   public abstract boolean hasContainingPackage();
 
   /** If there is a containing package, returns its name. */
-  abstract PackageIdentifier getContainingPackageName();
+  public abstract PackageIdentifier getContainingPackageName();
 
   /** If there is a containing package, returns its package root */
   public abstract Path getContainingPackageRoot();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileStateValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/FileStateValue.java
index 030b50b..3d06d7c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileStateValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FileStateValue.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.skyframe;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
 import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
@@ -49,7 +50,8 @@
  *
  * <p>All subclasses must implement {@link #equals} and {@link #hashCode} properly.
  */
-abstract class FileStateValue implements SkyValue {
+@VisibleForTesting
+public abstract class FileStateValue implements SkyValue {
 
   static final FileStateValue DIRECTORY_FILE_STATE_NODE = DirectoryFileStateValue.INSTANCE;
   static final FileStateValue NONEXISTENT_FILE_STATE_NODE = NonexistentFileStateValue.INSTANCE;
@@ -92,8 +94,9 @@
         + "neither a file nor directory nor symlink.");
   }
 
+  @VisibleForTesting
   @ThreadSafe
-  static SkyKey key(RootedPath rootedPath) {
+  public static SkyKey key(RootedPath rootedPath) {
     return new SkyKey(SkyFunctions.FILE_STATE, rootedPath);
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobValue.java
index 6de0fbd..c93370c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobValue.java
@@ -25,12 +25,10 @@
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 
-/**
- * A value corresponding to a glob.
- */
+/** A value corresponding to a glob. */
 @Immutable
 @ThreadSafe
-final class GlobValue implements SkyValue {
+public final class GlobValue implements SkyValue {
 
   static final GlobValue EMPTY = new GlobValue(
       NestedSetBuilder.<PathFragment>emptySet(Order.STABLE_ORDER));
@@ -44,7 +42,7 @@
   /**
    * Returns glob matches.
    */
-  NestedSet<PathFragment> getMatches() {
+  public NestedSet<PathFragment> getMatches() {
     return matches;
   }
 
@@ -75,7 +73,8 @@
    * @throws InvalidGlobPatternException if the pattern is not valid.
    */
   @ThreadSafe
-  static SkyKey key(PackageIdentifier packageId, String pattern, boolean excludeDirs)
+  public static SkyKey key(PackageIdentifier packageId, String pattern, boolean excludeDirs,
+      PathFragment subdir)
       throws InvalidGlobPatternException {
     if (pattern.indexOf('?') != -1) {
       throw new InvalidGlobPatternException(pattern, "wildcard ? forbidden");
@@ -86,7 +85,7 @@
       throw new InvalidGlobPatternException(pattern, error);
     }
 
-    return internalKey(packageId, PathFragment.EMPTY_FRAGMENT, pattern, excludeDirs);
+    return internalKey(packageId, subdir, pattern, excludeDirs);
   }
 
   /**
@@ -116,7 +115,7 @@
    * An exception that indicates that a glob pattern is syntactically invalid.
    */
   @ThreadSafe
-  static final class InvalidGlobPatternException extends Exception {
+  public static final class InvalidGlobPatternException extends Exception {
     private final String pattern;
 
     InvalidGlobPatternException(String pattern, String error) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index 7f2f224..8b354cd 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -293,7 +293,7 @@
       boolean excludeDirs = globPattern.getSecond();
       SkyKey globSkyKey;
       try {
-        globSkyKey = GlobValue.key(packageId, pattern, excludeDirs);
+        globSkyKey = GlobValue.key(packageId, pattern, excludeDirs, PathFragment.EMPTY_FRAGMENT);
       } catch (InvalidGlobPatternException e) {
         // Globs that make it to pkg.getGlobPatterns() should already be filtered for errors.
         throw new IllegalStateException(e);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
index 4000d0b..9b986c1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
@@ -65,6 +65,7 @@
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.Symlinks;
+import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.protobuf.ByteString;
 
 import java.io.IOException;
@@ -517,8 +518,16 @@
    * <p>This method is just a wrapper around {@link Action#discoverInputs} that properly processes
    * any ActionExecutionException thrown before rethrowing it to the caller.
    */
-  Collection<Artifact> discoverInputs(Action action, ActionExecutionContext actionExecutionContext)
+  Collection<Artifact> discoverInputs(Action action, PerActionFileCache graphFileCache,
+      MetadataHandler metadataHandler, Environment env)
       throws ActionExecutionException, InterruptedException {
+    ActionExecutionContext actionExecutionContext =
+        ActionExecutionContext.forInputDiscovery(
+            executorEngine,
+            new DelegatingPairFileCache(graphFileCache, perBuildFileCache),
+            metadataHandler,
+            actionLogBufferPathGenerator.generate(),
+            env);
     try {
       return action.discoverInputs(actionExecutionContext);
     } catch (ActionExecutionException e) {
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrExceptionUtils.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrExceptionUtils.java
index e66f4fa..32bc254 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrExceptionUtils.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrExceptionUtils.java
@@ -13,10 +13,13 @@
 // limitations under the License.
 package com.google.devtools.build.skyframe;
 
+import com.google.common.annotations.VisibleForTesting;
+
 import javax.annotation.Nullable;
 
 /** Utilities for producing and consuming ValueOrException(2|3|4)? instances. */
-class ValueOrExceptionUtils {
+@VisibleForTesting
+public class ValueOrExceptionUtils {
 
   /** The bottom exception type. */
   class BottomException extends Exception {
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrUntypedException.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrUntypedException.java
index c7ea7d4..0b2e680 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrUntypedException.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrUntypedException.java
@@ -13,6 +13,8 @@
 // limitations under the License.
 package com.google.devtools.build.skyframe;
 
+import com.google.common.annotations.VisibleForTesting;
+
 import javax.annotation.Nullable;
 
 /**
@@ -22,7 +24,8 @@
  * {@link ValueOrExceptionUtils}. It's an abstract class (as opposed to an interface) to avoid
  * exposing the methods outside the package.
  */
-abstract class ValueOrUntypedException {
+@VisibleForTesting
+public abstract class ValueOrUntypedException {
 
   /** Returns the stored value, if there was one. */
   @Nullable
diff --git a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
index 2f601d4..565601b 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
@@ -37,20 +37,33 @@
 import com.google.devtools.build.lib.actions.ResourceSet;
 import com.google.devtools.build.lib.actions.Root;
 import com.google.devtools.build.lib.actions.cache.MetadataHandler;
+import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.exec.SingleBuildFileCache;
 import com.google.devtools.build.lib.syntax.Label;
 import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.ResourceUsage;
 import com.google.devtools.build.lib.util.io.FileOutErr;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import com.google.devtools.build.skyframe.AbstractSkyFunctionEnvironment;
+import com.google.devtools.build.skyframe.BuildDriver;
+import com.google.devtools.build.skyframe.ErrorInfo;
+import com.google.devtools.build.skyframe.EvaluationResult;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.ValueOrExceptionUtils;
+import com.google.devtools.build.skyframe.ValueOrUntypedException;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.annotation.Nullable;
@@ -80,6 +93,74 @@
             : ActionInputHelper.actionGraphMiddlemanExpander(actionGraph));
   }
 
+  public static ActionExecutionContext createContextForInputDiscovery(Executor executor,
+      FileOutErr fileOutErr, Path execRoot, MetadataHandler metadataHandler,
+      BuildDriver buildDriver) {
+    return ActionExecutionContext.forInputDiscovery(
+        executor,
+        new SingleBuildFileCache(execRoot.getPathString(), execRoot.getFileSystem()),
+        metadataHandler, fileOutErr,
+        new BlockingSkyFunctionEnvironment(buildDriver,
+            executor == null ? null : executor.getEventHandler()));
+
+  }
+
+
+  /**
+   * {@link SkyFunction.Environment} that internally makes a full Skyframe evaluate call for the
+   * requested keys, blocking until the values are ready.
+   */
+  private static class BlockingSkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
+    private final BuildDriver driver;
+    private final EventHandler eventHandler;
+
+    private BlockingSkyFunctionEnvironment(BuildDriver driver, EventHandler eventHandler) {
+      this.driver = driver;
+      this.eventHandler = eventHandler;
+    }
+
+    @Override
+    protected Map<SkyKey, ValueOrUntypedException> getValueOrUntypedExceptions(
+        Iterable<SkyKey> depKeys) {
+      EvaluationResult<SkyValue> evaluationResult;
+      Map<SkyKey, ValueOrUntypedException> result = new HashMap<>();
+      try {
+        evaluationResult = driver.evaluate(depKeys, /*keepGoing=*/false,
+            ResourceUsage.getAvailableProcessors(), eventHandler);
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        for (SkyKey key : depKeys) {
+          result.put(key, ValueOrExceptionUtils.ofNull());
+        }
+        return result;
+      }
+      for (SkyKey key : depKeys) {
+        SkyValue value = evaluationResult.get(key);
+        if (value != null) {
+          result.put(key, ValueOrExceptionUtils.ofValue(value));
+          continue;
+        }
+        ErrorInfo errorInfo = evaluationResult.getError(key);
+        if (errorInfo == null || errorInfo.getException() == null) {
+          result.put(key, ValueOrExceptionUtils.ofNull());
+          continue;
+        }
+        result.put(key, ValueOrExceptionUtils.ofExn(errorInfo.getException()));
+      }
+      return result;
+    }
+
+    @Override
+    public EventHandler getListener() {
+      return null;
+    }
+
+    @Override
+    public boolean inErrorBubblingForTesting() {
+      return false;
+    }
+  }
+
   /**
    * A dummy ActionOwner implementation for use in tests.
    */
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/util/FsApparatus.java b/src/test/java/com/google/devtools/build/lib/vfs/util/FsApparatus.java
index 08d3158..eff9ffb 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/util/FsApparatus.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/util/FsApparatus.java
@@ -24,6 +24,7 @@
 import junit.framework.AssertionFailedError;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 
 /**
@@ -89,10 +90,8 @@
   }
 
   /**
-   * Initializes this apparatus (if it hasn't been initialized yet), and creates
-   * a scratch file in the scratch filesystem with the given {@code pathName}
-   * with {@code lines} being its content. The method returns a Path instance
-   * for the scratch file.
+   * Creates a scratch file in the scratch filesystem with the given {@code pathName} with
+   * {@code lines} being its content. The method returns a Path instance for the scratch file.
    */
   public Path file(String pathName, String... lines) throws IOException {
     Path file = path(pathName);
@@ -110,6 +109,18 @@
   }
 
   /**
+   * Creates or recreates a scratch file just like {@link #file} but tolerating an existing file.
+   */
+  public Path overwriteFile(String pathName, String... lines) throws IOException {
+    try {
+      path(pathName).delete();
+    } catch (FileNotFoundException e) {
+      // Ignored.
+    }
+    return file(pathName, lines);
+  }
+
+  /**
    * Initializes this apparatus (if it hasn't been initialized yet), and creates
    * a directory in the scratch filesystem, with the given {@code pathName}.
    * Creates parent directories as necessary.