// Copyright 2014 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.packages;

import static com.google.devtools.build.lib.packages.Attribute.attr;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
import static com.google.devtools.build.lib.packages.Type.STRING_NO_INTERN;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement;
import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory.TransitionType;
import com.google.devtools.build.lib.analysis.platform.PlatformConstants;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.packages.Attribute.StarlarkComputedDefaultTemplate.CannotPrecomputeDefaultsException;
import com.google.devtools.build.lib.packages.ConfigurationFragmentPolicy.MissingFragmentPolicy;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.packages.RuleFactory.AttributeValues;
import com.google.devtools.build.lib.packages.Type.LabelClass;
import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import com.google.devtools.build.lib.starlarkbuildapi.StarlarkSubruleApi;
import com.google.devtools.build.lib.util.HashCodes;
import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.FormatMethod;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkCallable;
import net.starlark.java.eval.StarlarkFunction;
import net.starlark.java.eval.StarlarkThread;

/**
 * Instances of RuleClass encapsulate the set of attributes of a given "class" of rule, such as
 * <code>cc_binary</code>.
 *
 * <p>This is an instance of the "meta-class" pattern for Rules: we achieve using <i>values</i> what
 * subclasses achieve using <i>types</i>. (The "Design Patterns" book doesn't include this pattern,
 * so think of it as something like a cross between a Flyweight and a State pattern. Like Flyweight,
 * we avoid repeatedly storing data that belongs to many instances. Like State, we delegate from
 * Rule to RuleClass for the specific behavior of that rule (though unlike state, a Rule object
 * never changes its RuleClass). This avoids the need to declare one Java class per class of Rule,
 * yet achieves the same behavior.)
 *
 * <p>The use of a metaclass also allows us to compute a mapping from Attributes to small integers
 * and share this between all rules of the same metaclass. This means we can save the attribute
 * dictionary for each rule instance using an array, which is much more compact than a hashtable.
 *
 * <p>Rule classes whose names start with "$" are considered "abstract"; since they are not valid
 * identifiers, they cannot be named in the build language. However, they are useful for grouping
 * related attributes which are inherited.
 *
 * <p>The exact values in this class are important. In particular:
 *
 * <ul>
 *   <li>Changing an attribute from MANDATORY to OPTIONAL creates the potential for null-pointer
 *       exceptions in code that expects a value.
 *   <li>Attributes whose names are preceded by a "$" or a ":" are "hidden", and cannot be redefined
 *       in a BUILD file. They are a useful way of adding a special dependency. By convention,
 *       attributes starting with "$" are implicit dependencies, and those starting with a ":" are
 *       late-bound implicit dependencies, i.e. dependencies that can only be resolved when the
 *       configuration is known.
 *   <li>Attributes should not be introduced into the hierarchy higher then necessary.
 *   <li>The 'deps' and 'data' attributes are treated specially by the code that builds the runfiles
 *       tree. All targets appearing in these attributes appears beneath the ".runfiles" tree; in
 *       addition, "deps" may have rule-specific semantics.
 * </ul>
 *
 * TODO(bazel-team): Consider breaking up this class in more manageable subclasses.
 */
// Non-final only for mocking in tests. Do not subclass!
@Immutable
public class RuleClass implements RuleClassData {

  /** The name attribute, present for all rules at index 0. Also defined for all symbolic macros. */
  public static final Attribute NAME_ATTRIBUTE =
      attr("name", STRING_NO_INTERN)
          .nonconfigurable("All rules have a non-customizable \"name\" attribute")
          .mandatory()
          .build();

  /**
   * Maximum attributes per RuleClass or {@link MacroClass}. Current value was chosen to be high
   * enough to be considered a non-breaking change for reasonable use. It was also chosen to be low
   * enough to give significant headroom before hitting limits imposed by the compact attribute
   * value storage strategy in {@link Rule}.
   */
  public static final int MAX_ATTRIBUTES = 200;

  /**
   * Maximum attribute name length. Chosen to accommodate existing and prevent extreme outliers from
   * forming - extreme values create bloat, both in memory usage and various outputs, including but
   * not limited to, query output.
   */
  private static final int MAX_ATTRIBUTE_NAME_LENGTH = 128;

  @SerializationConstant
  static final Function<? super Rule, Set<String>> NO_OPTION_REFERENCE =
      Functions.constant(ImmutableSet.of());

  public static final PathFragment THIRD_PARTY_PREFIX = PathFragment.create("third_party");
  public static final PathFragment EXPERIMENTAL_PREFIX = PathFragment.create("experimental");

  /** The attribute that declares the set of metadata labels which apply to this target. */
  public static final String APPLICABLE_METADATA_ATTR = "package_metadata";

  public static final String APPLICABLE_METADATA_ATTR_ALT = "applicable_licenses";

  /** The attribute that declares the list of aspect hints that apply to this target. */
  public static final String ASPECT_HINTS_ATTR = "aspect_hints";

  public static final String DEFAULT_TEST_RUNNER_EXEC_GROUP_NAME = "test";
  public static final DeclaredExecGroup DEFAULT_TEST_RUNNER_EXEC_GROUP =
      DeclaredExecGroup.builder()
          .addToolchainType(
              ToolchainTypeRequirement.create(PlatformConstants.DEFAULT_TEST_TOOLCHAIN_TYPE))
          .build();

  /** Interface for determining whether a rule needs toolchain resolution or not. */
  @FunctionalInterface
  public interface ToolchainResolutionMode extends Serializable {
    boolean useToolchainResolution(Rule rule);

    ToolchainResolutionMode ENABLED = (unused) -> true;
    ToolchainResolutionMode DISABLED = (unused) -> false;
  }

  /** Enum to determine whether a rule class uses auto exec groups. */
  public enum AutoExecGroupsMode {
    /** The rule class does not support auto exec groups. */
    DISABLED,
    /** The rule class uses auto exec groups regardless of other settings in the configuration. */
    ENABLED,
    /**
     * The rule class uses auto exec groups if configured using the {@code _use_auto_exec_groups}
     * attribute and {@code --incompatible_auto_exec_groups} flag.
     */
    DYNAMIC;

    public boolean isEnabled(AttributeMap attributes, boolean isAllowedByConfiguration) {
      return switch (this) {
        case DISABLED -> false;
        case ENABLED -> true;
        case DYNAMIC -> {
          if (attributes.has("$use_auto_exec_groups")) {
            yield attributes.get("$use_auto_exec_groups", Type.BOOLEAN);
          } else {
            yield isAllowedByConfiguration;
          }
        }
      };
    }
  }

  /** A factory or builder class for rule implementations. */
  public interface ConfiguredTargetFactory<
      ConfiguredTargetT, ContextT, ActionConflictExceptionT extends Throwable> {
    /**
     * Returns a fully initialized configured target instance using the given context, or {@code
     * null} on certain rule errors (typically if {@code ruleContext.hasErrors()} becomes {@code
     * true} while trying to create the target).
     *
     * @throws RuleErrorException if configured target creation could not be completed due to rule
     *     errors
     * @throws ActionConflictExceptionT if there were conflicts during action registration
     */
    @Nullable
    ConfiguredTargetT create(ContextT ruleContext)
        throws InterruptedException, RuleErrorException, ActionConflictExceptionT;

    /**
     * Exception indicating that configured target creation could not be completed. General error
     * messaging should be done via {@link
     * com.google.devtools.build.lib.analysis.RuleErrorConsumer}; this exception only interrupts
     * configured target creation in cases where it can no longer continue.
     */
    final class RuleErrorException extends Exception {
      public RuleErrorException() {
        super();
      }

      public RuleErrorException(String message) {
        super(message);
      }

      public RuleErrorException(Throwable cause) {
        super(cause);
      }

      public RuleErrorException(String message, Throwable cause) {
        super(message, cause);
      }
    }
  }

  /**
   * For Bazel's constraint system: the attribute that declares the set of environments a rule
   * supports, overriding the defaults for their respective groups.
   */
  public static final String RESTRICTED_ENVIRONMENT_ATTR = "restricted_to";

  /**
   * For Bazel's constraint system: the attribute that declares the set of environments a rule
   * supports, appending them to the defaults for their respective groups.
   */
  public static final String COMPATIBLE_ENVIRONMENT_ATTR = "compatible_with";

  /**
   * For Bazel's constraint system: the attribute that declares the list of constraints that the
   * target platform must satisfy to be considered compatible.
   */
  public static final String TARGET_COMPATIBLE_WITH_ATTR = "target_compatible_with";

  /**
   * For Bazel's constraint system: the attribute that declares the list of constraints that the
   * default exec group's execution platform must satisfy to be considered compatible.
   */
  public static final String EXEC_COMPATIBLE_WITH_ATTR = "exec_compatible_with";

  /**
   * For Bazel's constraint system: the attribute that declares the list of constraints that the
   * given exec groups' execution platforms must satisfy to be considered compatible.
   */
  public static final String EXEC_GROUP_COMPATIBLE_WITH_ATTR = "exec_group_compatible_with";

  /**
   * The attribute that declares execution properties that should be added to actions created by
   * this target.
   */
  public static final String EXEC_PROPERTIES_ATTR = "exec_properties";

  /**
   * For Bazel's constraint system: the implicit attribute used to store rule class restriction
   * defaults as specified by {@link Builder#restrictedTo}.
   */
  public static final String DEFAULT_RESTRICTED_ENVIRONMENT_ATTR =
      "$" + RESTRICTED_ENVIRONMENT_ATTR;

  /**
   * For Bazel's constraint system: the implicit attribute used to store rule class compatibility
   * defaults as specified by {@link Builder#compatibleWith}.
   */
  public static final String DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR =
      "$" + COMPATIBLE_ENVIRONMENT_ATTR;

  /**
   * Name of the attribute that stores all {@link
   * com.google.devtools.build.lib.rules.config.ConfigRuleClasses} labels this rule references (i.e.
   * select() keys). This is specially populated in {@link #populateRuleAttributeValues}.
   *
   * <p>This isn't technically necessary for builds: select() keys are evaluated in {@link
   * com.google.devtools.build.lib.skyframe.PrerequisiteProducer#computeConfigConditions} instead of
   * normal dependency resolution because they're needed to determine other dependencies. So there's
   * no intrinsic reason why we need an extra attribute to store them.
   *
   * <p>There are four reasons why we still create this attribute:
   *
   * <ol>
   *   <li>Collecting them once in {@link #populateRuleAttributeValues} instead of multiple times in
   *       ConfiguredTargetFunction saves extra looping over the rule's attributes.
   *   <li>Query's dependency resolution has no equivalent of {@link
   *       com.google.devtools.build.lib.skyframe.PrerequisiteProducer#computeConfigConditions} and
   *       we need to make sure its coverage remains complete.
   *   <li>Manual configuration trimming uses the normal dependency resolution process to work
   *       correctly and config_setting keys are subject to this trimming.
   *   <li>{@link Rule#useToolchainResolution(Rule) supports conditional toolchain resolution for
   *      targets with non-empty select()s. This requirement would go away if platform info was
   *      prepared for all rules regardless of toolchain needs.
   * </ol>
   *
   * <p>It should be possible to clean up these issues if we decide we don't want an artificial
   * attribute dependency. But care has to be taken to do that safely.
   */
  public static final String CONFIG_SETTING_DEPS_ATTRIBUTE = "$config_dependencies";

