Refactor Skylark Environment-s
Make Environment-s freezable: Introduce a class Mutability
as a revokable capability to mutate objects in an Environment.
For now, only Environment-s carry this capability.
Make sure that every Mutability is revoked in the same function that create...
This reinstates a change that previously rolled-back because it broke the
serializability of SkylarkLookupValue. Bad news: serializing it succeeds for the
wrong reason, because a SkylarkEnvironment was stored as a result (now an
Environment.Extension) that was Serializable but inherited its bindings from an Environment (now an Environment.BaseExtension) which wasn't Serializable.
Apparently, Java doesn't try to serialize the bindings then (or at least doesn't
error out when it fails), because these bindings map variable names to pretty
arbitrary objects, and a lot of those we find in practice aren't Serializable.
Thus the current code passes the same tests as the previous code, but obviously
the serialization is just as ineffective as it used to be.
--
MOS_MIGRATED_REVID=102776694
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
index 18d605d..2f4f86e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
@@ -38,10 +38,11 @@
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.rules.SkylarkModules;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Environment.Extension;
import com.google.devtools.build.lib.syntax.Label;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
+import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.SkylarkType;
-import com.google.devtools.build.lib.syntax.ValidationEnvironment;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.OptionsClassProvider;
@@ -310,9 +311,7 @@
private final PrerequisiteValidator prerequisiteValidator;
- private final ImmutableMap<String, SkylarkType> skylarkAccessibleJavaClasses;
-
- private final ValidationEnvironment skylarkValidationEnvironment;
+ private final Environment.Frame globals;
public ConfiguredRuleClassProvider(
PathFragment preludePath,
@@ -338,10 +337,7 @@
this.configurationFragments = configurationFragments;
this.configurationCollectionFactory = configurationCollectionFactory;
this.prerequisiteValidator = prerequisiteValidator;
- this.skylarkAccessibleJavaClasses = skylarkAccessibleJavaClasses;
- this.skylarkValidationEnvironment = new ValidationEnvironment(
- // TODO(bazel-team): refactor constructors so we don't have those null-s
- createSkylarkRuleClassEnvironment(null, null));
+ this.globals = createGlobals(skylarkAccessibleJavaClasses);
}
public PrerequisiteValidator getPrerequisiteValidator() {
@@ -425,22 +421,46 @@
return BuildOptions.of(configurationOptions, optionsProvider);
}
- @Override
- public SkylarkEnvironment createSkylarkRuleClassEnvironment(
- EventHandler eventHandler, String astFileContentHashCode) {
- SkylarkEnvironment env = SkylarkModules.getNewEnvironment(eventHandler, astFileContentHashCode);
- for (Map.Entry<String, SkylarkType> entry : skylarkAccessibleJavaClasses.entrySet()) {
- env.update(entry.getKey(), entry.getValue().getType());
+ private Environment.Frame createGlobals(
+ ImmutableMap<String, SkylarkType> skylarkAccessibleJavaClasses) {
+ try (Mutability mutability = Mutability.create("ConfiguredRuleClassProvider globals")) {
+ Environment env = createSkylarkRuleClassEnvironment(
+ mutability, SkylarkModules.GLOBALS, null, null, null);
+ for (Map.Entry<String, SkylarkType> entry : skylarkAccessibleJavaClasses.entrySet()) {
+ env.setup(entry.getKey(), entry.getValue().getType());
+ }
+ return env.getGlobals();
}
- env.setLoadingPhase();
+ }
+
+ private Environment createSkylarkRuleClassEnvironment(
+ Mutability mutability,
+ Environment.Frame globals,
+ EventHandler eventHandler,
+ String astFileContentHashCode,
+ Map<PathFragment, Extension> importMap) {
+ Environment env = Environment.builder(mutability)
+ .setSkylark()
+ .setGlobals(globals)
+ .setEventHandler(eventHandler)
+ .setFileContentHashCode(astFileContentHashCode)
+ .setImportedExtensions(importMap)
+ .setLoadingPhase()
+ .build();
return env;
}
@Override
- public ValidationEnvironment getSkylarkValidationEnvironment() {
- return skylarkValidationEnvironment;
+ public Environment createSkylarkRuleClassEnvironment(
+ Mutability mutability,
+ EventHandler eventHandler,
+ String astFileContentHashCode,
+ Map<PathFragment, Extension> importMap) {
+ return createSkylarkRuleClassEnvironment(
+ mutability, globals, eventHandler, astFileContentHashCode, importMap);
}
+
@Override
public String getDefaultWorkspaceFile() {
return defaultWorkspaceFile;
diff --git a/src/main/java/com/google/devtools/build/lib/events/Location.java b/src/main/java/com/google/devtools/build/lib/events/Location.java
index 74b04a9..1b9110d 100644
--- a/src/main/java/com/google/devtools/build/lib/events/Location.java
+++ b/src/main/java/com/google/devtools/build/lib/events/Location.java
@@ -149,6 +149,18 @@
}
/**
+ * Returns a line corresponding to the position denoted by getStartOffset.
+ * Returns null if this information is not available.
+ */
+ public Integer getStartLine() {
+ LineAndColumn lac = getStartLineAndColumn();
+ if (lac == null) {
+ return null;
+ }
+ return lac.getLine();
+ }
+
+ /**
* Returns a (line, column) pair corresponding to the position denoted by
* getEndOffset. Returns null if this information is not available.
*/
diff --git a/src/main/java/com/google/devtools/build/lib/packages/ExternalPackage.java b/src/main/java/com/google/devtools/build/lib/packages/ExternalPackage.java
index fda1a83..49852ee 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/ExternalPackage.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/ExternalPackage.java
@@ -24,6 +24,7 @@
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.RuleFactory.InvalidRuleException;
+import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Label.SyntaxException;
@@ -120,7 +121,8 @@
attributes.put("actual", actual);
}
StoredEventHandler handler = new StoredEventHandler();
- Rule rule = RuleFactory.createRule(this, bindRuleClass, attributes, handler, null, location);
+ Rule rule = RuleFactory.createRule(
+ this, bindRuleClass, attributes, handler, null, location, null);
overwriteRule(rule);
rule.setVisibility(ConstantRuleVisibility.PUBLIC);
}
@@ -130,11 +132,11 @@
* WORKSPACE files to overwrite "earlier" ones.
*/
public Builder createAndAddRepositoryRule(RuleClass ruleClass, RuleClass bindRuleClass,
- Map<String, Object> kwargs, FuncallExpression ast)
+ Map<String, Object> kwargs, FuncallExpression ast, Environment env)
throws InvalidRuleException, NameConflictException, SyntaxException {
StoredEventHandler eventHandler = new StoredEventHandler();
- Rule tempRule = RuleFactory.createRule(this, ruleClass, kwargs, eventHandler, ast,
- ast.getLocation());
+ Rule tempRule = RuleFactory.createRule(
+ this, ruleClass, kwargs, eventHandler, ast, ast.getLocation(), env);
addEvents(eventHandler.getEvents());
try {
repositoryMap.put(RepositoryName.create("@" + tempRule.getName()), tempRule);
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index d5239fe..6cb807f 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -37,6 +37,7 @@
import com.google.devtools.build.lib.syntax.BuiltinFunction;
import com.google.devtools.build.lib.syntax.ClassObject;
import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Environment.Extension;
import com.google.devtools.build.lib.syntax.Environment.NoSuchVariableException;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalUtils;
@@ -46,10 +47,9 @@
import com.google.devtools.build.lib.syntax.GlobList;
import com.google.devtools.build.lib.syntax.Identifier;
import com.google.devtools.build.lib.syntax.Label;
-import com.google.devtools.build.lib.syntax.MethodLibrary;
+import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import com.google.devtools.build.lib.syntax.Runtime;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.syntax.SkylarkSignature;
import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
@@ -143,6 +143,9 @@
/**
* An extension to the global namespace of the BUILD language.
*/
+ // TODO(bazel-team): this is largely unrelated to syntax.Environment.Extension,
+ // and should probably be renamed PackageFactory.RuntimeExtension, since really,
+ // we're extending the Runtime with more classes.
public interface EnvironmentExtension {
/**
* Update the global environment with the identifiers this extension contributes.
@@ -326,7 +329,6 @@
private final RuleFactory ruleFactory;
private final RuleClassProvider ruleClassProvider;
- private final Environment globalEnv;
private AtomicReference<? extends UnixGlob.FilesystemCalls> syscalls;
private Preprocessor.Factory preprocessorFactory = Preprocessor.Factory.NullFactory.INSTANCE;
@@ -367,7 +369,6 @@
this.platformSetRegexps = platformSetRegexps;
this.ruleFactory = new RuleFactory(ruleClassProvider);
this.ruleClassProvider = ruleClassProvider;
- globalEnv = newGlobalEnvironment();
threadPool = new ThreadPoolExecutor(100, 100, 15L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new ThreadFactoryBuilder().setNameFormat("Legacy globber %d").build());
@@ -402,15 +403,6 @@
/**
- * Returns the static environment initialized once and shared by all packages
- * created by this factory. No updates occur to this environment once created.
- */
- @VisibleForTesting
- public Environment getEnvironment() {
- return globalEnv;
- }
-
- /**
* Returns the immutable, unordered set of names of all the known rule
* classes.
*/
@@ -631,8 +623,8 @@
"'environment_group argument'", context.pkgBuilder.getBuildFileLabel());
try {
- context.pkgBuilder.addEnvironmentGroup(name, environments, defaults,
- context.eventHandler, loc);
+ context.pkgBuilder.addEnvironmentGroup(
+ name, environments, defaults, context.eventHandler, loc);
return Runtime.NONE;
} catch (Label.SyntaxException e) {
throw new EvalException(loc,
@@ -910,7 +902,7 @@
Environment env)
throws RuleFactory.InvalidRuleException, Package.NameConflictException {
RuleClass ruleClass = getBuiltInRuleClass(ruleClassName, ruleFactory);
- RuleFactory.createAndAddRule(context, ruleClass, kwargs, ast, env.getStackTrace());
+ RuleFactory.createAndAddRule(context, ruleClass, kwargs, ast, env);
}
private static RuleClass getBuiltInRuleClass(String ruleClassName, RuleFactory ruleFactory) {
@@ -958,16 +950,6 @@
};
}
- /**
- * Returns a new environment populated with common entries that can be shared
- * across packages and that don't require the context.
- */
- private static Environment newGlobalEnvironment() {
- Environment env = new Environment();
- MethodLibrary.setupMethodEnvironment(env);
- return env;
- }
-
/****************************************************************************
* Package creation.
*/
@@ -995,7 +977,7 @@
Preprocessor.Result preprocessingResult,
Iterable<Event> preprocessingEvents,
List<Statement> preludeStatements,
- Map<PathFragment, SkylarkEnvironment> imports,
+ Map<PathFragment, Extension> imports,
ImmutableList<Label> skylarkFileDependencies,
CachingPackageLocator locator,
RuleVisibility defaultVisibility,
@@ -1063,12 +1045,12 @@
packageId,
buildFile,
preprocessingResult,
- preprocessingResult.events,
- ImmutableList.<Statement>of(), /* preludeStatements */
- ImmutableMap.<PathFragment, SkylarkEnvironment>of(), /* imports */
- ImmutableList.<Label>of(), /* skylarkFileDependencies */
+ /*preprocessingEvents=*/preprocessingResult.events,
+ /*preludeStatements=*/ImmutableList.<Statement>of(),
+ /*imports=*/ImmutableMap.<PathFragment, Extension>of(),
+ /*skylarkFileDependencies=*/ImmutableList.<Label>of(),
locator,
- ConstantRuleVisibility.PUBLIC, /* defaultVisibility */
+ /*defaultVisibility=*/ConstantRuleVisibility.PUBLIC,
globber)
.build();
Event.replayEventsOn(eventHandler, result.getEvents());
@@ -1110,8 +1092,12 @@
return Preprocessor.Result.noPreprocessing(inputSource);
}
try {
- return preprocessor.preprocess(inputSource, packageId.toString(), globber,
- globalEnv, ruleFactory.getRuleClassNames());
+ return preprocessor.preprocess(
+ inputSource,
+ packageId.toString(),
+ globber,
+ Environment.BUILD,
+ ruleFactory.getRuleClassNames());
} catch (IOException e) {
List<Event> events = ImmutableList.of(Event.error(Location.fromFile(buildFile),
"preprocessing failed: " + e.getMessage()));
@@ -1211,19 +1197,20 @@
// or if not possible, at least make them straight copies from the native module variant.
// or better, use a common Environment.Frame for these common bindings
// (that shares a backing ImmutableMap for the bindings?)
- pkgEnv.update("native", nativeModule);
- pkgEnv.update("distribs", newDistribsFunction.apply(context));
- pkgEnv.update("glob", newGlobFunction.apply(context, /*async=*/false));
- pkgEnv.update("mocksubinclude", newMockSubincludeFunction.apply(context));
- pkgEnv.update("licenses", newLicensesFunction.apply(context));
- pkgEnv.update("exports_files", newExportsFilesFunction.apply());
- pkgEnv.update("package_group", newPackageGroupFunction.apply());
- pkgEnv.update("package", newPackageFunction(packageArguments));
- pkgEnv.update("environment_group", newEnvironmentGroupFunction.apply(context));
+ pkgEnv
+ .setup("native", nativeModule)
+ .setup("distribs", newDistribsFunction.apply(context))
+ .setup("glob", newGlobFunction.apply(context, /*async=*/false))
+ .setup("mocksubinclude", newMockSubincludeFunction.apply(context))
+ .setup("licenses", newLicensesFunction.apply(context))
+ .setup("exports_files", newExportsFilesFunction.apply())
+ .setup("package_group", newPackageGroupFunction.apply())
+ .setup("package", newPackageFunction(packageArguments))
+ .setup("environment_group", newEnvironmentGroupFunction.apply(context));
for (String ruleClass : ruleFactory.getRuleClassNames()) {
BaseFunction ruleFunction = newRuleFunction(ruleFactory, ruleClass);
- pkgEnv.update(ruleClass, ruleFunction);
+ pkgEnv.setup(ruleClass, ruleFunction);
}
for (EnvironmentExtension extension : environmentExtensions) {
@@ -1254,62 +1241,65 @@
PackageIdentifier packageId, BuildFileAST buildFileAST, Path buildFilePath, Globber globber,
Iterable<Event> pastEvents, RuleVisibility defaultVisibility, boolean containsError,
boolean containsTransientError, MakeEnvironment.Builder pkgMakeEnv,
- Map<PathFragment, SkylarkEnvironment> imports,
+ Map<PathFragment, Extension> imports,
ImmutableList<Label> skylarkFileDependencies) throws InterruptedException {
- // Important: Environment should be unreachable by the end of this method!
- StoredEventHandler eventHandler = new StoredEventHandler();
- Environment pkgEnv = new Environment(globalEnv, eventHandler);
- pkgEnv.setLoadingPhase();
-
Package.LegacyBuilder pkgBuilder = new Package.LegacyBuilder(
packageId, ruleClassProvider.getRunfilesPrefix());
+ StoredEventHandler eventHandler = new StoredEventHandler();
- pkgBuilder.setGlobber(globber)
- .setFilename(buildFilePath)
- .setMakeEnv(pkgMakeEnv)
- .setDefaultVisibility(defaultVisibility)
- // "defaultVisibility" comes from the command line. Let's give the BUILD file a chance to
- // set default_visibility once, be reseting the PackageBuilder.defaultVisibilitySet flag.
- .setDefaultVisibilitySet(false)
- .setSkylarkFileDependencies(skylarkFileDependencies)
- .setWorkspaceName(externalPkg.getWorkspaceName());
+ try (Mutability mutability = Mutability.create("package %s", packageId)) {
+ Environment pkgEnv = Environment.builder(mutability)
+ .setGlobals(Environment.BUILD)
+ .setEventHandler(eventHandler)
+ .setImportedExtensions(imports)
+ .setLoadingPhase()
+ .build();
- Event.replayEventsOn(eventHandler, pastEvents);
+ pkgBuilder.setGlobber(globber)
+ .setFilename(buildFilePath)
+ .setMakeEnv(pkgMakeEnv)
+ .setDefaultVisibility(defaultVisibility)
+ // "defaultVisibility" comes from the command line. Let's give the BUILD file a chance to
+ // set default_visibility once, be reseting the PackageBuilder.defaultVisibilitySet flag.
+ .setDefaultVisibilitySet(false)
+ .setSkylarkFileDependencies(skylarkFileDependencies)
+ .setWorkspaceName(externalPkg.getWorkspaceName());
- // Stuff that closes over the package context:`
- PackageContext context = new PackageContext(pkgBuilder, globber, eventHandler);
- buildPkgEnv(pkgEnv, context, ruleFactory);
+ Event.replayEventsOn(eventHandler, pastEvents);
- if (containsError) {
- pkgBuilder.setContainsErrors();
- }
+ // Stuff that closes over the package context:
+ PackageContext context = new PackageContext(pkgBuilder, globber, eventHandler);
+ buildPkgEnv(pkgEnv, context, ruleFactory);
+ pkgEnv.setupDynamic(PKG_CONTEXT, context);
+ pkgEnv.setupDynamic(Runtime.PKG_NAME, packageId.toString());
- if (containsTransientError) {
- pkgBuilder.setContainsTemporaryErrors();
- }
+ if (containsError) {
+ pkgBuilder.setContainsErrors();
+ }
- if (!validatePackageIdentifier(packageId, buildFileAST.getLocation(), eventHandler)) {
- pkgBuilder.setContainsErrors();
- }
+ if (containsTransientError) {
+ pkgBuilder.setContainsTemporaryErrors();
+ }
- pkgEnv.setImportedExtensions(imports);
- pkgEnv.updateAndPropagate(PKG_CONTEXT, context);
- pkgEnv.updateAndPropagate(Runtime.PKG_NAME, packageId.toString());
+ if (!validatePackageIdentifier(packageId, buildFileAST.getLocation(), eventHandler)) {
+ pkgBuilder.setContainsErrors();
+ }
- if (!validateAssignmentStatements(pkgEnv, buildFileAST, eventHandler)) {
- pkgBuilder.setContainsErrors();
- }
+ if (!validateAssignmentStatements(pkgEnv, buildFileAST, eventHandler)) {
+ pkgBuilder.setContainsErrors();
+ }
- if (buildFileAST.containsErrors()) {
- pkgBuilder.setContainsErrors();
- }
+ if (buildFileAST.containsErrors()) {
+ pkgBuilder.setContainsErrors();
+ }
- // TODO(bazel-team): (2009) the invariant "if errors are reported, mark the package
- // as containing errors" is strewn all over this class. Refactor to use an
- // event sensor--and see if we can simplify the calling code in
- // createPackage().
- if (!buildFileAST.exec(pkgEnv, eventHandler)) {
- pkgBuilder.setContainsErrors();
+ // TODO(bazel-team): (2009) the invariant "if errors are reported, mark the package
+ // as containing errors" is strewn all over this class. Refactor to use an
+ // event sensor--and see if we can simplify the calling code in
+ // createPackage().
+ if (!buildFileAST.exec(pkgEnv, eventHandler)) {
+ pkgBuilder.setContainsErrors();
+ }
}
pkgBuilder.addEvents(eventHandler.getEvents());
@@ -1328,29 +1318,36 @@
// of all globs.
return;
}
- // Important: Environment should be unreachable by the end of this method!
- Environment pkgEnv = new Environment();
- pkgEnv.setLoadingPhase();
+ try (Mutability mutability = Mutability.create("prefetchGlobs for %s", packageId)) {
+ Environment pkgEnv = Environment.builder(mutability)
+ .setGlobals(Environment.BUILD)
+ .setEventHandler(NullEventHandler.INSTANCE)
+ .setLoadingPhase()
+ .build();
- Package.LegacyBuilder pkgBuilder = new Package.LegacyBuilder(packageId,
- ruleClassProvider.getRunfilesPrefix());
+ Package.LegacyBuilder pkgBuilder = new Package.LegacyBuilder(packageId,
+ ruleClassProvider.getRunfilesPrefix());
- pkgBuilder.setFilename(buildFilePath)
- .setMakeEnv(pkgMakeEnv)
- .setDefaultVisibility(defaultVisibility)
- // "defaultVisibility" comes from the command line. Let's give the BUILD file a chance to
- // set default_visibility once, be reseting the PackageBuilder.defaultVisibilitySet flag.
- .setDefaultVisibilitySet(false);
+ pkgBuilder.setFilename(buildFilePath)
+ .setMakeEnv(pkgMakeEnv)
+ .setDefaultVisibility(defaultVisibility)
+ // "defaultVisibility" comes from the command line. Let's give the BUILD file a chance to
+ // set default_visibility once, be reseting the PackageBuilder.defaultVisibilitySet flag.
+ .setDefaultVisibilitySet(false);
- // Stuff that closes over the package context:
- PackageContext context = new PackageContext(pkgBuilder, globber, NullEventHandler.INSTANCE);
- buildPkgEnv(pkgEnv, context, ruleFactory);
- pkgEnv.update("glob", newGlobFunction.apply(context, /*async=*/true));
- // The Fileset function is heavyweight in that it can run glob(). Avoid this during the
- // preloading phase.
- pkgEnv.remove("FilesetEntry");
-
- buildFileAST.exec(pkgEnv, NullEventHandler.INSTANCE);
+ // Stuff that closes over the package context:
+ PackageContext context = new PackageContext(pkgBuilder, globber, NullEventHandler.INSTANCE);
+ buildPkgEnv(pkgEnv, context, ruleFactory);
+ try {
+ pkgEnv.update("glob", newGlobFunction.apply(context, /*async=*/true));
+ // The Fileset function is heavyweight in that it can run glob(). Avoid this during the
+ // preloading phase.
+ pkgEnv.update("FilesetEntry", Runtime.NONE);
+ } catch (EvalException e) {
+ throw new AssertionError(e);
+ }
+ buildFileAST.exec(pkgEnv, NullEventHandler.INSTANCE);
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Preprocessor.java b/src/main/java/com/google/devtools/build/lib/packages/Preprocessor.java
index babe5ff..acf06f9 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Preprocessor.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Preprocessor.java
@@ -146,7 +146,7 @@
* @param in the BUILD file to be preprocessed.
* @param packageName the BUILD file's package.
* @param globber a globber for evaluating globs.
- * @param globalEnv the GLOBALS Python environment.
+ * @param globals the global bindings for the Python environment.
* @param ruleNames the set of names of all rules in the build language.
* @throws IOException if there was an I/O problem during preprocessing.
* @return a pair of the ParserInputSource and a map of subincludes seen during the evaluation
@@ -155,7 +155,7 @@
ParserInputSource in,
String packageName,
Globber globber,
- Environment globalEnv,
+ Environment.Frame globals,
Set<String> ruleNames)
throws IOException, InterruptedException;
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index 1202f04..91723d0 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -35,13 +35,13 @@
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.syntax.Argument;
import com.google.devtools.build.lib.syntax.BaseFunction;
+import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.FragmentClassNameResolver;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.GlobList;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Label.SyntaxException;
import com.google.devtools.build.lib.syntax.Runtime;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -499,7 +499,7 @@
private BaseFunction configuredTargetFunction = null;
private Function<? super Rule, Map<String, Label>> externalBindingsFunction =
NO_EXTERNAL_BINDINGS;
- private SkylarkEnvironment ruleDefinitionEnvironment = null;
+ private Environment ruleDefinitionEnvironment = null;
private Set<Class<?>> configurationFragments = new LinkedHashSet<>();
private MissingFragmentPolicy missingFragmentPolicy = MissingFragmentPolicy.FAIL_ANALYSIS;
private Map<ConfigurationTransition, ImmutableSet<String>> requiredFragmentNames =
@@ -798,7 +798,7 @@
/**
* Sets the rule definition environment. Meant for Skylark usage.
*/
- public Builder setRuleDefinitionEnvironment(SkylarkEnvironment env) {
+ public Builder setRuleDefinitionEnvironment(Environment env) {
this.ruleDefinitionEnvironment = env;
return this;
}
@@ -970,7 +970,7 @@
* The Skylark rule definition environment of this RuleClass.
* Null for non Skylark executable RuleClasses.
*/
- @Nullable private final SkylarkEnvironment ruleDefinitionEnvironment;
+ @Nullable private final Environment ruleDefinitionEnvironment;
/**
* The set of required configuration fragments; this should list all fragments that can be
@@ -1023,7 +1023,7 @@
ImmutableSet<Class<?>> advertisedProviders,
@Nullable BaseFunction configuredTargetFunction,
Function<? super Rule, Map<String, Label>> externalBindingsFunction,
- @Nullable SkylarkEnvironment ruleDefinitionEnvironment,
+ @Nullable Environment ruleDefinitionEnvironment,
Set<Class<?>> allowedConfigurationFragments,
MissingFragmentPolicy missingFragmentPolicy,
boolean supportsConstraintChecking,
@@ -1085,7 +1085,7 @@
ImmutableSet<Class<?>> advertisedProviders,
@Nullable BaseFunction configuredTargetFunction,
Function<? super Rule, Map<String, Label>> externalBindingsFunction,
- @Nullable SkylarkEnvironment ruleDefinitionEnvironment,
+ @Nullable Environment ruleDefinitionEnvironment,
Set<Class<?>> allowedConfigurationFragments,
ImmutableMap<ConfigurationTransition, ImmutableSet<String>> allowedConfigurationFragmentNames,
@Nullable FragmentClassNameResolver fragmentNameResolver,
@@ -1741,7 +1741,7 @@
/**
* Returns this RuleClass's rule definition environment.
*/
- @Nullable public SkylarkEnvironment getRuleDefinitionEnvironment() {
+ @Nullable public Environment getRuleDefinitionEnvironment() {
return ruleDefinitionEnvironment;
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
index 958d88a..e2ce1ba 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
@@ -15,12 +15,15 @@
package com.google.devtools.build.lib.packages;
import com.google.devtools.build.lib.events.EventHandler;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
-import com.google.devtools.build.lib.syntax.ValidationEnvironment;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Environment.Extension;
+import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Map;
+import javax.annotation.Nullable;
+
/**
* The collection of the supported build rules. Provides an Environment for Skylark rule creation.
*/
@@ -47,17 +50,19 @@
Map<String, Class<? extends AspectFactory<?, ?, ?>>> getAspectFactoryMap();
/**
- * Returns a new Skylark Environment instance for rule creation. Implementations need to be
- * thread safe.
+ * Returns a new Skylark Environment instance for rule creation.
+ * Implementations need to be thread safe.
+ * Be sure to close() the mutability before you return the results of said evaluation.
+ * @param mutability the Mutability for the current evaluation context
+ * @param eventHandler the EventHandler for warnings, errors, etc.
+ * @param astFileContentHashCode the hash code identifying this environment.
+ * @return an Environment, in which to evaluate load time skylark forms.
*/
- SkylarkEnvironment createSkylarkRuleClassEnvironment(
- EventHandler eventHandler, String astFileContentHashCode);
-
- /**
- * Returns a validation environment for static analysis of skylark files.
- * The environment has to contain all built-in functions and objects.
- */
- ValidationEnvironment getSkylarkValidationEnvironment();
+ Environment createSkylarkRuleClassEnvironment(
+ Mutability mutability,
+ EventHandler eventHandler,
+ @Nullable String astFileContentHashCode,
+ @Nullable Map<PathFragment, Extension> importMap);
/**
* Returns the default content of the WORKSPACE file.
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java b/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
index 4875708..8c073d2 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
@@ -15,20 +15,24 @@
package com.google.devtools.build.lib.packages;
import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.Package.NameConflictException;
import com.google.devtools.build.lib.packages.PackageFactory.PackageContext;
+import com.google.devtools.build.lib.syntax.BaseFunction;
+import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Label.SyntaxException;
-import com.google.devtools.build.lib.syntax.StackTraceElement;
+import com.google.devtools.build.lib.syntax.UserDefinedFunction;
+import com.google.devtools.build.lib.util.Pair;
import java.util.Map;
import java.util.Set;
+import javax.annotation.Nullable;
+
/**
* Given a rule class and a set of attributes, returns a Rule instance. Also
* performs a number of checks and associates the rule and the owning package
@@ -78,7 +82,7 @@
EventHandler eventHandler,
FuncallExpression ast,
Location location,
- ImmutableList<StackTraceElement> stackTrace)
+ @Nullable Environment env)
throws InvalidRuleException {
Preconditions.checkNotNull(ruleClass);
String ruleClassName = ruleClass.getName();
@@ -108,31 +112,19 @@
}
try {
- Rule rule = ruleClass.createRuleWithLabel(pkgBuilder, label,
- addGeneratorAttributesForMacros(attributeValues, stackTrace), eventHandler, ast,
+ return ruleClass.createRuleWithLabel(
+ pkgBuilder,
+ label,
+ addGeneratorAttributesForMacros(attributeValues, env),
+ eventHandler,
+ ast,
location);
- return rule;
} catch (SyntaxException e) {
throw new RuleFactory.InvalidRuleException(ruleClass + " " + e.getMessage());
}
}
/**
- * Creates and returns a rule instance (without a stack trace).
- */
- static Rule createRule(
- Package.Builder pkgBuilder,
- RuleClass ruleClass,
- Map<String, Object> attributeValues,
- EventHandler eventHandler,
- FuncallExpression ast,
- Location location)
- throws InvalidRuleException {
- return createRule(pkgBuilder, ruleClass, attributeValues, eventHandler, ast, location,
- ImmutableList.<StackTraceElement>of());
- }
-
- /**
* Creates a rule instance, adds it to the package and returns it.
*
* @param pkgBuilder the under-construction package to which the rule belongs
@@ -145,34 +137,40 @@
* rule creation
* @param ast the abstract syntax tree of the rule expression (optional)
* @param location the location at which this rule was declared
- * @param stackTrace the stack trace containing all functions that led to the creation of
- * this rule (optional)
* @throws InvalidRuleException if the rule could not be constructed for any
* reason (e.g. no <code>name</code> attribute is defined)
- * @throws NameConflictException
+ * @throws InvalidRuleException, NameConflictException
*/
- static Rule createAndAddRule(Package.Builder pkgBuilder,
- RuleClass ruleClass,
- Map<String, Object> attributeValues,
- EventHandler eventHandler,
- FuncallExpression ast,
- Location location,
- ImmutableList<StackTraceElement> stackTrace)
+ static Rule createAndAddRule(
+ Package.Builder pkgBuilder,
+ RuleClass ruleClass,
+ Map<String, Object> attributeValues,
+ EventHandler eventHandler,
+ FuncallExpression ast,
+ Location location,
+ Environment env)
throws InvalidRuleException, NameConflictException {
Rule rule = createRule(
- pkgBuilder, ruleClass, attributeValues, eventHandler, ast, location, stackTrace);
+ pkgBuilder, ruleClass, attributeValues, eventHandler, ast, location, env);
pkgBuilder.addRule(rule);
return rule;
}
- public static Rule createAndAddRule(PackageContext context,
+ public static Rule createAndAddRule(
+ PackageContext context,
RuleClass ruleClass,
Map<String, Object> attributeValues,
FuncallExpression ast,
- ImmutableList<StackTraceElement> stackTrace)
+ Environment env)
throws InvalidRuleException, NameConflictException {
- return createAndAddRule(context.pkgBuilder, ruleClass, attributeValues, context.eventHandler,
- ast, ast.getLocation(), stackTrace);
+ return createAndAddRule(
+ context.pkgBuilder,
+ ruleClass,
+ attributeValues,
+ context.eventHandler,
+ ast,
+ ast.getLocation(),
+ env);
}
/**
@@ -192,22 +190,30 @@
* <p>Otherwise, it returns the given attributes without any changes.
*/
private static Map<String, Object> addGeneratorAttributesForMacros(
- Map<String, Object> args, ImmutableList<StackTraceElement> stackTrace) {
- if (stackTrace.size() <= 2 || args.containsKey("generator_name")
- || args.containsKey("generator_function")) {
- // Returns the original arguments if a) there is only the rule itself on the stack
- // trace (=> no macro) or b) the attributes have already been set by Python pre-processing.
- // The stack trace will always have at least two entries: one for the call to the rule and one
- // for its implementation. Consequently, we check for size <= 2.
+ Map<String, Object> args, @Nullable Environment env) {
+ // Returns the original arguments if a) there is only the rule itself on the stack
+ // trace (=> no macro) or b) the attributes have already been set by Python pre-processing.
+ if (env == null) {
+ return args;
+ }
+ boolean hasName = args.containsKey("generator_name");
+ boolean hasFunc = args.containsKey("generator_function");
+ // TODO(bazel-team): resolve cases in our code where hasName && !hasFunc, or hasFunc && !hasName
+ if (hasName || hasFunc) {
+ return args;
+ }
+ Pair<FuncallExpression, BaseFunction> topCall = env.getTopCall();
+ if (topCall == null || !(topCall.second instanceof UserDefinedFunction)) {
return args;
}
- StackTraceElement generator = stackTrace.get(0);
+ FuncallExpression generator = topCall.first;
+ BaseFunction function = topCall.second;
String name = generator.getNameArg();
ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
builder.putAll(args);
builder.put("generator_name", (name == null) ? args.get("name") : name);
- builder.put("generator_function", generator.getName());
+ builder.put("generator_function", function.getName());
builder.put("generator_location", Location.printPathAndLine(generator.getLocation()));
try {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
index 054865f..abbbdf8 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
@@ -29,7 +29,7 @@
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.FunctionSignature;
import com.google.devtools.build.lib.syntax.Label;
-import com.google.devtools.build.lib.syntax.MethodLibrary;
+import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import java.io.File;
@@ -44,14 +44,30 @@
private final Builder builder;
private final Environment environment;
- public WorkspaceFactory(Builder builder, RuleClassProvider ruleClassProvider) {
- this(builder, ruleClassProvider, null);
+ /**
+ * @param builder a builder for the Workspace
+ * @param ruleClassProvider a provider for known rule classes
+ * @param mutability the Mutability for the current evaluation context
+ */
+ public WorkspaceFactory(
+ Builder builder, RuleClassProvider ruleClassProvider, Mutability mutability) {
+ this(builder, ruleClassProvider, mutability, null);
}
+ // TODO(bazel-team): document installDir
+ /**
+ * @param builder a builder for the Workspace
+ * @param ruleClassProvider a provider for known rule classes
+ * @param mutability the Mutability for the current evaluation context
+ * @param installDir an optional directory into which to install software
+ */
public WorkspaceFactory(
- Builder builder, RuleClassProvider ruleClassProvider, @Nullable String installDir) {
+ Builder builder,
+ RuleClassProvider ruleClassProvider,
+ Mutability mutability,
+ @Nullable String installDir) {
this.builder = builder;
- this.environment = createWorkspaceEnv(builder, ruleClassProvider, installDir);
+ this.environment = createWorkspaceEnv(builder, ruleClassProvider, mutability, installDir);
}
public void parse(ParserInputSource source)
@@ -122,13 +138,13 @@
private static BuiltinFunction newRuleFunction(
final RuleFactory ruleFactory, final Builder builder, final String ruleClassName) {
return new BuiltinFunction(ruleClassName,
- FunctionSignature.KWARGS, BuiltinFunction.USE_AST) {
- public Object invoke(Map<String, Object> kwargs, FuncallExpression ast)
+ FunctionSignature.KWARGS, BuiltinFunction.USE_AST_ENV) {
+ public Object invoke(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
throws EvalException {
try {
RuleClass ruleClass = ruleFactory.getRuleClass(ruleClassName);
RuleClass bindRuleClass = ruleFactory.getRuleClass("bind");
- builder.createAndAddRepositoryRule(ruleClass, bindRuleClass, kwargs, ast);
+ builder.createAndAddRepositoryRule(ruleClass, bindRuleClass, kwargs, ast, env);
} catch (RuleFactory.InvalidRuleException | Package.NameConflictException |
Label.SyntaxException e) {
throw new EvalException(ast.getLocation(), e.getMessage());
@@ -139,25 +155,30 @@
}
private Environment createWorkspaceEnv(
- Builder builder, RuleClassProvider ruleClassProvider, String installDir) {
- Environment workspaceEnv = new Environment();
- MethodLibrary.setupMethodEnvironment(workspaceEnv);
- workspaceEnv.setLoadingPhase();
-
+ Builder builder,
+ RuleClassProvider ruleClassProvider,
+ Mutability mutability,
+ String installDir) {
+ Environment workspaceEnv = Environment.builder(mutability)
+ .setGlobals(Environment.BUILD)
+ .setLoadingPhase()
+ .build();
RuleFactory ruleFactory = new RuleFactory(ruleClassProvider);
- for (String ruleClass : ruleFactory.getRuleClassNames()) {
- BaseFunction ruleFunction = newRuleFunction(ruleFactory, builder, ruleClass);
- workspaceEnv.update(ruleClass, ruleFunction);
+ try {
+ for (String ruleClass : ruleFactory.getRuleClassNames()) {
+ BaseFunction ruleFunction = newRuleFunction(ruleFactory, builder, ruleClass);
+ workspaceEnv.update(ruleClass, ruleFunction);
+ }
+ if (installDir != null) {
+ workspaceEnv.update("__embedded_dir__", installDir);
+ }
+ File jreDirectory = new File(System.getProperty("java.home"));
+ workspaceEnv.update("DEFAULT_SERVER_JAVABASE", jreDirectory.getParentFile().toString());
+ workspaceEnv.update("bind", newBindFunction(ruleFactory, builder));
+ workspaceEnv.update("workspace", newWorkspaceNameFunction(builder));
+ return workspaceEnv;
+ } catch (EvalException e) {
+ throw new AssertionError(e);
}
-
- if (installDir != null) {
- workspaceEnv.update("__embedded_dir__", installDir);
- }
- File jreDirectory = new File(System.getProperty("java.home"));
- workspaceEnv.update("DEFAULT_SERVER_JAVABASE", jreDirectory.getParentFile().toString());
-
- workspaceEnv.update("bind", newBindFunction(ruleFactory, builder));
- workspaceEnv.update("workspace", newWorkspaceNameFunction(builder));
- return workspaceEnv;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java
index 787e43f..5f87e74 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java
@@ -37,8 +37,8 @@
import com.google.devtools.build.lib.query2.output.OutputFormatter.UnorderedFormatter;
import com.google.devtools.build.lib.query2.output.QueryOptions.OrderOutput;
import com.google.devtools.build.lib.query2.proto.proto2api.Build;
+import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.Label;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.util.BinaryPredicate;
import java.io.IOException;
@@ -141,7 +141,7 @@
rule.isAttributeValueExplicitlySpecified(attr), /*includeGlobs=*/ false));
}
- SkylarkEnvironment env = rule.getRuleClassObject().getRuleDefinitionEnvironment();
+ Environment env = rule.getRuleClassObject().getRuleDefinitionEnvironment();
if (env != null) {
// The RuleDefinitionEnvironment is always defined for Skylark rules and
// always null for non Skylark rules.
@@ -150,7 +150,7 @@
.setName(RULE_IMPLEMENTATION_HASH_ATTR_NAME)
.setType(ProtoUtils.getDiscriminatorFromType(
com.google.devtools.build.lib.packages.Type.STRING))
- .setStringValue(env.getTransitiveFileContentHashCode()));
+ .setStringValue(env.getTransitiveContentHashCode()));
}
ImmutableMultimap<Attribute, Label> aspectsDependencies =
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java
index 75afd45..e6d8f3c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java
@@ -31,7 +31,6 @@
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkModule;
import com.google.devtools.build.lib.syntax.SkylarkSignature;
@@ -105,7 +104,7 @@
}
private static Attribute.Builder<?> createAttribute(
- Type<?> type, Map<String, Object> arguments, FuncallExpression ast, SkylarkEnvironment env)
+ Type<?> type, Map<String, Object> arguments, FuncallExpression ast, Environment env)
throws EvalException, ConversionException {
// We use an empty name now so that we can set it later.
// This trick makes sense only in the context of Skylark (builtin rules should not use it).
@@ -185,7 +184,7 @@
Map<String, Object> kwargs, Type<?> type, FuncallExpression ast, Environment env)
throws EvalException {
try {
- return createAttribute(type, kwargs, ast, (SkylarkEnvironment) env);
+ return createAttribute(type, kwargs, ast, env);
} catch (ConversionException e) {
throw new EvalException(ast.getLocation(), e.getMessage());
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
index c52c58c..3054420 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
@@ -19,11 +19,8 @@
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.packages.SkylarkNativeModule;
import com.google.devtools.build.lib.syntax.Environment;
-import com.google.devtools.build.lib.syntax.EvaluationContext;
-import com.google.devtools.build.lib.syntax.MethodLibrary;
+import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.Runtime;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
-import com.google.devtools.build.lib.syntax.ValidationEnvironment;
/**
* The basis for a Skylark Environment with all build-related modules registered.
@@ -44,41 +41,43 @@
SkylarkRuleClassFunctions.class,
SkylarkRuleImplementationFunctions.class);
+ /** Global bindings for all Skylark modules */
+ public static final Environment.Frame GLOBALS = createGlobals();
+
+ private static Environment.Frame createGlobals() {
+ try (Mutability mutability = Mutability.create("SkylarkModules")) {
+ Environment env = Environment.builder(mutability)
+ .setSkylark()
+ .setGlobals(Environment.SKYLARK)
+ .build();
+ for (Class<?> moduleClass : MODULES) {
+ Runtime.registerModuleGlobals(env, moduleClass);
+ }
+ return env.getGlobals();
+ }
+ }
+
+
/**
- * Returns a new SkylarkEnvironment with the elements of the Skylark modules.
+ * Create an {@link Environment} in which to load a Skylark file.
+ * @param eventHandler an EventHandler for warnings, errors, etc.
+ * @param astFileContentHashCode a hash code identifying the file being evaluated
+ * @param mutability the Mutability for the current evaluation context
+ * @return a new Environment with the elements of the Skylark modules.
*/
- public static SkylarkEnvironment getNewEnvironment(
- EventHandler eventHandler, String astFileContentHashCode) {
- SkylarkEnvironment env = new SkylarkEnvironment(eventHandler, astFileContentHashCode);
- setupEnvironment(env);
- return env;
+ public static Environment getNewEnvironment(
+ EventHandler eventHandler, String astFileContentHashCode, Mutability mutability) {
+ return Environment.builder(mutability)
+ .setSkylark()
+ .setGlobals(GLOBALS)
+ .setEventHandler(eventHandler)
+ .setFileContentHashCode(astFileContentHashCode)
+ .build();
}
@VisibleForTesting
- public static SkylarkEnvironment getNewEnvironment(EventHandler eventHandler) {
- return getNewEnvironment(eventHandler, null);
- }
-
- private static void setupEnvironment(Environment env) {
- MethodLibrary.setupMethodEnvironment(env);
- for (Class<?> moduleClass : MODULES) {
- Runtime.registerModuleGlobals(env, moduleClass);
- }
- // Even though PACKAGE_NAME has no value _now_ and will be bound later,
- // it needs to be visible for the ValidationEnvironment to be happy.
- env.update(Runtime.PKG_NAME, Runtime.NONE);
- }
-
- /**
- * Returns a new ValidationEnvironment with the elements of the Skylark modules.
- */
- public static ValidationEnvironment getValidationEnvironment() {
- // TODO(bazel-team): refactor constructors so we don't have those null-s
- return new ValidationEnvironment(getNewEnvironment(null));
- }
-
- public static EvaluationContext newEvaluationContext(EventHandler eventHandler) {
- return EvaluationContext.newSkylarkContext(
- getNewEnvironment(eventHandler), getValidationEnvironment());
+ public static Environment getNewEnvironment(
+ EventHandler eventHandler, Mutability mutability) {
+ return getNewEnvironment(eventHandler, null, mutability);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
index 42449c5..2595cf9 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
@@ -71,7 +71,6 @@
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkModuleNameResolver;
import com.google.devtools.build.lib.syntax.SkylarkSignature;
@@ -236,13 +235,11 @@
+ "bin directory. This is used for compatibility with existing rules."),
@Param(name = "fragments", type = SkylarkList.class, generic1 = String.class,
defaultValue = "[]",
- doc =
- "List of names of configuration fragments that the rule requires "
+ doc = "List of names of configuration fragments that the rule requires "
+ "in target configuration."),
@Param(name = "host_fragments", type = SkylarkList.class, generic1 = String.class,
defaultValue = "[]",
- doc =
- "List of names of configuration fragments that the rule requires "
+ doc = "List of names of configuration fragments that the rule requires "
+ "in host configuration.")},
useAst = true, useEnvironment = true)
private static final BuiltinFunction rule = new BuiltinFunction("rule") {
@@ -280,14 +277,16 @@
if (implicitOutputs != Runtime.NONE) {
if (implicitOutputs instanceof BaseFunction) {
BaseFunction func = (BaseFunction) implicitOutputs;
- final SkylarkCallbackFunction callback =
- new SkylarkCallbackFunction(func, ast, (SkylarkEnvironment) funcallEnv);
+ SkylarkCallbackFunction callback =
+ new SkylarkCallbackFunction(func, ast, funcallEnv);
builder.setImplicitOutputsFunction(
new SkylarkImplicitOutputsFunctionWithCallback(callback, ast.getLocation()));
} else {
builder.setImplicitOutputsFunction(
- new SkylarkImplicitOutputsFunctionWithMap(ImmutableMap.copyOf(castMap(implicitOutputs,
- String.class, String.class, "implicit outputs of the rule class"))));
+ new SkylarkImplicitOutputsFunctionWithMap(
+ ImmutableMap.copyOf(castMap(
+ implicitOutputs, String.class, String.class,
+ "implicit outputs of the rule class"))));
}
}
@@ -298,8 +297,7 @@
registerRequiredFragments(fragments, hostFragments, builder);
builder.setConfiguredTargetFunction(implementation);
- builder.setRuleDefinitionEnvironment(
- ((SkylarkEnvironment) funcallEnv).getGlobalEnvironment());
+ builder.setRuleDefinitionEnvironment(funcallEnv);
return new RuleFunction(builder, type);
}
@@ -353,7 +351,7 @@
RuleClass ruleClass = builder.build(ruleClassName);
PackageContext pkgContext = (PackageContext) env.lookup(PackageFactory.PKG_CONTEXT);
return RuleFactory.createAndAddRule(
- pkgContext, ruleClass, (Map<String, Object>) args[0], ast, env.getStackTrace());
+ pkgContext, ruleClass, (Map<String, Object>) args[0], ast, env);
} catch (InvalidRuleException | NameConflictException | NoSuchVariableException e) {
throw new EvalException(ast.getLocation(), e.getMessage());
}
@@ -373,8 +371,8 @@
}
}
- public static void exportRuleFunctions(SkylarkEnvironment env, PathFragment skylarkFile) {
- for (String name : env.getDirectVariableNames()) {
+ public static void exportRuleFunctions(Environment env, PathFragment skylarkFile) {
+ for (String name : env.getGlobals().getDirectVariableNames()) {
try {
Object value = env.lookup(name);
if (value instanceof RuleFunction) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
index 409b408..405abe4 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
@@ -34,11 +34,12 @@
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.ClassObject;
import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
+import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
import com.google.devtools.build.lib.syntax.EvalUtils;
+import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.Runtime;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.syntax.SkylarkType;
@@ -54,13 +55,19 @@
public static ConfiguredTarget buildRule(RuleContext ruleContext,
BaseFunction ruleImplementation) {
String expectFailure = ruleContext.attributes().get("expect_failure", Type.STRING);
- try {
+ try (Mutability mutability = Mutability.create("configured target")) {
SkylarkRuleContext skylarkRuleContext = new SkylarkRuleContext(ruleContext);
- SkylarkEnvironment env = ruleContext.getRule().getRuleClassObject()
- .getRuleDefinitionEnvironment().cloneEnv(
- ruleContext.getAnalysisEnvironment().getEventHandler());
- Object target = ruleImplementation.call(ImmutableList.<Object>of(skylarkRuleContext),
- ImmutableMap.<String, Object>of(), null, env);
+ Environment env = Environment.builder(mutability)
+ .setSkylark()
+ .setGlobals(
+ ruleContext.getRule().getRuleClassObject().getRuleDefinitionEnvironment().getGlobals())
+ .setEventHandler(ruleContext.getAnalysisEnvironment().getEventHandler())
+ .build(); // NB: we do *not* setLoadingPhase()
+ Object target = ruleImplementation.call(
+ ImmutableList.<Object>of(skylarkRuleContext),
+ ImmutableMap.<String, Object>of(),
+ /*ast=*/null,
+ env);
if (ruleContext.hasErrors()) {
return null;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ASTFileLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ASTFileLookupFunction.java
index 2b33904..fe44617 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ASTFileLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ASTFileLookupFunction.java
@@ -20,6 +20,9 @@
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.Mutability;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.ValidationEnvironment;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
@@ -113,26 +116,33 @@
if (lookupResult == null) {
return null;
}
-
- BuildFileAST ast = null;
if (!lookupResult.lookupSuccessful()) {
return ASTFileLookupValue.noFile();
- } else {
- Path path = lookupResult.rootedPath().asPath();
- // Skylark files end with bzl.
- boolean parseAsSkylark = astFilePathFragment.getPathString().endsWith(".bzl");
- try {
- ast = parseAsSkylark
- ? BuildFileAST.parseSkylarkFile(path, env.getListener(),
- packageManager, ruleClassProvider.getSkylarkValidationEnvironment().clone())
- : BuildFileAST.parseBuildFile(path, env.getListener(),
- packageManager, false);
- } catch (IOException e) {
+ }
+ BuildFileAST ast = null;
+ Path path = lookupResult.rootedPath().asPath();
+ // Skylark files end with bzl.
+ boolean parseAsSkylark = astFilePathFragment.getPathString().endsWith(".bzl");
+ try {
+ if (parseAsSkylark) {
+ try (Mutability mutability = Mutability.create("validate")) {
+ ast = BuildFileAST.parseSkylarkFile(path, env.getListener(),
+ packageManager, new ValidationEnvironment(
+ ruleClassProvider.createSkylarkRuleClassEnvironment(
+ mutability,
+ env.getListener(),
+ // the two below don't matter for extracting the ValidationEnvironment:
+ /*astFileContentHashCode=*/null,
+ /*importMap=*/null)
+ .setupDynamic(Runtime.PKG_NAME, Runtime.NONE)));
+ }
+ } else {
+ ast = BuildFileAST.parseBuildFile(path, env.getListener(), packageManager, false);
+ }
+ } catch (IOException e) {
throw new ASTLookupFunctionException(new ErrorReadingSkylarkExtensionException(
e.getMessage()), Transience.TRANSIENT);
- }
}
-
return ASTFileLookupValue.withFile(ast);
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index 5cb7685..12eea02 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -46,10 +46,10 @@
import com.google.devtools.build.lib.skyframe.GlobValue.InvalidGlobPatternException;
import com.google.devtools.build.lib.skyframe.SkylarkImportLookupFunction.SkylarkImportFailedException;
import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.Environment.Extension;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.ParserInputSource;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.syntax.Statement;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.Path;
@@ -553,7 +553,8 @@
if (eventHandler.hasErrors()) {
importResult =
new SkylarkImportResult(
- ImmutableMap.<PathFragment, SkylarkEnvironment>of(), ImmutableList.<Label>of());
+ ImmutableMap.<PathFragment, Extension>of(),
+ ImmutableList.<Label>of());
includeRepositoriesFetched = true;
} else {
importResult =
@@ -581,7 +582,7 @@
Environment env)
throws PackageFunctionException {
ImmutableMap<Location, PathFragment> imports = buildFileAST.getImports();
- Map<PathFragment, SkylarkEnvironment> importMap = new HashMap<>();
+ Map<PathFragment, Extension> importMap = new HashMap<>();
ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
try {
for (Map.Entry<Location, PathFragment> entry : imports.entrySet()) {
@@ -601,7 +602,7 @@
InconsistentFilesystemException.class, ASTLookupInputException.class,
BuildFileNotFoundException.class);
if (importLookupValue != null) {
- importMap.put(importFile, importLookupValue.getImportedEnvironment());
+ importMap.put(importFile, importLookupValue.getEnvironmentExtension());
fileDependencies.add(importLookupValue.getDependency());
}
}
@@ -882,9 +883,10 @@
/** A simple value class to store the result of the Skylark imports.*/
private static final class SkylarkImportResult {
- private final Map<PathFragment, SkylarkEnvironment> importMap;
+ private final Map<PathFragment, Extension> importMap;
private final ImmutableList<Label> fileDependencies;
- private SkylarkImportResult(Map<PathFragment, SkylarkEnvironment> importMap,
+ private SkylarkImportResult(
+ Map<PathFragment, Extension> importMap,
ImmutableList<Label> fileDependencies) {
this.importMap = importMap;
this.fileDependencies = fileDependencies;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
index 0bd16ae..bcf56c4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
@@ -25,9 +25,10 @@
import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions;
import com.google.devtools.build.lib.skyframe.ASTFileLookupValue.ASTLookupInputException;
import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.Environment.Extension;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Label.SyntaxException;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
+import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
@@ -76,7 +77,7 @@
throw new SkylarkImportLookupFunctionException(SkylarkImportFailedException.noFile(file));
}
- Map<PathFragment, SkylarkEnvironment> importMap = new HashMap<>();
+ Map<PathFragment, Extension> importMap = new HashMap<>();
ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
BuildFileAST ast = astLookupValue.getAST();
// TODO(bazel-team): Refactor this code and PackageFunction to reduce code duplications.
@@ -96,7 +97,7 @@
importsLookupValue = (SkylarkImportLookupValue) env.getValueOrThrow(
importsLookupKey, ASTLookupInputException.class);
if (importsLookupValue != null) {
- importMap.put(importFile, importsLookupValue.getImportedEnvironment());
+ importMap.put(importFile, importsLookupValue.getEnvironmentExtension());
fileDependencies.add(importsLookupValue.getDependency());
}
} catch (ASTLookupInputException e) {
@@ -114,12 +115,13 @@
file));
}
- SkylarkEnvironment extensionEnv = createEnv(ast, file, importMap, env);
- // Skylark UserDefinedFunctions are sharing function definition Environments, so it's extremely
- // important not to modify them from this point. Ideally they should be only used to import
- // symbols and serve as global Environments of UserDefinedFunctions.
+ // Skylark UserDefinedFunction-s in that file will share this function definition Environment,
+ // which will be frozen by the time it is returned by createExtension.
+ Extension extension =
+ createExtension(ast, file, importMap, env);
+
return new SkylarkImportLookupValue(
- extensionEnv, new SkylarkFileDependency(label, fileDependencies.build()));
+ extension, new SkylarkFileDependency(label, fileDependencies.build()));
}
/**
@@ -164,29 +166,33 @@
}
/**
- * Creates the SkylarkEnvironment to be imported. After it's returned, the Environment
- * must not be modified.
+ * Creates the Extension to be imported.
*/
- private SkylarkEnvironment createEnv(BuildFileAST ast, PathFragment file,
- Map<PathFragment, SkylarkEnvironment> importMap, Environment env)
+ private Extension createExtension(
+ BuildFileAST ast,
+ PathFragment file,
+ Map<PathFragment, Extension> importMap,
+ Environment env)
throws InterruptedException, SkylarkImportLookupFunctionException {
StoredEventHandler eventHandler = new StoredEventHandler();
// TODO(bazel-team): this method overestimates the changes which can affect the
// Skylark RuleClass. For example changes to comments or unused functions can modify the hash.
// A more accurate - however much more complicated - way would be to calculate a hash based on
// the transitive closure of the accessible AST nodes.
- SkylarkEnvironment extensionEnv = ruleClassProvider
- .createSkylarkRuleClassEnvironment(eventHandler, ast.getContentHashCode());
- extensionEnv.update("native", packageFactory.getNativeModule());
- extensionEnv.setImportedExtensions(importMap);
- ast.exec(extensionEnv, eventHandler);
- SkylarkRuleClassFunctions.exportRuleFunctions(extensionEnv, file);
+ try (Mutability mutability = Mutability.create("importing %s", file)) {
+ com.google.devtools.build.lib.syntax.Environment extensionEnv =
+ ruleClassProvider.createSkylarkRuleClassEnvironment(
+ mutability, eventHandler, ast.getContentHashCode(), importMap)
+ .setupOverride("native", packageFactory.getNativeModule());
+ ast.exec(extensionEnv, eventHandler);
+ SkylarkRuleClassFunctions.exportRuleFunctions(extensionEnv, file);
- Event.replayEventsOn(env.getListener(), eventHandler.getEvents());
- if (eventHandler.hasErrors()) {
- throw new SkylarkImportLookupFunctionException(SkylarkImportFailedException.errors(file));
+ Event.replayEventsOn(env.getListener(), eventHandler.getEvents());
+ if (eventHandler.hasErrors()) {
+ throw new SkylarkImportLookupFunctionException(SkylarkImportFailedException.errors(file));
+ }
+ return new Extension(extensionEnv);
}
- return extensionEnv;
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupValue.java
index 020a426..0ac9fd4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupValue.java
@@ -18,8 +18,8 @@
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.PackageIdentifier.RepositoryName;
import com.google.devtools.build.lib.skyframe.ASTFileLookupValue.ASTLookupInputException;
+import com.google.devtools.build.lib.syntax.Environment.Extension;
import com.google.devtools.build.lib.syntax.LoadStatement;
-import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
@@ -30,7 +30,7 @@
*/
public class SkylarkImportLookupValue implements SkyValue {
- private final SkylarkEnvironment importedEnvironment;
+ private final Extension environmentExtension;
/**
* The immediate Skylark file dependency descriptor class corresponding to this value.
* Using this reference it's possible to reach the transitive closure of Skylark files
@@ -39,16 +39,16 @@
private final SkylarkFileDependency dependency;
public SkylarkImportLookupValue(
- SkylarkEnvironment importedEnvironment, SkylarkFileDependency dependency) {
- this.importedEnvironment = Preconditions.checkNotNull(importedEnvironment);
+ Extension environmentExtension, SkylarkFileDependency dependency) {
+ this.environmentExtension = Preconditions.checkNotNull(environmentExtension);
this.dependency = Preconditions.checkNotNull(dependency);
}
/**
- * Returns the imported SkylarkEnvironment.
+ * Returns the Extension
*/
- public SkylarkEnvironment getImportedEnvironment() {
- return importedEnvironment;
+ public Extension getEnvironmentExtension() {
+ return environmentExtension;
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
index 30f0c6a..ce45ca2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
@@ -20,6 +20,7 @@
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.WorkspaceFactory;
import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -62,18 +63,25 @@
Path repoWorkspace = workspaceRoot.getRoot().getRelative(workspaceRoot.getRelativePath());
Builder builder = new Builder(repoWorkspace,
packageFactory.getRuleClassProvider().getRunfilesPrefix());
- WorkspaceFactory parser = new WorkspaceFactory(
- builder, packageFactory.getRuleClassProvider(), installDir.getPathString());
- parser.parse(ParserInputSource.create(
- ruleClassProvider.getDefaultWorkspaceFile(), new PathFragment("DEFAULT.WORKSPACE")));
- if (!workspaceFileValue.exists()) {
- return new PackageValue(builder.build());
- }
+ try (Mutability mutability = Mutability.create("workspace %s", repoWorkspace)) {
+ WorkspaceFactory parser =
+ new WorkspaceFactory(
+ builder,
+ packageFactory.getRuleClassProvider(),
+ mutability,
+ installDir.getPathString());
+ parser.parse(
+ ParserInputSource.create(
+ ruleClassProvider.getDefaultWorkspaceFile(), new PathFragment("DEFAULT.WORKSPACE")));
+ if (!workspaceFileValue.exists()) {
+ return new PackageValue(builder.build());
+ }
- try {
- parser.parse(ParserInputSource.create(repoWorkspace));
- } catch (IOException e) {
- throw new WorkspaceFileFunctionException(e, Transience.TRANSIENT);
+ try {
+ parser.parse(ParserInputSource.create(repoWorkspace));
+ } catch (IOException e) {
+ throw new WorkspaceFileFunctionException(e, Transience.TRANSIENT);
+ }
}
return new PackageValue(builder.build());
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
index 8d5f48f..f3f7819 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
@@ -23,6 +23,7 @@
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.Type.ConversionException;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -59,7 +60,7 @@
// Provide optimized argument frobbing depending of FunctionSignature and CallerSignature
// (that FuncallExpression must supply), optimizing for the all-positional and all-keyword cases.
// Also, use better pure maps to minimize map O(n) re-creation events when processing keyword maps.
-public abstract class BaseFunction {
+public abstract class BaseFunction implements Serializable {
// The name of the function
private final String name;
@@ -407,35 +408,29 @@
* @param args a list of all positional arguments (as in *starArg)
* @param kwargs a map for key arguments (as in **kwArgs)
* @param ast the expression for this function's definition
- * @param parentEnv the lexical Environment for the function call
+ * @param env the Environment in the function is called
* @return the value resulting from evaluating the function with the given arguments
* @throws construction of EvalException-s containing source information.
*/
public Object call(List<Object> args,
@Nullable Map<String, Object> kwargs,
@Nullable FuncallExpression ast,
- @Nullable Environment parentEnv)
+ Environment env)
throws EvalException, InterruptedException {
- Environment env = getOrCreateChildEnvironment(parentEnv);
- env.addToStackTrace(new StackTraceElement(this, kwargs));
+ Preconditions.checkState(isConfigured(), "Function %s was not configured", getName());
+
+ // ast is null when called from Java (as there's no Skylark call site).
+ Location loc = ast == null ? Location.BUILTIN : ast.getLocation();
+
+ Object[] arguments = processArguments(args, kwargs, loc);
+ canonicalizeArguments(arguments, loc);
+
try {
- Preconditions.checkState(isConfigured(), "Function %s was not configured", getName());
-
- // ast is null when called from Java (as there's no Skylark call site).
- Location loc = ast == null ? location : ast.getLocation();
-
- Object[] arguments = processArguments(args, kwargs, loc);
- canonicalizeArguments(arguments, loc);
-
- try {
- return call(arguments, ast, env);
- } catch (EvalExceptionWithStackTrace ex) {
- throw updateStackTrace(ex, loc);
- } catch (EvalException | RuntimeException | InterruptedException ex) {
- throw updateStackTrace(new EvalExceptionWithStackTrace(ex, loc), loc);
- }
- } finally {
- env.removeStackTraceElement();
+ return call(arguments, ast, env);
+ } catch (EvalExceptionWithStackTrace ex) {
+ throw updateStackTrace(ex, loc);
+ } catch (EvalException | RuntimeException | InterruptedException ex) {
+ throw updateStackTrace(new EvalExceptionWithStackTrace(ex, loc), loc);
}
}
@@ -556,7 +551,7 @@
if (pos < howManyArgsToPrint - 1) {
builder.append(", ");
}
- }
+ }
builder.append(")");
return builder.toString();
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java
index e3634f1..4324069 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java
@@ -122,9 +122,11 @@
@Override
@Nullable
public Object call(Object[] args,
- @Nullable FuncallExpression ast, @Nullable Environment env)
+ FuncallExpression ast, Environment env)
throws EvalException, InterruptedException {
- final Location loc = (ast == null) ? location : ast.getLocation();
+ Preconditions.checkNotNull(ast);
+ Preconditions.checkNotNull(env);
+ Location loc = ast.getLocation();
// Add extra arguments, if needed
if (extraArgs != null) {
@@ -151,6 +153,7 @@
this.getClass().getName() + "#" + getName());
// Last but not least, actually make an inner call to the function with the resolved arguments.
try {
+ env.enterScope(this, ast, env.getGlobals());
return invokeMethod.invoke(this, args);
} catch (InvocationTargetException x) {
Throwable e = x.getCause();
@@ -191,6 +194,7 @@
throw badCallException(loc, e, args);
} finally {
Profiler.instance().completeTask(ProfilerTask.SKYLARK_BUILTIN_FN);
+ env.exitScope();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ClassObject.java b/src/main/java/com/google/devtools/build/lib/syntax/ClassObject.java
index 2d5cee7..f046132 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ClassObject.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ClassObject.java
@@ -23,6 +23,7 @@
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.events.Location;
+import java.io.Serializable;
import java.util.Map;
import javax.annotation.Nullable;
@@ -60,7 +61,7 @@
doc = "A special language element to support structs (i.e. simple value objects). "
+ "See the global <a href=\"globals.html#struct\">struct</a> function "
+ "for more details.")
- public class SkylarkClassObject implements ClassObject {
+ public class SkylarkClassObject implements ClassObject, Serializable {
/** Error message to use when errorMessage argument is null. */
private static final String DEFAULT_ERROR_MESSAGE = "'struct' object has no attribute '%s'";
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
index ed30671..1bfba3a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
@@ -15,73 +15,359 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.EventKind;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.CachingPackageLocator;
+import com.google.devtools.build.lib.syntax.Mutability.Freezable;
+import com.google.devtools.build.lib.syntax.Mutability.MutabilityException;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
+import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import javax.annotation.Nullable;
/**
- * The BUILD environment.
+ * An Environment is the main entry point to evaluating code in the BUILD language or Skylark.
+ * It embodies all the state that is required to evaluate such code,
+ * except for the current instruction pointer, which is an {@link ASTNode}
+ * whose {@link Statement#exec exec} or {@link Expression#eval eval} method is invoked with
+ * this Environment, in a straightforward direct-style AST-walking interpreter.
+ * {@link Continuation}-s are explicitly represented, but only partly, with another part being
+ * implicit in a series of try-catch statements, to maintain the direct style. One notable trick
+ * is how a {@link UserDefinedFunction} implements returning values as the function catching a
+ * {@link ReturnStatement.ReturnException} thrown by a {@link ReturnStatement} in the body.
+ *
+ * <p>Every Environment has a {@link Mutability} field, and must be used within a function that
+ * creates and closes this {@link Mutability} with the try-with-resource pattern.
+ * This {@link Mutability} is also used when initializing mutable objects within that Environment;
+ * when closed at the end of the computation freezes the Environment and all those objects that
+ * then become forever immutable. The pattern enforces the discipline that there should be no
+ * dangling mutable Environment, or concurrency between interacting Environment-s.
+ * It is also an error to try to mutate an Environment and its objects from another Environment,
+ * before the {@link Mutability} is closed.
+ *
+ * <p>One creates an Environment using the {@link #builder} function, then
+ * populates it with {@link #setup}, {@link #setupDynamic} and sometimes {@link #setupOverride},
+ * before to evaluate code in it with {@link #eval}, or with {@link BuildFileAST#exec}
+ * (where the AST was obtained by passing a {@link ValidationEnvironment} constructed from the
+ * Environment to {@link BuildFileAST#parseBuildFile} or {@link BuildFileAST#parseSkylarkFile}).
+ * When the computation is over, the frozen Environment can still be queried with {@link #lookup}.
+ *
+ * <p>Final fields of an Environment represent its dynamic state, i.e. state that remains the same
+ * throughout a given evaluation context, and don't change with source code location,
+ * while mutable fields embody its static state, that change with source code location.
+ * The seeming paradox is that the words "dynamic" and "static" refer to the point of view
+ * of the source code, and here we have a dual point of view.
*/
-public class Environment {
-
- protected final Map<String, Object> env = new HashMap<>();
+public final class Environment implements Freezable {
/**
- * The parent environment. For Skylark it's the global environment,
- * used for global read only variable lookup.
+ * A Frame is a Map of bindings, plus a {@link Mutability} and a parent Frame
+ * from which to inherit bindings.
+ *
+ * <p>A Frame contains bindings mapping variable name to variable value in a given scope.
+ * It may also inherit bindings from a parent Frame corresponding to a parent scope,
+ * which in turn may inherit bindings from its own parent, etc., transitively.
+ * Bindings may shadow bindings from the parent. In Skylark, you may only mutate
+ * bindings from the current Frame, which always got its {@link Mutability} with the
+ * current {@link Environment}; but future extensions may make it more like Python
+ * and allow mutation of bindings in outer Frame-s (or then again may not).
+ *
+ * <p>A Frame inherits the {@link Mutability} from the {@link Environment} in which it was
+ * originally created. When that {@link Environment} is finalized and its {@link Mutability}
+ * is closed, it becomes immutable, including the Frame, which can be shared in other
+ * {@link Environment}-s. Indeed, a {@link UserDefinedFunction} will close over the global
+ * Frame of its definition {@link Environment}, which will thus be reused (immutably)
+ * in all any {@link Environment} in which this function is called, so it's important to
+ * preserve the {@link Mutability} to make sure no Frame is modified after it's been finalized.
*/
- protected final Environment parent;
+ public static final class Frame implements Freezable {
+
+ private final Mutability mutability;
+ final Frame parent;
+ final Map<String, Object> bindings = new HashMap<>();
+
+ Frame(Mutability mutability, Frame parent) {
+ this.mutability = mutability;
+ this.parent = parent;
+ }
+
+ @Override
+ public final Mutability mutability() {
+ return mutability;
+ }
+
+ /**
+ * Gets a binding from the current frame or if not found its parent.
+ * @param varname the name of the variable to be bound
+ * @return the value bound to variable
+ */
+ public Object get(String varname) {
+ if (bindings.containsKey(varname)) {
+ return bindings.get(varname);
+ }
+ if (parent != null) {
+ return parent.get(varname);
+ }
+ return null;
+ }
+
+ /**
+ * Modifies a binding in the current Frame.
+ * Does not try to modify an inherited binding.
+ * This will shadow any inherited binding, which may be an error
+ * that you want to guard against before calling this function.
+ * @param env the Environment attempting the mutation
+ * @param varname the name of the variable to be bound
+ * @param value the value to bind to the variable
+ */
+ public void put(Environment env, String varname, Object value)
+ throws MutabilityException {
+ Mutability.checkMutable(this, env);
+ bindings.put(varname, value);
+ }
+
+ /**
+ * Adds the variable names of this Frame and its transitive parents to the given set.
+ * This provides a O(n) way of extracting the list of all variables visible in an Environment.
+ * @param vars the set of visible variables in the Environment, being computed.
+ */
+ public void addVariableNamesTo(Set<String> vars) {
+ vars.addAll(bindings.keySet());
+ if (parent != null) {
+ parent.addVariableNamesTo(vars);
+ }
+ }
+
+ public Set<String> getDirectVariableNames() {
+ return bindings.keySet();
+ }
+
+ @Override
+ public String toString() {
+ String prefix = "Frame";
+ StringBuilder sb = new StringBuilder();
+ for (Frame f = this; f != null; f = f.parent) {
+ Printer.formatTo(sb, "%s%s%r",
+ ImmutableList.<Object>of(prefix, f.mutability(), f.bindings));
+ prefix = "=>";
+ }
+ return sb.toString();
+ }
+ }
/**
- * Map from a Skylark extension to an environment, which contains all symbols defined in the
- * extension.
+ * A Continuation contains data saved during a function call and restored when the function exits.
*/
- protected Map<PathFragment, SkylarkEnvironment> importedExtensions;
+ private static final class Continuation {
+ /** The {@link BaseFunction} being evaluated that will return into this Continuation. */
+ BaseFunction function;
+
+ /** The {@link FuncallExpression} to which this Continuation will return. */
+ FuncallExpression caller;
+
+ /** The next Continuation after this Continuation. */
+ @Nullable Continuation continuation;
+
+ /** The lexical Frame of the caller. */
+ Frame lexicalFrame;
+
+ /** The global Frame of the caller. */
+ Frame globalFrame;
+
+ /** The set of known global variables of the caller. */
+ @Nullable Set<String> knownGlobalVariables;
+
+ /** Whether the caller is in Skylark mode. */
+ boolean isSkylark;
+
+ Continuation(
+ Continuation continuation,
+ BaseFunction function,
+ FuncallExpression caller,
+ Frame lexicalFrame,
+ Frame globalFrame,
+ Set<String> knownGlobalVariables,
+ boolean isSkylark) {
+ this.continuation = continuation;
+ this.function = function;
+ this.caller = caller;
+ this.lexicalFrame = lexicalFrame;
+ this.globalFrame = globalFrame;
+ this.isSkylark = isSkylark;
+ }
+ }
+
+ // TODO(bazel-team): Fix this scary failure of serializability.
+ // skyframe.SkylarkImportLookupFunction processes a .bzl and returns an Extension,
+ // for use by whoever imports the .bzl file. Skyframe may subsequently serialize the results.
+ // And it will fail to process these bindings, because they are inherited from a non-serializable
+ // class (in previous versions of the code the serializable SkylarkEnvironment was inheriting
+ // from the non-serializable Environment and being returned by said Function).
+ // If we try to merge this otherwise redundant superclass into Extension, though,
+ // skyframe experiences a massive failure to serialize things, and it's unclear how far
+ // reaching the need to make things Serializable goes, though clearly we'll need to make
+ // a whole lot of things Serializable, and for efficiency, we'll want to implement sharing
+ // of imported values rather than a code explosion.
+ private static class BaseExtension {
+ final ImmutableMap<String, Object> bindings;
+ BaseExtension(Environment env) {
+ this.bindings = ImmutableMap.copyOf(env.globalFrame.bindings);
+ }
+ }
/**
- * A set of variables propagating through function calling. It's only used to call
- * native rules from Skylark build extensions.
+ * An Extension to be imported with load() into a BUILD or .bzl file.
*/
- protected Set<String> propagatingVariables = new HashSet<>();
+ public static final class Extension extends BaseExtension implements Serializable {
- // Only used in the global environment.
- // TODO(bazel-team): make this a final field part of constructor.
- private boolean isLoadingPhase = false;
+ private final String transitiveContentHashCode;
+
+ /**
+ * Constructs an Extension by extracting the new global definitions from an Environment.
+ * Also caches a hash code for the transitive content of the file and its dependencies.
+ * @param env the Environment from which to extract an Extension.
+ */
+ public Extension(Environment env) {
+ super(env);
+ this.transitiveContentHashCode = env.getTransitiveContentHashCode();
+ }
+
+ @VisibleForTesting // This is only used in one test.
+ public String getTransitiveContentHashCode() {
+ return transitiveContentHashCode;
+ }
+
+ /** get the value bound to a variable in this Extension */
+ public Object get(String varname) {
+ return bindings.get(varname);
+ }
+
+ /** does this Extension contain a binding for the named variable? */
+ public boolean containsKey(String varname) {
+ return bindings.containsKey(varname);
+ }
+ }
+
+ /**
+ * Static Frame for lexical variables that are always looked up in the current Environment
+ * or for the definition Environment of the function currently being evaluated.
+ */
+ private Frame lexicalFrame;
+
+ /**
+ * Static Frame for global variables; either the current lexical Frame if evaluation is currently
+ * happening at the global scope of a BUILD file, or the global Frame at the time of function
+ * definition if evaluation is currently happening in the body of a function. Thus functions can
+ * close over other functions defined in the same file.
+ */
+ private Frame globalFrame;
+
+ /**
+ * Dynamic Frame for variables that are always looked up in the runtime Environment,
+ * and never in the lexical or "global" Environment as it was at the time of function definition.
+ * For instance, PACKAGE_NAME.
+ */
+ private final Frame dynamicFrame;
+
+ /**
+ * 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, so shouldn't be null.
+ */
+ private final EventHandler eventHandler;
+
+ /**
+ * For each imported extensions, a global Skylark frame from which to load() individual bindings.
+ */
+ private final Map<PathFragment, Extension> importedExtensions;
+
+ /**
+ * Is this Environment being executed in Skylark context?
+ */
+ private boolean isSkylark;
+
+ /**
+ * Is this Environment being executed during the loading phase?
+ * Many builtin functions are only enabled during the loading phase, and check this flag.
+ */
+ private final boolean isLoadingPhase;
+
+ /**
+ * When in a lexical (Skylark) Frame, this set contains the variable names that are global,
+ * as determined not by global declarations (not currently supported),
+ * but by previous lookups that ended being global or dynamic.
+ * 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.
+ */
+ @Nullable private Set<String> knownGlobalVariables;
+
+ /**
+ * When in a lexical (Skylark) frame, this lists the names of the functions in the call stack.
+ * We currently use it to artificially disable recursion.
+ */
+ @Nullable private Continuation continuation;
+
+ /**
+ * Enters a scope by saving state to a new Continuation
+ * @param function the function whose scope to enter
+ * @param caller the source AST node for the caller
+ * @param globals the global Frame that this function closes over from its definition Environment
+ */
+ void enterScope(BaseFunction function, FuncallExpression caller, Frame globals) {
+ continuation = new Continuation(
+ continuation, function, caller, lexicalFrame, globalFrame, knownGlobalVariables, isSkylark);
+ lexicalFrame = new Frame(mutability(), null);
+ globalFrame = globals;
+ knownGlobalVariables = new HashSet<String>();
+ isSkylark = true;
+ }
+
+ /**
+ * Exits a scope by restoring state from the current continuation
+ */
+ void exitScope() {
+ Preconditions.checkNotNull(continuation);
+ lexicalFrame = continuation.lexicalFrame;
+ globalFrame = continuation.globalFrame;
+ knownGlobalVariables = continuation.knownGlobalVariables;
+ isSkylark = continuation.isSkylark;
+ continuation = continuation.continuation;
+ }
+
+ /**
+ * When evaluating code from a file, this contains a hash of the file.
+ */
+ @Nullable private String fileContentHashCode;
/**
* Is this Environment being evaluated during the loading phase?
- * This is fixed during environment setup, and enables various functions
+ * This is fixed during Environment setup, and enables various functions
* that are not available during the analysis phase.
- * @return true if this environment corresponds to code during the loading phase.
+ * @return true if this Environment corresponds to code during the loading phase.
*/
- boolean isLoadingPhase() {
+ private boolean isLoadingPhase() {
return isLoadingPhase;
}
/**
- * Enable loading phase only functions in this Environment.
- * This should only be done during setup before code is evaluated.
- */
- public void setLoadingPhase() {
- isLoadingPhase = true;
- }
-
- /**
- * Checks that the current Evaluation context is in loading phase.
+ * Checks that the current Environment is in the loading phase.
* @param symbol name of the function being only authorized thus.
*/
public void checkLoadingPhase(String symbol, Location loc) throws EvalException {
@@ -91,168 +377,379 @@
}
/**
- * Is this a global environment?
- * @return true if this is a global (top-level) environment
- * as opposed to inside the body of a function
+ * Is this a global Environment?
+ * @return true if the current code is being executed at the top-level,
+ * as opposed to inside the body of a function.
*/
- public boolean isGlobal() {
- return true;
+ boolean isGlobal() {
+ return lexicalFrame == null;
}
/**
- * 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.
+ * Is the current code Skylark?
+ * @return true if Skylark was enabled when this code was read.
*/
- @Nullable protected EventHandler eventHandler;
-
- /**
- * A stack trace containing the current history of functions and the calling rule.
- *
- * <p>For the rule, the stack trace has two elements: one for the call to the rule in the BUILD
- * file and one for the actual rule implementation.
- */
- private Deque<StackTraceElement> stackTrace;
-
- /**
- * Constructs an empty root non-Skylark environment.
- * The root environment is also the global environment.
- */
- public Environment(Deque<StackTraceElement> stackTrace) {
- this.parent = null;
- this.importedExtensions = new HashMap<>();
- this.stackTrace = stackTrace;
- setupGlobal();
- }
-
- public Environment() {
- this(new LinkedList<StackTraceElement>());
+ // TODO(bazel-team): Delete this function.
+ // This function is currently used in various functions that change their behavior with respect to
+ // lists depending on the Skylark-ness of the code; lists should be unified between the two modes.
+ boolean isSkylark() {
+ return isSkylark;
}
/**
- * Constructs an empty child environment.
+ * Is the caller of the current function executing Skylark code?
+ * @return true if this is skylark was enabled when this code was read.
*/
- public Environment(Environment parent, Deque<StackTraceElement> stackTrace) {
- Preconditions.checkNotNull(parent);
- this.parent = parent;
- this.importedExtensions = new HashMap<>();
- this.stackTrace = stackTrace;
+ // TODO(bazel-team): Delete this function.
+ // This function is currently used by MethodLibrary to modify behavior of lists
+ // depending on the Skylark-ness of the code; lists should be unified between the two modes.
+ boolean isCallerSkylark() {
+ return continuation.isSkylark;
}
- public Environment(Environment parent) {
- this(parent, new LinkedList<StackTraceElement>());
+ @Override
+ public Mutability mutability() {
+ // the mutability of the environment is that of its dynamic frame.
+ return dynamicFrame.mutability();
}
/**
- * Constructs an empty child environment with an EventHandler.
+ * @return the current Frame, in which variable side-effects happen.
*/
- public Environment(Environment parent, EventHandler eventHandler) {
- this(parent);
- this.eventHandler = Preconditions.checkNotNull(eventHandler);
+ private Frame currentFrame() {
+ return isGlobal() ? globalFrame : lexicalFrame;
}
+ /**
+ * @return the global variables for the Environment (not including dynamic bindings).
+ */
+ public Frame getGlobals() {
+ return globalFrame;
+ }
+
+ /**
+ * Returns an EventHandler for errors and warnings.
+ * The BUILD language doesn't use it directly, but can call Skylark code that does use it.
+ * @return an EventHandler
+ */
public EventHandler getEventHandler() {
return 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.
- update("False", Runtime.FALSE);
- update("True", Runtime.TRUE);
- update("None", Runtime.NONE);
+ /**
+ * @return the current stack trace as a list of functions.
+ */
+ public ImmutableList<BaseFunction> getStackTrace() {
+ ImmutableList.Builder<BaseFunction> builder = new ImmutableList.Builder<>();
+ for (Continuation k = continuation; k != null; k = k.continuation) {
+ builder.add(k.function);
+ }
+ return builder.build().reverse();
}
- public boolean isSkylark() {
- return false;
+
+ /**
+ * Returns the FuncallExpression and the BaseFunction for the top-level call being evaluated.
+ */
+ public Pair<FuncallExpression, BaseFunction> getTopCall() {
+ Continuation continuation = this.continuation;
+ if (continuation == null) {
+ return null;
+ }
+ while (continuation.continuation != null) {
+ continuation = continuation.continuation;
+ }
+ return new Pair<>(continuation.caller, continuation.function);
}
- protected boolean hasVariable(String varname) {
- return env.containsKey(varname);
+ /**
+ * Constructs an Environment.
+ * This is the main, most basic constructor.
+ * @param globalFrame a frame for the global Environment
+ * @param dynamicFrame a frame for the dynamic Environment
+ * @param eventHandler an EventHandler for warnings, errors, etc
+ * @param importedExtensions Extension-s from which to import bindings with load()
+ * @param isSkylark true if in Skylark context
+ * @param fileContentHashCode a hash for the source file being evaluated, if any
+ * @param isLoadingPhase true if in loading phase
+ */
+ private Environment(
+ Frame globalFrame,
+ Frame dynamicFrame,
+ EventHandler eventHandler,
+ Map<PathFragment, Extension> importedExtensions,
+ boolean isSkylark,
+ @Nullable String fileContentHashCode,
+ boolean isLoadingPhase) {
+ this.globalFrame = Preconditions.checkNotNull(globalFrame);
+ this.dynamicFrame = Preconditions.checkNotNull(dynamicFrame);
+ Preconditions.checkArgument(globalFrame.mutability().isMutable());
+ Preconditions.checkArgument(dynamicFrame.mutability().isMutable());
+ this.eventHandler = eventHandler;
+ this.importedExtensions = importedExtensions;
+ this.isSkylark = isSkylark;
+ this.fileContentHashCode = fileContentHashCode;
+ this.isLoadingPhase = isLoadingPhase;
+ }
+
+ /**
+ * A Builder class for Environment
+ */
+ public static class Builder {
+ private final Mutability mutability;
+ private boolean isSkylark = false;
+ private boolean isLoadingPhase = false;
+ @Nullable private Frame parent;
+ @Nullable private EventHandler eventHandler;
+ @Nullable private Map<PathFragment, Extension> importedExtensions;
+ @Nullable private String fileContentHashCode;
+
+ Builder(Mutability mutability) {
+ this.mutability = mutability;
+ }
+
+ /** Enables Skylark for code read in this Environment. */
+ public Builder setSkylark() {
+ Preconditions.checkState(!isSkylark);
+ isSkylark = true;
+ return this;
+ }
+
+ /** Enables loading phase only functions in this Environment. */
+ public Builder setLoadingPhase() {
+ Preconditions.checkState(!isLoadingPhase);
+ isLoadingPhase = true;
+ return this;
+ }
+
+ /** Inherits global bindings from the given parent Frame. */
+ public Builder setGlobals(Frame parent) {
+ Preconditions.checkState(this.parent == null);
+ this.parent = parent;
+ return this;
+ }
+
+ /** Sets an EventHandler for errors and warnings. */
+ public Builder setEventHandler(EventHandler eventHandler) {
+ Preconditions.checkState(this.eventHandler == null);
+ this.eventHandler = eventHandler;
+ return this;
+ }
+
+ /** Declares imported extensions for load() statements. */
+ public Builder setImportedExtensions (Map<PathFragment, Extension> importedExtensions) {
+ Preconditions.checkState(this.importedExtensions == null);
+ this.importedExtensions = importedExtensions;
+ return this;
+ }
+
+ /** Declares content hash for the source file for this Environment. */
+ public Builder setFileContentHashCode(String fileContentHashCode) {
+ this.fileContentHashCode = fileContentHashCode;
+ return this;
+ }
+
+ /** Builds the Environment. */
+ public Environment build() {
+ Preconditions.checkArgument(mutability.isMutable());
+ if (parent != null) {
+ Preconditions.checkArgument(!parent.mutability().isMutable());
+ }
+ Frame globalFrame = new Frame(mutability, parent);
+ Frame dynamicFrame = new Frame(mutability, null);
+ if (importedExtensions == null) {
+ importedExtensions = ImmutableMap.of();
+ }
+ Environment env = new Environment(
+ globalFrame,
+ dynamicFrame,
+ eventHandler,
+ importedExtensions,
+ isSkylark,
+ fileContentHashCode,
+ isLoadingPhase);
+ return env;
+ }
+ }
+
+ public static Builder builder(Mutability mutability) {
+ return new Builder(mutability);
+ }
+
+ /**
+ * Sets a binding for a special dynamic variable in this Environment.
+ * This is not for end-users, and will throw an AssertionError in case of conflict.
+ * @param varname the name of the dynamic variable to be bound
+ * @param value a value to bind to the variable
+ * @return this Environment, in fluid style
+ */
+ public Environment setupDynamic(String varname, Object value) {
+ if (dynamicFrame.get(varname) != null) {
+ throw new AssertionError(
+ String.format("Trying to bind dynamic variable '%s' but it is already bound",
+ varname));
+ }
+ if (lexicalFrame != null && lexicalFrame.get(varname) != null) {
+ throw new AssertionError(
+ String.format("Trying to bind dynamic variable '%s' but it is already bound lexically",
+ varname));
+ }
+ if (globalFrame.get(varname) != null) {
+ throw new AssertionError(
+ String.format("Trying to bind dynamic variable '%s' but it is already bound globally",
+ varname));
+ }
+ try {
+ dynamicFrame.put(this, varname, value);
+ } catch (MutabilityException e) {
+ // End users don't have access to setupDynamic, and it is an implementation error
+ // if we encounter a mutability exception.
+ throw new AssertionError(
+ Printer.format(
+ "Trying to bind dynamic variable '%s' in frozen environment %r", varname, this),
+ e);
+ }
+ return this;
+ }
+
+
+ /**
+ * Modifies a binding in the current Frame of this Environment, as would an
+ * {@link AssignmentStatement}. Does not try to modify an inherited binding.
+ * This will shadow any inherited binding, which may be an error
+ * that you want to guard against before calling this function.
+ * @param varname the name of the variable to be bound
+ * @param value the value to bind to the variable
+ * @return this Environment, in fluid style
+ */
+ public Environment update(String varname, Object value) throws EvalException {
+ Preconditions.checkNotNull(value, "update(value == null)");
+ // prevents clashes between static and dynamic variables.
+ if (dynamicFrame.get(varname) != null) {
+ throw new EvalException(
+ null, String.format("Trying to update special read-only global variable '%s'", varname));
+ }
+ if (isKnownGlobalVariable(varname)) {
+ throw new EvalException(
+ null, String.format("Trying to update read-only global variable '%s'", varname));
+ }
+ try {
+ currentFrame().put(this, varname, Preconditions.checkNotNull(value));
+ } catch (MutabilityException e) {
+ // Note that since at this time we don't accept the global keyword, and don't have closures,
+ // end users should never be able to mutate a frozen Environment, and a MutabilityException
+ // is therefore a failed assertion for Bazel. However, it is possible to shadow a binding
+ // imported from a parent Environment by updating the current Environment, which will not
+ // trigger a MutabilityException.
+ throw new AssertionError(
+ Printer.format("Can't update %s to %r in frozen environment", varname, value),
+ e);
+ }
+ return this;
+ }
+
+ private boolean hasVariable(String varname) {
+ try {
+ lookup(varname);
+ return true;
+ } catch (NoSuchVariableException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Initializes a binding in this Environment. It is an error if the variable is already bound.
+ * This is not for end-users, and will throw an AssertionError in case of conflict.
+ * @param varname the name of the variable to be bound
+ * @param value the value to bind to the variable
+ * @return this Environment, in fluid style
+ */
+ public Environment setup(String varname, Object value) {
+ if (hasVariable(varname)) {
+ throw new AssertionError(String.format("variable '%s' already bound", varname));
+ }
+ return setupOverride(varname, value);
+ }
+
+ /**
+ * Initializes a binding in this environment. Overrides any previous binding.
+ * This is not for end-users, and will throw an AssertionError in case of conflict.
+ * @param varname the name of the variable to be bound
+ * @param value the value to bind to the variable
+ * @return this Environment, in fluid style
+ */
+ public Environment setupOverride(String varname, Object value) {
+ try {
+ return update(varname, value);
+ } catch (EvalException ee) {
+ throw new AssertionError(ee);
+ }
}
/**
* @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 {
- Object value = env.get(varname);
- if (value == null) {
- if (parent != null) {
- return parent.lookup(varname);
+ // Which Frame to lookup first doesn't matter because update prevents clashes.
+ if (lexicalFrame != null) {
+ Object lexicalValue = lexicalFrame.get(varname);
+ if (lexicalValue != null) {
+ return lexicalValue;
}
+ }
+ Object globalValue = globalFrame.get(varname);
+ Object dynamicValue = dynamicFrame.get(varname);
+ if (globalValue == null && dynamicValue == null) {
throw new NoSuchVariableException(varname);
}
- return value;
+ if (knownGlobalVariables != null) {
+ knownGlobalVariables.add(varname);
+ }
+ if (globalValue != null) {
+ return globalValue;
+ }
+ return dynamicValue;
}
/**
- * Like <code>lookup(String)</code>, but instead of throwing an exception in
- * the case where "varname" is not defined, "defaultValue" is returned instead.
- *
+ * Like {@link #lookup(String)}, but instead of throwing an exception in the case
+ * where <code>varname</code> is not defined, <code>defaultValue</code> 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);
- }
+ Preconditions.checkState(!isSkylark);
+ try {
+ return lookup(varname);
+ } catch (NoSuchVariableException e) {
return defaultValue;
}
- return value;
}
/**
- * Updates the value of variable "varname" in the environment, corresponding
- * to an {@link AssignmentStatement}.
+ * @return true if varname is a known global variable,
+ * because it has been read in the context of the current function.
*/
- public Environment update(String varname, Object value) {
- Preconditions.checkNotNull(value, "update(value == null)");
- env.put(varname, value);
- return this;
+ boolean isKnownGlobalVariable(String varname) {
+ return knownGlobalVariables != null && knownGlobalVariables.contains(varname);
+ }
+
+ public void handleEvent(Event event) {
+ eventHandler.handle(event);
}
/**
- * 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 directly defined in this environment.
- */
- public Set<String> getDirectVariableNames() {
- return env.keySet();
- }
-
- /**
- * Returns the (immutable) set of names of all variables defined in this
- * environment. Exposed for testing; not very efficient!
+ * @return the (immutable) set of names of all variables defined in this
+ * Environment. Exposed for testing.
*/
@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;
+ Set<String> vars = new HashSet<>();
+ if (lexicalFrame != null) {
+ lexicalFrame.addVariableNamesTo(vars);
}
+ globalFrame.addVariableNamesTo(vars);
+ dynamicFrame.addVariableNamesTo(vars);
+ return vars;
}
@Override
@@ -268,23 +765,29 @@
@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);
- }
+ out.append("Environment(lexicalFrame=");
+ out.append(lexicalFrame);
+ out.append(", globalFrame=");
+ out.append(globalFrame);
+ out.append(", dynamicFrame=");
+ out.append(dynamicFrame);
+ out.append(", eventHandler.getClass()=");
+ out.append(eventHandler.getClass());
+ out.append(", importedExtensions=");
+ out.append(importedExtensions);
+ out.append(", isSkylark=");
+ out.append(isSkylark);
+ out.append(", fileContentHashCode=");
+ out.append(fileContentHashCode);
+ out.append(", isLoadingPhase=");
+ out.append(isLoadingPhase);
+ out.append(")");
return out.toString();
}
/**
- * An exception thrown when an attempt is made to lookup a non-existent
- * variable in the environment.
+ * 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) {
@@ -293,88 +796,175 @@
}
/**
- * An exception thrown when an attempt is made to import a symbol from a file
+ * 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");
+ LoadFailedException(PathFragment extension) {
+ super(String.format("file '%s' was not correctly loaded. "
+ + "Make sure the 'load' statement appears in the global scope in your file",
+ extension));
}
}
- public void setImportedExtensions(Map<PathFragment, SkylarkEnvironment> importedExtensions) {
- this.importedExtensions = importedExtensions;
- }
-
public void importSymbol(PathFragment extension, Identifier symbol, String nameInLoadedFile)
throws NoSuchVariableException, LoadFailedException {
+ Preconditions.checkState(isGlobal()); // loading is only allowed at global scope.
+
if (!importedExtensions.containsKey(extension)) {
- throw new LoadFailedException(extension.toString());
+ throw new LoadFailedException(extension);
}
- Object value = importedExtensions.get(extension).lookup(nameInLoadedFile);
- if (!isSkylark()) {
+ Extension ext = importedExtensions.get(extension);
+
+ // TODO(bazel-team): Throw a LoadFailedException instead, with an appropriate message.
+ // Throwing a NoSuchVariableException is backward compatible, but backward.
+ if (!ext.containsKey(nameInLoadedFile)) {
+ throw new NoSuchVariableException(nameInLoadedFile);
+ }
+
+ Object value = ext.get(nameInLoadedFile);
+ // TODO(bazel-team): Unify data structures between Skylark and BUILD,
+ // and stop doing the conversions below:
+ if (!isSkylark) {
value = SkylarkType.convertFromSkylark(value);
}
- update(symbol.getName(), value);
- }
-
- public ImmutableList<StackTraceElement> getStackTrace() {
- return ImmutableList.copyOf(stackTrace);
- }
-
- protected Deque<StackTraceElement> getCopyOfStackTrace() {
- return new LinkedList<>(stackTrace);
- }
-
- /**
- * Adds the given element to the stack trace (iff the stack is empty) and returns whether it was
- * successful.
- */
- public boolean tryAddingStackTraceRoot(StackTraceElement element) {
- if (stackTrace.isEmpty()) {
- stackTrace.add(element);
- return true;
+ try {
+ update(symbol.getName(), value);
+ } catch (EvalException e) {
+ throw new LoadFailedException(extension);
}
- return false;
- }
-
- public void addToStackTrace(StackTraceElement element) {
- stackTrace.add(element);
}
/**
- * Removes the only remaining element from the stack trace.
- *
- * <p>This particular element describes the outer-most calling function (usually a rule).
- *
- * <p> This method is required since {@link FuncallExpression} does not create a new {@link
- * Environment}, hence it has to add and remove its {@link StackTraceElement} from an existing
- * one.
+ * Returns a hash code calculated from the hash code of this Environment and the
+ * transitive closure of other Environments it loads.
*/
- public void removeStackTraceRoot() {
- Preconditions.checkArgument(stackTrace.size() == 1);
- stackTrace.clear();
+ public String getTransitiveContentHashCode() {
+ // Calculate a new hash from the hash of the loaded Extension-s.
+ Fingerprint fingerprint = new Fingerprint();
+ fingerprint.addString(Preconditions.checkNotNull(fileContentHashCode));
+ TreeSet<PathFragment> paths = new TreeSet<>(importedExtensions.keySet());
+ for (PathFragment path : paths) {
+ fingerprint.addString(importedExtensions.get(path).getTransitiveContentHashCode());
+ }
+ return fingerprint.hexDigestAndReset();
}
- public void removeStackTraceElement() {
- // TODO(fwe): find out why the precond doesn't work
- // Preconditions.checkArgument(stackTrace.size() > 1);
- stackTrace.removeLast();
+
+ /** A read-only Environment.Frame with global constants in it only */
+ public static final Frame CONSTANTS_ONLY = createConstantsGlobals();
+
+ /** A read-only Environment.Frame with initial globals for the BUILD language */
+ public static final Frame BUILD = createBuildGlobals();
+
+ /** A read-only Environment.Frame with initial globals for Skylark */
+ public static final Frame SKYLARK = createSkylarkGlobals();
+
+ private static Environment.Frame createConstantsGlobals() {
+ try (Mutability mutability = Mutability.create("CONSTANTS")) {
+ Environment env = Environment.builder(mutability).build();
+ Runtime.setupConstants(env);
+ return env.getGlobals();
+ }
+ }
+
+ private static Environment.Frame createBuildGlobals() {
+ try (Mutability mutability = Mutability.create("BUILD")) {
+ Environment env = Environment.builder(mutability).build();
+ Runtime.setupConstants(env);
+ Runtime.setupMethodEnvironment(env, MethodLibrary.buildGlobalFunctions);
+ return env.getGlobals();
+ }
+ }
+
+ private static Environment.Frame createSkylarkGlobals() {
+ try (Mutability mutability = Mutability.create("SKYLARK")) {
+ Environment env = Environment.builder(mutability).setSkylark().build();
+ Runtime.setupConstants(env);
+ Runtime.setupMethodEnvironment(env, MethodLibrary.skylarkGlobalFunctions);
+ return env.getGlobals();
+ }
+ }
+
+
+ /**
+ * The fail fast handler, which throws a AssertionError whenever an error or warning occurs.
+ */
+ public static final EventHandler FAIL_FAST_HANDLER = new EventHandler() {
+ @Override
+ public void handle(Event event) {
+ Preconditions.checkArgument(
+ !EventKind.ERRORS_AND_WARNINGS.contains(event.getKind()), event);
+ }
+ };
+
+ /** Mock package locator class */
+ private static final class EmptyPackageLocator implements CachingPackageLocator {
+ @Override
+ public Path getBuildFileForPackage(PackageIdentifier packageName) {
+ return null;
+ }
+ }
+
+ /** A mock package locator */
+ @VisibleForTesting
+ static final CachingPackageLocator EMPTY_PACKAGE_LOCATOR = new EmptyPackageLocator();
+
+ /**
+ * Creates a Lexer without a supporting file.
+ * @param input a list of lines of code
+ */
+ @VisibleForTesting
+ Lexer createLexer(String... input) {
+ return new Lexer(ParserInputSource.create(Joiner.on("\n").join(input), null),
+ eventHandler);
}
/**
- * Returns whether the given {@link BaseFunction} is part of this {@link Environment}'s stack
- * trace.
+ * Parses some String input without a supporting file, returning statements and comments.
+ * @param input a list of lines of code
*/
- public boolean stackTraceContains(BaseFunction function) {
- for (StackTraceElement element : stackTrace) {
- if (element.hasFunction(function)) {
- return true;
+ @VisibleForTesting
+ Parser.ParseResult parseFileWithComments(String... input) {
+ return isSkylark
+ ? Parser.parseFileForSkylark(
+ createLexer(input),
+ eventHandler,
+ EMPTY_PACKAGE_LOCATOR,
+ new ValidationEnvironment(this))
+ : Parser.parseFile(
+ createLexer(input),
+ eventHandler,
+ EMPTY_PACKAGE_LOCATOR,
+ /*parsePython=*/false);
+ }
+
+ /**
+ * Parses some String input without a supporting file, returning statements only.
+ * @param input a list of lines of code
+ */
+ @VisibleForTesting
+ List<Statement> parseFile(String... input) {
+ return parseFileWithComments(input).statements;
+ }
+
+ /**
+ * Evaluates code some String input without a supporting file.
+ * @param input a list of lines of code to evaluate
+ * @return the value of the last statement if it's an Expression or else null
+ */
+ @Nullable public Object eval(String... input) throws EvalException, InterruptedException {
+ Object last = null;
+ for (Statement statement : parseFile(input)) {
+ if (statement instanceof ExpressionStatement) {
+ last = ((ExpressionStatement) statement).getExpression().eval(this);
+ } else {
+ statement.exec(this);
+ last = null;
}
}
- return false;
+ return last;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalExceptionWithStackTrace.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalExceptionWithStackTrace.java
index 8688465..e3a1978 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalExceptionWithStackTrace.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalExceptionWithStackTrace.java
@@ -213,7 +213,7 @@
return String.format(
"\tFile \"%s\", line %d, in %s%n\t\t%s",
printPath(location.getPath()),
- location.getStartLineAndColumn().getLine(),
+ location.getStartLine(),
element.getLabel(),
element.getCause().getLabel());
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvaluationContext.java b/src/main/java/com/google/devtools/build/lib/syntax/EvaluationContext.java
deleted file mode 100644
index 6a24cb3..0000000
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvaluationContext.java
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2015 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.Joiner;
-import com.google.common.base.Preconditions;
-import com.google.devtools.build.lib.cmdline.PackageIdentifier;
-import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.lib.events.EventHandler;
-import com.google.devtools.build.lib.events.EventKind;
-import com.google.devtools.build.lib.packages.CachingPackageLocator;
-import com.google.devtools.build.lib.syntax.Environment.NoSuchVariableException;
-import com.google.devtools.build.lib.vfs.Path;
-
-import java.util.List;
-
-import javax.annotation.Nullable;
-
-/**
- * Context for the evaluation of programs.
- */
-public final class EvaluationContext {
-
- @Nullable private EventHandler eventHandler;
- private Environment env;
- @Nullable private ValidationEnvironment validationEnv;
- private boolean parsePython;
-
- private EvaluationContext(EventHandler eventHandler, Environment env,
- @Nullable ValidationEnvironment validationEnv, boolean parsePython) {
- this.eventHandler = eventHandler;
- this.env = env;
- this.validationEnv = validationEnv;
- this.parsePython = parsePython;
- }
-
- /**
- * The fail fast handler, which throws a runtime exception whenever we encounter an error event.
- */
- public static final EventHandler FAIL_FAST_HANDLER = new EventHandler() {
- @Override
- public void handle(Event event) {
- Preconditions.checkArgument(
- !EventKind.ERRORS_AND_WARNINGS.contains(event.getKind()), event);
- }
- };
-
- public static EvaluationContext newBuildContext(EventHandler eventHandler, Environment env,
- boolean parsePython) {
- return new EvaluationContext(eventHandler, env, null, parsePython);
- }
-
- public static EvaluationContext newBuildContext(EventHandler eventHandler, Environment env) {
- return newBuildContext(eventHandler, env, false);
- }
-
- public static EvaluationContext newBuildContext(EventHandler eventHandler) {
- return newBuildContext(eventHandler, new Environment());
- }
-
- public static EvaluationContext newSkylarkContext(
- Environment env, ValidationEnvironment validationEnv) {
- return new EvaluationContext(env.getEventHandler(), env, validationEnv, false);
- }
-
- public static EvaluationContext newSkylarkContext(EventHandler eventHandler) {
- return newSkylarkContext(new SkylarkEnvironment(eventHandler), new ValidationEnvironment());
- }
-
- /** Base context for Skylark evaluation for internal use only, while initializing builtins */
- static final EvaluationContext SKYLARK_INITIALIZATION = newSkylarkContext(FAIL_FAST_HANDLER);
-
- @VisibleForTesting
- public Environment getEnvironment() {
- return env;
- }
-
- /** Mock package locator */
- private static final class EmptyPackageLocator implements CachingPackageLocator {
- @Override
- public Path getBuildFileForPackage(PackageIdentifier packageName) {
- return null;
- }
- }
-
- /** An empty package locator */
- private static final CachingPackageLocator EMPTY_PACKAGE_LOCATOR = new EmptyPackageLocator();
-
- /** Create a Lexer without a supporting file */
- @VisibleForTesting
- Lexer createLexer(String... input) {
- return new Lexer(ParserInputSource.create(Joiner.on("\n").join(input), null),
- eventHandler);
- }
-
- /** Is this a Skylark evaluation context? */
- public boolean isSkylark() {
- return env.isSkylark();
- }
-
- /** Parse a string without a supporting file, returning statements and comments */
- @VisibleForTesting
- Parser.ParseResult parseFileWithComments(String... input) {
- return isSkylark()
- ? Parser.parseFileForSkylark(createLexer(input), eventHandler, null, validationEnv)
- : Parser.parseFile(createLexer(input), eventHandler, EMPTY_PACKAGE_LOCATOR, parsePython);
- }
-
- /** Parse a string without a supporting file, returning statements only */
- @VisibleForTesting
- List<Statement> parseFile(String... input) {
- return parseFileWithComments(input).statements;
- }
-
- /** Parse an Expression from string without a supporting file */
- @VisibleForTesting
- Expression parseExpression(String... input) {
- return Parser.parseExpression(createLexer(input), eventHandler);
- }
-
- /** Evaluate an Expression */
- @VisibleForTesting
- Object evalExpression(Expression expression) throws EvalException, InterruptedException {
- return expression.eval(env);
- }
-
- /** Evaluate an Expression as parsed from String-s */
- Object evalExpression(String... input) throws EvalException, InterruptedException {
- return evalExpression(parseExpression(input));
- }
-
- /** Parse a build (not Skylark) Statement from string without a supporting file */
- @VisibleForTesting
- Statement parseStatement(String... input) {
- return Parser.parseStatement(createLexer(input), eventHandler);
- }
-
- /**
- * Evaluate a Statement
- * @param statement the Statement
- * @return the value of the evaluation, if it's an Expression, or else null
- */
- @Nullable private Object eval(Statement statement) throws EvalException, InterruptedException {
- if (statement instanceof ExpressionStatement) {
- return evalExpression(((ExpressionStatement) statement).getExpression());
- }
- statement.exec(env);
- return null;
- }
-
- /**
- * Evaluate a list of Statement-s
- * @return the value of the last statement if it's an Expression or else null
- */
- @Nullable private Object eval(List<Statement> statements)
- throws EvalException, InterruptedException {
- Object last = null;
- for (Statement statement : statements) {
- last = eval(statement);
- }
- return last;
- }
-
- /** Update a variable in the environment, in fluent style */
- public EvaluationContext update(String varname, Object value) throws EvalException {
- env.update(varname, value);
- if (validationEnv != null) {
- validationEnv.declare(varname, null);
- }
- return this;
- }
-
- /** Lookup a variable in the environment */
- public Object lookup(String varname) throws NoSuchVariableException {
- return env.lookup(varname);
- }
-
- /** Evaluate a series of statements */
- public Object eval(String... input) throws EvalException, InterruptedException {
- return eval(parseFile(input));
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Expression.java b/src/main/java/com/google/devtools/build/lib/syntax/Expression.java
index b7842d4..1886843 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Expression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Expression.java
@@ -42,8 +42,8 @@
/**
* Returns the inferred type of the result of the Expression.
*
- * <p>Checks the semantics of the Expression using the SkylarkEnvironment according to
- * the rules of the Skylark language, throws EvalException in case of a semantical error.
+ * <p>Checks the semantics of the Expression using the {@link Environment} according to
+ * the rules of the Skylark language, throws {@link EvalException} in case of a semantical error.
*
* @see Statement
*/
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
index 2ef5d83..1e4497b 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
@@ -36,6 +36,8 @@
import java.util.Map;
import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+
/**
* Syntax node for a function call expression.
*/
@@ -125,6 +127,7 @@
return methodMap.build();
}
+ @Nullable
private static SkylarkCallable getAnnotationFromParentClass(Class<?> classObj, Method method) {
boolean keepLooking = false;
try {
@@ -167,7 +170,7 @@
}
}
- private final Expression obj;
+ @Nullable private final Expression obj;
private final Identifier func;
@@ -182,7 +185,7 @@
* arbitrary expressions. In any case, the "func" expression is always
* evaluated, so functions and variables share a common namespace.
*/
- public FuncallExpression(Expression obj, Identifier func,
+ public FuncallExpression(@Nullable Expression obj, Identifier func,
List<Argument.Passed> args) {
this.obj = obj;
this.func = func;
@@ -353,21 +356,11 @@
// TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
// matching methods, it still can be a problem. Figure out how the Java compiler does it
// exactly and copy that behaviour.
- // TODO(bazel-team): check if this and SkylarkBuiltInFunctions.createObject can be merged.
- private Object invokeJavaMethod(
- Object obj, Class<?> objClass, String methodName, List<Object> args, boolean hasKwArgs)
- throws EvalException {
+ private MethodDescriptor findJavaMethod(
+ Class<?> objClass, String methodName, List<Object> args) throws EvalException {
MethodDescriptor matchingMethod = null;
List<MethodDescriptor> methods = getMethods(objClass, methodName, args.size(), getLocation());
if (methods != null) {
- if (hasKwArgs) {
- throw new EvalException(
- func.getLocation(),
- String.format(
- "Keyword arguments are not allowed when calling a java method"
- + "\nwhile calling method '%s' on object of type %s",
- func.getName(), EvalUtils.getDataTypeNameFromClass(objClass)));
- }
for (MethodDescriptor method : methods) {
Class<?>[] params = method.getMethod().getParameterTypes();
int i = 0;
@@ -384,7 +377,7 @@
matchingMethod = method;
} else {
throw new EvalException(
- func.getLocation(),
+ getLocation(),
String.format(
"Type %s has multiple matches for %s",
EvalUtils.getDataTypeNameFromClass(objClass),
@@ -394,15 +387,14 @@
}
}
if (matchingMethod != null && !matchingMethod.getAnnotation().structField()) {
- return callMethod(matchingMethod, methodName, obj, args.toArray(), getLocation());
- } else {
- throw new EvalException(
- getLocation(),
- String.format(
- "Type %s has no %s",
- EvalUtils.getDataTypeNameFromClass(objClass),
- formatMethod(args)));
+ return matchingMethod;
}
+ throw new EvalException(
+ getLocation(),
+ String.format(
+ "Type %s has no %s",
+ EvalUtils.getDataTypeNameFromClass(objClass),
+ formatMethod(args)));
}
private String formatMethod(List<Object> args) {
@@ -496,20 +488,7 @@
@Override
Object eval(Environment env) throws EvalException, InterruptedException {
- // Adds the calling rule to the stack trace of the Environment if it is a BUILD environment.
- // There are two reasons for this:
- // a) When using aliases in load(), the rule class name in the BUILD file will differ from
- // the implementation name in the bzl file. Consequently, we need to store the calling name.
- // b) We need the location of the calling rule inside the BUILD file.
- boolean hasAddedElement =
- env.isSkylark() ? false : env.tryAddingStackTraceRoot(new StackTraceElement(func, args));
- try {
- return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
- } finally {
- if (hasAddedElement) {
- env.removeStackTraceRoot();
- }
- }
+ return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
}
/**
@@ -556,15 +535,28 @@
// When calling a Java method, the name is not in the Environment,
// so evaluating 'func' would fail.
evalArguments(posargs, kwargs, env, null);
+ Class<?> objClass;
+ Object obj;
if (objValue instanceof Class<?>) {
- // Static Java method call. We can return the value from here directly because
- // invokeJavaMethod() has special checks.
- return invokeJavaMethod(
- null, (Class<?>) objValue, func.getName(), posargs.build(), !kwargs.isEmpty());
+ // Static call
+ obj = null;
+ objClass = (Class<?>) objValue;
} else {
- return invokeJavaMethod(
- objValue, objValue.getClass(), func.getName(), posargs.build(), !kwargs.isEmpty());
+ obj = objValue;
+ objClass = objValue.getClass();
}
+ String name = func.getName();
+ ImmutableList<Object> args = posargs.build();
+ MethodDescriptor method = findJavaMethod(objClass, name, args);
+ if (!kwargs.isEmpty()) {
+ throw new EvalException(
+ func.getLocation(),
+ String.format(
+ "Keyword arguments are not allowed when calling a java method"
+ + "\nwhile calling method '%s' for type %s",
+ name, EvalUtils.getDataTypeNameFromClass(objClass)));
+ }
+ return callMethod(method, name, obj, args.toArray(), getLocation());
} else {
throw new EvalException(
getLocation(),
@@ -616,6 +608,25 @@
? ArgConversion.TO_SKYLARK : ArgConversion.NO_CONVERSION;
}
+ /**
+ * Returns the value of the argument 'name' (or null if there is none).
+ * This function is used to associate debugging information to rules created by skylark "macros".
+ */
+ @Nullable
+ public String getNameArg() {
+ for (Argument.Passed arg : args) {
+ if (arg != null) {
+ String name = arg.getName();
+ if (name != null && name.equals("name")) {
+ Expression expr = arg.getValue();
+ return (expr != null && expr instanceof StringLiteral)
+ ? ((StringLiteral) expr).getValue() : null;
+ }
+ }
+ }
+ return null;
+ }
+
@Override
public void accept(SyntaxTreeVisitor visitor) {
visitor.visit(this);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FunctionDefStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/FunctionDefStatement.java
index ae7c027..acc09c9 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FunctionDefStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FunctionDefStatement.java
@@ -50,10 +50,14 @@
defaultValues.add(expr.eval(env));
}
}
- env.update(ident.getName(), new UserDefinedFunction(
- ident, FunctionSignature.WithValues.<Object, SkylarkType>create(
- signature.getSignature(), defaultValues, types),
- statements, (SkylarkEnvironment) env));
+ env.update(
+ ident.getName(),
+ new UserDefinedFunction(
+ ident,
+ FunctionSignature.WithValues.<Object, SkylarkType>create(
+ signature.getSignature(), defaultValues, types),
+ statements,
+ env.getGlobals()));
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LValue.java b/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
index df5f486..1a39755 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
@@ -84,8 +84,7 @@
if (env.isSkylark()) {
// The variable may have been referenced successfully if a global variable
// with the same name exists. In this case an Exception needs to be thrown.
- SkylarkEnvironment skylarkEnv = (SkylarkEnvironment) env;
- if (skylarkEnv.hasBeenReadGlobalVariable(ident.getName())) {
+ if (env.isKnownGlobalVariable(ident.getName())) {
throw new EvalException(
loc,
String.format(
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index 996ae41..b1ca1f6 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -776,7 +776,7 @@
new BuiltinFunction("append") {
public Runtime.NoneType invoke(List<Object> self, Object item,
Location loc, Environment env) throws EvalException, ConversionException {
- if (env.isSkylark()) {
+ if (env.isCallerSkylark()) {
throw new EvalException(loc,
"function 'append' is not available in Skylark");
}
@@ -806,7 +806,7 @@
new BuiltinFunction("extend") {
public Runtime.NoneType invoke(List<Object> self, List<Object> items,
Location loc, Environment env) throws EvalException, ConversionException {
- if (env.isSkylark()) {
+ if (env.isCallerSkylark()) {
throw new EvalException(loc,
"function 'append' is not available in Skylark");
}
@@ -920,7 +920,7 @@
List<Object> list = Lists.newArrayListWithCapacity(dict.size());
for (Map.Entry<?, ?> entries : dict.entrySet()) {
List<?> item = ImmutableList.of(entries.getKey(), entries.getValue());
- list.add(env.isSkylark() ? SkylarkList.tuple(item) : item);
+ list.add(env.isCallerSkylark() ? SkylarkList.tuple(item) : item);
}
return convert(list, env, loc);
}
@@ -966,7 +966,7 @@
@SuppressWarnings("unchecked")
private static Iterable<Object> convert(Collection<?> list, Environment env, Location loc)
throws EvalException {
- if (env.isSkylark()) {
+ if (env.isCallerSkylark()) {
return SkylarkList.list(list, loc);
} else {
return Lists.newArrayList(list);
@@ -1386,7 +1386,7 @@
useLocation = true, useEnvironment = true)
private static final BuiltinFunction print = new BuiltinFunction("print") {
public Runtime.NoneType invoke(String sep, SkylarkList starargs,
- Location loc, SkylarkEnvironment env) throws EvalException {
+ Location loc, Environment env) throws EvalException {
String msg = Joiner.on(sep).join(Iterables.transform(starargs,
new com.google.common.base.Function<Object, String>() {
@Override
@@ -1493,19 +1493,6 @@
/**
- * Set up a given environment for supported class methods.
- */
- public static void setupMethodEnvironment(Environment env) {
- setupMethodEnvironment(env, env.isSkylark() ? skylarkGlobalFunctions : buildGlobalFunctions);
- }
-
- private static void setupMethodEnvironment(Environment env, Iterable<BaseFunction> functions) {
- for (BaseFunction function : functions) {
- env.update(function.getName(), function);
- }
- }
-
- /**
* Collect global functions for the validation environment.
*/
public static void setupValidationEnvironment(Set<String> builtIn) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java b/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java
new file mode 100644
index 0000000..a742b7f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java
@@ -0,0 +1,140 @@
+// Copyright 2015 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.base.Preconditions;
+
+import java.io.Serializable;
+
+/**
+ * A Mutability is a resource associated with an {@link Environment} during an evaluation,
+ * that gives those who possess it a revokable capability to mutate this Environment and
+ * the objects created within that {@link Environment}. At the end of the evaluation,
+ * the resource is irreversibly closed, at which point the capability is revoked,
+ * and it is not possible to mutate either this {@link Environment} or these objects.
+ *
+ * <p>Evaluation in an {@link Environment} may thus mutate objects created in the same
+ * {@link Environment}, but may not mutate {@link Freezable} objects (lists, sets, dicts)
+ * created in a previous, concurrent or future {@link Environment}, and conversely,
+ * the bindings and objects in this {@link Environment} will be protected from
+ * mutation by evaluation in a different {@link Environment}.
+ *
+ * <p>Only a single thread may use the {@link Environment} and objects created within it while the
+ * Mutability is still mutable as tested by {@link #isMutable}. Once the Mutability resource
+ * is closed, the {@link Environment} and its objects are immutable and can be simultaneously used
+ * by arbitrarily many threads.
+ *
+ * <p>The safe usage of a Mutability requires to always use try-with-resource style:
+ * <code>try(Mutability mutability = Mutability.create(fmt, ...)) { ... }</code>
+ * Thus, you can create a Mutability, build an {@link Environment}, mutate that {@link Environment}
+ * and create mutable objects as you evaluate in that {@link Environment}, and finally return the
+ * resulting {@link Environment}, at which point the resource is closed, and the {@link Environment}
+ * and the objects it contains all become immutable.
+ * (Unsafe usage is allowed only in test code that is not part of the Bazel jar.)
+ */
+// TODO(bazel-team): When we start using Java 8, this safe usage pattern can be enforced
+// through the use of a higher-order function.
+public final class Mutability implements AutoCloseable, Serializable {
+ private boolean isMutable;
+ private final String annotation; // For error reporting.
+
+ /**
+ * Creates a Mutability.
+ * @param annotation an Object used for error reporting,
+ * describing to the user the context in which this Mutability was active.
+ */
+ private Mutability(String annotation) {
+ this.isMutable = true;
+ this.annotation = Preconditions.checkNotNull(annotation);
+ }
+
+ /**
+ * Creates a Mutability.
+ * @param pattern is a {@link Printer#format} pattern used to lazily produce a string
+ * for error reporting
+ * @param arguments are the optional {@link Printer#format} arguments to produce that string
+ */
+ public static Mutability create(String pattern, Object... arguments) {
+ // For efficiency, we could be lazy and use formattable instead of format,
+ // but the result is going to be serialized, anyway.
+ return new Mutability(Printer.format(pattern, arguments));
+ }
+
+ String getAnnotation() {
+ return annotation;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(isMutable ? "[%s]" : "(%s)", annotation);
+ }
+
+ boolean isMutable() {
+ return isMutable;
+ }
+
+ /**
+ * Freezes this Mutability, marking as immutable all {@link Freezable} objects that use it.
+ */
+ @Override
+ public void close() {
+ isMutable = false;
+ }
+
+ /**
+ * A MutabilityException will be thrown when the user attempts to mutate an object he shouldn't.
+ */
+ static class MutabilityException extends Exception {
+ MutabilityException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Each {@link Freezable} object possesses a revokable Mutability attribute telling whether
+ * the object is still mutable. All {@link Freezable} objects created in the same
+ * {@link Environment} will share the same Mutability, inherited from this {@link Environment}.
+ * Only evaluation in the same {@link Environment} is allowed to mutate these objects,
+ * and only until the Mutability is irreversibly revoked.
+ */
+ public interface Freezable {
+ /**
+ * Returns the {@link Mutability} capability associated with this Freezable object.
+ */
+ Mutability mutability();
+ }
+
+ /**
+ * Checks that this Freezable object can be mutated from the given {@link Environment}.
+ * @param object a Freezable object that we check is still mutable.
+ * @param env the {@link Environment} attempting the mutation.
+ * @throws MutabilityException when the object was frozen already, or is from another context.
+ */
+ public static void checkMutable(Freezable object, Environment env)
+ throws MutabilityException {
+ if (!object.mutability().isMutable()) {
+ throw new MutabilityException("trying to mutate a frozen object");
+ }
+ // Consider an {@link Environment} e1, in which is created {@link UserDefinedFunction} f1,
+ // that closes over some variable v1 bound to list l1. If somehow, via the magic of callbacks,
+ // f1 or l1 is passed as argument to some function f2 evaluated in {@link environment} e2
+ // while e1 is be mutable, e2, being a different {@link Environment}, should not be
+ // allowed to mutate objects from e1. It's a bug, that shouldn't happen in our current code
+ // base, so we throw an AssertionError. If in the future such situations are allowed to happen,
+ // then we should throw a MutabilityException instead.
+ if (!object.mutability().equals(env.mutability())) {
+ throw new AssertionError("trying to mutate an object from a different context");
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java b/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java
index 50ef2546..9230d6b 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java
@@ -33,11 +33,11 @@
@SkylarkSignature(name = "True", returnType = Boolean.class,
doc = "Literal for the boolean true.")
- static final Boolean TRUE = true;
+ private static final Boolean TRUE = true;
@SkylarkSignature(name = "False", returnType = Boolean.class,
doc = "Literal for the boolean false.")
- static final Boolean FALSE = false;
+ private static final Boolean FALSE = false;
/**
* There should be only one instance of this type to allow "== None" tests.
@@ -70,11 +70,11 @@
/**
* Set up a given environment for supported class methods.
*/
- static Environment setupConstants(Environment env) throws EvalException {
+ static Environment setupConstants(Environment env) {
// 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.
- return env.update("False", FALSE).update("True", TRUE).update("None", NONE);
+ return env.setup("False", FALSE).setup("True", TRUE).setup("None", NONE);
}
@@ -128,7 +128,7 @@
public static void registerModuleGlobals(Environment env, Class<?> moduleClass) {
try {
if (moduleClass.isAnnotationPresent(SkylarkModule.class)) {
- env.update(
+ env.setup(
moduleClass.getAnnotation(SkylarkModule.class).name(), moduleClass.newInstance());
}
for (Field field : moduleClass.getDeclaredFields()) {
@@ -142,7 +142,7 @@
if (!(value instanceof BuiltinFunction.Factory
|| (value instanceof BaseFunction
&& !annotation.objectType().equals(Object.class)))) {
- env.update(annotation.name(), value);
+ env.setup(annotation.name(), value);
}
}
}
@@ -168,9 +168,9 @@
}
static void setupMethodEnvironment(
- Environment env, Iterable<BaseFunction> functions) throws EvalException {
+ Environment env, Iterable<BaseFunction> functions) {
for (BaseFunction function : functions) {
- env.update(function.getName(), function);
+ env.setup(function.getName(), function);
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java
index 94b1917..6140acb 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java
@@ -22,21 +22,25 @@
private final BaseFunction callback;
private final FuncallExpression ast;
- private final SkylarkEnvironment funcallEnv;
+ private final Environment funcallEnv;
- public SkylarkCallbackFunction(BaseFunction callback, FuncallExpression ast,
- SkylarkEnvironment funcallEnv) {
+ public SkylarkCallbackFunction(
+ BaseFunction callback, FuncallExpression ast, Environment funcallEnv) {
this.callback = callback;
this.ast = ast;
this.funcallEnv = funcallEnv;
}
public Object call(ClassObject ctx, Object... arguments) throws EvalException {
- try {
+ try (Mutability mutability = Mutability.create("callback %s", callback)) {
+ Environment env = Environment.builder(mutability)
+ .setSkylark()
+ .setEventHandler(funcallEnv.getEventHandler())
+ .setGlobals(funcallEnv.getGlobals())
+ .build();
return callback.call(
- ImmutableList.<Object>builder().add(ctx).add(arguments).build(), null, ast, funcallEnv);
- } catch (InterruptedException | ClassCastException
- | IllegalArgumentException e) {
+ ImmutableList.<Object>builder().add(ctx).add(arguments).build(), null, ast, env);
+ } catch (InterruptedException | ClassCastException | IllegalArgumentException e) {
throw new EvalException(ast.getLocation(), e.getMessage());
}
}
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
deleted file mode 100644
index 0ff19f9..0000000
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java
+++ /dev/null
@@ -1,205 +0,0 @@
-// 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<String> 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<StackTraceElement> 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<StackTraceElement> stackTrace) {
- super(stackTrace);
- this.eventHandler = eventHandler;
- this.fileContentHashCode = astFileContentHashCode;
- }
-
- public SkylarkEnvironment(EventHandler eventHandler, String astFileContentHashCode) {
- this(eventHandler, astFileContentHashCode, new LinkedList<StackTraceElement>());
- }
-
- @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<String, Object> 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 <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();
- }
-
- /**
- * 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();
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
index bf3c832..6b31625 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
@@ -204,8 +204,13 @@
} else if (param.defaultValue().isEmpty()) {
return Runtime.NONE;
} else {
- try {
- return EvaluationContext.SKYLARK_INITIALIZATION.evalExpression(param.defaultValue());
+ try (Mutability mutability = Mutability.create("initialization")) {
+ return Environment.builder(mutability)
+ .setSkylark()
+ .setGlobals(Environment.CONSTANTS_ONLY)
+ .setEventHandler(Environment.FAIL_FAST_HANDLER)
+ .build()
+ .eval(param.defaultValue());
} catch (Exception e) {
throw new RuntimeException(String.format(
"Exception while processing @SkylarkSignature.Param %s, default value %s",
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
index 96aaaa9..498c66e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
@@ -25,6 +25,7 @@
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.events.Location;
+import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@@ -80,7 +81,7 @@
*/
// TODO(bazel-team): move the FunctionType side-effect out of the type object
// and into the validation environment.
-public abstract class SkylarkType {
+public abstract class SkylarkType implements Serializable {
// The main primitives to override in subclasses
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StackTraceElement.java b/src/main/java/com/google/devtools/build/lib/syntax/StackTraceElement.java
deleted file mode 100644
index 6653c58..0000000
--- a/src/main/java/com/google/devtools/build/lib/syntax/StackTraceElement.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2015 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.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.syntax.Argument.Passed;
-
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nullable;
-
-/**
- * Represents an element of {@link Environment}'s stack trace.
- */
-// TODO(fwe): maybe combine this with EvalExceptionWithStackTrace.StackTraceElement
-public final class StackTraceElement {
- private final String name;
-
- @Nullable
- private final Location location;
-
- @Nullable
- private final BaseFunction func;
-
- @Nullable
- private final String nameArg;
-
- public StackTraceElement(BaseFunction func, Map<String, Object> kwargs) {
- this(func.getName(), func.getLocation(), func, getNameArg(kwargs));
- }
-
- public StackTraceElement(Identifier identifier, List<Passed> args) {
- this(identifier.getName(), identifier.getLocation(), null, getNameArg(args));
- }
-
- private StackTraceElement(String name, Location location, BaseFunction func, String nameArg) {
- this.name = name;
- this.location = location;
- this.func = func;
- this.nameArg = nameArg;
- }
-
- /**
- * Returns the value of the argument 'name' (or null if there is none).
- */
- @Nullable
- private static String getNameArg(Map<String, Object> kwargs) {
- Object value = (kwargs == null) ? null : kwargs.get("name");
- return (value == null) ? null : value.toString();
- }
-
- /**
- * Returns the value of the argument 'name' (or null if there is none).
- */
- @Nullable
- private static String getNameArg(List<Passed> args) {
- for (Argument.Passed arg : args) {
- if (arg != null) {
- String name = arg.getName();
- if (name != null && name.equals("name")) {
- Expression expr = arg.getValue();
- return (expr != null && expr instanceof StringLiteral)
- ? ((StringLiteral) expr).getValue() : null;
- }
- }
- }
- return null;
- }
-
- public String getName() {
- return name;
- }
-
- /**
- * Returns the value of the argument 'name' or null, if not present.
- */
- @Nullable
- public String getNameArg() {
- return nameArg;
- }
-
- public Location getLocation() {
- return location;
- }
-
- /**
- * Returns a more expressive description of this element, if possible.
- */
- public String getLabel() {
- return (nameArg == null) ? getName() : String.format("%s(name = \"%s\")", name, nameArg);
- }
-
- public boolean hasFunction(BaseFunction func) {
- return this.func != null && this.func.equals(func);
- }
-
- @Override
- public String toString() {
- return String.format(
- "%s @ %s", getLabel(), (location == null) ? "<unknown>" : location.toString());
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Statement.java b/src/main/java/com/google/devtools/build/lib/syntax/Statement.java
index ca89b1a..2c99209 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Statement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Statement.java
@@ -27,8 +27,8 @@
abstract void exec(Environment env) throws EvalException, InterruptedException;
/**
- * Checks the semantics of the Statement using the SkylarkEnvironment according to
- * the rules of the Skylark language. The SkylarkEnvironment can be used e.g. to check
+ * Checks the semantics of the Statement using the Environment according to
+ * the rules of the Skylark language. The Environment can be used e.g. to check
* variable type collision, read only variables, detecting recursion, existence of
* built-in variables, functions, etc.
*
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
index 1ad455da..074f79f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.events.Location.LineAndColumn;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.ProfilerTask;
@@ -26,15 +27,16 @@
public class UserDefinedFunction extends BaseFunction {
private final ImmutableList<Statement> statements;
- private final SkylarkEnvironment definitionEnv;
+
+ // we close over the globals at the time of definition
+ private final Environment.Frame definitionGlobals;
protected UserDefinedFunction(Identifier function,
FunctionSignature.WithValues<Object, SkylarkType> signature,
- ImmutableList<Statement> statements, SkylarkEnvironment definitionEnv) {
+ ImmutableList<Statement> statements, Environment.Frame definitionGlobals) {
super(function.getName(), signature, function.getLocation());
-
this.statements = statements;
- this.definitionEnv = definitionEnv;
+ this.definitionGlobals = definitionGlobals;
}
public FunctionSignature.WithValues<Object, SkylarkType> getFunctionSignature() {
@@ -48,24 +50,37 @@
@Override
public Object call(Object[] arguments, FuncallExpression ast, Environment env)
throws EvalException, InterruptedException {
- ImmutableList<String> names = signature.getSignature().getNames();
-
- // Registering the functions's arguments as variables in the local Environment
- int i = 0;
- for (String name : names) {
- env.update(name, arguments[i++]);
+ if (!env.mutability().isMutable()) {
+ throw new EvalException(getLocation(), "Trying to call in frozen environment");
+ }
+ if (env.getStackTrace().contains(this)) {
+ throw new EvalException(getLocation(),
+ String.format("Recursion was detected when calling '%s' from '%s'",
+ getName(), Iterables.getLast(env.getStackTrace()).getName()));
}
Profiler.instance().startTask(ProfilerTask.SKYLARK_USER_FN,
getLocationPathAndLine() + "#" + getName());
Statement lastStatement = null;
try {
- for (Statement stmt : statements) {
- lastStatement = stmt;
- stmt.exec(env);
+ env.enterScope(this, ast, definitionGlobals);
+ ImmutableList<String> names = signature.getSignature().getNames();
+
+ // Registering the functions's arguments as variables in the local Environment
+ int i = 0;
+ for (String name : names) {
+ env.update(name, arguments[i++]);
}
- } catch (ReturnStatement.ReturnException e) {
- return e.getValue();
+
+ try {
+ for (Statement stmt : statements) {
+ lastStatement = stmt;
+ stmt.exec(env);
+ }
+ } catch (ReturnStatement.ReturnException e) {
+ return e.getValue();
+ }
+ return Runtime.NONE;
} catch (EvalExceptionWithStackTrace ex) {
// We need this block since the next "catch" must only catch EvalExceptions that don't have a
// stack trace yet.
@@ -77,8 +92,8 @@
throw real;
} finally {
Profiler.instance().completeTask(ProfilerTask.SKYLARK_USER_FN);
+ env.exitScope();
}
- return Runtime.NONE;
}
/**
@@ -103,14 +118,4 @@
}
return builder.toString();
}
-
-
- /**
- * Creates a new environment for the execution of this function.
- */
- @Override
- protected Environment getOrCreateChildEnvironment(Environment parent) throws EvalException {
- return SkylarkEnvironment.createEnvironmentForFunctionCalling(
- parent, definitionEnv, this);
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
index 9b71f96..9d56271 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
@@ -15,7 +15,6 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.events.Location;
import java.util.HashMap;
@@ -31,7 +30,7 @@
* @see Statement#validate
* @see Expression#validate
*/
-public class ValidationEnvironment {
+public final class ValidationEnvironment {
private final ValidationEnvironment parent;
@@ -45,9 +44,6 @@
// branches of if-else statements.
private Stack<Set<String>> futureReadOnlyVariables = new Stack<>();
- // Whether this validation environment is not modified therefore clonable or not.
- private boolean clonable;
-
/**
* Tracks the number of nested for loops that contain the statement that is currently being
* validated
@@ -55,38 +51,14 @@
private int loopCount = 0;
/**
- * Create a ValidationEnvironment for a given global Environment
+ * Create a ValidationEnvironment for a given global Environment.
*/
public ValidationEnvironment(Environment env) {
- this(env.getVariableNames());
Preconditions.checkArgument(env.isGlobal());
- }
-
- public ValidationEnvironment(Set<String> builtinVariables) {
parent = null;
+ Set<String> builtinVariables = env.getVariableNames();
variables.addAll(builtinVariables);
readOnlyVariables.addAll(builtinVariables);
- clonable = true;
- }
-
- private ValidationEnvironment(Set<String> builtinVariables, Set<String> readOnlyVariables) {
- parent = null;
- this.variables = new HashSet<>(builtinVariables);
- this.readOnlyVariables = new HashSet<>(readOnlyVariables);
- clonable = false;
- }
-
- // ValidationEnvironment for a new Environment()
- private static ImmutableSet<String> globalTypes = ImmutableSet.of("False", "True", "None");
-
- public ValidationEnvironment() {
- this(globalTypes);
- }
-
- @Override
- public ValidationEnvironment clone() {
- Preconditions.checkState(clonable);
- return new ValidationEnvironment(variables, readOnlyVariables);
}
/**
@@ -95,7 +67,6 @@
public ValidationEnvironment(ValidationEnvironment parent) {
// Don't copy readOnlyVariables: Variables may shadow global values.
this.parent = parent;
- this.clonable = false;
}
/**
@@ -120,7 +91,6 @@
}
variables.add(varname);
variableLocations.put(varname, location);
- clonable = false;
}
private void checkReadonly(String varname, Location location) throws EvalException {
@@ -133,7 +103,8 @@
* Returns true if the symbol exists in the validation environment.
*/
public boolean hasSymbolInEnvironment(String varname) {
- return variables.contains(varname) || topLevel().variables.contains(varname);
+ return variables.contains(varname)
+ || (parent != null && topLevel().variables.contains(varname));
}
private ValidationEnvironment topLevel() {
@@ -147,7 +118,6 @@
*/
public void startTemporarilyDisableReadonlyCheckSession() {
futureReadOnlyVariables.add(new HashSet<String>());
- clonable = false;
}
/**
@@ -159,7 +129,6 @@
if (!futureReadOnlyVariables.isEmpty()) {
futureReadOnlyVariables.peek().addAll(variables);
}
- clonable = false;
}
/**
@@ -167,7 +136,6 @@
*/
public void finishTemporarilyDisableReadonlyCheckBranch() {
readOnlyVariables.removeAll(futureReadOnlyVariables.peek());
- clonable = false;
}
/**