add on_leave_scope to BuildOptions. This is pretty much the same with the StarlarkOptions map in BuildOptions.

PiperOrigin-RevId: 846001362
Change-Id: If804a47ffcac73063eee766849c3573c478d8ff6
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
index 067cdae..458d49a 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
@@ -116,6 +116,7 @@
         .addStarlarkOptions(labelizeStarlarkOptions(provider.getStarlarkOptions()))
         .addScopeTypeMap(
             convertScopesAttributes(provider.getScopesAttributes(), provider.getStarlarkOptions()))
+        .addOnLeaveScopeValues(labelizeStarlarkOptions(provider.getOnLeaveScopeValues()))
         .build();
   }
 
@@ -189,6 +190,7 @@
             }
             fingerprint.addString(OptionsBase.mapToCacheKey(starlarkOptionsMap));
             fingerprint.addString(OptionsBase.mapToCacheKey(scopes));
+            fingerprint.addString(OptionsBase.mapToCacheKey(onLeaveScopeValuesMap));
             checksum = fingerprint.hexDigestAndReset();
           }
         }
@@ -217,6 +219,7 @@
         .add("fragmentOptions", fragmentOptionsMap.values())
         .add("starlarkOptions", starlarkOptionsMap)
         .add("scopes", scopes)
+        .add("onLeaveScopeValues", onLeaveScopeValuesMap)
         .toString();
   }
 
@@ -249,6 +252,11 @@
     return scopes;
   }
 
+  /** Starlark on-leave scope values, sorted lexicographically by name. */
+  public ImmutableMap<Label, Object> getOnLeaveScopeValues() {
+    return onLeaveScopeValuesMap;
+  }
+
   /**
    * Creates a copy of the BuildOptions object that contains copies of the FragmentOptions and
    * Starlark options.
@@ -265,7 +273,8 @@
     // Note that this assumes that starlark option values are immutable.
     ImmutableMap<Label, Object> starlarkOptions = ImmutableMap.copyOf(starlarkOptionsMap);
     ImmutableMap<Label, Scope.ScopeType> scopes = this.scopes;
-    return new BuildOptions(nativeOptions, starlarkOptions, scopes);
+    ImmutableMap<Label, Object> onLeaveScopeValues = ImmutableMap.copyOf(onLeaveScopeValuesMap);
+    return new BuildOptions(nativeOptions, starlarkOptions, scopes, onLeaveScopeValues);
   }
 
   @Override
@@ -299,6 +308,9 @@
   /** Maps Starlark options names to {@link Scope} information */
   private final ImmutableMap<Label, Scope.ScopeType> scopes;
 
+  /** Maps Starlark options names to their on-leave scope values. */
+  private final ImmutableMap<Label, Object> onLeaveScopeValuesMap;
+
   // Lazily initialized both for performance and correctness - BuildOptions instances may be mutated
   // after construction but before consumption. Access via checksum() to ensure initialization. This
   // field is volatile as per https://errorprone.info/bugpattern/DoubleCheckedLocking, which
@@ -308,10 +320,12 @@
   private BuildOptions(
       ImmutableMap<Class<? extends FragmentOptions>, FragmentOptions> fragmentOptionsMap,
       ImmutableMap<Label, Object> starlarkOptionsMap,
-      ImmutableMap<Label, Scope.ScopeType> scopes) {
+      ImmutableMap<Label, Scope.ScopeType> scopes,
+      ImmutableMap<Label, Object> onLeaveScopeValuesMap) {
     this.fragmentOptionsMap = fragmentOptionsMap;
     this.starlarkOptionsMap = starlarkOptionsMap;
     this.scopes = scopes;
+    this.onLeaveScopeValuesMap = onLeaveScopeValuesMap;
   }
 
   /**
@@ -391,6 +405,7 @@
       }
       this.addStarlarkOptions(options.getStarlarkOptions());
       this.addScopeTypeMap(options.getScopeTypeMap());
+      this.addOnLeaveScopeValues(options.getOnLeaveScopeValues());
       return this;
     }
 
@@ -469,6 +484,29 @@
     public Builder removeStarlarkOption(Label key) {
       starlarkOptions.remove(key);
       removeScope(key);
+      onLeaveScopeValues.remove(key);
+      return this;
+    }
+
+    /**
+     * Adds multiple Starlark on-leave scope values to the builder. Overrides previous instances of
+     * the same key.
+     */
+    @CanIgnoreReturnValue
+    public Builder addOnLeaveScopeValues(Map<Label, Object> options) {
+      onLeaveScopeValues.putAll(options);
+      return this;
+    }
+
+    @CanIgnoreReturnValue
+    public Builder addOnLeaveScopeValue(Label key, Object value) {
+      onLeaveScopeValues.put(key, value);
+      return this;
+    }
+
+    @CanIgnoreReturnValue
+    public Builder removeOnLeaveScopeValue(Label key) {
+      onLeaveScopeValues.remove(key);
       return this;
     }
 
