blob: 1bfba3a5d734ec57f16f4bdfb91a73563a71c8ec [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
2//
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
17import com.google.common.annotations.VisibleForTesting;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000018import com.google.common.base.Joiner;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.common.base.Preconditions;
20import com.google.common.collect.ImmutableList;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000021import com.google.common.collect.ImmutableMap;
22import com.google.devtools.build.lib.cmdline.PackageIdentifier;
23import com.google.devtools.build.lib.events.Event;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.events.EventHandler;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000025import com.google.devtools.build.lib.events.EventKind;
Francois-Rene Rideau3a8ddcc2015-08-26 22:30:38 +000026import com.google.devtools.build.lib.events.Location;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000027import com.google.devtools.build.lib.packages.CachingPackageLocator;
28import com.google.devtools.build.lib.syntax.Mutability.Freezable;
29import com.google.devtools.build.lib.syntax.Mutability.MutabilityException;
30import com.google.devtools.build.lib.util.Fingerprint;
31import com.google.devtools.build.lib.util.Pair;
32import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import com.google.devtools.build.lib.vfs.PathFragment;
34
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000035import java.io.Serializable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import java.util.HashMap;
37import java.util.HashSet;
38import java.util.List;
39import java.util.Map;
40import java.util.Set;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000041import java.util.TreeSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042
43import javax.annotation.Nullable;
44
45/**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000046 * An Environment is the main entry point to evaluating code in the BUILD language or Skylark.
47 * It embodies all the state that is required to evaluate such code,
48 * except for the current instruction pointer, which is an {@link ASTNode}
49 * whose {@link Statement#exec exec} or {@link Expression#eval eval} method is invoked with
50 * this Environment, in a straightforward direct-style AST-walking interpreter.
51 * {@link Continuation}-s are explicitly represented, but only partly, with another part being
52 * implicit in a series of try-catch statements, to maintain the direct style. One notable trick
53 * is how a {@link UserDefinedFunction} implements returning values as the function catching a
54 * {@link ReturnStatement.ReturnException} thrown by a {@link ReturnStatement} in the body.
55 *
56 * <p>Every Environment has a {@link Mutability} field, and must be used within a function that
57 * creates and closes this {@link Mutability} with the try-with-resource pattern.
58 * This {@link Mutability} is also used when initializing mutable objects within that Environment;
59 * when closed at the end of the computation freezes the Environment and all those objects that
60 * then become forever immutable. The pattern enforces the discipline that there should be no
61 * dangling mutable Environment, or concurrency between interacting Environment-s.
62 * It is also an error to try to mutate an Environment and its objects from another Environment,
63 * before the {@link Mutability} is closed.
64 *
65 * <p>One creates an Environment using the {@link #builder} function, then
66 * populates it with {@link #setup}, {@link #setupDynamic} and sometimes {@link #setupOverride},
67 * before to evaluate code in it with {@link #eval}, or with {@link BuildFileAST#exec}
68 * (where the AST was obtained by passing a {@link ValidationEnvironment} constructed from the
69 * Environment to {@link BuildFileAST#parseBuildFile} or {@link BuildFileAST#parseSkylarkFile}).
70 * When the computation is over, the frozen Environment can still be queried with {@link #lookup}.
71 *
72 * <p>Final fields of an Environment represent its dynamic state, i.e. state that remains the same
73 * throughout a given evaluation context, and don't change with source code location,
74 * while mutable fields embody its static state, that change with source code location.
75 * The seeming paradox is that the words "dynamic" and "static" refer to the point of view
76 * of the source code, and here we have a dual point of view.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010077 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000078public final class Environment implements Freezable {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010080 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000081 * A Frame is a Map of bindings, plus a {@link Mutability} and a parent Frame
82 * from which to inherit bindings.
83 *
84 * <p>A Frame contains bindings mapping variable name to variable value in a given scope.
85 * It may also inherit bindings from a parent Frame corresponding to a parent scope,
86 * which in turn may inherit bindings from its own parent, etc., transitively.
87 * Bindings may shadow bindings from the parent. In Skylark, you may only mutate
88 * bindings from the current Frame, which always got its {@link Mutability} with the
89 * current {@link Environment}; but future extensions may make it more like Python
90 * and allow mutation of bindings in outer Frame-s (or then again may not).
91 *
92 * <p>A Frame inherits the {@link Mutability} from the {@link Environment} in which it was
93 * originally created. When that {@link Environment} is finalized and its {@link Mutability}
94 * is closed, it becomes immutable, including the Frame, which can be shared in other
95 * {@link Environment}-s. Indeed, a {@link UserDefinedFunction} will close over the global
96 * Frame of its definition {@link Environment}, which will thus be reused (immutably)
97 * in all any {@link Environment} in which this function is called, so it's important to
98 * preserve the {@link Mutability} to make sure no Frame is modified after it's been finalized.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010099 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000100 public static final class Frame implements Freezable {
101
102 private final Mutability mutability;
103 final Frame parent;
104 final Map<String, Object> bindings = new HashMap<>();
105
106 Frame(Mutability mutability, Frame parent) {
107 this.mutability = mutability;
108 this.parent = parent;
109 }
110
111 @Override
112 public final Mutability mutability() {
113 return mutability;
114 }
115
116 /**
117 * Gets a binding from the current frame or if not found its parent.
118 * @param varname the name of the variable to be bound
119 * @return the value bound to variable
120 */
121 public Object get(String varname) {
122 if (bindings.containsKey(varname)) {
123 return bindings.get(varname);
124 }
125 if (parent != null) {
126 return parent.get(varname);
127 }
128 return null;
129 }
130
131 /**
132 * Modifies a binding in the current Frame.
133 * Does not try to modify an inherited binding.
134 * This will shadow any inherited binding, which may be an error
135 * that you want to guard against before calling this function.
136 * @param env the Environment attempting the mutation
137 * @param varname the name of the variable to be bound
138 * @param value the value to bind to the variable
139 */
140 public void put(Environment env, String varname, Object value)
141 throws MutabilityException {
142 Mutability.checkMutable(this, env);
143 bindings.put(varname, value);
144 }
145
146 /**
147 * Adds the variable names of this Frame and its transitive parents to the given set.
148 * This provides a O(n) way of extracting the list of all variables visible in an Environment.
149 * @param vars the set of visible variables in the Environment, being computed.
150 */
151 public void addVariableNamesTo(Set<String> vars) {
152 vars.addAll(bindings.keySet());
153 if (parent != null) {
154 parent.addVariableNamesTo(vars);
155 }
156 }
157
158 public Set<String> getDirectVariableNames() {
159 return bindings.keySet();
160 }
161
162 @Override
163 public String toString() {
164 String prefix = "Frame";
165 StringBuilder sb = new StringBuilder();
166 for (Frame f = this; f != null; f = f.parent) {
167 Printer.formatTo(sb, "%s%s%r",
168 ImmutableList.<Object>of(prefix, f.mutability(), f.bindings));
169 prefix = "=>";
170 }
171 return sb.toString();
172 }
173 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100174
175 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000176 * A Continuation contains data saved during a function call and restored when the function exits.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100177 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000178 private static final class Continuation {
179 /** The {@link BaseFunction} being evaluated that will return into this Continuation. */
180 BaseFunction function;
181
182 /** The {@link FuncallExpression} to which this Continuation will return. */
183 FuncallExpression caller;
184
185 /** The next Continuation after this Continuation. */
186 @Nullable Continuation continuation;
187
188 /** The lexical Frame of the caller. */
189 Frame lexicalFrame;
190
191 /** The global Frame of the caller. */
192 Frame globalFrame;
193
194 /** The set of known global variables of the caller. */
195 @Nullable Set<String> knownGlobalVariables;
196
197 /** Whether the caller is in Skylark mode. */
198 boolean isSkylark;
199
200 Continuation(
201 Continuation continuation,
202 BaseFunction function,
203 FuncallExpression caller,
204 Frame lexicalFrame,
205 Frame globalFrame,
206 Set<String> knownGlobalVariables,
207 boolean isSkylark) {
208 this.continuation = continuation;
209 this.function = function;
210 this.caller = caller;
211 this.lexicalFrame = lexicalFrame;
212 this.globalFrame = globalFrame;
213 this.isSkylark = isSkylark;
214 }
215 }
216
217 // TODO(bazel-team): Fix this scary failure of serializability.
218 // skyframe.SkylarkImportLookupFunction processes a .bzl and returns an Extension,
219 // for use by whoever imports the .bzl file. Skyframe may subsequently serialize the results.
220 // And it will fail to process these bindings, because they are inherited from a non-serializable
221 // class (in previous versions of the code the serializable SkylarkEnvironment was inheriting
222 // from the non-serializable Environment and being returned by said Function).
223 // If we try to merge this otherwise redundant superclass into Extension, though,
224 // skyframe experiences a massive failure to serialize things, and it's unclear how far
225 // reaching the need to make things Serializable goes, though clearly we'll need to make
226 // a whole lot of things Serializable, and for efficiency, we'll want to implement sharing
227 // of imported values rather than a code explosion.
228 private static class BaseExtension {
229 final ImmutableMap<String, Object> bindings;
230 BaseExtension(Environment env) {
231 this.bindings = ImmutableMap.copyOf(env.globalFrame.bindings);
232 }
233 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100234
235 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000236 * An Extension to be imported with load() into a BUILD or .bzl file.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100237 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000238 public static final class Extension extends BaseExtension implements Serializable {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100239
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000240 private final String transitiveContentHashCode;
241
242 /**
243 * Constructs an Extension by extracting the new global definitions from an Environment.
244 * Also caches a hash code for the transitive content of the file and its dependencies.
245 * @param env the Environment from which to extract an Extension.
246 */
247 public Extension(Environment env) {
248 super(env);
249 this.transitiveContentHashCode = env.getTransitiveContentHashCode();
250 }
251
252 @VisibleForTesting // This is only used in one test.
253 public String getTransitiveContentHashCode() {
254 return transitiveContentHashCode;
255 }
256
257 /** get the value bound to a variable in this Extension */
258 public Object get(String varname) {
259 return bindings.get(varname);
260 }
261
262 /** does this Extension contain a binding for the named variable? */
263 public boolean containsKey(String varname) {
264 return bindings.containsKey(varname);
265 }
266 }
267
268 /**
269 * Static Frame for lexical variables that are always looked up in the current Environment
270 * or for the definition Environment of the function currently being evaluated.
271 */
272 private Frame lexicalFrame;
273
274 /**
275 * Static Frame for global variables; either the current lexical Frame if evaluation is currently
276 * happening at the global scope of a BUILD file, or the global Frame at the time of function
277 * definition if evaluation is currently happening in the body of a function. Thus functions can
278 * close over other functions defined in the same file.
279 */
280 private Frame globalFrame;
281
282 /**
283 * Dynamic Frame for variables that are always looked up in the runtime Environment,
284 * and never in the lexical or "global" Environment as it was at the time of function definition.
285 * For instance, PACKAGE_NAME.
286 */
287 private final Frame dynamicFrame;
288
289 /**
290 * An EventHandler for errors and warnings. This is not used in the BUILD language,
291 * however it might be used in Skylark code called from the BUILD language, so shouldn't be null.
292 */
293 private final EventHandler eventHandler;
294
295 /**
296 * For each imported extensions, a global Skylark frame from which to load() individual bindings.
297 */
298 private final Map<PathFragment, Extension> importedExtensions;
299
300 /**
301 * Is this Environment being executed in Skylark context?
302 */
303 private boolean isSkylark;
304
305 /**
306 * Is this Environment being executed during the loading phase?
307 * Many builtin functions are only enabled during the loading phase, and check this flag.
308 */
309 private final boolean isLoadingPhase;
310
311 /**
312 * When in a lexical (Skylark) Frame, this set contains the variable names that are global,
313 * as determined not by global declarations (not currently supported),
314 * but by previous lookups that ended being global or dynamic.
315 * This is necessary because if in a function definition something
316 * reads a global variable after which a local variable with the same name is assigned an
317 * Exception needs to be thrown.
318 */
319 @Nullable private Set<String> knownGlobalVariables;
320
321 /**
322 * When in a lexical (Skylark) frame, this lists the names of the functions in the call stack.
323 * We currently use it to artificially disable recursion.
324 */
325 @Nullable private Continuation continuation;
326
327 /**
328 * Enters a scope by saving state to a new Continuation
329 * @param function the function whose scope to enter
330 * @param caller the source AST node for the caller
331 * @param globals the global Frame that this function closes over from its definition Environment
332 */
333 void enterScope(BaseFunction function, FuncallExpression caller, Frame globals) {
334 continuation = new Continuation(
335 continuation, function, caller, lexicalFrame, globalFrame, knownGlobalVariables, isSkylark);
336 lexicalFrame = new Frame(mutability(), null);
337 globalFrame = globals;
338 knownGlobalVariables = new HashSet<String>();
339 isSkylark = true;
340 }
341
342 /**
343 * Exits a scope by restoring state from the current continuation
344 */
345 void exitScope() {
346 Preconditions.checkNotNull(continuation);
347 lexicalFrame = continuation.lexicalFrame;
348 globalFrame = continuation.globalFrame;
349 knownGlobalVariables = continuation.knownGlobalVariables;
350 isSkylark = continuation.isSkylark;
351 continuation = continuation.continuation;
352 }
353
354 /**
355 * When evaluating code from a file, this contains a hash of the file.
356 */
357 @Nullable private String fileContentHashCode;
Francois-Rene Rideau3a8ddcc2015-08-26 22:30:38 +0000358
359 /**
360 * Is this Environment being evaluated during the loading phase?
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000361 * This is fixed during Environment setup, and enables various functions
Francois-Rene Rideau3a8ddcc2015-08-26 22:30:38 +0000362 * that are not available during the analysis phase.
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000363 * @return true if this Environment corresponds to code during the loading phase.
Francois-Rene Rideau3a8ddcc2015-08-26 22:30:38 +0000364 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000365 private boolean isLoadingPhase() {
Francois-Rene Rideau3a8ddcc2015-08-26 22:30:38 +0000366 return isLoadingPhase;
367 }
368
369 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000370 * Checks that the current Environment is in the loading phase.
Francois-Rene Rideau3a8ddcc2015-08-26 22:30:38 +0000371 * @param symbol name of the function being only authorized thus.
372 */
373 public void checkLoadingPhase(String symbol, Location loc) throws EvalException {
374 if (!isLoadingPhase()) {
375 throw new EvalException(loc, symbol + "() can only be called during the loading phase");
376 }
377 }
378
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100379 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000380 * Is this a global Environment?
381 * @return true if the current code is being executed at the top-level,
382 * as opposed to inside the body of a function.
Francois-Rene Rideau6e7160d2015-08-26 17:22:35 +0000383 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000384 boolean isGlobal() {
385 return lexicalFrame == null;
Francois-Rene Rideau6e7160d2015-08-26 17:22:35 +0000386 }
387
388 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000389 * Is the current code Skylark?
390 * @return true if Skylark was enabled when this code was read.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100391 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000392 // TODO(bazel-team): Delete this function.
393 // This function is currently used in various functions that change their behavior with respect to
394 // lists depending on the Skylark-ness of the code; lists should be unified between the two modes.
395 boolean isSkylark() {
396 return isSkylark;
Florian Weikert6a663392015-09-02 14:04:33 +0000397 }
398
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100399 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000400 * Is the caller of the current function executing Skylark code?
401 * @return true if this is skylark was enabled when this code was read.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100402 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000403 // TODO(bazel-team): Delete this function.
404 // This function is currently used by MethodLibrary to modify behavior of lists
405 // depending on the Skylark-ness of the code; lists should be unified between the two modes.
406 boolean isCallerSkylark() {
407 return continuation.isSkylark;
Florian Weikert6a663392015-09-02 14:04:33 +0000408 }
409
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000410 @Override
411 public Mutability mutability() {
412 // the mutability of the environment is that of its dynamic frame.
413 return dynamicFrame.mutability();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100414 }
415
416 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000417 * @return the current Frame, in which variable side-effects happen.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100418 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000419 private Frame currentFrame() {
420 return isGlobal() ? globalFrame : lexicalFrame;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100421 }
422
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000423 /**
424 * @return the global variables for the Environment (not including dynamic bindings).
425 */
426 public Frame getGlobals() {
427 return globalFrame;
428 }
429
430 /**
431 * Returns an EventHandler for errors and warnings.
432 * The BUILD language doesn't use it directly, but can call Skylark code that does use it.
433 * @return an EventHandler
434 */
Francois-Rene Rideaue6a46b82015-04-10 18:31:43 +0000435 public EventHandler getEventHandler() {
436 return eventHandler;
437 }
438
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000439 /**
440 * @return the current stack trace as a list of functions.
441 */
442 public ImmutableList<BaseFunction> getStackTrace() {
443 ImmutableList.Builder<BaseFunction> builder = new ImmutableList.Builder<>();
444 for (Continuation k = continuation; k != null; k = k.continuation) {
445 builder.add(k.function);
446 }
447 return builder.build().reverse();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100448 }
449
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000450
451 /**
452 * Returns the FuncallExpression and the BaseFunction for the top-level call being evaluated.
453 */
454 public Pair<FuncallExpression, BaseFunction> getTopCall() {
455 Continuation continuation = this.continuation;
456 if (continuation == null) {
457 return null;
458 }
459 while (continuation.continuation != null) {
460 continuation = continuation.continuation;
461 }
462 return new Pair<>(continuation.caller, continuation.function);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100463 }
464
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000465 /**
466 * Constructs an Environment.
467 * This is the main, most basic constructor.
468 * @param globalFrame a frame for the global Environment
469 * @param dynamicFrame a frame for the dynamic Environment
470 * @param eventHandler an EventHandler for warnings, errors, etc
471 * @param importedExtensions Extension-s from which to import bindings with load()
472 * @param isSkylark true if in Skylark context
473 * @param fileContentHashCode a hash for the source file being evaluated, if any
474 * @param isLoadingPhase true if in loading phase
475 */
476 private Environment(
477 Frame globalFrame,
478 Frame dynamicFrame,
479 EventHandler eventHandler,
480 Map<PathFragment, Extension> importedExtensions,
481 boolean isSkylark,
482 @Nullable String fileContentHashCode,
483 boolean isLoadingPhase) {
484 this.globalFrame = Preconditions.checkNotNull(globalFrame);
485 this.dynamicFrame = Preconditions.checkNotNull(dynamicFrame);
486 Preconditions.checkArgument(globalFrame.mutability().isMutable());
487 Preconditions.checkArgument(dynamicFrame.mutability().isMutable());
488 this.eventHandler = eventHandler;
489 this.importedExtensions = importedExtensions;
490 this.isSkylark = isSkylark;
491 this.fileContentHashCode = fileContentHashCode;
492 this.isLoadingPhase = isLoadingPhase;
493 }
494
495 /**
496 * A Builder class for Environment
497 */
498 public static class Builder {
499 private final Mutability mutability;
500 private boolean isSkylark = false;
501 private boolean isLoadingPhase = false;
502 @Nullable private Frame parent;
503 @Nullable private EventHandler eventHandler;
504 @Nullable private Map<PathFragment, Extension> importedExtensions;
505 @Nullable private String fileContentHashCode;
506
507 Builder(Mutability mutability) {
508 this.mutability = mutability;
509 }
510
511 /** Enables Skylark for code read in this Environment. */
512 public Builder setSkylark() {
513 Preconditions.checkState(!isSkylark);
514 isSkylark = true;
515 return this;
516 }
517
518 /** Enables loading phase only functions in this Environment. */
519 public Builder setLoadingPhase() {
520 Preconditions.checkState(!isLoadingPhase);
521 isLoadingPhase = true;
522 return this;
523 }
524
525 /** Inherits global bindings from the given parent Frame. */
526 public Builder setGlobals(Frame parent) {
527 Preconditions.checkState(this.parent == null);
528 this.parent = parent;
529 return this;
530 }
531
532 /** Sets an EventHandler for errors and warnings. */
533 public Builder setEventHandler(EventHandler eventHandler) {
534 Preconditions.checkState(this.eventHandler == null);
535 this.eventHandler = eventHandler;
536 return this;
537 }
538
539 /** Declares imported extensions for load() statements. */
540 public Builder setImportedExtensions (Map<PathFragment, Extension> importedExtensions) {
541 Preconditions.checkState(this.importedExtensions == null);
542 this.importedExtensions = importedExtensions;
543 return this;
544 }
545
546 /** Declares content hash for the source file for this Environment. */
547 public Builder setFileContentHashCode(String fileContentHashCode) {
548 this.fileContentHashCode = fileContentHashCode;
549 return this;
550 }
551
552 /** Builds the Environment. */
553 public Environment build() {
554 Preconditions.checkArgument(mutability.isMutable());
555 if (parent != null) {
556 Preconditions.checkArgument(!parent.mutability().isMutable());
557 }
558 Frame globalFrame = new Frame(mutability, parent);
559 Frame dynamicFrame = new Frame(mutability, null);
560 if (importedExtensions == null) {
561 importedExtensions = ImmutableMap.of();
562 }
563 Environment env = new Environment(
564 globalFrame,
565 dynamicFrame,
566 eventHandler,
567 importedExtensions,
568 isSkylark,
569 fileContentHashCode,
570 isLoadingPhase);
571 return env;
572 }
573 }
574
575 public static Builder builder(Mutability mutability) {
576 return new Builder(mutability);
577 }
578
579 /**
580 * Sets a binding for a special dynamic variable in this Environment.
581 * This is not for end-users, and will throw an AssertionError in case of conflict.
582 * @param varname the name of the dynamic variable to be bound
583 * @param value a value to bind to the variable
584 * @return this Environment, in fluid style
585 */
586 public Environment setupDynamic(String varname, Object value) {
587 if (dynamicFrame.get(varname) != null) {
588 throw new AssertionError(
589 String.format("Trying to bind dynamic variable '%s' but it is already bound",
590 varname));
591 }
592 if (lexicalFrame != null && lexicalFrame.get(varname) != null) {
593 throw new AssertionError(
594 String.format("Trying to bind dynamic variable '%s' but it is already bound lexically",
595 varname));
596 }
597 if (globalFrame.get(varname) != null) {
598 throw new AssertionError(
599 String.format("Trying to bind dynamic variable '%s' but it is already bound globally",
600 varname));
601 }
602 try {
603 dynamicFrame.put(this, varname, value);
604 } catch (MutabilityException e) {
605 // End users don't have access to setupDynamic, and it is an implementation error
606 // if we encounter a mutability exception.
607 throw new AssertionError(
608 Printer.format(
609 "Trying to bind dynamic variable '%s' in frozen environment %r", varname, this),
610 e);
611 }
612 return this;
613 }
614
615
616 /**
617 * Modifies a binding in the current Frame of this Environment, as would an
618 * {@link AssignmentStatement}. Does not try to modify an inherited binding.
619 * This will shadow any inherited binding, which may be an error
620 * that you want to guard against before calling this function.
621 * @param varname the name of the variable to be bound
622 * @param value the value to bind to the variable
623 * @return this Environment, in fluid style
624 */
625 public Environment update(String varname, Object value) throws EvalException {
626 Preconditions.checkNotNull(value, "update(value == null)");
627 // prevents clashes between static and dynamic variables.
628 if (dynamicFrame.get(varname) != null) {
629 throw new EvalException(
630 null, String.format("Trying to update special read-only global variable '%s'", varname));
631 }
632 if (isKnownGlobalVariable(varname)) {
633 throw new EvalException(
634 null, String.format("Trying to update read-only global variable '%s'", varname));
635 }
636 try {
637 currentFrame().put(this, varname, Preconditions.checkNotNull(value));
638 } catch (MutabilityException e) {
639 // Note that since at this time we don't accept the global keyword, and don't have closures,
640 // end users should never be able to mutate a frozen Environment, and a MutabilityException
641 // is therefore a failed assertion for Bazel. However, it is possible to shadow a binding
642 // imported from a parent Environment by updating the current Environment, which will not
643 // trigger a MutabilityException.
644 throw new AssertionError(
645 Printer.format("Can't update %s to %r in frozen environment", varname, value),
646 e);
647 }
648 return this;
649 }
650
651 private boolean hasVariable(String varname) {
652 try {
653 lookup(varname);
654 return true;
655 } catch (NoSuchVariableException e) {
656 return false;
657 }
658 }
659
660 /**
661 * Initializes a binding in this Environment. It is an error if the variable is already bound.
662 * This is not for end-users, and will throw an AssertionError in case of conflict.
663 * @param varname the name of the variable to be bound
664 * @param value the value to bind to the variable
665 * @return this Environment, in fluid style
666 */
667 public Environment setup(String varname, Object value) {
668 if (hasVariable(varname)) {
669 throw new AssertionError(String.format("variable '%s' already bound", varname));
670 }
671 return setupOverride(varname, value);
672 }
673
674 /**
675 * Initializes a binding in this environment. Overrides any previous binding.
676 * This is not for end-users, and will throw an AssertionError in case of conflict.
677 * @param varname the name of the variable to be bound
678 * @param value the value to bind to the variable
679 * @return this Environment, in fluid style
680 */
681 public Environment setupOverride(String varname, Object value) {
682 try {
683 return update(varname, value);
684 } catch (EvalException ee) {
685 throw new AssertionError(ee);
686 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100687 }
688
689 /**
690 * @return the value from the environment whose name is "varname".
691 * @throws NoSuchVariableException if the variable is not defined in the Environment.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100692 */
693 public Object lookup(String varname) throws NoSuchVariableException {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000694 // Which Frame to lookup first doesn't matter because update prevents clashes.
695 if (lexicalFrame != null) {
696 Object lexicalValue = lexicalFrame.get(varname);
697 if (lexicalValue != null) {
698 return lexicalValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100699 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000700 }
701 Object globalValue = globalFrame.get(varname);
702 Object dynamicValue = dynamicFrame.get(varname);
703 if (globalValue == null && dynamicValue == null) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100704 throw new NoSuchVariableException(varname);
705 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000706 if (knownGlobalVariables != null) {
707 knownGlobalVariables.add(varname);
708 }
709 if (globalValue != null) {
710 return globalValue;
711 }
712 return dynamicValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100713 }
714
715 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000716 * Like {@link #lookup(String)}, but instead of throwing an exception in the case
717 * where <code>varname</code> is not defined, <code>defaultValue</code> is returned instead.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100718 */
719 public Object lookup(String varname, Object defaultValue) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000720 Preconditions.checkState(!isSkylark);
721 try {
722 return lookup(varname);
723 } catch (NoSuchVariableException e) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100724 return defaultValue;
725 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100726 }
727
728 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000729 * @return true if varname is a known global variable,
730 * because it has been read in the context of the current function.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100731 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000732 boolean isKnownGlobalVariable(String varname) {
733 return knownGlobalVariables != null && knownGlobalVariables.contains(varname);
734 }
735
736 public void handleEvent(Event event) {
737 eventHandler.handle(event);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100738 }
739
740 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000741 * @return the (immutable) set of names of all variables defined in this
742 * Environment. Exposed for testing.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100743 */
744 @VisibleForTesting
745 public Set<String> getVariableNames() {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000746 Set<String> vars = new HashSet<>();
747 if (lexicalFrame != null) {
748 lexicalFrame.addVariableNamesTo(vars);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100749 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000750 globalFrame.addVariableNamesTo(vars);
751 dynamicFrame.addVariableNamesTo(vars);
752 return vars;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100753 }
754
755 @Override
756 public int hashCode() {
757 throw new UnsupportedOperationException(); // avoid nondeterminism
758 }
759
760 @Override
761 public boolean equals(Object that) {
762 throw new UnsupportedOperationException();
763 }
764
765 @Override
766 public String toString() {
767 StringBuilder out = new StringBuilder();
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000768 out.append("Environment(lexicalFrame=");
769 out.append(lexicalFrame);
770 out.append(", globalFrame=");
771 out.append(globalFrame);
772 out.append(", dynamicFrame=");
773 out.append(dynamicFrame);
774 out.append(", eventHandler.getClass()=");
775 out.append(eventHandler.getClass());
776 out.append(", importedExtensions=");
777 out.append(importedExtensions);
778 out.append(", isSkylark=");
779 out.append(isSkylark);
780 out.append(", fileContentHashCode=");
781 out.append(fileContentHashCode);
782 out.append(", isLoadingPhase=");
783 out.append(isLoadingPhase);
784 out.append(")");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100785 return out.toString();
786 }
787
788 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000789 * An Exception thrown when an attempt is made to lookup a non-existent
790 * variable in the Environment.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100791 */
792 public static class NoSuchVariableException extends Exception {
793 NoSuchVariableException(String variable) {
794 super("no such variable: " + variable);
795 }
796 }
797
798 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000799 * An Exception thrown when an attempt is made to import a symbol from a file
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100800 * that was not properly loaded.
801 */
802 public static class LoadFailedException extends Exception {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000803 LoadFailedException(PathFragment extension) {
804 super(String.format("file '%s' was not correctly loaded. "
805 + "Make sure the 'load' statement appears in the global scope in your file",
806 extension));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100807 }
808 }
809
Florian Weikert9d659ad2015-07-23 14:44:36 +0000810 public void importSymbol(PathFragment extension, Identifier symbol, String nameInLoadedFile)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100811 throws NoSuchVariableException, LoadFailedException {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000812 Preconditions.checkState(isGlobal()); // loading is only allowed at global scope.
813
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100814 if (!importedExtensions.containsKey(extension)) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000815 throw new LoadFailedException(extension);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100816 }
Florian Weikert9d659ad2015-07-23 14:44:36 +0000817
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000818 Extension ext = importedExtensions.get(extension);
819
820 // TODO(bazel-team): Throw a LoadFailedException instead, with an appropriate message.
821 // Throwing a NoSuchVariableException is backward compatible, but backward.
822 if (!ext.containsKey(nameInLoadedFile)) {
823 throw new NoSuchVariableException(nameInLoadedFile);
824 }
825
826 Object value = ext.get(nameInLoadedFile);
827 // TODO(bazel-team): Unify data structures between Skylark and BUILD,
828 // and stop doing the conversions below:
829 if (!isSkylark) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100830 value = SkylarkType.convertFromSkylark(value);
831 }
Florian Weikert9d659ad2015-07-23 14:44:36 +0000832
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000833 try {
834 update(symbol.getName(), value);
835 } catch (EvalException e) {
836 throw new LoadFailedException(extension);
Francois-Rene Rideau5a94e592015-09-04 19:13:47 +0000837 }
Florian Weikert6a663392015-09-02 14:04:33 +0000838 }
839
840 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000841 * Returns a hash code calculated from the hash code of this Environment and the
842 * transitive closure of other Environments it loads.
Florian Weikert6a663392015-09-02 14:04:33 +0000843 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000844 public String getTransitiveContentHashCode() {
845 // Calculate a new hash from the hash of the loaded Extension-s.
846 Fingerprint fingerprint = new Fingerprint();
847 fingerprint.addString(Preconditions.checkNotNull(fileContentHashCode));
848 TreeSet<PathFragment> paths = new TreeSet<>(importedExtensions.keySet());
849 for (PathFragment path : paths) {
850 fingerprint.addString(importedExtensions.get(path).getTransitiveContentHashCode());
851 }
852 return fingerprint.hexDigestAndReset();
Janak Ramakrishnanb6e33bc2015-09-06 21:05:23 +0000853 }
854
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000855
856 /** A read-only Environment.Frame with global constants in it only */
857 public static final Frame CONSTANTS_ONLY = createConstantsGlobals();
858
859 /** A read-only Environment.Frame with initial globals for the BUILD language */
860 public static final Frame BUILD = createBuildGlobals();
861
862 /** A read-only Environment.Frame with initial globals for Skylark */
863 public static final Frame SKYLARK = createSkylarkGlobals();
864
865 private static Environment.Frame createConstantsGlobals() {
866 try (Mutability mutability = Mutability.create("CONSTANTS")) {
867 Environment env = Environment.builder(mutability).build();
868 Runtime.setupConstants(env);
869 return env.getGlobals();
870 }
871 }
872
873 private static Environment.Frame createBuildGlobals() {
874 try (Mutability mutability = Mutability.create("BUILD")) {
875 Environment env = Environment.builder(mutability).build();
876 Runtime.setupConstants(env);
877 Runtime.setupMethodEnvironment(env, MethodLibrary.buildGlobalFunctions);
878 return env.getGlobals();
879 }
880 }
881
882 private static Environment.Frame createSkylarkGlobals() {
883 try (Mutability mutability = Mutability.create("SKYLARK")) {
884 Environment env = Environment.builder(mutability).setSkylark().build();
885 Runtime.setupConstants(env);
886 Runtime.setupMethodEnvironment(env, MethodLibrary.skylarkGlobalFunctions);
887 return env.getGlobals();
888 }
889 }
890
891
892 /**
893 * The fail fast handler, which throws a AssertionError whenever an error or warning occurs.
894 */
895 public static final EventHandler FAIL_FAST_HANDLER = new EventHandler() {
896 @Override
897 public void handle(Event event) {
898 Preconditions.checkArgument(
899 !EventKind.ERRORS_AND_WARNINGS.contains(event.getKind()), event);
900 }
901 };
902
903 /** Mock package locator class */
904 private static final class EmptyPackageLocator implements CachingPackageLocator {
905 @Override
906 public Path getBuildFileForPackage(PackageIdentifier packageName) {
907 return null;
908 }
909 }
910
911 /** A mock package locator */
912 @VisibleForTesting
913 static final CachingPackageLocator EMPTY_PACKAGE_LOCATOR = new EmptyPackageLocator();
914
915 /**
916 * Creates a Lexer without a supporting file.
917 * @param input a list of lines of code
918 */
919 @VisibleForTesting
920 Lexer createLexer(String... input) {
921 return new Lexer(ParserInputSource.create(Joiner.on("\n").join(input), null),
922 eventHandler);
Francois-Rene Rideau5a94e592015-09-04 19:13:47 +0000923 }
924
925 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000926 * Parses some String input without a supporting file, returning statements and comments.
927 * @param input a list of lines of code
Francois-Rene Rideau5a94e592015-09-04 19:13:47 +0000928 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000929 @VisibleForTesting
930 Parser.ParseResult parseFileWithComments(String... input) {
931 return isSkylark
932 ? Parser.parseFileForSkylark(
933 createLexer(input),
934 eventHandler,
935 EMPTY_PACKAGE_LOCATOR,
936 new ValidationEnvironment(this))
937 : Parser.parseFile(
938 createLexer(input),
939 eventHandler,
940 EMPTY_PACKAGE_LOCATOR,
941 /*parsePython=*/false);
942 }
943
944 /**
945 * Parses some String input without a supporting file, returning statements only.
946 * @param input a list of lines of code
947 */
948 @VisibleForTesting
949 List<Statement> parseFile(String... input) {
950 return parseFileWithComments(input).statements;
951 }
952
953 /**
954 * Evaluates code some String input without a supporting file.
955 * @param input a list of lines of code to evaluate
956 * @return the value of the last statement if it's an Expression or else null
957 */
958 @Nullable public Object eval(String... input) throws EvalException, InterruptedException {
959 Object last = null;
960 for (Statement statement : parseFile(input)) {
961 if (statement instanceof ExpressionStatement) {
962 last = ((ExpressionStatement) statement).getExpression().eval(this);
963 } else {
964 statement.exec(this);
965 last = null;
Florian Weikert6a663392015-09-02 14:04:33 +0000966 }
967 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000968 return last;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100969 }
970}