-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
StarlarkBuiltinsFunction.java
395 lines (361 loc) · 17.3 KB
/
StarlarkBuiltinsFunction.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
// 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.skyframe;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.AutoloadSymbols;
import com.google.devtools.build.lib.packages.AutoloadSymbols.AutoloadException;
import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment;
import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment.InjectionException;
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
import com.google.devtools.build.lib.skyframe.BzlLoadFunction.InlineCacheManager;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.skyframe.RecordingSkyFunctionEnvironment;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.SkyframeLookupResult;
import javax.annotation.Nullable;
import net.starlark.java.eval.Dict;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Module;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkSemantics;
// TODO(#11437): Update the design doc to change `@builtins` -> `@_builtins`.
// TODO(#11437): Add support to BzlLoadCycleReporter to pretty-print cycles involving
// @_builtins.
// TODO(#11437): Add tombstone feature: If a native symbol is a tombstone object, this signals to
// StarlarkBuiltinsFunction that the corresponding symbol *must* be defined by @_builtins.
// Furthermore, if exports.bzl also says the symbol is a tombstone, any attempt to use it results
// in failure, as if the symbol doesn't exist at all (or with a user-friendly error message saying
// to migrate by adding a load()). Combine tombstones with reading the current incompatible flags
// within @_builtins for awesomeness.
// TODO(#11437, #11954, #11983): To the extent that BUILD-loaded .bzls and WORKSPACE-loaded .bzls
// have the same environment, builtins injection should apply to both of them, not just to
// BUILD-loaded .bzls.
/**
* A Skyframe function that evaluates the {@code @_builtins} pseudo-repository and reports the
* values exported by {@link #EXPORTS_ENTRYPOINT}. The {@code @_builtins} pseudo-repository shares a
* repo mapping with the {@code @bazel_tools} repository.
*
* <p>The process of "builtins injection" refers to evaluating this Skyfunction and applying its
* result to {@link BzlLoadFunction}'s computation. See also the <a
* href="https://docs.google.com/document/d/1GW7UVo1s9X0cti9OMgT3ga5ozKYUWLPk9k8c4-34rC4">design
* doc</a>:
*
* <p>This function has two trivial keys, so there can only be two values in the build at a time.
* First key/value (without autoloads) has a single dependency, on the result of evaluating the
* exports.bzl file to a {@link BzlLoadValue}. Second key/value has dependencies on the results of
* extenally loaded symbols and rules. For more information on the second value see {@link
* AutoloadSymbols}.
*
* <p>This function supports a special "inlining" mode, similar to {@link BzlLoadFunction} (see that
* class's javadoc and code comments). Whenever we inline {@link BzlLoadFunction} we also inline
* {@link StarlarkBuiltinsFunction} (and {@link StarlarkBuiltinsFunction}'s calls to {@link
* BzlLoadFunction} are then themselves inlined!). Similar to {@link BzlLoadFunction}'s inlining, we
* cache the result of this computation, and this caching is managed by {@link
* BzlLoadFunction.InlineCacheManager}. But since there's only a single {@link
* StarlarkBuiltinsValue} node and we don't need to worry about that node's value changing at future
* invocations or subsequent versions (see {@link InlineCacheManager#reset} for why), our caching
* strategy is much simpler and we don't need to bother inlining deps of the Skyframe subgraph.
*/
public class StarlarkBuiltinsFunction implements SkyFunction {
/**
* The label where {@code @_builtins} symbols are exported from. (Note that this is never
* conflated with an actual repository named "{@code @_builtins}" because 1) it is only ever
* accessed through a special SkyKey, and 2) we disallow the user from defining a repo named
* {@code @_builtins} to avoid confusion.)
*/
static final Label EXPORTS_ENTRYPOINT =
Label.parseCanonicalUnchecked("@_builtins//:exports.bzl"); // unused
/**
* Key for loading exports.bzl. Note that {@code keyForBuiltins} (as opposed to {@code
* keyForBuild}) ensures we can resolve {@code @_builtins}, which is otherwise inaccessible. It
* also prevents us from cyclically requesting StarlarkBuiltinsFunction again to evaluate
* exports.bzl.
*/
static final BzlLoadValue.Key EXPORTS_ENTRYPOINT_KEY =
BzlLoadValue.keyForBuiltins(EXPORTS_ENTRYPOINT);
// Used to obtain the injected environment.
private final BazelStarlarkEnvironment bazelStarlarkEnvironment;
public StarlarkBuiltinsFunction(BazelStarlarkEnvironment bazelStarlarkEnvironment) {
this.bazelStarlarkEnvironment = bazelStarlarkEnvironment;
}
@Override
@Nullable
public SkyValue compute(SkyKey skyKey, Environment env)
throws StarlarkBuiltinsFunctionException, InterruptedException {
try {
return computeInternal(
env,
bazelStarlarkEnvironment,
((StarlarkBuiltinsValue.Key) skyKey.argument()).isWithAutoloads(),
/* inliningState= */ null,
/* bzlLoadFunction= */ null);
} catch (BuiltinsFailedException e) {
throw new StarlarkBuiltinsFunctionException(e);
}
}
/**
* Computes this Skyfunction under inlining of {@link BzlLoadFunction}, forwarding the given
* inlining state.
*
* <p>The given Skyframe environment must be a {@link RecordingSkyFunctionEnvironment}. It is
* unwrapped before calling {@link BzlLoadFunction}'s inlining code path.
*
* <p>Returns null on Skyframe restart or error.
*/
@Nullable
public static StarlarkBuiltinsValue computeInline(
StarlarkBuiltinsValue.Key key,
BzlLoadFunction.InliningState inliningState,
BazelStarlarkEnvironment bazelStarlarkEnvironment,
BzlLoadFunction bzlLoadFunction)
throws BuiltinsFailedException, InterruptedException {
checkNotNull(bzlLoadFunction.inlineCacheManager);
StarlarkBuiltinsValue cachedBuiltins = bzlLoadFunction.inlineCacheManager.builtinsRef.get();
if (cachedBuiltins != null) {
// See the comment in InlineCacheManager#reset for why it's sound to not inline deps of the
// entire subgraph here.
return cachedBuiltins;
}
// See BzlLoadFunction#computeInline and BzlLoadFunction.InliningState for an explanation of the
// inlining mechanism and its invariants. For our purposes, the Skyframe environment to use
// comes from inliningState.
StarlarkBuiltinsValue computedBuiltins =
computeInternal(
inliningState.getEnvironment(),
bazelStarlarkEnvironment,
key.isWithAutoloads(),
inliningState,
bzlLoadFunction);
if (computedBuiltins == null) {
return null;
}
// There's a benign race where multiple threads may try to compute-and-cache the single builtins
// value. Ensure the value computed by winner of that race gets used by everyone.
bzlLoadFunction.inlineCacheManager.builtinsRef.compareAndSet(null, computedBuiltins);
return bzlLoadFunction.inlineCacheManager.builtinsRef.get();
}
// bzlLoadFunction and inliningState are non-null iff using inlining code path.
@Nullable
private static StarlarkBuiltinsValue computeInternal(
Environment env,
BazelStarlarkEnvironment bazelStarlarkEnvironment,
boolean isWithAutoloads,
@Nullable BzlLoadFunction.InliningState inliningState,
@Nullable BzlLoadFunction bzlLoadFunction)
throws BuiltinsFailedException, InterruptedException {
StarlarkSemantics starlarkSemantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
if (starlarkSemantics == null) {
return null;
}
// Return the empty value if builtins injection is disabled.
if (starlarkSemantics.get(BuildLanguageOptions.EXPERIMENTAL_BUILTINS_BZL_PATH).isEmpty()) {
return StarlarkBuiltinsValue.createEmpty(starlarkSemantics);
}
AutoloadSymbols autoloadSymbols = AutoloadSymbols.AUTOLOAD_SYMBOLS.get(env);
if (autoloadSymbols == null) {
return null;
}
if (autoloadSymbols.isEnabled()
&& starlarkSemantics.getBool(BuildLanguageOptions.ENABLE_BZLMOD)) {
// We can't do autoloads where the rules are implemented (disabling them when running in
// main repository named rules_python)
ModuleFileValue mainModule =
(ModuleFileValue) env.getValue(ModuleFileValue.KEY_FOR_ROOT_MODULE);
if (mainModule == null) {
return null;
}
if (autoloadSymbols.autoloadsDisabledForRepo(mainModule.getModule().getName())) {
isWithAutoloads = false;
}
}
// Load exports.bzl. If we were requested using inlining, make sure to inline the call back into
// BzlLoadFunction.
// If requested also loads "autoloads": rules and providers from external repositories.
BzlLoadValue exportsValue;
try {
if (inliningState == null) {
exportsValue =
(BzlLoadValue)
env.getValueOrThrow(EXPORTS_ENTRYPOINT_KEY, BzlLoadFailedException.class);
} else {
exportsValue = bzlLoadFunction.computeInline(EXPORTS_ENTRYPOINT_KEY, inliningState);
}
} catch (BzlLoadFailedException ex) {
throw BuiltinsFailedException.errorEvaluatingBuiltinsBzls(ex);
}
ImmutableMap<String, BzlLoadValue> autoBzlLoadValues;
try {
ImmutableMap<String, BzlLoadValue.Key> autoBzlLoadKeys =
isWithAutoloads ? autoloadSymbols.getLoadKeys(env) : ImmutableMap.of();
if (autoBzlLoadKeys == null) {
return null;
}
ImmutableMap.Builder<String, BzlLoadValue> autoBzlLoadValuesBuilder =
ImmutableMap.builderWithExpectedSize(autoBzlLoadKeys.size());
if (inliningState == null) {
SkyframeLookupResult values = env.getValuesAndExceptions(autoBzlLoadKeys.values());
for (var symbolKeyEntry : autoBzlLoadKeys.entrySet()) {
String symbol = symbolKeyEntry.getKey();
BzlLoadValue value =
(BzlLoadValue)
values.getOrThrow(symbolKeyEntry.getValue(), BzlLoadFailedException.class);
if (value != null) {
autoBzlLoadValuesBuilder.put(symbol, value);
}
}
} else {
for (var symbolKeyEntry : autoBzlLoadKeys.entrySet()) {
String symbol = symbolKeyEntry.getKey();
BzlLoadValue value =
bzlLoadFunction.computeInline(symbolKeyEntry.getValue(), inliningState);
if (value != null) {
autoBzlLoadValuesBuilder.put(symbol, value);
}
}
}
autoBzlLoadValues = autoBzlLoadValuesBuilder.buildOrThrow();
} catch (BzlLoadFailedException ex) {
throw BuiltinsFailedException.errorEvaluatingAutoloadedBzls(
ex, starlarkSemantics.getBool(BuildLanguageOptions.ENABLE_BZLMOD));
}
if (env.valuesMissing()) {
return null;
}
// Compute digest of exports.bzl and possibly externally loaded symbols (Bazel)
byte[] transitiveDigest = exportsValue.getTransitiveDigest();
if (!autoBzlLoadValues.isEmpty()) {
Fingerprint fp = new Fingerprint();
fp.addBytes(transitiveDigest);
for (BzlLoadValue value : autoBzlLoadValues.values()) {
fp.addBytes(value.getTransitiveDigest());
}
transitiveDigest = fp.digestAndReset();
}
// Apply declarations of exports.bzl to the native predeclared symbols.
Module module = exportsValue.getModule();
try {
ImmutableMap<String, Object> exportedToplevels = getDict(module, "exported_toplevels");
ImmutableMap<String, Object> exportedRules = getDict(module, "exported_rules");
ImmutableMap<String, Object> exportedToJava = getDict(module, "exported_to_java");
ImmutableMap<String, Object> predeclaredForBuildBzl =
bazelStarlarkEnvironment.createBuildBzlEnvUsingInjection(
exportedToplevels,
exportedRules,
starlarkSemantics.get(BuildLanguageOptions.EXPERIMENTAL_BUILTINS_INJECTION_OVERRIDE));
ImmutableMap<String, Object> predeclaredForWorkspaceBzl =
bazelStarlarkEnvironment.createWorkspaceBzlEnvUsingInjection(
exportedToplevels,
exportedRules,
starlarkSemantics.get(BuildLanguageOptions.EXPERIMENTAL_BUILTINS_INJECTION_OVERRIDE));
ImmutableMap<String, Object> predeclaredForBuild =
bazelStarlarkEnvironment.createBuildEnvUsingInjection(
exportedRules,
starlarkSemantics.get(BuildLanguageOptions.EXPERIMENTAL_BUILTINS_INJECTION_OVERRIDE));
// Apply declarations of externally loaded symbols to the native predeclared symbols.
ImmutableMap<String, Object> newSymbols = autoloadSymbols.processLoads(autoBzlLoadValues);
predeclaredForBuild =
autoloadSymbols.modifyBuildEnv(isWithAutoloads, predeclaredForBuild, newSymbols);
predeclaredForBuildBzl =
autoloadSymbols.modifyBuildBzlEnv(isWithAutoloads, predeclaredForBuildBzl, newSymbols);
return StarlarkBuiltinsValue.create(
predeclaredForBuildBzl,
predeclaredForWorkspaceBzl,
predeclaredForBuild,
exportedToJava,
transitiveDigest,
starlarkSemantics);
} catch (EvalException | InjectionException ex) {
throw BuiltinsFailedException.errorApplyingExports(ex);
} catch (AutoloadException ex) {
throw new BuiltinsFailedException(
String.format("Failed to apply symbols loaded externally: %s", ex.getMessage()),
ex,
Transience.PERSISTENT);
}
}
/**
* Attempts to retrieve the string-keyed dict named {@code dictName} from the given {@code
* module}.
*
* @return a copy of the dict mappings on success
* @throws EvalException if the symbol isn't present or is not a dict whose keys are all strings
*/
@Nullable
private static ImmutableMap<String, Object> getDict(Module module, String dictName)
throws EvalException {
Object value = module.getGlobal(dictName);
if (value == null) {
throw Starlark.errorf("expected a '%s' dictionary to be defined", dictName);
}
return ImmutableMap.copyOf(Dict.cast(value, String.class, Object.class, dictName + " dict"));
}
/**
* An exception that occurs while trying to determine the injected builtins.
*
* <p>This exception type typically wraps a {@link BzlLoadFailedException} and is wrapped by a
* {@link BzlLoadFailedException} in turn.
*/
static final class BuiltinsFailedException extends Exception {
private final Transience transience;
private BuiltinsFailedException(String errorMessage, Exception cause, Transience transience) {
super(errorMessage, cause);
this.transience = transience;
}
Transience getTransience() {
return transience;
}
static BuiltinsFailedException errorEvaluatingBuiltinsBzls(BzlLoadFailedException cause) {
return errorEvaluatingBuiltinsBzls(cause, cause.getTransience());
}
static BuiltinsFailedException errorEvaluatingBuiltinsBzls(
Exception cause, Transience transience) {
return new BuiltinsFailedException(
String.format("Failed to load builtins sources: %s", cause.getMessage()),
cause,
transience);
}
static BuiltinsFailedException errorEvaluatingAutoloadedBzls(
BzlLoadFailedException cause, boolean bzlmodEnabled) {
String additionalMessage =
bzlmodEnabled
? ""
: " Most likely you need to upgrade the version of rules repository in the"
+ " WORKSPACE file.";
return new BuiltinsFailedException(
String.format(
"Failed to autoload external symbols: %s%s", cause.getMessage(), additionalMessage),
cause,
cause.getTransience());
}
static BuiltinsFailedException errorApplyingExports(Exception cause) {
return new BuiltinsFailedException(
String.format("Failed to apply declared builtins: %s", cause.getMessage()),
cause,
Transience.PERSISTENT);
}
}
/** The exception type thrown by {@link StarlarkBuiltinsFunction}. */
static final class StarlarkBuiltinsFunctionException extends SkyFunctionException {
private StarlarkBuiltinsFunctionException(BuiltinsFailedException cause) {
super(cause, cause.transience);
}
}
}