| // 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 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.devtools.build.lib.bugreport.BugReport; |
| 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.LabelConstants; |
| 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.RepositoryName; |
| import com.google.devtools.build.lib.cmdline.StarlarkThreadContext; |
| import com.google.devtools.build.lib.cmdline.TargetPattern; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.events.EventKind; |
| 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.packages.WorkspaceFileValue.WorkspaceFileKey; |
| import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; |
| 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.util.DetailedExitCode; |
| import com.google.devtools.build.lib.util.StringUtil; |
| 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.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.OptionalInt; |
| import java.util.OptionalLong; |
| 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.Starlark; |
| 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 { |
| |
| // 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 |
| // - grouping metadata based on whether it's known prior to BUILD file evaluation, prior to |
| // symbolic macro evalutaion, or at the time of final Package construction |
| // - machinery specific to external package / WORKSPACE / bzlmod |
| |
| // ==== 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 |
| } |
| |
| /** |
| * The "workspace name" of packages generated by Bzlmod to contain repo rules. |
| * |
| * <p>Normally, packages containing repo rules are differentiated from packages containing build |
| * rules by the {@link PackageIdentifier}: The singular repo-rule-containing package is {@code |
| * //external}. However, in Bzlmod, packages containing repo rules need to have meaningful {@link |
| * PackageIdentifier}s, so there needs to be some other way to distinguish them from |
| * build-rule-containing packages. We use the following magic string as the "workspace name" for |
| * repo-rule-containing packages generated by Bzlmod. |
| * |
| * @see #isRepoRulePackage() |
| */ |
| private static final String DUMMY_WORKSPACE_NAME_FOR_BZLMOD_PACKAGES = "__dummy_workspace_bzlmod"; |
| |
| /** Sentinel value for package overhead being empty. */ |
| private static final long PACKAGE_OVERHEAD_UNSET = -1; |
| |
| // ==== General package metadata fields ==== |
| |
| private final Metadata metadata; |
| |
| private final Optional<Root> sourceRoot; |
| |
| // For BUILD files, this is initialized immediately. For WORKSPACE files, it is known only after |
| // Starlark evaluation of the WORKSPACE file has finished. |
| private String workspaceName; |
| |
| // Can be changed during BUILD file evaluation due to exports_files() modifying its visibility. |
| private InputFile buildFile; |
| |
| // Mutated during BUILD file evaluation (but not by symbolic macro evaluation). |
| private PackageArgs packageArgs = PackageArgs.DEFAULT; |
| |
| // Mutated during BUILD file evaluation (but not by symbolic macro evaluation). |
| private ImmutableMap<String, String> makeEnv; |
| |
| // These two fields are mutually exclusive. Which one is set depends on |
| // PackageSettings#precomputeTransitiveLoads. See Package.Builder#setLoads. |
| @Nullable private ImmutableList<Module> directLoads; |
| @Nullable private ImmutableList<Label> transitiveLoads; |
| |
| /** |
| * True iff this package's BUILD files contained lexical or grammatical errors, or experienced |
| * errors during evaluation, or semantic errors during the construction of any rule. |
| * |
| * <p>Note: A package containing errors does not necessarily prevent a build; if all the rules |
| * needed for a given build were constructed prior to the first error, the build may proceed. |
| */ |
| private boolean containsErrors; |
| |
| /** |
| * The first detailed error encountered during this package's construction and evaluation, or |
| * {@code null} if there were no such errors or all its errors lacked details. |
| */ |
| @Nullable private FailureDetail failureDetail; |
| |
| private long computationSteps; |
| |
| /** |
| * A rough approximation of the memory and general accounting costs associated with a loaded |
| * package. A value of -1 means it is unset. Stored as a long to take up less memory per pkg. |
| */ |
| private long packageOverhead = PACKAGE_OVERHEAD_UNSET; |
| |
| // ==== Fields specific to external package / WORKSPACE logic ==== |
| |
| /** |
| * The map from each repository to that repository's remappings map. This is only used in the |
| * //external package, it is an empty map for all other packages. For example, an entry of {"@foo" |
| * : {"@x", "@y"}} indicates that, within repository foo, "@x" should be remapped to "@y". |
| */ |
| private ImmutableMap<RepositoryName, ImmutableMap<String, RepositoryName>> |
| externalPackageRepositoryMappings; |
| |
| private ImmutableList<TargetPattern> registeredExecutionPlatforms; |
| private ImmutableList<TargetPattern> registeredToolchains; |
| private OptionalInt firstWorkspaceSuffixRegisteredToolchain; |
| |
| // ==== Target and macro fields ==== |
| |
| /** The collection of all targets defined in this package, indexed by name. */ |
| // TODO(bazel-team): Clarify what this map contains when a rule and its output both share the same |
| // name. |
| private ImmutableSortedMap<String, Target> targets; |
| |
| /** |
| * The collection of all symbolic macro instances defined in this package, indexed by their {@link |
| * MacroInstance#getId id} (not name). |
| */ |
| // TODO(#19922): 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". |
| 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 #finishInit}. |
| */ |
| @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. |
| */ |
| // 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. |
| private ImmutableMap<String, MacroInstance> targetsToDeclaringMacros; |
| |
| // ==== 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. Certain Metadata fields |
| * like the package's name must be known before that point, while other fields are filled in when |
| * the builder calls {@link Package#finishInit}. |
| */ |
| // TODO(#19922): Better separate fields that must be known a priori from those determined through |
| // BUILD evaluation. |
| private Package(Metadata metadata) { |
| this.metadata = metadata; |
| this.sourceRoot = computeSourceRoot(metadata); |
| } |
| |
| // ==== General package metadata accessors ==== |
| |
| public Metadata getMetadata() { |
| return metadata; |
| } |
| |
| /** |
| * Returns this package's identifier. |
| * |
| * <p>This is a suffix of {@code getFilename().getParentDirectory()}. |
| */ |
| public PackageIdentifier getPackageIdentifier() { |
| return metadata.packageIdentifier(); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| /** |
| * Whether this package should contain only repo rules (returns {@code true}) or only build rules |
| * (returns {@code false}). |
| */ |
| private boolean isRepoRulePackage() { |
| return metadata.isRepoRulePackage; |
| } |
| |
| /** |
| * Returns the map of repository reassignments for BUILD packages. This will be empty for packages |
| * within the main workspace. |
| */ |
| public RepositoryMapping getRepositoryMapping() { |
| return metadata.repositoryMapping(); |
| } |
| |
| /** |
| * How to enforce visibility on <code>config_setting</code> See {@link |
| * ConfigSettingVisibilityPolicy} for details. |
| * |
| * <p>Null for repo rule packages. |
| */ |
| @Nullable |
| public ConfigSettingVisibilityPolicy getConfigSettingVisibilityPolicy() { |
| return metadata.configSettingVisibilityPolicy(); |
| } |
| |
| /** |
| * Returns the name of the workspace this package is in. Used as a prefix for the runfiles |
| * directory. This can be set in the WORKSPACE file. This must be a valid target name. |
| */ |
| public String getWorkspaceName() { |
| return workspaceName; |
| } |
| |
| /** Returns the InputFile target for this package's BUILD file. */ |
| public InputFile getBuildFile() { |
| return buildFile; |
| } |
| |
| /** |
| * Returns the label of this package's BUILD file. |
| * |
| * <p>Typically <code>getBuildFileLabel().getName().equals("BUILD")</code> -- though not |
| * necessarily: data in a subdirectory of a test package may use a different filename to avoid |
| * inadvertently creating a new package. |
| */ |
| public Label getBuildFileLabel() { |
| return buildFile.getLabel(); |
| } |
| |
| /** |
| * 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 the root of the source tree beneath which this package's BUILD file was found, or |
| * {@link Optional#empty} if this package was derived from a WORKSPACE file. |
| * |
| * <p>Assumes invariant: If non-empty, {@code |
| * getSourceRoot().get().getRelative(packageId.getSourceRoot()).equals(getPackageDirectory())} |
| */ |
| public Optional<Root> getSourceRoot() { |
| return sourceRoot; |
| } |
| |
| /** |
| * 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}. |
| */ |
| public ImmutableList<Label> getOrComputeTransitivelyLoadedStarlarkFiles() { |
| return transitiveLoads != null ? transitiveLoads : computeTransitiveLoads(directLoads); |
| } |
| |
| /** |
| * 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. |
| */ |
| 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. |
| */ |
| 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); |
| } |
| } |
| |
| private static ImmutableList<Label> computeTransitiveLoads(Iterable<Module> directLoads) { |
| Set<Label> loads = new LinkedHashSet<>(); |
| BazelModuleContext.visitLoadGraphRecursively(directLoads, loads::add); |
| return ImmutableList.copyOf(loads); |
| } |
| |
| /** |
| * Returns true if errors were encountered during evaluation of this package. (The package may be |
| * incomplete and its contents should not be relied upon for critical operations. However, any |
| * Rules belonging to the package are guaranteed to be intact, unless their <code>containsErrors() |
| * </code> flag is set.) |
| */ |
| public boolean containsErrors() { |
| return containsErrors; |
| } |
| |
| /** |
| * Returns the first {@link FailureDetail} describing one of the package's errors, or {@code null} |
| * if it has no errors or all its errors lack details. |
| */ |
| @Nullable |
| public FailureDetail getFailureDetail() { |
| return failureDetail; |
| } |
| |
| /** Returns the number of Starlark computation steps executed by this BUILD file. */ |
| public long getComputationSteps() { |
| return computationSteps; |
| } |
| |
| /** Returns package overhead as configured by the configured {@link PackageOverheadEstimator}. */ |
| public OptionalLong getPackageOverhead() { |
| return packageOverhead == PACKAGE_OVERHEAD_UNSET |
| ? OptionalLong.empty() |
| : OptionalLong.of(packageOverhead); |
| } |
| |
| // ==== Accessors specific to external package / WORKSPACE logic ==== |
| |
| /** |
| * Returns the repository mapping for the requested external repository. |
| * |
| * @throws UnsupportedOperationException if called from a package other than the //external |
| * package |
| */ |
| public ImmutableMap<String, RepositoryName> getExternalPackageRepositoryMapping( |
| RepositoryName repository) { |
| if (!isRepoRulePackage()) { |
| throw new UnsupportedOperationException( |
| "Can only access the external package repository" |
| + "mappings from the //external package"); |
| } |
| return externalPackageRepositoryMappings.getOrDefault(repository, ImmutableMap.of()); |
| } |
| |
| /** |
| * Returns the full map of repository mappings collected so far. |
| * |
| * @throws UnsupportedOperationException if called from a package other than the //external |
| * package |
| */ |
| ImmutableMap<RepositoryName, ImmutableMap<String, RepositoryName>> |
| getExternalPackageRepositoryMappings() { |
| if (!isRepoRulePackage()) { |
| throw new UnsupportedOperationException( |
| "Can only access the external package repository" |
| + "mappings from the //external package"); |
| } |
| return this.externalPackageRepositoryMappings; |
| } |
| |
| public ImmutableList<TargetPattern> getRegisteredExecutionPlatforms() { |
| return registeredExecutionPlatforms; |
| } |
| |
| public ImmutableList<TargetPattern> getRegisteredToolchains() { |
| return registeredToolchains; |
| } |
| |
| public ImmutableList<TargetPattern> getUserRegisteredToolchains() { |
| return getRegisteredToolchains() |
| .subList( |
| 0, firstWorkspaceSuffixRegisteredToolchain.orElse(getRegisteredToolchains().size())); |
| } |
| |
| public ImmutableList<TargetPattern> getWorkspaceSuffixRegisteredToolchains() { |
| return getRegisteredToolchains() |
| .subList( |
| firstWorkspaceSuffixRegisteredToolchain.orElse(getRegisteredToolchains().size()), |
| getRegisteredToolchains().size()); |
| } |
| |
| OptionalInt getFirstWorkspaceSuffixRegisteredToolchain() { |
| return firstWorkspaceSuffixRegisteredToolchain; |
| } |
| |
| // ==== Target and macro accessors ==== |
| |
| /** Returns an (immutable, ordered) view of all the targets belonging to this package. */ |
| public ImmutableSortedMap<String, Target> getTargets() { |
| return targets; |
| } |
| |
| /** |
| * 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.values(), 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) targets.get(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; |
| } |
| |
| /** |
| * Throws {@link MacroNamespaceViolationException} if the given target (which must be a member of |
| * this package) violates macro naming rules. |
| */ |
| 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)); |
| } |
| } |
| |
| /** |
| * Returns the target (a member of this package) whose name is "targetName". First rules are |
| * searched, then output files, then input files. The target name must be valid, as defined by |
| * {@code LabelValidator#validateTargetName}. |
| * |
| * @throws NoSuchTargetException if the specified target was not found. |
| */ |
| public Target getTarget(String targetName) throws NoSuchTargetException { |
| Target target = targets.get(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(targetName); |
| throw new NoSuchTargetException( |
| label, |
| String.format( |
| "target '%s' not declared in package '%s' defined by %s%s", |
| targetName, |
| getName(), |
| metadata.buildFilename().asPath().getPathString(), |
| alternateTargetSuggestion)); |
| } |
| } |
| |
| private String getAlternateTargetSuggestion(String targetName) { |
| // 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 " |
| + getName() |
| + "/BUILD, or define a " |
| + "filegroup?)"; |
| } else if (filename.exists()) { |
| return "; however, a source file of this name exists. (Perhaps add " |
| + "'exports_files([\"" |
| + targetName |
| + "\"])' to " |
| + getName() |
| + "/BUILD?)"; |
| } else { |
| return TargetSuggester.suggestTargets(targetName, targets.keySet()); |
| } |
| } |
| |
| /** |
| * 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 or no such target by the given name exists. |
| */ |
| @Nullable |
| public MacroInstance getDeclaringMacroForTarget(String target) { |
| return targetsToDeclaringMacros.get(target); |
| } |
| |
| // ==== Initialization ==== |
| |
| private static Optional<Root> computeSourceRoot(Metadata metadata) { |
| if (metadata.isRepoRulePackage()) { |
| return Optional.empty(); |
| } |
| |
| RootedPath buildFileRootedPath = metadata.buildFilename(); |
| Root buildFileRoot = buildFileRootedPath.getRoot(); |
| PathFragment pkgIdFragment = metadata.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(metadata.getPackageDirectory()), |
| "Invalid BUILD file name for package '%s': %s (in source %s with packageDirectory %s and" |
| + " package identifier source root %s)", |
| metadata.packageIdentifier(), |
| metadata.buildFilename(), |
| sourceRoot, |
| metadata.getPackageDirectory(), |
| metadata.packageIdentifier().getSourceRoot()); |
| |
| return Optional.of(sourceRoot); |
| } |
| |
| /** |
| * Completes the initialization of this package. Only after this method may a package by shared |
| * publicly. |
| */ |
| private void finishInit(Builder builder) { |
| this.containsErrors |= builder.containsErrors(); |
| if (directLoads == null && transitiveLoads == null) { |
| Preconditions.checkState(containsErrors, "Loads not set for error-free package"); |
| builder.setLoads(ImmutableList.of()); |
| } |
| |
| this.workspaceName = builder.workspaceName; |
| |
| this.makeEnv = ImmutableMap.copyOf(builder.makeEnv); |
| this.targets = ImmutableSortedMap.copyOf(builder.recorder.getTargetMap()); |
| this.macros = ImmutableSortedMap.copyOf(builder.recorder.getMacroMap()); |
| this.macroNamespaceViolatingTargets = |
| ImmutableMap.copyOf(builder.recorder.getMacroNamespaceViolatingTargets()); |
| this.targetsToDeclaringMacros = |
| ImmutableSortedMap.copyOf(builder.recorder.getTargetsToDeclaringMacros()); |
| this.failureDetail = builder.getFailureDetail(); |
| this.registeredExecutionPlatforms = ImmutableList.copyOf(builder.registeredExecutionPlatforms); |
| this.registeredToolchains = ImmutableList.copyOf(builder.registeredToolchains); |
| this.firstWorkspaceSuffixRegisteredToolchain = builder.firstWorkspaceSuffixRegisteredToolchain; |
| ImmutableMap.Builder<RepositoryName, ImmutableMap<String, RepositoryName>> |
| repositoryMappingsBuilder = ImmutableMap.builder(); |
| if (!builder.externalPackageRepositoryMappings.isEmpty() && !builder.isRepoRulePackage()) { |
| // 'repo_mapping' should only be used in the //external package, i.e. should only appear |
| // in WORKSPACE files. Currently, if someone tries to use 'repo_mapping' in a BUILD rule, they |
| // will get a "no such attribute" error. This check is to protect against a 'repo_mapping' |
| // attribute being added to a rule in the future. |
| throw new IllegalArgumentException( |
| "'repo_mapping' may only be used in the //external package"); |
| } |
| builder.externalPackageRepositoryMappings.forEach( |
| (k, v) -> repositoryMappingsBuilder.put(k, ImmutableMap.copyOf(v))); |
| this.externalPackageRepositoryMappings = repositoryMappingsBuilder.buildOrThrow(); |
| OptionalLong overheadEstimate = builder.packageOverheadEstimator.estimatePackageOverhead(this); |
| this.packageOverhead = overheadEstimate.orElse(PACKAGE_OVERHEAD_UNSET); |
| } |
| |
| // TODO(bazel-team): This is a mutation, but Package is supposed to be immutable. In practice it |
| // seems like this is only called during Package construction (including deserialization), |
| // principally via Rule#reportError. I would bet that all of the callers have access to a |
| // Package.Builder, in which case they should report the bit using the builder instead. But maybe |
| // it's easier to just checkState() that the Package hasn't already finished constructing. |
| void setContainsErrors() { |
| containsErrors = true; |
| } |
| |
| // ==== Stringification / debugging ==== |
| |
| @Override |
| public String toString() { |
| return "Package(" |
| + getName() |
| + ")=" |
| + (targets != null ? getTargets(Rule.class) : "initializing..."); |
| } |
| |
| /** |
| * 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 (i.e. not one for |
| * WORKSPACE or bzlmod). |
| */ |
| public static Builder newPackageBuilder( |
| PackageSettings packageSettings, |
| PackageIdentifier id, |
| 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, |
| // TODO(bazel-team): See Builder() constructor comment about use of null for this param. |
| @Nullable ConfigSettingVisibilityPolicy configSettingVisibilityPolicy, |
| @Nullable Globber globber, |
| boolean enableNameConflictChecking) { |
| // Determine whether this is for a repo rule package. We shouldn't actually have to do this |
| // because newPackageBuilder() is supposed to only be called for normal packages. Unfortunately |
| // serialization still uses the same code path for deserializing BUILD and WORKSPACE files, |
| // violating this method's contract. |
| boolean isRepoRulePackage = |
| id.equals(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER) |
| // For bzlmod packages, setWorkspaceName() is not called, so this expression doesn't |
| // change during package evaluation. |
| || workspaceName.equals(DUMMY_WORKSPACE_NAME_FOR_BZLMOD_PACKAGES); |
| |
| return new Builder( |
| new Metadata( |
| /* packageIdentifier= */ id, |
| /* buildFilename= */ filename, |
| /* isRepoRulePackage= */ isRepoRulePackage, |
| /* repositoryMapping= */ repositoryMapping, |
| /* associatedModuleName= */ associatedModuleName, |
| /* associatedModuleVersion= */ associatedModuleVersion, |
| /* configSettingVisibilityPolicy= */ configSettingVisibilityPolicy, |
| /* succinctTargetNotFoundErrors= */ packageSettings.succinctTargetNotFoundErrors()), |
| SymbolGenerator.create(id), |
| packageSettings.precomputeTransitiveLoads(), |
| noImplicitFileExport, |
| simplifyUnconditionalSelectsInRuleAttrs, |
| workspaceName, |
| mainRepositoryMapping, |
| cpuBoundSemaphore, |
| packageOverheadEstimator, |
| generatorMap, |
| globber, |
| enableNameConflictChecking); |
| } |
| |
| public static Builder newExternalPackageBuilder( |
| PackageSettings packageSettings, |
| WorkspaceFileKey workspaceFileKey, |
| String workspaceName, |
| RepositoryMapping mainRepoMapping, |
| boolean noImplicitFileExport, |
| boolean simplifyUnconditionalSelectsInRuleAttrs, |
| PackageOverheadEstimator packageOverheadEstimator) { |
| return new Builder( |
| new Metadata( |
| /* packageIdentifier= */ LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER, |
| /* buildFilename= */ workspaceFileKey.getPath(), |
| /* isRepoRulePackage= */ true, |
| /* repositoryMapping= */ mainRepoMapping, |
| /* associatedModuleName= */ Optional.empty(), |
| /* associatedModuleVersion= */ Optional.empty(), |
| /* configSettingVisibilityPolicy= */ null, |
| /* succinctTargetNotFoundErrors= */ packageSettings.succinctTargetNotFoundErrors()), |
| // The SymbolGenerator is based on workspaceFileKey rather than a package id or path, |
| // in order to distinguish different chunks of the same WORKSPACE file. |
| SymbolGenerator.create(workspaceFileKey), |
| packageSettings.precomputeTransitiveLoads(), |
| noImplicitFileExport, |
| simplifyUnconditionalSelectsInRuleAttrs, |
| workspaceName, |
| mainRepoMapping, |
| /* cpuBoundSemaphore= */ null, |
| packageOverheadEstimator, |
| /* generatorMap= */ null, |
| /* globber= */ null, |
| /* enableNameConflictChecking= */ true); |
| } |
| |
| public static Builder newExternalPackageBuilderForBzlmod( |
| RootedPath moduleFilePath, |
| boolean noImplicitFileExport, |
| boolean simplifyUnconditionalSelectsInRuleAttrs, |
| PackageIdentifier basePackageId, |
| RepositoryMapping repoMapping) { |
| return new Builder( |
| new Metadata( |
| /* packageIdentifier= */ basePackageId, |
| /* buildFilename= */ moduleFilePath, |
| /* isRepoRulePackage= */ true, |
| /* repositoryMapping= */ repoMapping, |
| /* associatedModuleName= */ Optional.empty(), |
| /* associatedModuleVersion= */ Optional.empty(), |
| /* configSettingVisibilityPolicy= */ null, |
| /* succinctTargetNotFoundErrors= */ PackageSettings.DEFAULTS |
| .succinctTargetNotFoundErrors()), |
| SymbolGenerator.create(basePackageId), |
| PackageSettings.DEFAULTS.precomputeTransitiveLoads(), |
| noImplicitFileExport, |
| simplifyUnconditionalSelectsInRuleAttrs, |
| /* workspaceName= */ DUMMY_WORKSPACE_NAME_FOR_BZLMOD_PACKAGES, |
| /* mainRepositoryMapping= */ null, |
| /* cpuBoundSemaphore= */ null, |
| PackageOverheadEstimator.NOOP_ESTIMATOR, |
| /* generatorMap= */ null, |
| /* globber= */ null, |
| /* enableNameConflictChecking= */ true) |
| .setLoads(ImmutableList.of()); |
| } |
| |
| // ==== Non-trivial nested classes ==== |
| |
| /** |
| * 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 TargetDefinitionContext { |
| |
| /** |
| * A bundle of options affecting package construction, that is not specific to any particular |
| * package. |
| */ |
| 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() {}; |
| } |
| |
| private final boolean precomputeTransitiveLoads; |
| private final boolean noImplicitFileExport; |
| |
| // The map from each repository to that repository's remappings map. |
| // This is only used in the //external package, it is an empty map for all other packages. |
| private final HashMap<RepositoryName, HashMap<String, RepositoryName>> |
| externalPackageRepositoryMappings = new HashMap<>(); |
| |
| /** Estimates the package overhead of this package. */ |
| private final PackageOverheadEstimator packageOverheadEstimator; |
| |
| // A package's FailureDetail field derives from the events on its Builder's event handler. |
| // During package deserialization, those events are unavailable, because those events aren't |
| // serialized [*]. Its FailureDetail value is serialized, however. During deserialization, that |
| // value is assigned here, so that it can be assigned to the deserialized package. |
| // |
| // Likewise, during workspace part assembly, errors from parent parts should propagate to their |
| // children. |
| // |
| // [*] Not in the context of the package, anyway. Skyframe values containing a package may |
| // serialize events emitted during its construction/evaluation. |
| @Nullable private FailureDetail failureDetailOverride = null; |
| |
| // 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 final List<TargetPattern> registeredExecutionPlatforms = new ArrayList<>(); |
| private final List<TargetPattern> registeredToolchains = new ArrayList<>(); |
| |
| /** |
| * Tracks the index within {@link #registeredToolchains} of the first toolchain registered from |
| * the WORKSPACE suffixes rather than the WORKSPACE file (if any). |
| * |
| * <p>This is needed to distinguish between these toolchains during resolution: toolchains |
| * registered in WORKSPACE have precedence over those defined in non-root Bazel modules, which |
| * in turn have precedence over those from the WORKSPACE suffixes. |
| */ |
| private OptionalInt firstWorkspaceSuffixRegisteredToolchain = OptionalInt.empty(); |
| |
| private boolean alreadyBuilt = false; |
| |
| private Builder( |
| Metadata metadata, |
| SymbolGenerator<?> symbolGenerator, |
| boolean precomputeTransitiveLoads, |
| boolean noImplicitFileExport, |
| boolean simplifyUnconditionalSelectsInRuleAttrs, |
| String workspaceName, |
| RepositoryMapping mainRepositoryMapping, |
| @Nullable Semaphore cpuBoundSemaphore, |
| PackageOverheadEstimator packageOverheadEstimator, |
| @Nullable ImmutableMap<Location, String> generatorMap, |
| @Nullable Globber globber, |
| boolean enableNameConflictChecking) { |
| super( |
| metadata, |
| new Package(metadata), |
| symbolGenerator, |
| simplifyUnconditionalSelectsInRuleAttrs, |
| workspaceName, |
| mainRepositoryMapping, |
| cpuBoundSemaphore, |
| generatorMap, |
| globber, |
| enableNameConflictChecking); |
| this.precomputeTransitiveLoads = precomputeTransitiveLoads; |
| this.noImplicitFileExport = noImplicitFileExport; |
| this.packageOverheadEstimator = packageOverheadEstimator; |
| } |
| |
| /** 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) ctx : null; |
| } |
| |
| /** |
| * Retrieves this object from a Starlark thread. If not present, throws {@code EvalException} |
| * with an error message indicating that {@code what} can't be used in this Starlark |
| * environment. |
| * |
| * <p>If {@code allowBuild} is false, this method also throws if we're currently executing a |
| * BUILD file (or legacy macro called from a BUILD file). |
| * |
| * <p>If {@code allowFinalizers} is false, this method also throws if we're currently executing |
| * a rule finalizer implementation (or a legacy macro called from within such an |
| * implementation). |
| * |
| * <p>If {@code allowNonFinalizerSymbolicMacros} is false, this method also throws if we're |
| * currently executing the implementation of a symbolic macro implementation which is not a rule |
| * finalizer (or a legacy macro called from within such an implementation). |
| * |
| * <p>If {@code allowWorkspace} is false, this method also throws if we're currently executing a |
| * WORKSPACE file (or a legacy macro called from a WORKSPACE file). |
| * |
| * <p>It is not allowed for all three bool params to be false. |
| */ |
| @CanIgnoreReturnValue |
| public static Builder fromOrFail( |
| StarlarkThread thread, |
| String what, |
| boolean allowBuild, |
| boolean allowFinalizers, |
| boolean allowNonFinalizerSymbolicMacros, |
| boolean allowWorkspace) |
| throws EvalException { |
| Preconditions.checkArgument( |
| allowBuild || allowFinalizers || allowNonFinalizerSymbolicMacros || allowWorkspace); |
| |
| @Nullable StarlarkThreadContext ctx = thread.getThreadLocal(StarlarkThreadContext.class); |
| boolean bad = false; |
| if (ctx instanceof Builder builder) { |
| bad |= !allowBuild && !builder.isRepoRulePackage(); |
| bad |= !allowFinalizers && builder.recorder.currentlyInFinalizer(); |
| bad |= !allowNonFinalizerSymbolicMacros && builder.recorder.currentlyInNonFinalizerMacro(); |
| bad |= !allowWorkspace && builder.isRepoRulePackage(); |
| if (!bad) { |
| return builder; |
| } |
| } |
| |
| boolean symbolicMacrosEnabled = |
| thread |
| .getSemantics() |
| .getBool(BuildLanguageOptions.EXPERIMENTAL_ENABLE_FIRST_CLASS_MACROS); |
| ArrayList<String> allowedUses = new ArrayList<>(); |
| if (allowBuild) { |
| // Only disambiguate as "legacy" if the alternative, symbolic macros, are enabled. |
| allowedUses.add( |
| String.format("a BUILD file (or %smacro)", symbolicMacrosEnabled ? "legacy " : "")); |
| } |
| // Even if symbolic macros are allowed, don't mention them in the error message unless they |
| // are enabled. |
| if (symbolicMacrosEnabled) { |
| if (allowFinalizers && allowNonFinalizerSymbolicMacros) { |
| allowedUses.add("a symbolic macro"); |
| } else if (allowFinalizers) { |
| allowedUses.add("a rule finalizer"); |
| } else if (allowNonFinalizerSymbolicMacros) { |
| allowedUses.add("a non-finalizer symbolic macro"); |
| } |
| } |
| if (allowWorkspace) { |
| allowedUses.add("a WORKSPACE file"); |
| } |
| throw Starlark.errorf( |
| "%s can only be used while evaluating %s", what, StringUtil.joinEnglishList(allowedUses)); |
| } |
| |
| /** Convenience method for {@link #fromOrFail} that permits any context with a Builder. */ |
| @CanIgnoreReturnValue |
| public static Builder fromOrFail(StarlarkThread thread, String what) throws EvalException { |
| return fromOrFail( |
| thread, |
| what, |
| /* allowBuild= */ true, |
| /* allowFinalizers= */ true, |
| /* allowNonFinalizerSymbolicMacros= */ true, |
| /* allowWorkspace= */ true); |
| } |
| |
| /** |
| * Convenience method for {@link #fromOrFail} that permits only BUILD contexts (without symbolic |
| * macros). |
| */ |
| @CanIgnoreReturnValue |
| public static Builder fromOrFailAllowBuildOnly(StarlarkThread thread, String what) |
| throws EvalException { |
| return fromOrFail( |
| thread, |
| what, |
| /* allowBuild= */ true, |
| /* allowFinalizers= */ false, |
| /* allowNonFinalizerSymbolicMacros= */ false, |
| /* allowWorkspace= */ false); |
| } |
| |
| /** Convenience method for {@link #fromOrFail} that permits only WORKSPACE contexts. */ |
| @CanIgnoreReturnValue |
| public static Builder fromOrFailAllowWorkspaceOnly(StarlarkThread thread, String what) |
| throws EvalException { |
| return fromOrFail( |
| thread, |
| what, |
| /* allowBuild= */ false, |
| /* allowFinalizers= */ false, |
| /* allowNonFinalizerSymbolicMacros= */ false, |
| /* allowWorkspace= */ true); |
| } |
| |
| /** |
| * Convenience method for {@link #fromOrFail} that permits BUILD or WORKSPACE or rule finalizer |
| * contexts. |
| */ |
| @CanIgnoreReturnValue |
| public static Builder fromOrFailDisallowNonFinalizerMacros(StarlarkThread thread, String what) |
| throws EvalException { |
| return fromOrFail( |
| thread, |
| what, |
| /* allowBuild= */ true, |
| /* allowFinalizers= */ true, |
| /* allowNonFinalizerSymbolicMacros= */ false, |
| /* allowWorkspace= */ true); |
| } |
| |
| /** |
| * Convenience method for {@link #fromOrFail} that permits BUILD or symbolic macro contexts |
| * (including rule finalizers). |
| */ |
| @CanIgnoreReturnValue |
| public static Builder fromOrFailDisallowWorkspace(StarlarkThread thread, String what) |
| throws EvalException { |
| return fromOrFail( |
| thread, |
| what, |
| /* allowBuild= */ true, |
| /* allowFinalizers= */ true, |
| /* allowNonFinalizerSymbolicMacros= */ true, |
| /* allowWorkspace= */ false); |
| } |
| |
| /** |
| * Updates the externalPackageRepositoryMappings entry for {@code repoWithin}. Adds new entry |
| * from {@code localName} to {@code mappedName} in {@code repoWithin}'s map. |
| * |
| * @param repoWithin the RepositoryName within which the mapping should apply |
| * @param localName the name that actually appears in the WORKSPACE and BUILD files in the |
| * {@code repoWithin} repository |
| * @param mappedName the RepositoryName by which localName should be referenced |
| */ |
| @CanIgnoreReturnValue |
| Builder addRepositoryMappingEntry( |
| RepositoryName repoWithin, String localName, RepositoryName mappedName) { |
| HashMap<String, RepositoryName> mapping = |
| externalPackageRepositoryMappings.computeIfAbsent( |
| repoWithin, (RepositoryName k) -> new HashMap<>()); |
| mapping.put(localName, mappedName); |
| return this; |
| } |
| |
| /** Adds all the mappings from a given {@link Package}. */ |
| @CanIgnoreReturnValue |
| Builder addRepositoryMappings(Package aPackage) { |
| ImmutableMap<RepositoryName, ImmutableMap<String, RepositoryName>> repositoryMappings = |
| aPackage.externalPackageRepositoryMappings; |
| for (Map.Entry<RepositoryName, ImmutableMap<String, RepositoryName>> repositoryName : |
| repositoryMappings.entrySet()) { |
| for (Map.Entry<String, RepositoryName> repositoryNameRepositoryNameEntry : |
| repositoryName.getValue().entrySet()) { |
| addRepositoryMappingEntry( |
| repositoryName.getKey(), |
| repositoryNameRepositoryNameEntry.getKey(), |
| repositoryNameRepositoryNameEntry.getValue()); |
| } |
| } |
| return this; |
| } |
| |
| /** |
| * Return a read-only copy of the name mapping of external repositories for a given repository. |
| * Reading that mapping directly from the builder allows to also take mappings into account that |
| * are only discovered while constructing the external package (e.g., the mapping of the name of |
| * the main workspace to the canonical main name '@'). |
| */ |
| RepositoryMapping getRepositoryMappingFor(RepositoryName name) { |
| Map<String, RepositoryName> mapping = externalPackageRepositoryMappings.get(name); |
| if (mapping == null) { |
| return RepositoryMapping.ALWAYS_FALLBACK; |
| } else { |
| return RepositoryMapping.createAllowingFallback(mapping); |
| } |
| } |
| |
| @Override |
| public void mergePackageArgsFrom(PackageArgs packageArgs) { |
| pkg.packageArgs = pkg.packageArgs.mergeWith(packageArgs); |
| } |
| |
| @Override |
| public void mergePackageArgsFrom(PackageArgs.Builder builder) { |
| mergePackageArgsFrom(builder.build()); |
| } |
| |
| @Override |
| public PackageArgs getPartialPackageArgs() { |
| return pkg.packageArgs; |
| } |
| |
| /** Sets the number of Starlark computation steps executed by this BUILD file. */ |
| void setComputationSteps(long n) { |
| pkg.computationSteps = n; |
| } |
| |
| void setFailureDetailOverride(FailureDetail failureDetail) { |
| failureDetailOverride = failureDetail; |
| } |
| |
| @Nullable |
| FailureDetail getFailureDetail() { |
| if (failureDetailOverride != null) { |
| return failureDetailOverride; |
| } |
| |
| List<Event> undetailedEvents = null; |
| for (Event event : localEventHandler.getEvents()) { |
| if (event.getKind() != EventKind.ERROR) { |
| continue; |
| } |
| DetailedExitCode detailedExitCode = event.getProperty(DetailedExitCode.class); |
| if (detailedExitCode != null && detailedExitCode.getFailureDetail() != null) { |
| return detailedExitCode.getFailureDetail(); |
| } |
| if (containsErrors()) { |
| if (undetailedEvents == null) { |
| undetailedEvents = new ArrayList<>(); |
| } |
| undetailedEvents.add(event); |
| } |
| } |
| if (undetailedEvents != null) { |
| BugReport.sendNonFatalBugReport( |
| new IllegalStateException("Package has undetailed error from " + undetailedEvents)); |
| } |
| return null; |
| } |
| |
| // TODO(#19922): Require this to be set before BUILD evaluation. |
| @CanIgnoreReturnValue |
| public Builder setLoads(Iterable<Module> directLoads) { |
| checkLoadsNotSet(); |
| if (precomputeTransitiveLoads) { |
| pkg.transitiveLoads = computeTransitiveLoads(directLoads); |
| } else { |
| pkg.directLoads = ImmutableList.copyOf(directLoads); |
| } |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| Builder setTransitiveLoadsForDeserialization(ImmutableList<Label> transitiveLoads) { |
| checkLoadsNotSet(); |
| pkg.transitiveLoads = Preconditions.checkNotNull(transitiveLoads); |
| return this; |
| } |
| |
| private void checkLoadsNotSet() { |
| Preconditions.checkState( |
| pkg.directLoads == null, "Direct loads already set: %s", pkg.directLoads); |
| Preconditions.checkState( |
| pkg.transitiveLoads == null, "Transitive loads already set: %s", pkg.transitiveLoads); |
| } |
| |
| public Set<Target> getTargets() { |
| return recorder.getTargets(); |
| } |
| |
| void replaceTarget(Target newTarget) { |
| Preconditions.checkArgument( |
| newTarget.getPackage() == pkg, // pointer comparison since we're constructing `pkg` |
| "Replacement target belongs to package '%s', expected '%s'", |
| newTarget.getPackage(), |
| pkg); |
| recorder.replaceTarget(newTarget); |
| } |
| |
| @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); |
| } |
| |
| @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); |
| } |
| |
| /** |
| * 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)); |
| } |
| } |
| |
| void addRegisteredExecutionPlatforms(List<TargetPattern> platforms) { |
| this.registeredExecutionPlatforms.addAll(platforms); |
| } |
| |
| void addRegisteredToolchains(List<TargetPattern> toolchains, boolean forWorkspaceSuffix) { |
| if (forWorkspaceSuffix && firstWorkspaceSuffixRegisteredToolchain.isEmpty()) { |
| firstWorkspaceSuffixRegisteredToolchain = OptionalInt.of(registeredToolchains.size()); |
| } |
| this.registeredToolchains.addAll(toolchains); |
| } |
| |
| void setFirstWorkspaceSuffixRegisteredToolchain( |
| OptionalInt firstWorkspaceSuffixRegisteredToolchain) { |
| this.firstWorkspaceSuffixRegisteredToolchain = firstWorkspaceSuffixRegisteredToolchain; |
| } |
| |
| /** |
| * Ensures that all symbolic macros in the package have expanded. |
| * |
| * <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 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. |
| if (!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); |
| } |
| } |
| } |
| |
| @CanIgnoreReturnValue |
| private Builder beforeBuild(boolean discoverAssumedInputFiles) throws NoSuchPackageException { |
| // For correct semantics, we refuse to build a package that 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(), |
| "Cannot build a package with unexpanded symbolic macros; call" |
| + " expandAllRemainingMacros()"); |
| |
| if (ioException != null) { |
| throw new NoSuchPackageException( |
| getPackageIdentifier(), ioExceptionMessage, ioException, ioExceptionDetailedExitCode); |
| } |
| |
| // 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; |
| } |
| |
| // 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. |
| pkg.buildFile = |
| Preconditions.checkNotNull( |
| (InputFile) recorder.getTargetMap().get(buildFileLabel.getName())); |
| |
| // TODO(bazel-team): We run testSuiteImplicitTestsAccumulator here in beforeBuild(), but what |
| // if one of the accumulated tests is later removed in PackageFunction, between the call to |
| // buildPartial() and finishBuild(), due to a label-crossing-subpackage-boundary error? Seems |
| // like that would mean a test_suite is referencing a Target that's been deleted from its |
| // Package. |
| |
| // Clear tests before discovering them again in order to keep this method idempotent - |
| // otherwise we may double-count tests if we're called twice due to a skyframe restart, etc. |
| testSuiteImplicitTestsAccumulator.clearAccumulatedTests(); |
| |
| Map<String, InputFile> newInputFiles = new HashMap<>(); |
| for (Rule rule : recorder.getRules()) { |
| if (discoverAssumedInputFiles) { |
| // Labels mentioned by a rule that refer to an unknown target in the current package are |
| // assumed to be InputFiles, unless they overlap a namespace owned by a macro. Create |
| // these InputFiles now. But don't do this for rules created within a symbolic macro, |
| // since we don't want the evaluation of the macro to affect the semantics of whether or |
| // not this target was created (i.e. all implicitly created files are knowable without |
| // necessarily evaluating symbolic macros). |
| if (recorder.isRuleCreatedInMacro(rule)) { |
| continue; |
| } |
| // We use a temporary map, newInputFiles, to avoid concurrent modification to this.targets |
| // while iterating (via getRules() above). |
| List<Label> labels = recorder.getRuleLabels(rule); |
| for (Label label : labels) { |
| String name = label.getName(); |
| if (label.getPackageIdentifier().equals(metadata.packageIdentifier()) |
| && !recorder.getTargetMap().containsKey(name) |
| && !newInputFiles.containsKey(name)) { |
| // Check for collision with a macro namespace. Currently this is a linear loop over |
| // all symbolic macros in the package. |
| // TODO(#19922): This is quadratic complexity, optimize with a trie or similar if |
| // needed. |
| boolean macroConflictsFound = false; |
| for (MacroInstance macro : recorder.getMacroMap().values()) { |
| macroConflictsFound |= |
| TargetRecorder.nameIsWithinMacroNamespace(name, macro.getName()); |
| } |
| if (!macroConflictsFound) { |
| Location loc = rule.getLocation(); |
| newInputFiles.put( |
| name, |
| // Targets added this way are not in any macro, so |
| // copyAppendingCurrentMacroLocation() munging isn't applicable. |
| noImplicitFileExport |
| ? new PrivateVisibilityInputFile(pkg, label, loc) |
| : new InputFile(pkg, label, loc)); |
| } |
| } |
| } |
| } |
| |
| testSuiteImplicitTestsAccumulator.processRule(rule); |
| } |
| |
| // Make sure all accumulated values are sorted for determinism. |
| testSuiteImplicitTestsAccumulator.sortTests(); |
| |
| for (InputFile file : newInputFiles.values()) { |
| recorder.addInputFileUnchecked(file); |
| } |
| |
| return this; |
| } |
| |
| /** Intended for use by {@link com.google.devtools.build.lib.skyframe.PackageFunction} only. */ |
| // TODO(bazel-team): It seems like the motivation for this method (added in cl/74794332) is to |
| // allow PackageFunction to delete targets that are found to violate the |
| // label-crossing-subpackage-boundaries check. Is there a simpler way to express this idea that |
| // doesn't make package-building a multi-stage process? |
| @CanIgnoreReturnValue |
| public Builder buildPartial() throws NoSuchPackageException { |
| if (alreadyBuilt) { |
| return this; |
| } |
| return beforeBuild(/* discoverAssumedInputFiles= */ true); |
| } |
| |
| /** Intended for use by {@link com.google.devtools.build.lib.skyframe.PackageFunction} only. */ |
| public Package finishBuild() { |
| if (alreadyBuilt) { |
| return pkg; |
| } |
| |
| // Freeze rules, compacting their attributes' representations. |
| for (Rule rule : recorder.getRules()) { |
| rule.freeze(); |
| } |
| |
| // 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(); |
| } |
| } |
| |
| // Build the package. |
| pkg.finishInit(this); |
| alreadyBuilt = true; |
| return pkg; |
| } |
| |
| /** 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 pkg; |
| } |
| beforeBuild(discoverAssumedInputFiles); |
| return finishBuild(); |
| } |
| } |
| |
| /** A collection of data that is known before BUILD file evaluation even begins. */ |
| public record Metadata( |
| PackageIdentifier packageIdentifier, |
| RootedPath buildFilename, |
| boolean isRepoRulePackage, |
| RepositoryMapping repositoryMapping, |
| Optional<String> associatedModuleName, |
| Optional<String> associatedModuleVersion, |
| @Nullable ConfigSettingVisibilityPolicy configSettingVisibilityPolicy, |
| boolean succinctTargetNotFoundErrors) { |
| |
| public Metadata { |
| Preconditions.checkNotNull(packageIdentifier); |
| Preconditions.checkNotNull(buildFilename); |
| Preconditions.checkNotNull(repositoryMapping); |
| Preconditions.checkNotNull(associatedModuleName); |
| Preconditions.checkNotNull(associatedModuleVersion); |
| |
| // Check for consistency between isRepoRulePackage and whether the buildFilename is a |
| // WORKSPACE / MODULE.bazel file. |
| String baseName = buildFilename.asPath().getBaseName(); |
| boolean isWorkspaceFile = |
| baseName.equals(LabelConstants.WORKSPACE_DOT_BAZEL_FILE_NAME.getPathString()) |
| || baseName.equals(LabelConstants.WORKSPACE_FILE_NAME.getPathString()); |
| boolean isModuleDotBazelFile = |
| baseName.equals(LabelConstants.MODULE_DOT_BAZEL_FILE_NAME.getPathString()); |
| Preconditions.checkArgument(isRepoRulePackage == (isWorkspaceFile || isModuleDotBazelFile)); |
| } |
| |
| /** 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 buildFilename.asPath().getParentDirectory(); |
| } |
| } |
| |
| /** 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); |
| } |
| } |
| } |