| // Copyright 2015 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.syntax; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkGlobalLibrary; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; |
| import java.lang.reflect.Field; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Global constants and support for static registration of builtin symbols. |
| */ |
| // TODO(bazel-team): Rename to SkylarkRuntime to avoid conflict with java.lang.Runtime. |
| public final class Runtime { |
| |
| private Runtime() {} |
| |
| @SkylarkSignature(name = "True", returnType = Boolean.class, |
| doc = "Literal for the boolean true.") |
| private static final Boolean TRUE = true; |
| |
| @SkylarkSignature(name = "False", returnType = Boolean.class, |
| doc = "Literal for the boolean false.") |
| private static final Boolean FALSE = false; |
| |
| /** There should be only one instance of this type to allow "== None" tests. */ |
| @SkylarkModule( |
| name = "NoneType", |
| documented = false, |
| doc = "Unit type, containing the unique value None." |
| ) |
| @Immutable |
| public static final class NoneType implements SkylarkValue { |
| private NoneType() {} |
| |
| @Override |
| public String toString() { |
| return "None"; |
| } |
| |
| @Override |
| public boolean isImmutable() { |
| return true; |
| } |
| |
| @Override |
| public void repr(SkylarkPrinter printer) { |
| printer.append("None"); |
| } |
| } |
| |
| /** Marker for unbound variables in cases where neither Java null nor Skylark None is suitable. */ |
| @Immutable |
| public static final class UnboundMarker implements SkylarkValue { |
| private UnboundMarker() {} |
| |
| @Override |
| public String toString() { |
| return "<unbound>"; |
| } |
| |
| @Override |
| public boolean isImmutable() { |
| return true; |
| } |
| |
| @Override |
| public void repr(SkylarkPrinter printer) { |
| printer.append("<unbound>"); |
| } |
| } |
| |
| @SkylarkSignature( |
| name = "<unbound>", |
| returnType = UnboundMarker.class, |
| documented = false, |
| doc = "Marker for unbound values in cases where neither Starlark None nor Java null can do.") |
| public static final UnboundMarker UNBOUND = new UnboundMarker(); |
| |
| @SkylarkSignature(name = "None", returnType = NoneType.class, |
| doc = "Literal for the None value.") |
| public static final NoneType NONE = new NoneType(); |
| |
| @SkylarkSignature(name = "PACKAGE_NAME", returnType = String.class, |
| doc = "<b>Deprecated. Use <a href=\"native.html#package_name\">package_name()</a> " |
| + "instead.</b> The name of the package being evaluated. " |
| + "For example, in the BUILD file <code>some/package/BUILD</code>, its value " |
| + "will be <code>some/package</code>. " |
| + "If the BUILD file calls a function defined in a .bzl file, PACKAGE_NAME will " |
| + "match the caller BUILD file package. " |
| + "In .bzl files, do not access PACKAGE_NAME at the file-level (outside of functions), " |
| + "either directly or by calling a function at the file-level that accesses " |
| + "PACKAGE_NAME (PACKAGE_NAME is only defined during BUILD file evaluation)." |
| + "Here is an example of a .bzl file:<br>" |
| + "<pre class=language-python>" |
| + "# a = PACKAGE_NAME # not allowed outside functions\n" |
| + "def extension():\n" |
| + " return PACKAGE_NAME</pre>" |
| + "In this case, <code>extension()</code> can be called from a BUILD file (even " |
| + "indirectly), but not in a file-level expression in the .bzl file. " |
| + "When implementing a rule, use <a href=\"ctx.html#label\">ctx.label</a> to know where " |
| + "the rule comes from. ") |
| public static final String PKG_NAME = "PACKAGE_NAME"; |
| |
| @SkylarkSignature( |
| name = "REPOSITORY_NAME", |
| returnType = String.class, |
| doc = |
| "<b>Deprecated. Use <a href=\"native.html#repository_name\">repository_name()</a> " |
| + "instead.</b> The name of the repository the rule or build extension is called " |
| + "from. " |
| + "For example, in packages that are called into existence by the WORKSPACE stanza " |
| + "<code>local_repository(name='local', path=...)</code> it will be set to " |
| + "<code>@local</code>. In packages in the main repository, it will be set to " |
| + "<code>@</code>. It can only be accessed in functions (transitively) called from " |
| + "BUILD files, i.e. it follows the same restrictions as " |
| + "<a href=\"#PACKAGE_NAME\">PACKAGE_NAME</a>.") |
| public static final String REPOSITORY_NAME = "REPOSITORY_NAME"; |
| |
| /** Adds bindings for False/True/None constants to the given map builder. */ |
| public static void addConstantsToBuilder(ImmutableMap.Builder<String, Object> builder) { |
| // In Python 2.x, True and False are global values and can be redefined by the user. |
| // In Python 3.x, they are keywords. We implement them as values. Currently they can't be |
| // redefined because builtins can't be overridden. In the future we should permit shadowing of |
| // most builtins but still prevent shadowing of these constants. |
| builder |
| .put("False", FALSE) |
| .put("True", TRUE) |
| .put("None", NONE); |
| } |
| |
| |
| /** |
| * Returns the canonical class representing the namespace associated with the given class, i.e., |
| * the class under which builtins should be registered. |
| */ |
| public static Class<?> getSkylarkNamespace(Class<?> clazz) { |
| return String.class.isAssignableFrom(clazz) |
| ? StringModule.class |
| : EvalUtils.getSkylarkType(clazz); |
| } |
| |
| /** |
| * A registry of builtins, including both global builtins and builtins that are under some |
| * namespace. |
| * |
| * <p>The registry contains Starlark universal builtins (None, len, etc), core build-language |
| * functions (depset, select, glob, etc), members of the native module (FilesetEntry, |
| * local_repository, vardef, ...), but not the native rules (cc_library, etc) nor other built-ins |
| * (CcCompilationInfo, java_common.JavaRuntimeInfo, etc). |
| * |
| * <p>Concurrency model: This object is thread-safe. Read accesses are always allowed, while write |
| * accesses are only allowed before this object has been frozen ({@link #freeze}). Prior to |
| * freezing, all operations are synchronized, while after freezing they are lockless. |
| */ |
| public static class BuiltinRegistry { |
| |
| /** |
| * Whether the registry's construction has completed. |
| * |
| * <p>Mutating methods may only be called while this is still false. Accessor methods may be |
| * called at any time. |
| * |
| * <p>We use {@code volatile} rather than {@link AtomicBoolean} because the bit flip only |
| * happens once, and doesn't require correlated reads and writes. |
| */ |
| private volatile boolean frozen = false; |
| |
| /** |
| * All registered builtins, keyed and sorted by an identifying (but otherwise unimportant) |
| * string. |
| * |
| * <p>The string is typically formed from the builtin's simple name and the Java class in which |
| * it is defined. The Java class need not correspond to a namespace. (This map includes global |
| * builtins that have no namespace.) |
| */ |
| private final Map<String, Object> allBuiltins = new TreeMap<>(); |
| |
| /** All non-global builtin functions, keyed by their namespace class and their name. */ |
| private final Map<Class<?>, Map<String, BaseFunction>> functions = new HashMap<>(); |
| |
| /** |
| * Marks the registry as initialized, if it wasn't already. |
| * |
| * <p>It is guaranteed that after this method returns, all accessor methods are safe without |
| * synchronization; i.e. no mutation operation can touch the data structures. |
| */ |
| public void freeze() { |
| // Similar to double-checked locking, but no need to check again on the inside since we don't |
| // care if two threads set the bit at once. The synchronized block is only to provide |
| // exclusion with mutations. |
| if (!this.frozen) { |
| synchronized (this) { |
| this.frozen = true; |
| } |
| } |
| } |
| |
| /** Registers a builtin with the given simple name, that was defined in the given Java class. */ |
| public synchronized void registerBuiltin(Class<?> definingClass, String name, Object builtin) { |
| String key = String.format("%s.%s", definingClass.getName(), name); |
| Preconditions.checkArgument( |
| !allBuiltins.containsKey(key), |
| "Builtin '%s' registered multiple times", |
| key); |
| |
| Preconditions.checkState( |
| !frozen, |
| "Attempted to register builtin '%s' after registry has already been frozen", |
| key); |
| |
| allBuiltins.put(key, builtin); |
| } |
| |
| /** |
| * Registers a function underneath a namespace. |
| * |
| * <p>This is independent of {@link #registerBuiltin}. |
| */ |
| public synchronized void registerFunction(Class<?> namespace, BaseFunction function) { |
| Preconditions.checkNotNull(namespace); |
| Preconditions.checkNotNull(function.getObjectType()); |
| Class<?> skylarkNamespace = getSkylarkNamespace(namespace); |
| Preconditions.checkArgument(skylarkNamespace.equals(namespace)); |
| Class<?> objType = getSkylarkNamespace(function.getObjectType()); |
| Preconditions.checkArgument(objType.equals(skylarkNamespace)); |
| |
| Preconditions.checkState( |
| !frozen, |
| "Attempted to register function '%s' in namespace '%s' after registry has already been " |
| + "frozen", |
| function, |
| namespace); |
| |
| functions.computeIfAbsent(namespace, k -> new HashMap<>()); |
| functions.get(namespace).put(function.getName(), function); |
| } |
| |
| /** Returns a list of all registered builtins, in a deterministic order. */ |
| public ImmutableList<Object> getBuiltins() { |
| if (frozen) { |
| return ImmutableList.copyOf(allBuiltins.values()); |
| } else { |
| synchronized (this) { |
| return ImmutableList.copyOf(allBuiltins.values()); |
| } |
| } |
| } |
| |
| @Nullable |
| private Map<String, BaseFunction> getFunctionsInNamespace(Class<?> namespace) { |
| return functions.get(getSkylarkNamespace(namespace)); |
| } |
| |
| /** |
| * Given a namespace, returns the function with the given name. |
| * |
| * <p>If the namespace does not exist or has no function with that name, returns null. |
| */ |
| public BaseFunction getFunction(Class<?> namespace, String name) { |
| if (frozen) { |
| Map<String, BaseFunction> namespaceFunctions = getFunctionsInNamespace(namespace); |
| return namespaceFunctions != null ? namespaceFunctions.get(name) : null; |
| } else { |
| synchronized (this) { |
| Map<String, BaseFunction> namespaceFunctions = getFunctionsInNamespace(namespace); |
| return namespaceFunctions != null ? namespaceFunctions.get(name) : null; |
| } |
| } |
| } |
| |
| /** |
| * Given a namespace, returns all function names. |
| * |
| * <p>If the namespace does not exist, returns an empty set. |
| */ |
| public ImmutableSet<String> getFunctionNames(Class<?> namespace) { |
| if (frozen) { |
| Map<String, BaseFunction> namespaceFunctions = getFunctionsInNamespace(namespace); |
| if (namespaceFunctions == null) { |
| return ImmutableSet.of(); |
| } |
| return ImmutableSet.copyOf(namespaceFunctions.keySet()); |
| } else { |
| synchronized (this) { |
| Map<String, BaseFunction> namespaceFunctions = getFunctionsInNamespace(namespace); |
| if (namespaceFunctions == null) { |
| return ImmutableSet.of(); |
| } |
| return ImmutableSet.copyOf(namespaceFunctions.keySet()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * All Skylark builtins. |
| * |
| * <p>Note that just because a symbol is registered here does not necessarily mean that it is |
| * accessible in a particular {@link Environment}. This registry should include any builtin that |
| * is available in any environment. |
| * |
| * <p>Thread safety: This object is unsynchronized. The register functions are typically called |
| * from within static initializer blocks, which should be fine. |
| */ |
| private static final BuiltinRegistry builtins = new BuiltinRegistry(); |
| |
| /** |
| * Retrieve the static instance containing information on all known Skylark builtins. |
| * |
| * @deprecated do not use a static singleton registry -- instead set up the Skylark environment |
| * with 'global' objects |
| */ |
| @Deprecated |
| public static BuiltinRegistry getBuiltinRegistry() { |
| return builtins; |
| } |
| |
| /** |
| * Convenience overload of {@link #setupModuleGlobals(ImmutableMap.Builder, Class)} to add |
| * bindings directly to an {@link Environment}. |
| * |
| * @param env the Environment into which to register fields |
| * @param moduleClass the Class object containing globals |
| * @deprecated use {@link #setupSkylarkLibrary} instead (and {@link SkylarkCallable} instead of |
| * {@link SkylarkSignature}) |
| */ |
| @Deprecated |
| public static void setupModuleGlobals(Environment env, Class<?> moduleClass) { |
| ImmutableMap.Builder<String, Object> envBuilder = ImmutableMap.builder(); |
| |
| setupModuleGlobals(envBuilder, moduleClass); |
| for (Map.Entry<String, Object> envEntry : envBuilder.build().entrySet()) { |
| env.setup(envEntry.getKey(), envEntry.getValue()); |
| } |
| } |
| |
| /** |
| * Adds global (top-level) symbols, provided by the given class object, to the given bindings |
| * builder. |
| * |
| * <p>Global symbols may be provided by the given class in the following ways: |
| * <ul> |
| * <li>If the class is annotated with {@link SkylarkModule}, an instance of that object is |
| * a global object with the module's name.</li> |
| * <li>If the class has fields annotated with {@link SkylarkSignature}, each of these |
| * fields is a global object with the signature's name.</li> |
| * <li>If the class is annotated with {@link SkylarkGlobalLibrary}, then all of its methods |
| * which are annotated with |
| * {@link com.google.devtools.build.lib.skylarkinterface.SkylarkCallable} are global |
| * callables.</li> |
| * </ul> |
| * |
| * <p>On collisions, this method throws an {@link AssertionError}. Collisions may occur if |
| * multiple global libraries have functions of the same name, two modules of the same name |
| * are given, or if two subclasses of the same module are given. |
| * |
| * @param builder the builder for the "bindings" map, which maps from symbol names to objects, |
| * and which will be built into a global frame |
| * @param moduleClass the Class object containing globals |
| * @deprecated use {@link #setupSkylarkLibrary} instead (and {@link SkylarkCallable} instead of |
| * {@link SkylarkSignature}) |
| */ |
| @Deprecated |
| public static void setupModuleGlobals(ImmutableMap.Builder<String, Object> builder, |
| Class<?> moduleClass) { |
| try { |
| if (SkylarkInterfaceUtils.getSkylarkModule(moduleClass) != null |
| || SkylarkInterfaceUtils.hasSkylarkGlobalLibrary(moduleClass)) { |
| setupSkylarkLibrary(builder, moduleClass.getConstructor().newInstance()); |
| } |
| for (Field field : moduleClass.getDeclaredFields()) { |
| if (field.isAnnotationPresent(SkylarkSignature.class)) { |
| // Fields in Skylark modules are sometimes private. |
| // Nevertheless they have to be annotated with SkylarkSignature. |
| field.setAccessible(true); |
| SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class); |
| Object value = field.get(null); |
| // Ignore function factories and non-global functions |
| if (!(value instanceof BuiltinFunction.Factory |
| || (value instanceof BaseFunction |
| && !annotation.objectType().equals(Object.class)))) { |
| builder.put(annotation.name(), value); |
| } |
| } |
| } |
| } catch (ReflectiveOperationException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Adds global (top-level) symbols, provided by the given object, to the given bindings |
| * builder. |
| * |
| * <p>Global symbols may be provided by the given object in the following ways: |
| * <ul> |
| * <li>If its class is annotated with {@link SkylarkModule}, an instance of that object is |
| * a global object with the module's name.</li> |
| * <li>If its class is annotated with {@link SkylarkGlobalLibrary}, then all of its methods |
| * which are annotated with |
| * {@link com.google.devtools.build.lib.skylarkinterface.SkylarkCallable} are global |
| * callables.</li> |
| * </ul> |
| * |
| * <p>On collisions, this method throws an {@link AssertionError}. Collisions may occur if |
| * multiple global libraries have functions of the same name, two modules of the same name |
| * are given, or if two subclasses of the same module are given. |
| * |
| * @param builder the builder for the "bindings" map, which maps from symbol names to objects, |
| * and which will be built into a global frame |
| * @param moduleInstance the object containing globals |
| * @throws AssertionError if there are name collisions |
| * @throws IllegalArgumentException if {@code moduleInstance} is not annotated with |
| * {@link SkylarkGlobalLibrary} nor {@link SkylarkModule} |
| */ |
| public static void setupSkylarkLibrary(ImmutableMap.Builder<String, Object> builder, |
| Object moduleInstance) { |
| Class<?> moduleClass = moduleInstance.getClass(); |
| SkylarkModule skylarkModule = SkylarkInterfaceUtils.getSkylarkModule(moduleClass); |
| boolean hasSkylarkGlobalLibrary = SkylarkInterfaceUtils.hasSkylarkGlobalLibrary(moduleClass); |
| |
| Preconditions.checkArgument(hasSkylarkGlobalLibrary || skylarkModule != null, |
| "%s must be annotated with @SkylarkGlobalLibrary or @SkylarkModule", |
| moduleClass); |
| |
| if (skylarkModule != null) { |
| builder.put(skylarkModule.name(), moduleInstance); |
| } |
| if (hasSkylarkGlobalLibrary) { |
| for (String methodName : FuncallExpression.getMethodNames(moduleClass)) { |
| builder.put(methodName, FuncallExpression.getBuiltinCallable(moduleInstance, methodName)); |
| } |
| } |
| } |
| } |