// 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.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.auto.value.AutoBuilder;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.cmdline.BazelModuleContext;
import com.google.devtools.build.lib.cmdline.BazelModuleContext.LoadGraphVisitor;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.StarlarkThreadContext;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.collect.nestedset.NestedSetsShouldBeInternedByEquality;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.packages.Package.Builder.PackageLimits;
import com.google.devtools.build.lib.packages.Package.Builder.PackageSettings;
import com.google.devtools.build.lib.packages.TargetRecorder.MacroNamespaceViolationException;
import com.google.devtools.build.lib.packages.TargetRecorder.NameConflictException;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.PackageLoading;
import com.google.devtools.build.lib.server.FailureDetails.PackageLoading.Code;
import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.SerializationContext;
import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.util.HashCodes;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Semaphore;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Module;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.eval.SymbolGenerator;
import net.starlark.java.syntax.Location;

/**
 * A package, which is a container of {@link Rule}s, each of which contains a dictionary of named
 * attributes.
 *
 * <p>Package instances are intended to be immutable and for all practical purposes can be treated
 * as such. Note, however, that some member variables exposed via the public interface are not
 * strictly immutable, so until their types are guaranteed immutable we're not applying the
 * {@code @Immutable} annotation here.
 *
 * <p>This class should not be extended - it's only non-final for mocking!
 *
 * <p>When changing this class, make sure to make corresponding changes to serialization!
 */
@SuppressWarnings("JavaLangClash")
public class Package extends Packageoid {

  // TODO(bazel-team): This class and its builder are ginormous. Future refactoring work might
  // attempt to separate the concerns of:
  //   - instantiating targets/macros, adding them to the package, and accessing/indexing them
  //     afterwards
  //   - utility logical like validating names, checking for conflicts, etc.
  //   - tracking and enforcement of limits

  // ==== Static fields and enums ====

  /**
   * How to enforce config_setting visibility settings.
   *
   * <p>This is a temporary setting in service of https://github.com/bazelbuild/bazel/issues/12669.
   * After enough depot cleanup, config_setting will have the same visibility enforcement as all
   * other rules.
   */
  public enum ConfigSettingVisibilityPolicy {
    /** Don't enforce visibility for any config_setting. */
    LEGACY_OFF,
    /** Honor explicit visibility settings on config_setting, else use //visibility:public. */
    DEFAULT_PUBLIC,
    /** Enforce config_setting visibility exactly the same as all other rules. */
    DEFAULT_STANDARD
  }

  // ==== Target and macro fields ====

  // Can be changed during BUILD file evaluation due to exports_files() modifying its visibility.
  // Cannot be in Declarations because, since it's a Target, it holds a back reference to this
  // Package object.
  private InputFile buildFile;

  /**
   * The collection of all symbolic macro instances defined in this package, indexed by their {@link
   * MacroInstance#getId id} (not name). Null until the package is fully initialized by its
   * builder's {@code finishBuild()}.
   */
  // TODO(bazel-team): Consider enforcing that macro namespaces are "exclusive", meaning that target
  // names may only suffix a macro name when the target is created (transitively) within the macro.
  // This would be a major change that would break the (common) use case where a BUILD file
  // declares both "foo" and "foo_test".
  @Nullable private ImmutableSortedMap<String, MacroInstance> macros;

  /**
   * A map from names of targets declared in a symbolic macro which violate macro naming rules, such
   * as "lib%{name}-src.jar" implicit outputs in java rules, to the name of the macro instance where
   * they were declared.
   *
   * <p>Initialized by the builder in {@link Builder#finishBuild}.
   */
  @Nullable private ImmutableMap<String, String> macroNamespaceViolatingTargets;

  /**
   * A map from names of targets declared in a symbolic macro to the (innermost) macro instance
   * where they were declared. Omits targets not declared in symbolic macros.
   *
   * <p>Null for packages produced by deserialization.
   */
  // TODO: #19922 - If this field were made serializable (currently it's not), it would subsume
  // macroNamespaceViolatingTargets, since we can just map the target to its macro and then check
  // whether it is in the macro's namespace.
  //
  // TODO: #19922 - Don't maintain this extra map of all macro-instantiated targets. We have a
  // couple options:
  //   1) Have Target store a reference to its declaring MacroInstance directly. To avoid adding a
  //      field to that class (a not insignificant cost), we can merge it with the reference to its
  //      package: If we're not in a macro, we point to the package, and if we are, we point to the
  //      innermost macro, and hop to the MacroInstance to get a reference to the Package (or parent
  //      macro).
  //   2) To support lazy macro evaluation, we'll probably need a prefix trie in Package to find the
  //      macros whose namespaces contain the requested target name. For targets that respect their
  //      macro's namespace, we could just look them up in the trie. This assumes we already know
  //      whether the target is well-named, which we wouldn't if we got rid of
  //      macroNamespaceViolatingTargets.
  @Nullable private ImmutableMap<String, MacroInstance> targetsToDeclaringMacro;

  /**
   * A map from names of targets declared in a symbolic macro to the package where the macro that
   * declared it was defined, as per {@link MacroInstance#getDefinitionPackage}. Omits targets not
   * declared in symbolic macros.
   *
   * <p>Null for packages not produced by deserialization.
   */
  @Nullable private ImmutableMap<String, PackageIdentifier> targetsToDeclaringPackage;

  // ==== Constructor ====

  /**
   * Constructs a new (incomplete) Package instance. Intended only for use by {@link
   * Package.Builder}.
   *
   * <p>Packages and Targets refer to one another. Therefore, the builder needs to have a Package
   * instance on-hand before it can associate any targets with the package. The {@link
   * Package.Metadata} fields like the package's name must be known before that point, while other
   * fields are filled in only when the builder calls {@link Builder#finishBuild}.
   */
  // TODO(#19922): Better separate fields that must be known a priori from those determined through
  // BUILD evaluation.
  private Package(Metadata metadata, Declarations declarations) {
    super(metadata, declarations);
  }

  // ==== General package metadata accessors ====

  /**
   * Returns the name of this package. If this build is using external repositories then this name
   * may not be unique!
   */
  public String getName() {
    return metadata.getName();
  }

  /** Like {@link #getName}, but has type {@code PathFragment}. */
  public PathFragment getNameFragment() {
    return getPackageIdentifier().getPackageFragment();
  }

  /**
   * Returns the filename of the BUILD file which defines this package. The parent directory of the
   * BUILD file is the package directory.
   */
  public RootedPath getFilename() {
    return metadata.buildFilename();
  }

  /** Returns the directory containing the package's BUILD file. */
  public Path getPackageDirectory() {
    return metadata.getPackageDirectory();
  }

  /**
   * How to enforce visibility on <code>config_setting</code> See {@link
   * ConfigSettingVisibilityPolicy} for details.
   */
  @Nullable
  public ConfigSettingVisibilityPolicy getConfigSettingVisibilityPolicy() {
    return metadata.configSettingVisibilityPolicy();
  }

  /** Convenience wrapper for {@link Metadata#workspaceName} */
  public String getWorkspaceName() {
    return getMetadata().workspaceName();
  }

  /** Returns the InputFile target for this package's BUILD file. */
  public InputFile getBuildFile() {
    return buildFile;
  }

  /** Convenience wrapper for {@link Declarations#getPackageArgs} */
  public PackageArgs getPackageArgs() {
    return getDeclarations().getPackageArgs();
  }

  /** Convenience wrapper for {@link Declarations#getMakeEnvironment} */
  public ImmutableMap<String, String> getMakeEnvironment() {
    return getDeclarations().getMakeEnvironment();
  }

  /**
   * Returns the root of the source tree beneath which this package's BUILD file was found.
   *
   * <p>Assumes invariant: {@code
   * getSourceRoot().getRelative(packageId.getSourceRoot()).equals(getPackageDirectory())}
   */
  public Root getSourceRoot() {
    return metadata.sourceRoot();
  }

  private static ImmutableList<Label> computeTransitiveLoads(Iterable<Module> directLoads) {
    Set<Label> loads = new LinkedHashSet<>();
    BazelModuleContext.visitLoadGraphRecursively(directLoads, loads::add);
    return ImmutableList.copyOf(loads);
  }

