Centralize control over the StarlarkThread in RuleContext

This lifts management of the StarlarkThread during rule analysis from
StarlarkRuleConfiguredTargetUtil to RuleContext. This allows the
StarlarkThread to be accessed in native rules that may wish to call
into Starlark code using the @_builtins mechanism.

The mutability is frozen at the end of the target's analysis, so multiple top-level calls to @_builtins-defined Starlark code within the same rule will share the same unfrozen mutability.

A follow-up will centralize management of the StarlarkRuleContext object as well.

Work toward #11437.

PiperOrigin-RevId: 348099327
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index 8c8251b..07de174 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -63,11 +63,13 @@
 import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
 import com.google.devtools.build.lib.packages.Aspect;
 import com.google.devtools.build.lib.packages.AspectDescriptor;
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.BazelStarlarkContext;
 import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.packages.BuiltinProvider;
 import com.google.devtools.build.lib.packages.ConfigurationFragmentPolicy;
@@ -112,7 +114,9 @@
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import net.starlark.java.eval.EvalException;
+import net.starlark.java.eval.Mutability;
 import net.starlark.java.eval.StarlarkSemantics;
+import net.starlark.java.eval.StarlarkThread;
 import net.starlark.java.syntax.Location;
 
 /**
@@ -126,7 +130,7 @@
  * @see com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory
  */
 public final class RuleContext extends TargetContext
