// 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.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.io.Serializable; import java.util.Deque; import java.util.HashSet; import java.util.LinkedList; import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nullable; /** * The environment for Skylark. */ public class SkylarkEnvironment extends Environment implements Serializable { /** * 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 readGlobalVariables = new HashSet<>(); @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.stackTraceContains(function)) { throw new EvalException( function.getLocation(), "Recursion was detected when calling '" + function.getName() + "' from '" + Iterables.getLast(callerEnv.getStackTrace()).getName() + "'"); } 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, callerEnv.getCopyOfStackTrace(), callerEnv.eventHandler); if (callerEnv.isLoadingPhase()) { childEnv.setLoadingPhase(); } try { for (String varname : callerEnv.propagatingVariables) { childEnv.updateAndPropagate(varname, callerEnv.lookup(varname)); } } catch (NoSuchVariableException e) { // This should never happen. throw new IllegalStateException(e); } return childEnv; } private SkylarkEnvironment(SkylarkEnvironment definitionEnv, Deque stackTrace, EventHandler eventHandler) { super(definitionEnv.getGlobalEnvironment(), 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, Deque stackTrace) { super(stackTrace); this.eventHandler = eventHandler; this.fileContentHashCode = astFileContentHashCode; } public SkylarkEnvironment(EventHandler eventHandler, String astFileContentHashCode) { this(eventHandler, astFileContentHashCode, new LinkedList()); } @VisibleForTesting public SkylarkEnvironment(EventHandler eventHandler) { this(eventHandler, null); } public SkylarkEnvironment(SkylarkEnvironment globalEnv) { super(globalEnv); this.eventHandler = globalEnv.eventHandler; } /** * Clones this Skylark global environment. */ public SkylarkEnvironment cloneEnv(EventHandler eventHandler) { Preconditions.checkArgument(isGlobal()); SkylarkEnvironment newEnv = new SkylarkEnvironment(eventHandler, this.fileContentHashCode, getCopyOfStackTrace()); for (Entry entry : env.entrySet()) { newEnv.env.put(entry.getKey(), entry.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. */ @Override public boolean isGlobal() { 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 isSkylark() { 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 { 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 lookup(String), 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(); } /** * 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; } 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(); } }