| // Copyright 2025 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.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.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.packages.Package.Builder.PackageLimits; |
| import com.google.devtools.build.lib.packages.Package.Builder.PackageSettings; |
| import com.google.devtools.build.lib.packages.Package.ConfigSettingVisibilityPolicy; |
| import com.google.devtools.build.lib.packages.Package.Declarations; |
| import com.google.devtools.build.lib.packages.Package.Metadata; |
| import com.google.devtools.build.lib.packages.TargetRecorder.MacroNamespaceViolationException; |
| import com.google.devtools.build.lib.vfs.RootedPath; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.concurrent.Semaphore; |
| import javax.annotation.Nullable; |
| import net.starlark.java.eval.Module; |
| import net.starlark.java.eval.StarlarkThread; |
| import net.starlark.java.eval.SymbolGenerator; |
| import net.starlark.java.syntax.Location; |
| |
| /** |
| * A piece of a {@link Package}: either the subset obtained by evaluating a BUILD file and not |
| * expanding any symbolic macros; or the subset obtained by evaluating exactly one symbolic macro |
| * instance. |
| * |
| * <p>To obtain a {@link Package} from a {@link PackagePiece}, use a PackageProvider or skyframe |
| * machinery. |
| */ |
| // TODO(https://github.com/bazelbuild/bazel/issues/23852): as a future optimization, consider adding |
| // another class of package piece obtained by evaluating a set of macros. |
| public abstract sealed class PackagePiece extends Packageoid |
| permits PackagePiece.ForBuildFile, PackagePiece.ForMacro { |
| /** |
| * The collection of all symbolic macro instances defined in this package piece, indexed by their |
| * name (not by {@link MacroInstance#getId id} - contrast with {@link Package#macros}). Null until |
| * the package piece is fully initialized by {@link #setMacrosByName}, in turn called by this |
| * package piece's builder's {@code finishBuild()}. |
| */ |
| @Nullable private ImmutableSortedMap<String, MacroInstance> macrosByName; |
| |
| public abstract PackagePieceIdentifier getIdentifier(); |
| |
| /** |
| * Returns a (read-only, ordered) iterable of all the targets belonging to this package piece |
| * which are instances of the specified class. Doesn't search in any other package pieces. |
| */ |
| public <T extends Target> Iterable<T> getTargets(Class<T> targetClass) { |
| return Iterables.filter(targets.values(), targetClass); |
| } |
| |
| @Override |
| public Target getTarget(String targetName) throws NoSuchTargetException { |
| Target target = targets.get(targetName); |
| if (target != null) { |
| return target; |
| } |
| |
| throw noSuchTargetException(targetName); |
| } |
| |
| /** |
| * Returns the macro instance declared in this package piece having the provided name; or null if |
| * no such macro instance exists. |
| */ |
| @Nullable |
| public MacroInstance getMacroByName(String name) { |
| return macrosByName.get(name); |
| } |
| |
| /** Returns a list of all the macro instances defined in this package piece, ordered by name. */ |
| public ImmutableList<MacroInstance> getMacros() { |
| return ImmutableList.copyOf(macrosByName.values()); |
| } |
| |
| private NoSuchTargetException noSuchTargetException(String targetName) { |
| Label label; |
| try { |
| label = Label.create(getPackageIdentifier(), targetName); |
| } catch (LabelSyntaxException e) { |
| throw new IllegalArgumentException(targetName, e); |
| } |
| |
| if (getMetadata().succinctTargetNotFoundErrors()) { |
| return new NoSuchTargetException( |
| label, |
| String.format("target '%s' not declared in %s", targetName, getShortDescription())); |
| } else { |
| String alternateTargetSuggestion = |
| Package.getAlternateTargetSuggestion(getMetadata(), targetName, targets.keySet()); |
| return new NoSuchTargetException( |
| label, |
| String.format( |
| "target '%s' not declared in %s%s", |
| targetName, getShortDescription(), alternateTargetSuggestion)); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return String.format( |
| "PackagePiece(%s defined by %s)=%s", |
| getIdentifier().getCanonicalFormName(), |
| getCanonicalFormDefinedBy(), |
| targets != null ? getTargets(Rule.class) : "initializing..."); |
| } |
| |
| /** |
| * Returns the canonical form of the BUILD file label if this is a {@link |
| * PackagePiece.ForBuildFile}, or the canonical form of the macro class's declaring .bzl label and |
| * macro name, in {@code label%name} format, if this is a {@link PackagePiece.ForMacro}. |
| */ |
| public abstract String getCanonicalFormDefinedBy(); |
| |
| /** |
| * Sets the macros map for this package piece. Intended only to be called by this package piece's |
| * builder. |
| * |
| * @param macros a collection of macro instances, which must have unique names. |
| */ |
| protected void setMacrosByName(Collection<MacroInstance> macros) { |
| ImmutableSortedMap.Builder<String, MacroInstance> macrosByName = |
| ImmutableSortedMap.naturalOrder(); |
| for (MacroInstance macro : macros) { |
| macrosByName.put(macro.getName(), macro); |
| } |
| this.macrosByName = macrosByName.buildOrThrow(); |
| } |
| |
| protected PackagePiece(Metadata metadata, Declarations declarations) { |
| super(metadata, declarations); |
| } |
| |
| /** |
| * A {@link PackagePiece} obtained by evaluating a BUILD file, without expanding any symbolic |
| * macros. |
| */ |
| public static final class ForBuildFile extends PackagePiece { |
| private final PackagePieceIdentifier.ForBuildFile identifier; |
| // 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 |
| // PackagePiece.ForBuildFile object. |
| private InputFile buildFile; |
| |
| @Override |
| public PackagePieceIdentifier.ForBuildFile getIdentifier() { |
| return identifier; |
| } |
| |
| @Override |
| public String getCanonicalFormDefinedBy() { |
| return getMetadata().buildFileLabel().getCanonicalForm(); |
| } |
| |
| @Override |
| public String getShortDescription() { |
| return String.format("top-level package piece defined by %s", getCanonicalFormDefinedBy()); |
| } |
| |
| /** Returns the InputFile target for this package's BUILD file. */ |
| public InputFile getBuildFile() { |
| return buildFile; |
| } |
| |
| @Override |
| public void checkMacroNamespaceCompliance(Target target) { |
| checkArgument(this.equals(target.getPackageoid()), "Target must belong to this packageoid"); |
| // No-op: no macros to violate. |
| } |
| |
| private ForBuildFile(PackagePieceIdentifier.ForBuildFile identifier, Metadata metadata) { |
| super(metadata, new Declarations.Builder()); |
| checkArgument(identifier.getPackageIdentifier().equals(metadata.packageIdentifier())); |
| this.identifier = identifier; |
| } |
| |
| /** Creates a new {@link PackagePiece.ForBuildFile.Builder}. */ |
| // TODO(bazel-team): when JEP 482 ("flexible constructors") is enabled, we can remove this |
| // method and use the builder's constructor directly. |
| public static Builder newBuilder( |
| PackageSettings packageSettings, |
| PackagePieceIdentifier.ForBuildFile identifier, |
| RootedPath filename, |
| String workspaceName, |
| Optional<String> associatedModuleName, |
| Optional<String> associatedModuleVersion, |
| boolean noImplicitFileExport, |
| boolean simplifyUnconditionalSelectsInRuleAttrs, |
| RepositoryMapping repositoryMapping, |
| RepositoryMapping mainRepositoryMapping, |
| @Nullable Semaphore cpuBoundSemaphore, |
| PackageOverheadEstimator packageOverheadEstimator, |
| @Nullable ImmutableMap<Location, String> generatorMap, |
| @Nullable ConfigSettingVisibilityPolicy configSettingVisibilityPolicy, |
| @Nullable Globber globber, |
| boolean enableNameConflictChecking, |
| boolean trackFullMacroInformation, |
| PackageLimits packageLimits) { |
| Metadata metadata = |
| Metadata.builder() |
| .packageIdentifier(identifier.getPackageIdentifier()) |
| .buildFilename(filename) |
| .workspaceName(workspaceName) |
| .repositoryMapping(repositoryMapping) |
| .associatedModuleName(associatedModuleName) |
| .associatedModuleVersion(associatedModuleVersion) |
| .configSettingVisibilityPolicy(configSettingVisibilityPolicy) |
| .succinctTargetNotFoundErrors(packageSettings.succinctTargetNotFoundErrors()) |
| .build(); |
| ForBuildFile forBuildFile = new ForBuildFile(identifier, metadata); |
| return new Builder( |
| forBuildFile, |
| packageSettings.precomputeTransitiveLoads(), |
| noImplicitFileExport, |
| simplifyUnconditionalSelectsInRuleAttrs, |
| mainRepositoryMapping, |
| cpuBoundSemaphore, |
| packageOverheadEstimator, |
| generatorMap, |
| globber, |
| enableNameConflictChecking, |
| trackFullMacroInformation, |
| packageLimits); |
| } |
| |
| /** A builder for {@link PackagePiece.ForBuildFile} objects. */ |
| public static class Builder extends Package.AbstractBuilder { |
| |
| /** 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; |
| } |
| |
| public PackagePiece.ForBuildFile getPackagePiece() { |
| return (PackagePiece.ForBuildFile) pkg; |
| } |
| |
| @Override |
| @CanIgnoreReturnValue |
| public Builder setLoads(Iterable<Module> directLoads) { |
| return (Builder) super.setLoads(directLoads); |
| } |
| |
| @Override |
| public boolean eagerlyExpandMacros() { |
| return false; |
| } |
| |
| @Override |
| @CanIgnoreReturnValue |
| public Builder buildPartial() throws NoSuchPackageException { |
| return (Builder) super.buildPartial(); |
| } |
| |
| @Override |
| protected void setBuildFile(InputFile buildFile) { |
| ((ForBuildFile) pkg).buildFile = checkNotNull(buildFile); |
| } |
| |
| @Override |
| public ForBuildFile finishBuild() { |
| return (ForBuildFile) super.finishBuild(); |
| } |
| |
| @Override |
| protected void packageoidInitializationHook() { |
| super.packageoidInitializationHook(); |
| getPackagePiece().computationSteps = getComputationSteps(); |
| getPackagePiece().setMacrosByName(recorder.getMacroMap().values()); |
| } |
| |
| private Builder( |
| ForBuildFile forBuildFile, |
| boolean precomputeTransitiveLoads, |
| boolean noImplicitFileExport, |
| boolean simplifyUnconditionalSelectsInRuleAttrs, |
| RepositoryMapping mainRepositoryMapping, |
| @Nullable Semaphore cpuBoundSemaphore, |
| PackageOverheadEstimator packageOverheadEstimator, |
| @Nullable ImmutableMap<Location, String> generatorMap, |
| @Nullable Globber globber, |
| boolean enableNameConflictChecking, |
| boolean trackFullMacroInformation, |
| PackageLimits packageLimits) { |
| super( |
| forBuildFile.getMetadata(), |
| forBuildFile, |
| SymbolGenerator.create(forBuildFile.getIdentifier()), |
| precomputeTransitiveLoads, |
| noImplicitFileExport, |
| simplifyUnconditionalSelectsInRuleAttrs, |
| mainRepositoryMapping, |
| cpuBoundSemaphore, |
| packageOverheadEstimator, |
| generatorMap, |
| globber, |
| enableNameConflictChecking, |
| trackFullMacroInformation, |
| /* enableTargetMapSnapshotting= */ false, |
| packageLimits); |
| } |
| } |
| } |
| |
| /** A {@link PackagePiece} obtained by evaluating a symbolic macro instance. */ |
| public static final class ForMacro extends PackagePiece { |
| private final PackagePieceIdentifier.ForMacro identifier; |
| private final MacroInstance evaluatedMacro; |
| // Null until the package piece is fully initialized by its builder's {@code finishBuild()}. |
| @Nullable private ImmutableSet<String> macroNamespaceViolations = null; |
| |
| @Override |
| public PackagePieceIdentifier.ForMacro getIdentifier() { |
| return identifier; |
| } |
| |
| @Override |
| public String getCanonicalFormDefinedBy() { |
| MacroClass macroClass = evaluatedMacro.getMacroClass(); |
| return String.format( |
| "%s%%%s", macroClass.getDefiningBzlLabel().getCanonicalForm(), macroClass.getName()); |
| } |
| |
| @Override |
| public String getShortDescription() { |
| return String.format( |
| "package piece for %smacro %s defined by %s", |
| getEvaluatedMacro().getMacroClass().isFinalizer() ? "finalizer " : "", |
| getIdentifier().getCanonicalFormName(), |
| getCanonicalFormDefinedBy()); |
| } |
| |
| public MacroInstance getEvaluatedMacro() { |
| return evaluatedMacro; |
| } |
| |
| /** |
| * Returns the ID of the package of the .bzl file declaring the macro which was expanded to |
| * produce this package piece; it is considered to be the location in which this package piece's |
| * targets are declared for visibility purposes. |
| */ |
| public PackageIdentifier getDeclaringPackage() { |
| return evaluatedMacro.getMacroClass().getDefiningBzlLabel().getPackageIdentifier(); |
| } |
| |
| @Override |
| public void checkMacroNamespaceCompliance(Target target) |
| throws MacroNamespaceViolationException { |
| checkArgument(this.equals(target.getPackageoid()), "Target must belong to this packageoid"); |
| checkNotNull( |
| macroNamespaceViolations, |
| "This method is only available after the package piece has been fully initialized."); |
| if (macroNamespaceViolations.contains(target.getName())) { |
| throw new MacroNamespaceViolationException( |
| String.format( |
| "Target %s declared in symbolic macro '%s' violates macro naming rules and cannot" |
| + " be built. %s", |
| target.getLabel(), evaluatedMacro.getName(), TargetRecorder.MACRO_NAMING_RULES), |
| target); |
| } |
| } |
| |
| private static void checkIdentifierMatchesMacro( |
| PackagePieceIdentifier.ForMacro identifier, MacroInstance macro) { |
| checkArgument( |
| macro.getPackageMetadata().packageIdentifier().equals(identifier.getPackageIdentifier())); |
| checkArgument(macro.getName().equals(identifier.getInstanceName())); |
| } |
| |
| private ForMacro( |
| Metadata metadata, |
| Declarations declarations, |
| MacroInstance evaluatedMacro, |
| PackagePieceIdentifier parentIdentifier) { |
| super(metadata, declarations.checkImmutable()); |
| checkArgument( |
| metadata |
| .packageIdentifier() |
| .equals(evaluatedMacro.getPackageMetadata().packageIdentifier())); |
| checkArgument(metadata.packageIdentifier().equals(parentIdentifier.getPackageIdentifier())); |
| if (evaluatedMacro.getParent() != null) { |
| checkIdentifierMatchesMacro( |
| (PackagePieceIdentifier.ForMacro) parentIdentifier, evaluatedMacro.getParent()); |
| } else { |
| checkArgument(parentIdentifier instanceof PackagePieceIdentifier.ForBuildFile); |
| } |
| this.identifier = |
| new PackagePieceIdentifier.ForMacro( |
| metadata.packageIdentifier(), parentIdentifier, evaluatedMacro.getName()); |
| this.evaluatedMacro = evaluatedMacro; |
| } |
| |
| /** Creates a new {@link PackagePiece.ForMacro.Builder}. */ |
| // TODO(bazel-team): when JEP 482 ("flexible constructors") is enabled, we can remove this |
| // method and use the builder's constructor directly. |
| public static Builder newBuilder( |
| Metadata metadata, |
| Declarations declarations, |
| MacroInstance evaluatedMacro, |
| PackagePieceIdentifier parentIdentifier, |
| boolean simplifyUnconditionalSelectsInRuleAttrs, |
| RepositoryMapping mainRepositoryMapping, |
| @Nullable Semaphore cpuBoundSemaphore, |
| PackageOverheadEstimator packageOverheadEstimator, |
| boolean enableNameConflictChecking, |
| boolean trackFullMacroInformation, |
| PackageLimits packageLimits, |
| @Nullable ImmutableMap<String, Rule> existingRulesMapForFinalizer) { |
| ForMacro forMacro = new ForMacro(metadata, declarations, evaluatedMacro, parentIdentifier); |
| return new Builder( |
| forMacro, |
| simplifyUnconditionalSelectsInRuleAttrs, |
| mainRepositoryMapping, |
| cpuBoundSemaphore, |
| packageOverheadEstimator, |
| enableNameConflictChecking, |
| trackFullMacroInformation, |
| packageLimits, |
| existingRulesMapForFinalizer); |
| } |
| |
| /** A builder for {@link PackagePieceForMacro} objects. */ |
| public static class Builder extends TargetDefinitionContext { |
| // Non-null iff this is a builder for a finalizer package piece and the non-finalizer package |
| // pieces that it depends upon are not in error. Used for native.existing_rules() and |
| // native.existing_rule(). |
| @Nullable private final ImmutableMap<String, Rule> existingRulesMapForFinalizer; |
| |
| /** 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; |
| } |
| |
| public PackagePiece.ForMacro getPackagePiece() { |
| return (PackagePiece.ForMacro) pkg; |
| } |
| |
| @Override |
| public boolean eagerlyExpandMacros() { |
| return false; |
| } |
| |
| /** Can only be called for a finalizer package piece. */ |
| @Override |
| Map<String, Rule> getRulesSnapshotView() { |
| checkState( |
| getPackagePiece().getEvaluatedMacro().getMacroClass().isFinalizer(), |
| "%s is defined by a non-finalizer macro", |
| getPackagePiece().getShortDescription()); |
| return checkNotNull( |
| existingRulesMapForFinalizer, |
| "native.existing_rules map was not set in builder for %s", |
| getPackagePiece().getShortDescription()); |
| } |
| |
| /** Can only be called for a finalizer package piece. */ |
| @Nullable |
| @Override |
| Rule getNonFinalizerInstantiatedRule(String name) { |
| return getRulesSnapshotView().get(name); |
| } |
| |
| @Override |
| @CanIgnoreReturnValue |
| public Builder buildPartial() throws NoSuchPackageException { |
| return (Builder) super.buildPartial(); |
| } |
| |
| @Override |
| public ForMacro finishBuild() { |
| return (ForMacro) super.finishBuild(); |
| } |
| |
| @Override |
| protected void packageoidInitializationHook() { |
| getPackagePiece().computationSteps = getComputationSteps(); |
| super.packageoidInitializationHook(); |
| ForMacro forMacro = getPackagePiece(); |
| forMacro.setMacrosByName(recorder.getMacroMap().values()); |
| forMacro.macroNamespaceViolations = |
| ImmutableSet.copyOf(recorder.getMacroNamespaceViolatingTargets().keySet()); |
| } |
| |
| private Builder( |
| ForMacro forMacro, |
| boolean simplifyUnconditionalSelectsInRuleAttrs, |
| RepositoryMapping mainRepositoryMapping, |
| @Nullable Semaphore cpuBoundSemaphore, |
| PackageOverheadEstimator packageOverheadEstimator, |
| boolean enableNameConflictChecking, |
| boolean trackFullMacroInformation, |
| PackageLimits packageLimits, |
| @Nullable ImmutableMap<String, Rule> existingRulesMapForFinalizer) { |
| super( |
| forMacro.getMetadata(), |
| forMacro, |
| SymbolGenerator.create(forMacro.getIdentifier()), |
| simplifyUnconditionalSelectsInRuleAttrs, |
| mainRepositoryMapping, |
| cpuBoundSemaphore, |
| packageOverheadEstimator, |
| /* generatorMap= */ null, |
| /* globber= */ null, |
| enableNameConflictChecking, |
| trackFullMacroInformation, |
| /* enableTargetMapSnapshotting= */ false, |
| packageLimits); |
| this.existingRulesMapForFinalizer = existingRulesMapForFinalizer; |
| } |
| } |
| } |
| } |