  // ==== Target and macro accessors ====

  /**
   * Returns a (read-only, ordered) iterable of all the targets belonging to this package which are
   * instances of the specified class.
   */
  public <T extends Target> Iterable<T> getTargets(Class<T> targetClass) {
    return Iterables.filter(targets, targetClass);
  }

  /**
   * Returns the rule that corresponds to a particular BUILD target name. Useful for walking through
   * the dependency graph of a target. Fails if the target is not a Rule.
   */
  public Rule getRule(String targetName) {
    return (Rule) checkNotNull(getTargetOrNull(targetName), targetName);
  }

  /**
   * Returns a map from names of targets declared in a symbolic macro which violate macro naming
   * rules, such as "lib%{name}-src.jar" implicit outputs in java rules, to the name of the macro
   * instance where they were declared.
   */
  ImmutableMap<String, String> getMacroNamespaceViolatingTargets() {
    Preconditions.checkNotNull(
        macroNamespaceViolatingTargets,
        "This method is only available after the package has been loaded.");
    return macroNamespaceViolatingTargets;
  }

  /**
   * Returns a map from names of targets declared in a symbolic macro to the package containing said
   * macro's .bzl code.
   */
  ImmutableMap<String, PackageIdentifier> getTargetsToDeclaringPackage() {
    if (targetsToDeclaringPackage != null) {
      return targetsToDeclaringPackage;
    } else {
      ImmutableMap.Builder<String, PackageIdentifier> result = ImmutableMap.builder();
      for (Map.Entry<String, MacroInstance> entry : targetsToDeclaringMacro.entrySet()) {
        result.put(entry.getKey(), entry.getValue().getDefinitionPackage());
      }
      return result.buildOrThrow();
    }
  }

  @Override
  public void checkMacroNamespaceCompliance(Target target) throws MacroNamespaceViolationException {
    Preconditions.checkArgument(
        this.equals(target.getPackage()), "Target must belong to this package");
    @Nullable
    String macroNamespaceViolated = getMacroNamespaceViolatingTargets().get(target.getName());
    if (macroNamespaceViolated != null) {
      throw new MacroNamespaceViolationException(
          String.format(
              "Target %s declared in symbolic macro '%s' violates macro naming rules and cannot be"
                  + " built. %s",
              target.getLabel(), macroNamespaceViolated, TargetRecorder.MACRO_NAMING_RULES),
          target);
    }
  }

  @Override
  public Target getTarget(String targetName) throws NoSuchTargetException {
    Target target = getTargetOrNull(targetName);
    if (target != null) {
      return target;
    }

    Label label;
    try {
      label = Label.create(metadata.packageIdentifier(), targetName);
    } catch (LabelSyntaxException e) {
      throw new IllegalArgumentException(targetName, e);
    }

    if (metadata.succinctTargetNotFoundErrors()) {
      throw new NoSuchTargetException(
          label, String.format("target '%s' not declared in package '%s'", targetName, getName()));
    } else {
      String alternateTargetSuggestion =
          getAlternateTargetSuggestion(metadata, targetName, targets);
      throw new NoSuchTargetException(
          label,
          String.format(
              "target '%s' not declared in package '%s' defined by %s%s",
              targetName,
              getName(),
              metadata.buildFilename().asPath().getPathString(),
              alternateTargetSuggestion));
    }
  }

  static String getAlternateTargetSuggestion(
      Metadata metadata, String targetName, ImmutableList<Target> otherTargets) {
    // If there's a file on the disk that's not mentioned in the BUILD file,
    // produce a more informative error.  NOTE! this code path is only executed
    // on failure, which is (relatively) very rare.  In the common case no
    // stat(2) is executed.
    Path filename = metadata.getPackageDirectory().getRelative(targetName);
    if (!PathFragment.isNormalized(targetName) || "*".equals(targetName)) {
      // Don't check for file existence if the target name is not normalized
      // because the error message would be confusing and wrong. If the
      // targetName is "foo/bar/.", and there is a directory "foo/bar", it
      // doesn't mean that "//pkg:foo/bar/." is a valid label.
      // Also don't check if the target name is a single * character since
      // it's invalid on Windows.
      return "";
    } else if (filename.isDirectory()) {
      return "; however, a source directory of this name exists.  (Perhaps add "
          + "'exports_files([\""
          + targetName
          + "\"])' to "
          + getRepoRelativeBuildFilePathString(metadata)
          + ", or define a "
          + "filegroup?)";
    } else if (filename.exists()) {
      return "; however, a source file of this name exists.  (Perhaps add "
          + "'exports_files([\""
          + targetName
          + "\"])' to "
          + getRepoRelativeBuildFilePathString(metadata)
          + "?)";
    } else {
      return TargetSuggester.suggestTargets(
          targetName, Lists.transform(otherTargets, Target::getName));
    }
  }

  private static String getRepoRelativeBuildFilePathString(Metadata metadata) {
    return metadata
        .packageIdentifier()
        .getPackageFragment()
        .getRelative(metadata.buildFilename().asPath().getBaseName())
        .getPathString();
  }

  /**
   * Returns all symbolic macros defined in the package, indexed by {@link MacroInstance#getId id}.
   *
   * <p>Note that {@code MacroInstance}s hold just the information known at the time a macro was
   * declared, even though by the time the {@code Package} is fully constructed we already have
   * fully evaluated these macros.
   */
  public ImmutableMap<String, MacroInstance> getMacrosById() {
    return macros;
  }

  /**
   * Returns the (innermost) symbolic macro instance that declared the given target, or null if the
   * target was not created in a symbolic macro.
   *
   * <p>Throws {@link IllegalArgumentException} if the given name is not a target in this package.
   *
   * <p>For packages produced by deserialization, this information is not available and {@code
   * IllegalStateException} is thrown.
   */
  @Nullable
  public MacroInstance getDeclaringMacroForTarget(String target) {
    Preconditions.checkState(
        targetsToDeclaringMacro != null,
        "Cannot retrieve MacroInstance information from deserialized packages");
    Preconditions.checkArgument(getTargetOrNull(target) != null, "unknown target '%s'", target);
    return targetsToDeclaringMacro.get(target);
  }

  /**
   * Returns the id of the package where the (innermost) macro that declared the given target was
   * defined (as per {@link MacroInstance#getDefinitionLocation}), or null if the target was not
   * created in a symbolic macro.
   *
   * <p>The caller should interpret a null result to mean that the declaration location of the
   * target is this package.
   *
   * <p>Throws {@link IllegalArgumentException} if the given name is not a target in this package.
   */
  @Nullable
  public PackageIdentifier getDeclaringPackageForTargetIfInMacro(String target) {
    Preconditions.checkArgument(getTargetOrNull(target) != null, "unknown target '%s'", target);
    // Exactly one of targetsToDeclaringMacro and targetsToDeclaringPackage is non-null, depending
    // on whether this package was produced by deserialization.
    if (targetsToDeclaringMacro != null) {
      MacroInstance macro = targetsToDeclaringMacro.get(target);
      return macro != null ? macro.getDefinitionPackage() : null;
    } else {
      return targetsToDeclaringPackage.get(target);
    }
  }

  // ==== Stringification / debugging ====

  @Override
  public String toString() {
    return "Package("
        + getName()
        + ")="
        + (targets != null ? getTargets(Rule.class) : "initializing...");
  }

  @Override
  public String getShortDescription() {
    return "package " + getPackageIdentifier().getCanonicalForm();
  }

  /**
   * Dumps the package for debugging. Do not depend on the exact format/contents of this debugging
   * output.
   */
  public void dump(PrintStream out) {
    out.println("  Package " + getName() + " (" + metadata.buildFilename().asPath() + ")");

    // Rules:
    out.println("    Rules");
    for (Rule rule : getTargets(Rule.class)) {
      out.println("      " + rule.getTargetKind() + " " + rule.getLabel());
      for (Attribute attr : rule.getAttributes()) {
        for (Object possibleValue :
            AggregatingAttributeMapper.of(rule).visitAttribute(attr.getName(), attr.getType())) {
          out.println("        " + attr.getName() + " = " + possibleValue);
        }
      }
    }

    // Files:
    out.println("    Files");
    for (FileTarget file : getTargets(FileTarget.class)) {
      out.print("      " + file.getTargetKind() + " " + file.getLabel());
      if (file instanceof OutputFile) {
        out.println(" (generated by " + ((OutputFile) file).getGeneratingRule().getLabel() + ")");
      } else {
        out.println();
      }
    }
  }