-    implements ActionConstructionContext, ActionRegistry, RuleErrorConsumer {
+    implements ActionConstructionContext, ActionRegistry, RuleErrorConsumer, AutoCloseable {
 
   public boolean isAllowTagsPropagation() throws InterruptedException {
     return this.getAnalysisEnvironment()
@@ -220,6 +224,13 @@
   /* lazily computed cache for Make variables, computed from the above. See get... method */
   private transient ConfigurationMakeVariableContext configurationMakeVariableContext = null;
 
+  /**
+   * Thread used for any Starlark evaluation during analysis, e.g. rule implementation function for
+   * a Starlark-defined rule, or Starlarkified helper logic for native rules that have been
+   * partially migrated to {@code @_builtins}.
+   */
+  private final StarlarkThread starlarkThread;
+
   private RuleContext(
       Builder builder,
       AttributeMap attributes,
@@ -232,7 +243,10 @@
       ImmutableMap<String, Attribute> aspectAttributes,
       @Nullable ToolchainCollection<ResolvedToolchainContext> toolchainContexts,
       ConstraintSemantics<RuleContext> constraintSemantics,
-      ImmutableSet<String> requiredConfigFragments)
+      ImmutableSet<String> requiredConfigFragments,
+      String toolsRepository,
+      StarlarkSemantics starlarkSemantics,
+      Mutability mutability)
       throws InvalidExecGroupException {
     super(
         builder.env,
@@ -267,6 +281,8 @@
     this.execProperties = parseExecProperties(builder.rawExecProperties);
     this.constraintSemantics = constraintSemantics;
     this.requiredConfigFragments = requiredConfigFragments;
+    this.starlarkThread =
+        createStarlarkThread(toolsRepository, starlarkSemantics, mutability); // uses above state
   }
 
   private void getAllFeatures(Set<String> allEnabledFeatures, Set<String> allDisabledFeatures) {
@@ -1199,6 +1215,37 @@
     return configurationMakeVariableContext;
   }
 
+  private StarlarkThread createStarlarkThread(
+      String toolsRepository, StarlarkSemantics starlarkSemantics, Mutability mutability) {
+    AnalysisEnvironment env = getAnalysisEnvironment();
+    StarlarkThread thread = new StarlarkThread(mutability, starlarkSemantics);
+    thread.setPrintHandler(Event.makeDebugPrintHandler(env.getEventHandler()));
+    new BazelStarlarkContext(
+            BazelStarlarkContext.Phase.ANALYSIS,
+            toolsRepository,
+            /*fragmentNameToClass=*/ null,
+            getTarget().getPackage().getRepositoryMapping(),
+            /*convertedLabelsInPackage=*/ new HashMap<>(),
+            getSymbolGenerator(),
+            getLabel())
+        .storeInThread(thread);
+    return thread;
+  }
+
+  public StarlarkThread getStarlarkThread() {
+    return starlarkThread;
+  }
+
+  /**
+   * Prepares Starlark objects created during this target's analysis for use by others. Freezes
+   * mutability, clears expensive references.
+   */
+  @Override
+  public void close() {
+    starlarkThread.mutability().freeze();
+    // TODO(#11437): Once we own the StarlarkRuleContext in this class, nullify() it here as well.
+  }
+
   @Nullable
   public ResolvedToolchainContext getToolchainContext() {
     return toolchainContexts == null ? null : toolchainContexts.getDefaultToolchainContext();
@@ -1698,10 +1745,10 @@
     return "RuleContext(" + getLabel() + ", " + getConfiguration() + ")";
   }
 
-  /**
-   * Builder class for a RuleContext.
-   */
-  public static final class Builder implements RuleErrorConsumer  {
+  /** Builder class for a RuleContext. */
+  // TODO(bazel-team): I get the feeling we could delete much of the boilerplate by replacing some
+  // of these fields with a RuleClassProvider -- both in the builder and in the RuleContext itself.
+  public static final class Builder implements RuleErrorConsumer {
     private final AnalysisEnvironment env;
     private final Target target;
     private final ConfigurationFragmentPolicy configurationFragmentPolicy;
@@ -1713,6 +1760,9 @@
     private final RuleErrorConsumer reporter;
     private OrderedSetMultimap<Attribute, ConfiguredTargetAndData> prerequisiteMap;
     private ImmutableMap<Label, ConfigMatchingProvider> configConditions = ImmutableMap.of();
+    private String toolsRepository;
+    private StarlarkSemantics starlarkSemantics;
+    private Mutability mutability;
     private NestedSet<PackageGroupContents> visibility;
     private ImmutableMap<String, Attribute> aspectAttributes;
     private ImmutableList<Aspect> aspects;
@@ -1752,6 +1802,9 @@
     public RuleContext build() throws InvalidExecGroupException {
       Preconditions.checkNotNull(prerequisiteMap);
       Preconditions.checkNotNull(configConditions);
+      Preconditions.checkNotNull(toolsRepository);
+      Preconditions.checkNotNull(starlarkSemantics);
+      Preconditions.checkNotNull(mutability);
       Preconditions.checkNotNull(visibility);
       Preconditions.checkNotNull(constraintSemantics);
       AttributeMap attributes =
@@ -1780,7 +1833,10 @@
           aspectAttributes != null ? aspectAttributes : ImmutableMap.<String, Attribute>of(),
           toolchainContexts,
           constraintSemantics,
-          requiredConfigFragments);
+          requiredConfigFragments,
+          toolsRepository,
+          starlarkSemantics,
+          mutability);
     }
 
     private void checkAttributesNonEmpty(AttributeMap attributes) {
@@ -1804,6 +1860,23 @@
       }
     }
 
+    // TODO(bazel-team): This field is only used by BazelStarlarkContext. Investigate whether that's
+    // even needed in the analysis phase, and delete it if not.
+    public Builder setToolsRepository(String toolsRepository) {
+      this.toolsRepository = toolsRepository;
+      return this;
+    }
+
+    public Builder setStarlarkSemantics(StarlarkSemantics starlarkSemantics) {
+      this.starlarkSemantics = starlarkSemantics;
+      return this;
+    }
+
+    public Builder setMutability(Mutability mutability) {
+      this.mutability = mutability;
+      return this;
+    }
+
     public Builder setVisibility(NestedSet<PackageGroupContents> visibility) {
       this.visibility = visibility;
       return this;