  /**
   * A support class to make it easier to create {@code RuleClass} instances. This class follows the
   * 'fluent builder' pattern.
   *
   * <p>The {@link #addAttribute} method will throw an exception if an attribute of that name
   * already exists. Use {@link #overrideAttribute} in that case.
   */
  public static final class Builder {
    private static final Pattern RULE_NAME_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");

    /** The type of the rule class, which determines valid names and required attributes. */
    public enum RuleClassType {
      /**
       * Abstract rules are intended for rule classes that are just used to factor out common
       * attributes, and for rule classes that are used only internally. These rules cannot be
       * instantiated by a BUILD file.
       *
       * <p>The rule name must contain a '$' and {@link TargetUtils#isTestRuleName} must return
       * false for the name.
       */
      ABSTRACT {
        @Override
        public void checkName(String name) {
          Preconditions.checkArgument(
              (name.contains("$") && !TargetUtils.isTestRuleName(name)) || name.isEmpty());
        }

        @Override
        public void checkAttributes(Map<String, Attribute> attributes) {
          // No required attributes.
        }
      },

      /**
       * Invisible rule classes should contain a dollar sign so that they cannot be instantiated by
       * the user. They are different from abstract rules in that they can be instantiated at will.
       */
      INVISIBLE {
        @Override
        public void checkName(String name) {
          Preconditions.checkArgument(name.contains("$"));
        }

        @Override
        public void checkAttributes(Map<String, Attribute> attributes) {
          // No required attributes.
        }
      },

      /**
       * Normal rules are instantiable by BUILD files, possibly via a macro (symbolic or legacy), in
       * which case the rule's symbol is namespaced under {@code native}. Normal rule names must
       * therefore obey the rules for identifiers in the BUILD language. In addition, {@link
       * TargetUtils#isTestRuleName} must return false for the name.
       */
      NORMAL {
        @Override
        public void checkName(String name) {
          Preconditions.checkArgument(
              !TargetUtils.isTestRuleName(name) && RULE_NAME_PATTERN.matcher(name).matches(),
              "Invalid rule name: %s",
              name);
        }

        @Override
        public void checkAttributes(Map<String, Attribute> attributes) {
          for (Attribute attribute : REQUIRED_ATTRIBUTES_FOR_NORMAL_RULES) {
            Attribute presentAttribute = attributes.get(attribute.getName());
            Preconditions.checkState(
                presentAttribute != null,
                "Missing mandatory '%s' attribute in normal rule class.",
                attribute.getName());
            Preconditions.checkState(
                presentAttribute.getType().equals(attribute.getType()),
                "Mandatory attribute '%s' in normal rule class has incorrect type (expected"
                    + " %s).",
                attribute.getName(),
                attribute.getType());
          }
        }
      },

      /**
       * Normal rules with the additional restriction that they can only be instantiated by BUILD
       * files or legacy macros - but not symbolic macros.
       */
      BUILD_ONLY {
        @Override
        public void checkName(String name) {
          NORMAL.checkName(name);
        }

        @Override
        public void checkAttributes(Map<String, Attribute> attributes) {
          NORMAL.checkAttributes(attributes);
        }
      },

      /**
       * Test rules are instantiable by BUILD files and are handled specially when run with the
       * 'test' command. Their names must obey the rules for identifiers in the BUILD language and
       * {@link TargetUtils#isTestRuleName} must return true for the name.
       *
       * <p>In addition, test rules must contain certain attributes. See {@link
       * Builder#REQUIRED_ATTRIBUTES_FOR_TESTS}.
       */
      TEST {
        @Override
        public void checkName(String name) {
          Preconditions.checkArgument(
              TargetUtils.isTestRuleName(name) && RULE_NAME_PATTERN.matcher(name).matches());
        }

        @Override
        public void checkAttributes(Map<String, Attribute> attributes) {
          for (Attribute attribute : REQUIRED_ATTRIBUTES_FOR_TESTS) {
            Attribute presentAttribute = attributes.get(attribute.getName());
            Preconditions.checkState(
                presentAttribute != null,
                "Missing mandatory '%s' attribute in test rule class.",
                attribute.getName());
            Preconditions.checkState(
                presentAttribute.getType().equals(attribute.getType()),
                "Mandatory attribute '%s' in test rule class has incorrect type (expected %s).",
                attribute.getName(),
                attribute.getType());
          }
        }
      },

      /**
       * Placeholder rules are only instantiated when packages which refer to non-native rule
       * classes are deserialized. At this time, non-native rule classes can't be serialized. To
       * prevent crashes on deserialization, when a package containing a rule with a non-native rule
       * class is deserialized, the rule is assigned a placeholder rule class. This is compatible
       * with our limited set of package serialization use cases.
       *
       * <p>Placeholder rule class names obey the rule for identifiers.
       */
      PLACEHOLDER {
        @Override
        public void checkName(String name) {
          Preconditions.checkArgument(RULE_NAME_PATTERN.matcher(name).matches(), name);
        }

        @Override
        public void checkAttributes(Map<String, Attribute> attributes) {
          // No required attributes; this rule class cannot have the wrong set of attributes now
          // because, if it did, the rule class would have failed to build before the package
          // referring to it was serialized.
        }
      };

      /**
       * Checks whether the given name is valid for the current rule class type.
       *
       * @throws IllegalArgumentException if the name is not valid
       */
      public abstract void checkName(String name);

      /**
       * Checks whether the given set of attributes contains all the required attributes for the
       * current rule class type.
       *
       * @throws IllegalArgumentException if a required attribute is missing
       */
      protected abstract void checkAttributes(Map<String, Attribute> attributes);
    }

    /**
     * A predicate that filters rule classes based on their names.
     *
     * <p>In {@link Rule}, {@code ruleClass} refers to the string name of the rule class while
     * {@code ruleClassObject} refers to the actual instance of {@link RuleClass}. Here, {@code
     * RuleClassName} emphasizes that the underlying logic of the predicate is based only on the
     * {@code String} name. The public methods, {@link #asPredicateOfRuleClass} and {@link
     * #asPredicateOfRuleClassObject} revert to the common convention used in {@link Rule}.
     */
    @AutoCodec
    public static class RuleClassNamePredicate {

      private static final RuleClassNamePredicate UNSPECIFIED_INSTANCE =
          new RuleClassNamePredicate(ImmutableSet.of(), PredicateType.UNSPECIFIED, null);

      private final ImmutableSet<String> ruleClassNames;

      private final PredicateType predicateType;

      private final Predicate<String> ruleClassNamePredicate;
      private final Predicate<RuleClass> ruleClassPredicate;
      // if non-null, used ONLY for checking overlap
      @Nullable private final Set<?> overlappable;

      @VisibleForSerialization
      enum PredicateType {
        ONLY,
        All_EXCEPT,
        UNSPECIFIED
      }

      @VisibleForSerialization
      RuleClassNamePredicate(
          ImmutableSet<String> ruleClassNames, PredicateType predicateType, Set<?> overlappable) {
        this.ruleClassNames = ruleClassNames;
        this.predicateType = predicateType;
        this.overlappable = overlappable;

        switch (predicateType) {
          case All_EXCEPT -> {
            Predicate<String> containing = only(ruleClassNames).asPredicateOfRuleClass();
            ruleClassNamePredicate =
                new DescribedPredicate<>(Predicates.not(containing), "all but " + containing);
            ruleClassPredicate =
                new DescribedPredicate<>(
                    Predicates.compose(ruleClassNamePredicate, RuleClass::getName),
                    ruleClassNamePredicate.toString());
          }
          case ONLY -> {
            ruleClassNamePredicate =
                new DescribedPredicate<>(
                    Predicates.in(ruleClassNames), StringUtil.joinEnglishList(ruleClassNames));
            ruleClassPredicate =
                new DescribedPredicate<>(
                    Predicates.compose(ruleClassNamePredicate, RuleClass::getName),
                    ruleClassNamePredicate.toString());
          }
          case UNSPECIFIED -> {
            ruleClassNamePredicate = Predicates.alwaysTrue();
            ruleClassPredicate = Predicates.alwaysTrue();
          }
          default ->
              // This shouldn't happen normally since the constructor is private and within this
              // file.
              throw new IllegalArgumentException(
                  "Predicate type was not specified when constructing a RuleClassNamePredicate.");
        }
      }

      public static RuleClassNamePredicate only(Iterable<String> ruleClassNamesAsIterable) {
        ImmutableSet<String> ruleClassNames = ImmutableSet.copyOf(ruleClassNamesAsIterable);
        return new RuleClassNamePredicate(ruleClassNames, PredicateType.ONLY, ruleClassNames);
      }

      public static RuleClassNamePredicate only(String... ruleClasses) {
        return only(Arrays.asList(ruleClasses));
      }

      public static RuleClassNamePredicate allExcept(String... ruleClasses) {
        ImmutableSet<String> ruleClassNames = ImmutableSet.copyOf(ruleClasses);
        Preconditions.checkState(!ruleClassNames.isEmpty(), "Use unspecified() instead");
        return new RuleClassNamePredicate(ruleClassNames, PredicateType.All_EXCEPT, null);
      }

      /**
       * This is a special sentinel value which represents a "default" {@link
       * RuleClassNamePredicate} which is unspecified. Note that a call to its {@link
       * RuleClassNamePredicate#asPredicateOfRuleClass} produces {@code
       * Predicates.<RuleClass>alwaysTrue()}, which is a sentinel value for other parts of bazel.
       */
      public static RuleClassNamePredicate unspecified() {
        return UNSPECIFIED_INSTANCE;
      }

      final Predicate<String> asPredicateOfRuleClass() {
        return ruleClassNamePredicate;
      }

      final Predicate<RuleClass> asPredicateOfRuleClassObject() {
        return ruleClassPredicate;
      }

      /**
       * Determines whether two {@code RuleClassNamePredicate}s should be considered incompatible as
       * rule class predicate and rule class warning predicate.
       *
       * <p>Specifically, if both list sets of explicit rule class names to permit, those two sets
       * must be disjoint, so the restriction only applies when both predicates have been created by
       * {@link #only}.
       */
      boolean consideredOverlapping(RuleClassNamePredicate that) {
        return this.overlappable != null
            && that.overlappable != null
            && !Collections.disjoint(this.overlappable, that.overlappable);
      }

      @Override
      public int hashCode() {
        return HashCodes.hashObjects(ruleClassNames, predicateType);
      }

      @Override
      public boolean equals(Object obj) {
        // NOTE: Specifically not checking equality of ruleClassPredicate.
        // By construction, if the name predicates are equals, the rule class predicates are, too.
        return obj instanceof RuleClassNamePredicate
            && ruleClassNames.equals(((RuleClassNamePredicate) obj).ruleClassNames)
            && predicateType.equals(((RuleClassNamePredicate) obj).predicateType);
      }

      @Override
      public String toString() {
        return ruleClassNamePredicate.toString();
      }