  // ==== Error reporting ====

  /**
   * Returns an error {@link Event} with {@link Location} and {@link DetailedExitCode} properties.
   */
  public static Event error(Location location, String message, Code code) {
    return errorWithDetailedExitCode(
        location,
        message,
        DetailedExitCode.of(
            FailureDetail.newBuilder()
                .setMessage(message)
                .setPackageLoading(PackageLoading.newBuilder().setCode(code))
                .build()));
  }

  /** Similar to {@link #error} but with a custom {@link DetailedExitCode}. */
  public static Event errorWithDetailedExitCode(
      Location location, String message, DetailedExitCode detailedExitCode) {
    Event error = Event.error(location, message);
    return error.withProperty(DetailedExitCode.class, detailedExitCode);
  }

  /**
   * If {@code pkg.containsErrors()}, sends an errorful "package contains errors" {@link Event}
   * (augmented with {@code pkg.getFailureDetail()}, if present) to the given {@link EventHandler}.
   */
  public static void maybeAddPackageContainsErrorsEventToHandler(
      Package pkg, EventHandler eventHandler) {
    if (pkg.containsErrors()) {
      eventHandler.handle(
          Event.error(
              String.format(
                  "package contains errors: %s%s",
                  pkg.getNameFragment(),
                  pkg.getFailureDetail() != null
                      ? ": " + pkg.getFailureDetail().getMessage()
                      : "")));
    }
  }

  /**
   * Given a {@link FailureDetail} and target, returns a modified {@code FailureDetail} that
   * attributes its error to the target.
   *
   * <p>If the given detail is null, then a generic {@link Code#TARGET_MISSING} detail identifying
   * the target is returned.
   */
  public static FailureDetail contextualizeFailureDetailForTarget(
      @Nullable FailureDetail failureDetail, Target target) {
    String prefix =
        "Target '" + target.getLabel() + "' contains an error and its package is in error";
    if (failureDetail == null) {
      return FailureDetail.newBuilder()
          .setMessage(prefix)
          .setPackageLoading(PackageLoading.newBuilder().setCode(Code.TARGET_MISSING))
          .build();
    }
    return failureDetail.toBuilder().setMessage(prefix + ": " + failureDetail.getMessage()).build();
  }

  // ==== Builders ====

  /** Returns a new {@link Builder} suitable for constructing an ordinary package. */
  public static Builder newPackageBuilder(
      PackageSettings packageSettings,
      PackageIdentifier id,
      RootedPath filename,
      String workspaceName,
      Optional<String> associatedModuleName,
      Optional<String> associatedModuleVersion,
      boolean noImplicitFileExport,
      boolean simplifyUnconditionalSelectsInRuleAttrs,
      boolean symbolicMacroStrictAttrs,
      RepositoryMapping repositoryMapping,
      RepositoryMapping mainRepositoryMapping,
      @Nullable Semaphore cpuBoundSemaphore,
      PackageOverheadEstimator packageOverheadEstimator,
      @Nullable ImmutableMap<Location, String> generatorMap,
      // TODO(bazel-team): See Builder() constructor comment about use of null for this param.
      @Nullable ConfigSettingVisibilityPolicy configSettingVisibilityPolicy,
      @Nullable Globber globber,
      boolean enableNameConflictChecking,
      boolean trackFullMacroInformation,
      PackageValidator packageValidator,
      PackageLimits packageLimits) {
    return new Builder(
        Metadata.builder()
            .packageIdentifier(id)
            .buildFilename(filename)
            .workspaceName(workspaceName)
            .repositoryMapping(repositoryMapping)
            .associatedModuleName(associatedModuleName)
            .associatedModuleVersion(associatedModuleVersion)
            .configSettingVisibilityPolicy(configSettingVisibilityPolicy)
            .succinctTargetNotFoundErrors(packageSettings.succinctTargetNotFoundErrors())
            .build(),
        new Declarations.Builder(),
        SymbolGenerator.create(id),
        packageSettings.precomputeTransitiveLoads(),
        noImplicitFileExport,
        simplifyUnconditionalSelectsInRuleAttrs,
        symbolicMacroStrictAttrs,
        mainRepositoryMapping,
        cpuBoundSemaphore,
        packageOverheadEstimator,
        generatorMap,
        globber,
        enableNameConflictChecking,
        trackFullMacroInformation,
        packageValidator,
        packageLimits);
  }

  /** Returns a new {@link Builder} suitable for constructing a package from package pieces. */
  public static Builder newPackageFromPackagePiecesBuilder(
      PackageSettings packageSettings,
      Metadata metadata,
      Declarations declarations,
      boolean noImplicitFileExport,
      boolean simplifyUnconditionalSelectsInRuleAttrs,
      boolean symbolicMacroStrictAttrs,
      RepositoryMapping mainRepositoryMapping,
      @Nullable Semaphore cpuBoundSemaphore,
      PackageOverheadEstimator packageOverheadEstimator,
      @Nullable ImmutableMap<Location, String> generatorMap,
      @Nullable Globber globber,
      boolean enableNameConflictChecking,
      boolean trackFullMacroInformation,
      PackageValidator packageValidator,
      PackageLimits packageLimits,
      InputFile buildFile) {
    Builder builder =
        new Builder(
            metadata,
            declarations.checkImmutable(),
            SymbolGenerator.create(metadata.packageIdentifier()),
            packageSettings.precomputeTransitiveLoads(),
            noImplicitFileExport,
            simplifyUnconditionalSelectsInRuleAttrs,
            symbolicMacroStrictAttrs,
            mainRepositoryMapping,
            cpuBoundSemaphore,
            packageOverheadEstimator,
            generatorMap,
            globber,
            enableNameConflictChecking,
            trackFullMacroInformation,
            packageValidator,
            packageLimits);
    checkArgument(
        buildFile.getPackageMetadata().packageIdentifier().equals(metadata.packageIdentifier()));
    builder.recorder.replaceInputFileUnchecked(buildFile);
    builder.setBuildFile(buildFile);
    return builder;
  }

  // ==== Non-trivial nested classes ====

  /**
   * Common base class for builders for {@link Package} and {@link PackagePiece.ForBuildFile}
   * objects, containing the shared logic for processing top-level BUILD file declarations, for
   * example the "package" callable.
   */
  // TODO(https://github.com/bazelbuild/bazel/issues/23852): this class should be moved elsewhere -
  // probably to an inner clas of Packageoid - but that would require also moving Declarations and
  // PackageArgs, so that their private fields can be mutated only by the builder.
  public abstract static class AbstractBuilder extends TargetDefinitionContext {
    private final boolean precomputeTransitiveLoads;

    /** True iff the "package" function has already been called in this BUILD file. */
    private boolean packageFunctionUsed;

    // The following field is populated by setLoads() and should be used only for analysis_test.
    // TODO: b/291752414 - This should be serialized but only if needed for analysis_test;
    // serializing unconditionally would interfere with lazy symbolic macro change pruning.
    private byte[] transitiveBzlDigest = new byte[0];

    protected final boolean noImplicitFileExport;

    /** Retrieves this object from a Starlark thread. Returns null if not present. */
    @Nullable
    public static AbstractBuilder fromOrNull(StarlarkThread thread) {
      StarlarkThreadContext ctx = thread.getThreadLocal(StarlarkThreadContext.class);
      return ctx instanceof AbstractBuilder builder ? builder : null;
    }

    /**
     * Retrieves this object from a Starlark thread. If not present, throws an {@link EvalException}
     * with an error message indicating that {@code what} can only be used in a BUILD file or a
     * legacy macro.
     */
    @CanIgnoreReturnValue
    public static AbstractBuilder fromOrFailAllowBuildOnly(
        StarlarkThread thread, String what, String participle) throws EvalException {
      @Nullable StarlarkThreadContext ctx = thread.getThreadLocal(StarlarkThreadContext.class);
      if (ctx instanceof AbstractBuilder builder
          && builder.recorder.getCurrentMacroFrame() == null) {
        return builder;
      }
      throw newFromOrFailException(
          what, participle, thread.getSemantics(), EnumSet.of(FromOrFailMode.NO_MACROS));
    }