@@ -476,7 +514,8 @@
       return new BuildOptions(
           sortedImmutableHashMap(fragmentOptions, LEXICAL_FRAGMENT_OPTIONS_COMPARATOR),
           sortedImmutableHashMap(starlarkOptions, naturalOrder()),
-          sortedImmutableHashMap(scopes, naturalOrder()));
+          sortedImmutableHashMap(scopes, naturalOrder()),
+          sortedImmutableHashMap(onLeaveScopeValues, naturalOrder()));
     }
 
     /**
@@ -500,6 +539,7 @@
 
     // TODO: b/377559852 - Merge scopes into starlarkOptionsMap
     private final LinkedHashMap<Label, Scope.ScopeType> scopes = new LinkedHashMap<>();
+    private final LinkedHashMap<Label, Object> onLeaveScopeValues = new LinkedHashMap<>();
 
     private Builder() {}
   }
@@ -534,6 +574,7 @@
       context.putSharedValue(options.fragmentOptionsMap, null, IMMUTABLE_MAP_CODEC, codedOut);
       context.putSharedValue(options.starlarkOptionsMap, null, IMMUTABLE_MAP_CODEC, codedOut);
       context.putSharedValue(options.scopes, null, IMMUTABLE_MAP_CODEC, codedOut);
+      context.putSharedValue(options.onLeaveScopeValuesMap, null, IMMUTABLE_MAP_CODEC, codedOut);
     }
 
     @Override
@@ -555,6 +596,12 @@
           DeserializationBuilder::setStarlarkOptionsMap);
       context.getSharedValue(
           codedIn, null, IMMUTABLE_MAP_CODEC, builder, DeserializationBuilder::setScopes);
+      context.getSharedValue(
+          codedIn,
+          null,
+          IMMUTABLE_MAP_CODEC,
+          builder,
+          DeserializationBuilder::setOnLeaveScopeValuesMap);
       return builder;
     }
 
@@ -565,10 +612,12 @@
       ImmutableMap<Label, Object> starlarkOptionsMap;
       // TODO: b/377559852 - Merge scopes into starlarkOptionsMap
       ImmutableMap<Label, Scope.ScopeType> scopes;
+      ImmutableMap<Label, Object> onLeaveScopeValuesMap;
 
       @Override
       public BuildOptions call() {
-        return new BuildOptions(fragmentOptionsMap, starlarkOptionsMap, scopes);
+        return new BuildOptions(
+            fragmentOptionsMap, starlarkOptionsMap, scopes, onLeaveScopeValuesMap);
       }
 
       @SuppressWarnings("unchecked")
@@ -586,6 +635,11 @@
       private static void setScopes(DeserializationBuilder builder, Object value) {
         builder.scopes = (ImmutableMap<Label, Scope.ScopeType>) value;
       }
+
+      @SuppressWarnings("unchecked")
+      private static void setOnLeaveScopeValuesMap(DeserializationBuilder builder, Object value) {
+        builder.onLeaveScopeValuesMap = (ImmutableMap<Label, Object>) value;
+      }
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
index c43f79d..d75e296 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
@@ -185,6 +185,7 @@
   private final LoadingCache<Class<? extends OptionsBase>, Optional<OptionsBase>> optionsCache;
   private final Map<String, Object> starlarkOptions;
   private final Map<String, String> scopesAttributes;
+  private final Map<String, Object> onLeaveScopeValues;
 
   /** A human-readable description of all the non-default option settings. */
   private final String optionsDescription;
@@ -237,6 +238,7 @@
                 });
     this.starlarkOptions = options.getStarlarkOptions();
     this.scopesAttributes = options.getScopesAttributes();
+    this.onLeaveScopeValues = options.getOnLeaveScopeValues();
     this.needsInstrumentationFilter = needsInstrumentationFilter;
     this.runTests = runTests;
     this.checkForActionConflicts = checkForActionConflicts;
@@ -278,6 +280,11 @@
   }
 
   @Override
