Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 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 | |
| 15 | package com.google.devtools.build.lib.skyframe; |
| 16 | |
Googler | bf6e469 | 2023-06-30 08:32:01 -0700 | [diff] [blame] | 17 | import com.google.common.collect.ImmutableMap; |
janakr | 3af783a | 2020-09-08 14:12:14 -0700 | [diff] [blame] | 18 | import com.google.common.hash.HashFunction; |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 19 | import com.google.devtools.build.lib.actions.FileValue; |
Googler | 4c84ce1 | 2022-09-23 00:40:53 -0700 | [diff] [blame] | 20 | import com.google.devtools.build.lib.cmdline.BazelCompileContext; |
John Field | a97e17f | 2015-11-13 02:19:52 +0000 | [diff] [blame] | 21 | import com.google.devtools.build.lib.cmdline.Label; |
Googler | f0890f0 | 2019-10-01 07:28:48 -0700 | [diff] [blame] | 22 | import com.google.devtools.build.lib.events.Event; |
Ivo List | f9ed0b0 | 2024-08-23 02:37:47 -0700 | [diff] [blame] | 23 | import com.google.devtools.build.lib.packages.AutoloadSymbols; |
brandjon | 015535a | 2021-02-01 10:30:37 -0800 | [diff] [blame] | 24 | import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment; |
tomlu | 67c84b1 | 2017-11-06 19:49:16 +0100 | [diff] [blame] | 25 | import com.google.devtools.build.lib.vfs.FileSystemUtils; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 26 | import com.google.devtools.build.lib.vfs.Path; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 27 | import com.google.devtools.build.lib.vfs.RootedPath; |
| 28 | import com.google.devtools.build.skyframe.SkyFunction; |
| 29 | import com.google.devtools.build.skyframe.SkyFunctionException; |
| 30 | import com.google.devtools.build.skyframe.SkyFunctionException.Transience; |
| 31 | import com.google.devtools.build.skyframe.SkyKey; |
| 32 | import com.google.devtools.build.skyframe.SkyValue; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 33 | import java.io.IOException; |
Googler | 5cc598c | 2022-07-06 03:29:45 -0700 | [diff] [blame] | 34 | import javax.annotation.Nullable; |
adonovan | 450c7ad | 2020-09-14 13:00:21 -0700 | [diff] [blame] | 35 | import net.starlark.java.eval.Module; |
| 36 | import net.starlark.java.eval.StarlarkSemantics; |
| 37 | import net.starlark.java.syntax.FileOptions; |
| 38 | import net.starlark.java.syntax.ParserInput; |
adonovan | c42a06f | 2020-12-02 14:42:55 -0800 | [diff] [blame] | 39 | import net.starlark.java.syntax.Program; |
adonovan | 450c7ad | 2020-09-14 13:00:21 -0700 | [diff] [blame] | 40 | import net.starlark.java.syntax.StarlarkFile; |
adonovan | c42a06f | 2020-12-02 14:42:55 -0800 | [diff] [blame] | 41 | import net.starlark.java.syntax.SyntaxError; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 42 | |
| 43 | /** |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 44 | * A Skyframe function that compiles the .bzl file denoted by a Label. |
John Field | a97e17f | 2015-11-13 02:19:52 +0000 | [diff] [blame] | 45 | * |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 46 | * <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} |
Googler | 66d099e | 2019-09-26 08:07:06 -0700 | [diff] [blame] | 48 | * package. If the file (or the package containing it) doesn't exist, the function doesn't fail, but |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 49 | * instead returns a specific {@code NO_FILE} {@link BzlCompileValue}. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 50 | */ |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 51 | // TODO(adonovan): actually compile. The name is a step ahead of the implementation. |
| 52 | public class BzlCompileFunction implements SkyFunction { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 53 | |
Googler | 696f70a | 2023-05-12 11:17:18 -0700 | [diff] [blame] | 54 | private final BazelStarlarkEnvironment bazelStarlarkEnvironment; |
janakr | 3af783a | 2020-09-08 14:12:14 -0700 | [diff] [blame] | 55 | private final HashFunction hashFunction; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 56 | |
Googler | 696f70a | 2023-05-12 11:17:18 -0700 | [diff] [blame] | 57 | public BzlCompileFunction( |
| 58 | BazelStarlarkEnvironment bazelStarlarkEnvironment, HashFunction hashFunction) { |
| 59 | this.bazelStarlarkEnvironment = bazelStarlarkEnvironment; |
janakr | 3af783a | 2020-09-08 14:12:14 -0700 | [diff] [blame] | 60 | this.hashFunction = hashFunction; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 61 | } |
| 62 | |
| 63 | @Override |
brandjon | c782bbc | 2020-08-05 13:47:13 -0700 | [diff] [blame] | 64 | public SkyValue compute(SkyKey skyKey, Environment env) |
| 65 | throws SkyFunctionException, InterruptedException { |
nharmata | dc1d9dc | 2020-04-18 16:53:28 -0700 | [diff] [blame] | 66 | try { |
brandjon | c782bbc | 2020-08-05 13:47:13 -0700 | [diff] [blame] | 67 | return computeInline( |
Googler | 696f70a | 2023-05-12 11:17:18 -0700 | [diff] [blame] | 68 | (BzlCompileValue.Key) skyKey.argument(), env, bazelStarlarkEnvironment, hashFunction); |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 69 | } catch (FailedIOException e) { |
| 70 | throw new FunctionException(e); |
nharmata | dc1d9dc | 2020-04-18 16:53:28 -0700 | [diff] [blame] | 71 | } |
| 72 | } |
| 73 | |
Googler | 5cc598c | 2022-07-06 03:29:45 -0700 | [diff] [blame] | 74 | @Nullable |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 75 | static BzlCompileValue computeInline( |
| 76 | BzlCompileValue.Key key, |
nharmata | 3487930 | 2020-04-22 11:30:15 -0700 | [diff] [blame] | 77 | Environment env, |
Googler | 696f70a | 2023-05-12 11:17:18 -0700 | [diff] [blame] | 78 | BazelStarlarkEnvironment bazelStarlarkEnvironment, |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 79 | HashFunction hashFunction) |
| 80 | throws FailedIOException, InterruptedException { |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 81 | byte[] bytes; |
| 82 | byte[] digest; |
| 83 | String inputName; |
| 84 | |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 85 | if (key.kind == BzlCompileValue.Kind.EMPTY_PRELUDE) { |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 86 | // 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) { |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 99 | throw new FailedIOException(e, Transience.PERSISTENT); |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 100 | } |
| 101 | if (fileValue == null) { |
| 102 | return null; |
| 103 | } |
| 104 | |
| 105 | if (fileValue.exists()) { |
| 106 | if (!fileValue.isFile()) { |
| 107 | return fileValue.isDirectory() |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 108 | ? BzlCompileValue.noFile("cannot load '%s': is a directory", key.label) |
| 109 | : BzlCompileValue.noFile( |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 110 | "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) { |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 121 | throw new FailedIOException(e, Transience.TRANSIENT); |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 122 | } |
| 123 | digest = fileValue.getDigest(); // may be null |
| 124 | inputName = path.toString(); |
| 125 | } else { |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 126 | if (key.kind == BzlCompileValue.Kind.PRELUDE) { |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 127 | // A non-existent prelude is fine. |
| 128 | bytes = new byte[] {}; |
| 129 | digest = null; |
| 130 | inputName = "<default prelude>"; |
| 131 | } else { |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 132 | return BzlCompileValue.noFile("cannot load '%s': no such file", key.label); |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 133 | } |
| 134 | } |
John Field | a97e17f | 2015-11-13 02:19:52 +0000 | [diff] [blame] | 135 | } |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 136 | |
| 137 | // Compute digest if we didn't already get it from a fileValue. |
| 138 | if (digest == null) { |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 139 | digest = hashFunction.hashBytes(bytes).asBytes(); |
John Field | a97e17f | 2015-11-13 02:19:52 +0000 | [diff] [blame] | 140 | } |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 141 | |
adonovan | 034220a | 2020-03-24 10:11:26 -0700 | [diff] [blame] | 142 | StarlarkSemantics semantics = PrecomputedValue.STARLARK_SEMANTICS.get(env); |
| 143 | if (semantics == null) { |
brandjon | b712f33 | 2017-04-29 16:03:32 +0200 | [diff] [blame] | 144 | return null; |
| 145 | } |
John Field | a97e17f | 2015-11-13 02:19:52 +0000 | [diff] [blame] | 146 | |
Googler | bf6e469 | 2023-06-30 08:32:01 -0700 | [diff] [blame] | 147 | ImmutableMap<String, Object> predeclared; |
Googler | a67200c | 2023-05-22 12:16:29 -0700 | [diff] [blame] | 148 | if (key.isSclDialect()) { |
| 149 | predeclared = bazelStarlarkEnvironment.getStarlarkGlobals().getSclToplevels(); |
| 150 | } else if (key.kind == BzlCompileValue.Kind.BUILTINS) { |
Googler | 696f70a | 2023-05-12 11:17:18 -0700 | [diff] [blame] | 151 | predeclared = bazelStarlarkEnvironment.getBuiltinsBzlEnv(); |
brandjon | 9db98c4 | 2020-08-24 08:43:55 -0700 | [diff] [blame] | 152 | } else { |
| 153 | // Use the predeclared environment for BUILD-loaded bzl files, ignoring injection. It is not |
brandjon | 4be7554 | 2020-08-30 14:34:01 -0700 | [diff] [blame] | 154 | // 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 |
Googler | c4358db | 2023-05-12 10:27:12 -0700 | [diff] [blame] | 156 | // all we need to do symbol resolution. |
| 157 | // |
brandjon | 4be7554 | 2020-08-30 14:34:01 -0700 | [diff] [blame] | 158 | // 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 List | f9ed0b0 | 2024-08-23 02:37:47 -0700 | [diff] [blame] | 161 | |
| 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. |
Googler | 3bc5dea | 2024-10-02 01:26:15 -0700 | [diff] [blame] | 164 | AutoloadSymbols autoloadSymbols = AutoloadSymbols.AUTOLOAD_SYMBOLS.get(env); |
Ivo List | f9ed0b0 | 2024-08-23 02:37:47 -0700 | [diff] [blame] | 165 | if (autoloadSymbols == null) { |
| 166 | return null; |
| 167 | } |
| 168 | predeclared = autoloadSymbols.getUninjectedBuildBzlEnv(key.getLabel()); |
brandjon | 9db98c4 | 2020-08-24 08:43:55 -0700 | [diff] [blame] | 169 | } |
| 170 | |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 171 | // We have all deps. Parse, resolve, and return. |
| 172 | ParserInput input = ParserInput.fromLatin1(bytes, inputName); |
adonovan | 034220a | 2020-03-24 10:11:26 -0700 | [diff] [blame] | 173 | FileOptions options = |
| 174 | FileOptions.builder() |
adonovan | 1f9b8ed | 2020-12-10 04:05:12 -0800 | [diff] [blame] | 175 | // 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()) |
Googler | 0099454 | 2023-05-23 06:42:51 -0700 | [diff] [blame] | 180 | // .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()) |
adonovan | 034220a | 2020-03-24 10:11:26 -0700 | [diff] [blame] | 187 | .build(); |
brandjon | 2a73a73 | 2020-08-08 06:41:14 -0700 | [diff] [blame] | 188 | StarlarkFile file = StarlarkFile.parse(input, options); |
adonovan | c42a06f | 2020-12-02 14:42:55 -0800 | [diff] [blame] | 189 | |
| 190 | // compile |
Googler | 4c84ce1 | 2022-09-23 00:40:53 -0700 | [diff] [blame] | 191 | 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 | } |
adonovan | c42a06f | 2020-12-02 14:42:55 -0800 | [diff] [blame] | 205 | 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( |
Googler | d3bef49 | 2022-09-08 08:04:26 -0700 | [diff] [blame] | 211 | "compilation of module '%s'%s failed", |
adonovan | c42a06f | 2020-12-02 14:42:55 -0800 | [diff] [blame] | 212 | key.label.toPathFragment(), |
Googler | d3bef49 | 2022-09-08 08:04:26 -0700 | [diff] [blame] | 213 | StarlarkBuiltinsValue.isBuiltinsRepo(key.label.getRepository()) ? " (internal)" : ""); |
adonovan | c42a06f | 2020-12-02 14:42:55 -0800 | [diff] [blame] | 214 | } |
Michajlo Matijkiw | 2a7c802 | 2015-09-22 02:22:12 +0000 | [diff] [blame] | 215 | } |
| 216 | |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 217 | static final class FailedIOException extends Exception { |
brandjon | b9c81cd | 2020-08-24 11:00:58 -0700 | [diff] [blame] | 218 | private final Transience transience; |
| 219 | |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 220 | private FailedIOException(IOException cause, Transience transience) { |
brandjon | b9c81cd | 2020-08-24 11:00:58 -0700 | [diff] [blame] | 221 | super(cause.getMessage(), cause); |
| 222 | this.transience = transience; |
| 223 | } |
| 224 | |
| 225 | Transience getTransience() { |
| 226 | return transience; |
| 227 | } |
| 228 | } |
| 229 | |
adonovan | 0a1f0f5 | 2020-09-16 11:11:04 -0700 | [diff] [blame] | 230 | private static final class FunctionException extends SkyFunctionException { |
| 231 | private FunctionException(FailedIOException cause) { |
brandjon | b9c81cd | 2020-08-24 11:00:58 -0700 | [diff] [blame] | 232 | super(cause, cause.transience); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 233 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 234 | } |
| 235 | } |