    /**
     * Retrieves this object from a Starlark thread. If not present, throws an {@link EvalException}
     * with an error message indicating that {@code what} can only be used in a BUILD file or a
     * legacy macro.
     */
    @CanIgnoreReturnValue
    public static AbstractBuilder fromOrFailAllowBuildOnly(StarlarkThread thread, String what)
        throws EvalException {
      return fromOrFailAllowBuildOnly(thread, what, "used");
    }

    // TODO(#19922): Require this to be set before BUILD evaluation.
    @CanIgnoreReturnValue
    public AbstractBuilder setLoads(Iterable<Module> directLoads) {
      Declarations.Builder declarationsBuilder =
          pkg.getDeclarations().checkMutable().checkLoadsNotSet();
      if (precomputeTransitiveLoads) {
        declarationsBuilder.setTransitiveLoads(computeTransitiveLoads(directLoads));
      } else {
        declarationsBuilder.setDirectLoads(ImmutableList.copyOf(directLoads));
      }
      Fingerprint fp = new Fingerprint();
      for (Module module : directLoads) {
        fp.addBytes(BazelModuleContext.of(module).bzlTransitiveDigest());
      }
      this.transitiveBzlDigest = fp.digestAndReset();
      return this;
    }

    public byte[] getTransitiveBzlDigest() {
      Preconditions.checkState(transitiveBzlDigest.length != 0);
      return transitiveBzlDigest;
    }

    static ImmutableList<Label> computeTransitiveLoads(Iterable<Module> directLoads) {
      Set<Label> loads = new LinkedHashSet<>();
      BazelModuleContext.visitLoadGraphRecursively(directLoads, loads::add);
      return ImmutableList.copyOf(loads);
    }

    @CanIgnoreReturnValue
    AbstractBuilder setTransitiveLoadsForDeserialization(ImmutableList<Label> transitiveLoads) {
      pkg.getDeclarations().checkMutable().checkLoadsNotSet().setTransitiveLoads(transitiveLoads);
      return this;
    }

    public void mergePackageArgsFrom(PackageArgs packageArgs) {
      pkg.getDeclarations().checkMutable().mergePackageArgsFrom(packageArgs);
    }

    public void mergePackageArgsFrom(PackageArgs.Builder builder) {
      mergePackageArgsFrom(builder.build());
    }

    public void setMakeVariable(String name, String value) {
      makeEnv.put(name, value);
    }

    /** Returns whether the "package" function has been called yet */
    boolean isPackageFunctionUsed() {
      return packageFunctionUsed;
    }

    void setPackageFunctionUsed() {
      packageFunctionUsed = true;
    }

    public Set<Target> getTargets() {
      return recorder.getTargets();
    }

    /** Adds an environment group to the package. Not valid within symbolic macros. */
    void addEnvironmentGroup(
        String name,
        List<Label> environments,
        List<Label> defaults,
        EventHandler eventHandler,
        Location location)
        throws NameConflictException, LabelSyntaxException {
      Preconditions.checkState(currentMacro() == null);

      if (hasDuplicateLabels(environments, name, "environments", location, eventHandler)
          || hasDuplicateLabels(defaults, name, "defaults", location, eventHandler)) {
        setContainsErrors();
        return;
      }

      EnvironmentGroup group =
          new EnvironmentGroup(createLabel(name), pkg, environments, defaults, location);
      recorder.addTarget(group);

      // Invariant: once group is inserted into targets, it must also:
      // (a) be inserted into environmentGroups, or
      // (b) have its group.processMemberEnvironments called.
      // Otherwise it will remain uninitialized,
      // causing crashes when it is later toString-ed.

      for (Event error : group.validateMembership()) {
        eventHandler.handle(error);
        setContainsErrors();
      }

      // For each declared environment, make sure it doesn't also belong to some other group.
      for (Label environment : group.getEnvironments()) {
        EnvironmentGroup otherGroup = environmentGroups.get(environment);
        if (otherGroup != null) {
          eventHandler.handle(
              Package.error(
                  location,
                  String.format(
                      "environment %s belongs to both %s and %s",
                      environment, group.getLabel(), otherGroup.getLabel()),
                  Code.ENVIRONMENT_IN_MULTIPLE_GROUPS));
          setContainsErrors();
          // Ensure the orphan gets (trivially) initialized.
          group.processMemberEnvironments(ImmutableMap.of());
        } else {
          environmentGroups.put(environment, group);
        }
      }
    }

    /**
     * Returns true if any labels in the given list appear multiple times, reporting an appropriate
     * error message if so.
     *
     * <p>TODO(bazel-team): apply this to all build functions (maybe automatically?), possibly
     * integrate with RuleClass.checkForDuplicateLabels.
     */
    private static boolean hasDuplicateLabels(
        List<Label> labels,
        String owner,
        String attrName,
        Location location,
        EventHandler eventHandler) {
      Set<Label> dupes = CollectionUtils.duplicatedElementsOf(labels);
      for (Label dupe : dupes) {
        eventHandler.handle(
            Package.error(
                location,
                String.format(
                    "label '%s' is duplicated in the '%s' list of '%s'", dupe, attrName, owner),
                Code.DUPLICATE_LABEL));
      }
      return !dupes.isEmpty();
    }

    protected void beforeBuildWithoutDiscoveringAssumedInputFiles() throws NoSuchPackageException {
      // We create an InputFile corresponding to the BUILD file in Builder's constructor. However,
      // the visibility of this target may be overridden with an exports_files directive, so we wait
      // until now to obtain the current instance from the targets map.
      setBuildFile((InputFile) recorder.getTargetMap().get(metadata.buildFileLabel().getName()));

      super.beforeBuild();
    }

    protected abstract void setBuildFile(InputFile buildFile);

    @CanIgnoreReturnValue
    @Override
    protected AbstractBuilder beforeBuild() throws NoSuchPackageException {
      beforeBuildWithoutDiscoveringAssumedInputFiles();
      Map<String, InputFile> newInputFiles =
          createAssumedInputFiles(pkg, recorder, noImplicitFileExport);
      for (InputFile file : newInputFiles.values()) {
        recorder.addInputFileUnchecked(file);
      }
      return this;
    }

    /**
     * Creates and returns input files for targets that have been referenced but not explicitly
     * declared in this package.
     *
     * <p>Precisely: For each label L appearing in one or more label-typed attributes of one or more
     * declarations D (either of a target or a symbolic macro), we create an {@code InputFile} for L
     * and return it in the map (keyed by its name) if all of the following are true:
     *
     * <ol>
     *   <li>L points to within the current package.
     *   <li>The package does not otherwise declare a target or macro named L.
     *   <li>D is not itself declared inside a symbolic macro.
     * </ol>
     *
     * <p>The third condition ensures that we can know all *possible* implicitly created input files
     * without evaluating any symbolic macros. However, if the label lies within one or more
     * symbolic macro's namespaces, then we do still need to evaluate those macros to determine
     * whether or not the second condition is true, i.e. whether the label points to a target the
     * macro declares (or a submacro it clashes with), or defaults to an implicitly created input
     * file.
     */
    private static Map<String, InputFile> createAssumedInputFiles(
        Packageoid pkg, TargetRecorder recorder, boolean noImplicitFileExport) {
      Map<String, InputFile> implicitlyCreatedInputFiles = new HashMap<>();

      for (Rule rule : recorder.getRules()) {
        if (!recorder.isRuleCreatedInMacro(rule)) {
          for (Label label : recorder.getRuleLabels(rule)) {
            maybeCreateAssumedInputFile(
                implicitlyCreatedInputFiles,
                pkg,
                recorder,
                noImplicitFileExport,
                label,
                rule.getLocation());
          }
        }
      }

      for (MacroInstance macro : recorder.getMacroMap().values()) {
        if (macro.getParent() == null) {
          macro.visitExplicitAttributeLabels(
              label ->
                  maybeCreateAssumedInputFile(
                      implicitlyCreatedInputFiles,
                      pkg,
                      recorder,
                      noImplicitFileExport,
                      label,
                      // TODO(bazel-team): We don't save a MacroInstance's location information yet,
                      // but when we do, use that here.
                      Location.BUILTIN));
        }
      }

      return implicitlyCreatedInputFiles;
    }

