Create infrastructure to restrict top-level Starlark objects by flag
As proof of concept, this restricts AnalysisFailureInfo and AnalysisTestResultInfo to be unavailable as top-level symbols without --experimental_analysis_testing_improvements . This is technically a breaking change, but these symbols were unusable before this change, documented as being experimental, and are not included in any binary release of Bazel.
RELNOTES: None.
PiperOrigin-RevId: 217593936
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/ActionsInfoProviderApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/ActionsInfoProviderApi.java
index d19086a..8c21ebc 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/ActionsInfoProviderApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/ActionsInfoProviderApi.java
@@ -21,7 +21,7 @@
* Provider for structs containing actions created during the analysis of a rule.
*/
@SkylarkModule(name = "Actions",
- doc = "",
+ doc = "<b>Deprecated and subject to imminent removal. Please do not use.</b>",
documented = false,
category = SkylarkModuleCategory.PROVIDER)
// TODO(cparsons): Deprecate and remove this API.
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/test/TestingBootstrap.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/test/TestingBootstrap.java
index 745df17..f8d5a4b 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/test/TestingBootstrap.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/test/TestingBootstrap.java
@@ -18,6 +18,8 @@
import com.google.devtools.build.lib.skylarkbuildapi.Bootstrap;
import com.google.devtools.build.lib.skylarkbuildapi.test.AnalysisFailureInfoApi.AnalysisFailureInfoProviderApi;
import com.google.devtools.build.lib.skylarkbuildapi.test.AnalysisTestResultInfoApi.AnalysisTestResultInfoProviderApi;
+import com.google.devtools.build.lib.syntax.FlagGuardedValue;
+import com.google.devtools.build.lib.syntax.SkylarkSemantics.FlagIdentifier;
/**
* {@link Bootstrap} for skylark objects related to testing.
@@ -39,7 +41,13 @@
@Override
public void addBindingsToBuilder(ImmutableMap.Builder<String, Object> builder) {
builder.put("testing", testingModule);
- builder.put("AnalysisFailureInfo", analysisFailureInfoProvider);
- builder.put("AnalysisTestResultInfo", testResultInfoProvider);
+ builder.put("AnalysisFailureInfo",
+ FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
+ FlagIdentifier.EXPERIMENTAL_ANALYSIS_TESTING_IMPROVEMENTS,
+ analysisFailureInfoProvider));
+ builder.put("AnalysisTestResultInfo",
+ FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
+ FlagIdentifier.EXPERIMENTAL_ANALYSIS_TESTING_IMPROVEMENTS,
+ testResultInfoProvider));
}
}
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 8e01e89..f0957a3 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
@@ -41,6 +41,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
@@ -272,6 +273,14 @@
/** Bindings are maintained in order of creation. */
private final LinkedHashMap<String, Object> bindings;
+ /**
+ * A list of bindings which *would* exist in this global frame under certain semantic
+ * flags, but do not exist using the semantic flags used in this frame's creation.
+ * This map should not be used for lookups; it should only be used to throw descriptive
+ * error messages when a lookup of a restricted object is attempted.
+ **/
+ private final LinkedHashMap<String, FlagGuardedValue> restrictedBindings;
+
/** Set of bindings that are exported (can be loaded from other modules). */
private final HashSet<String> exportedBindings;
@@ -281,6 +290,7 @@
this.universe = null;
this.label = null;
this.bindings = new LinkedHashMap<>();
+ this.restrictedBindings = new LinkedHashMap<>();
this.exportedBindings = new HashSet<>();
}
@@ -288,7 +298,8 @@
Mutability mutability,
@Nullable GlobalFrame universe,
@Nullable Label label,
- @Nullable Map<String, Object> bindings) {
+ @Nullable Map<String, Object> bindings,
+ @Nullable Map<String, FlagGuardedValue> restrictedBindings) {
Preconditions.checkState(universe == null || universe.universe == null);
this.mutability = Preconditions.checkNotNull(mutability);
this.universe = universe;
@@ -303,26 +314,68 @@
if (bindings != null) {
this.bindings.putAll(bindings);
}
+ this.restrictedBindings = new LinkedHashMap<>();
+ if (restrictedBindings != null) {
+ this.restrictedBindings.putAll(restrictedBindings);
+ }
+ if (universe != null) {
+ this.restrictedBindings.putAll(universe.restrictedBindings);
+ }
this.exportedBindings = new HashSet<>();
}
public GlobalFrame(Mutability mutability) {
- this(mutability, null, null, null);
+ this(mutability, null, null, null, null);
}
public GlobalFrame(Mutability mutability, @Nullable GlobalFrame universe) {
- this(mutability, universe, null, null);
+ this(mutability, universe, null, null, null);
}
public GlobalFrame(
Mutability mutability, @Nullable GlobalFrame universe, @Nullable Label label) {
- this(mutability, universe, label, null);
+ this(mutability, universe, label, null, null);
}
/** Constructs a global frame for the given builtin bindings. */
public static GlobalFrame createForBuiltins(Map<String, Object> bindings) {
Mutability mutability = Mutability.create("<builtins>").freeze();
- return new GlobalFrame(mutability, null, null, bindings);
+ return new GlobalFrame(mutability, null, null, bindings, null);
+ }
+
+ /**
+ * Constructs a global frame based on the given parent frame, filtering out flag-restricted
+ * global objects.
+ */
+ public static GlobalFrame filterOutRestrictedBindings(
+ Mutability mutability, GlobalFrame parent, SkylarkSemantics semantics) {
+ if (parent == null) {
+ return new GlobalFrame(mutability);
+ }
+ Map<String, Object> filteredBindings = new LinkedHashMap<>();
+ Map<String, FlagGuardedValue> restrictedBindings = new LinkedHashMap<>();
+
+ for (Entry<String, Object> binding : parent.getTransitiveBindings().entrySet()) {
+ if (binding.getValue() instanceof FlagGuardedValue) {
+ FlagGuardedValue val = (FlagGuardedValue) binding.getValue();
+ if (val.isObjectAccessibleUsingSemantics(semantics)) {
+ filteredBindings.put(binding.getKey(), val.getObject(semantics));
+ } else {
+ restrictedBindings.put(binding.getKey(), val);
+ }
+ } else {
+ filteredBindings.put(binding.getKey(), binding.getValue());
+ }
+ }
+
+ restrictedBindings.putAll(parent.restrictedBindings);
+
+ return new GlobalFrame(
+ mutability,
+ null /*parent */,
+ parent.label,
+ filteredBindings,
+ restrictedBindings);
}
private void checkInitialized() {
@@ -356,7 +409,8 @@
*/
public GlobalFrame withLabel(Label label) {
checkInitialized();
- return new GlobalFrame(mutability, /*universe*/ null, label, bindings);
+ return new GlobalFrame(mutability, /*universe*/ null, label, bindings,
+ /*restrictedBindings*/ null);
}
/** Returns the {@link Mutability} of this {@link GlobalFrame}. */
@@ -928,6 +982,9 @@
/** Builds the Environment. */
public Environment build() {
Preconditions.checkArgument(!mutability.isFrozen());
+ if (semantics == null) {
+ throw new IllegalArgumentException("must call either setSemantics or useDefaultSemantics");
+ }
if (parent != null) {
Preconditions.checkArgument(parent.mutability().isFrozen(), "parent frame must be frozen");
if (parent.universe != null) { // This code path doesn't happen in Bazel.
@@ -938,14 +995,20 @@
parent.mutability(),
null /* parent */,
parent.label,
- parent.getTransitiveBindings());
+ parent.getTransitiveBindings(),
+ parent.restrictedBindings);
}
}
+
+ // Filter out restricted objects from the universe scope. This cannot be done in-place in
+ // creation of the input global universe scope, because this environment's semantics may not
+ // have been available during its creation. Thus, create a new universe scope for this
+ // environment which is equivalent in every way except that restricted bindings are
+ // filtered out.
+ parent = GlobalFrame.filterOutRestrictedBindings(mutability, parent, semantics);
+
GlobalFrame globalFrame = new GlobalFrame(mutability, parent);
LexicalFrame dynamicFrame = LexicalFrame.create(mutability);
- if (semantics == null) {
- throw new IllegalArgumentException("must call either setSemantics or useDefaultSemantics");
- }
if (importedExtensions == null) {
importedExtensions = ImmutableMap.of();
}
@@ -1143,6 +1206,16 @@
}
/**
+ * Returns a map containing all bindings that are technically <i>present</i> but are
+ * <i>restricted</i> in the current frame with the current semantics. Such bindings should be
+ * treated unresolvable; this method should be invoked to prepare error messaging for
+ * evaluation environments where access of these restricted objects may have been attempted.
+ */
+ public Map<String, FlagGuardedValue> getRestrictedBindings() {
+ return globalFrame.restrictedBindings;
+ }
+
+ /**
* Returns true if varname is a known global variable (i.e., it has been read in the context of
* the current function).
*/
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FlagGuardedValue.java b/src/main/java/com/google/devtools/build/lib/syntax/FlagGuardedValue.java
new file mode 100644
index 0000000..e21c23d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FlagGuardedValue.java
@@ -0,0 +1,119 @@
+// Copyright 2018 The Bazel Authors. 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 com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.SkylarkSemantics.FlagIdentifier;
+
+/**
+ * Wrapper on a value that controls its accessibility in Starlark based on the value of a
+ * semantic flag.
+ *
+ * <p>For example, this could control whether symbol "Foo" exists in the Starlark
+ * global frame: such a symbol might only be accessible if --experimental_foo is set to true.
+ * In order to create this control, an instance of this class should be added to the global
+ * frame under "Foo". This flag guard will throw a descriptive {@link EvalException} when
+ * "Foo" would be accessed without the proper flag.
+ */
+public class FlagGuardedValue {
+ private final Object obj;
+ private final FlagIdentifier flagIdentifier;
+ private final FlagType flagType;
+
+ private enum FlagType {
+ DEPRECATION,
+ EXPERIMENTAL;
+ }
+
+ private FlagGuardedValue(Object obj, FlagIdentifier flagIdentifier, FlagType flagType) {
+ this.obj = obj;
+ this.flagIdentifier = flagIdentifier;
+ this.flagType = flagType;
+ }
+
+ /**
+ * Creates a flag guard which only permits access of the given object when the given flag is
+ * true. If the given flag is false and the object would be accessed, an error is thrown
+ * describing the feature as experimental, and describing that the flag must be set to true.
+ */
+ public static FlagGuardedValue onlyWhenExperimentalFlagIsTrue(
+ FlagIdentifier flagIdentifier, Object obj) {
+ return new FlagGuardedValue(obj, flagIdentifier, FlagType.EXPERIMENTAL);
+ }
+
+ /**
+ * Creates a flag guard which only permits access of the given object when the given flag is
+ * false. If the given flag is true and the object would be accessed, an error is thrown
+ * describing the feature as deprecated, and describing that the flag must be set to false.
+ */
+ public static FlagGuardedValue onlyWhenIncompatibleFlagIsFalse(
+ FlagIdentifier flagIdentifier, Object obj) {
+ return new FlagGuardedValue(obj, flagIdentifier, FlagType.DEPRECATION);
+ }
+
+ /**
+ * Returns an {@link EvalException} with error appropriate to throw when one attempts to
+ * access this guard's protected object when it should be inaccessible in the given semantics.
+ *
+ * @throws IllegalArgumentException if {@link #isObjectAccessibleUsingSemantics} is true
+ * given the semantics
+ */
+ public EvalException getEvalExceptionFromAttemptingAccess(
+ Location location, SkylarkSemantics semantics, String symbolDescription) {
+ Preconditions.checkArgument(!isObjectAccessibleUsingSemantics(semantics),
+ "getEvalExceptionFromAttemptingAccess should only be called if the underlying "
+ + "object is inaccessible given the semantics");
+ if (flagType == FlagType.EXPERIMENTAL) {
+ return new EvalException(
+ location,
+ symbolDescription
+ + " is experimental and thus unavailable with the current flags. It may be "
+ + "enabled by setting --" + flagIdentifier.getFlagName());
+ } else {
+ return new EvalException(
+ location,
+ symbolDescription
+ + " is deprecated and will be removed soon. It may be temporarily re-enabled by "
+ + "setting --" + flagIdentifier.getFlagName() + "=false");
+
+ }
+ }
+
+ /**
+ * Returns this guard's underlying object. This should be called when appropriate validation
+ * has occurred to ensure that the object is accessible with the given semantics.
+ *
+ * @throws IllegalArgumentException if {@link #isObjectAccessibleUsingSemantics} is false
+ * given the semantics
+ */
+ public Object getObject(SkylarkSemantics semantics) {
+ Preconditions.checkArgument(isObjectAccessibleUsingSemantics(semantics),
+ "getObject should only be called if the underlying object is accessible given the "
+ + "semantics");
+ return obj;
+ }
+
+ /**
+ * Returns true if this guard's underlying object is accessible under the given semantics.
+ */
+ public boolean isObjectAccessibleUsingSemantics(SkylarkSemantics semantics) {
+ if (flagType == FlagType.EXPERIMENTAL) {
+ return semantics.isFeatureEnabledBasedOnTogglingFlags(flagIdentifier, FlagIdentifier.NONE);
+ } else {
+ return semantics.isFeatureEnabledBasedOnTogglingFlags(FlagIdentifier.NONE, flagIdentifier);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
index 0240a67..3605c61 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
@@ -33,7 +33,10 @@
@AutoValue
public abstract class SkylarkSemantics {
- /** Enum where each element represents a skylark semantics flag. */
+ /**
+ * Enum where each element represents a skylark semantics flag. The name of each value should
+ * be the exact name of the flag transformed to upper case (for error representation).
+ */
public enum FlagIdentifier {
EXPERIMENTAL_ANALYSIS_TESTING_IMPROVEMENTS(
SkylarkSemantics::experimentalAnalysisTestingImprovements),
@@ -52,6 +55,14 @@
FlagIdentifier(Function<SkylarkSemantics, Boolean> semanticsFunction) {
this.semanticsFunction = semanticsFunction;
}
+
+ /**
+ * Returns the name of the flag that this identifier controls. For example, EXPERIMENTAL_FOO
+ * would return 'experimental_foo'.
+ */
+ public String getFlagName() {
+ return this.name().toLowerCase();
+ }
}
/**
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 8900e5f..39744fe 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
@@ -85,14 +85,14 @@
}
}
- private final SkylarkSemantics semantics;
+ private final Environment env;
private Block block;
private int loopCount;
/** Create a ValidationEnvironment for a given global Environment (containing builtins). */
ValidationEnvironment(Environment env) {
Preconditions.checkArgument(env.isGlobal());
- semantics = env.getSemantics();
+ this.env = env;
block = new Block(Scope.Universe, null);
Set<String> builtinVariables = env.getVariableNames();
block.variables.addAll(builtinVariables);
@@ -157,6 +157,13 @@
public void visit(Identifier node) {
@Nullable Block b = blockThatDefines(node.getName());
if (b == null) {
+ // The identifier might not exist because it was restricted (hidden) by the current semantics.
+ // If this is the case, output a more helpful error message than 'not found'.
+ FlagGuardedValue result = env.getRestrictedBindings().get(node.getName());
+ if (result != null) {
+ throw new ValidationException(result.getEvalExceptionFromAttemptingAccess(
+ node.getLocation(), env.getSemantics(), node.getName()));
+ }
throw new ValidationException(node.createInvalidIdentifierException(getAllSymbols()));
}
node.setScope(b.scope);
@@ -326,7 +333,7 @@
/** Validates the AST and runs static checks. */
private void validateAst(List<Statement> statements) {
// Check that load() statements are on top.
- if (semantics.incompatibleBzlDisallowLoadAfterStatement()) {
+ if (env.getSemantics().incompatibleBzlDisallowLoadAfterStatement()) {
checkLoadAfterStatement(statements);
}
diff --git a/src/test/java/com/google/devtools/build/docgen/SkylarkDocumentationTest.java b/src/test/java/com/google/devtools/build/docgen/SkylarkDocumentationTest.java
index 412a74e..16c0aa8 100644
--- a/src/test/java/com/google/devtools/build/docgen/SkylarkDocumentationTest.java
+++ b/src/test/java/com/google/devtools/build/docgen/SkylarkDocumentationTest.java
@@ -18,7 +18,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-import com.google.devtools.build.docgen.skylark.SkylarkBuiltinMethodDoc;
import com.google.devtools.build.docgen.skylark.SkylarkJavaMethodDoc;
import com.google.devtools.build.docgen.skylark.SkylarkMethodDoc;
import com.google.devtools.build.docgen.skylark.SkylarkModuleDoc;
@@ -87,9 +86,8 @@
Map<String, SkylarkModuleDoc> modules = SkylarkDocumentationCollector.collectModules();
SkylarkModuleDoc topLevel =
modules.remove(SkylarkDocumentationCollector.getTopLevelModule().name());
- for (Map.Entry<String, SkylarkBuiltinMethodDoc> entry :
- topLevel.getBuiltinMethods().entrySet()) {
- docMap.put(entry.getKey(), entry.getValue().getDocumentation());
+ for (SkylarkMethodDoc method : topLevel.getMethods()) {
+ docMap.put(method.getName(), method.getDocumentation());
}
for (Map.Entry<String, SkylarkModuleDoc> entry : modules.entrySet()) {
docMap.put(entry.getKey(), entry.getValue().getDocumentation());
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
index 92b16ec..d89f9fb 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
@@ -2119,7 +2119,9 @@
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:r");
- assertContainsEvent("'Provider' object is not callable");
+ assertContainsEvent(
+ "AnalysisTestResultInfo is experimental and thus unavailable with the current flags. "
+ + "It may be enabled by setting --experimental_analysis_testing_improvements");
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index bec987f..071019e 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -36,6 +36,7 @@
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
+import com.google.devtools.build.lib.syntax.SkylarkSemantics.FlagIdentifier;
import com.google.devtools.build.lib.testutil.TestMode;
import java.util.List;
import java.util.Map;
@@ -2296,4 +2297,80 @@
.testIfErrorContains("'AnalysisFailure' has no field 'message'", "val.message")
.testIfErrorContains("'AnalysisFailure' has no field 'label'", "val.label");
}
+
+ @Test
+ public void testExperimentalFlagGuardedValue() throws Exception {
+ // This test uses an arbitrary experimental flag to verify this functionality. If this
+ // experimental flag were to go away, this test may be updated to use any experimental flag.
+ // The flag itself is unimportant to the test.
+ FlagGuardedValue val = FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
+ FlagIdentifier.EXPERIMENTAL_ANALYSIS_TESTING_IMPROVEMENTS,
+ "foo");
+ String errorMessage = "GlobalSymbol is experimental and thus unavailable with the current "
+ + "flags. It may be enabled by setting --experimental_analysis_testing_improvements";
+
+ new SkylarkTest(
+ ImmutableMap.of("GlobalSymbol", val),
+ "--experimental_analysis_testing_improvements=true")
+ .setUp("var = GlobalSymbol")
+ .testLookup("var", "foo");
+
+ new SkylarkTest(
+ ImmutableMap.of("GlobalSymbol", val),
+ "--experimental_analysis_testing_improvements=false")
+ .testIfErrorContains(errorMessage,
+ "var = GlobalSymbol");
+
+ new SkylarkTest(
+ ImmutableMap.of("GlobalSymbol", val),
+ "--experimental_analysis_testing_improvements=false")
+ .testIfErrorContains(errorMessage,
+ "def my_function():",
+ " var = GlobalSymbol");
+
+ new SkylarkTest(
+ ImmutableMap.of("GlobalSymbol", val),
+ "--experimental_analysis_testing_improvements=false")
+ .setUp("GlobalSymbol = 'other'",
+ "var = GlobalSymbol")
+ .testLookup("var", "other");
+ }
+
+ @Test
+ public void testIncompatibleFlagGuardedValue() throws Exception {
+ // This test uses an arbitrary incompatible flag to verify this functionality. If this
+ // incompatible flag were to go away, this test may be updated to use any incompatible flag.
+ // The flag itself is unimportant to the test.
+ FlagGuardedValue val = FlagGuardedValue.onlyWhenIncompatibleFlagIsFalse(
+ FlagIdentifier.INCOMPATIBLE_NO_TARGET_OUTPUT_GROUP,
+ "foo");
+ String errorMessage = "GlobalSymbol is deprecated and will be removed soon. It may be "
+ + "temporarily re-enabled by setting --incompatible_no_target_output_group=false";
+
+ new SkylarkTest(
+ ImmutableMap.of("GlobalSymbol", val),
+ "--incompatible_no_target_output_group=false")
+ .setUp("var = GlobalSymbol")
+ .testLookup("var", "foo");
+
+ new SkylarkTest(
+ ImmutableMap.of("GlobalSymbol", val),
+ "--incompatible_no_target_output_group=true")
+ .testIfErrorContains(errorMessage,
+ "var = GlobalSymbol");
+
+ new SkylarkTest(
+ ImmutableMap.of("GlobalSymbol", val),
+ "--incompatible_no_target_output_group=true")
+ .testIfErrorContains(errorMessage,
+ "def my_function():",
+ " var = GlobalSymbol");
+
+ new SkylarkTest(
+ ImmutableMap.of("GlobalSymbol", val),
+ "--incompatible_no_target_output_group=true")
+ .setUp("GlobalSymbol = 'other'",
+ "var = GlobalSymbol")
+ .testLookup("var", "other");
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
index 4f6c725..ac07655 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
@@ -17,6 +17,7 @@
import static org.junit.Assert.fail;
import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
import com.google.common.truth.Ordered;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventCollector;
@@ -39,6 +40,7 @@
import com.google.devtools.build.lib.testutil.TestMode;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import org.junit.Before;
/**
@@ -98,12 +100,17 @@
protected Environment newEnvironmentWithSkylarkOptions(String... skylarkOptions)
throws Exception {
+ return newEnvironmentWithBuiltinsAndSkylarkOptions(ImmutableMap.of(), skylarkOptions);
+ }
+
+ protected Environment newEnvironmentWithBuiltinsAndSkylarkOptions(Map<String, Object> builtins,
+ String... skylarkOptions) throws Exception {
if (testMode == null) {
throw new IllegalArgumentException(
"TestMode is null. Please set a Testmode via setMode() or set the "
+ "Environment manually by overriding newEnvironment()");
}
- return testMode.createEnvironment(getEventHandler(), skylarkOptions);
+ return testMode.createEnvironment(getEventHandler(), builtins, skylarkOptions);
}
/**
@@ -117,6 +124,17 @@
env = newEnvironmentWithSkylarkOptions(skylarkOptions);
}
+ protected void setMode(TestMode testMode, Map<String, Object> builtins,
+ String... skylarkOptions) throws Exception {
+ this.testMode = testMode;
+ env = newEnvironmentWithBuiltinsAndSkylarkOptions(builtins, skylarkOptions);
+ }
+
+ protected void enableSkylarkMode(Map<String, Object> builtins,
+ String... skylarkOptions) throws Exception {
+ setMode(TestMode.SKYLARK, builtins, skylarkOptions);
+ }
+
protected void enableSkylarkMode(String... skylarkOptions) throws Exception {
setMode(TestMode.SKYLARK, skylarkOptions);
}
@@ -589,14 +607,20 @@
*/
protected class SkylarkTest extends ModalTestCase {
private final String[] skylarkOptions;
+ private final Map<String, Object> builtins;
public SkylarkTest(String... skylarkOptions) {
+ this(ImmutableMap.of(), skylarkOptions);
+ }
+
+ public SkylarkTest(Map<String, Object> builtins, String... skylarkOptions) {
+ this.builtins = builtins;
this.skylarkOptions = skylarkOptions;
}
@Override
protected void run(Testable testable) throws Exception {
- enableSkylarkMode(skylarkOptions);
+ enableSkylarkMode(builtins, skylarkOptions);
testable.run();
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/testutil/TestMode.java b/src/test/java/com/google/devtools/build/lib/testutil/TestMode.java
index 9b848bd..e850ac6 100644
--- a/src/test/java/com/google/devtools/build/lib/testutil/TestMode.java
+++ b/src/test/java/com/google/devtools/build/lib/testutil/TestMode.java
@@ -13,13 +13,16 @@
// limitations under the License.
package com.google.devtools.build.lib.testutil;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.skylark.SkylarkModules;
import com.google.devtools.build.lib.events.EventHandler;
-import com.google.devtools.build.lib.packages.BazelLibrary;
import com.google.devtools.build.lib.packages.SkylarkSemanticsOptions;
import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Environment.GlobalFrame;
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.SkylarkSemantics;
import com.google.devtools.common.options.OptionsParser;
+import java.util.Map;
/**
* Describes a particular testing mode by determining how the
@@ -36,10 +39,12 @@
public static final TestMode BUILD =
new TestMode() {
@Override
- public Environment createEnvironment(EventHandler eventHandler, String... skylarkOptions)
+ public Environment createEnvironment(EventHandler eventHandler,
+ Map<String, Object> builtins,
+ String... skylarkOptions)
throws Exception {
return Environment.builder(Mutability.create("build test"))
- .setGlobals(BazelLibrary.GLOBALS)
+ .setGlobals(createGlobalFrame(builtins))
.setEventHandler(eventHandler)
.setSemantics(TestMode.parseSkylarkSemantics(skylarkOptions))
.build();
@@ -49,16 +54,26 @@
public static final TestMode SKYLARK =
new TestMode() {
@Override
- public Environment createEnvironment(EventHandler eventHandler, String... skylarkOptions)
+ public Environment createEnvironment(EventHandler eventHandler,
+ Map<String, Object> builtins, String... skylarkOptions)
throws Exception {
return Environment.builder(Mutability.create("skylark test"))
- .setGlobals(BazelLibrary.GLOBALS)
+ .setGlobals(createGlobalFrame(builtins))
.setEventHandler(eventHandler)
.setSemantics(TestMode.parseSkylarkSemantics(skylarkOptions))
.build();
}
};
- public abstract Environment createEnvironment(EventHandler eventHandler, String... skylarkOptions)
- throws Exception;
+ private static GlobalFrame createGlobalFrame(Map<String, Object> builtins) {
+ ImmutableMap.Builder<String, Object> envBuilder = ImmutableMap.builder();
+
+ SkylarkModules.addSkylarkGlobalsToBuilder(envBuilder);
+ envBuilder.putAll(builtins);
+ return GlobalFrame.createForBuiltins(envBuilder.build());
+ }
+
+ public abstract Environment createEnvironment(EventHandler eventHandler,
+ Map<String, Object> builtins,
+ String... skylarkOptions) throws Exception;
}