Update from Google.
--
MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
new file mode 100644
index 0000000..a148a70
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
@@ -0,0 +1,345 @@
+// 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.toString());
+ }
+ 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();
+ }
+}