    /**
     * Adds an implicitly created input file to the given map if the label points within the current
     * package and there is no existing target or macro for that label.
     */
    private static void maybeCreateAssumedInputFile(
        Map<String, InputFile> implicitlyCreatedInputFiles,
        Packageoid pkg,
        TargetRecorder recorder,
        boolean noImplicitFileExport,
        Label label,
        Location loc) {
      String name = label.getName();
      if (!label.getPackageIdentifier().equals(pkg.getPackageIdentifier())) {
        return;
      }
      if (recorder.getTargetMap().containsKey(name)
          || recorder.hasMacroWithName(name)
          || implicitlyCreatedInputFiles.containsKey(name)) {
        return;
      }

      implicitlyCreatedInputFiles.put(
          name,
          noImplicitFileExport
              ? new PrivateVisibilityInputFile(pkg, label, loc)
              : new InputFile(pkg, label, loc));
    }

    @Override
    protected void finalBuilderValidationHook() {
      // Now all targets have been loaded, so we validate the group's member environments.
      for (EnvironmentGroup envGroup : ImmutableSet.copyOf(environmentGroups.values())) {
        List<Event> errors = envGroup.processMemberEnvironments(recorder.getTargetMap());
        if (!errors.isEmpty()) {
          Event.replayEventsOn(localEventHandler, errors);
          // TODO(bazel-team): Can't we automatically infer containsError from the presence of
          // ERRORs on our handler?
          setContainsErrors();
        }
      }
    }

    @Override
    protected void packageoidInitializationHook() {
      // Finish Package.Declarations construction.
      if (pkg.getDeclarations() instanceof Declarations.Builder declarationsBuilder) {
        if (declarationsBuilder.directLoads == null
            && declarationsBuilder.transitiveLoads == null) {
          checkState(pkg.containsErrors(), "Loads not set for error-free package");
          setLoads(ImmutableList.of());
        }
        pkg.declarations = declarationsBuilder.setMakeEnvironment(makeEnv).build();
      }
    }

    AbstractBuilder(
        Package.Metadata metadata,
        Packageoid pkg,
        SymbolGenerator<?> symbolGenerator,
        boolean precomputeTransitiveLoads,
        boolean noImplicitFileExport,
        boolean simplifyUnconditionalSelectsInRuleAttrs,
        boolean symbolicMacroStrictAttrs,
        RepositoryMapping mainRepositoryMapping,
        @Nullable Semaphore cpuBoundSemaphore,
        PackageOverheadEstimator packageOverheadEstimator,
        @Nullable ImmutableMap<Location, String> generatorMap,
        @Nullable Globber globber,
        boolean enableNameConflictChecking,
        boolean trackFullMacroInformation,
        boolean enableTargetMapSnapshotting,
        PackageValidator packageValidator,
        PackageLimits packageLimits) {
      super(
          metadata,
          pkg,
          symbolGenerator,
          simplifyUnconditionalSelectsInRuleAttrs,
          symbolicMacroStrictAttrs,
          mainRepositoryMapping,
          cpuBoundSemaphore,
          packageOverheadEstimator,
          generatorMap,
          globber,
          enableNameConflictChecking,
          trackFullMacroInformation,
          enableTargetMapSnapshotting,
          packageLimits);
      this.precomputeTransitiveLoads = precomputeTransitiveLoads;
      this.noImplicitFileExport = noImplicitFileExport;
      if (packageValidator.defaultTestOnly(metadata.packageIdentifier())) {
        mergePackageArgsFrom(PackageArgs.builder().setDefaultTestOnly(true));
      }
      // Add target for the BUILD file itself.
      // (This may be overridden by an exports_file declaration; or, for a package from package
      // pieces, by the PackagePiece.ForBuildFile's BUILD file target set in
      // newPackageFromPackagePiecesBuilder().)
      recorder.addInputFileUnchecked(
          new InputFile(pkg, metadata.buildFileLabel(), metadata.getBuildFileLocation()));
    }
  }

  /**
   * A builder for {@link Package} objects. Only intended to be used by {@link PackageFactory} and
   * {@link com.google.devtools.build.lib.skyframe.PackageFunction}.
   */
  public static class Builder extends AbstractBuilder {

    /**
     * A bundle of statically-defined options affecting package construction, that is not specific
     * to any particular package and does not change for the lifetime of the server.
     */
    public interface PackageSettings {
      /**
       * Returns whether or not extra detail should be added to {@link NoSuchTargetException}s
       * thrown from {@link #getTarget}. Useful for toning down verbosity in situations where it can
       * be less helpful.
       */
      // TODO(bazel-team): Arguably, this could be replaced by a boolean param to getTarget(), or
      // some separate action taken by the caller. But there's a lot of call sites that would need
      // updating.
      default boolean succinctTargetNotFoundErrors() {
        return false;
      }

      /**
       * Determines whether to precompute a list of transitively loaded starlark files while
       * building packages.
       *
       * <p>Typically, direct loads are stored as a {@code ImmutableList<Module>}. This is
       * sufficient to reconstruct the full load graph by recursively traversing {@link
       * BazelModuleContext#loads}. If the package is going to be serialized, however, it may make
       * more sense to precompute a flat list containing the labels of all transitively loaded bzl
       * files since {@link Module} is costly to serialize.
       *
       * <p>If this returns {@code true}, transitive loads are stored as an {@code
       * ImmutableList<Label>} and direct loads are not stored.
       */
      default boolean precomputeTransitiveLoads() {
        return false;
      }

      PackageSettings DEFAULTS = new PackageSettings() {};
    }

    /** A bundle of options affecting resource limits on package construction. */
    public interface PackageLimits {
      /**
       * The maximum number of Starlark computation steps that are allowed to be executed while
       * building a package (or, transitively, any package piece). If this limit is exceeded, the
       * package or package piece immediately stops building.
       *
       * <p>Confusingly, for historical Google-specific reasons, this limit is <em>not</em> the same
       * as {@code --max_computation_steps}.
       *
       * <ul>
       *   <li>This limit (maxStarlarkComputationStepsPerPackage) is only set by Google-specific
       *       logic, is currently not used in open-source Bazel, and exceeding the limit causes the
       *       package builder to immediately stop and print a stack trace. The intent is to harden
       *       infrastructure against runaway Starlark computations.
       *   <li>By contrast, {@code --max_computation_steps} is enforced by {@link PackageFactory}
       *       post-factum, after the package has been built. The intent is to enforce code health
       *       by limiting the complexity of packages in a repo.
       * </ul>
       *
       * <p>If lazy symbolic macro expansion is enabled, unless a complete {@link Package} is
       * loaded, the limit is enforced only per package piece.
       */
      // TODO(b/417468797): merge with --max_computation_steps enforcement.
      default long maxStarlarkComputationStepsPerPackage() {
        return Long.MAX_VALUE;
      }

      public static final PackageLimits DEFAULTS = new PackageLimits() {};
    }

    // The snapshot of {@link #targets} for use in rule finalizer macros. Contains all
    // non-finalizer-instantiated rule targets (i.e. all rule targets except for those instantiated
    // in a finalizer or in a macro called from a finalizer).
    //
    // Initialized by expandAllRemainingMacros() and reset to null by beforeBuild().
    @Nullable private Map<String, Rule> rulesSnapshotViewForFinalizers;

    /**
     * Ids of all symbolic macros that have been declared but not yet evaluated.
     *
     * <p>These are listed in the order they were declared. (This probably doesn't matter, but let's
     * be protective against possible non-determinism.)
     *
     * <p>Generally, ordinary symbolic macros are evaluated eagerly and not added to this set, while
     * finalizers, as well as any macros called by finalizers, always use deferred evaluation and
     * end up in here.
     */
    private final Set<String> unexpandedMacros = new LinkedHashSet<>();

