diff --git a/src/main/java/com/google/devtools/build/lib/packages/LabelConverter.java b/src/main/java/com/google/devtools/build/lib/packages/LabelConverter.java index 64ac5ba4084d5a..d00603e81b6f59 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/LabelConverter.java +++ b/src/main/java/com/google/devtools/build/lib/packages/LabelConverter.java @@ -26,6 +26,8 @@ /** * Converts a label literal string into a {@link Label} object, using the appropriate base package * and repo mapping. + * + *

Instances are not thread-safe, due to an internal cache. */ public class LabelConverter { diff --git a/src/main/java/com/google/devtools/build/lib/packages/Package.java b/src/main/java/com/google/devtools/build/lib/packages/Package.java index 70c3739210cf61..664a9c31c2561e 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/Package.java +++ b/src/main/java/com/google/devtools/build/lib/packages/Package.java @@ -124,6 +124,9 @@ private NameConflictException(String message) { } } + /** Governs the error message behavior of {@link Package#getTarget}. */ + // 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. private final boolean succinctTargetNotFoundErrors; /** @@ -216,30 +219,29 @@ public long getComputationSteps() { } /** - * Package initialization, part 1 of 3: instantiates a new package with the given name. - * - *

As part of initialization, {@link Builder} constructs {@link InputFile} and {@link - * PackageGroup} instances that require a valid Package instance where {@link - * Package#getNameFragment()} is accessible. That's why these settings are applied here at the - * start. + * Constructs a new (incomplete) Package instance. Intended only for use by {@link + * Package.Builder}. * - *

{@code name} MUST be a suffix of {@code filename.getParentDirectory())}. + *

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( - PackageIdentifier packageId, - String workspaceName, + Metadata metadata, Optional associatedModuleName, Optional associatedModuleVersion, boolean succinctTargetNotFoundErrors) { - this.metadata = new Metadata(); - this.metadata.packageIdentifier = packageId; - this.metadata.workspaceName = workspaceName; + this.metadata = metadata; + // TODO(#19922): move these three fields into Metadata this.associatedModuleName = associatedModuleName; this.associatedModuleVersion = associatedModuleVersion; this.succinctTargetNotFoundErrors = succinctTargetNotFoundErrors; } - /** Returns this packages' identifier. */ + /** Returns this package's identifier. */ public PackageIdentifier getPackageIdentifier() { return metadata.packageIdentifier; } @@ -319,10 +321,7 @@ private static Root getSourceRoot(RootedPath buildFileRootedPath, PathFragment p } /** - * Package initialization: part 3 of 3: applies all other settings and completes initialization of - * the package. - * - *

Only after this method is called can this package be considered "complete" and be shared + * Completes the initialization of this package. Only after this method may a package by shared * publicly. */ private void finishInit(Builder builder) { @@ -366,7 +365,6 @@ private void finishInit(Builder builder) { this.registeredExecutionPlatforms = ImmutableList.copyOf(builder.registeredExecutionPlatforms); this.registeredToolchains = ImmutableList.copyOf(builder.registeredToolchains); this.firstWorkspaceSuffixRegisteredToolchain = builder.firstWorkspaceSuffixRegisteredToolchain; - this.metadata.repositoryMapping = Preconditions.checkNotNull(builder.repositoryMapping); ImmutableMap.Builder> repositoryMappingsBuilder = ImmutableMap.builder(); if (!builder.externalPackageRepositoryMappings.isEmpty() && !builder.isRepoRulePackage()) { @@ -527,6 +525,8 @@ public static void maybeAddPackageContainsErrorsEventToHandler( } } + // TODO(bazel-team): Seems like we shouldn't permit this mutation on an already-initialized + // Package. Is it possible for this to be called today after initialization? void setContainsErrors() { containsErrors = true; } @@ -818,7 +818,10 @@ private static DetailedExitCode createDetailedCode(String errorMessage, Code cod */ public static class Builder { - /** Defines configuration to control the runtime behavior of {@link Package}s. */ + /** + * 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 @@ -864,13 +867,6 @@ default boolean precomputeTransitiveLoads() { private final HashMap> externalPackageRepositoryMappings = new HashMap<>(); - /** - * The map of repository reassignments for BUILD packages loaded within external repositories. - * It contains an entry from "@

" to "@" for packages within the main - * workspace. - */ - private final RepositoryMapping repositoryMapping; - /** Converts label literals to Label objects within this package. */ private final LabelConverter labelConverter; @@ -964,6 +960,7 @@ String getGeneratorNameByLocation(Location loc) { } /** Sets the package's map of "generator_name" values keyed by the location of the call site. */ + // TODO(#19922): Require this to be set before BUILD evaluation. @CanIgnoreReturnValue public Builder setGeneratorMap(ImmutableMap map) { this.generatorMap = map; @@ -1006,19 +1003,22 @@ public T intern(T sample) { Optional associatedModuleVersion, boolean noImplicitFileExport, RepositoryMapping repositoryMapping, + // TODO(#19922): Spurious parameter, delete. RepositoryMapping mainRepositoryMapping, @Nullable Semaphore cpuBoundSemaphore, PackageOverheadEstimator packageOverheadEstimator) { + Metadata metadata = new Metadata(); + metadata.packageIdentifier = Preconditions.checkNotNull(id); + metadata.workspaceName = Preconditions.checkNotNull(workspaceName); + metadata.repositoryMapping = Preconditions.checkNotNull(repositoryMapping); this.pkg = new Package( - id, - workspaceName, + metadata, associatedModuleName, associatedModuleVersion, packageSettings.succinctTargetNotFoundErrors()); this.precomputeTransitiveLoads = packageSettings.precomputeTransitiveLoads(); this.noImplicitFileExport = noImplicitFileExport; - this.repositoryMapping = repositoryMapping; this.labelConverter = new LabelConverter(id, repositoryMapping); if (pkg.getName().startsWith("javatests/")) { mergePackageArgsFrom(PackageArgs.builder().setDefaultTestOnly(true)); @@ -1107,6 +1107,7 @@ Interner> getListInterner() { } /** Sets the name of this package's BUILD file. */ + // TODO(#19922): Require this to be set before BUILD evaluation. @CanIgnoreReturnValue public Builder setFilename(RootedPath filename) { pkg.metadata.filename = filename; @@ -1187,6 +1188,7 @@ public PackageArgs getPartialPackageArgs() { } /** Sets visibility enforcement policy for config_setting. */ + // TODO(#19922): Require this to be set before BUILD evaluation. @CanIgnoreReturnValue public Builder setConfigSettingVisibilityPolicy(ConfigSettingVisibilityPolicy policy) { this.pkg.metadata.configSettingVisibilityPolicy = policy; @@ -1194,6 +1196,11 @@ public Builder setConfigSettingVisibilityPolicy(ConfigSettingVisibilityPolicy po } /** Uses the workspace name from {@code //external} to set this package's workspace name. */ + // TODO(#19922): Seems like this is only used for WORKSPACE logic (`workspace()` callable), but + // it clashes with the notion that, for BUILD files, the workspace name should be supplied to + // the Builder constructor and not mutated during evaluation. Either put up with this until we + // delete WORKSPACE logic, or else separate the `workspace()` callable's mutation from this + // metadata field. @CanIgnoreReturnValue @VisibleForTesting public Builder setWorkspaceName(String workspaceName) { @@ -1292,6 +1299,7 @@ FailureDetail getFailureDetail() { return null; } + // TODO(#19922): Require this to be set before BUILD evaluation. @CanIgnoreReturnValue public Builder setLoads(Iterable directLoads) { checkLoadsNotSet(); @@ -1645,10 +1653,10 @@ void setFirstWorkspaceSuffixRegisteredToolchain( @CanIgnoreReturnValue private Builder beforeBuild(boolean discoverAssumedInputFiles) throws NoSuchPackageException { - Preconditions.checkNotNull(pkg); + // TODO(#19922): Won't have to check filename/buildFileLabel if we merge setFilename into the + // Builder constructor. Preconditions.checkNotNull(pkg.metadata.filename); Preconditions.checkNotNull(pkg.metadata.buildFileLabel); - Preconditions.checkNotNull(makeEnv); if (ioException != null) { throw new NoSuchPackageException( getPackageIdentifier(), ioExceptionMessage, ioException, ioExceptionDetailedExitCode); @@ -1665,13 +1673,19 @@ private Builder beforeBuild(boolean discoverAssumedInputFiles) throws NoSuchPack targets = ((SnapshottableBiMap) targets).getUnderlyingBiMap(); } - // We create the original BUILD InputFile when the package filename is set; however, the - // visibility may be overridden with an exports_files directive, so we need to obtain the - // current instance here. + // We create an InputFile corresponding to the BUILD file when setFilename is called. 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.metadata.buildFile = (InputFile) Preconditions.checkNotNull(targets.get(pkg.metadata.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(); @@ -1682,7 +1696,15 @@ private Builder beforeBuild(boolean discoverAssumedInputFiles) throws NoSuchPack // All labels mentioned by a rule that refer to an unknown target in the current package // are assumed to be InputFiles, so let's create them. We add them to a temporary map // to avoid concurrent modification to this.targets while iterating (via getRules()). - List