aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java253
1 files changed, 253 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java
new file mode 100644
index 0000000000..7e6f4140a4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java
@@ -0,0 +1,253 @@
+// 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.Iterables;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * The environment for Skylark.
+ */
+public class SkylarkEnvironment extends Environment {
+
+ /**
+ * This set contains the variable names of all the successful lookups from the global
+ * environment. This is necessary because if in a function definition something
+ * reads a global variable after which a local variable with the same name is assigned an
+ * Exception needs to be thrown.
+ */
+ private final Set<String> readGlobalVariables = new HashSet<>();
+
+ private ImmutableList<String> stackTrace;
+
+ @Nullable private String fileContentHashCode;
+
+ /**
+ * Creates a Skylark Environment for function calling, from the global Environment of the
+ * caller Environment (which must be a Skylark Environment).
+ */
+ public static SkylarkEnvironment createEnvironmentForFunctionCalling(
+ Environment callerEnv, SkylarkEnvironment definitionEnv,
+ UserDefinedFunction function) throws EvalException {
+ if (callerEnv.getStackTrace().contains(function.getName())) {
+ throw new EvalException(function.getLocation(), "Recursion was detected when calling '"
+ + function.getName() + "' from '" + Iterables.getLast(callerEnv.getStackTrace()) + "'");
+ }
+ ImmutableList<String> stackTrace = new ImmutableList.Builder<String>()
+ .addAll(callerEnv.getStackTrace())
+ .add(function.getName())
+ .build();
+ SkylarkEnvironment childEnv =
+ // Always use the caller Environment's EventHandler. We cannot assume that the
+ // definition Environment's EventHandler is still working properly.
+ new SkylarkEnvironment(definitionEnv, stackTrace, callerEnv.eventHandler);
+ try {
+ for (String varname : callerEnv.propagatingVariables) {
+ childEnv.updateAndPropagate(varname, callerEnv.lookup(varname));
+ }
+ } catch (NoSuchVariableException e) {
+ // This should never happen.
+ throw new IllegalStateException(e);
+ }
+ childEnv.disabledVariables = callerEnv.disabledVariables;
+ childEnv.disabledNameSpaces = callerEnv.disabledNameSpaces;
+ return childEnv;
+ }
+
+ private SkylarkEnvironment(SkylarkEnvironment definitionEnv, ImmutableList<String> stackTrace,
+ EventHandler eventHandler) {
+ super(definitionEnv.getGlobalEnvironment());
+ this.stackTrace = stackTrace;
+ this.eventHandler = Preconditions.checkNotNull(eventHandler,
+ "EventHandler cannot be null in an Environment which calls into Skylark");
+ }
+
+ /**
+ * Creates a global SkylarkEnvironment.
+ */
+ public SkylarkEnvironment(EventHandler eventHandler, String astFileContentHashCode) {
+ super();
+ stackTrace = ImmutableList.of();
+ this.eventHandler = eventHandler;
+ this.fileContentHashCode = astFileContentHashCode;
+ }
+
+ @VisibleForTesting
+ public SkylarkEnvironment(EventHandler eventHandler) {
+ this(eventHandler, null);
+ }
+
+ public SkylarkEnvironment(SkylarkEnvironment globalEnv) {
+ super(globalEnv);
+ stackTrace = ImmutableList.of();
+ this.eventHandler = globalEnv.eventHandler;
+ }
+
+ @Override
+ public ImmutableList<String> getStackTrace() {
+ return stackTrace;
+ }
+
+ /**
+ * Clones this Skylark global environment.
+ */
+ public SkylarkEnvironment cloneEnv(EventHandler eventHandler) {
+ Preconditions.checkArgument(isGlobalEnvironment());
+ SkylarkEnvironment newEnv = new SkylarkEnvironment(eventHandler, this.fileContentHashCode);
+ for (Entry<String, Object> entry : env.entrySet()) {
+ newEnv.env.put(entry.getKey(), entry.getValue());
+ }
+ for (Map.Entry<Class<?>, Map<String, Function>> functionMap : functions.entrySet()) {
+ newEnv.functions.put(functionMap.getKey(), functionMap.getValue());
+ }
+ return newEnv;
+ }
+
+ /**
+ * Returns the global environment. Only works for Skylark environments. For the global Skylark
+ * environment this method returns this Environment.
+ */
+ public SkylarkEnvironment getGlobalEnvironment() {
+ // If there's a parent that's the global environment, otherwise this is.
+ return parent != null ? (SkylarkEnvironment) parent : this;
+ }
+
+ /**
+ * Returns true if this is a Skylark global environment.
+ */
+ public boolean isGlobalEnvironment() {
+ return parent == null;
+ }
+
+ /**
+ * Returns true if varname has been read as a global variable.
+ */
+ public boolean hasBeenReadGlobalVariable(String varname) {
+ return readGlobalVariables.contains(varname);
+ }
+
+ @Override
+ public boolean isSkylarkEnabled() {
+ return true;
+ }
+
+ /**
+ * @return the value from the environment whose name is "varname".
+ * @throws NoSuchVariableException if the variable is not defined in the environment.
+ */
+ @Override
+ 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 && parent.hasVariable(varname)) {
+ readGlobalVariables.add(varname);
+ 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.
+ */
+ @Override
+ public Object lookup(String varname, Object defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Updates the value of variable "varname" in the environment, corresponding
+ * to an AssignmentStatement.
+ */
+ @Override
+ public void update(String varname, Object value) {
+ Preconditions.checkNotNull(value, "update(value == null)");
+ env.put(varname, value);
+ }
+
+ /**
+ * Returns the class of the variable or null if the variable does not exist. This function
+ * works only in the local Environment, it doesn't check the global Environment.
+ */
+ public Class<?> getVariableType(String varname) {
+ Object variable = env.get(varname);
+ return variable != null ? EvalUtils.getSkylarkType(variable.getClass()) : null;
+ }
+
+ /**
+ * Removes the functions and the modules (i.e. the symbol of the module from the top level
+ * Environment and the functions attached to it) from the Environment which should be present
+ * only during the loading phase.
+ */
+ public void disableOnlyLoadingPhaseObjects() {
+ List<String> objectsToRemove = new ArrayList<>();
+ List<Class<?>> modulesToRemove = new ArrayList<>();
+ for (Map.Entry<String, Object> entry : env.entrySet()) {
+ Object object = entry.getValue();
+ if (object instanceof SkylarkFunction) {
+ if (((SkylarkFunction) object).isOnlyLoadingPhase()) {
+ objectsToRemove.add(entry.getKey());
+ }
+ } else if (object.getClass().isAnnotationPresent(SkylarkModule.class)) {
+ if (object.getClass().getAnnotation(SkylarkModule.class).onlyLoadingPhase()) {
+ objectsToRemove.add(entry.getKey());
+ modulesToRemove.add(entry.getValue().getClass());
+ }
+ }
+ }
+ for (String symbol : objectsToRemove) {
+ disabledVariables.add(symbol);
+ }
+ for (Class<?> moduleClass : modulesToRemove) {
+ disabledNameSpaces.add(moduleClass);
+ }
+ }
+
+ public void handleEvent(Event event) {
+ eventHandler.handle(event);
+ }
+
+ /**
+ * Returns a hash code calculated from the hash code of this Environment and the
+ * transitive closure of other Environments it loads.
+ */
+ public String getTransitiveFileContentHashCode() {
+ Fingerprint fingerprint = new Fingerprint();
+ fingerprint.addString(Preconditions.checkNotNull(fileContentHashCode));
+ // Calculate a new hash from the hash of the loaded Environments.
+ for (SkylarkEnvironment env : importedExtensions.values()) {
+ fingerprint.addString(env.getTransitiveFileContentHashCode());
+ }
+ return fingerprint.hexDigestAndReset();
+ }
+}