    private Builder(
        Metadata metadata,
        Declarations declarations,
        SymbolGenerator<?> symbolGenerator,
        boolean precomputeTransitiveLoads,
        boolean noImplicitFileExport,
        boolean simplifyUnconditionalSelectsInRuleAttrs,
        boolean symbolicMacroStrictAttrs,
        RepositoryMapping mainRepositoryMapping,
        @Nullable Semaphore cpuBoundSemaphore,
        PackageOverheadEstimator packageOverheadEstimator,
        @Nullable ImmutableMap<Location, String> generatorMap,
        @Nullable Globber globber,
        boolean enableNameConflictChecking,
        boolean trackFullMacroInformation,
        PackageValidator packageValidator,
        PackageLimits packageLimits) {
      super(
          metadata,
          new Package(metadata, declarations),
          symbolGenerator,
          precomputeTransitiveLoads,
          noImplicitFileExport,
          simplifyUnconditionalSelectsInRuleAttrs,
          symbolicMacroStrictAttrs,
          mainRepositoryMapping,
          cpuBoundSemaphore,
          packageOverheadEstimator,
          generatorMap,
          globber,
          enableNameConflictChecking,
          trackFullMacroInformation,
          /* enableTargetMapSnapshotting= */ true,
          packageValidator,
          packageLimits);
    }

    /** Retrieves this object from a Starlark thread. Returns null if not present. */
    @Nullable
    public static Builder fromOrNull(StarlarkThread thread) {
      StarlarkThreadContext ctx = thread.getThreadLocal(StarlarkThreadContext.class);
      return ctx instanceof Builder builder ? builder : null;
    }

    Package getPackage() {
      return (Package) pkg;
    }

    @Override
    @CanIgnoreReturnValue
    public Builder setLoads(Iterable<Module> directLoads) {
      return (Builder) super.setLoads(directLoads);
    }

    @Override
    Map<String, Rule> getRulesSnapshotView() {
      if (rulesSnapshotViewForFinalizers != null) {
        return rulesSnapshotViewForFinalizers;
      } else {
        return super.getRulesSnapshotView();
      }
    }

    @Override
    @Nullable
    Rule getNonFinalizerInstantiatedRule(String name) {
      if (rulesSnapshotViewForFinalizers != null) {
        return rulesSnapshotViewForFinalizers.get(name);
      } else {
        return super.getNonFinalizerInstantiatedRule(name);
      }
    }

    public void addRuleUnchecked(Rule rule) {
      Preconditions.checkArgument(rule.getPackage() == pkg);
      recorder.addRuleUnchecked(rule);
    }

    /** Adds all targets, macros, and Starlark computation steps from a given package piece. */
    public void addAllFromPackagePiece(PackagePiece packagePiece) throws NameConflictException {
      // We add the BUILD file in newPackageFromPackagePiecesBuilder(), not here. (We want to ensure
      // that the package always has a BUILD file target, even if addAllFromPackagePiece would throw
      // a name conflict.)
      this.recorder.addAllFromPackagePiece(packagePiece, /* skipBuildFile= */ true);
      this.computationSteps += packagePiece.getComputationSteps();
    }

    @Override
    public boolean eagerlyExpandMacros() {
      return true;
    }

    @Override
    public void addMacro(MacroInstance macro) throws NameConflictException {
      super.addMacro(macro);
      unexpandedMacros.add(macro.getId());
    }

    // For Package deserialization.
    void putAllMacroNamespaceViolatingTargets(Map<String, String> macroNamespaceViolatingTargets) {
      recorder.putAllMacroNamespaceViolatingTargets(macroNamespaceViolatingTargets);
    }

    void putAllTargetsToDeclaringPackage(Map<String, PackageIdentifier> targetsToDeclaringPackage) {
      recorder.putAllTargetsToDeclaringPackage(targetsToDeclaringPackage);
    }

    /**
     * Marks a symbolic macro as having finished evaluating.
     *
     * <p>This will prevent the macro from being run by {@link #expandAllRemainingMacros}.
     *
     * <p>The macro must not have previously been marked complete.
     */
    public void markMacroComplete(MacroInstance macro) {
      String id = macro.getId();
      if (!unexpandedMacros.remove(id)) {
        throw new IllegalArgumentException(
            String.format("Macro id '%s' unknown or already marked complete", id));
      }
    }

    /**
     * Ensures that all symbolic macros in an error-free package have expanded. No-op if the package
     * already {@link #containsErrors}.
     *
     * <p>This does not run any macro that has already been evaluated. It *does* run macros that are
     * newly discovered during the operation of this method.
     */
    public void expandAllRemainingMacros(StarlarkSemantics semantics)
        throws EvalException, InterruptedException {
      // TODO: #19922 - Protect against unreasonable macro stack depth and large numbers of symbolic
      // macros overall, for both the eager and deferred evaluation strategies.

      // Note that this operation is idempotent for symmetry with build()/buildPartial(). Though
      // it's not entirely clear that this is necessary.

      // TODO: #19922 - Once compatibility with native.existing_rules() in legacy macros is no
      // longer a concern, we will want to support delayed expansion of non-finalizer macros before
      // the finalizer expansion step.

      // Finalizer expansion step. Requires that the package not be in error (no point in finalizing
      // a package that already threw an EvalException).
      if (!containsErrors() && !unexpandedMacros.isEmpty()) {
        Preconditions.checkState(
            unexpandedMacros.stream()
                .allMatch(id -> recorder.getMacroMap().get(id).getMacroClass().isFinalizer()),
            "At the beginning of finalizer expansion, unexpandedMacros must contain only"
                + " finalizers");

        // Save a snapshot of rule targets for use by native.existing_rules() inside all finalizers.
        // We must take this snapshot before calling any finalizer because the snapshot must not
        // include any rule instantiated by a finalizer or macro called from a finalizer.
        if (rulesSnapshotViewForFinalizers == null) {
          Preconditions.checkState(
              recorder.getTargetMap() instanceof SnapshottableBiMap<?, ?>,
              "Cannot call expandAllRemainingMacros() after beforeBuild() has been called");
          rulesSnapshotViewForFinalizers = getRulesSnapshotView();
        }

        while (!unexpandedMacros.isEmpty()) { // NB: collection mutated by body
          String id = unexpandedMacros.iterator().next();
          MacroInstance macro = recorder.getMacroMap().get(id);
          MacroClass.executeMacroImplementation(macro, this, semantics);
        }
      }
    }

    @Override
    @CanIgnoreReturnValue
    protected Builder beforeBuild() throws NoSuchPackageException {
      // For correct semantics, we refuse to build a package that hasn't thrown any EvalExceptions
      // but has declared symbolic macros that have not yet been expanded. (Currently finalizers are
      // the only use case where this happens, but the Package logic is agnostic to that detail.)
      //
      // Production code should be calling expandAllRemainingMacros() to guarantee that nothing is
      // left unexpanded. Tests that do not declare any symbolic macros need not make the call.
      // Package deserialization doesn't have to do it either, since we shouldn't be evaluating
      // symbolic macros on the deserialized result of an already evaluated package.
      Preconditions.checkState(
          unexpandedMacros.isEmpty() || containsErrors(),
          "Cannot build a package with unexpanded symbolic macros; call"
              + " expandAllRemainingMacros()");

      // SnapshottableBiMap does not allow removing targets (in order to efficiently track rule
      // insertion order). However, we *do* need to support removal of targets in
      // PackageFunction.handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions
      // which is called *between* calls to beforeBuild and finishBuild. We thus repoint the targets
      // map to the SnapshottableBiMap's underlying bimap and thus stop tracking insertion order.
      // After this point, snapshots of targets should no longer be used, and any further
      // getRulesSnapshotView calls will throw.
      if (recorder.getTargetMap() instanceof SnapshottableBiMap<?, ?>) {
        recorder.unwrapSnapshottableBiMap();
        rulesSnapshotViewForFinalizers = null;
      }

      super.beforeBuild();

      return this;
    }

    @Override
    @CanIgnoreReturnValue
    public Builder buildPartial() throws NoSuchPackageException {
      return (Builder) super.buildPartial();
    }

    @Override
    protected void setBuildFile(InputFile buildFile) {
      ((Package) pkg).buildFile = checkNotNull(buildFile);
    }

    @Override
    public Package finishBuild() {
      return (Package) super.finishBuild();
    }