      /** A pass-through predicate, except that an explicit {@link #toString()} is provided. */
      private static class DescribedPredicate<T> implements Predicate<T> {
        private final Predicate<T> delegate; // the actual predicate
        private final String description;

        private DescribedPredicate(Predicate<T> delegate, String description) {
          this.delegate = delegate;
          this.description = description;
        }

        @Override
        public boolean apply(T input) {
          return delegate.apply(input);
        }

        @Override
        public int hashCode() {
          return delegate.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
          return obj instanceof DescribedPredicate
              && delegate.equals(((DescribedPredicate<?>) obj).delegate);
        }

        @Override
        public String toString() {
          return description;
        }
      }
    }

    /**
     * Name of default attribute implicitly added to all Starlark RuleClasses that are {@code
     * build_setting}s.
     */
    public static final String STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME = "build_setting_default";

    static final String STARLARK_BUILD_SETTING_HELP_ATTR_NAME = "help";

    static final String BUILD_SETTING_DEFAULT_NONCONFIGURABLE =
        "Build setting defaults are referenced during analysis.";

    /** List of required attributes for normal rules, name and type. */
    static final ImmutableList<Attribute> REQUIRED_ATTRIBUTES_FOR_NORMAL_RULES =
        ImmutableList.of(attr("tags", Types.STRING_LIST).build());

    /** List of required attributes for test rules, name and type. */
    static final ImmutableList<Attribute> REQUIRED_ATTRIBUTES_FOR_TESTS =
        ImmutableList.of(
            attr("tags", Types.STRING_LIST).build(),
            attr("size", Type.STRING).build(),
            attr("timeout", Type.STRING).build(),
            attr("flaky", Type.BOOLEAN).build(),
            attr("shard_count", Type.INTEGER).build(),
            attr("local", Type.BOOLEAN).build());

    private final String name;
    private ImmutableList<StarlarkThread.CallStackEntry> callstack = ImmutableList.of();
    private final RuleClassType type;
    @Nullable private RuleClass starlarkParent = null;
    @Nullable private StarlarkFunction initializer = null;
    @Nullable private LabelConverter labelConverterForInitializer = null;

    // The extendable may take 3 value, null means that the default allowlist should be use when
    // rule is extendable in practice.
    @Nullable private Boolean extendable = null;
    @Nullable private Label extendableAllowlist = null;
    @Nullable private Label defaultExtendableAllowlist = null;
    private final boolean starlark;
    private boolean starlarkTestable = false;
    private boolean documented;
    private boolean outputsToBindir = true;
    private boolean dependencyResolutionRule = false;
    private boolean isMaterializerRule = false;
    private boolean materializerRuleAllowsRealDeps = false;
    private boolean isExecutableStarlark = false;
    private boolean isAnalysisTest = false;
    private boolean hasAnalysisTestTransition = false;
    private final ImmutableList.Builder<AllowlistChecker> allowlistCheckers =
        ImmutableList.builder();
    private boolean ignoreLicenses = false;
    private ImplicitOutputsFunction implicitOutputsFunction = SafeImplicitOutputsFunction.NONE;
    private TransitionFactory<RuleTransitionData> transitionFactory = NoTransition.getFactory();
    private ConfiguredTargetFactory<?, ?, ?> configuredTargetFactory = null;
    private final AdvertisedProviderSet.Builder advertisedProviders =
        AdvertisedProviderSet.builder();
    private StarlarkCallable configuredTargetFunction = null;
    private BuildSetting buildSetting = null;

    private ImmutableList<? extends StarlarkSubruleApi> subrules = ImmutableList.of();
    private Function<? super Rule, ? extends Set<String>> optionReferenceFunction =
        NO_OPTION_REFERENCE;

    /** The following 3 fields are null iff the rule is native. */
    @Nullable private Label ruleDefinitionEnvironmentLabel;

    @Nullable private byte[] ruleDefinitionEnvironmentDigest = null;

    // TODO(b/366027483): in theory, ruleDefinitionEnvironmentLabel ought to equal
    // starlarkExtensionLabel, and we ought to get rid of one of them.
    @Nullable private Label starlarkExtensionLabel = null;

    // May be non-null only if the rule is Starlark-defined.
    @Nullable private String starlarkDocumentation = null;

    private final ConfigurationFragmentPolicy.Builder configurationFragmentPolicy =
        new ConfigurationFragmentPolicy.Builder();

    private boolean supportsConstraintChecking = true;

    private final Map<String, Attribute> attributes = new LinkedHashMap<>();
    private final Set<ToolchainTypeRequirement> toolchainTypes = new LinkedHashSet<>();
    private ToolchainResolutionMode toolchainResolutionMode = ToolchainResolutionMode.ENABLED;
    private final Set<Label> executionPlatformConstraints = new LinkedHashSet<>();
    private OutputFile.Kind outputFileKind = OutputFile.Kind.FILE;
    private final Map<String, DeclaredExecGroup> execGroups = new LinkedHashMap<>();
    private AutoExecGroupsMode autoExecGroupsMode = AutoExecGroupsMode.DYNAMIC;

    /**
     * Constructs a new {@link RuleClass.Builder} using all attributes from all parent rule classes.
     * An attribute cannot exist in more than one parent.
     *
     * <p>The rule type affects the allowed names and the required attributes (see {@link
     * RuleClassType}).
     *
     * @param parents There may be either multiple native {@code RuleClassType.ABSTRACT} rules or a
     *     single Starlark rule.
     * @throws IllegalArgumentException if an attribute with the same name exists in more than one
     *     parent
     */
    public Builder(String name, RuleClassType type, boolean starlark, RuleClass... parents) {
      Preconditions.checkArgument(
          (parents.length == 1 && parents[0].isStarlark())
              || Arrays.stream(parents).allMatch(rule -> !rule.isStarlark()));
      this.name = name;
      this.starlark = starlark;
      this.type = type;
      Preconditions.checkState(starlark || type != RuleClassType.PLACEHOLDER, name);
      this.documented = type != RuleClassType.ABSTRACT;
      addAttribute(NAME_ATTRIBUTE);
      if (parents.length == 1
          && parents[0].isStarlark()
          && parents[0].getRuleClassType() != RuleClassType.ABSTRACT) {
        // the condition removes {@link StarlarkRuleClassFunctions.baseRule} and binaryBaseRule,
        // which are marked as Starlark (because of Stardoc) && abstract at the same time
        starlarkParent = parents[0];
        Preconditions.checkArgument(starlarkParent.isExtendable());
      }

      for (RuleClass parent : parents) {

        if (parent.isMaterializerRule()) {
          isMaterializerRule = true;
          if (parent.materializerRuleAllowsRealDeps) {
            materializerRuleAllowsRealDeps = true;
          }
        } else if (isMaterializerRule) {
          throw new IllegalArgumentException(
              "Inconsistent value of isMaterializerRule among parents");
        }

        if (parent.dependencyResolutionRule) {
          dependencyResolutionRule = true;
        } else if (dependencyResolutionRule) {
          throw new IllegalArgumentException(
              "Inconsistent value of dependencyResolutionRule among parents");
        }

        configurationFragmentPolicy.includeConfigurationFragmentsFrom(
            parent.getConfigurationFragmentPolicy());
        supportsConstraintChecking = parent.supportsConstraintChecking;

        addToolchainTypes(parent.getToolchainTypes());
        addExecutionPlatformConstraints(parent.getExecutionPlatformConstraints());
        try {
          addExecGroups(parent.getDeclaredExecGroups(), false);
        } catch (DuplicateExecGroupError e) {
          throw new IllegalArgumentException(
              String.format(
                  "An execution group named '%s' is inherited multiple times with different"
                      + " requirements in %s ruleclass",
                  e.getDuplicateGroup(), name));
        }

        this.autoExecGroupsMode = parent.getAutoExecGroupsMode();

        for (Attribute attribute : parent.getAttributeProvider().getAttributes()) {
          String attrName = attribute.getName();
          Preconditions.checkArgument(
              !attributes.containsKey(attrName) || attributes.get(attrName).equals(attribute),
              "Attribute %s is inherited multiple times in %s ruleclass",
              attrName,
              name);
          attributes.put(attrName, attribute);
        }

        allowlistCheckers.addAll(parent.getAllowlistCheckers());

        advertisedProviders.addParent(parent.getAdvertisedProviders());

        if (parent.getDefaultImplicitOutputsFunction() != SafeImplicitOutputsFunction.NONE) {
          if (implicitOutputsFunction != SafeImplicitOutputsFunction.NONE) {
            throw new IllegalArgumentException("Only a single parent may set implicit outputs");
          }
          implicitOutputsFunction = parent.getDefaultImplicitOutputsFunction();
        }
      }
      // TODO(bazel-team): move this testonly attribute setting to somewhere else
      // preferably to some base RuleClass implementation.
      if (this.type.equals(RuleClassType.TEST)) {
        Attribute.Builder<Boolean> testOnlyAttr =
            attr("testonly", BOOLEAN)
                .value(true)
                .nonconfigurable("policy decision: this shouldn't depend on the configuration");
        if (attributes.containsKey("testonly")) {
          override(testOnlyAttr);
        } else {
          add(testOnlyAttr);
        }
      }
    }

    /**
     * Same as {@link #build}, except for a Starlark-defined rule class; the rule class's key will
     * be derived from the Starlark file label (falling back to the rule definition environment
     * label if null) and the name.
     *
     * @param name rule class name; if the builder was initialized with an empty name, this value
     *     will override it.
     * @param starlarkExtensionLabel the label of the Starlark file where the rule class was
     *     exported.
     */
    public RuleClass buildStarlark(String name, Label starlarkExtensionLabel) {
      Preconditions.checkState(starlark);
      this.starlarkExtensionLabel = starlarkExtensionLabel;
      return build(name, starlarkExtensionLabel + "%" + name);
    }

    /**
     * For a native rule, checks that required attributes for test rules are present, creates the
     * {@link RuleClass} object and returns it.
     *
     * @throws IllegalStateException if any of the required attributes is missing
     */
    public RuleClass build() {
      // For built-ins, name == key
      return build(name, name);
    }

    /** Same as {@link #build} except with setting the name and key parameters. */
    private RuleClass build(String name, String key) {
      Preconditions.checkArgument(this.name.isEmpty() || this.name.equals(name));
      type.checkName(name);

      checkAttributes(name);

      Preconditions.checkState(
          (type == RuleClassType.ABSTRACT)
              == (configuredTargetFactory == null && configuredTargetFunction == null),
          "Bad combo for %s: %s %s %s",
          name,
          type,
          configuredTargetFactory,
          configuredTargetFunction);
      if (starlark) {
        assertStarlarkRuleClassHasImplementationFunction();
        assertStarlarkRuleClassHasEnvironmentLabel();
      }
      if (type == RuleClassType.PLACEHOLDER) {
        Preconditions.checkNotNull(ruleDefinitionEnvironmentDigest, this.name);
      }

      if (buildSetting != null) {
        Type<?> type = buildSetting.getType();
        Attribute.Builder<?> defaultAttrBuilder =
            attr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, type)
                .nonconfigurable(BUILD_SETTING_DEFAULT_NONCONFIGURABLE)
                .mandatory();
        this.add(defaultAttrBuilder);

        this.add(
            attr(STARLARK_BUILD_SETTING_HELP_ATTR_NAME, Type.STRING)
                .nonconfigurable(BUILD_SETTING_DEFAULT_NONCONFIGURABLE));

        // Build setting rules should opt out of toolchain resolution, since they form part of the
        // configuration.
        this.toolchainResolutionMode(ToolchainResolutionMode.DISABLED);
      }

