Allow passing a minimal version to use for skyframe evaluations.

Allows the evaluator to specify a version compatible with its versioning scheme. This minimal version is used for the MTSV of hermetic nodes with no non-hermetic dependencies (e.g. `FileSymlinkCycleUniquenessFunction`) instead of the previously used `null`. This way it can be distinguished from a transient error which also uses `null`.

`InMemoryNodeEntry` no longer considers the MTSV, since no evaluator implementations depend on that behavior.

PiperOrigin-RevId: 445933706
diff --git a/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java
index c586075..385bd2b 100644
--- a/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java
@@ -125,6 +125,7 @@
   AbstractParallelEvaluator(
       ProcessableGraph graph,
       Version graphVersion,
+      Version minimalVersion,
       ImmutableMap<SkyFunctionName, SkyFunction> skyFunctions,
       ExtendedEventHandler reporter,
       EmittedEventState emittedEventState,
@@ -146,6 +147,7 @@
         new ParallelEvaluatorContext(
             graph,
             graphVersion,
+            minimalVersion,
             skyFunctions,
             reporter,
             emittedEventState,
@@ -161,7 +163,7 @@
             stateCache);
   }
 
-  private Supplier<QuiescingExecutor> getQuiescingExecutorSupplier(
+  private static Supplier<QuiescingExecutor> getQuiescingExecutorSupplier(
       Supplier<ExecutorService> executorService,
       int cpuHeavySkyKeysThreadPoolSize,
       int executionJobsThreadPoolSize) {
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
index 5b3031e..92e71926 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
@@ -186,6 +186,7 @@
             new ParallelEvaluator(
                 graph,
                 graphVersion,
+                MinimalVersion.INSTANCE,
                 skyFunctions,
                 evaluationContext.getEventHandler(),
                 emittedEventState,
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
index ad0be18..17c5d0d 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
@@ -13,8 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.skyframe;
 
-import static com.google.common.base.MoreObjects.firstNonNull;
-
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -276,14 +274,14 @@
       SkyValue value, Version graphVersion, @Nullable Version maxTransitiveSourceVersion)
       throws InterruptedException {
     Preconditions.checkState(isReady(), "Not ready (this=%s, value=%s)", this, value);
-    Version version = firstNonNull(maxTransitiveSourceVersion, graphVersion);
     Preconditions.checkState(
-        this.lastChangedVersion.atMost(version) && this.lastEvaluatedVersion.atMost(version),
+        this.lastChangedVersion.atMost(graphVersion)
+            && this.lastEvaluatedVersion.atMost(graphVersion),
         "Bad version (this=%s, version=%s, value=%s)",
         this,
-        version,
+        graphVersion,
         value);
-    this.lastEvaluatedVersion = version;
+    this.lastEvaluatedVersion = graphVersion;
 
     if (dirtyBuildingState.unchangedFromLastBuild(value)) {
       // If the value is the same as before, just use the old value. Note that we don't use the new
@@ -292,7 +290,7 @@
     } else {
       // If this is a new value, or it has changed since the last build, set the version to the
       // current graph version.
-      this.lastChangedVersion = version;
+      this.lastChangedVersion = graphVersion;
       this.value = value;
     }
     return setStateFinishedAndReturnReverseDepsToSignal();
diff --git a/src/main/java/com/google/devtools/build/skyframe/ParallelEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/ParallelEvaluator.java
index e554dde..250b9c2 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ParallelEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ParallelEvaluator.java
@@ -61,8 +61,9 @@
   public ParallelEvaluator(
       ProcessableGraph graph,
       Version graphVersion,
+      Version minimalVersion,
       ImmutableMap<SkyFunctionName, SkyFunction> skyFunctions,
-      final ExtendedEventHandler reporter,
+      ExtendedEventHandler reporter,
       EmittedEventState emittedEventState,
       EventFilter storedEventFilter,
       ErrorInfoManager errorInfoManager,
@@ -77,6 +78,7 @@
     super(
         graph,
         graphVersion,
+        minimalVersion,
         skyFunctions,
         reporter,
         emittedEventState,
@@ -507,7 +509,7 @@
    * <p>{@code visitor} may be null, but only in the case where all graph entries corresponding to
    * {@code skyKeys} are known to be in the DONE state ({@code entry.isDone()} returns true).
    */
-  <T extends SkyValue> EvaluationResult<T> constructResult(
+  private <T extends SkyValue> EvaluationResult<T> constructResult(
       Iterable<SkyKey> skyKeys,
       @Nullable Map<SkyKey, ValueWithMetadata> bubbleErrorInfo,
       boolean catastrophe)
diff --git a/src/main/java/com/google/devtools/build/skyframe/ParallelEvaluatorContext.java b/src/main/java/com/google/devtools/build/skyframe/ParallelEvaluatorContext.java
index fb7311d..2fa6ea2 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ParallelEvaluatorContext.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ParallelEvaluatorContext.java
@@ -39,6 +39,7 @@
 
   private final QueryableGraph graph;
   private final Version graphVersion;
+  private final Version minimalVersion;
   private final ImmutableMap<SkyFunctionName, SkyFunction> skyFunctions;
   private final ExtendedEventHandler reporter;
   private final NestedSetVisitor<TaggedEvents> replayingNestedSetEventVisitor;
@@ -74,6 +75,7 @@
   public ParallelEvaluatorContext(
       QueryableGraph graph,
       Version graphVersion,
+      Version minimalVersion,
       ImmutableMap<SkyFunctionName, SkyFunction> skyFunctions,
       ExtendedEventHandler reporter,
       EmittedEventState emittedEventState,
@@ -87,6 +89,7 @@
       Cache<SkyKey, SkyKeyComputeState> stateCache) {
     this.graph = graph;
     this.graphVersion = graphVersion;
+    this.minimalVersion = minimalVersion;
     this.skyFunctions = skyFunctions;
     this.reporter = reporter;
     this.graphInconsistencyReceiver = graphInconsistencyReceiver;
@@ -151,6 +154,10 @@
     return graphVersion;
   }
 
+  Version getMinimalVersion() {
+    return minimalVersion;
+  }
+
   boolean keepGoing() {
     return keepGoing;
   }
diff --git a/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java b/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java
index f941c6e..2a3fe35 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java
@@ -414,7 +414,21 @@
      * Returns the max transitive source version of a {@link NodeEntry}.
      *
      * <p>This value might not consider all deps' source versions if called before all deps have
-     * been requested or if {@link #valuesMissing()} returns true.
+     * been requested or if {@link #valuesMissing} returns {@code true}.
+     *
+     * <p>Rules for calculation of the max transitive source version:
+     *
+     * <ul>
+     *   <li>Returns {@code null} during cycle detection and error bubbling, or for transient
+     *       errors.
+     *   <li>If the node is {@link FunctionHermeticity#NONHERMETIC}, returns the version passed to
+     *       {@link #injectVersionForNonHermeticFunction} if it was called, or else {@code null}.
+     *   <li>For all other nodes, queries {@link NodeEntry#getMaxTransitiveSourceVersion} of direct
+     *       dependency nodes and chooses the maximal version seen (according to {@link
+     *       Version#atMost}). If there are no direct dependencies, returns {@link
+     *       ParallelEvaluatorContext#getMinimalVersion}. If any direct dependency node has a {@code
+     *       null} MTSV, returns {@code null}.
+     * </ul>
      */
     @Nullable
     Version getMaxTransitiveSourceVersionSoFar();
diff --git a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
index 5c463a0..967166a 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
@@ -207,7 +207,7 @@
     this.maxTransitiveSourceVersion =
         bubbleErrorInfo == null
                 && skyKey.functionName().getHermeticity() != FunctionHermeticity.NONHERMETIC
-            ? MinimalVersion.INSTANCE
+            ? evaluatorContext.getMinimalVersion()
             : null;
     this.previouslyRequestedDepsValues = batchPrefetch(throwIfPreviouslyRequestedDepsUndone);
     Preconditions.checkState(
@@ -847,10 +847,6 @@
       }
     }
 
-    if (temporaryDirectDeps.isEmpty()
-        && skyKey.functionName().getHermeticity() != FunctionHermeticity.NONHERMETIC) {
-      maxTransitiveSourceVersion = null; // No dependencies on source.
-    }
     Preconditions.checkState(
         maxTransitiveSourceVersion == null || newlyRegisteredDeps.isEmpty(),
         "Dependency registration not supported when tracking max transitive source versions");
diff --git a/src/test/java/com/google/devtools/build/skyframe/EagerInvalidatorTest.java b/src/test/java/com/google/devtools/build/skyframe/EagerInvalidatorTest.java
index 8491cdb..7618b07 100644
--- a/src/test/java/com/google/devtools/build/skyframe/EagerInvalidatorTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/EagerInvalidatorTest.java
@@ -137,6 +137,7 @@
         new ParallelEvaluator(
             graph,
             graphVersion,
+            MinimalVersion.INSTANCE,
             tester.getSkyFunctionMap(),
             reporter,
             new MemoizingEvaluator.EmittedEventState(),
diff --git a/src/test/java/com/google/devtools/build/skyframe/ParallelEvaluatorTest.java b/src/test/java/com/google/devtools/build/skyframe/ParallelEvaluatorTest.java
index c729c63..b855156 100644
--- a/src/test/java/com/google/devtools/build/skyframe/ParallelEvaluatorTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/ParallelEvaluatorTest.java
@@ -120,6 +120,7 @@
     return new ParallelEvaluator(
         graph,
         oldGraphVersion,
+        MinimalVersion.INSTANCE,
         builders,
         storedEventHandler,
         new MemoizingEvaluator.EmittedEventState(),
@@ -3118,6 +3119,7 @@
         new ParallelEvaluator(
             graph,
             graphVersion,
+            MinimalVersion.INSTANCE,
             tester.getSkyFunctionMap(),
             storedEventHandler,
             new MemoizingEvaluator.EmittedEventState(),