| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.packages; | |
| import static com.google.common.base.MoreObjects.firstNonNull; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Joiner; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.BiMap; | |
| 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.ImmutableSortedSet; | |
| import com.google.common.collect.Interner; | |
| import com.google.common.collect.Iterables; | |
| import com.google.common.collect.Lists; | |
| import com.google.common.collect.Maps; | |
| import com.google.devtools.build.lib.bugreport.BugReport; | |
| 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.TargetPattern; | |
| import com.google.devtools.build.lib.collect.CollectionUtils; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; | |
| 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.events.ExtendedEventHandler.Postable; | |
| import com.google.devtools.build.lib.packages.License.DistributionType; | |
| import com.google.devtools.build.lib.packages.Package.Builder.DefaultPackageSettings; | |
| import com.google.devtools.build.lib.packages.Package.Builder.PackageSettings; | |
| 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.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.Collection; | |
| import java.util.Collections; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.Optional; | |
| import java.util.Set; | |
| import java.util.TreeMap; | |
| import javax.annotation.Nullable; | |
| import net.starlark.java.eval.Module; | |
| import net.starlark.java.eval.StarlarkSemantics; | |
| import net.starlark.java.eval.StarlarkThread; | |
| import net.starlark.java.spelling.SpellChecker; | |
| 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 { | |
| /** | |
| * 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"; | |
| /** Common superclass for all name-conflict exceptions. */ | |
| public static class NameConflictException extends Exception { | |
| private NameConflictException(String message) { | |
| super(message); | |
| } | |
| } | |
| /** | |
| * The repository identifier for this package. | |
| */ | |
| private final PackageIdentifier packageIdentifier; | |
| private final boolean succinctTargetNotFoundErrors; | |
| /** The filename of this package's BUILD file. */ | |
| private RootedPath filename; | |
| /** | |
| * The directory in which this package's BUILD file resides. All InputFile | |
| * members of the packages are located relative to this directory. | |
| */ | |
| private Path packageDirectory; | |
| /** | |
| * 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. | |
| */ | |
| private String workspaceName; | |
| /** | |
| * The root of the source tree in which this package was found. It is an invariant that {@code | |
| * sourceRoot.getRelative(packageId.getSourceRoot()).equals(packageDirectory)}. Returns {@link | |
| * Optional#empty} if this {@link Package} is derived from a WORKSPACE file. | |
| */ | |
| private Optional<Root> sourceRoot; | |
| /** | |
| * The "Make" environment of this package, containing package-local | |
| * definitions of "Make" variables. | |
| */ | |
| private ImmutableMap<String, String> makeEnv; | |
| /** The collection of all targets defined in this package, indexed by name. */ | |
| private ImmutableSortedMap<String, Target> targets; | |
| /** | |
| * Default visibility for rules that do not specify it. | |
| */ | |
| private RuleVisibility defaultVisibility; | |
| private boolean defaultVisibilitySet; | |
| /** | |
| * 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 | |
| } | |
| private ConfigSettingVisibilityPolicy configSettingVisibilityPolicy; | |
| /** | |
| * Default package-level 'testonly' value for rules that do not specify it. | |
| */ | |
| private boolean defaultTestOnly = false; | |
| /** | |
| * Default package-level 'deprecation' value for rules that do not specify it. | |
| */ | |
| private String defaultDeprecation; | |
| /** | |
| * Default header strictness checking for rules that do not specify it. | |
| */ | |
| private String defaultHdrsCheck; | |
| /** | |
| * The InputFile target corresponding to this package's BUILD file. | |
| */ | |
| private InputFile buildFile; | |
| /** | |
| * 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; | |
| /** The list of transitive closure of the Starlark file dependencies. */ | |
| private ImmutableList<Label> starlarkFileDependencies; | |
| /** The package's default "package_metadata" attribute. */ | |
| private ImmutableSet<Label> defaultPackageMetadata = ImmutableSet.of(); | |
| /** | |
| * The package's default "licenses" and "distribs" attributes, as specified | |
| * in calls to licenses() and distribs() in the BUILD file. | |
| */ | |
| // These sets contain the values specified by the most recent licenses() or | |
| // distribs() declarations encountered during package parsing: | |
| private License defaultLicense; | |
| private Set<License.DistributionType> defaultDistributionSet; | |
| /** | |
| * 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; | |
| /** | |
| * The map of repository reassignments for BUILD packages. This will be empty for packages within | |
| * the main workspace. | |
| */ | |
| private RepositoryMapping repositoryMapping; | |
| /** | |
| * The repository mapping of the main repository. This is only used internally to obtain | |
| * user-friendly apparent names from canonical repository names in error message arising from this | |
| * package. | |
| */ | |
| private RepositoryMapping mainRepositoryMapping; | |
| private Set<Label> defaultCompatibleWith = ImmutableSet.of(); | |
| private Set<Label> defaultRestrictedTo = ImmutableSet.of(); | |
| private ImmutableSet<String> features; | |
| private ImmutableList<TargetPattern> registeredExecutionPlatforms; | |
| private ImmutableList<TargetPattern> registeredToolchains; | |
| private long computationSteps; | |
| private ImmutableMap<String, Module> loads; | |
| /** Returns the number of Starlark computation steps executed by this BUILD file. */ | |
| public long getComputationSteps() { | |
| return computationSteps; | |
| } | |
| /** | |
| * Returns the mapping, for each load statement in this BUILD file in source order, from the load | |
| * string to the module it loads. It thus indirectly records the package's complete load DAG. In | |
| * some configurations the information may be unavailable (null). | |
| */ | |
| @Nullable | |
| public ImmutableMap<String, Module> getLoads() { | |
| return loads; | |
| } | |
| /** | |
| * Package initialization, part 1 of 3: instantiates a new package with the given name. | |
| * | |
| * <p>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. | |
| * | |
| * <p>{@code name} <b>MUST</b> be a suffix of {@code filename.getParentDirectory())}. | |
| */ | |
| private Package( | |
| PackageIdentifier packageId, String workspaceName, boolean succinctTargetNotFoundErrors) { | |
| this.packageIdentifier = packageId; | |
| this.workspaceName = workspaceName; | |
| this.succinctTargetNotFoundErrors = succinctTargetNotFoundErrors; | |
| } | |
| /** Returns this packages' identifier. */ | |
| public PackageIdentifier getPackageIdentifier() { | |
| return packageIdentifier; | |
| } | |
| /** | |
| * Whether this package should contain only repo rules (returns {@code true}) or only build rules | |
| * (returns {@code false}). | |
| */ | |
| public boolean isRepoRulePackage() { | |
| return packageIdentifier.equals(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER) | |
| || workspaceName.equals(DUMMY_WORKSPACE_NAME_FOR_BZLMOD_PACKAGES); | |
| } | |
| /** | |
| * 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> getRepositoryMapping(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()); | |
| } | |
| /** Get the repository mapping for this package. */ | |
| public RepositoryMapping getRepositoryMapping() { | |
| return repositoryMapping; | |
| } | |
| /** | |
| * 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; | |
| } | |
| /** | |
| * Package initialization: part 2 of 3: sets this package's default header | |
| * strictness checking. | |
| * | |
| * <p>This is needed to support C++-related rule classes | |
| * which accesses {@link #getDefaultHdrsCheck} from the still-under-construction | |
| * package. | |
| */ | |
| private void setDefaultHdrsCheck(String defaultHdrsCheck) { | |
| this.defaultHdrsCheck = defaultHdrsCheck; | |
| } | |
| /** | |
| * Set the default 'testonly' value for this package. | |
| */ | |
| private void setDefaultTestOnly(boolean testOnly) { | |
| defaultTestOnly = testOnly; | |
| } | |
| /** | |
| * Set the default 'deprecation' value for this package. | |
| */ | |
| private void setDefaultDeprecation(String deprecation) { | |
| defaultDeprecation = deprecation; | |
| } | |
| /** | |
| * Sets the default value to use for a rule's {@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR} | |
| * attribute when not explicitly specified by the rule. | |
| */ | |
| private void setDefaultCompatibleWith(Set<Label> environments) { | |
| defaultCompatibleWith = environments; | |
| } | |
| /** | |
| * Sets the default value to use for a rule's {@link RuleClass#RESTRICTED_ENVIRONMENT_ATTR} | |
| * attribute when not explicitly specified by the rule. | |
| */ | |
| private void setDefaultRestrictedTo(Set<Label> environments) { | |
| defaultRestrictedTo = environments; | |
| } | |
| /** | |
| * Returns the source root (a directory) 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; | |
| } | |
| private static Root getSourceRoot(RootedPath buildFileRootedPath, PathFragment packageFragment) { | |
| PathFragment packageDirectory = buildFileRootedPath.getRootRelativePath().getParentDirectory(); | |
| if (packageFragment.equals(packageDirectory)) { | |
| // Fast path: BUILD file path and package name are the same, don't create an extra root. | |
| return buildFileRootedPath.getRoot(); | |
| } | |
| PathFragment current = buildFileRootedPath.asPath().asFragment().getParentDirectory(); | |
| for (int i = 0, len = packageFragment.segmentCount(); i < len && current != null; i++) { | |
| current = current.getParentDirectory(); | |
| } | |
| if (current == null || current.isEmpty()) { | |
| // This is never really expected to work. The check below in #finishInit should fail. | |
| return buildFileRootedPath.getRoot(); | |
| } | |
| // Note that current is an absolute path. | |
| return Root.fromPath(buildFileRootedPath.getRoot().getRelative(current)); | |
| } | |
| /** | |
| * Package initialization: part 3 of 3: applies all other settings and completes | |
| * initialization of the package. | |
| * | |
| * <p>Only after this method is called can this package be considered "complete" | |
| * and be shared publicly. | |
| */ | |
| private void finishInit(Builder builder) { | |
| // If any error occurred during evaluation of this package, consider all | |
| // rules in the package to be "in error" also (even if they were evaluated | |
| // prior to the error). This behaviour is arguably stricter than need be, | |
| // but stopping a build only for some errors but not others creates user | |
| // confusion. | |
| if (builder.containsErrors) { | |
| for (Rule rule : builder.getRules()) { | |
| rule.setContainsErrors(); | |
| } | |
| } | |
| this.filename = builder.getFilename(); | |
| this.packageDirectory = filename.asPath().getParentDirectory(); | |
| String baseName = filename.getRootRelativePath().getBaseName(); | |
| if (isWorkspaceFile(baseName) || isModuleDotBazelFile(baseName)) { | |
| Preconditions.checkState(isRepoRulePackage()); | |
| this.sourceRoot = Optional.empty(); | |
| } else { | |
| Root sourceRoot = getSourceRoot(filename, packageIdentifier.getSourceRoot()); | |
| if (sourceRoot.asPath() == null | |
| || !sourceRoot.getRelative(packageIdentifier.getSourceRoot()).equals(packageDirectory)) { | |
| throw new IllegalArgumentException( | |
| "Invalid BUILD file name for package '" | |
| + packageIdentifier | |
| + "': " | |
| + filename | |
| + " (in source " | |
| + sourceRoot | |
| + " with packageDirectory " | |
| + packageDirectory | |
| + " and package identifier source root " | |
| + packageIdentifier.getSourceRoot() | |
| + ")"); | |
| } | |
| this.sourceRoot = Optional.of(sourceRoot); | |
| } | |
| this.makeEnv = ImmutableMap.copyOf(builder.makeEnv); | |
| this.targets = ImmutableSortedMap.copyOf(builder.targets); | |
| this.defaultVisibility = builder.defaultVisibility; | |
| this.defaultVisibilitySet = builder.defaultVisibilitySet; | |
| this.configSettingVisibilityPolicy = builder.configSettingVisibilityPolicy; | |
| this.buildFile = builder.buildFile; | |
| this.containsErrors = builder.containsErrors; | |
| this.failureDetail = builder.getFailureDetail(); | |
| this.starlarkFileDependencies = builder.starlarkFileDependencies; | |
| this.defaultLicense = builder.defaultLicense; | |
| this.defaultDistributionSet = builder.defaultDistributionSet; | |
| this.defaultPackageMetadata = ImmutableSortedSet.copyOf(builder.defaultPackageMetadata); | |
| this.features = ImmutableSortedSet.copyOf(builder.features); | |
| this.registeredExecutionPlatforms = ImmutableList.copyOf(builder.registeredExecutionPlatforms); | |
| this.registeredToolchains = ImmutableList.copyOf(builder.registeredToolchains); | |
| this.repositoryMapping = Preconditions.checkNotNull(builder.repositoryMapping); | |
| this.mainRepositoryMapping = Preconditions.checkNotNull(builder.mainRepositoryMapping); | |
| 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(); | |
| } | |
| private static boolean isWorkspaceFile(String baseFileName) { | |
| return baseFileName.equals(LabelConstants.WORKSPACE_DOT_BAZEL_FILE_NAME.getPathString()) | |
| || baseFileName.equals(LabelConstants.WORKSPACE_FILE_NAME.getPathString()); | |
| } | |
| private static boolean isModuleDotBazelFile(String baseFileName) { | |
| return baseFileName.equals(LabelConstants.MODULE_DOT_BAZEL_FILE_NAME.getPathString()); | |
| } | |
| /** Returns the list of transitive closure of the Starlark file dependencies of this package. */ | |
| public ImmutableList<Label> getStarlarkFileDependencies() { | |
| return starlarkFileDependencies; | |
| } | |
| /** | |
| * 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 filename; | |
| } | |
| /** | |
| * Returns the directory containing the package's BUILD file. | |
| */ | |
| public Path getPackageDirectory() { | |
| return packageDirectory; | |
| } | |
| /** | |
| * Returns the name of this package. If this build is using external repositories then this name | |
| * may not be unique! | |
| */ | |
| public String getName() { | |
| return packageIdentifier.getPackageFragment().getPathString(); | |
| } | |
| /** | |
| * Like {@link #getName}, but has type {@code PathFragment}. | |
| */ | |
| public PathFragment getNameFragment() { | |
| return packageIdentifier.getPackageFragment(); | |
| } | |
| /** | |
| * Returns all make variables for a given platform. | |
| */ | |
| public ImmutableMap<String, String> getMakeEnvironment() { | |
| return makeEnv; | |
| } | |
| /** | |
| * 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 InputFile target for this package's BUILD file. | |
| */ | |
| public InputFile getBuildFile() { | |
| return buildFile; | |
| } | |
| /** | |
| * 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 a {@link FailureDetail} attributing a target error to the package's {@link | |
| * FailureDetail}, or a generic {@link Code#TARGET_MISSING} failure detail if the package has | |
| * none. | |
| * | |
| * <p>May only be called when {@link #containsErrors()} is true and with a target whose package is | |
| * this one. | |
| */ | |
| public FailureDetail contextualizeFailureDetailForTarget(Target target) { | |
| Preconditions.checkState( | |
| target.getPackage().packageIdentifier.equals(packageIdentifier), | |
| "contextualizeFailureDetailForTarget called for target not in package. target=%s," | |
| + " package=%s", | |
| target, | |
| this); | |
| Preconditions.checkState( | |
| containsErrors, | |
| "contextualizeFailureDetailForTarget called for package not in error. target=%s", | |
| 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(); | |
| } | |
| /** Returns an (immutable, ordered) view of all the targets belonging to this package. */ | |
| public ImmutableSortedMap<String, Target> getTargets() { | |
| return targets; | |
| } | |
| /** Common getTargets implementation, accessible by {@link Package.Builder}. */ | |
| private static Set<Target> getTargets(BiMap<String, Target> targetMap) { | |
| return targetMap.values(); | |
| } | |
| /** | |
| * 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 getTargets(targets, targetClass); | |
| } | |
| /** | |
| * Common getTargets implementation, accessible by both {@link Package} and | |
| * {@link Package.Builder}. | |
| */ | |
| private static <T extends Target> Iterable<T> getTargets(Map<String, Target> targetMap, | |
| Class<T> targetClass) { | |
| return Iterables.filter(targetMap.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 this package's workspace name. | |
| */ | |
| public String getWorkspaceName() { | |
| return workspaceName; | |
| } | |
| /** | |
| * Returns the features specified in the <code>package()</code> declaration. | |
| */ | |
| public ImmutableSet<String> getFeatures() { | |
| return features; | |
| } | |
| /** | |
| * 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(packageIdentifier, targetName); | |
| } catch (LabelSyntaxException e) { | |
| throw new IllegalArgumentException(targetName, e); | |
| } | |
| if (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(), filename.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 = packageDirectory.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 { | |
| String suggestedTarget = SpellChecker.suggest(targetName, targets.keySet()); | |
| String targetSuggestion = | |
| suggestedTarget == null ? null : String.format("did you mean '%s'?", suggestedTarget); | |
| String blazeQuerySuggestion = | |
| String.format( | |
| "Tip: use `query \"%s:*\"` to see all the targets in that package", | |
| packageIdentifier.getDisplayForm(mainRepositoryMapping)); | |
| return String.format( | |
| " (%s)", Joiner.on(" ").skipNulls().join(targetSuggestion, blazeQuerySuggestion)); | |
| } | |
| } | |
| /** | |
| * Returns the default visibility for this package. | |
| */ | |
| public RuleVisibility getDefaultVisibility() { | |
| return defaultVisibility; | |
| } | |
| /** | |
| * How to enforce visibility on <code>config_setting</code> See | |
| * {@link ConfigSettingVisibilityPolicy} for details. | |
| */ | |
| public ConfigSettingVisibilityPolicy getConfigSettingVisibilityPolicy() { | |
| return configSettingVisibilityPolicy; | |
| } | |
| /** | |
| * Returns the default testonly value. | |
| */ | |
| public Boolean getDefaultTestOnly() { | |
| return defaultTestOnly; | |
| } | |
| /** | |
| * Returns the default deprecation value. | |
| */ | |
| public String getDefaultDeprecation() { | |
| return defaultDeprecation; | |
| } | |
| /** Gets the default header checking mode. */ | |
| public String getDefaultHdrsCheck() { | |
| return defaultHdrsCheck != null ? defaultHdrsCheck : "strict"; | |
| } | |
| /** | |
| * Returns whether the default header checking mode has been set or it is the | |
| * default value. | |
| */ | |
| public boolean isDefaultHdrsCheckSet() { | |
| return defaultHdrsCheck != null; | |
| } | |
| public boolean isDefaultVisibilitySet() { | |
| return defaultVisibilitySet; | |
| } | |
| /** Gets the package metadata list for the default metadata declared by this package. */ | |
| public Set<Label> getDefaultPackageMetadata() { | |
| return defaultPackageMetadata; | |
| } | |
| /** Gets the parsed license object for the default license declared by this package. */ | |
| License getDefaultLicense() { | |
| return defaultLicense; | |
| } | |
| /** Returns the parsed set of distributions declared as the default for this package. */ | |
| Set<License.DistributionType> getDefaultDistribs() { | |
| return defaultDistributionSet; | |
| } | |
| /** | |
| * Returns the default value to use for a rule's {@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR} | |
| * attribute when not explicitly specified by the rule. | |
| */ | |
| public Set<Label> getDefaultCompatibleWith() { | |
| return defaultCompatibleWith; | |
| } | |
| /** | |
| * Returns the default value to use for a rule's {@link RuleClass#RESTRICTED_ENVIRONMENT_ATTR} | |
| * attribute when not explicitly specified by the rule. | |
| */ | |
| public Set<Label> getDefaultRestrictedTo() { | |
| return defaultRestrictedTo; | |
| } | |
| public ImmutableList<TargetPattern> getRegisteredExecutionPlatforms() { | |
| return registeredExecutionPlatforms; | |
| } | |
| public ImmutableList<TargetPattern> getRegisteredToolchains() { | |
| return registeredToolchains; | |
| } | |
| @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() + " (" + filename.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(); | |
| } | |
| } | |
| } | |
| public static Builder newExternalPackageBuilder( | |
| PackageSettings helper, | |
| RootedPath workspacePath, | |
| String workspaceName, | |
| RepositoryMapping mainRepoMapping, | |
| StarlarkSemantics starlarkSemantics) { | |
| return new Builder( | |
| helper, | |
| LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER, | |
| workspaceName, | |
| starlarkSemantics.getBool(BuildLanguageOptions.INCOMPATIBLE_NO_IMPLICIT_FILE_EXPORT), | |
| mainRepoMapping, | |
| mainRepoMapping) | |
| .setFilename(workspacePath); | |
| } | |
| public static Builder newExternalPackageBuilderForBzlmod( | |
| RootedPath moduleFilePath, | |
| StarlarkSemantics starlarkSemantics, | |
| PackageIdentifier basePackageId, | |
| RepositoryMapping repoMapping) { | |
| return new Builder( | |
| DefaultPackageSettings.INSTANCE, | |
| basePackageId, | |
| DUMMY_WORKSPACE_NAME_FOR_BZLMOD_PACKAGES, | |
| starlarkSemantics.getBool(BuildLanguageOptions.INCOMPATIBLE_NO_IMPLICIT_FILE_EXPORT), | |
| repoMapping, | |
| // This mapping is *not* the main repository's mapping, but since it is only used to | |
| // construct a query command in an error message and the package built here can't be | |
| // seen by query, the particular value does not matter. | |
| RepositoryMapping.ALWAYS_FALLBACK) | |
| .setFilename(moduleFilePath); | |
| } | |
| /** | |
| * Returns an error {@link Event} with {@link Location} and {@link DetailedExitCode} properties. | |
| */ | |
| public static Event error(Location location, String message, Code code) { | |
| Event error = Event.error(location, message); | |
| return error.withProperty(DetailedExitCode.class, createDetailedCode(message, code)); | |
| } | |
| private static DetailedExitCode createDetailedCode(String errorMessage, Code code) { | |
| return DetailedExitCode.of( | |
| FailureDetail.newBuilder() | |
| .setMessage(errorMessage) | |
| .setPackageLoading(PackageLoading.newBuilder().setCode(code)) | |
| .build()); | |
| } | |
| /** | |
| * 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 { | |
| /** Defines configuration to control the runtime behavior of {@link Package}s. */ | |
| 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. | |
| */ | |
| boolean succinctTargetNotFoundErrors(); | |
| /** | |
| * Reports whether to record the set of Modules loaded by this package, which enables richer | |
| * modes of blaze query. | |
| */ | |
| boolean recordLoadedModules(); | |
| } | |
| /** Default {@link PackageSettings}. */ | |
| public static class DefaultPackageSettings implements PackageSettings { | |
| public static final DefaultPackageSettings INSTANCE = new DefaultPackageSettings(); | |
| private DefaultPackageSettings() {} | |
| @Override | |
| public boolean succinctTargetNotFoundErrors() { | |
| return false; | |
| } | |
| @Override | |
| public boolean recordLoadedModules() { | |
| return true; | |
| } | |
| } | |
| /** | |
| * The output instance for this builder. Needs to be instantiated and | |
| * available with name info throughout initialization. All other settings | |
| * are applied during {@link #build}. See {@link Package#Package} | |
| * and {@link Package#finishInit} for details. | |
| */ | |
| private final Package pkg; | |
| private final boolean noImplicitFileExport; | |
| private static final CallStack.Factory callStackFactory = new CallStack.Factory(); | |
| // 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<>(); | |
| /** | |
| * The map of repository reassignments for BUILD packages loaded within external repositories. | |
| * It contains an entry from "@<main workspace name>" to "@" for packages within the main | |
| * workspace. | |
| */ | |
| private final RepositoryMapping repositoryMapping; | |
| /** | |
| * The repository mapping of the main repository. This is only used to resolve user-friendly | |
| * apparent names from canonical repository names in error message arising from this package. | |
| */ | |
| private final RepositoryMapping mainRepositoryMapping; | |
| /** Converts label literals to Label objects within this package. */ | |
| private final LabelConverter labelConverter; | |
| private RootedPath filename = null; | |
| private Label buildFileLabel = null; | |
| private InputFile buildFile = null; | |
| // TreeMap so that the iteration order of variables is predictable. This is useful so that the | |
| // serialized representation is deterministic. | |
| private final TreeMap<String, String> makeEnv = new TreeMap<>(); | |
| private RuleVisibility defaultVisibility = ConstantRuleVisibility.PRIVATE; | |
| private ConfigSettingVisibilityPolicy configSettingVisibilityPolicy; | |
| private boolean defaultVisibilitySet; | |
| private final List<String> features = new ArrayList<>(); | |
| private final List<Event> events = Lists.newArrayList(); | |
| private final List<Postable> posts = Lists.newArrayList(); | |
| @Nullable private String ioExceptionMessage = null; | |
| @Nullable private IOException ioException = null; | |
| @Nullable private DetailedExitCode ioExceptionDetailedExitCode = null; | |
| private boolean containsErrors = false; | |
| // A package's FailureDetail field derives from its Builder's events. 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; | |
| private ImmutableList<Label> defaultPackageMetadata = ImmutableList.of(); | |
| private License defaultLicense = License.NO_LICENSE; | |
| private Set<License.DistributionType> defaultDistributionSet = License.DEFAULT_DISTRIB; | |
| // All targets added to the package. We use SnapshottableBiMap to help track insertion order of | |
| // Rule targets, for use by native.existing_rules(). | |
| private BiMap<String, Target> targets = | |
| new SnapshottableBiMap<>(target -> target instanceof Rule); | |
| private final Map<Label, EnvironmentGroup> environmentGroups = new HashMap<>(); | |
| /** | |
| * Stores labels for each rule so that we don't have to call the costly {@link Rule#getLabels} | |
| * twice (once for {@link #checkForInputOutputConflicts} and once for {@link #beforeBuild}). | |
| * | |
| * <p>Remains {@code null} when rules are added via {@link #addRuleUnchecked}, which occurs with | |
| * package deserialization. Set back to {@code null} after building. | |
| */ | |
| @Nullable private Map<Rule, List<Label>> ruleLabels = null; | |
| private ImmutableList<Label> starlarkFileDependencies = ImmutableList.of(); | |
| private final List<TargetPattern> registeredExecutionPlatforms = new ArrayList<>(); | |
| private final List<TargetPattern> registeredToolchains = new ArrayList<>(); | |
| /** | |
| * True iff the "package" function has already been called in this package. | |
| */ | |
| private boolean packageFunctionUsed; | |
| /** | |
| * The collection of the prefixes of every output file. Maps every prefix to an output file | |
| * whose prefix it is. | |
| * | |
| * <p>This is needed to make the output file prefix conflict check be reasonably fast. However, | |
| * since it can potentially take a lot of memory and is useless after the package has been | |
| * loaded, it isn't passed to the package itself. | |
| */ | |
| private final Map<String, OutputFile> outputFilePrefixes = new HashMap<>(); | |
| private final Interner<ImmutableList<?>> listInterner = new ThreadCompatibleInterner<>(); | |
| private ImmutableMap<Location, String> generatorMap = ImmutableMap.of(); | |
| private final TestSuiteImplicitTestsAccumulator testSuiteImplicitTestsAccumulator = | |
| new TestSuiteImplicitTestsAccumulator(); | |
| /** Returns the "generator_name" to use for a given call site location in a BUILD file. */ | |
| @Nullable | |
| public String getGeneratorNameByLocation(Location loc) { | |
| return generatorMap.get(loc); | |
| } | |
| /** Sets the package's map of "generator_name" values keyed by the location of the call site. */ | |
| @CanIgnoreReturnValue | |
| public Builder setGeneratorMap(ImmutableMap<Location, String> map) { | |
| this.generatorMap = map; | |
| return this; | |
| } | |
| /** | |
| * Returns the value to use for {@code test_suite}s' {@code $implicit_tests} attribute, as-is, | |
| * when the {@code test_suite} doesn't specify an explicit, non-empty {@code tests} value. The | |
| * returned list is mutated by the package-building process - it may be observed to be empty or | |
| * incomplete before package loading is complete. When package loading is complete it will | |
| * contain the label of each non-manual test matching the provided tags in the package, in label | |
| * order. | |
| * | |
| * <p>This method <b>MUST</b> be called before the package is built - otherwise the requested | |
| * implicit tests won't be accumulated. | |
| */ | |
| List<Label> getTestSuiteImplicitTestsRef(List<String> tags) { | |
| return testSuiteImplicitTestsAccumulator.getTestSuiteImplicitTestsRefForTags(tags); | |
| } | |
| @ThreadCompatible | |
| private static final class ThreadCompatibleInterner<T> implements Interner<T> { | |
| private final Map<T, T> interns = new HashMap<>(); | |
| @Override | |
| public T intern(T sample) { | |
| T existing = interns.putIfAbsent(sample, sample); | |
| return firstNonNull(existing, sample); | |
| } | |
| } | |
| private boolean alreadyBuilt = false; | |
| Builder( | |
| PackageSettings packageSettings, | |
| PackageIdentifier id, | |
| String workspaceName, | |
| boolean noImplicitFileExport, | |
| RepositoryMapping repositoryMapping, | |
| RepositoryMapping mainRepositoryMapping) { | |
| this.pkg = new Package(id, workspaceName, packageSettings.succinctTargetNotFoundErrors()); | |
| this.noImplicitFileExport = noImplicitFileExport; | |
| this.repositoryMapping = repositoryMapping; | |
| this.mainRepositoryMapping = mainRepositoryMapping; | |
| this.labelConverter = new LabelConverter(id, repositoryMapping); | |
| if (pkg.getName().startsWith("javatests/")) { | |
| setDefaultTestonly(true); | |
| } | |
| } | |
| PackageIdentifier getPackageIdentifier() { | |
| return pkg.getPackageIdentifier(); | |
| } | |
| /** | |
| * Determine whether this package should contain build rules (returns {@code false}) or repo | |
| * rules (returns {@code true}). | |
| */ | |
| boolean isRepoRulePackage() { | |
| return pkg.isRepoRulePackage(); | |
| } | |
| String getPackageWorkspaceName() { | |
| return pkg.getWorkspaceName(); | |
| } | |
| /** | |
| * 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; | |
| } | |
| public LabelConverter getLabelConverter() { | |
| return labelConverter; | |
| } | |
| Interner<ImmutableList<?>> getListInterner() { | |
| return listInterner; | |
| } | |
| /** Sets the name of this package's BUILD file. */ | |
| @CanIgnoreReturnValue | |
| public Builder setFilename(RootedPath filename) { | |
| this.filename = filename; | |
| try { | |
| buildFileLabel = createLabel(filename.getRootRelativePath().getBaseName()); | |
| addInputFile(buildFileLabel, Location.fromFile(filename.asPath().toString())); | |
| } catch (LabelSyntaxException e) { | |
| // This can't actually happen. | |
| throw new AssertionError("Package BUILD file has an illegal name: " + filename); | |
| } | |
| return this; | |
| } | |
| Label getBuildFileLabel() { | |
| return buildFileLabel; | |
| } | |
| /** | |
| * 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); | |
| } | |
| } | |
| RootedPath getFilename() { | |
| return filename; | |
| } | |
| /** | |
| * Returns {@link Postable}s accumulated while building the package. | |
| * | |
| * <p>Should retrieved and reported as close to after {@link #build()} or {@link #finishBuild()} | |
| * as possible - any earlier and the data may be incomplete. | |
| */ | |
| public List<Postable> getPosts() { | |
| return posts; | |
| } | |
| /** | |
| * Returns {@link Event}s accumulated while building the package. | |
| * | |
| * <p>Should retrieved and reported as close to after {@link #build()} or {@link #finishBuild()} | |
| * as possible - any earlier and the data may be incomplete. | |
| */ | |
| public List<Event> getEvents() { | |
| return events; | |
| } | |
| @CanIgnoreReturnValue | |
| Builder setMakeVariable(String name, String value) { | |
| this.makeEnv.put(name, value); | |
| return this; | |
| } | |
| /** | |
| * Sets the default visibility for this package. Called at most once per package from | |
| * PackageFactory. | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder setDefaultVisibility(RuleVisibility visibility) { | |
| this.defaultVisibility = visibility; | |
| this.defaultVisibilitySet = true; | |
| return this; | |
| } | |
| /** Sets whether the default visibility is set in the BUILD file. */ | |
| @CanIgnoreReturnValue | |
| public Builder setDefaultVisibilitySet(boolean defaultVisibilitySet) { | |
| this.defaultVisibilitySet = defaultVisibilitySet; | |
| return this; | |
| } | |
| /** Sets visibility enforcement policy for <code>config_setting</code>. */ | |
| @CanIgnoreReturnValue | |
| public Builder setConfigSettingVisibilityPolicy(ConfigSettingVisibilityPolicy policy) { | |
| this.configSettingVisibilityPolicy = policy; | |
| return this; | |
| } | |
| /** Sets the default value of 'testonly'. Rule-level 'testonly' will override this. */ | |
| @CanIgnoreReturnValue | |
| Builder setDefaultTestonly(boolean defaultTestonly) { | |
| pkg.setDefaultTestOnly(defaultTestonly); | |
| return this; | |
| } | |
| /** Sets the default value of 'deprecation'. Rule-level 'deprecation' will append to this. */ | |
| @CanIgnoreReturnValue | |
| Builder setDefaultDeprecation(String defaultDeprecation) { | |
| pkg.setDefaultDeprecation(defaultDeprecation); | |
| return this; | |
| } | |
| /** Uses the workspace name from {@code //external} to set this package's workspace name. */ | |
| @CanIgnoreReturnValue | |
| @VisibleForTesting | |
| public Builder setWorkspaceName(String workspaceName) { | |
| pkg.workspaceName = workspaceName; | |
| return this; | |
| } | |
| /** | |
| * Returns whether the "package" function has been called yet | |
| */ | |
| boolean isPackageFunctionUsed() { | |
| return packageFunctionUsed; | |
| } | |
| void setPackageFunctionUsed() { | |
| packageFunctionUsed = true; | |
| } | |
| /** Sets the number of Starlark computation steps executed by this BUILD file. */ | |
| void setComputationSteps(long n) { | |
| pkg.computationSteps = n; | |
| } | |
| /** Sets the load mapping for this package. */ | |
| void setLoads(ImmutableMap<String, Module> loads) { | |
| pkg.loads = Preconditions.checkNotNull(loads); | |
| } | |
| /** Sets the default header checking mode. */ | |
| @CanIgnoreReturnValue | |
| public Builder setDefaultHdrsCheck(String hdrsCheck) { | |
| // Note that this setting is propagated directly to the package because | |
| // other code needs the ability to read this info directly from the | |
| // under-construction package. See {@link Package#setDefaultHdrsCheck}. | |
| pkg.setDefaultHdrsCheck(hdrsCheck); | |
| return this; | |
| } | |
| @CanIgnoreReturnValue | |
| public Builder addFeatures(Iterable<String> features) { | |
| Iterables.addAll(this.features, features); | |
| return this; | |
| } | |
| Builder setIOException(IOException e, String message, DetailedExitCode detailedExitCode) { | |
| this.ioException = e; | |
| this.ioExceptionMessage = message; | |
| this.ioExceptionDetailedExitCode = detailedExitCode; | |
| return setContainsErrors(); | |
| } | |
| /** | |
| * Declares that errors were encountering while loading this package. If called, {@link | |
| * #addEvent} or {@link #addEvents} should already have been called with an {@link Event} of | |
| * type {@link EventKind#ERROR} that includes a {@link FailureDetail}. | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder setContainsErrors() { | |
| containsErrors = true; | |
| return this; | |
| } | |
| public boolean containsErrors() { | |
| return containsErrors; | |
| } | |
| @CanIgnoreReturnValue | |
| Builder addPosts(Iterable<Postable> posts) { | |
| for (Postable post : posts) { | |
| this.posts.add(post); | |
| } | |
| return this; | |
| } | |
| @CanIgnoreReturnValue | |
| Builder addEvents(Iterable<Event> events) { | |
| for (Event event : events) { | |
| addEvent(event); | |
| } | |
| return this; | |
| } | |
| @CanIgnoreReturnValue | |
| public Builder addEvent(Event event) { | |
| this.events.add(event); | |
| return this; | |
| } | |
| public void setFailureDetailOverride(FailureDetail failureDetail) { | |
| failureDetailOverride = failureDetail; | |
| } | |
| @Nullable | |
| FailureDetail getFailureDetail() { | |
| if (failureDetailOverride != null) { | |
| return failureDetailOverride; | |
| } | |
| List<Event> undetailedEvents = null; | |
| for (Event event : this.events) { | |
| 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.sendBugReport( | |
| new IllegalStateException("Package has undetailed error from " + undetailedEvents)); | |
| } | |
| return null; | |
| } | |
| @CanIgnoreReturnValue | |
| public Builder setStarlarkFileDependencies(ImmutableList<Label> starlarkFileDependencies) { | |
| this.starlarkFileDependencies = starlarkFileDependencies; | |
| return this; | |
| } | |
| /** | |
| * Sets the default value to use for a rule's {@link RuleClass#APPLICABLE_LICENSES_ATTR} | |
| * attribute when not explicitly specified by the rule. Records a package error if any labels | |
| * are duplicated. | |
| */ | |
| void setDefaultPackageMetadata(List<Label> licenses, String attrName, Location location) { | |
| if (hasDuplicateLabels( | |
| licenses, "package " + pkg.getName(), attrName, location, this::addEvent)) { | |
| setContainsErrors(); | |
| } | |
| this.defaultPackageMetadata = ImmutableList.copyOf(licenses); | |
| } | |
| ImmutableList<Label> getDefaultPackageMetadata() { | |
| return defaultPackageMetadata; | |
| } | |
| /** | |
| * Sets the default license for this package. | |
| */ | |
| void setDefaultLicense(License license) { | |
| this.defaultLicense = license; | |
| } | |
| License getDefaultLicense() { | |
| return defaultLicense; | |
| } | |
| /** | |
| * Initializes the default set of distributions for targets in this package. | |
| * | |
| * <p> TODO(bazel-team): (2011) consider moving the license & distribs info into Metadata--maybe | |
| * even in the Build language. | |
| */ | |
| void setDefaultDistribs(Set<DistributionType> dists) { | |
| this.defaultDistributionSet = dists; | |
| } | |
| Set<DistributionType> getDefaultDistribs() { | |
| return defaultDistributionSet; | |
| } | |
| /** | |
| * Sets the default value to use for a rule's {@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR} | |
| * attribute when not explicitly specified by the rule. Records a package error if any labels | |
| * are duplicated. | |
| */ | |
| void setDefaultCompatibleWith(List<Label> environments, String attrName, Location location) { | |
| if (hasDuplicateLabels( | |
| environments, "package " + pkg.getName(), attrName, location, this::addEvent)) { | |
| setContainsErrors(); | |
| } | |
| pkg.setDefaultCompatibleWith(ImmutableSet.copyOf(environments)); | |
| } | |
| /** | |
| * Sets the default value to use for a rule's {@link RuleClass#RESTRICTED_ENVIRONMENT_ATTR} | |
| * attribute when not explicitly specified by the rule. Records a package error if | |
| * any labels are duplicated. | |
| */ | |
| void setDefaultRestrictedTo(List<Label> environments, String attrName, Location location) { | |
| if (hasDuplicateLabels( | |
| environments, "package " + pkg.getName(), attrName, location, this::addEvent)) { | |
| setContainsErrors(); | |
| } | |
| pkg.setDefaultRestrictedTo(ImmutableSet.copyOf(environments)); | |
| } | |
| /** | |
| * Creates a new {@link Rule} {@code r} where {@code r.getPackage()} is the {@link Package} | |
| * associated with this {@link Builder}. | |
| * | |
| * <p>The created {@link Rule} will have no output files and therefore will be in an invalid | |
| * state. | |
| */ | |
| Rule createRule( | |
| Label label, | |
| RuleClass ruleClass, | |
| Location location, | |
| List<StarlarkThread.CallStackEntry> callstack, | |
| AttributeContainer attributeContainer) { // required by WorkspaceFactory.setParent hack | |
| return new Rule( | |
| pkg, | |
| label, | |
| ruleClass, | |
| location, | |
| callStackFactory.createFrom(callstack), | |
| attributeContainer); | |
| } | |
| /** | |
| * Same as {@link #createRule(Label, RuleClass, Location, List, AttributeContainer)}, except | |
| * allows specifying an {@link ImplicitOutputsFunction} override. | |
| * | |
| * <p>Only use if you know what you're doing. | |
| */ | |
| Rule createRule( | |
| Label label, | |
| RuleClass ruleClass, | |
| Location location, | |
| List<StarlarkThread.CallStackEntry> callstack, | |
| ImplicitOutputsFunction implicitOutputsFunction) { | |
| return new Rule( | |
| pkg, | |
| label, | |
| ruleClass, | |
| location, | |
| callStackFactory.createFrom(callstack), | |
| AttributeContainer.newMutableInstance(ruleClass), | |
| implicitOutputsFunction); | |
| } | |
| @Nullable | |
| Target getTarget(String name) { | |
| return targets.get(name); | |
| } | |
| /** | |
| * Replaces a target in the {@link Package} under construction with a new target with the same | |
| * name and belonging to the same package. | |
| * | |
| * <p>A hack needed for {@link WorkspaceFactoryHelper}. | |
| */ | |
| void replaceTarget(Target newTarget) { | |
| Preconditions.checkArgument( | |
| targets.containsKey(newTarget.getName()), | |
| "No existing target with name '%s' in the targets map", | |
| newTarget.getName()); | |
| Preconditions.checkArgument( | |
| newTarget.getPackage() == pkg, // pointer comparison since we're constructing `pkg` | |
| "Replacement target belongs to package '%s', expected '%s'", | |
| newTarget.getPackage(), | |
| pkg); | |
| Target oldTarget = targets.put(newTarget.getName(), newTarget); | |
| if (newTarget instanceof Rule && ruleLabels != null) { | |
| List<Label> ruleLabelsForOldTarget = ruleLabels.remove(oldTarget); | |
| if (ruleLabelsForOldTarget != null) { | |
| ruleLabels.put((Rule) newTarget, ruleLabelsForOldTarget); | |
| } | |
| } | |
| } | |
| public Set<Target> getTargets() { | |
| return Package.getTargets(targets); | |
| } | |
| /** | |
| * Returns a lightweight snapshot view of the names of all rule targets belonging to this | |
| * package at the time of this call. | |
| * | |
| * @throws IllegalStateException if this method is called after {@link #beforeBuild} has been | |
| * called. | |
| */ | |
| Map<String, Rule> getRulesSnapshotView() { | |
| if (targets instanceof SnapshottableBiMap<?, ?>) { | |
| return Maps.transformValues( | |
| ((SnapshottableBiMap<String, Target>) targets).getTrackedSnapshot(), | |
| target -> (Rule) target); | |
| } else { | |
| throw new IllegalStateException( | |
| "getRulesSnapshotView() cannot be used after beforeBuild() has been called"); | |
| } | |
| } | |
| /** | |
| * Returns an {@link Iterable} of all the rule instance targets belonging to this package. | |
| * | |
| * <p>The returned {@link Iterable} will be deterministically ordered, in the order the rule | |
| * instance targets were instantiated. | |
| */ | |
| private Iterable<Rule> getRules() { | |
| return Package.getTargets(targets, Rule.class); | |
| } | |
| /** | |
| * An input file name conflicts with an existing package member. | |
| */ | |
| static class GeneratedLabelConflict extends NameConflictException { | |
| private GeneratedLabelConflict(String message) { | |
| super(message); | |
| } | |
| } | |
| /** | |
| * Creates an input file target in this package with the specified name. | |
| * | |
| * @param targetName name of the input file. This must be a valid target | |
| * name as defined by {@link | |
| * com.google.devtools.build.lib.cmdline.LabelValidator#validateTargetName}. | |
| * @return the newly-created InputFile, or the old one if it already existed. | |
| * @throws GeneratedLabelConflict if the name was already taken by a Rule or | |
| * an OutputFile target. | |
| * @throws IllegalArgumentException if the name is not a valid label | |
| */ | |
| InputFile createInputFile(String targetName, Location location) | |
| throws GeneratedLabelConflict { | |
| Target existing = targets.get(targetName); | |
| if (existing == null) { | |
| try { | |
| return addInputFile(createLabel(targetName), location); | |
| } catch (LabelSyntaxException e) { | |
| throw new IllegalArgumentException( | |
| "FileTarget in package " + pkg.getName() + " has illegal name: " + targetName, e); | |
| } | |
| } else if (existing instanceof InputFile) { | |
| return (InputFile) existing; // idempotent | |
| } else { | |
| throw new GeneratedLabelConflict("generated label '//" + pkg.getName() + ":" | |
| + targetName + "' conflicts with existing " | |
| + existing.getTargetKind()); | |
| } | |
| } | |
| /** | |
| * Sets the visibility and license for an input file. The input file must already exist as | |
| * a member of this package. | |
| * @throws IllegalArgumentException if the input file doesn't exist in this | |
| * package's target map. | |
| */ | |
| void setVisibilityAndLicense(InputFile inputFile, RuleVisibility visibility, License license) { | |
| String filename = inputFile.getName(); | |
| Target cacheInstance = targets.get(filename); | |
| if (!(cacheInstance instanceof InputFile)) { | |
| throw new IllegalArgumentException("Can't set visibility for nonexistent FileTarget " | |
| + filename + " in package " + pkg.getName() + "."); | |
| } | |
| if (!((InputFile) cacheInstance).isVisibilitySpecified() | |
| || cacheInstance.getVisibility() != visibility | |
| || !Objects.equals(cacheInstance.getLicense(), license)) { | |
| targets.put( | |
| filename, | |
| new VisibilityLicenseSpecifiedInputFile( | |
| pkg, cacheInstance.getLabel(), cacheInstance.getLocation(), visibility, license)); | |
| } | |
| } | |
| /** | |
| * Creates a label for a target inside this package. | |
| * | |
| * @throws LabelSyntaxException if the {@code targetName} is invalid | |
| */ | |
| Label createLabel(String targetName) throws LabelSyntaxException { | |
| return Label.create(pkg.getPackageIdentifier(), targetName); | |
| } | |
| /** Adds a package group to the package. */ | |
| void addPackageGroup( | |
| String name, | |
| Collection<String> packages, | |
| Collection<Label> includes, | |
| boolean allowPublicPrivate, | |
| boolean repoRootMeansCurrentRepo, | |
| EventHandler eventHandler, | |
| Location location) | |
| throws NameConflictException, LabelSyntaxException { | |
| PackageGroup group = | |
| new PackageGroup( | |
| createLabel(name), | |
| pkg, | |
| packages, | |
| includes, | |
| allowPublicPrivate, | |
| repoRootMeansCurrentRepo, | |
| eventHandler, | |
| location); | |
| Target existing = targets.get(group.getName()); | |
| if (existing != null) { | |
| throw nameConflict(group, existing); | |
| } | |
| targets.put(group.getName(), group); | |
| if (group.containsErrors()) { | |
| setContainsErrors(); | |
| } | |
| } | |
| /** | |
| * Returns true if any labels in the given list appear multiple times, reporting an appropriate | |
| * error message if so. | |
| * | |
| * <p>TODO(bazel-team): apply this to all build functions (maybe automatically?), possibly | |
| * integrate with RuleClass.checkForDuplicateLabels. | |
| */ | |
| private static boolean hasDuplicateLabels( | |
| List<Label> labels, | |
| String owner, | |
| String attrName, | |
| Location location, | |
| EventHandler eventHandler) { | |
| Set<Label> dupes = CollectionUtils.duplicatedElementsOf(labels); | |
| for (Label dupe : dupes) { | |
| eventHandler.handle( | |
| error( | |
| location, | |
| String.format( | |
| "label '%s' is duplicated in the '%s' list of '%s'", dupe, attrName, owner), | |
| Code.DUPLICATE_LABEL)); | |
| } | |
| return !dupes.isEmpty(); | |
| } | |
| /** | |
| * Adds an environment group to the package. | |
| */ | |
| void addEnvironmentGroup(String name, List<Label> environments, List<Label> defaults, | |
| EventHandler eventHandler, Location location) | |
| throws NameConflictException, LabelSyntaxException { | |
| if (hasDuplicateLabels(environments, name, "environments", location, eventHandler) | |
| || hasDuplicateLabels(defaults, name, "defaults", location, eventHandler)) { | |
| setContainsErrors(); | |
| return; | |
| } | |
| EnvironmentGroup group = new EnvironmentGroup(createLabel(name), pkg, environments, | |
| defaults, location); | |
| Target existing = targets.get(group.getName()); | |
| if (existing != null) { | |
| throw nameConflict(group, existing); | |
| } | |
| targets.put(group.getName(), group); | |
| // Invariant: once group is inserted into targets, it must also: | |
| // (a) be inserted into environmentGroups, or | |
| // (b) have its group.processMemberEnvironments called. | |
| // Otherwise it will remain uninitialized, | |
| // causing crashes when it is later toString-ed. | |
| for (Event error : group.validateMembership()) { | |
| eventHandler.handle(error); | |
| setContainsErrors(); | |
| } | |
| // For each declared environment, make sure it doesn't also belong to some other group. | |
| for (Label environment : group.getEnvironments()) { | |
| EnvironmentGroup otherGroup = environmentGroups.get(environment); | |
| if (otherGroup != null) { | |
| eventHandler.handle( | |
| error( | |
| location, | |
| String.format( | |
| "environment %s belongs to both %s and %s", | |
| environment, group.getLabel(), otherGroup.getLabel()), | |
| Code.ENVIRONMENT_IN_MULTIPLE_GROUPS)); | |
| setContainsErrors(); | |
| // Ensure the orphan gets (trivially) initialized. | |
| group.processMemberEnvironments(ImmutableMap.of()); | |
| } else { | |
| environmentGroups.put(environment, group); | |
| } | |
| } | |
| } | |
| /** | |
| * Same as {@link #addRule}, except with no name conflict checks. | |
| * | |
| * <p>Don't call this function unless you know what you're doing. | |
| */ | |
| void addRuleUnchecked(Rule rule) { | |
| Preconditions.checkArgument(rule.getPackage() == pkg); | |
| // Now, modify the package: | |
| for (OutputFile outputFile : rule.getOutputFiles()) { | |
| targets.put(outputFile.getName(), outputFile); | |
| PathFragment outputFileFragment = PathFragment.create(outputFile.getName()); | |
| int segmentCount = outputFileFragment.segmentCount(); | |
| for (int i = 1; i < segmentCount; i++) { | |
| String prefix = outputFileFragment.subFragment(0, i).toString(); | |
| outputFilePrefixes.putIfAbsent(prefix, outputFile); | |
| } | |
| } | |
| targets.put(rule.getName(), rule); | |
| if (rule.containsErrors()) { | |
| this.setContainsErrors(); | |
| } | |
| } | |
| void addRule(Rule rule) throws NameConflictException { | |
| List<Label> labels = rule.getLabels(); | |
| checkForConflicts(rule, labels); | |
| addRuleUnchecked(rule); | |
| if (ruleLabels == null) { | |
| ruleLabels = new HashMap<>(); | |
| } | |
| ruleLabels.put(rule, labels); | |
| } | |
| void addRegisteredExecutionPlatforms(List<TargetPattern> platforms) { | |
| this.registeredExecutionPlatforms.addAll(platforms); | |
| } | |
| void addRegisteredToolchains(List<TargetPattern> toolchains) { | |
| this.registeredToolchains.addAll(toolchains); | |
| } | |
| @CanIgnoreReturnValue | |
| private Builder beforeBuild(boolean discoverAssumedInputFiles) throws NoSuchPackageException { | |
| Preconditions.checkNotNull(pkg); | |
| Preconditions.checkNotNull(filename); | |
| Preconditions.checkNotNull(buildFileLabel); | |
| Preconditions.checkNotNull(makeEnv); | |
| 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 (targets instanceof SnapshottableBiMap<?, ?>) { | |
| targets = ((SnapshottableBiMap<String, Target>) 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. | |
| buildFile = (InputFile) Preconditions.checkNotNull(targets.get(buildFileLabel.getName())); | |
| // 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 : getRules()) { | |
| if (discoverAssumedInputFiles) { | |
| // 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<Label> labels = ruleLabels != null ? ruleLabels.get(rule) : rule.getLabels(); | |
| for (Label label : labels) { | |
| if (label.getPackageIdentifier().equals(pkg.getPackageIdentifier()) | |
| && !targets.containsKey(label.getName()) | |
| && !newInputFiles.containsKey(label.getName())) { | |
| Location loc = rule.getLocation(); | |
| newInputFiles.put( | |
| label.getName(), | |
| 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()) { | |
| addInputFile(file); | |
| } | |
| return this; | |
| } | |
| /** Intended for use by {@link com.google.devtools.build.lib.skyframe.PackageFunction} only. */ | |
| 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 targets and distributions. | |
| for (Rule rule : getRules()) { | |
| rule.freeze(); | |
| } | |
| ruleLabels = null; | |
| targets = Maps.unmodifiableBiMap(targets); | |
| defaultDistributionSet = | |
| Collections.unmodifiableSet(defaultDistributionSet); | |
| // Now all targets have been loaded, so we validate the group's member environments. | |
| for (EnvironmentGroup envGroup : ImmutableSet.copyOf(environmentGroups.values())) { | |
| Collection<Event> errors = envGroup.processMemberEnvironments(targets); | |
| if (!errors.isEmpty()) { | |
| addEvents(errors); | |
| setContainsErrors(); | |
| } | |
| } | |
| // Build the package. | |
| pkg.finishInit(this); | |
| alreadyBuilt = true; | |
| return pkg; | |
| } | |
| public Package build() throws NoSuchPackageException { | |
| return build(/*discoverAssumedInputFiles=*/ true); | |
| } | |
| /** | |
| * Build the package, optionally adding any labels in the package not already associated with a | |
| * target as an input file. | |
| */ | |
| Package build(boolean discoverAssumedInputFiles) throws NoSuchPackageException { | |
| if (alreadyBuilt) { | |
| return pkg; | |
| } | |
| beforeBuild(discoverAssumedInputFiles); | |
| return finishBuild(); | |
| } | |
| private InputFile addInputFile(Label label, Location location) { | |
| return addInputFile(new InputFile(pkg, label, location)); | |
| } | |
| private InputFile addInputFile(InputFile inputFile) { | |
| Target prev = targets.put(inputFile.getLabel().getName(), inputFile); | |
| Preconditions.checkState(prev == null); | |
| return inputFile; | |
| } | |
| /** | |
| * Precondition check for addRule. We must maintain these invariants of the package: | |
| * | |
| * <ul> | |
| * <li>Each name refers to at most one target. | |
| * <li>No rule with errors is inserted into the package. | |
| * <li>The generating rule of every output file in the package must itself be in the package. | |
| * </ul> | |
| */ | |
| private void checkForConflicts(Rule rule, List<Label> labels) throws NameConflictException { | |
| String name = rule.getName(); | |
| Target existing = targets.get(name); | |
| if (existing != null) { | |
| throw nameConflict(rule, existing); | |
| } | |
| List<OutputFile> outputFiles = rule.getOutputFiles(); | |
| Map<String, OutputFile> outputFilesByName = | |
| Maps.newHashMapWithExpectedSize(outputFiles.size()); | |
| for (OutputFile outputFile : outputFiles) { | |
| String outputFileName = outputFile.getName(); | |
| if (outputFilesByName.put(outputFileName, outputFile) != null) { | |
| throw duplicateOutputFile(outputFile, outputFile); // Duplicate within a single rule. | |
| } | |
| existing = targets.get(outputFileName); | |
| if (existing != null) { | |
| throw duplicateOutputFile(outputFile, existing); | |
| } | |
| // Check if this output file is the prefix of an already existing one. | |
| if (outputFilePrefixes.containsKey(outputFileName)) { | |
| throw conflictingOutputFile(outputFile, outputFilePrefixes.get(outputFileName)); | |
| } | |
| // Check if a prefix of this output file matches an already existing one. | |
| PathFragment outputFileFragment = PathFragment.create(outputFileName); | |
| int segmentCount = outputFileFragment.segmentCount(); | |
| for (int i = 1; i < segmentCount; i++) { | |
| String prefix = outputFileFragment.subFragment(0, i).toString(); | |
| if (outputFilesByName.containsKey(prefix)) { | |
| throw conflictingOutputFile(outputFile, outputFilesByName.get(prefix)); | |
| } | |
| if (targets.get(prefix) instanceof OutputFile) { | |
| throw conflictingOutputFile(outputFile, (OutputFile) targets.get(prefix)); | |
| } | |
| outputFilePrefixes.putIfAbsent(prefix, outputFile); | |
| } | |
| } | |
| checkForInputOutputConflicts(rule, labels, outputFilesByName.keySet()); | |
| } | |
| /** | |
| * A utility method that checks for conflicts between input file names and output file names for | |
| * a rule from a build file. | |
| * | |
| * @param rule the rule whose inputs and outputs are to be checked for conflicts. | |
| * @param labels the rules {@linkplain Rule#getLabels labels}. | |
| * @param outputFiles a set containing the names of output files to be generated by the rule. | |
| * @throws NameConflictException if a conflict is found. | |
| */ | |
| private static void checkForInputOutputConflicts( | |
| Rule rule, List<Label> labels, Set<String> outputFiles) throws NameConflictException { | |
| PackageIdentifier packageIdentifier = rule.getLabel().getPackageIdentifier(); | |
| for (Label inputLabel : labels) { | |
| if (packageIdentifier.equals(inputLabel.getPackageIdentifier()) | |
| && outputFiles.contains(inputLabel.getName())) { | |
| throw inputOutputNameConflict(rule, inputLabel.getName()); | |
| } | |
| } | |
| } | |
| /** An output file conflicts with another output file or the BUILD file. */ | |
| private static NameConflictException duplicateOutputFile( | |
| OutputFile duplicate, Target existing) { | |
| return new NameConflictException(duplicate.getTargetKind() + " '" + duplicate.getName() | |
| + "' in rule '" + duplicate.getGeneratingRule().getName() + "' " | |
| + conflictsWith(existing)); | |
| } | |
| /** The package contains two targets with the same name. */ | |
| private static NameConflictException nameConflict(Target duplicate, Target existing) { | |
| return new NameConflictException(duplicate.getTargetKind() + " '" + duplicate.getName() | |
| + "' in package '" + duplicate.getLabel().getPackageName() + "' " | |
| + conflictsWith(existing)); | |
| } | |
| /** A a rule has a input/output name conflict. */ | |
| private static NameConflictException inputOutputNameConflict( | |
| Rule rule, String conflictingName) { | |
| return new NameConflictException("rule '" + rule.getName() + "' has file '" | |
| + conflictingName + "' as both an input and an output"); | |
| } | |
| private static NameConflictException conflictingOutputFile( | |
| OutputFile added, OutputFile existing) { | |
| if (added.getGeneratingRule() == existing.getGeneratingRule()) { | |
| return new NameConflictException(String.format( | |
| "rule '%s' has conflicting output files '%s' and '%s'", added.getGeneratingRule() | |
| .getName(), added.getName(), existing.getName())); | |
| } else { | |
| return new NameConflictException(String.format( | |
| "output file '%s' of rule '%s' conflicts with output file '%s' of rule '%s'", added | |
| .getName(), added.getGeneratingRule().getName(), existing.getName(), existing | |
| .getGeneratingRule().getName())); | |
| } | |
| } | |
| /** | |
| * Utility function for generating exception messages. | |
| */ | |
| private static String conflictsWith(Target target) { | |
| String message = "conflicts with existing "; | |
| if (target instanceof OutputFile) { | |
| message += | |
| "generated file from rule '" | |
| + ((OutputFile) target).getGeneratingRule().getName() | |
| + "'"; | |
| } else { | |
| message += target.getTargetKind(); | |
| } | |
| return message + ", defined at " + target.getLocation(); | |
| } | |
| } | |
| /** 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); | |
| } | |
| } | |
| } |