      if (starlark
          && (type == RuleClassType.NORMAL || type == RuleClassType.TEST)
          && outputsToBindir
          && !starlarkTestable
          && !isAnalysisTest
          && buildSetting == null) {
        if (extendable == null) { // The rule can be extended, use fallback
          extendable = true;
          extendableAllowlist = defaultExtendableAllowlist;
        }
      } else {
        // This kind of rule can't be extended
        if (Boolean.TRUE.equals(extendable) || extendableAllowlist != null) {
          throw new IllegalArgumentException("The rule cannot be extended");
        }
        extendable = false;
      }

      return new RuleClass(
          name,
          callstack,
          key,
          type,
          starlarkParent,
          initializer,
          labelConverterForInitializer,
          starlark,
          starlarkExtensionLabel,
          starlarkDocumentation,
          extendable,
          extendableAllowlist,
          starlarkTestable,
          documented,
          outputsToBindir,
          dependencyResolutionRule,
          isMaterializerRule,
          materializerRuleAllowsRealDeps,
          isExecutableStarlark,
          isAnalysisTest,
          hasAnalysisTestTransition,
          allowlistCheckers.build(),
          ignoreLicenses,
          implicitOutputsFunction,
          transitionFactory,
          configuredTargetFactory,
          advertisedProviders.build(),
          configuredTargetFunction,
          optionReferenceFunction,
          ruleDefinitionEnvironmentLabel,
          ruleDefinitionEnvironmentDigest,
          configurationFragmentPolicy.build(),
          supportsConstraintChecking,
          toolchainTypes,
          toolchainResolutionMode,
          executionPlatformConstraints,
          execGroups,
          autoExecGroupsMode,
          outputFileKind,
          ImmutableList.copyOf(attributes.values()),
          buildSetting,
          subrules);
    }

    private void checkAttributes(String ruleClassName) {
      Preconditions.checkArgument(
          attributes.size() <= MAX_ATTRIBUTES,
          "Rule class %s declared too many attributes (%s > %s)",
          ruleClassName,
          attributes.size(),
          MAX_ATTRIBUTES);

      ImmutableList.Builder<String> attributesNotForDependencyResolutionBuilder =
          ImmutableList.builder();

      for (var entry : attributes.entrySet()) {
        String attributeName = entry.getKey();
        Attribute attribute = entry.getValue();

        int attributeNameLength =
            StarlarkSubruleApi.getUserDefinedNameIfSubruleAttr(subrules, attributeName)
                .map(String::length)
                .orElse(attributeName.length());

        // TODO(b/151171037): This check would make more sense at Attribute creation time, but the
        // use of unchecked exceptions in these APIs makes it brittle.
        Preconditions.checkArgument(
            attributeNameLength <= MAX_ATTRIBUTE_NAME_LENGTH,
            "Attribute %s.%s's name is too long (%s > %s)",
            ruleClassName,
            attributeName,
            attributeNameLength,
            MAX_ATTRIBUTE_NAME_LENGTH);

        if (dependencyResolutionRule) {
          if (attribute.getType().getLabelClass() == LabelClass.DEPENDENCY
              && !attribute.isForDependencyResolution()) {
            attributesNotForDependencyResolutionBuilder.add(attributeName);
          }
        }
      }

      ImmutableList<String> attributesNotForDependencyResolution =
          attributesNotForDependencyResolutionBuilder.build();
      if (!attributesNotForDependencyResolution.isEmpty()) {
        throw new IllegalStateException(
            "Rule is available for dependency resolution but some dependency attributes aren't: "
                + Joiner.on(", ").join(attributesNotForDependencyResolution));
      }

      type.checkAttributes(attributes);
    }

    private void assertStarlarkRuleClassHasImplementationFunction() {
      Preconditions.checkState(
          (type == RuleClassType.NORMAL || type == RuleClassType.TEST)
              == (configuredTargetFunction != null),
          "%s %s",
          type,
          configuredTargetFunction);
    }

    private void assertStarlarkRuleClassHasEnvironmentLabel() {
      Preconditions.checkState(
          (type == RuleClassType.NORMAL
                  || type == RuleClassType.TEST
                  || type == RuleClassType.PLACEHOLDER)
              == (ruleDefinitionEnvironmentLabel != null),
          "Concrete Starlark rule classes can't have null labels: %s %s",
          ruleDefinitionEnvironmentLabel,
          type);
    }

    @CanIgnoreReturnValue
    public Builder initializer(
        StarlarkFunction initializer, LabelConverter labelConverterForInitializer) {
      this.initializer = initializer;
      this.labelConverterForInitializer = labelConverterForInitializer;
      return this;
    }

    public void setExtendableByAllowlist(Label extendableAllowlist) {
      this.extendable = true;
      this.extendableAllowlist = extendableAllowlist;
    }

    /** Set the rule extendable or not, without an allowlist. */
    public void setExtendable(boolean extendable) {
      this.extendable = extendable;
      this.extendableAllowlist = null;
    }

    /**
     * Sets the default allowlist, which is used as a fallback, when user doesn't set extendable or
     * extendable by allowlist
     */
    public void setDefaultExtendableAllowlist(Label extendableAllowlist) {
      this.defaultExtendableAllowlist = extendableAllowlist;
    }

    /**
     * Declares that the implementation of the associated rule class requires the given fragments to
     * be present.
     *
     * <p>The value is inherited by subclasses.
     */
    @CanIgnoreReturnValue
    public Builder requiresConfigurationFragments(
        Class<? extends Fragment>... configurationFragments) {
      configurationFragmentPolicy.requiresConfigurationFragments(
          ImmutableSet.copyOf(configurationFragments));
      return this;
    }

    /**
     * Declares the configuration fragments that are required by this rule for the target
     * configuration.
     *
     * <p>In contrast to {@link #requiresConfigurationFragments(Class...)}, this method takes the
     * Starlark module names of fragments instead of their classes.
     */
    @CanIgnoreReturnValue
    public Builder requiresConfigurationFragmentsByStarlarkModuleName(
        Collection<String> configurationFragmentNames) {
      configurationFragmentPolicy.requiresConfigurationFragmentsByStarlarkBuiltinName(
          configurationFragmentNames);
      return this;
    }

    /** Sets the Starlark call stack associated with this rule class's creation. */
    @CanIgnoreReturnValue
    public Builder setCallStack(ImmutableList<StarlarkThread.CallStackEntry> callstack) {
      this.callstack = callstack;
      return this;
    }

    @CanIgnoreReturnValue
    public Builder setStarlarkTestable() {
      Preconditions.checkState(starlark, "Cannot set starlarkTestable on a non-Starlark rule");
      starlarkTestable = true;
      return this;
    }

    /**
     * Sets the policy for the case where the configuration is missing required fragment class (see
     * {@link #requiresConfigurationFragments}).
     */
    @CanIgnoreReturnValue
    public Builder setMissingFragmentPolicy(
        Class<?> fragmentClass, MissingFragmentPolicy missingFragmentPolicy) {
      configurationFragmentPolicy.setMissingFragmentPolicy(fragmentClass, missingFragmentPolicy);
      return this;
    }

    @CanIgnoreReturnValue
    public Builder setUndocumented() {
      documented = false;
      return this;
    }

    /**
     * Determines the outputs of this rule to be created beneath the {@code genfiles} directory. By
     * default, files are created beneath the {@code bin} directory.
     *
     * <p>This property is not inherited and this method should not be called by builder of {@link
     * RuleClassType#ABSTRACT} rule class.
     *
     * @throws IllegalStateException if called for abstract rule class builder
     */
    @CanIgnoreReturnValue
    public Builder setOutputToGenfiles() {
      Preconditions.checkState(
          type != RuleClassType.ABSTRACT,
          "Setting not inherited property (output to genrules) of abstract rule class '%s'",
          name);
      this.outputsToBindir = false;
      return this;
    }

    /**
     * Sets the implicit outputs function of the rule class. The default implicit outputs function
     * is {@link SafeImplicitOutputsFunction#NONE}.
     *
     * <p>This property is not inherited and this method should not be called by builder of {@link
     * RuleClassType#ABSTRACT} rule class.
     *
     * @throws IllegalStateException if called for abstract rule class builder
     */
    @CanIgnoreReturnValue
    public Builder setImplicitOutputsFunction(ImplicitOutputsFunction implicitOutputsFunction) {
      Preconditions.checkState(
          type != RuleClassType.ABSTRACT,
          "Setting not inherited property (implicit output function) of abstract rule class '%s'",
          name);
      this.implicitOutputsFunction = implicitOutputsFunction;
      return this;
    }

    /** Applies the given transition factory to all incoming edges for this rule class. */
    @CanIgnoreReturnValue
    public Builder cfg(TransitionFactory<RuleTransitionData> transitionFactory) {
      Preconditions.checkState(
          type != RuleClassType.ABSTRACT,
          "Setting not inherited property (cfg) of abstract rule class '%s'",
          name);
      Preconditions.checkState(
          NoTransition.isInstance(this.transitionFactory), "Property cfg has already been set");
      Preconditions.checkNotNull(transitionFactory);
      Preconditions.checkArgument(
          transitionFactory.transitionType().isCompatibleWith(TransitionType.RULE));
      Preconditions.checkArgument(!transitionFactory.isSplit());
      this.transitionFactory = transitionFactory;
      return this;
    }

    @CanIgnoreReturnValue
    public Builder factory(ConfiguredTargetFactory<?, ?, ?> factory) {
      this.configuredTargetFactory = factory;
      return this;
    }

    /**
     * State that the rule class being built always supplies the specified provider.
     *
     * <p>When computing the set of aspects required for a rule, only the providers listed here are
     * considered. The presence of a provider here means that the rule <b>must</b> implement said
     * provider.
     *
     * <p>This is here so that we can do the loading phase overestimation required for "blaze
     * query", which does not have the configured targets available.
     */
    @CanIgnoreReturnValue
    public Builder advertiseProvider(Class<?>... providers) {
      for (Class<?> provider : providers) {
        advertisedProviders.addBuiltin(provider);
      }
      return this;
    }

    @CanIgnoreReturnValue
    public Builder advertiseStarlarkProvider(StarlarkProviderIdentifier... starlarkProviders) {
      for (StarlarkProviderIdentifier starlarkProviderIdentifier : starlarkProviders) {
        advertisedProviders.addStarlark(starlarkProviderIdentifier);
      }
      return this;
    }

    /**
     * Set if the rule can have any provider. This is called for the {@code alias} rule and other
     * alias-like rules such as {@code bind}.
     */
    @CanIgnoreReturnValue
    public Builder canHaveAnyProvider() {
      advertisedProviders.canHaveAnyProvider();
      return this;
    }

