| // Copyright 2014 Google Inc. 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.annotations.VisibleForTesting; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; | |
| import com.google.devtools.build.lib.events.EventHandler; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.HashMap; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import javax.annotation.Nullable; | |
| /** | |
| * The BUILD environment. | |
| */ | |
| public class Environment { | |
| @SkylarkBuiltin(name = "True", returnType = Boolean.class, doc = "Literal for the boolean true.") | |
| private static final Boolean TRUE = true; | |
| @SkylarkBuiltin(name = "False", returnType = Boolean.class, | |
| doc = "Literal for the boolean false.") | |
| private static final Boolean FALSE = false; | |
| @SkylarkBuiltin(name = "PACKAGE_NAME", returnType = String.class, | |
| doc = "The name of the package the rule or build extension is called from. " | |
| + "This variable is special, because its value comes from outside of the extension " | |
| + "module (it comes from the BUILD file), so it can only be accessed in functions " | |
| + "(transitively) called from BUILD files. For example:<br>" | |
| + "<pre class=language-python>def extension():\n" | |
| + " return PACKAGE_NAME</pre>" | |
| + "In this case calling <code>extension()</code> works from the BUILD file (if the " | |
| + "function is loaded), but not as a top level function call in the extension module.") | |
| public static final String PKG_NAME = "PACKAGE_NAME"; | |
| /** | |
| * There should be only one instance of this type to allow "== None" tests. | |
| */ | |
| @Immutable | |
| public static final class NoneType { | |
| @Override | |
| public String toString() { return "None"; } | |
| private NoneType() {} | |
| } | |
| @SkylarkBuiltin(name = "None", returnType = NoneType.class, doc = "Literal for the None value.") | |
| public static final NoneType NONE = new NoneType(); | |
| protected final Map<String, Object> env = new HashMap<>(); | |
| // Functions with namespaces. Works only in the global environment. | |
| protected final Map<Class<?>, Map<String, Function>> functions = new HashMap<>(); | |
| /** | |
| * The parent environment. For Skylark it's the global environment, | |
| * used for global read only variable lookup. | |
| */ | |
| protected final Environment parent; | |
| /** | |
| * Map from a Skylark extension to an environment, which contains all symbols defined in the | |
| * extension. | |
| */ | |
| protected Map<PathFragment, SkylarkEnvironment> importedExtensions; | |
| /** | |
| * A set of disable variables propagating through function calling. This is needed because | |
| * UserDefinedFunctions lock the definition Environment which should be immutable. | |
| */ | |
| protected Set<String> disabledVariables = new HashSet<>(); | |
| /** | |
| * A set of disable namespaces propagating through function calling. See disabledVariables. | |
| */ | |
| protected Set<Class<?>> disabledNameSpaces = new HashSet<>(); | |
| /** | |
| * A set of variables propagating through function calling. It's only used to call | |
| * native rules from Skylark build extensions. | |
| */ | |
| protected Set<String> propagatingVariables = new HashSet<>(); | |
| /** | |
| * An EventHandler for errors and warnings. This is not used in the BUILD language, | |
| * however it might be used in Skylark code called from the BUILD language. | |
| */ | |
| @Nullable protected EventHandler eventHandler; | |
| /** | |
| * Constructs an empty root non-Skylark environment. | |
| * The root environment is also the global environment. | |
| */ | |
| public Environment() { | |
| this.parent = null; | |
| this.importedExtensions = new HashMap<>(); | |
| setupGlobal(); | |
| } | |
| /** | |
| * Constructs an empty child environment. | |
| */ | |
| public Environment(Environment parent) { | |
| Preconditions.checkNotNull(parent); | |
| this.parent = parent; | |
| this.importedExtensions = new HashMap<>(); | |
| } | |
| /** | |
| * Constructs an empty child environment with an EventHandler. | |
| */ | |
| public Environment(Environment parent, EventHandler eventHandler) { | |
| this(parent); | |
| this.eventHandler = Preconditions.checkNotNull(eventHandler); | |
| } | |
| // Sets up the global environment | |
| private void setupGlobal() { | |
| // 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, for the sake of | |
| // simplicity. We define them as Boolean objects. | |
| env.put("False", FALSE); | |
| env.put("True", TRUE); | |
| env.put("None", NONE); | |
| } | |
| public boolean isSkylarkEnabled() { | |
| return false; | |
| } | |
| protected boolean hasVariable(String varname) { | |
| return env.containsKey(varname); | |
| } | |
| /** | |
| * @return the value from the environment whose name is "varname". | |
| * @throws NoSuchVariableException if the variable is not defined in the Environment. | |
| * | |
| */ | |
| public Object lookup(String varname) throws NoSuchVariableException { | |
| if (disabledVariables.contains(varname)) { | |
| throw new NoSuchVariableException(varname); | |
| } | |
| Object value = env.get(varname); | |
| if (value == null) { | |
| if (parent != null) { | |
| return parent.lookup(varname); | |
| } | |
| throw new NoSuchVariableException(varname); | |
| } | |
| return value; | |
| } | |
| /** | |
| * Like <code>lookup(String)</code>, but instead of throwing an exception in | |
| * the case where "varname" is not defined, "defaultValue" is returned instead. | |
| * | |
| */ | |
| public Object lookup(String varname, Object defaultValue) { | |
| Object value = env.get(varname); | |
| if (value == null) { | |
| if (parent != null) { | |
| return parent.lookup(varname, defaultValue); | |
| } | |
| return defaultValue; | |
| } | |
| return value; | |
| } | |
| /** | |
| * Updates the value of variable "varname" in the environment, corresponding | |
| * to an {@link AssignmentStatement}. | |
| */ | |
| public void update(String varname, Object value) { | |
| Preconditions.checkNotNull(value, "update(value == null)"); | |
| env.put(varname, value); | |
| } | |
| /** | |
| * Same as {@link #update}, but also marks the variable propagating, meaning it will | |
| * be present in the execution environment of a UserDefinedFunction called from this | |
| * Environment. Using this method is discouraged. | |
| */ | |
| public void updateAndPropagate(String varname, Object value) { | |
| update(varname, value); | |
| propagatingVariables.add(varname); | |
| } | |
| /** | |
| * Remove the variable from the environment, returning | |
| * any previous mapping (null if there was none). | |
| */ | |
| public Object remove(String varname) { | |
| return env.remove(varname); | |
| } | |
| /** | |
| * Returns the (immutable) set of names of all variables defined in this | |
| * environment. Exposed for testing; not very efficient! | |
| */ | |
| @VisibleForTesting | |
| public Set<String> getVariableNames() { | |
| if (parent == null) { | |
| return env.keySet(); | |
| } else { | |
| Set<String> vars = new HashSet<>(); | |
| vars.addAll(env.keySet()); | |
| vars.addAll(parent.getVariableNames()); | |
| return vars; | |
| } | |
| } | |
| @Override | |
| public int hashCode() { | |
| throw new UnsupportedOperationException(); // avoid nondeterminism | |
| } | |
| @Override | |
| public boolean equals(Object that) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public String toString() { | |
| StringBuilder out = new StringBuilder(); | |
| out.append("Environment{"); | |
| List<String> keys = new ArrayList<>(env.keySet()); | |
| Collections.sort(keys); | |
| for (String key : keys) { | |
| out.append(key).append(" -> ").append(env.get(key)).append(", "); | |
| } | |
| out.append("}"); | |
| if (parent != null) { | |
| out.append("=>"); | |
| out.append(parent); | |
| } | |
| return out.toString(); | |
| } | |
| /** | |
| * An exception thrown when an attempt is made to lookup a non-existent | |
| * variable in the environment. | |
| */ | |
| public static class NoSuchVariableException extends Exception { | |
| NoSuchVariableException(String variable) { | |
| super("no such variable: " + variable); | |
| } | |
| } | |
| /** | |
| * An exception thrown when an attempt is made to import a symbol from a file | |
| * that was not properly loaded. | |
| */ | |
| public static class LoadFailedException extends Exception { | |
| LoadFailedException(String file) { | |
| super("file '" + file + "' was not correctly loaded. Make sure the 'load' statement appears " | |
| + "in the global scope, in the BUILD file"); | |
| } | |
| } | |
| public void setImportedExtensions(Map<PathFragment, SkylarkEnvironment> importedExtensions) { | |
| this.importedExtensions = importedExtensions; | |
| } | |
| public void importSymbol(PathFragment extension, String symbol) | |
| throws NoSuchVariableException, LoadFailedException { | |
| if (!importedExtensions.containsKey(extension)) { | |
| throw new LoadFailedException(extension.toString()); | |
| } | |
| Object value = importedExtensions.get(extension).lookup(symbol); | |
| if (!isSkylarkEnabled()) { | |
| value = SkylarkType.convertFromSkylark(value); | |
| } | |
| update(symbol, value); | |
| } | |
| /** | |
| * Registers a function with namespace to this global environment. | |
| */ | |
| public void registerFunction(Class<?> nameSpace, String name, Function function) { | |
| Preconditions.checkArgument(parent == null); | |
| if (!functions.containsKey(nameSpace)) { | |
| functions.put(nameSpace, new HashMap<String, Function>()); | |
| } | |
| functions.get(nameSpace).put(name, function); | |
| } | |
| private Map<String, Function> getNamespaceFunctions(Class<?> nameSpace) { | |
| if (disabledNameSpaces.contains(nameSpace) | |
| || (parent != null && parent.disabledNameSpaces.contains(nameSpace))) { | |
| return null; | |
| } | |
| Environment topLevel = this; | |
| while (topLevel.parent != null) { | |
| topLevel = topLevel.parent; | |
| } | |
| return topLevel.functions.get(nameSpace); | |
| } | |
| /** | |
| * Returns the function of the namespace of the given name or null of it does not exists. | |
| */ | |
| public Function getFunction(Class<?> nameSpace, String name) { | |
| Map<String, Function> nameSpaceFunctions = getNamespaceFunctions(nameSpace); | |
| return nameSpaceFunctions != null ? nameSpaceFunctions.get(name) : null; | |
| } | |
| /** | |
| * Returns the function names registered with the namespace. | |
| */ | |
| public Set<String> getFunctionNames(Class<?> nameSpace) { | |
| Map<String, Function> nameSpaceFunctions = getNamespaceFunctions(nameSpace); | |
| return nameSpaceFunctions != null ? nameSpaceFunctions.keySet() : ImmutableSet.<String>of(); | |
| } | |
| /** | |
| * Return the current stack trace (list of function names). | |
| */ | |
| public ImmutableList<String> getStackTrace() { | |
| // Empty list, since this environment does not allow function definition | |
| // (see SkylarkEnvironment) | |
| return ImmutableList.of(); | |
| } | |
| } |