+  public Map<String, Object> getOnLeaveScopeValues() {
+    return onLeaveScopeValues;
+  }
+
+  @Override
   public Map<String, Object> getExplicitStarlarkOptions(
       Predicate<? super ParsedOptionDescription> filter) {
     throw new UnsupportedOperationException("No known callers to this implementation");
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java b/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
index c68fdb8..0f6ddcc 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
@@ -114,6 +114,9 @@
   // Map of starlark options to their {@link Scope.ScopeType}.
   private final Map<String, String> scopes = new TreeMap<>();
 
+  // Map of starlark options to their on-leave scope values.
+  private final Map<String, Object> onLeaveScopeValues = new TreeMap<>();
+
   // Map of parsed starlark options to their loaded BuildSetting objects (used for canonicalization)
   private final Map<String, BuildSetting> parsedBuildSettings = new LinkedHashMap<>();
 
@@ -226,6 +229,7 @@
 
     Map<String, Object> parsedOptions = new HashMap<>();
     Map<String, String> scopeTypeMap = new HashMap<>();
+    Map<String, Object> onLeaveScopeMap = new HashMap<>();
     for (String buildSetting : buildSettingWithTargetAndValue.keySet()) {
       Pair<Target, Object> buildSettingAndFinalValue =
           buildSettingWithTargetAndValue.get(buildSetting);
@@ -272,11 +276,19 @@
       }
       scopeTypeMap.put(buildSetting, scopeType);
       nativeOptionsParser.setScopesAttributes(ImmutableMap.copyOf(scopeTypeMap));
+
+      if (attrMap.isAttributeValueExplicitlySpecified("on_leave_scope")) {
+        var onLeaveScopeValue = attrMap.get("on_leave_scope", buildSettingObject.getType());
+        if (onLeaveScopeValue != null) {
+          onLeaveScopeMap.put(buildSetting, onLeaveScopeValue);
+        }
+      }
     }
 
     nativeOptionsParser.setStarlarkOptions(ImmutableMap.copyOf(parsedOptions));
     this.starlarkOptions.putAll(parsedOptions);
     this.scopes.putAll(scopeTypeMap);
+    this.onLeaveScopeValues.putAll(onLeaveScopeMap);
     return true;
   }
 
@@ -432,6 +444,10 @@
     return ImmutableMap.copyOf(this.buildSettingDefaults);
   }
 