    /**
     * Adds an attribute to the builder.
     *
     * <p>Throws an IllegalStateException if an attribute of that name already exists.
     *
     * <p>TODO(bazel-team): stop using unchecked exceptions in this way.
     */
    @CanIgnoreReturnValue
    public Builder addAttribute(Attribute attribute) {
      Attribute prevVal = attributes.putIfAbsent(attribute.getName(), attribute);
      if (prevVal != null) {
        throw new IllegalStateException(
            String.format(
                "There is already a built-in attribute '%s' which cannot be overridden.",
                attribute.getName()));
      }
      return this;
    }

    private void overrideAttribute(Attribute attribute) {
      String attrName = attribute.getName();
      Preconditions.checkState(
          attributes.containsKey(attrName),
          "No such attribute '%s' to override in ruleclass '%s'.",
          attrName,
          name);
      Type<?> origType = attributes.get(attrName).getType();
      Type<?> newType = attribute.getType();
      Preconditions.checkState(
          origType.equals(newType),
          "The type of the new attribute '%s' is different from the original one '%s'.",
          newType,
          origType);
      attributes.put(attrName, attribute);
    }

    /**
     * Builds provided attribute and attaches it to this rule class.
     *
     * <p>Typically rule classes should only declare a handful of attributes - this expectation is
     * enforced when the instance is built.
     *
     * <p>Attribute names should be meaningful but short; overly long names are rejected at
     * instantiation.
     */
    @CanIgnoreReturnValue
    public <TYPE> Builder add(Attribute.Builder<TYPE> attr) {
      addAttribute(attr.build());
      return this;
    }

    @FormatMethod
    private static void failIf(boolean condition, String message, Object... args)
        throws EvalException {
      if (condition) {
        throw Starlark.errorf(message, args);
      }
    }

    /**
     * Overrides the attribute with the same name. This method does additional checks required for
     * overriding attributes in Starlark
     */
    @CanIgnoreReturnValue
    public Builder override(Attribute attr) throws EvalException {
      Attribute parentAttr = attributes.get(attr.getName());
      failIf(
          !parentAttr.starlarkDefined(),
          "attribute `%s`: built-in attributes cannot be overridden.",
          parentAttr.getPublicName());
      failIf(
          !parentAttr.isPublic(),
          "attribute `%s`: private attributes cannot be overridden.",
          parentAttr.getPublicName());
      failIf(
          parentAttr.getType() != BuildType.LABEL_LIST && parentAttr.getType() != BuildType.LABEL,
          "attribute `%s`: Only label types maybe be overridden.",
          parentAttr.getPublicName());
      failIf(
          parentAttr.getType() != attr.getType(),
          "attribute `%s`: Types of parent and child's attributes mismatch.",
          parentAttr.getPublicName());
      attr.failIfNotAValidOverride();

      Attribute.Builder<?> attrBuilder = copy(attr.getName());
      if (attr.getDefaultValueUnchecked() != null) {
        attrBuilder.defaultValue(attr.getDefaultValueUnchecked());
      }
      attrBuilder.addAspects(attr.getAspectsList());
      override(attrBuilder);
      return this;
    }

    /**
     * Builds attribute from the attribute builder and overrides the attribute with the same name.
     *
     * @throws IllegalArgumentException if the attribute does not override one of the same name
     */
    @CanIgnoreReturnValue
    public <TYPE> Builder override(Attribute.Builder<TYPE> attr) {
      overrideAttribute(attr.build());
      return this;
    }

    /** True if the rule class contains an attribute named {@code name}. */
    public boolean contains(String name) {
      return attributes.containsKey(name);
    }

    public Attribute getAttribute(String name) {
      return attributes.get(name);
    }

    /** Returns a list of all attributes added to this Builder so far. */
    public ImmutableList<Attribute> getAttributes() {
      return ImmutableList.copyOf(attributes.values());
    }

    /** Sets the rule implementation function. Meant for Starlark usage. */
    @CanIgnoreReturnValue
    public Builder setConfiguredTargetFunction(StarlarkCallable func) {
      this.configuredTargetFunction = func;
      return this;
    }

    @CanIgnoreReturnValue
    public Builder setBuildSetting(BuildSetting buildSetting) {
      this.buildSetting = buildSetting;
      return this;
    }

    @CanIgnoreReturnValue
    public Builder setSubrules(ImmutableList<? extends StarlarkSubruleApi> subrules) {
      this.subrules = subrules;
      return this;
    }

    public ImmutableList<? extends StarlarkSubruleApi> getSubrules() {
      return subrules;
    }

    public ImmutableSet<? extends StarlarkSubruleApi> getParentSubrules() {
      ImmutableSet.Builder<StarlarkSubruleApi> builder = ImmutableSet.builder();
      RuleClass currentParent = starlarkParent;
      while (currentParent != null) {
        builder.addAll(starlarkParent.getSubrules());
        currentParent = currentParent.starlarkParent;
      }
      return builder.build();
    }

    /**
     * Sets the rule definition environment label and transitive digest. Meant for Starlark usage.
     */
    @CanIgnoreReturnValue
    public Builder setRuleDefinitionEnvironmentLabelAndDigest(Label label, byte[] digest) {
      this.ruleDefinitionEnvironmentLabel = Preconditions.checkNotNull(label, this.name);
      this.ruleDefinitionEnvironmentDigest = Preconditions.checkNotNull(digest, this.name);
      return this;
    }

    /**
     * Sets the Starlark documentation string, if one was provided, for a Starlark-defined rule
     * class. Cannot be set for a non-Starlark-defined rule class.
     */
    @CanIgnoreReturnValue
    public Builder setStarlarkDocumentation(String starlarkDocumentation) {
      Preconditions.checkState(starlark, this.name);
      this.starlarkDocumentation = Preconditions.checkNotNull(starlarkDocumentation, this.name);
      return this;
    }

    /**
     * Returns the Starlark documentation string, if one was provided, for a Starlark-defined rule
     * class.
     */
    @Nullable
    public String getStarlarkDocumentation() {
      return this.starlarkDocumentation;
    }

    public Label getRuleDefinitionEnvironmentLabel() {
      return this.ruleDefinitionEnvironmentLabel;
    }

    /**
     * Removes an attribute with the same name from this rule class.
     *
     * @throws IllegalArgumentException if the attribute with this name does not exist
     */
    @CanIgnoreReturnValue
    public Builder removeAttribute(String name) {
      Preconditions.checkState(
          attributes.containsKey(name), "No such attribute '%s' to remove.", name);
      attributes.remove(name);
      return this;
    }

    /**
     * Mark the rule as "for dependency resolution". Rules so marked can only depend on other rules
     * also marked as such.
     */
    @CanIgnoreReturnValue
    public Builder setDependencyResolutionRule() {
      this.dependencyResolutionRule = true;
      return this;
    }

    /** Mark the rule as a materializer rule. */
    @CanIgnoreReturnValue
    public Builder setIsMaterializerRule(boolean isMaterializerRule) {
      this.isMaterializerRule = isMaterializerRule;
      return this;
    }

    /** Mark the rule as a materializer rule that allows real deps. */
    @CanIgnoreReturnValue
    public Builder setMaterializerRuleAllowsRealDeps(boolean allowRealDeps) {
      this.materializerRuleAllowsRealDeps = allowRealDeps;
      return this;
    }

    /**
     * This rule class outputs a default executable for every rule with the same name as the
     * rules's. Only works for Starlark.
     */
    @CanIgnoreReturnValue
    public Builder setExecutableStarlark() {
      this.isExecutableStarlark = true;
      return this;
    }

    /** This rule class is marked as an analysis test. */
    @CanIgnoreReturnValue
    public Builder setIsAnalysisTest() {
      this.isAnalysisTest = true;
      return this;
    }

    public boolean isAnalysisTest() {
      return this.isAnalysisTest;
    }

    /**
     * This rule class has at least one attribute with an analysis test transition. (A
     * starlark-defined transition using analysis_test_transition()).
     */
    @CanIgnoreReturnValue
    public Builder setHasAnalysisTestTransition() {
      this.hasAnalysisTestTransition = true;
      return this;
    }

    /** Add an allowlistChecker to be checked as part of the rule implementation. */
    @CanIgnoreReturnValue
    public Builder addAllowlistChecker(AllowlistChecker allowlistChecker) {
      this.allowlistCheckers.add(allowlistChecker);
      return this;
    }

    /**
     * This rule class never declares a license regardless of what the rule's or package's <code>
     * licenses</code> attribute says.
     */
    // TODO(b/130286108): remove the licenses attribute completely from such rules.
    @CanIgnoreReturnValue
    public Builder setIgnoreLicenses() {
      this.ignoreLicenses = true;
      return this;
    }

    public RuleClassType getType() {
      return this.type;
    }

    /**
     * Sets the kind of output files this rule creates. DO NOT USE! This only exists to support the
     * non-open-sourced {@code fileset} rule. {@see OutputFile.Kind}.
     */
    @CanIgnoreReturnValue
    public Builder setOutputFileKind(OutputFile.Kind outputFileKind) {
      this.outputFileKind = Preconditions.checkNotNull(outputFileKind);
      return this;
    }

    /**
     * Declares that instances of this rule are compatible with the specified environments, in
     * addition to the defaults declared by their environment groups. This can be overridden by
     * rule-specific declarations. See {@link
     * com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics} for details.
     */
    @CanIgnoreReturnValue
    public Builder compatibleWith(Label... environments) {
      add(
          attr(DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR, LABEL_LIST)
              .value(ImmutableList.copyOf(environments)));
      return this;
    }

    /**
     * Declares that instances of this rule are restricted to the specified environments, i.e. these
     * override the defaults declared by their environment groups. This can be overridden by
     * rule-specific declarations. See {@link
     * com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics} for details.
     *
     * <p>The input list cannot be empty.
     */
    @CanIgnoreReturnValue
    public Builder restrictedTo(Label firstEnvironment, Label... otherEnvironments) {
      ImmutableList<Label> environments =
          ImmutableList.<Label>builder().add(firstEnvironment).add(otherEnvironments).build();
      add(attr(DEFAULT_RESTRICTED_ENVIRONMENT_ATTR, LABEL_LIST).value(environments));
      return this;
    }

    /**
     * Exempts rules of this type from the constraint enforcement system. This should only be
     * applied to rules that are intrinsically incompatible with constraint checking (any
     * application of this method weakens the reach and strength of the system).
     *
     * @param reason user-informative message explaining the reason for exemption (not used)
     */
    @CanIgnoreReturnValue
    public Builder exemptFromConstraintChecking(String reason) {
      Preconditions.checkState(this.supportsConstraintChecking);
      this.supportsConstraintChecking = false;
      attributes.remove(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR);
      attributes.remove(RuleClass.RESTRICTED_ENVIRONMENT_ATTR);
      attributes.remove(RuleClass.TARGET_COMPATIBLE_WITH_ATTR);
      return this;
    }

