blob: 4a6bcce7ed2e915f60a0adf0b78500793c0b74f4 [file] [log] [blame]
// Copyright 2020 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.packages;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.starlark.java.eval.FlagGuardedValue;
import net.starlark.java.eval.Starlark;
// TODO(adonovan): move skyframe.PackageFunction into lib.packages so we needn't expose this and
// the other env-building functions.
/**
* This class encapsulates knowledge of how to set up the Starlark environment for BUILD, WORKSPACE,
* and bzl file evaluation, including the top-level predeclared symbols, the {@code native} module,
* and the special environment for {@code @_builtins} bzl evaluation.
*
* <p>The set of available symbols is determined by 1) gathering registered toplevels, rules,
* extensions, etc., from the {@link ConfiguredRuleClassProvider} and {@link PackageFactory}, and
* then 2) applying builtins injection (see {@link StarlarkBuiltinsFunction}, if applicable. The
* result of (1) is cached by an instance of this class. (2) is obtained using helper methods on
* this class, and cached in {@link StarlarkBuiltinsValue}.
*/
public final class BazelStarlarkEnvironment {
// TODO(#11954): Eventually the BUILD and WORKSPACE bzl dialects should converge. Right now they
// only differ on the "native" object.
private final RuleClassProvider ruleClassProvider;
private final ImmutableMap<String, ?> ruleFunctions;
/** The "native" module fields for a BUILD-loaded bzl module, before builtins injection. */
private final ImmutableMap<String, Object> uninjectedBuildBzlNativeBindings;
/** The "native" module fields for a WORKSPACE-loaded bzl module. */
private final ImmutableMap<String, Object> workspaceBzlNativeBindings;
/** The top-level predeclared symbols for a BUILD-loaded bzl module, before builtins injection. */
private final ImmutableMap<String, Object> uninjectedBuildBzlEnv;
/** The top-level predeclared symbols for BUILD files, before builtins injection and prelude. */
private final ImmutableMap<String, Object> uninjectedBuildEnv;
/** The top-level predeclared symbols for a WORKSPACE-loaded bzl module. */
private final ImmutableMap<String, Object> workspaceBzlEnv;
/** The top-level predeclared symbols for a bzl module in the {@code @_builtins} pseudo-repo. */
private final ImmutableMap<String, Object> builtinsBzlEnv;
BazelStarlarkEnvironment(
RuleClassProvider ruleClassProvider,
ImmutableMap<String, ?> ruleFunctions,
List<PackageFactory.EnvironmentExtension> environmentExtensions,
Object packageFunction,
String version) {
this.ruleClassProvider = ruleClassProvider;
this.ruleFunctions = ruleFunctions;
this.uninjectedBuildBzlNativeBindings =
createUninjectedBuildBzlNativeBindings(
ruleFunctions, packageFunction, environmentExtensions);
this.workspaceBzlNativeBindings = createWorkspaceBzlNativeBindings(ruleClassProvider, version);
this.uninjectedBuildBzlEnv =
createUninjectedBuildBzlEnv(ruleClassProvider, uninjectedBuildBzlNativeBindings);
this.workspaceBzlEnv = createWorkspaceBzlEnv(ruleClassProvider, workspaceBzlNativeBindings);
this.builtinsBzlEnv =
createBuiltinsBzlEnv(
ruleClassProvider, uninjectedBuildBzlNativeBindings, uninjectedBuildBzlEnv);
this.uninjectedBuildEnv =
createUninjectedBuildEnv(ruleFunctions, packageFunction, environmentExtensions);
}
/**
* Returns the contents of the "native" object for BUILD-loaded bzls, not accounting for builtins
* injection.
*/
public ImmutableMap<String, Object> getUninjectedBuildBzlNativeBindings() {
return uninjectedBuildBzlNativeBindings;
}
/** Returns the contents of the "native" object for WORKSPACE-loaded bzls. */
public ImmutableMap<String, Object> getWorkspaceBzlNativeBindings() {
return workspaceBzlNativeBindings;
}
/**
* Returns the original environment for BUILD-loaded bzl files, not accounting for builtins
* injection.
*
* <p>The post-injection environment may differ from this one by what symbols a name is bound to,
* but the set of symbols remains the same.
*/
public ImmutableMap<String, Object> getUninjectedBuildBzlEnv() {
return uninjectedBuildBzlEnv;
}
/**
* Returns the original environment for BUILD files, not accounting for builtins injection or
* application of the prelude.
*
* <p>Applying builtins injection may update name bindings, but not add or remove them. I.e. some
* names may refer to different symbols but the static set of names remains the same. Applying the
* prelude file may update and add name bindings but not remove them.
*/
public ImmutableMap<String, Object> getUninjectedBuildEnv() {
return uninjectedBuildEnv;
}
/** Returns the environment for WORKSPACE-loaded bzl files. */
public ImmutableMap<String, Object> getWorkspaceBzlEnv() {
return workspaceBzlEnv;
}
/** Returns the environment for bzl files in the {@code @_builtins} pseudo-repository. */
public ImmutableMap<String, Object> getBuiltinsBzlEnv() {
return builtinsBzlEnv;
}
/**
* Produces everything that would be in the "native" object for BUILD-loaded bzl files if builtins
* injection didn't happen.
*/
private static ImmutableMap<String, Object> createUninjectedBuildBzlNativeBindings(
Map<String, ?> ruleFunctions,
Object packageFunction,
List<PackageFactory.EnvironmentExtension> environmentExtensions) {
ImmutableMap.Builder<String, Object> env = new ImmutableMap.Builder<>();
env.putAll(StarlarkNativeModule.BINDINGS_FOR_BUILD_FILES);
env.putAll(ruleFunctions);
env.put("package", packageFunction);
for (PackageFactory.EnvironmentExtension ext : environmentExtensions) {
ext.updateNative(env);
}
return env.build();
}
/** Produces everything in the "native" object for WORKSPACE-loaded bzl files. */
private static ImmutableMap<String, Object> createWorkspaceBzlNativeBindings(
RuleClassProvider ruleClassProvider, String version) {
return WorkspaceFactory.createNativeModuleBindings(ruleClassProvider, version);
}
/** Constructs a "native" module object with the given contents. */
private static Object createNativeModule(Map<String, Object> bindings) {
return StructProvider.STRUCT.create(bindings, "no native function or rule '%s'");
}
private static ImmutableMap<String, Object> createUninjectedBuildBzlEnv(
RuleClassProvider ruleClassProvider, Map<String, Object> uninjectedBuildBzlNativeBindings) {
Map<String, Object> env = new HashMap<>();
env.putAll(ruleClassProvider.getEnvironment());
// Determine the "native" module.
// TODO(#11954): Use the same "native" object for both BUILD- and WORKSPACE-loaded .bzls, and
// just have it be a dynamic error to call the wrong thing at the wrong time. This is a breaking
// change.
env.put("native", createNativeModule(uninjectedBuildBzlNativeBindings));
return ImmutableMap.copyOf(env);
}
private static ImmutableMap<String, Object> createUninjectedBuildEnv(
Map<String, ?> ruleFunctions,
Object packageFunction,
List<PackageFactory.EnvironmentExtension> environmentExtensions) {
ImmutableMap.Builder<String, Object> env = ImmutableMap.builder();
env.putAll(StarlarkLibrary.BUILD); // e.g. rule, select, depset
env.putAll(StarlarkNativeModule.BINDINGS_FOR_BUILD_FILES);
env.put("package", packageFunction);
env.putAll(ruleFunctions);
for (PackageFactory.EnvironmentExtension ext : environmentExtensions) {
ext.update(env);
}
return env.build();
}
private static ImmutableMap<String, Object> createWorkspaceBzlEnv(
RuleClassProvider ruleClassProvider, Map<String, Object> workspaceBzlNativeBindings) {
Map<String, Object> env = new HashMap<>();
env.putAll(ruleClassProvider.getEnvironment());
// See above comments for native in BUILD bzls.
env.put("native", createNativeModule(workspaceBzlNativeBindings));
return ImmutableMap.copyOf(env);
}
private static ImmutableMap<String, Object> createBuiltinsBzlEnv(
RuleClassProvider ruleClassProvider,
ImmutableMap<String, Object> uninjectedBuildBzlNativeBindings,
ImmutableMap<String, Object> uninjectedBuildBzlEnv) {
Map<String, Object> env = new HashMap<>();
env.putAll(ruleClassProvider.getEnvironment());
// Clear out rule-specific symbols like CcInfo.
env.keySet().removeAll(ruleClassProvider.getNativeRuleSpecificBindings().keySet());
// For _builtins.toplevel, replace all FlagGuardedValues with the underlying value;
// StarlarkSemantics flags do not affect @_builtins.
//
// We do this because otherwise we'd need to differentiate the _builtins.toplevel object (and
// therefore the @_builtins environment) based on StarlarkSemantics. That seems unnecessary.
// Instead we trust @_builtins to not misuse flag-guarded features, same as native code.
//
// If foo is flag-guarded (either experimental or incompatible), it is unconditionally visible
// as _builtins.toplevel.foo. It is legal to list it in exported_toplevels unconditionally, but
// the flag still controls whether the symbol is actually visible to user code.
Map<String, Object> unwrappedBuildBzlSymbols = new HashMap<>();
for (Map.Entry<String, Object> entry : uninjectedBuildBzlEnv.entrySet()) {
Object symbol = entry.getValue();
if (symbol instanceof FlagGuardedValue) {
symbol = ((FlagGuardedValue) symbol).getObject();
}
unwrappedBuildBzlSymbols.put(entry.getKey(), symbol);
}
Object builtinsModule =
new BuiltinsInternalModule(
createNativeModule(uninjectedBuildBzlNativeBindings),
// createNativeModule() is good enough for the "toplevel" and "internal" objects too.
createNativeModule(unwrappedBuildBzlSymbols),
createNativeModule(ruleClassProvider.getStarlarkBuiltinsInternals()));
Object conflictingValue = env.put("_builtins", builtinsModule);
Preconditions.checkState(
conflictingValue == null, "'_builtins' name is reserved for builtins injection");
return ImmutableMap.copyOf(env);
}
/**
* Constructs an environment for a BUILD-loaded bzl file based on the default environment as well
* as the given {@code @_builtins}-injected top-level symbols and "native" bindings.
*
* <p>Injected symbols must override an existing symbol of that name. Furthermore, the overridden
* symbol must be a rule or a piece of a specific ruleset's logic (e.g., {@code CcInfo} or {@code
* cc_library}), not a generic built-in (e.g., {@code provider} or {@code glob}). Throws
* InjectionException if these conditions are not met.
*
* @see StarlarkBuiltinsFunction
*/
public ImmutableMap<String, Object> createBuildBzlEnvUsingInjection(
Map<String, Object> injectedToplevels, Map<String, Object> injectedRules)
throws InjectionException {
// Determine top-level symbols.
Map<String, Object> env = new HashMap<>();
env.putAll(uninjectedBuildBzlEnv);
for (Map.Entry<String, Object> symbol : injectedToplevels.entrySet()) {
String name = symbol.getKey();
if (!env.containsKey(name) && !Starlark.UNIVERSE.containsKey(name)) {
throw new InjectionException(
String.format(
"Injected top-level symbol '%s' must override an existing symbol by that name",
name));
} else if (!ruleClassProvider.getNativeRuleSpecificBindings().containsKey(name)) {
throw new InjectionException(
String.format("Cannot override top-level builtin '%s' with an injected value", name));
} else {
env.put(name, symbol.getValue());
}
}
// Determine "native" bindings.
// See above comments for native in BUILD bzls.
Map<String, Object> nativeBindings = new HashMap<>();
nativeBindings.putAll(uninjectedBuildBzlNativeBindings);
for (Map.Entry<String, Object> symbol : injectedRules.entrySet()) {
String name = symbol.getKey();
Object preexisting = nativeBindings.put(name, symbol.getValue());
if (preexisting == null) {
throw new InjectionException(
String.format("Injected rule '%s' must override an existing rule by that name", name));
} else if (!ruleFunctions.containsKey(name)) {
throw new InjectionException(
String.format("Cannot override native module field '%s' with an injected value", name));
}
}
env.put("native", createNativeModule(nativeBindings));
return ImmutableMap.copyOf(env);
}
/**
* Constructs an environment for a BUILD file based on the default environment and the given
* {@code @_builtins}-injected rule symbols.
*
* <p>Injected rule symbols must override an existing native rule of that name. Only rules may be
* overridden in this manner, not generic built-ins such as {@code package} or {@code glob}.
* Throws InjectionException if these conditions are not met.
*/
public ImmutableMap<String, Object> createBuildEnvUsingInjection(
Map<String, Object> injectedRules) throws InjectionException {
HashMap<String, Object> env = new HashMap<>(uninjectedBuildEnv);
for (Map.Entry<String, Object> symbol : injectedRules.entrySet()) {
String name = symbol.getKey();
if (!env.containsKey(name) && !Starlark.UNIVERSE.containsKey(name)) {
throw new InjectionException(
String.format("Injected rule '%s' must override an existing rule by that name", name));
} else if (!ruleFunctions.containsKey(name)) {
throw new InjectionException(
String.format("Cannot override top-level builtin '%s' with an injected value", name));
} else {
env.put(name, symbol.getValue());
}
}
return ImmutableMap.copyOf(env);
}
/** Indicates a problem performing builtins injection. */
public static final class InjectionException extends Exception {
public InjectionException(String message) {
super(message);
}
}
}