| // Copyright 2018 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.packages; | |
| import com.google.common.base.Preconditions; | |
| import net.starlark.java.eval.EvalException; | |
| import net.starlark.java.eval.Starlark; | |
| import net.starlark.java.eval.StarlarkThread; | |
| /* | |
| * TODO(b/236456122): We should break this class up into separate classes for each kind of Starlark | |
| * evaluation environment, as opposed to storing possibly inapplicable nullable fields on this | |
| * class. Then we can use static methods with signatures like fromOrFail(StarlarkThread, String) in | |
| * place of the Phase enum and its associated check* methods. | |
| * | |
| * Some kinds of evaluation environments include: | |
| * - .bzl loading | |
| * - BUILD evaluation (should absorb PackageFactory.PackageContext -- no need to store multiple | |
| * thread-locals in StarlarkThread) | |
| * - WORKSPACE evaluation (shares logic with BUILD) | |
| * - implicit outputs | |
| * - computed defaults | |
| * - transition implementation | |
| * - Args.map_each | |
| * - probably others | |
| * | |
| * BazelStarlarkContext itself could probably be deleted. The only thing common to almost all Bazel | |
| * Starlark environments is SymbolGenerator, which in principle could be used to uniquely identify | |
| * depsets or even StarlarkFunction, though in practice it doesn't have nearly that widespread usage | |
| * yet. SymbolGenerator could be promoted to a core feature of StarlarkThread if it reaches that | |
| * point. If we do keep BazelStarlarkContext around as a common base class of the other context | |
| * classes, it should be renamed BazelThreadContext for symmetry with BazelModuleContext. | |
| * | |
| * Even if we can otherwise get rid of BazelStarlarkContext, it may still be handy to retain this | |
| * class as a static namespace for helper methods for storing and retrieving contexts on | |
| * StarlarkThreads. In particular, it'd avoid the very likely bug of a storeInThread | |
| * implementation in one of the context classes forgetting to setUncheckedExceptionContext(this). It | |
| * also would give us a place to keep javadoc about the proper way to use these context objects in | |
| * Bazel. | |
| */ | |
| /** | |
| * Bazel-specific contextual information associated with a Starlark evaluation thread. | |
| * | |
| * <p>This is stored in the StarlarkThread object as a thread-local. A subclass of this class may be | |
| * used for certain kinds of Starlark evaluations; in that case it is still keyed in the | |
| * thread-locals under {@code BazelStarlarkContext.class}. | |
| * | |
| * <p>This object is mutable and should not be accessed simultaneously or reused for more than one | |
| * Starlark thread. | |
| */ | |
| public class BazelStarlarkContext implements StarlarkThread.UncheckedExceptionContext { | |
| /** The phase to which this Starlark thread belongs. */ | |
| // TODO(b/236456122): Eliminate. | |
| public enum Phase { | |
| WORKSPACE, | |
| LOADING, | |
| ANALYSIS | |
| } | |
| /** | |
| * Retrieves this context from a Starlark thread, or throws {@link EvalException} if unavailable. | |
| */ | |
| public static BazelStarlarkContext fromOrFail(StarlarkThread thread) throws EvalException { | |
| BazelStarlarkContext ctx = thread.getThreadLocal(BazelStarlarkContext.class); | |
| if (ctx == null) { | |
| throw Starlark.errorf( | |
| "this function cannot be called from %s", thread.getContextDescription()); | |
| } | |
| return ctx; | |
| } | |
| /** | |
| * Saves this {@code BazelStarlarkContext} in the specified Starlark thread. Call only once, | |
| * before evaluation begins. | |
| */ | |
| public void storeInThread(StarlarkThread thread) { | |
| Preconditions.checkState(thread.getThreadLocal(BazelStarlarkContext.class) == null); | |
| thread.setThreadLocal(BazelStarlarkContext.class, this); | |
| // TODO(b/236456122): We can probably replace the concept of setUncheckedExceptionContext with a | |
| // string parameter to StarlarkThread construction. In that case, storeInThread() becomes more | |
| // superfluous, and there's one less reason to keep the inheritance hierarchy of | |
| // BazelStarlarkContext and its children. | |
| thread.setUncheckedExceptionContext(this); | |
| } | |
| // A generic counter for uniquely identifying symbols created in this Starlark evaluation. | |
| private final SymbolGenerator<?> symbolGenerator; | |
| // TODO(b/236456122): Eliminate Phase, migrate analysisRuleLabel to a separate context class. | |
| private final Phase phase; | |
| /** | |
| * @param phase the phase to which this Starlark thread belongs | |
| * @param symbolGenerator a {@link SymbolGenerator} to be used when creating objects to be | |
| * compared using reference equality. | |
| */ | |
| // TODO(b/236456122): Consider taking an owner in place of a SymbolGenerator, and constructing | |
| // the latter ourselves. Seems like we don't want to tempt anyone into sharing a SymbolGenerator. | |
| public BazelStarlarkContext(Phase phase, SymbolGenerator<?> symbolGenerator) { | |
| this.phase = Preconditions.checkNotNull(phase); | |
| this.symbolGenerator = Preconditions.checkNotNull(symbolGenerator); | |
| } | |
| /** Returns the phase associated with this context. */ | |
| public Phase getPhase() { | |
| return phase; | |
| } | |
| public SymbolGenerator<?> getSymbolGenerator() { | |
| return symbolGenerator; | |
| } | |
| @Override | |
| public String getContextForUncheckedException() { | |
| return phase.toString(); | |
| } | |
| /** | |
| * Checks that the Starlark thread is in the loading or the workspace phase. | |
| * | |
| * @param function name of a function that requires this check | |
| */ | |
| // TODO(b/236456122): The Phase enum is incomplete. Ex: `Args.map_each` evaluation happens at | |
| // execution time. So this is a misnomer and possibly wrong in those contexts. | |
| public static void checkLoadingOrWorkspacePhase(StarlarkThread thread, String function) | |
| throws EvalException { | |
| BazelStarlarkContext ctx = thread.getThreadLocal(BazelStarlarkContext.class); | |
| if (ctx == null) { | |
| throw Starlark.errorf( | |
| "'%s' cannot be called from %s", function, thread.getContextDescription()); | |
| } | |
| if (ctx.phase == Phase.ANALYSIS) { | |
| throw Starlark.errorf("'%s' cannot be called during the analysis phase", function); | |
| } | |
| } | |
| /** | |
| * Checks that the current StarlarkThread is in the loading phase. | |
| * | |
| * @param function name of a function that requires this check | |
| */ | |
| public static void checkLoadingPhase(StarlarkThread thread, String function) | |
| throws EvalException { | |
| BazelStarlarkContext ctx = thread.getThreadLocal(BazelStarlarkContext.class); | |
| if (ctx == null) { | |
| throw Starlark.errorf( | |
| "'%s' cannot be called from %s", function, thread.getContextDescription()); | |
| } | |
| if (ctx.phase != Phase.LOADING) { | |
| throw Starlark.errorf( | |
| "'%s' can only be called from a BUILD file, or a macro invoked from a BUILD file", | |
| function); | |
| } | |
| } | |
| /** | |
| * Checks that the current StarlarkThread is in the workspace phase. | |
| * | |
| * @param function name of a function that requires this check | |
| */ | |
| public static void checkWorkspacePhase(StarlarkThread thread, String function) | |
| throws EvalException { | |
| BazelStarlarkContext ctx = thread.getThreadLocal(BazelStarlarkContext.class); | |
| if (ctx == null) { | |
| throw Starlark.errorf( | |
| "'%s' cannot be called from %s", function, thread.getContextDescription()); | |
| } | |
| if (ctx.phase != Phase.WORKSPACE) { | |
| throw Starlark.errorf("'%s' can only be called during workspace loading", function); | |
| } | |
| } | |
| } |