    /**
     * Causes rules of this type to implicitly reference the configuration fragments associated with
     * the options its attributes reference.
     *
     * <p>This is only intended for use by {@code config_setting} - other rules should not use this!
     */
    @CanIgnoreReturnValue
    public Builder setOptionReferenceFunctionForConfigSettingOnly(
        Function<? super Rule, ? extends Set<String>> optionReferenceFunction) {
      this.optionReferenceFunction = Preconditions.checkNotNull(optionReferenceFunction);
      return this;
    }

    /**
     * Cause rules of this type to request the specified toolchains be available via toolchain
     * resolution when a target is configured.
     */
    @CanIgnoreReturnValue
    public Builder addToolchainTypes(Iterable<ToolchainTypeRequirement> toolchainTypes) {
      Iterables.addAll(this.toolchainTypes, toolchainTypes);
      return this;
    }

    /**
     * Cause rules of this type to request the specified toolchains be available via toolchain
     * resolution when a target is configured.
     */
    @CanIgnoreReturnValue
    public Builder addToolchainTypes(ToolchainTypeRequirement... toolchainTypes) {
      return addToolchainTypes(ImmutableList.copyOf(toolchainTypes));
    }

    /**
     * Adds execution groups to this rule class. Errors out if multiple different groups with the
     * same name are added.
     */
    @CanIgnoreReturnValue
    public Builder addExecGroups(Map<String, DeclaredExecGroup> execGroups, boolean override) {
      for (Map.Entry<String, DeclaredExecGroup> group : execGroups.entrySet()) {
        String name = group.getKey();
        if (this.execGroups.containsKey(name) && !override) {
          // If trying to add a new execution group with the same name as a execution group that
          // already exists, check if they are equivalent and error out if not.
          DeclaredExecGroup existingGroup = this.execGroups.get(name);
          DeclaredExecGroup newGroup = group.getValue();
          if (!existingGroup.equals(newGroup)) {
            throw new DuplicateExecGroupError(name);
          }
        } else {
          this.execGroups.put(name, group.getValue());
        }
      }
      return this;
    }

    /** An error to help report {@link DeclaredExecGroup}s with the same name */
    static class DuplicateExecGroupError extends RuntimeException {
      private final String duplicateGroup;

      DuplicateExecGroupError(String duplicateGroup) {
        super(String.format("Multiple execution groups with the same name: '%s'.", duplicateGroup));
        this.duplicateGroup = duplicateGroup;
      }

      String getDuplicateGroup() {
        return duplicateGroup;
      }
    }

    /** Checks whether the rule class has an exec group with the given name. */
    public boolean hasExecGroup(String name) {
      return this.execGroups.containsKey(name);
    }

    /** Sets how this rule class uses auto exec groups. */
    @CanIgnoreReturnValue
    public Builder autoExecGroupsMode(AutoExecGroupsMode autoExecGroupsMode) {
      this.autoExecGroupsMode = autoExecGroupsMode;
      return this;
    }

    /**
     * Causes rules to use toolchain resolution to determine the execution platform and toolchains.
     * Rules that are part of configuring toolchains and platforms should set this to {@code
     * DISABLED}.
     */
    @CanIgnoreReturnValue
    public Builder toolchainResolutionMode(ToolchainResolutionMode mode) {
      this.toolchainResolutionMode = mode;
      return this;
    }

    /**
     * Adds additional execution platform constraints that apply for all targets from this rule.
     *
     * <p>Please note that this value is inherited by child rules.
     */
    public Builder addExecutionPlatformConstraints(Label... constraints) {
      return this.addExecutionPlatformConstraints(Lists.newArrayList(constraints));
    }

    /**
     * Adds additional execution platform constraints that apply for all targets from this rule.
     *
     * <p>Please note that this value is inherited by child rules.
     */
    @CanIgnoreReturnValue
    public Builder addExecutionPlatformConstraints(Iterable<Label> constraints) {
      Iterables.addAll(this.executionPlatformConstraints, constraints);
      return this;
    }