+  public ImmutableMap<String, Object> getOnLeaveScopeValues() {
+    return ImmutableMap.copyOf(this.onLeaveScopeValues);
+  }
+
   public boolean checkIfParsedOptionAllowsMultiple(String option) {
     BuildSetting setting = parsedBuildSettings.get(option);
     return setting.allowsMultiple() || setting.isRepeatableFlag();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BuildOptionsScopeFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/BuildOptionsScopeFunction.java
index ba558ce..44dd9d2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BuildOptionsScopeFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BuildOptionsScopeFunction.java
@@ -64,18 +64,25 @@
     LinkedHashMap<Label, Scope> scopes = new LinkedHashMap<>();
     for (Label scopedFlag : key.getFlagsWithIncompleteScopeInfo()) {
       Scope.ScopeType scopeType = key.getBuildOptions().getScopeTypeMap().get(scopedFlag);
+      Object onLeaveScopeValue = key.getBuildOptions().getOnLeaveScopeValues().get(scopedFlag);
       if (scopeType == null) {
-        scopeType = getScopeType(env, scopedFlag, scopedFlag.getPackageIdentifier());
-        if (scopeType == null) {
+        Target target = getTarget(env, scopedFlag, scopedFlag.getPackageIdentifier());
+        if (target == null) {
           return null;
         }
+        scopeType = getScopeType(target);
+        onLeaveScopeValue = getOnleaveScopeValue(target);
       }
       scopes.put(scopedFlag, new Scope(scopeType, null));
 
       // this is needed because the final BuildOptions used to create the BuildConfigurationKey
       // needs to have the scopeType set for all starlark flags.
       fullyResolvedBuildOptionsBuilder =
-          fullyResolvedBuildOptionsBuilder.addScopeType(scopedFlag, scopeType);
+          onLeaveScopeValue != null
+              ? fullyResolvedBuildOptionsBuilder
+                  .addScopeType(scopedFlag, scopeType)
+                  .addOnLeaveScopeValue(scopedFlag, onLeaveScopeValue)
+              : fullyResolvedBuildOptionsBuilder.addScopeType(scopedFlag, scopeType);
     }
 
     // get PROJECT.scl files for each scoped flag that is not universal
@@ -160,9 +167,7 @@
     return projectFiles.build();
   }
 
-  @Nullable
-  private Scope.ScopeType getScopeType(
-      Environment env, Label label, PackageIdentifier packageIdentifier)
+  private Target getTarget(Environment env, Label label, PackageIdentifier packageIdentifier)
       throws BuildOptionsScopeFunctionException, InterruptedException {
     PackageContext packageContext = PackageContext.of(packageIdentifier, RepositoryMapping.EMPTY);
     SkyframeTargetLoader targetLoader = new SkyframeTargetLoader(env, packageContext);
@@ -178,6 +183,11 @@
       return null;
     }
 
+    return target;
+  }
+
+  private Scope.ScopeType getScopeType(Target target) {
+
     var attrs = RawAttributeMapper.of(target.getAssociatedRule());
     if (!attrs.has("scope", Type.STRING)
         // TODO: https://github.com/bazelbuild/bazel/issues/26909 - Honor the rule's actual
@@ -189,6 +199,19 @@
     return new Scope.ScopeType(attrs.get("scope", Type.STRING));
   }
 
+  @Nullable
+  private Object getOnleaveScopeValue(Target target) {
+    var attrs = RawAttributeMapper.of(target.getAssociatedRule());
+    if (!attrs.has("on_leave_scope")) {
+      // do nothing if on_leave_scope is not set.
+      return null;
+    }
+
+    return attrs.get(
+        "on_leave_scope",
+        target.getAssociatedRule().getRuleClassObject().getBuildSetting().getType());
+  }
+
   /**
    * Same as {@link ParsedFlagsFunction.SkyframeTargetLoader} but forking it here to avoid circular
    * dependencies.
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 f4ba7d5..89bd852 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
@@ -3620,6 +3620,11 @@
           }
 
           @Override
+          public ImmutableMap<String, Object> getOnLeaveScopeValues() {
+            return ImmutableMap.of();
+          }
+
+          @Override
           public ImmutableMap<String, Object> getExplicitStarlarkOptions(
               java.util.function.Predicate<? super ParsedOptionDescription> filter) {
             return ImmutableMap.of();
diff --git a/src/main/java/com/google/devtools/build/lib/testing/common/FakeOptions.java b/src/main/java/com/google/devtools/build/lib/testing/common/FakeOptions.java
index 043ad5e..982ea03 100644
--- a/src/main/java/com/google/devtools/build/lib/testing/common/FakeOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/testing/common/FakeOptions.java
@@ -115,6 +115,11 @@
   }
 
   @Override
+  public ImmutableMap<String, Object> getOnLeaveScopeValues() {
+    return ImmutableMap.of();
+  }
+
+  @Override
   public Map<String, Object> getExplicitStarlarkOptions(
       Predicate<? super ParsedOptionDescription> filter) {
     return ImmutableMap.of();
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParser.java b/src/main/java/com/google/devtools/common/options/OptionsParser.java
index 87298c3..59dff45 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParser.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParser.java
@@ -285,6 +285,7 @@
   private ImmutableSortedMap<String, Object> starlarkOptions = ImmutableSortedMap.of();
   // scopes for starlark options
   private ImmutableSortedMap<String, String> scopesAttributes = ImmutableSortedMap.of();
+  private final ImmutableSortedMap<String, Object> onLeaveScopeValues = ImmutableSortedMap.of();
   private final Map<String, String> aliases = new HashMap<>();
   private boolean success = true;
 
@@ -309,6 +310,11 @@
   }
 
   @Override
+  public ImmutableMap<String, Object> getOnLeaveScopeValues() {
+    return onLeaveScopeValues;
+  }
+
+  @Override
   public ImmutableSortedMap<String, Object> getExplicitStarlarkOptions(
       Predicate<? super ParsedOptionDescription> filter) {
     ImmutableSet<String> explicitOptions =
diff --git a/src/main/java/com/google/devtools/common/options/OptionsProvider.java b/src/main/java/com/google/devtools/common/options/OptionsProvider.java
index 09420b9..3fcb2ac 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsProvider.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsProvider.java
@@ -42,6 +42,11 @@
         }
 
         @Override
+        public ImmutableMap<String, Object> getOnLeaveScopeValues() {
+          return ImmutableMap.of();
+        }
+
+        @Override
         public ImmutableMap<String, Object> getExplicitStarlarkOptions(
             Predicate<? super ParsedOptionDescription> filter) {
           return ImmutableMap.of();
@@ -87,4 +92,6 @@
   ImmutableMap<String, String> getUserOptions();
 
   Map<String, String> getScopesAttributes();
+
+  Map<String, Object> getOnLeaveScopeValues();
 }