blob: f5bc53498b3c1718e6ce2336c4ebd0c423cfce89 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.skyframe;
16
Googlerbf6e4692023-06-30 08:32:01 -070017import com.google.common.collect.ImmutableMap;
janakr3af783a2020-09-08 14:12:14 -070018import com.google.common.hash.HashFunction;
shahan602cc852018-06-06 20:09:57 -070019import com.google.devtools.build.lib.actions.FileValue;
Googler4c84ce12022-09-23 00:40:53 -070020import com.google.devtools.build.lib.cmdline.BazelCompileContext;
John Fielda97e17f2015-11-13 02:19:52 +000021import com.google.devtools.build.lib.cmdline.Label;
Googlerf0890f02019-10-01 07:28:48 -070022import com.google.devtools.build.lib.events.Event;
Ivo Listf9ed0b02024-08-23 02:37:47 -070023import com.google.devtools.build.lib.packages.AutoloadSymbols;
brandjon015535a2021-02-01 10:30:37 -080024import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment;
tomlu67c84b12017-11-06 19:49:16 +010025import com.google.devtools.build.lib.vfs.FileSystemUtils;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.vfs.RootedPath;
28import com.google.devtools.build.skyframe.SkyFunction;
29import com.google.devtools.build.skyframe.SkyFunctionException;
30import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
31import com.google.devtools.build.skyframe.SkyKey;
32import com.google.devtools.build.skyframe.SkyValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import java.io.IOException;
Googler5cc598c2022-07-06 03:29:45 -070034import javax.annotation.Nullable;
adonovan450c7ad2020-09-14 13:00:21 -070035import net.starlark.java.eval.Module;
36import net.starlark.java.eval.StarlarkSemantics;
37import net.starlark.java.syntax.FileOptions;
38import net.starlark.java.syntax.ParserInput;
adonovanc42a06f2020-12-02 14:42:55 -080039import net.starlark.java.syntax.Program;
adonovan450c7ad2020-09-14 13:00:21 -070040import net.starlark.java.syntax.StarlarkFile;
adonovanc42a06f2020-12-02 14:42:55 -080041import net.starlark.java.syntax.SyntaxError;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042
43/**
adonovan0a1f0f52020-09-16 11:11:04 -070044 * A Skyframe function that compiles the .bzl file denoted by a Label.
John Fielda97e17f2015-11-13 02:19:52 +000045 *
adonovan0a1f0f52020-09-16 11:11:04 -070046 * <p>Given a {@link Label} referencing a Starlark file, BzlCompileFunction loads, parses, resolves,
47 * and compiles it. The Label must be absolute, and must not reference the special {@code external}
Googler66d099e2019-09-26 08:07:06 -070048 * package. If the file (or the package containing it) doesn't exist, the function doesn't fail, but
adonovan0a1f0f52020-09-16 11:11:04 -070049 * instead returns a specific {@code NO_FILE} {@link BzlCompileValue}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010050 */
adonovan0a1f0f52020-09-16 11:11:04 -070051// TODO(adonovan): actually compile. The name is a step ahead of the implementation.
52public class BzlCompileFunction implements SkyFunction {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010053
Googler696f70a2023-05-12 11:17:18 -070054 private final BazelStarlarkEnvironment bazelStarlarkEnvironment;
janakr3af783a2020-09-08 14:12:14 -070055 private final HashFunction hashFunction;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010056
Googler696f70a2023-05-12 11:17:18 -070057 public BzlCompileFunction(
58 BazelStarlarkEnvironment bazelStarlarkEnvironment, HashFunction hashFunction) {
59 this.bazelStarlarkEnvironment = bazelStarlarkEnvironment;
janakr3af783a2020-09-08 14:12:14 -070060 this.hashFunction = hashFunction;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010061 }
62
63 @Override
brandjonc782bbc2020-08-05 13:47:13 -070064 public SkyValue compute(SkyKey skyKey, Environment env)
65 throws SkyFunctionException, InterruptedException {
nharmatadc1d9dc2020-04-18 16:53:28 -070066 try {
brandjonc782bbc2020-08-05 13:47:13 -070067 return computeInline(
Googler696f70a2023-05-12 11:17:18 -070068 (BzlCompileValue.Key) skyKey.argument(), env, bazelStarlarkEnvironment, hashFunction);
adonovan0a1f0f52020-09-16 11:11:04 -070069 } catch (FailedIOException e) {
70 throw new FunctionException(e);
nharmatadc1d9dc2020-04-18 16:53:28 -070071 }
72 }
73
Googler5cc598c2022-07-06 03:29:45 -070074 @Nullable
adonovan0a1f0f52020-09-16 11:11:04 -070075 static BzlCompileValue computeInline(
76 BzlCompileValue.Key key,
nharmata34879302020-04-22 11:30:15 -070077 Environment env,
Googler696f70a2023-05-12 11:17:18 -070078 BazelStarlarkEnvironment bazelStarlarkEnvironment,
adonovan0a1f0f52020-09-16 11:11:04 -070079 HashFunction hashFunction)
80 throws FailedIOException, InterruptedException {
brandjon2a73a732020-08-08 06:41:14 -070081 byte[] bytes;
82 byte[] digest;
83 String inputName;
84
adonovan0a1f0f52020-09-16 11:11:04 -070085 if (key.kind == BzlCompileValue.Kind.EMPTY_PRELUDE) {
brandjon2a73a732020-08-08 06:41:14 -070086 // Default prelude is empty.
87 bytes = new byte[] {};
88 digest = null;
89 inputName = "<default prelude>";
90 } else {
91
92 // Obtain the file.
93 RootedPath rootedPath = RootedPath.toRootedPath(key.root, key.label.toPathFragment());
94 SkyKey fileSkyKey = FileValue.key(rootedPath);
95 FileValue fileValue = null;
96 try {
97 fileValue = (FileValue) env.getValueOrThrow(fileSkyKey, IOException.class);
98 } catch (IOException e) {
adonovan0a1f0f52020-09-16 11:11:04 -070099 throw new FailedIOException(e, Transience.PERSISTENT);
brandjon2a73a732020-08-08 06:41:14 -0700100 }
101 if (fileValue == null) {
102 return null;
103 }
104
105 if (fileValue.exists()) {
106 if (!fileValue.isFile()) {
107 return fileValue.isDirectory()
adonovan0a1f0f52020-09-16 11:11:04 -0700108 ? BzlCompileValue.noFile("cannot load '%s': is a directory", key.label)
109 : BzlCompileValue.noFile(
brandjon2a73a732020-08-08 06:41:14 -0700110 "cannot load '%s': not a regular file (dangling link?)", key.label);
111 }
112
113 // Read the file.
114 Path path = rootedPath.asPath();
115 try {
116 bytes =
117 fileValue.isSpecialFile()
118 ? FileSystemUtils.readContent(path)
119 : FileSystemUtils.readWithKnownFileSize(path, fileValue.getSize());
120 } catch (IOException e) {
adonovan0a1f0f52020-09-16 11:11:04 -0700121 throw new FailedIOException(e, Transience.TRANSIENT);
brandjon2a73a732020-08-08 06:41:14 -0700122 }
123 digest = fileValue.getDigest(); // may be null
124 inputName = path.toString();
125 } else {
adonovan0a1f0f52020-09-16 11:11:04 -0700126 if (key.kind == BzlCompileValue.Kind.PRELUDE) {
brandjon2a73a732020-08-08 06:41:14 -0700127 // A non-existent prelude is fine.
128 bytes = new byte[] {};
129 digest = null;
130 inputName = "<default prelude>";
131 } else {
adonovan0a1f0f52020-09-16 11:11:04 -0700132 return BzlCompileValue.noFile("cannot load '%s': no such file", key.label);
brandjon2a73a732020-08-08 06:41:14 -0700133 }
134 }
John Fielda97e17f2015-11-13 02:19:52 +0000135 }
brandjon2a73a732020-08-08 06:41:14 -0700136
137 // Compute digest if we didn't already get it from a fileValue.
138 if (digest == null) {
adonovan0a1f0f52020-09-16 11:11:04 -0700139 digest = hashFunction.hashBytes(bytes).asBytes();
John Fielda97e17f2015-11-13 02:19:52 +0000140 }
brandjon2a73a732020-08-08 06:41:14 -0700141
adonovan034220a2020-03-24 10:11:26 -0700142 StarlarkSemantics semantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
143 if (semantics == null) {
brandjonb712f332017-04-29 16:03:32 +0200144 return null;
145 }
John Fielda97e17f2015-11-13 02:19:52 +0000146
Googlerbf6e4692023-06-30 08:32:01 -0700147 ImmutableMap<String, Object> predeclared;
Googlera67200c2023-05-22 12:16:29 -0700148 if (key.isSclDialect()) {
149 predeclared = bazelStarlarkEnvironment.getStarlarkGlobals().getSclToplevels();
150 } else if (key.kind == BzlCompileValue.Kind.BUILTINS) {
Googler696f70a2023-05-12 11:17:18 -0700151 predeclared = bazelStarlarkEnvironment.getBuiltinsBzlEnv();
brandjon9db98c42020-08-24 08:43:55 -0700152 } else {
153 // Use the predeclared environment for BUILD-loaded bzl files, ignoring injection. It is not
brandjon4be75542020-08-30 14:34:01 -0700154 // the right env for the actual evaluation of BUILD-loaded bzl files because it doesn't
155 // map to the injected symbols. But the names of the symbols are the same, and the names are
Googlerc4358db2023-05-12 10:27:12 -0700156 // all we need to do symbol resolution.
157 //
brandjon4be75542020-08-30 14:34:01 -0700158 // For WORKSPACE-loaded bzl files, the env isn't quite right not because of injection but
159 // because the "native" object is different. But A) that will be fixed with #11954, and B) we
160 // don't care for the same reason as above.
Ivo Listf9ed0b02024-08-23 02:37:47 -0700161
162 // Takes into account --incompatible_autoload_externally, similarly to the comment above, this
163 // only defines the correct set of symbols, but does not load them yet.
Googler3bc5dea2024-10-02 01:26:15 -0700164 AutoloadSymbols autoloadSymbols = AutoloadSymbols.AUTOLOAD_SYMBOLS.get(env);
Ivo Listf9ed0b02024-08-23 02:37:47 -0700165 if (autoloadSymbols == null) {
166 return null;
167 }
168 predeclared = autoloadSymbols.getUninjectedBuildBzlEnv(key.getLabel());
brandjon9db98c42020-08-24 08:43:55 -0700169 }
170
brandjon2a73a732020-08-08 06:41:14 -0700171 // We have all deps. Parse, resolve, and return.
172 ParserInput input = ParserInput.fromLatin1(bytes, inputName);
adonovan034220a2020-03-24 10:11:26 -0700173 FileOptions options =
174 FileOptions.builder()
adonovan1f9b8ed2020-12-10 04:05:12 -0800175 // By default, Starlark load statements create file-local bindings.
176 // However, the BUILD prelude typically contains nothing but load
177 // statements whose bindings are intended to be visible in all BUILD
178 // files. The loadBindsGlobally flag allows us to retrieve them.
179 .loadBindsGlobally(key.isBuildPrelude())
Googler00994542023-05-23 06:42:51 -0700180 // .scl files should be ASCII-only in string literals.
181 // TODO(bazel-team): It'd be nice if we could intercept non-ASCII errors from the lexer,
182 // and modify the displayed message to clarify to the user that the string would be
183 // permitted in a .bzl file. But there's no easy way to do that short of either string
184 // matching the error message or reworking the interpreter API to put more structured
185 // detail in errors (i.e. new fields or error subclasses).
186 .stringLiteralsAreAsciiOnly(key.isSclDialect())
adonovan034220a2020-03-24 10:11:26 -0700187 .build();
brandjon2a73a732020-08-08 06:41:14 -0700188 StarlarkFile file = StarlarkFile.parse(input, options);
adonovanc42a06f2020-12-02 14:42:55 -0800189
190 // compile
Googler4c84ce12022-09-23 00:40:53 -0700191 final Module module;
192
193 if (key.kind == BzlCompileValue.Kind.EMPTY_PRELUDE) {
194 // The empty prelude has no label, so we can't use it to filter the predeclareds.
195 // This doesn't matter since the empty prelude doesn't attempt to access any predeclareds
196 // anyway.
197 module = Module.withPredeclared(semantics, predeclared);
198 } else {
199 // The BazelCompileContext holds additional contextual info to be associated with the Module
200 // The information is used to filter predeclareds
201 BazelCompileContext bazelCompileContext =
202 BazelCompileContext.create(key.label, file.getName());
203 module = Module.withPredeclaredAndData(semantics, predeclared, bazelCompileContext);
204 }
adonovanc42a06f2020-12-02 14:42:55 -0800205 try {
206 Program prog = Program.compileFile(file, module);
207 return BzlCompileValue.withProgram(prog, digest);
208 } catch (SyntaxError.Exception ex) {
209 Event.replayEventsOn(env.getListener(), ex.errors());
210 return BzlCompileValue.noFile(
Googlerd3bef492022-09-08 08:04:26 -0700211 "compilation of module '%s'%s failed",
adonovanc42a06f2020-12-02 14:42:55 -0800212 key.label.toPathFragment(),
Googlerd3bef492022-09-08 08:04:26 -0700213 StarlarkBuiltinsValue.isBuiltinsRepo(key.label.getRepository()) ? " (internal)" : "");
adonovanc42a06f2020-12-02 14:42:55 -0800214 }
Michajlo Matijkiw2a7c8022015-09-22 02:22:12 +0000215 }
216
adonovan0a1f0f52020-09-16 11:11:04 -0700217 static final class FailedIOException extends Exception {
brandjonb9c81cd2020-08-24 11:00:58 -0700218 private final Transience transience;
219
adonovan0a1f0f52020-09-16 11:11:04 -0700220 private FailedIOException(IOException cause, Transience transience) {
brandjonb9c81cd2020-08-24 11:00:58 -0700221 super(cause.getMessage(), cause);
222 this.transience = transience;
223 }
224
225 Transience getTransience() {
226 return transience;
227 }
228 }
229
adonovan0a1f0f52020-09-16 11:11:04 -0700230 private static final class FunctionException extends SkyFunctionException {
231 private FunctionException(FailedIOException cause) {
brandjonb9c81cd2020-08-24 11:00:58 -0700232 super(cause, cause.transience);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100233 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100234 }
235}