    /**
     * Returns an Attribute.Builder object which contains a replica of the same attribute in the
     * parent rule if exists.
     *
     * @param name the name of the attribute
     */
    public Attribute.Builder<?> copy(String name) {
      Preconditions.checkArgument(
          attributes.containsKey(name), "Attribute %s does not exist in parent rule class.", name);
      return attributes.get(name).cloneBuilder();
    }
  }

  // record containing both the common rule_class 'name' (e.g. "cc_library") as
  // well as the unique 'key' for the rule class. Key has the same value as
  // 'name' for native rules and a combination of label + name for Starlark.
  private final RuleClassId ruleClassId;
  private final ImmutableList<StarlarkThread.CallStackEntry> callstack; // of call to 'rule'

  /**
   * The kind of target represented by this RuleClass (e.g. "cc_library rule"). Note: Even though
   * there is partial duplication with the {@link RuleClass#name} field, we want to store this as a
   * separate field instead of generating it on demand in order to avoid string duplication.
   */
  private final String targetKind;

  private final RuleClassType type;
  @Nullable private final RuleClass starlarkParent;
  @Nullable private final StarlarkFunction initializer;
  @Nullable private final LabelConverter labelConverterForInitializer;
  private final boolean isStarlark;
  private final boolean extendable;
  // The following 2 fields may be non-null only if the rule is Starlark-defined.
  @Nullable private final Label starlarkExtensionLabel;
  @Nullable private final String starlarkDocumentation;
  @Nullable private final Label extendableAllowlist;
  private final boolean starlarkTestable;
  private final boolean documented;
  private final boolean outputsToBindir;
  private final boolean dependencyResolutionRule;
  private final boolean isMaterializerRule;
  private final boolean materializerRuleAllowsRealDeps;
  private final boolean isExecutableStarlark;
  private final boolean isAnalysisTest;
  private final boolean hasAnalysisTestTransition;
  private final ImmutableList<AllowlistChecker> allowlistCheckers;
  private final boolean hasAspects;

  private final AttributeProvider attributeProvider;

  /** The set of implicit outputs generated by a rule, expressed as a function of that rule. */
  private final ImplicitOutputsFunction implicitOutputsFunction;

  /**
   * A factory which will produce a configuration transition that should be applied on any edge of
   * the configured target graph that leads into a target of this rule class.
   */
  private final TransitionFactory<RuleTransitionData> transitionFactory;

  /** The factory that creates configured targets from this rule. */
  private final ConfiguredTargetFactory<?, ?, ?> configuredTargetFactory;

  /** The list of transitive info providers this class advertises to aspects. */
  private final AdvertisedProviderSet advertisedProviders;

  /**
   * The Starlark rule implementation of this RuleClass. Null for non Starlark executable
   * RuleClasses.
   */
  @Nullable private final StarlarkCallable configuredTargetFunction;

  /**
   * The BuildSetting associated with this rule. Null for all RuleClasses except Starlark-defined
   * rules that pass {@code build_setting} to their {@code rule()} declaration.
   */
  @Nullable private final BuildSetting buildSetting;

  /**
   * The subrules associated with this rule. Empty for all rule classes except Starlark-defined
   * rules that explicitly pass {@code subrules = [...]} to their {@code rule()} declaration
   */
  private final ImmutableSet<? extends StarlarkSubruleApi> subrules;

  /** Returns the options referenced by this rule's attributes. */
  private final Function<? super Rule, ? extends Set<String>> optionReferenceFunction;

  /**
   * The Starlark rule definition environment's label and hash code of this RuleClass. Null for non
   * Starlark executable RuleClasses.
   */
  @Nullable private final Label ruleDefinitionEnvironmentLabel;

  @Nullable private final byte[] ruleDefinitionEnvironmentDigest;

  private final OutputFile.Kind outputFileKind;

  /**
   * The set of configuration fragments which are legal for this rule's implementation to access.
   */
  private final ConfigurationFragmentPolicy configurationFragmentPolicy;

  /**
   * Determines whether instances of this rule should be checked for constraint compatibility with
   * their dependencies and the rules that depend on them. This should be true for everything except
   * for rules that are intrinsically incompatible with the constraint system.
   */
  private final boolean supportsConstraintChecking;

  private final ImmutableSet<ToolchainTypeRequirement> toolchainTypes;
  private final ToolchainResolutionMode toolchainResolutionMode;
  private final ImmutableSet<Label> executionPlatformConstraints;
  private final ImmutableMap<String, DeclaredExecGroup> declaredExecGroups;
  private final AutoExecGroupsMode autoExecGroupsMode;

  /**
   * Constructs an instance of RuleClass whose name is 'name', attributes are 'attributes'. The
   * {@code srcsAllowedFiles} determines which types of files are allowed as parameters to the
   * "srcs" attribute; rules are always allowed. For the "deps" attribute, there are four cases:
   *
   * <ul>
   *   <li>if the parameter is a file, it is allowed if its file type is given in {@code
   *       depsAllowedFiles},
   *   <li>if the parameter is a rule and the rule class is accepted by {@code depsAllowedRules},
   *       then it is allowed,
   *   <li>if the parameter is a rule and the rule class is not accepted by {@code
   *       depsAllowedRules}, but accepted by {@code depsAllowedRulesWithWarning}, then it is
   *       allowed, but triggers a warning;
   *   <li>all other parameters trigger an error.
   * </ul>
   *
   * <p>The {@code depsAllowedRules} predicate should have a {@code toString} method which returns a
   * plain English enumeration of the allowed rule class names, if it does not allow all rule
   * classes.
   */
  @VisibleForTesting
  RuleClass(
      String name,
      ImmutableList<StarlarkThread.CallStackEntry> callstack,
      String key,
      RuleClassType type,
      RuleClass starlarkParent,
      @Nullable StarlarkFunction initializer,
      @Nullable LabelConverter labelConverterForInitializer,
      boolean isStarlark,
      @Nullable Label starlarkExtensionLabel,
      @Nullable String starlarkDocumentation,
      boolean extendable,
      @Nullable Label extendableAllowlist,
      boolean starlarkTestable,
      boolean documented,
      boolean outputsToBindir,
      boolean dependencyResolutionRule,
      boolean isMaterializerRule,
      boolean materializerRuleAllowsRealDeps,
      boolean isExecutableStarlark,
      boolean isAnalysisTest,
      boolean hasAnalysisTestTransition,
      ImmutableList<AllowlistChecker> allowlistCheckers,
      boolean ignoreLicenses,
      ImplicitOutputsFunction implicitOutputsFunction,
      TransitionFactory<RuleTransitionData> transitionFactory,
      ConfiguredTargetFactory<?, ?, ?> configuredTargetFactory,
      AdvertisedProviderSet advertisedProviders,
      @Nullable StarlarkCallable configuredTargetFunction,
      Function<? super Rule, ? extends Set<String>> optionReferenceFunction,
      @Nullable Label ruleDefinitionEnvironmentLabel,
      @Nullable byte[] ruleDefinitionEnvironmentDigest,
      ConfigurationFragmentPolicy configurationFragmentPolicy,
      boolean supportsConstraintChecking,
      Set<ToolchainTypeRequirement> toolchainTypes,
      ToolchainResolutionMode toolchainResolutionMode,
      Set<Label> executionPlatformConstraints,
      Map<String, DeclaredExecGroup> declaredExecGroups,
      AutoExecGroupsMode autoExecGroupsMode,
      OutputFile.Kind outputFileKind,
      ImmutableList<Attribute> attributes,
      @Nullable BuildSetting buildSetting,
      ImmutableList<? extends StarlarkSubruleApi> subrules) {
    this.ruleClassId = RuleClassId.create(name, key);
    this.callstack = callstack;
    this.type = type;
    this.starlarkParent = starlarkParent;
    this.initializer = initializer;
    this.labelConverterForInitializer = labelConverterForInitializer;
    this.isStarlark = isStarlark;
    this.starlarkExtensionLabel = starlarkExtensionLabel;
    this.starlarkDocumentation = starlarkDocumentation;
    this.extendable = extendable;
    this.extendableAllowlist = extendableAllowlist;
    this.targetKind = name + Rule.targetKindSuffix();
    this.starlarkTestable = starlarkTestable;
    this.documented = documented;
    this.outputsToBindir = outputsToBindir;
    this.implicitOutputsFunction = implicitOutputsFunction;
    this.transitionFactory = transitionFactory;
    this.configuredTargetFactory = configuredTargetFactory;
    this.advertisedProviders = advertisedProviders;
    this.configuredTargetFunction = configuredTargetFunction;
    this.optionReferenceFunction = optionReferenceFunction;
    this.ruleDefinitionEnvironmentLabel = ruleDefinitionEnvironmentLabel;
    this.ruleDefinitionEnvironmentDigest = ruleDefinitionEnvironmentDigest;
    this.outputFileKind = outputFileKind;
    this.dependencyResolutionRule = dependencyResolutionRule;
    this.isMaterializerRule = isMaterializerRule;
    this.materializerRuleAllowsRealDeps = materializerRuleAllowsRealDeps;
    this.isExecutableStarlark = isExecutableStarlark;
    this.isAnalysisTest = isAnalysisTest;
    this.hasAnalysisTestTransition = hasAnalysisTestTransition;
    this.allowlistCheckers = allowlistCheckers;
    this.configurationFragmentPolicy = configurationFragmentPolicy;
    this.supportsConstraintChecking = supportsConstraintChecking;
    this.toolchainTypes = ImmutableSet.copyOf(toolchainTypes);
    this.toolchainResolutionMode = toolchainResolutionMode;
    this.executionPlatformConstraints = ImmutableSet.copyOf(executionPlatformConstraints);
    this.declaredExecGroups = ImmutableMap.copyOf(declaredExecGroups);
    this.autoExecGroupsMode = autoExecGroupsMode;
    this.buildSetting = buildSetting;
    this.subrules = ImmutableSet.copyOf(subrules);
    // Create the index and collect non-configurable attributes while doing some validation checks.
    Preconditions.checkState(
        !attributes.isEmpty() && attributes.get(0).equals(NAME_ATTRIBUTE),
        "Rule %s does not have name as its first attribute: %s",
        name,
        attributes);
    Map<String, Integer> attributeIndex = Maps.newHashMapWithExpectedSize(attributes.size());
    Map<String, Attribute> publicToPrivateNames =
        Maps.newHashMapWithExpectedSize(attributes.size());
    boolean computedHasAspects = false;
    ImmutableList.Builder<String> nonConfigurableAttributes = ImmutableList.builder();
    for (int i = 0; i < attributes.size(); i++) {
      Attribute attribute = attributes.get(i);
      String publicName = attribute.getPublicName();
      Attribute conflicting = publicToPrivateNames.put(publicName, attribute);
      if (conflicting != null) {
        throw new IllegalStateException(
            String.format(
                "Rule %s: Attributes %s and %s have an identical public name: %s",
                name, attribute.getName(), conflicting.getName(), publicName));
      }
      computedHasAspects |= attribute.hasAspects();
      attributeIndex.put(attribute.getName(), i);
      if (!attribute.isConfigurable()) {
        nonConfigurableAttributes.add(attribute.getName());
      }
    }
    this.attributeProvider =
        new AttributeProvider(
            attributes, attributeIndex, nonConfigurableAttributes.build(), name, ignoreLicenses);
    this.hasAspects = computedHasAspects;
  }

  /**
   * Returns the default function for determining the set of implicit outputs generated by a given
   * rule. If not otherwise specified, this will be the implementation used by {@link Rule}s created
   * with this {@link RuleClass}.
   *
   * <p>An implicit output is an OutputFile that automatically comes into existence when a rule of
   * this class is declared, and whose name is derived from the name of the rule.
   *
   * <p>Implicit outputs are a widely-relied upon. All ".so", and "_deploy.jar" targets referenced
   * in BUILD files are examples.
   */
  // (public for serialization)
  public ImplicitOutputsFunction getDefaultImplicitOutputsFunction() {
    return implicitOutputsFunction;
  }

  public TransitionFactory<RuleTransitionData> getTransitionFactory() {
    return transitionFactory;
  }

  public <T extends ConfiguredTargetFactory<?, ?, ?>> T getConfiguredTargetFactory(Class<T> clazz) {
    return clazz.cast(configuredTargetFactory);
  }

  public ConfiguredTargetFactory<?, ?, ?> getConfiguredTargetFactory() {
    return configuredTargetFactory;
  }

  /** Returns the class of rule that this RuleClass represents (e.g. "cc_library"). */
  @Override
  public String getName() {
    return ruleClassId.name();
  }

  public RuleClass getStarlarkParent() {
    return this.starlarkParent;
  }

  @Nullable
  public StarlarkFunction getInitializer() {
    return initializer;
  }

  @Nullable
  public LabelConverter getLabelConverterForInitializer() {
    return labelConverterForInitializer;
  }

  /**
   * Returns the stack of Starlark active function calls at the moment this rule class was created.
   * Entries appear outermost first, and exclude the built-in itself ('rule'). Empty for
   * non-Starlark rules.
   */
  public ImmutableList<StarlarkThread.CallStackEntry> getCallStack() {
    return callstack;
  }

  /** Returns the type of rule that this RuleClass represents. Only for use during serialization. */
  public RuleClassType getRuleClassType() {
    return type;
  }

  /** Returns a unique key. Used for profiling purposes. */
  public String getKey() {
    return ruleClassId.key();
  }

  /** Returns the record containing both the name and key. */
  public RuleClassId getRuleClassId() {
    return this.ruleClassId;
  }

  /** Returns the target kind of this class of rule (e.g. "cc_library rule"). */
  @Override
  public String getTargetKind() {
    return targetKind;
  }

  /**
   * Returns the attribute provider for this rule class. This can be queried to understand the
   * attribute schema associated with the rule.
   */
  public AttributeProvider getAttributeProvider() {
    return attributeProvider;
  }

  /**
   * Returns the set of advertised transitive info providers.
   *
   * <p>When computing the set of aspects required for a rule, only the providers listed here are
   * considered. The presence of a provider here does not mean that the rule <b>must</b> implement
   * said provider, merely that it <b>can</b>. After the configured target is constructed from this
   * rule, aspects will be filtered according to the set of actual providers.
   *
   * <p>This is here so that we can do the loading phase overestimation required for "blaze query",
   * which does not have the configured targets available.
   */
  @Override
  public AdvertisedProviderSet getAdvertisedProviders() {
    return advertisedProviders;
  }

  /** Returns this rule's policy for configuration fragment access. */
  public ConfigurationFragmentPolicy getConfigurationFragmentPolicy() {
    return configurationFragmentPolicy;
  }

  /** Returns true if rules of this type can be used with the constraint enforcement system. */
  public boolean supportsConstraintChecking() {
    return supportsConstraintChecking;
  }

  boolean hasAspects() {
    return hasAspects;
  }

  /**
   * Creates a new {@link Rule} {@code r} where {@code r.getPackageoid()} is the {@link Packageoid}
   * associated with {@code targetDefinitionContext}.
   *
   * <p>The created {@link Rule} will be populated with attribute values from {@code
   * attributeValues} or the default attribute values associated with this {@link RuleClass} and
   * {@code targetDefinitionContext}.
   *
   * <p>The created {@link Rule} will also be populated with output files. These output files will
   * have been collected from the explicitly provided values of type {@link BuildType#OUTPUT} and
   * {@link BuildType#OUTPUT_LIST} as well as from the implicit outputs determined by this {@link
   * RuleClass} and the values in {@code attributeValues}.
   *
   * <p>This performs several validity checks. Invalid output file labels result in a thrown {@link
   * LabelSyntaxException}. Computed default attributes that fail during precomputation result in a
   * {@link CannotPrecomputeDefaultsException}. All other errors are reported on {@code
   * eventHandler}.
   */
  <T> Rule createRule(
      TargetDefinitionContext targetDefinitionContext,
      Label ruleLabel,
      AttributeValues<T> attributeValues,
      boolean failOnUnknownAttributes,
      List<StarlarkThread.CallStackEntry> callstack)
      throws LabelSyntaxException, InterruptedException, CannotPrecomputeDefaultsException {
    EventHandler eventHandler = targetDefinitionContext.getLocalEventHandler();

    Rule rule = targetDefinitionContext.createRule(ruleLabel, this, callstack);
    attributeProvider.populateRuleAttributeValues(
        rule, targetDefinitionContext, attributeValues, failOnUnknownAttributes, isStarlark);
    checkAspectAllowedValues(rule, eventHandler);
    rule.populateOutputFiles(eventHandler, targetDefinitionContext.getPackageIdentifier());
    checkForDuplicateLabels(rule, eventHandler);

    checkForValidSizeAndTimeoutValues(rule, eventHandler);
    return rule;
  }

  /**
   * Same as {@link #createRule}, except without some internal checks.
   *
   * <p>Don't call this function unless you know what you're doing.
   */
  <T> Rule createRuleUnchecked(
      TargetDefinitionContext targetDefinitionContext,
      Label ruleLabel,
      AttributeValues<T> attributeValues,
      CallStack.Node callstack,
      ImplicitOutputsFunction implicitOutputsFunction)
      throws InterruptedException, CannotPrecomputeDefaultsException {
    Rule rule =
        targetDefinitionContext.createRule(
            ruleLabel, this, callstack.toLocation(), callstack.next());
    attributeProvider.populateRuleAttributeValues(
        rule, targetDefinitionContext, attributeValues, true, isStarlark);
    rule.populateOutputFilesUnchecked(targetDefinitionContext, implicitOutputsFunction);
    return rule;
  }

  /**
   * Report an error for each label that appears more than once in a LABEL_LIST attribute of the
   * given rule.
   *
   * @param rule The rule.
   * @param eventHandler The eventHandler to use to report the duplicated deps.
   */
  private static void checkForDuplicateLabels(Rule rule, EventHandler eventHandler) {
    AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
    for (Attribute attribute : rule.getAttributeProvider().getAttributes()) {
      Set<Label> duplicates = mapper.checkForDuplicateLabels(attribute);
      if (duplicates.isEmpty()) {
        continue;
      }
      for (Label label : duplicates) {
        rule.reportError(
            String.format(
                "Label '%s' is duplicated in the '%s' attribute of rule '%s'",
                label, attribute.getName(), rule.getName()),
            eventHandler);
      }
    }
  }

  /**
   * Report an error if the rule has a timeout or size attribute that is not a legal value. These
   * attributes appear on all tests.
   *
   * @param rule the rule to check
   * @param eventHandler the eventHandler to use to report the duplicated deps
   */
  private static void checkForValidSizeAndTimeoutValues(Rule rule, EventHandler eventHandler) {
    if (rule.getRuleClassObject().getAttributeProvider().hasAttr("size", Type.STRING)) {
      String size = NonconfigurableAttributeMapper.of(rule).get("size", Type.STRING);
      if (TestSize.getTestSize(size) == null) {
        rule.reportError(
            String.format("In rule '%s', size '%s' is not a valid size.", rule.getName(), size),
            eventHandler);
      }
    }
    if (rule.getRuleClassObject().getAttributeProvider().hasAttr("timeout", Type.STRING)) {
      String timeout = NonconfigurableAttributeMapper.of(rule).get("timeout", Type.STRING);
      if (TestTimeout.getTestTimeout(timeout) == null) {
        rule.reportError(
            String.format(
                "In rule '%s', timeout '%s' is not a valid timeout.", rule.getName(), timeout),
            eventHandler);
      }
    }
  }

  private static void checkAspectAllowedValues(Rule rule, EventHandler eventHandler) {
    if (rule.hasAspects()) {
      for (Attribute attrOfRule : rule.getAttributeProvider().getAttributes()) {
        for (Aspect aspect : attrOfRule.getAspects(rule)) {
          for (Attribute attrOfAspect : aspect.getDefinition().getAttributes().values()) {
            // By this point the AspectDefinition has been created and values assigned.
            if (attrOfAspect.checkAllowedValues()) {
              PredicateWithMessage<Object> allowedValues = attrOfAspect.getAllowedValues();
              Object value = attrOfAspect.getDefaultValue(null);
              if (!allowedValues.apply(value)) {
                if (RawAttributeMapper.of(rule).isConfigurable(attrOfAspect.getName())) {
                  rule.reportError(
                      String.format(
                          "%s: attribute '%s' has a select() and aspect %s also declares "
                              + "'%s'. Aspect attributes don't currently support select().",
                          rule.getLabel(),
                          attrOfAspect.getName(),
                          aspect.getDefinition().getName(),
                          rule.getLabel()),
                      eventHandler);
                } else {
                  rule.reportError(
                      String.format(
                          "%s: invalid value in '%s' attribute: %s",
                          rule.getLabel(),
                          attrOfAspect.getName(),
                          allowedValues.getErrorReason(value)),
                      eventHandler);
                }
              }
            }
          }
        }
      }
    }
  }

  @Override
  public String toString() {
    return ruleClassId.name();
  }

  public boolean isDocumented() {
    return documented;
  }

  /**
   * Returns true iff the outputs of this rule should be created beneath the <i>bin</i> directory,
   * false if beneath <i>genfiles</i>. For most rule classes, this is a constant, but for genrule,
   * it is a property of the individual rule instance, derived from the 'output_to_bindir'
   * attribute; see Rule.outputsToBindir().
   */
  public boolean outputsToBindir() {
    return outputsToBindir;
  }

  /** Returns this RuleClass's custom Starlark rule implementation. */
  @Nullable
  public StarlarkCallable getConfiguredTargetFunction() {
    return configuredTargetFunction;
  }

  @Nullable
  public BuildSetting getBuildSetting() {
    return buildSetting;
  }

  /** Returns a function that computes the options referenced by a rule. */
  public Function<? super Rule, ? extends Set<String>> getOptionReferenceFunction() {
    return optionReferenceFunction;
  }

  /**
   * For Starlark rule classes, returns this RuleClass's rule definition environment's label, which
   * is never null. Is null for native rules' RuleClass objects.
   *
   * <p>In certain unusual cases (for example, analysis test rule classes), the values of {@link
   * #getRuleDefinitionEnvironmentLabel()} and {@link #getStarlarkExtensionLabel()} may differ.
   */
  // TODO(b/366027483): unify starlarkExtensionLabel and ruleDefinitionEnvironmentLabel.
  @Nullable
  @Override
  public Label getRuleDefinitionEnvironmentLabel() {
    return ruleDefinitionEnvironmentLabel;
  }

  /**
   * Returns the digest for the RuleClass's rule definition environment, a hash of the .bzl file
   * defining the rule class and all the .bzl files it transitively loads. Null for native rules'
   * RuleClass objects.
   *
   * <p>This digest is sensitive to any changes in the declaration of the RuleClass itself,
   * including changes in the .bzl files it transitively loads, but it is not unique: all
   * RuleClasses defined within in the same .bzl file have the same digest.
   *
   * <p>To uniquely identify a rule class, we need the triple: ({@link
   * #getRuleDefinitionEnvironmentLabel()}, {@link #getRuleDefinitionEnvironmentDigest()}, {@link
   * #getName()}) The first two components are collectively known as the "rule definition
   * environment". Dependency analysis may compare these triples to detect whether a change to a
   * rule definition might have consequences for a rule instance that has not otherwise changed.
   *
   * <p>Note: this concept of rule definition environment is not related to the {@link
   * com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment} interface.
   */
  @Nullable
  public byte[] getRuleDefinitionEnvironmentDigest() {
    return ruleDefinitionEnvironmentDigest;
  }

  /** Returns true if this RuleClass is a Starlark-defined RuleClass. */
  @Override
  public boolean isStarlark() {
    return isStarlark;
  }

  /**
   * If this is a Starlark-defined rule class which had been exported, returns the label of the
   * Starlark file (typically a .bzl file, except for analysis test rule classes where it is a BUILD
   * file) where the rule definition was exported, or null otherwise.
   *
   * <p>If a Starlark rule class has been exported, the tuple (rule name, starlark extension label)
   * uniquely identifies it.
   *
   * <p>In certain unusual cases (for example, analysis test rule classes), the values of {@link
   * #getRuleDefinitionEnvironmentLabel()} and {@link #getStarlarkExtensionLabel()} may differ.
   */
  // TODO(b/366027483): unify starlarkExtensionLabel and ruleDefinitionEnvironmentLabel.
  @Nullable
  public Label getStarlarkExtensionLabel() {
    return starlarkExtensionLabel;
  }

  /**
   * If this is a Starlark-defined rule class which had been defined with a documentation string,
   * i.e. via {@code rule(doc = "...")}), returns that documentation string, or null otherwise.
   */
  @Nullable
  public String getStarlarkDocumentation() {
    return starlarkDocumentation;
  }

  /** Returns true if this RuleClass can be extended. */
  public boolean isExtendable() {
    return extendable;
  }

  @Nullable
  public Label getExtendableAllowlist() {
    return extendableAllowlist;
  }

  /** Returns true if this RuleClass is Starlark-defined and is subject to analysis-time tests. */
  public boolean isStarlarkTestable() {
    return starlarkTestable;
  }

  /** Returns true if rules of this class can be made available for dependency resolution. */
  @Override
  public boolean isDependencyResolutionRule() {
    return dependencyResolutionRule;
  }

  /** Whether this rule class is a materializer rule. */
  @Override
  public boolean isMaterializerRule() {
    return isMaterializerRule;
  }

  /** Whether this materializer rule allows real deps. */
  @Override
  public boolean materializerRuleAllowsRealDeps() {
    return materializerRuleAllowsRealDeps;
  }

  /** Returns true if this rule class outputs a default executable for every rule. */
  public boolean isExecutableStarlark() {
    return isExecutableStarlark;
  }

  /** Returns true if this rule class is an analysis test (set by analysis_test = true). */
  boolean isAnalysisTest() {
    return isAnalysisTest;
  }

  /**
   * Returns true if this rule class has at least one attribute with an analysis test transition. (A
   * starlark-defined transition using analysis_test_transition()).
   */
  boolean hasAnalysisTestTransition() {
    return hasAnalysisTestTransition;
  }

  /** Returns a list of AllowlistChecker to check. */
  public ImmutableList<AllowlistChecker> getAllowlistCheckers() {
    return allowlistCheckers;
  }

  /**
   * If true, no rule of this class ever declares a license regardless of what the rule's or
   * package's <code>licenses</code> attribute says.
   *
   * <p>This is useful for rule types that don't make sense for license checking.
   */
  boolean ignoreLicenses() {
    return attributeProvider.ignoreLicenses();
  }

  public ImmutableSet<ToolchainTypeRequirement> getToolchainTypes() {
    return toolchainTypes;
  }

  boolean useToolchainResolution(Rule rule) {
    return this.toolchainResolutionMode.useToolchainResolution(rule);
  }

  public ImmutableSet<Label> getExecutionPlatformConstraints() {
    return executionPlatformConstraints;
  }

  public ImmutableMap<String, DeclaredExecGroup> getDeclaredExecGroups() {
    return declaredExecGroups;
  }

  public AutoExecGroupsMode getAutoExecGroupsMode() {
    return autoExecGroupsMode;
  }

  OutputFile.Kind getOutputFileKind() {
    return outputFileKind;
  }

  /**
   * Returns true if this rule is a <code>license()</code> as described in
   * https://docs.google.com/document/d/1uwBuhAoBNrw8tmFs-NxlssI6VRolidGYdYqagLqHWt8/edit# or
   * similar metadata.
   *
   * <p>The intended use is to detect if this rule is of a type which would be used in <code>
   * default_package_metadata</code>, so that we don't apply it to an instanced of itself when
   * <code>applicable_metadata</code> is left unset. Doing so causes a self-referential loop. To
   * prevent that, we are overly cautious at this time, treating all rules from <code>@rules_license
   * </code> as potential metadata rules.
   *
   * <p>Most users will only use declarations from <code>@rules_license</code>. If they which to
   * create organization local rules, they must be careful to avoid loops by explicitly setting
   * <code>applicable_metadata</code> on each of the metadata targets they define, so that default
   * processing is not an issue.
   */
  public boolean isPackageMetadataRule() {
    // If it was not defined in Starlark, it can not be a new style package metadata rule.
    if (ruleDefinitionEnvironmentLabel == null) {
      return false;
    }
    if (ruleDefinitionEnvironmentLabel.getRepository().getName().equals("rules_license")) {
      // For now, we treat all rules in rules_license as potenial metadate rules.
      // In the future we should add a way to disambiguate the two. The least invasive
      // thing is to add a hidden attribute to mark metadata rules. That attribute
      // could have a default value referencing @rules_license//<something>. That style
      // of checking would allow users to apply it to their own metadata rules. We are
      // not building it today because the exact needs are not clear.
      return true;
    }
    // BEGIN-INTERNAL
    // TODO(aiuto): This is a Google-ism, remove from Bazel.
    String packageName = ruleDefinitionEnvironmentLabel.getPackageName();
    if (packageName.startsWith("tools/build_defs/license")
        || packageName.startsWith("third_party/rules_license")) {
      return true;
    }
    // END-INTERNAL
    return false;
  }

  public ImmutableSet<? extends StarlarkSubruleApi> getSubrules() {
    Preconditions.checkState(isStarlark());
    return subrules;
  }
}