    @Override
    protected void packageoidInitializationHook() {
      super.packageoidInitializationHook();
      Package pkg = getPackage();
      pkg.computationSteps = getComputationSteps();
      pkg.macros = ImmutableSortedMap.copyOf(recorder.getMacroMap());
      pkg.macroNamespaceViolatingTargets =
          ImmutableMap.copyOf(recorder.getMacroNamespaceViolatingTargets());
      pkg.targetsToDeclaringMacro =
          recorder.getTargetsToDeclaringMacro() != null
              ? ImmutableSortedMap.copyOf(recorder.getTargetsToDeclaringMacro())
              : null;
      pkg.targetsToDeclaringPackage =
          recorder.getTargetsToDeclaringPackage() != null
              ? ImmutableSortedMap.copyOf(recorder.getTargetsToDeclaringPackage())
              : null;
    }

    /** Completes package construction. Idempotent. */
    // TODO(brandjon): Do we actually care about idempotence?
    public Package build() throws NoSuchPackageException {
      return build(/* discoverAssumedInputFiles= */ true);
    }

    /**
     * Constructs the package (or does nothing if it's already built) and returns it.
     *
     * @param discoverAssumedInputFiles whether to automatically add input file targets to this
     *     package for "dangling labels", i.e. labels mentioned in this package that point to an
     *     up-until-now non-existent target in this package
     */
    Package build(boolean discoverAssumedInputFiles) throws NoSuchPackageException {
      if (alreadyBuilt) {
        return getPackage();
      }
      if (discoverAssumedInputFiles) {
        beforeBuild();
      } else {
        beforeBuildWithoutDiscoveringAssumedInputFiles();
      }
      return finishBuild();
    }
  }

  /**
   * A collection of data that is known before BUILD file evaluation even begins.
   *
   * <p><b>Important:</b> Tracking of transitive packages relies on a {@link
   * com.google.devtools.build.lib.collect.nestedset.NestedSet<Metadata>}, so this class must have a
   * cheap {@link #hashCode()}. Some fields, such as {@link #repositoryMapping}, would be cheap to
   * hash for Blaze but not Bazel.
   */
  // TODO(bazel-team): move to Packageoid.java or to its own file to reduce size of Package.java?
  @AutoCodec
  public record Metadata(
      PackageIdentifier packageIdentifier,
      RootedPath buildFilename,
      Label buildFileLabel,
      /**
       * The name of the workspace this package is in. Used as a prefix for the runfiles directory.
       */
      String workspaceName,
      RepositoryMapping repositoryMapping,
      Optional<String> associatedModuleName,
      Optional<String> associatedModuleVersion,
      @Nullable ConfigSettingVisibilityPolicy configSettingVisibilityPolicy,
      boolean succinctTargetNotFoundErrors,
      Root sourceRoot)
      implements NestedSetsShouldBeInternedByEquality {

    // See class-level Javadoc for an explanation of why we need this.
    @Override
    public int hashCode() {
      // Within a single build a package is uniquely identified by its PackageIdentifier.
      return packageIdentifier.hashCode();
    }

    public static Builder builder() {
      return new AutoBuilder_Package_Metadata_Builder();
    }

    /** Builder for {@link Metadata}. */
    @AutoBuilder(callMethod = "of")
    public interface Builder {
      Builder packageIdentifier(PackageIdentifier packageIdentifier);

      Builder buildFilename(RootedPath buildFilename);

      Builder workspaceName(String workspaceName);

      Builder repositoryMapping(RepositoryMapping repositoryMapping);

      Builder associatedModuleName(Optional<String> associatedModuleName);

      Builder associatedModuleVersion(Optional<String> associatedModuleVersion);

      Builder configSettingVisibilityPolicy(
          @Nullable ConfigSettingVisibilityPolicy configSettingVisibilityPolicy);

      Builder succinctTargetNotFoundErrors(boolean succinctTargetNotFoundErrors);

      Metadata build();
    }

    static Metadata of(
        PackageIdentifier packageIdentifier,
        RootedPath buildFilename,
        String workspaceName,
        RepositoryMapping repositoryMapping,
        Optional<String> associatedModuleName,
        Optional<String> associatedModuleVersion,
        @Nullable ConfigSettingVisibilityPolicy configSettingVisibilityPolicy,
        boolean succinctTargetNotFoundErrors) {
      Label buildFileLabel;
      try {
        buildFileLabel =
            Label.create(packageIdentifier, buildFilename.getRootRelativePath().getBaseName());
      } catch (LabelSyntaxException e) {
        // This can't actually happen.
        throw new AssertionError("Package BUILD file has an illegal name: " + buildFilename, e);
      }
      return new Metadata(
          packageIdentifier,
          buildFilename,
          buildFileLabel,
          workspaceName,
          repositoryMapping,
          associatedModuleName,
          associatedModuleVersion,
          configSettingVisibilityPolicy,
          succinctTargetNotFoundErrors,
          computeSourceRoot(packageIdentifier, buildFilename));
    }

    /**
     * @deprecated Use {@link #builder()} instead.
     */
    @Deprecated
    public Metadata {
      Preconditions.checkNotNull(packageIdentifier);
      Preconditions.checkNotNull(buildFilename);
      Preconditions.checkNotNull(workspaceName);
      Preconditions.checkNotNull(repositoryMapping);
      Preconditions.checkNotNull(associatedModuleName);
      Preconditions.checkNotNull(associatedModuleVersion);
      Preconditions.checkNotNull(sourceRoot);
    }

    /** Returns the name of this package (sans repository), e.g. "foo/bar". */
    public String getName() {
      return packageIdentifier.getPackageFragment().getPathString();
    }

    /**
     * Returns the directory in which this package's BUILD file resides.
     *
     * <p>All InputFile members of the packages are located relative to this directory.
     */
    public Path getPackageDirectory() {
      return getPackageDirectory(buildFilename);
    }

    /** Returns the {@link Location} of the package's BUILD file. */
    public Location getBuildFileLocation() {
      return Location.fromFile(buildFilename.asPath().toString());
    }

    private static Path getPackageDirectory(RootedPath buildFilename) {
      return buildFilename.asPath().getParentDirectory();
    }

    private static Root computeSourceRoot(
        PackageIdentifier packageIdentifier, RootedPath buildFilename) {
      Preconditions.checkNotNull(packageIdentifier);
      Preconditions.checkNotNull(buildFilename);

      RootedPath buildFileRootedPath = buildFilename;
      Root buildFileRoot = buildFileRootedPath.getRoot();
      PathFragment pkgIdFragment = packageIdentifier.getSourceRoot();
      PathFragment pkgDirFragment = buildFileRootedPath.getRootRelativePath().getParentDirectory();

      Root sourceRoot;
      if (pkgIdFragment.equals(pkgDirFragment)) {
        // Fast path: BUILD file path and package name are the same, don't create an extra root.
        sourceRoot = buildFileRoot;
      } else {
        // TODO(bazel-team): Can this expr be simplified to just pkgDirFragment?
        PathFragment current = buildFileRootedPath.asPath().asFragment().getParentDirectory();
        for (int i = 0, len = pkgIdFragment.segmentCount(); i < len && current != null; i++) {
          current = current.getParentDirectory();
        }
        if (current == null || current.isEmpty()) {
          // This is never really expected to work. The below check should fail.
          sourceRoot = buildFileRoot;
        } else {
          // Note that current is an absolute path.
          sourceRoot = Root.fromPath(buildFileRoot.getRelative(current));
        }
      }

      Preconditions.checkArgument(
          sourceRoot.asPath() != null
              && sourceRoot.getRelative(pkgIdFragment).equals(getPackageDirectory(buildFilename)),
          "Invalid BUILD file name for package '%s': %s (in source %s with packageDirectory %s and"
              + " package identifier source root %s)",
          packageIdentifier,
          buildFilename,
          sourceRoot,
          getPackageDirectory(buildFilename),
          packageIdentifier.getSourceRoot());

      return sourceRoot;
    }
  }

  /**
   * A collection of data about a package that is known after BUILD file evaluation has completed,
   * which doesn't require expanding any symbolic macros, and which transitively doesn't hold
   * references to {@link Package} or {@link PackagePiece} objects. Treated as immutable after BUILD
   * file evaluation has completed.
   *
   * <p>Instances of the base {@link Declarations} class are immutable; for a mutable builder, see
   * {@link Declarations.Builder}.
   */
  public static sealed class Declarations permits Declarations.Builder {
    // All fields are non-final only to allow builder subclass to mutate them.

