blob: 839b3114cf7800da940adb0cd8eed2f9f44ec1bf [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.syntax;
16
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000018import com.google.common.collect.ImmutableSet;
19import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
John Field585d1a02015-12-16 16:03:52 +000020import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
vladmos46907932017-06-30 14:01:45 +020021import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
John Field585d1a02015-12-16 16:03:52 +000022import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
23import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000024import java.lang.reflect.Field;
25import java.util.HashMap;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000026import java.util.Map;
brandjondc2c5502017-12-07 14:30:04 -080027import java.util.TreeMap;
28import javax.annotation.Nullable;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000029
30/**
brandjondc2c5502017-12-07 14:30:04 -080031 * Global constants and support for static registration of builtin symbols.
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000032 */
brandjondc2c5502017-12-07 14:30:04 -080033// TODO(bazel-team): Rename to SkylarkRuntime to avoid conflict with java.lang.Runtime.
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000034public final class Runtime {
35
36 private Runtime() {}
37
38 @SkylarkSignature(name = "True", returnType = Boolean.class,
39 doc = "Literal for the boolean true.")
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000040 private static final Boolean TRUE = true;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000041
42 @SkylarkSignature(name = "False", returnType = Boolean.class,
43 doc = "Literal for the boolean false.")
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000044 private static final Boolean FALSE = false;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000045
John Caterd7928422017-01-03 20:06:59 +000046 /** There should be only one instance of this type to allow "== None" tests. */
47 @SkylarkModule(
48 name = "NoneType",
49 documented = false,
50 doc = "Unit type, containing the unique value None."
51 )
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000052 @Immutable
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +000053 public static final class NoneType implements SkylarkValue {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000054 private NoneType() {}
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +000055
56 @Override
57 public String toString() {
58 return "None";
59 }
60
61 @Override
62 public boolean isImmutable() {
63 return true;
64 }
65
66 @Override
vladmos46907932017-06-30 14:01:45 +020067 public void repr(SkylarkPrinter printer) {
68 printer.append("None");
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +000069 }
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000070 }
71
Francois-Rene Rideau432d7152016-02-18 16:33:03 +000072 /** Marker for unbound variables in cases where neither Java null nor Skylark None is suitable. */
73 @Immutable
74 public static final class UnboundMarker implements SkylarkValue {
75 private UnboundMarker() {}
76
77 @Override
78 public String toString() {
79 return "<unbound>";
80 }
81
82 @Override
83 public boolean isImmutable() {
84 return true;
85 }
86
87 @Override
vladmos46907932017-06-30 14:01:45 +020088 public void repr(SkylarkPrinter printer) {
89 printer.append("<unbound>");
Francois-Rene Rideau432d7152016-02-18 16:33:03 +000090 }
91 }
92
93 @SkylarkSignature(name = "<unbound>", returnType = UnboundMarker.class, documented = false,
94 doc = "Marker for unbound values in cases where neither Skylark None nor Java null can do.")
95 public static final UnboundMarker UNBOUND = new UnboundMarker();
96
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000097 @SkylarkSignature(name = "None", returnType = NoneType.class,
98 doc = "Literal for the None value.")
99 public static final NoneType NONE = new NoneType();
100
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000101 @SkylarkSignature(name = "PACKAGE_NAME", returnType = String.class,
laurentlb286f1e92017-07-19 14:53:15 +0200102 doc = "<b>Deprecated. Use <a href=\"native.html#package_name\">package_name()</a> "
103 + "instead.</b> The name of the package being evaluated. "
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000104 + "For example, in the BUILD file <code>some/package/BUILD</code>, its value "
105 + "will be <code>some/package</code>. "
Laurent Le Brun053966c2017-02-14 22:02:04 +0000106 + "If the BUILD file calls a function defined in a .bzl file, PACKAGE_NAME will "
107 + "match the caller BUILD file package. "
108 + "In .bzl files, do not access PACKAGE_NAME at the file-level (outside of functions), "
109 + "either directly or by calling a function at the file-level that accesses "
110 + "PACKAGE_NAME (PACKAGE_NAME is only defined during BUILD file evaluation)."
111 + "Here is an example of a .bzl file:<br>"
112 + "<pre class=language-python>"
113 + "# a = PACKAGE_NAME # not allowed outside functions\n"
114 + "def extension():\n"
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000115 + " return PACKAGE_NAME</pre>"
Laurent Le Brun053966c2017-02-14 22:02:04 +0000116 + "In this case, <code>extension()</code> can be called from a BUILD file (even "
117 + "indirectly), but not in a file-level expression in the .bzl file. "
Laurent Le Brun66a2d3a2017-03-08 19:19:28 +0000118 + "When implementing a rule, use <a href=\"ctx.html#label\">ctx.label</a> to know where "
Laurent Le Brun053966c2017-02-14 22:02:04 +0000119 + "the rule comes from. ")
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000120 public static final String PKG_NAME = "PACKAGE_NAME";
121
Rodrigo Queiro07b9c6f2017-12-05 01:10:53 -0800122 @SkylarkSignature(
123 name = "REPOSITORY_NAME",
124 returnType = String.class,
125 doc =
126 "<b>Deprecated. Use <a href=\"native.html#repository_name\">repository_name()</a> "
127 + "instead.</b> The name of the repository the rule or build extension is called from. "
128 + "For example, in packages that are called into existence by the WORKSPACE stanza "
129 + "<code>local_repository(name='local', path=...)</code> it will be set to "
130 + "<code>@local</code>. In packages in the main repository, it will be set to "
131 + "<code>@</code>. It can only be accessed in functions (transitively) called from "
132 + "BUILD files, i.e. it follows the same restrictions as "
133 + "<a href=\"#PACKAGE_NAME\">PACKAGE_NAME</a>"
134 )
Lukacs Berki5d9b8a02015-10-21 12:36:56 +0000135 public static final String REPOSITORY_NAME = "REPOSITORY_NAME";
136
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000137 /**
138 * Set up a given environment for supported class methods.
139 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000140 static Environment setupConstants(Environment env) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000141 // In Python 2.x, True and False are global values and can be redefined by the user.
142 // In Python 3.x, they are keywords. We implement them as values, for the sake of
143 // simplicity. We define them as Boolean objects.
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000144 return env.setup("False", FALSE).setup("True", TRUE).setup("None", NONE);
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000145 }
146
147
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000148 /**
brandjondc2c5502017-12-07 14:30:04 -0800149 * Returns the canonical class representing the namespace associated with the given class, i.e.,
150 * the class under which builtins should be registered.
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000151 */
brandjondc2c5502017-12-07 14:30:04 -0800152 public static Class<?> getSkylarkNamespace(Class<?> clazz) {
153 return String.class.isAssignableFrom(clazz)
154 ? MethodLibrary.StringModule.class
155 : EvalUtils.getSkylarkType(clazz);
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000156 }
157
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000158 /**
brandjondc2c5502017-12-07 14:30:04 -0800159 * A registry of builtins, including both global builtins and builtins that are under some
160 * namespace.
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000161 *
brandjondc2c5502017-12-07 14:30:04 -0800162 * <p>This object is unsynchronized, but concurrent reads are fine.
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000163 */
brandjondc2c5502017-12-07 14:30:04 -0800164 public static class BuiltinRegistry {
165
166 /**
167 * All registered builtins, keyed and sorted by an identifying (but otherwise unimportant)
168 * string.
169 *
170 * <p>The string is typically formed from the builtin's simple name and the Java class in which
171 * it is defined. The Java class need not correspond to a namespace. (This map includes global
172 * builtins that have no namespace.)
173 */
174 private final Map<String, Object> allBuiltins = new TreeMap<>();
175
176 /** All non-global builtin functions, keyed by their namespace class and their name. */
177 private final Map<Class<?>, Map<String, BaseFunction>> functions = new HashMap<>();
178
179 /** Registers a builtin with the given simple name, that was defined in the given Java class. */
180 public void registerBuiltin(Class<?> definingClass, String name, Object builtin) {
181 String key = String.format("%s.%s", definingClass.getName(), name);
182 Preconditions.checkArgument(
183 !allBuiltins.containsKey(key),
184 "Builtin '%s' registered multiple times",
185 key);
186 allBuiltins.put(key, builtin);
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000187 }
brandjondc2c5502017-12-07 14:30:04 -0800188
189 /**
190 * Registers a function underneath a namespace.
191 *
192 * <p>This is independent of {@link #registerBuiltin}.
193 */
194 public void registerFunction(Class<?> namespace, BaseFunction function) {
195 Preconditions.checkNotNull(namespace);
196 Preconditions.checkNotNull(function.getObjectType());
197 Class<?> skylarkNamespace = getSkylarkNamespace(namespace);
198 Preconditions.checkArgument(skylarkNamespace.equals(namespace));
199 Class<?> objType = getSkylarkNamespace(function.getObjectType());
200 Preconditions.checkArgument(objType.equals(skylarkNamespace));
201
202 functions.computeIfAbsent(namespace, k -> new HashMap<>());
203 functions.get(namespace).put(function.getName(), function);
204 }
205
206 /** Returns a set of all registered builtins, in a deterministic order. */
207 public ImmutableSet<Object> getBuiltins() {
208 return ImmutableSet.copyOf(allBuiltins.values());
209 }
210
211 @Nullable
212 private Map<String, BaseFunction> getFunctionsInNamespace(Class<?> namespace) {
213 return functions.get(getSkylarkNamespace(namespace));
214 }
215
216 /**
217 * Given a namespace, returns the function with the given name.
218 *
219 * <p>If the namespace does not exist or has no function with that name, returns null.
220 */
221 public BaseFunction getFunction(Class<?> namespace, String name) {
222 Map<String, BaseFunction> namespaceFunctions = getFunctionsInNamespace(namespace);
223 return namespaceFunctions != null ? namespaceFunctions.get(name) : null;
224 }
225
226 /**
227 * Given a namespace, returns all function names.
228 *
229 * <p>If the namespace does not exist, returns an empty set.
230 */
231 public ImmutableSet<String> getFunctionNames(Class<?> namespace) {
232 Map<String, BaseFunction> namespaceFunctions = getFunctionsInNamespace(namespace);
233 if (namespaceFunctions == null) {
234 return ImmutableSet.of();
235 }
236 return ImmutableSet.copyOf(namespaceFunctions.keySet());
237 }
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000238 }
239
brandjondc2c5502017-12-07 14:30:04 -0800240 /**
241 * All Skylark builtins.
242 *
243 * <p>Note that just because a symbol is registered here does not necessarily mean that it is
244 * accessible in a particular {@link Environment}. This registry should include any builtin that
245 * is available in any environment.
246 *
247 * <p>Thread safety: This object is unsynchronized. The register functions are typically called
248 * from within static initializer blocks, which should be fine.
249 */
250 private static final BuiltinRegistry builtins = new BuiltinRegistry();
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000251
brandjondc2c5502017-12-07 14:30:04 -0800252 /** Retrieve the static instance containing information on all known Skylark builtins. */
253 public static BuiltinRegistry getBuiltinRegistry() {
254 return builtins;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000255 }
256
257 /**
258 * Registers global fields with SkylarkSignature into the specified Environment.
259 * @param env the Environment into which to register fields.
260 * @param moduleClass the Class object containing globals.
261 */
brandjondc2c5502017-12-07 14:30:04 -0800262 public static void setupModuleGlobals(Environment env, Class<?> moduleClass) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000263 try {
264 if (moduleClass.isAnnotationPresent(SkylarkModule.class)) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000265 env.setup(
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000266 moduleClass.getAnnotation(SkylarkModule.class).name(),
267 moduleClass.getConstructor().newInstance());
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000268 }
269 for (Field field : moduleClass.getDeclaredFields()) {
270 if (field.isAnnotationPresent(SkylarkSignature.class)) {
271 // Fields in Skylark modules are sometimes private.
272 // Nevertheless they have to be annotated with SkylarkSignature.
273 field.setAccessible(true);
274 SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class);
275 Object value = field.get(null);
276 // Ignore function factories and non-global functions
277 if (!(value instanceof BuiltinFunction.Factory
278 || (value instanceof BaseFunction
279 && !annotation.objectType().equals(Object.class)))) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000280 env.setup(annotation.name(), value);
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000281 }
282 }
283 }
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000284 } catch (ReflectiveOperationException e) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000285 throw new AssertionError(e);
286 }
287 }
288
289 /**
brandjondc2c5502017-12-07 14:30:04 -0800290 * Registers global fields with SkylarkSignature into the specified Environment. Alias for
291 * {@link #setupModuleGlobals}.
292 *
293 * @deprecated Use {@link #setupModuleGlobals} instead.
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000294 */
brandjondc2c5502017-12-07 14:30:04 -0800295 @Deprecated
296 // TODO(bazel-team): Remove after all callers updated.
297 public static void registerModuleGlobals(Environment env, Class<?> moduleClass) {
298 setupModuleGlobals(env, moduleClass);
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000299 }
300
301 static void setupMethodEnvironment(
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000302 Environment env, Iterable<BaseFunction> functions) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000303 for (BaseFunction function : functions) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000304 env.setup(function.getName(), function);
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000305 }
306 }
307}