| // Copyright 2024 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 static com.google.common.base.Preconditions.checkState; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.Interner; | |
| 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.LabelSyntaxException; | |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; | |
| import com.google.devtools.build.lib.cmdline.RepositoryMapping; | |
| import com.google.devtools.build.lib.cmdline.StarlarkThreadContext; | |
| import com.google.devtools.build.lib.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.StoredEventHandler; | |
| import com.google.devtools.build.lib.packages.Package.Builder.PackageLimits; | |
| import com.google.devtools.build.lib.packages.Package.Metadata; | |
| import com.google.devtools.build.lib.packages.TargetRecorder.MacroFrame; | |
| import com.google.devtools.build.lib.packages.TargetRecorder.NameConflictException; | |
| import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; | |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; | |
| import com.google.devtools.build.lib.util.DetailedExitCode; | |
| import com.google.devtools.build.lib.util.StringUtil; | |
| import com.google.devtools.build.lib.vfs.RootedPath; | |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | |
| import java.io.IOException; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.Comparator; | |
| import java.util.EnumSet; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.Optional; | |
| import java.util.OptionalLong; | |
| import java.util.TreeMap; | |
| import java.util.concurrent.Semaphore; | |
| import javax.annotation.Nullable; | |
| import net.starlark.java.eval.EvalException; | |
| import net.starlark.java.eval.Starlark; | |
| import net.starlark.java.eval.StarlarkSemantics; | |
| import net.starlark.java.eval.StarlarkThread; | |
| import net.starlark.java.eval.SymbolGenerator; | |
| import net.starlark.java.syntax.Location; | |
| /** | |
| * Base class of {@link Package.Builder} and {@link PackagePiece.Builder} that encapsulates all the | |
| * operations that may need to occur in the middle of BUILD file or symbolic macro evaluation, | |
| * without including operations specific to the setup or finalization of {@code Package} | |
| * construction. | |
| * | |
| * <p>In other words, if a {@link Package.Builder} or and {@link PackagePiece.Builder} method needs | |
| * to be called as a result of Starlark evaluation of either the BUILD file or its macros, the | |
| * operation belongs in this base class. | |
| * | |
| * <p>The motivation for this split is two-fold: 1) It keeps the size of Package.java smaller. 2) It | |
| * will make it easier to factor out common code for evaluating a whole package vs an individual | |
| * symbolic macro of that package (lazy macro evaluation). | |
| */ | |
| public abstract class TargetDefinitionContext extends StarlarkThreadContext { | |
| // TODO: #19922 - Avoid protected fields, encapsulate with getters/setters. Temporary state on way | |
| // to separating this class from Package.Builder. | |
| private final SymbolGenerator<?> symbolGenerator; | |
| // Same as pkg.metadata. | |
| protected final Metadata metadata; | |
| /** | |
| * The {@link Package} to be constructed with the help of this context. | |
| * | |
| * <p>Since the package has not yet been constructed, it is in an intermediate state and some | |
| * operations may fail unexpectedly. {@code TargetDefinitionContext} only uses this field to help | |
| * create the cyclic links between packages and their targets. | |
| */ | |
| protected final Packageoid pkg; | |
| // The container object on which targets and macro instances are added and conflicts are | |
| // detected. | |
| protected final TargetRecorder recorder; | |
| private final boolean simplifyUnconditionalSelectsInRuleAttrs; | |
| private final boolean symbolicMacroStrictAttrs; | |
| /** Converts label literals to Label objects within this package. */ | |
| private final LabelConverter labelConverter; | |
| /** | |
| * Semaphore held by the Skyframe thread when performing CPU work. | |
| * | |
| * <p>This should be released when performing I/O. | |
| */ | |
| @Nullable // Only non-null when inside PackageFunction.compute and the semaphore is enabled. | |
| private final Semaphore cpuBoundSemaphore; | |
| /** Estimates the cost of this packageoid. */ | |
| protected final PackageOverheadEstimator packageOverheadEstimator; | |
| // TreeMap so that the iteration order of variables is consistent regardless of insertion order | |
| // (which may change due to serialization). This is useful so that the serialized representation | |
| // is deterministic. | |
| protected final TreeMap<String, String> makeEnv = new TreeMap<>(); | |
| protected final StoredEventHandler localEventHandler = new StoredEventHandler(); | |
| @Nullable protected String ioExceptionMessage = null; | |
| @Nullable protected IOException ioException = null; | |
| @Nullable protected DetailedExitCode ioExceptionDetailedExitCode = null; | |
| // Used by glob(). Null for contexts where glob() is disallowed, like some tests. | |
| @Nullable private final Globber globber; | |
| protected final Map<Label, EnvironmentGroup> environmentGroups = new HashMap<>(); | |
| private final Interner<ImmutableList<?>> listInterner = new ThreadCompatibleInterner<>(); | |
| private final ImmutableMap<Location, String> generatorMap; | |
| private final PackageLimits packageLimits; | |
| protected final TestSuiteImplicitTestsAccumulator testSuiteImplicitTestsAccumulator = | |
| new TestSuiteImplicitTestsAccumulator(); | |
| // A packageoid's FailureDetail field derives from the events on its Builder's event handler. | |
| // During package deserialization, those events are unavailable, because those events aren't | |
| // serialized [*]. Its FailureDetail value is serialized, however. During deserialization, that | |
| // value is assigned here, so that it can be assigned to the deserialized package. | |
| // | |
| // Likewise, during workspace part assembly, errors from parent parts should propagate to their | |
| // children. | |
| // | |
| // [*] Not in the context of the package, anyway. Skyframe values containing a package may | |
| // serialize events emitted during its construction/evaluation. | |
| @Nullable private FailureDetail failureDetailOverride = null; | |
| protected boolean alreadyBuilt = false; | |
| protected long computationSteps = 0; | |
| /** Retrieves this object from a Starlark thread. Returns null if not present. */ | |
| @Nullable | |
| public static TargetDefinitionContext fromOrNull(StarlarkThread thread) { | |
| StarlarkThreadContext ctx = thread.getThreadLocal(StarlarkThreadContext.class); | |
| return ctx instanceof TargetDefinitionContext targetDefinitionContext | |
| ? targetDefinitionContext | |
| : null; | |
| } | |
| /** | |
| * Retrieves this object from a Starlark thread. If not present, throws an {@link EvalException} | |
| * with an error message indicating that {@code what} can only be used in a target definition | |
| * context - meaning in a BUILD file, or a legacy or symbolic macro. | |
| */ | |
| @CanIgnoreReturnValue | |
| public static TargetDefinitionContext fromOrFail(StarlarkThread thread, String what) | |
| throws EvalException { | |
| return fromOrFail(thread, what, "used"); | |
| } | |
| /** | |
| * Retrieves this object from a Starlark thread. If not present, throws an {@link EvalException} | |
| * with an error message indicating that {@code what} can only be {@code participle}d in a target | |
| * definition context - meaning in a BUILD file, or a legacy or symbolic macro. | |
| */ | |
| @CanIgnoreReturnValue | |
| public static TargetDefinitionContext fromOrFail( | |
| StarlarkThread thread, String what, String participle) throws EvalException { | |
| @Nullable StarlarkThreadContext ctx = thread.getThreadLocal(StarlarkThreadContext.class); | |
| if (ctx instanceof TargetDefinitionContext targetDefinitionContext) { | |
| return targetDefinitionContext; | |
| } | |
| throw newFromOrFailException( | |
| what, participle, thread.getSemantics(), EnumSet.noneOf(FromOrFailMode.class)); | |
| } | |
| /** | |
| * Retrieves this object from a Starlark thread. If not present, throws an {@link EvalException} | |
| * with an error message indicating that {@code what} can only be used in a BUILD file or a | |
| * finalizer symbolic macro. | |
| */ | |
| @CanIgnoreReturnValue | |
| public static TargetDefinitionContext fromOrFailDisallowNonFinalizerMacros( | |
| StarlarkThread thread, String what) throws EvalException { | |
| @Nullable StarlarkThreadContext ctx = thread.getThreadLocal(StarlarkThreadContext.class); | |
| if (ctx instanceof TargetDefinitionContext targetDefinitionContext | |
| && !targetDefinitionContext.recorder.currentlyInNonFinalizerMacro()) { | |
| return targetDefinitionContext; | |
| } | |
| throw newFromOrFailException( | |
| what, thread.getSemantics(), EnumSet.of(FromOrFailMode.ONLY_FINALIZER_MACROS)); | |
| } | |
| enum FromOrFailMode { | |
| NO_MACROS, | |
| ONLY_FINALIZER_MACROS, | |
| } | |
| static EvalException newFromOrFailException( | |
| String what, StarlarkSemantics semantics, EnumSet<FromOrFailMode> modes) { | |
| return newFromOrFailException(what, "used", semantics, modes); | |
| } | |
| static EvalException newFromOrFailException( | |
| String what, String participle, StarlarkSemantics semantics, EnumSet<FromOrFailMode> modes) { | |
| // TODO(bazel-team): append a description of the current evaluation context to the error, e.g. | |
| // "foo() can only be used while evaluating a BUILD file or a legacy macro; in particular, it | |
| // cannot be used at the top level of a .bzl file" | |
| boolean symbolicMacrosEnabled = | |
| semantics.getBool(BuildLanguageOptions.EXPERIMENTAL_ENABLE_FIRST_CLASS_MACROS); | |
| ArrayList<String> allowedUses = new ArrayList<>(); | |
| allowedUses.add("a BUILD file"); | |
| allowedUses.add( | |
| String.format( | |
| "a %s%smacro", | |
| symbolicMacrosEnabled ? "legacy " : "", | |
| symbolicMacrosEnabled | |
| && !modes.contains(FromOrFailMode.NO_MACROS) | |
| && !modes.contains(FromOrFailMode.ONLY_FINALIZER_MACROS) | |
| ? "or symbolic " | |
| : "")); | |
| if (symbolicMacrosEnabled && modes.contains(FromOrFailMode.ONLY_FINALIZER_MACROS)) { | |
| allowedUses.add("a rule finalizer"); | |
| } | |
| return Starlark.errorf( | |
| "%s can only be %s while evaluating %s", | |
| what, participle, StringUtil.joinEnglishList(allowedUses)); | |
| } | |
| /** | |
| * Returns an auto-closeable resource to synchronize the computation step count between this | |
| * context and its thread which has started execution. | |
| */ | |
| public StartedThreadComputationStepUpdater updateStartedThreadComputationSteps( | |
| StarlarkThread thread) { | |
| return new StartedThreadComputationStepUpdater(this, thread); | |
| } | |
| /** | |
| * Returns an auto-closeable resource to synchronize the computation step count between this | |
| * context and its thread whose execution is being paused, e.g. before pushing a new macro frame. | |
| */ | |
| public PausedThreadComputationStepUpdater updatePausedThreadComputationSteps( | |
| StarlarkThread thread) { | |
| return new PausedThreadComputationStepUpdater(this, thread); | |
| } | |
| /** | |
| * An auto-closeable resource to synchronize the computation step count between a {@link | |
| * TargetDefinitionContext} and its thread which has started execution. | |
| */ | |
| public static final class StartedThreadComputationStepUpdater implements AutoCloseable { | |
| private final TargetDefinitionContext context; | |
| private final StarlarkThread thread; | |
| private boolean closed = false; | |
| public StartedThreadComputationStepUpdater( | |
| TargetDefinitionContext context, StarlarkThread thread) { | |
| this.context = context; | |
| this.thread = thread; | |
| // Initialize the thread's computation step count to the context's total computation step | |
| // count. | |
| thread.incrementExecutedSteps(context.computationSteps); | |
| long threadMaxExecutionSteps = context.packageLimits.maxStarlarkComputationStepsPerPackage(); | |
| if (threadMaxExecutionSteps < Long.MAX_VALUE) { | |
| // StarlarkThread.setMaxExecutionSteps(limit) throws if we hit limit, but we want to allow | |
| // hitting the limit (but not going over). | |
| threadMaxExecutionSteps++; | |
| } | |
| thread.setMaxExecutionSteps(threadMaxExecutionSteps); | |
| } | |
| @Override | |
| public void close() { | |
| if (!closed) { | |
| context.setComputationSteps(thread.getExecutedSteps()); | |
| } | |
| closed = true; | |
| } | |
| } | |
| /** | |
| * An auto-closeable resource to synchronize the computation step count between a {@link | |
| * TargetDefinitionContext} and its thread whose execution is being paused. | |
| */ | |
| public static final class PausedThreadComputationStepUpdater implements AutoCloseable { | |
| private final TargetDefinitionContext context; | |
| private final StarlarkThread thread; | |
| private boolean closed = false; | |
| public PausedThreadComputationStepUpdater( | |
| TargetDefinitionContext context, StarlarkThread thread) { | |
| this.context = context; | |
| this.thread = thread; | |
| context.setComputationSteps(thread.getExecutedSteps()); | |
| } | |
| @Override | |
| public void close() { | |
| if (!closed) { | |
| checkState( | |
| thread.getExecutedSteps() <= context.computationSteps, | |
| "previously paused thread computation steps = %s cannot be greater than currently" | |
| + " recorded computation steps = %s", | |
| thread.getExecutedSteps(), | |
| context.computationSteps); | |
| thread.incrementExecutedSteps(context.computationSteps - thread.getExecutedSteps()); | |
| } | |
| closed = true; | |
| } | |
| } | |
| /** | |
| * Sets the context's computation step count from the computation step count of the current | |
| * thread. | |
| */ | |
| private void setComputationSteps(long threadComputationSteps) { | |
| checkState( | |
| threadComputationSteps >= computationSteps, | |
| "currently running thread computation steps = %s cannot be less than previously recorded" | |
| + " computation steps = %s", | |
| threadComputationSteps, | |
| computationSteps); | |
| computationSteps = threadComputationSteps; | |
| } | |
| /** Returns the "generator_name" to use for a given call site location in a BUILD file. */ | |
| @Nullable | |
| String getGeneratorNameByLocation(Location loc) { | |
| return generatorMap.get(loc); | |
| } | |
| /** | |
| * Returns the map from BUILD file locations to "generator_name" values; intended only for use by | |
| * skyframe.PackageFunction. | |
| */ | |
| public ImmutableMap<Location, String> getGeneratorMap() { | |
| return generatorMap; | |
| } | |
| /** | |
| * 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); | |
| } | |
| } | |
| TargetDefinitionContext( | |
| Metadata metadata, | |
| Packageoid pkg, | |
| SymbolGenerator<?> symbolGenerator, | |
| boolean simplifyUnconditionalSelectsInRuleAttrs, | |
| boolean symbolicMacroStrictAttrs, | |
| RepositoryMapping mainRepositoryMapping, | |
| @Nullable Semaphore cpuBoundSemaphore, | |
| PackageOverheadEstimator packageOverheadEstimator, | |
| @Nullable ImmutableMap<Location, String> generatorMap, | |
| @Nullable Globber globber, | |
| boolean enableNameConflictChecking, | |
| boolean trackFullMacroInformation, | |
| boolean enableTargetMapSnapshotting, | |
| PackageLimits packageLimits) { | |
| super(() -> mainRepositoryMapping); | |
| this.metadata = metadata; | |
| this.pkg = pkg; | |
| this.symbolGenerator = symbolGenerator; | |
| this.simplifyUnconditionalSelectsInRuleAttrs = simplifyUnconditionalSelectsInRuleAttrs; | |
| this.symbolicMacroStrictAttrs = symbolicMacroStrictAttrs; | |
| this.labelConverter = | |
| new LabelConverter(metadata.packageIdentifier(), metadata.repositoryMapping()); | |
| this.cpuBoundSemaphore = cpuBoundSemaphore; | |
| this.packageOverheadEstimator = packageOverheadEstimator; | |
| this.generatorMap = (generatorMap == null) ? ImmutableMap.of() : generatorMap; | |
| this.globber = globber; | |
| this.recorder = | |
| new TargetRecorder( | |
| enableNameConflictChecking, trackFullMacroInformation, enableTargetMapSnapshotting); | |
| this.packageLimits = packageLimits; | |
| } | |
| public Metadata getMetadata() { | |
| return metadata; | |
| } | |
| SymbolGenerator<?> getSymbolGenerator() { | |
| return symbolGenerator; | |
| } | |
| PackageIdentifier getPackageIdentifier() { | |
| return metadata.packageIdentifier(); | |
| } | |
| /** | |
| * Returns a short, lower-case description of the packageoid under construction, e.g. for use in | |
| * logging and error messages. | |
| */ | |
| String getShortDescription() { | |
| return pkg.getShortDescription(); | |
| } | |
| /** | |
| * Returns the name of the Bazel module associated with the repo this package is in. If this | |
| * package is in the special {@code @_builtins} pseudo-repo, this is empty. For repos generated by | |
| * module extensions, this is the name of the module hosting the extension. | |
| */ | |
| Optional<String> getAssociatedModuleName() { | |
| return metadata.associatedModuleName(); | |
| } | |
| /** | |
| * Returns the version of the Bazel module associated with the repo this package is in. If this | |
| * package is in the special {@code @_builtins} pseudo-repo, this is empty. For repos generated by | |
| * module extensions, this is the version of the module hosting the extension. | |
| */ | |
| Optional<String> getAssociatedModuleVersion() { | |
| return metadata.associatedModuleVersion(); | |
| } | |
| public LabelConverter getLabelConverter() { | |
| return labelConverter; | |
| } | |
| Interner<ImmutableList<?>> getListInterner() { | |
| return listInterner; | |
| } | |
| RootedPath getFilename() { | |
| return metadata.buildFilename(); | |
| } | |
| /** Returns the {@link StoredEventHandler} associated with this builder. */ | |
| public StoredEventHandler getLocalEventHandler() { | |
| return localEventHandler; | |
| } | |
| /** | |
| * Retrieves the current package args. Note that during BUILD file evaluation these are still | |
| * subject to mutation. | |
| */ | |
| public PackageArgs getPartialPackageArgs() { | |
| return pkg.getDeclarations().getPackageArgs(); | |
| } | |
| public boolean containsErrors() { | |
| return recorder.containsErrors(); | |
| } | |
| /** | |
| * Declares that errors were encountering while loading this package. | |
| * | |
| * <p>If this method is called, then there should also be an ERROR event added to the handler on | |
| * the {@link Package.Builder}. The event should include a {@link FailureDetail}. | |
| */ | |
| public void setContainsErrors() { | |
| checkState( | |
| pkg.targets == null, | |
| "TargetDefinitionContext.setContainsErrors() can only be used before finishBuild() has" | |
| + " propagated the builder's error status to the packageoid"); | |
| recorder.setContainsErrors(); | |
| } | |
| void setIOException(IOException e, String message, DetailedExitCode detailedExitCode) { | |
| this.ioException = e; | |
| this.ioExceptionMessage = message; | |
| this.ioExceptionDetailedExitCode = detailedExitCode; | |
| setContainsErrors(); | |
| } | |
| /** | |
| * Returns the {@link Globber} used to implement {@code glob()} functionality during BUILD | |
| * evaluation. Null for contexts where globbing is not possible, like some tests. | |
| */ | |
| @Nullable | |
| public Globber getGlobber() { | |
| return globber; | |
| } | |
| /** | |
| * Returns true if values of conditional rule attributes which only contain unconditional selects | |
| * should be simplified and stored as a non-select value. | |
| */ | |
| public boolean simplifyUnconditionalSelectsInRuleAttrs() { | |
| return this.simplifyUnconditionalSelectsInRuleAttrs; | |
| } | |
| /** Returns true if invalid attribute values in symbolic macros should fail the build. */ | |
| // TODO(bazel-team): This ought to always be true; unfortunately, enabling the strict behavior is | |
| // a breaking change. Remove after graveyarding --incompatible_symbolic_macro_strict_attrs. | |
| public boolean symbolicMacroStrictAttrs() { | |
| return this.symbolicMacroStrictAttrs; | |
| } | |
| /** | |
| * Returns the innermost currently executing symbolic macro, or null if not in a symbolic macro. | |
| */ | |
| @Nullable | |
| public MacroInstance currentMacro() { | |
| MacroFrame frame = recorder.getCurrentMacroFrame(); | |
| return frame == null ? null : frame.macroInstance; | |
| } | |
| /** | |
| * Creates a new {@link Rule} {@code r} where {@code r.getPackageoid()} is the {@link Packageoid} | |
| * associated with this {@link Builder}. | |
| * | |
| * <p>The created {@link Rule} will have no output files and therefore will be in an invalid | |
| * state. | |
| * | |
| * @param threadCallStack the call stack of the thread that created the rule. Call stacks for | |
| * threads of enclosing symbolic macros (if any) will be prepended to it automatically to form | |
| * the rule's full call stack. | |
| */ | |
| Rule createRule( | |
| Label label, RuleClass ruleClass, List<StarlarkThread.CallStackEntry> threadCallStack) { | |
| CallStack.Node fullInteriorCallStack; | |
| final Location location; | |
| if (currentMacro() != null) { | |
| location = currentMacro().getBuildFileLocation(); | |
| fullInteriorCallStack = CallStack.compact(threadCallStack, /* start= */ 0); | |
| for (MacroInstance macro = currentMacro(); macro != null; macro = macro.getParent()) { | |
| fullInteriorCallStack = | |
| CallStack.concatenate(macro.getParentCallStack(), fullInteriorCallStack); | |
| } | |
| } else { | |
| location = threadCallStack.isEmpty() ? Location.BUILTIN : threadCallStack.get(0).location; | |
| fullInteriorCallStack = CallStack.compact(threadCallStack, /* start= */ 1); | |
| } | |
| return createRule(label, ruleClass, location, fullInteriorCallStack); | |
| } | |
| Rule createRule( | |
| Label label, | |
| RuleClass ruleClass, | |
| Location location, | |
| @Nullable CallStack.Node interiorCallStack) { | |
| return new Rule(pkg, label, ruleClass, location, interiorCallStack); | |
| } | |
| /** Creates a new {@link MacroInstance} in this builder's packageoid. */ | |
| MacroInstance createMacro( | |
| MacroClass macroClass, | |
| String name, | |
| int sameNameDepth, | |
| List<StarlarkThread.CallStackEntry> parentCallStack) | |
| throws LabelSyntaxException, EvalException { | |
| MacroInstance parent = currentMacro(); | |
| final Location location; | |
| final CallStack.Node compactParentCallStack; | |
| if (parent != null) { | |
| location = parent.getBuildFileLocation(); | |
| compactParentCallStack = CallStack.compact(parentCallStack, /* start= */ 0); | |
| } else { | |
| location = parentCallStack.isEmpty() ? Location.BUILTIN : parentCallStack.get(0).location; | |
| compactParentCallStack = CallStack.compact(parentCallStack, /* start= */ 1); | |
| } | |
| return new MacroInstance( | |
| pkg.getMetadata(), | |
| pkg.getDeclarations(), | |
| parent, | |
| parent != null ? parent.getGeneratorName() : generatorMap.get(location), | |
| location, | |
| compactParentCallStack, | |
| macroClass, | |
| Label.create(pkg.getMetadata().packageIdentifier(), name), | |
| sameNameDepth); | |
| } | |
| /** Returns true if symbolic macros should be eagerly expanded in this context. */ | |
| public abstract boolean eagerlyExpandMacros(); | |
| @Nullable | |
| public MacroFrame getCurrentMacroFrame() { | |
| return recorder.getCurrentMacroFrame(); | |
| } | |
| @Nullable | |
| public MacroFrame setCurrentMacroFrame(@Nullable MacroFrame frame) { | |
| return recorder.setCurrentMacroFrame(frame); | |
| } | |
| public boolean currentlyInNonFinalizerMacro() { | |
| return recorder.currentlyInNonFinalizerMacro(); | |
| } | |
| @Nullable | |
| public Target getTarget(String name) { | |
| return recorder.getTarget(name); | |
| } | |
| // TODO: #19922 - Refactor finalizer expansion such that TargetDefinitionContext can handle | |
| // working with finalizer macros. At that point, getRulesSnapshotView() and | |
| // getNonFinalizerInstantiatedRule() must account for the snapshot view here rather than in the | |
| // override in Package.Builder. | |
| /** | |
| * Returns a lightweight snapshot view of the names of all rule targets belonging to this package | |
| * at the time of this call; in finalizer expansion stage, returns a lightweight snapshot view of | |
| * only the non-finalizer-instantiated rule targets. | |
| * | |
| * @throws IllegalStateException if this method is called after {@link | |
| * Package.Builder#beforeBuild} has been called. | |
| */ | |
| Map<String, Rule> getRulesSnapshotView() { | |
| if (recorder.getTargetMap() instanceof SnapshottableBiMap<?, ?>) { | |
| return Maps.transformValues( | |
| ((SnapshottableBiMap<String, Target>) recorder.getTargetMap()).getTrackedSnapshot(), | |
| target -> (Rule) target); | |
| } else { | |
| // TODO(https://github.com/bazelbuild/bazel/issues/23852): if we are in a PackagePiece | |
| // builder, trigger a skyframe restart and request a full Package. | |
| throw new IllegalStateException( | |
| "getRulesSnapshotView() cannot be used after beforeBuild() has been called"); | |
| } | |
| } | |
| /** | |
| * Returns a non-finalizer-instantiated rule target with the provided name belonging to this | |
| * package at the time of this call. If such a rule target cannot be returned, returns null. | |
| */ | |
| // TODO(https://github.com/bazelbuild/bazel/issues/23765): when we restrict | |
| // native.existing_rule() to be usable only in finalizer context, we can replace this method | |
| // with {@code getRulesSnapshotView().get(name)}; we don't do so at present because we do not | |
| // want to make unnecessary snapshots. | |
| @Nullable | |
| Rule getNonFinalizerInstantiatedRule(String name) { | |
| Target target = recorder.getTargetMap().get(name); | |
| return target instanceof Rule ? (Rule) target : null; | |
| } | |
| /** | |
| * Creates an input file target in this package with the specified name, if it does not yet exist. | |
| * | |
| * <p>This operation is idempotent. | |
| * | |
| * @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 {@code InputFile}, or the old one if it already existed. | |
| * @throws NameConflictException if the name was already taken by another target that is not an | |
| * input file | |
| * @throws IllegalArgumentException if the name is not a valid label | |
| */ | |
| InputFile createInputFile(String targetName, Location location) throws NameConflictException { | |
| Target existing = recorder.getTargetMap().get(targetName); | |
| if (existing instanceof InputFile) { | |
| return (InputFile) existing; // idempotent | |
| } | |
| InputFile inputFile; | |
| try { | |
| inputFile = new InputFile(pkg, createLabel(targetName), location); | |
| } catch (LabelSyntaxException e) { | |
| throw new IllegalArgumentException( | |
| "FileTarget in package " + metadata.getName() + " has illegal name: " + targetName, e); | |
| } | |
| recorder.addTarget(inputFile); | |
| return inputFile; | |
| } | |
| /** | |
| * 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. | |
| */ | |
| // TODO: #19922 - Don't allow exports_files() to modify visibility of targets that the current | |
| // symbolic macro did not create. Fun pathological example: exports_files() modifying the | |
| // visibility of :BUILD inside a symbolic macro. | |
| void setVisibilityAndLicense(InputFile inputFile, RuleVisibility visibility, License license) { | |
| String filename = inputFile.getName(); | |
| Target cacheInstance = recorder.getTargetMap().get(filename); | |
| if (!(cacheInstance instanceof InputFile)) { | |
| throw new IllegalArgumentException( | |
| "Can't set visibility for nonexistent FileTarget " | |
| + filename | |
| + " in package " | |
| + metadata.getName() | |
| + "."); | |
| } | |
| if (!((InputFile) cacheInstance).isVisibilitySpecified() | |
| || cacheInstance.getVisibility() != visibility | |
| || !Objects.equals(cacheInstance.getLicense(), license)) { | |
| recorder.replaceInputFileUnchecked( | |
| 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(metadata.packageIdentifier(), 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); | |
| recorder.addTarget(group); | |
| if (group.containsErrors()) { | |
| setContainsErrors(); | |
| } | |
| } | |
| public void addRule(Rule rule) throws NameConflictException { | |
| Preconditions.checkArgument(rule.getPackageoid() == pkg); | |
| recorder.addRule(rule); | |
| } | |
| public void addMacro(MacroInstance macro) throws NameConflictException { | |
| recorder.addMacro(macro); | |
| } | |
| @Nullable | |
| public Semaphore getCpuBoundSemaphore() { | |
| return cpuBoundSemaphore; | |
| } | |
| void setFailureDetailOverride(FailureDetail failureDetail) { | |
| failureDetailOverride = failureDetail; | |
| } | |
| @Nullable | |
| public FailureDetail getFailureDetail() { | |
| if (failureDetailOverride != null) { | |
| return failureDetailOverride; | |
| } | |
| List<Event> undetailedEvents = null; | |
| for (Event event : localEventHandler.getEvents()) { | |
| if (event.getKind() != EventKind.ERROR) { | |
| continue; | |
| } | |
| DetailedExitCode detailedExitCode = event.getProperty(DetailedExitCode.class); | |
| if (detailedExitCode != null && detailedExitCode.getFailureDetail() != null) { | |
| return detailedExitCode.getFailureDetail(); | |
| } | |
| if (containsErrors()) { | |
| if (undetailedEvents == null) { | |
| undetailedEvents = new ArrayList<>(); | |
| } | |
| undetailedEvents.add(event); | |
| } | |
| } | |
| if (undetailedEvents != null) { | |
| BugReport.sendNonFatalBugReport( | |
| new IllegalStateException( | |
| "TargetDefinitionContext has undetailed error from " | |
| + undetailedEvents | |
| + " for packageoid " | |
| + pkg)); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Returns the number of Starlark computation steps executed thus far by threads performing | |
| * evaluation of this packageoid, which are recorded by updaters created by {@link | |
| * #updateStartedThreadComputationSteps} and {@link #updatePausedThreadComputationSteps}. | |
| */ | |
| long getComputationSteps() { | |
| return computationSteps; | |
| } | |
| // | |
| // Packageoid (package or package piece) construction methods, intended for use only by | |
| // PackageFunction and friends. | |
| // | |
| @CanIgnoreReturnValue | |
| protected TargetDefinitionContext beforeBuild() throws NoSuchPackageException { | |
| if (ioException != null) { | |
| throw new NoSuchPackageException( | |
| getPackageIdentifier(), ioExceptionMessage, ioException, ioExceptionDetailedExitCode); | |
| } | |
| // 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(); | |
| for (Rule rule : recorder.getRules()) { | |
| testSuiteImplicitTestsAccumulator.processRule(rule); | |
| } | |
| // Make sure all accumulated values are sorted for determinism. | |
| testSuiteImplicitTestsAccumulator.sortTests(); | |
| return this; | |
| } | |
| /** Intended for use by {@link com.google.devtools.build.lib.skyframe.PackageFunction} only. */ | |
| // TODO(bazel-team): It seems like the motivation for this method (added in cl/74794332) is to | |
| // allow PackageFunction to delete targets that are found to violate the | |
| // label-crossing-subpackage-boundaries check. Is there a simpler way to express this idea that | |
| // doesn't make package-building a multi-stage process? | |
| @CanIgnoreReturnValue | |
| public TargetDefinitionContext buildPartial() throws NoSuchPackageException { | |
| if (alreadyBuilt) { | |
| return this; | |
| } | |
| return beforeBuild(); | |
| } | |
| /** | |
| * Intended for use by {@link com.google.devtools.build.lib.skyframe.PackageFunction} only. | |
| * | |
| * <p>This method is intended to be overridden by subclasses to perform packageoid-specific final | |
| * initialization steps. | |
| */ | |
| // Non-final only to allow subclasses to return a more specific type. | |
| public Packageoid finishBuild() { | |
| if (alreadyBuilt) { | |
| return pkg; | |
| } | |
| alreadyBuilt = true; | |
| // Freeze rules, compacting their attributes' representations. | |
| for (Rule rule : recorder.getRules()) { | |
| rule.freeze(); | |
| } | |
| // Freeze macros, compacting their attributes' representations. | |
| for (MacroInstance macro : recorder.getMacroMap().values()) { | |
| macro.freeze(); | |
| } | |
| // Last chance to set the builder's error status. | |
| finalBuilderValidationHook(); | |
| // Initialize packageoid. | |
| pkg.containsErrors |= containsErrors(); | |
| pkg.failureDetail = getFailureDetail(); | |
| pkg.targets = | |
| ImmutableList.sortedCopyOf( | |
| Comparator.comparing(Target::getName), recorder.getTargetMap().values()); | |
| packageoidInitializationHook(); | |
| // Overhead should be estimated after all packageoid fields have been set. | |
| OptionalLong overheadEstimate = packageOverheadEstimator.estimatePackageOverhead(pkg); | |
| pkg.packageOverhead = overheadEstimate.orElse(Packageoid.PACKAGE_OVERHEAD_UNSET); | |
| // Verify that we haven't introduced new errors on the builder since the call to | |
| // finalBuilderValidationHook(). | |
| if (containsErrors()) { | |
| checkState( | |
| pkg.containsErrors(), "Builder error status not propagated to package or package piece"); | |
| } | |
| return pkg; | |
| } | |
| /** | |
| * Performs final builder validations (if needed), possibly modifying the builder's error status. | |
| * | |
| * <p>This method is intended to be overridden by subclasses; it is invoked by {@link | |
| * #finishBuild()} immediately before initializing the packageoid and copying error status from | |
| * the builder to the packageoid. | |
| */ | |
| protected void finalBuilderValidationHook() {} | |
| /** | |
| * Sets remaining subclass-specific fields on the packageoid. | |
| * | |
| * <p>This method is intended to be overridden by subclasses; it is invoked by {@link | |
| * #finishBuild()} after {@link #finalBuilderValidationHook()} has passed and the packageoid's | |
| * base fields (such as error information, targets, and macros) have been frozen and set. This | |
| * method must not call {@link #setContainsErrors()} on the builder; but it is allowed to set | |
| * packageoid fields that impact overhead estimation. | |
| */ | |
| protected void packageoidInitializationHook() {} | |
| } |