    // These two fields are mutated only during BUILD file evaluation (not during symbolic macro
    // evaluation).
    protected PackageArgs packageArgs = PackageArgs.DEFAULT;
    protected ImmutableMap<String, String> makeEnv;

    // These two fields are mutually exclusive. Which one is set depends on
    // PackageSettings#precomputeTransitiveLoads. See Package.Builder#setLoads.
    @Nullable protected ImmutableList<Module> directLoads;
    @Nullable protected ImmutableList<Label> transitiveLoads;

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      return obj instanceof Declarations other
          && Objects.equals(packageArgs, other.packageArgs)
          && Objects.equals(makeEnv, other.makeEnv)
          // Serializers use getOrComputeTransitivelyLoadedStarlarkFiles() and don't distinguish
          // between directLoads and transitiveLoads.
          && Objects.equals(
              getOrComputeTransitivelyLoadedStarlarkFilesInternal(),
              other.getOrComputeTransitivelyLoadedStarlarkFilesInternal());
    }

    @Override
    public int hashCode() {
      return HashCodes.hashObjects(
          packageArgs,
          makeEnv,
          // Serializers use getOrComputeTransitivelyLoadedStarlarkFiles() and don't distinguish
          // between directLoads and transitiveLoads.
          getOrComputeTransitivelyLoadedStarlarkFilesInternal());
    }

    /**
     * Returns the collection of package-level attributes set by the {@code package()} callable and
     * similar methods.
     */
    public PackageArgs getPackageArgs() {
      return packageArgs;
    }

    /**
     * Returns the "Make" environment of this package, containing package-local definitions of
     * "Make" variables.
     */
    public ImmutableMap<String, String> getMakeEnvironment() {
      return makeEnv;
    }

    /**
     * Returns a list of Starlark files transitively loaded by this package.
     *
     * <p>If transitive loads are not {@linkplain PackageSettings#precomputeTransitiveLoads
     * precomputed}, performs a traversal over the load graph to compute them.
     *
     * <p>If only the count of transitively loaded files is needed, use {@link
     * #countTransitivelyLoadedStarlarkFiles}. For a customized online visitation, use {@link
     * #visitLoadGraph}.
     *
     * <p>This method can only be used after the Package or PackagePiece has been fully initialized
     * (i.e. after {@link TargetDefinitionContext#finishBuild} has been called).
     */
    public ImmutableList<Label> getOrComputeTransitivelyLoadedStarlarkFiles() {
      return checkNotNull(getOrComputeTransitivelyLoadedStarlarkFilesInternal());
    }

    @Nullable
    private ImmutableList<Label> getOrComputeTransitivelyLoadedStarlarkFilesInternal() {
      if (transitiveLoads != null) {
        return transitiveLoads;
      } else if (directLoads != null) {
        return computeTransitiveLoads(directLoads);
      } else {
        // Declarations not fully initialized.
        return null;
      }
    }

    /**
     * Counts the number Starlark files transitively loaded by this package.
     *
     * <p>If transitive loads are not {@linkplain PackageSettings#precomputeTransitiveLoads
     * precomputed}, performs a traversal over the load graph to count them.
     *
     * <p>This method can only be used after the Package or PackagePiece has been fully initialized
     * (i.e. after {@link TargetDefinitionContext#finishBuild} has been called).
     */
    public int countTransitivelyLoadedStarlarkFiles() {
      if (transitiveLoads != null) {
        return transitiveLoads.size();
      }
      Set<Label> loads = new HashSet<>();
      visitLoadGraph(loads::add);
      return loads.size();
    }

    /**
     * Performs an online visitation of the load graph rooted at this package.
     *
     * <p>If transitive loads were {@linkplain PackageSettings#precomputeTransitiveLoads
     * precomputed}, each file is passed to {@link LoadGraphVisitor#visit} once regardless of its
     * return value.
     *
     * <p>This method can only be used after the Package or PackagePiece has been fully initialized
     * (i.e. after {@link TargetDefinitionContext#finishBuild} has been called).
     */
    public <E1 extends Exception, E2 extends Exception> void visitLoadGraph(
        LoadGraphVisitor<E1, E2> visitor) throws E1, E2 {
      if (transitiveLoads != null) {
        for (Label load : transitiveLoads) {
          visitor.visit(load);
        }
      } else {
        BazelModuleContext.visitLoadGraphRecursively(directLoads, visitor);
      }
    }

    @CanIgnoreReturnValue
    public Declarations.Builder checkMutable() {
      if (this instanceof Declarations.Builder builder) {
        return builder;
      }
      throw new IllegalStateException("Package declarations has been finalized and is immutable.");
    }

    @CanIgnoreReturnValue
    public Declarations checkImmutable() {
      if (this instanceof Builder) {
        throw new IllegalStateException("Package declarations is in mutable state.");
      }
      return this;
    }

    private Declarations(
        PackageArgs packageArgs,
        ImmutableMap<String, String> makeEnv,
        @Nullable ImmutableList<Module> directLoads,
        @Nullable ImmutableList<Label> transitiveLoads) {
      this.packageArgs = checkNotNull(packageArgs);
      this.makeEnv = checkNotNull(makeEnv);
      this.directLoads = directLoads;
      this.transitiveLoads = transitiveLoads;
      checkArgument(
          directLoads == null ^ transitiveLoads == null,
          "Exactly one of directLoads and transitiveLoads must be set");
    }

    /** Default constructor for use only by {@link Builder}. */
    private Declarations() {}

    /** Builder for {@link Declarations}. */
    public static final class Builder extends Declarations {

      @CanIgnoreReturnValue
      public Builder setPackageArgs(PackageArgs packageArgs) {
        this.packageArgs = checkNotNull(packageArgs);
        return this;
      }

      @CanIgnoreReturnValue
      public Builder mergePackageArgsFrom(PackageArgs packageArgs) {
        this.packageArgs = this.packageArgs.mergeWith(checkNotNull(packageArgs));
        return this;
      }

      @CanIgnoreReturnValue
      public Builder setMakeEnvironment(Map<String, String> makeEnv) {
        this.makeEnv = ImmutableMap.copyOf(checkNotNull(makeEnv));
        return this;
      }

      @CanIgnoreReturnValue
      public Builder setDirectLoads(List<Module> directLoads) {
        this.directLoads = ImmutableList.copyOf(checkNotNull(directLoads));
        return this;
      }

      @CanIgnoreReturnValue
      public Builder setTransitiveLoads(List<Label> transitiveLoads) {
        this.transitiveLoads = ImmutableList.copyOf(checkNotNull(transitiveLoads));
        return this;
      }

      @CanIgnoreReturnValue
      private Builder checkLoadsNotSet() {
        checkState(directLoads == null, "Direct loads already set: %s", directLoads);
        checkState(transitiveLoads == null, "Transitive loads already set: %s", transitiveLoads);
        return this;
      }

      public Declarations build() {
        return new Declarations(packageArgs, makeEnv, directLoads, transitiveLoads);
      }

      public Builder() {
        packageArgs = PackageArgs.DEFAULT;
        makeEnv = ImmutableMap.of();
      }
    }
  }

  /** Package codec implementation. */
  @VisibleForTesting
  static final class PackageCodec implements ObjectCodec<Package> {
    @Override
    public Class<Package> getEncodedClass() {
      return Package.class;
    }

    @Override
    public void serialize(SerializationContext context, Package input, CodedOutputStream codedOut)
        throws IOException, SerializationException {
      context.checkClassExplicitlyAllowed(Package.class, input);
      PackageCodecDependencies codecDeps = context.getDependency(PackageCodecDependencies.class);
      codecDeps.getPackageSerializer().serialize(context, input, codedOut);
    }

    @Override
    public Package deserialize(DeserializationContext context, CodedInputStream codedIn)
        throws SerializationException, IOException {
      PackageCodecDependencies codecDeps = context.getDependency(PackageCodecDependencies.class);
      return codecDeps.getPackageSerializer().deserialize(context, codedIn);
    }
  }
}
