blob: e7b1037800799cf19042abf2f64389d75ce81878 [file]
// Copyright 2014 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 com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashFunction;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.cmdline.BazelCompileContext;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.io.InconsistentFilesystemException;
import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment;
import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
import com.google.devtools.build.lib.packages.PackageLoadingListener;
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
import com.google.devtools.build.lib.skyframe.BzlCompileValue.TypeOptions;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
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 java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Module;
import net.starlark.java.eval.Mutability;
import net.starlark.java.eval.Sequence;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.syntax.AssignmentStatement;
import net.starlark.java.syntax.FileOptions;
import net.starlark.java.syntax.Identifier;
import net.starlark.java.syntax.Location;
import net.starlark.java.syntax.ParserInput;
import net.starlark.java.syntax.Program;
import net.starlark.java.syntax.StarlarkFile;
import net.starlark.java.syntax.Statement;
import net.starlark.java.syntax.StringLiteral;
import net.starlark.java.syntax.SyntaxError;
/**
* A Skyframe function that compiles the .bzl file denoted by a Label.
*
* <p>Given a {@link Label} referencing a Starlark file, BzlCompileFunction loads, parses, resolves,
* and compiles it. The Label must be absolute, and must not reference the special {@code external}
* package. If the file (or the package containing it) doesn't exist, the function doesn't fail, but
* instead returns a specific {@code NO_FILE} {@link BzlCompileValue}.
*/
// TODO(adonovan): actually compile. The name is a step ahead of the implementation.
public class BzlCompileFunction implements SkyFunction {
private final BazelStarlarkEnvironment bazelStarlarkEnvironment;
private final HashFunction hashFunction;
private final PackageLoadingListener packageLoadingListener;
public BzlCompileFunction(
BazelStarlarkEnvironment bazelStarlarkEnvironment,
HashFunction hashFunction,
PackageLoadingListener packageLoadingListener) {
this.bazelStarlarkEnvironment = bazelStarlarkEnvironment;
this.hashFunction = hashFunction;
this.packageLoadingListener = packageLoadingListener;
}
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws SkyFunctionException, InterruptedException {
try {
return computeInline(
(BzlCompileValue.Key) skyKey.argument(),
env,
bazelStarlarkEnvironment,
hashFunction,
packageLoadingListener);
} catch (FailedIOException e) {
throw new FunctionException(e);
}
}
@Nullable
static BzlCompileValue computeInline(
BzlCompileValue.Key key,
Environment env,
BazelStarlarkEnvironment bazelStarlarkEnvironment,
HashFunction hashFunction,
PackageLoadingListener packageLoadingListener)
throws FailedIOException, InterruptedException {
byte[] bytes;
byte[] digest;
String inputName;
RootedPath rootedPath = null;
StarlarkSemantics semantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
if (semantics == null) {
return null;
}
if (key.kind == BzlCompileValue.Kind.EMPTY_PRELUDE) {
// Default prelude is empty.
bytes = new byte[] {};
digest = null;
inputName = "<default prelude>";
} else {
// Obtain the file.
rootedPath = RootedPath.toRootedPath(key.root, key.label.toPathFragment());
SkyKey fileSkyKey = FileValue.key(rootedPath);
FileValue fileValue = null;
try {
fileValue = (FileValue) env.getValueOrThrow(fileSkyKey, IOException.class);
} catch (IOException e) {
throw new FailedIOException(e, Transience.PERSISTENT);
}
if (fileValue == null) {
return null;
}
if (fileValue.exists()) {
if (!fileValue.isFile()) {
return fileValue.isDirectory()
? BzlCompileValue.noFile("cannot load '%s': is a directory", key.label)
: BzlCompileValue.noFile(
"cannot load '%s': not a regular file (dangling link?)", key.label);
}
Path path = rootedPath.asPath();
Location location = Location.fromFile(path.toString());
long fileSize = fileValue.getSize();
// Special files (such as named pipes or sockets used to coordinate timing in tests) do not
// report a meaningful size in stat(), so their size is checked after reading below.
if (key.kind == BzlCompileValue.Kind.NORMAL && !fileValue.isSpecialFile()) {
BzlCompileValue limitFailure =
checkHardFileSizeLimit(
key, fileSize, location, semantics, bazelStarlarkEnvironment, env);
if (env.valuesMissing()) {
return null;
}
if (limitFailure != null) {
return limitFailure;
}
}
// Read the file.
try {
bytes =
fileValue.isSpecialFile()
? FileSystemUtils.readContent(path)
: FileSystemUtils.readWithKnownFileSize(path, fileSize);
} catch (IOException e) {
throw new FailedIOException(e, Transience.TRANSIENT);
}
if (key.kind == BzlCompileValue.Kind.NORMAL && fileValue.isSpecialFile()) {
BzlCompileValue limitFailure =
checkHardFileSizeLimit(
key, bytes.length, location, semantics, bazelStarlarkEnvironment, env);
if (env.valuesMissing()) {
return null;
}
if (limitFailure != null) {
return limitFailure;
}
}
digest = fileValue.getDigest(); // may be null
inputName = path.toString();
} else {
if (key.kind == BzlCompileValue.Kind.PRELUDE) {
// A non-existent prelude is fine.
bytes = new byte[] {};
digest = null;
inputName = "<default prelude>";
} else {
return BzlCompileValue.noFile("cannot load '%s': no such file", key.label);
}
}
}
// Compute digest if we didn't already get it from a fileValue.
if (digest == null) {
digest = hashFunction.hashBytes(bytes).asBytes();
}
TypeOptions typeOptions = getTypeOptions(semantics, key);
boolean resolveTypeSyntax =
typeOptions.wantStaticTypeChecking() || typeOptions.wantDynamicTypeChecking();
ImmutableMap<String, Object> predeclared;
if (key.isSclDialect()) {
predeclared = bazelStarlarkEnvironment.getStarlarkGlobals().getSclToplevels();
} else if (key.kind == BzlCompileValue.Kind.BUILTINS) {
predeclared =
resolveTypeSyntax
? bazelStarlarkEnvironment.getBuiltinsBzlEnvWithExtraTypeConstructors()
: bazelStarlarkEnvironment.getBuiltinsBzlEnv();
} else {
// Use the predeclared environment for BUILD-loaded bzl files, ignoring injection. It is not
// the right env for the actual evaluation of BUILD-loaded bzl files because it doesn't
// map to the injected symbols. But the names of the symbols are the same, and the names are
// all we need to do symbol resolution.
//
// For WORKSPACE-loaded bzl files, the env isn't quite right not because of injection but
// because the "native" object is different. But A) that will be fixed with #11954, and B) we
// don't care for the same reason as above.
predeclared =
resolveTypeSyntax
? bazelStarlarkEnvironment.getUninjectedBuildBzlEnvWithExtraTypeConstructors()
: bazelStarlarkEnvironment.getUninjectedBuildBzlEnv();
}
// We have all deps. Parse, resolve, and return.
ParserInput input;
try {
input =
StarlarkUtil.createParserInput(
bytes,
inputName,
semantics.get(BuildLanguageOptions.INCOMPATIBLE_ENFORCE_STARLARK_UTF8),
env.getListener());
} catch (
@SuppressWarnings("UnusedException") // createParserInput() reports its own error message
StarlarkUtil.InvalidUtf8Exception e) {
return BzlCompileValue.noFile("compilation of '%s' failed", inputName);
}
FileOptions.Builder optionsBuilder =
FileOptions.builder()
// By default, Starlark load statements create file-local bindings.
// However, the BUILD prelude typically contains nothing but load
// statements whose bindings are intended to be visible in all BUILD
// files. The loadBindsGlobally flag allows us to retrieve them.
.loadBindsGlobally(key.isBuildPrelude())
// .scl files should be ASCII-only in string literals.
// TODO(bazel-team): It'd be nice if we could intercept non-ASCII errors from the lexer,
// and modify the displayed message to clarify to the user that the string would be
// permitted in a .bzl file. But there's no easy way to do that short of either string
// matching the error message or reworking the interpreter API to put more structured
// detail in errors (i.e. new fields or error subclasses).
.stringLiteralsAreAsciiOnly(key.isSclDialect());
updateFileOptions(optionsBuilder, typeOptions);
StarlarkFile file = StarlarkFile.parse(input, optionsBuilder.build());
if (key.kind == BzlCompileValue.Kind.NORMAL) {
BzlCompileValue softLimitFailure =
checkSoftFileSizeLimit(
key,
bytes.length,
file,
Location.fromFile(inputName),
semantics,
bazelStarlarkEnvironment,
env);
if (env.valuesMissing()) {
return null;
}
if (softLimitFailure != null) {
return softLimitFailure;
}
}
// compile
final Module module;
if (key.kind == BzlCompileValue.Kind.EMPTY_PRELUDE) {
// The empty prelude has no label, so we can't use it to filter the predeclareds.
// This doesn't matter since the empty prelude doesn't attempt to access any predeclareds
// anyway.
module = Module.withPredeclared(semantics, predeclared);
} else {
// The BazelCompileContext holds additional contextual info to be associated with the Module
// The information is used to filter predeclareds
BazelCompileContext bazelCompileContext =
BazelCompileContext.create(key.label, file.getName());
module = Module.withPredeclaredAndData(semantics, predeclared, bazelCompileContext);
}
try {
Program prog = Program.compileFile(file, module);
if (key.kind == BzlCompileValue.Kind.NORMAL) {
packageLoadingListener.onBzlCompileCompleteAndSuccessful(rootedPath, bytes.length);
}
return BzlCompileValue.withProgram(prog, digest, typeOptions);
} catch (SyntaxError.Exception ex) {
addSyntaxErrorsToListener(env.getListener(), ex.errors(), key);
return BzlCompileValue.noFile(
"compilation of module '%s'%s failed",
key.label.toPathFragment(),
StarlarkBuiltinsValue.isBuiltinsRepo(key.label.getRepository()) ? " (internal)" : "");
}
}
/**
* Whether the file should permit type syntax (annotations, etc.) based on flags and the type of
* file.
*/
private static TypeOptions getTypeOptions(StarlarkSemantics semantics, BzlCompileValue.Key key) {
boolean typeSyntaxFlag = semantics.getBool(StarlarkSemantics.EXPERIMENTAL_STARLARK_TYPE_SYNTAX);
List<String> allowlist =
semantics.get(BuildLanguageOptions.EXPERIMENTAL_STARLARK_TYPES_ALLOWED_PATHS);
boolean okFiletype =
// annotations in prelude not allowed (it has null key.label)
!key.isBuildPrelude()
// annotations in SCL not allowed (not yet compatible with Go-Starlark interpreter)
&& !key.isSclDialect();
boolean useTypeSyntax = false;
if (okFiletype) {
if (typeSyntaxFlag) {
if (allowlist.isEmpty()
|| allowlist.stream().anyMatch(s -> key.label.getCanonicalForm().startsWith(s))) {
useTypeSyntax = true;
}
}
if (key.isBuiltins()) {
// Always enable type syntax for @_builtins
useTypeSyntax = true;
}
}
boolean doStaticTypeChecking =
useTypeSyntax
&& (semantics.getBool(StarlarkSemantics.EXPERIMENTAL_STARLARK_STATIC_TYPE_CHECKING)
// Always enable static type checking for @_builtins
|| key.isBuiltins());
boolean doDynamicTypeChecking =
useTypeSyntax
&& semantics.getBool(StarlarkSemantics.EXPERIMENTAL_STARLARK_DYNAMIC_TYPE_CHECKING);
return new TypeOptions(useTypeSyntax, doStaticTypeChecking, doDynamicTypeChecking);
}
private static void updateFileOptions(FileOptions.Builder builder, TypeOptions typeOptions) {
boolean needsTypeInfo =
typeOptions.wantStaticTypeChecking() || typeOptions.wantDynamicTypeChecking();
builder
.allowTypeSyntax(typeOptions.useTypeSyntax())
.resolveTypeSyntax(needsTypeInfo)
.tolerateInvalidTypeExpressions(!needsTypeInfo);
}
/**
* Replays the syntax errors from a file onto an event handler, adding more context if necessary.
*/
private static void addSyntaxErrorsToListener(
EventHandler handler, List<SyntaxError> errors, BzlCompileValue.Key key) {
Event.replayEventsOn(handler, errors);
// If type annotations are disallowed, it could either be because the required flags aren't
// enabled or because the filetype disallows it.
for (var err : errors) {
if (err.message().contains(": type annotations are disallowed")) {
Location fileLoc = Location.fromFile(err.location().file());
String explanation =
key.isSclDialect()
? "Type annotations are not permitted in .scl files."
: """
Type annotations syntax can be enabled with --experimental_starlark_type_syntax \
and/or --experimental_starlark_types_allowed_paths.\
""";
handler.handle(Event.error(fileLoc, explanation));
}
}
}
static final class FailedIOException extends Exception {
private final Transience transience;
FailedIOException(IOException cause, Transience transience) {
super(cause.getMessage(), cause);
this.transience = transience;
}
Transience getTransience() {
return transience;
}
}
private static final class FunctionException extends SkyFunctionException {
private FunctionException(FailedIOException cause) {
super(cause, cause.transience);
}
}
private static final String KNOWN_OVERSIZED_BZL_FILE_SYMBOL = "_KNOWN_OVERSIZED_BZL_FILE";
@Nullable
private static Label parseAllowlistLabel(String raw) {
if (raw.isEmpty()) {
return null;
}
return Label.parseCanonicalUnchecked(raw);
}
private record AllowlistCheckResult(boolean allowlisted, @Nullable String errorMessage) {
static final AllowlistCheckResult ALLOWLISTED = new AllowlistCheckResult(true, null);
static final AllowlistCheckResult NOT_ALLOWLISTED = new AllowlistCheckResult(false, null);
static AllowlistCheckResult error(String errorMessage) {
return new AllowlistCheckResult(false, errorMessage);
}
}
private static final Cache<BzlCompileValue, ParsedAllowlist> allowlistCache =
CacheBuilder.newBuilder().weakKeys().build();
private record ParsedAllowlist(
@Nullable ImmutableSet<String> allowedEntries, @Nullable String errorMessage) {
static ParsedAllowlist success(ImmutableSet<String> allowedEntries) {
return new ParsedAllowlist(allowedEntries, null);
}
static ParsedAllowlist error(String errorMessage) {
return new ParsedAllowlist(null, errorMessage);
}
}
private static ParsedAllowlist evaluateAllowlist(
Label allowlistLabel,
BzlCompileValue allowlistCompileValue,
StarlarkSemantics semantics,
BazelStarlarkEnvironment bazelStarlarkEnvironment)
throws InterruptedException {
if (!allowlistCompileValue.lookupSuccessful()) {
return ParsedAllowlist.error(
String.format(
"Failed to compile allowlist file '%s': %s",
allowlistLabel.getCanonicalForm(), allowlistCompileValue.getError()));
}
ImmutableMap<String, Object> predeclared =
bazelStarlarkEnvironment.getStarlarkGlobals().getSclToplevels();
Module module = Module.withPredeclared(semantics, predeclared);
try (Mutability mu = Mutability.create()) {
StarlarkThread thread = StarlarkThread.createTransient(mu, semantics);
Starlark.execFileProgram(allowlistCompileValue.getProgram(), module, thread);
} catch (EvalException e) {
return ParsedAllowlist.error(
String.format(
"Failed to evaluate allowlist file '%s': %s",
allowlistLabel.getCanonicalForm(), e.getMessage()));
}
Object allowedObj = module.getGlobal("ALLOWED");
if (allowedObj == null) {
return ParsedAllowlist.error(
String.format(
"Allowlist file '%s' does not define an 'ALLOWED' symbol.",
allowlistLabel.getCanonicalForm()));
}
if (!(allowedObj instanceof Sequence<?> allowedSeq)) {
return ParsedAllowlist.error(
String.format(
"Allowlist file '%s': 'ALLOWED' must be a list of strings, but got %s.",
allowlistLabel.getCanonicalForm(), Starlark.type(allowedObj)));
}
ImmutableSet.Builder<String> allowedBuilder = ImmutableSet.builder();
for (Object item : allowedSeq) {
if (item instanceof String s) {
allowedBuilder.add(s);
}
}
return ParsedAllowlist.success(allowedBuilder.build());
}
@Nullable
private static BzlCompileValue checkHardFileSizeLimit(
BzlCompileValue.Key key,
long fileSize,
Location location,
StarlarkSemantics semantics,
BazelStarlarkEnvironment bazelStarlarkEnvironment,
Environment env)
throws InterruptedException {
long maxLimit = semantics.get(BuildLanguageOptions.MAX_BZL_FILE_SIZE);
if (maxLimit <= 0 || fileSize <= maxLimit) {
return null;
}
AllowlistCheckResult allowlistResult =
isFileAllowlisted(key, fileSize, maxLimit, semantics, bazelStarlarkEnvironment, env);
if (allowlistResult == null || allowlistResult.allowlisted()) {
return null;
}
String msg =
allowlistResult.errorMessage() != null
? allowlistResult.errorMessage()
: String.format(
"File '%s' size (%d bytes) exceeds the maximum allowed size (%d bytes). Loading"
+ " oversized Starlark files is blocked to protect Blaze from JVM memory"
+ " exhaustion.",
key.label, fileSize, maxLimit);
env.getListener().handle(Event.error(location, msg));
return BzlCompileValue.noFile("%s", msg);
}
@Nullable
private static BzlCompileValue checkSoftFileSizeLimit(
BzlCompileValue.Key key,
long fileSize,
StarlarkFile file,
Location location,
StarlarkSemantics semantics,
BazelStarlarkEnvironment bazelStarlarkEnvironment,
Environment env)
throws InterruptedException {
long softLimit = semantics.get(BuildLanguageOptions.SOFT_MAX_BZL_FILE_SIZE);
if (softLimit <= 0 || fileSize <= softLimit || hasKnownOversizedOptOut(file)) {
return null;
}
AllowlistCheckResult allowlistResult =
isFileAllowlisted(key, fileSize, softLimit, semantics, bazelStarlarkEnvironment, env);
if (allowlistResult == null || allowlistResult.allowlisted()) {
return null;
}
String msg =
allowlistResult.errorMessage() != null
? allowlistResult.errorMessage()
: String.format(
"File '%s' size (%d bytes) exceeds the soft maximum size (%d bytes). Split this"
+ " file or move data out of Starlark. To opt out up to the hard limit, set"
+ " `%s = \"%s\"` at the top level of the file.",
key.label,
fileSize,
softLimit,
KNOWN_OVERSIZED_BZL_FILE_SYMBOL,
BuildLanguageOptions.KNOWN_OVERSIZED_BZL_FILE_VALUE_EXAMPLE);
env.getListener().handle(Event.error(location, msg));
return BzlCompileValue.noFile("%s", msg);
}
static boolean registerAllowlistDepIfOversized(
BzlCompileValue.Key key, SkyValue bzlFileSkyValue, Environment env)
throws InterruptedException {
if (!(bzlFileSkyValue instanceof FileValue bzlFileValue)
|| key.kind != BzlCompileValue.Kind.NORMAL
|| !bzlFileValue.exists()
|| !bzlFileValue.isFile()) {
return true;
}
StarlarkSemantics semantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
if (semantics == null) {
return false;
}
Label allowlistLabel =
parseAllowlistLabel(semantics.get(BuildLanguageOptions.BZL_FILE_SIZE_LIMIT_ALLOWLIST));
if (allowlistLabel == null || key.label == null || key.label.equals(allowlistLabel)) {
return true;
}
long maxLimit = semantics.get(BuildLanguageOptions.MAX_BZL_FILE_SIZE);
long softLimit = semantics.get(BuildLanguageOptions.SOFT_MAX_BZL_FILE_SIZE);
boolean exceedsLimit =
bzlFileValue.isSpecialFile()
|| (maxLimit > 0 && bzlFileValue.getSize() > maxLimit)
|| (softLimit > 0 && bzlFileValue.getSize() > softLimit);
if (!exceedsLimit) {
return true;
}
PathFragment dir = Label.getContainingDirectory(allowlistLabel);
PackageIdentifier dirId = PackageIdentifier.create(allowlistLabel.getRepository(), dir);
ContainingPackageLookupValue packageLookup;
try {
packageLookup =
(ContainingPackageLookupValue)
env.getValueOrThrow(
ContainingPackageLookupValue.key(dirId),
BuildFileNotFoundException.class,
InconsistentFilesystemException.class);
} catch (BuildFileNotFoundException | InconsistentFilesystemException e) {
return true;
}
if (packageLookup == null || !packageLookup.hasContainingPackage()) {
return !env.valuesMissing();
}
Root root = packageLookup.getContainingPackageRoot();
SkyKey allowlistFileKey =
FileValue.key(RootedPath.toRootedPath(root, allowlistLabel.toPathFragment()));
try {
var unused = env.getValueOrThrow(allowlistFileKey, IOException.class);
} catch (IOException e) {
return true;
}
if (env.valuesMissing()) {
return false;
}
BzlCompileValue.Key allowlistCompileKey = BzlCompileValue.key(root, allowlistLabel);
try {
var unused = env.getValueOrThrow(allowlistCompileKey, FailedIOException.class);
} catch (FailedIOException e) {
return true;
}
return !env.valuesMissing();
}
private static boolean hasKnownOversizedOptOut(StarlarkFile file) {
for (Statement stmt : file.getStatements()) {
if (stmt instanceof AssignmentStatement assignment
&& assignment.getOperator() == null
&& assignment.getLHS() instanceof Identifier id
&& id.getName().equals(KNOWN_OVERSIZED_BZL_FILE_SYMBOL)
&& assignment.getRHS() instanceof StringLiteral str
&& BuildLanguageOptions.KNOWN_OVERSIZED_BZL_FILE_VALUE_PATTERN
.matcher(str.getValue())
.matches()) {
return true;
}
}
return false;
}
@Nullable
private static AllowlistCheckResult isFileAllowlisted(
BzlCompileValue.Key key,
long fileSize,
long limit,
StarlarkSemantics semantics,
BazelStarlarkEnvironment bazelStarlarkEnvironment,
Environment env)
throws InterruptedException {
Label allowlistLabel =
parseAllowlistLabel(semantics.get(BuildLanguageOptions.BZL_FILE_SIZE_LIMIT_ALLOWLIST));
if (allowlistLabel == null || key.label == null) {
return AllowlistCheckResult.NOT_ALLOWLISTED;
}
if (allowlistLabel.equals(key.label)) {
return AllowlistCheckResult.error(
String.format(
"Allowlist file '%s' size (%d bytes) exceeds the maximum allowed size (%d bytes)."
+ " The allowlist file itself cannot be oversized to avoid dependency cycles.",
key.label, fileSize, limit));
}
PathFragment dir = Label.getContainingDirectory(allowlistLabel);
PackageIdentifier dirId = PackageIdentifier.create(allowlistLabel.getRepository(), dir);
ContainingPackageLookupValue packageLookup;
try {
packageLookup =
(ContainingPackageLookupValue)
env.getValueOrThrow(
ContainingPackageLookupValue.key(dirId),
BuildFileNotFoundException.class,
InconsistentFilesystemException.class);
} catch (BuildFileNotFoundException | InconsistentFilesystemException e) {
return AllowlistCheckResult.error(
String.format(
"Failed to find containing package for allowlist '%s': %s",
allowlistLabel.getCanonicalForm(), e.getMessage()));
}
if (packageLookup == null) {
return null;
}
if (!packageLookup.hasContainingPackage()) {
return AllowlistCheckResult.error(
String.format(
"Failed to load allowlist file '%s': no containing package",
allowlistLabel.getCanonicalForm()));
}
Root root = packageLookup.getContainingPackageRoot();
SkyKey allowlistFileKey =
FileValue.key(RootedPath.toRootedPath(root, allowlistLabel.toPathFragment()));
FileValue allowlistFileValue;
try {
allowlistFileValue = (FileValue) env.getValueOrThrow(allowlistFileKey, IOException.class);
} catch (IOException e) {
return AllowlistCheckResult.error(
String.format(
"Failed to read allowlist file '%s': %s",
allowlistLabel.getCanonicalForm(), e.getMessage()));
}
if (allowlistFileValue == null) {
return null;
}
if (!allowlistFileValue.exists()) {
return AllowlistCheckResult.error(
String.format("Allowlist file '%s' does not exist.", allowlistLabel.getCanonicalForm()));
}
BzlCompileValue.Key allowlistCompileKey = BzlCompileValue.key(root, allowlistLabel);
BzlCompileValue allowlistCompileValue;
try {
allowlistCompileValue =
(BzlCompileValue) env.getValueOrThrow(allowlistCompileKey, FailedIOException.class);
} catch (FailedIOException e) {
return AllowlistCheckResult.error(
String.format(
"Failed to compile allowlist file '%s': %s",
allowlistLabel.getCanonicalForm(), e.getMessage()));
}
if (allowlistCompileValue == null) {
return null;
}
ParsedAllowlist parsedAllowlist;
try {
parsedAllowlist =
allowlistCache.get(
allowlistCompileValue,
() ->
evaluateAllowlist(
allowlistLabel, allowlistCompileValue, semantics, bazelStarlarkEnvironment));
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof InterruptedException interruptedException) {
throw interruptedException;
}
return AllowlistCheckResult.error(
String.format(
"Failed to evaluate allowlist file '%s': %s",
allowlistLabel.getCanonicalForm(),
cause != null ? cause.getMessage() : e.getMessage()));
}
if (parsedAllowlist.errorMessage() != null) {
return AllowlistCheckResult.error(parsedAllowlist.errorMessage());
}
ImmutableSet<String> allowed = parsedAllowlist.allowedEntries();
String pathString = key.label.toPathFragment().getPathString();
String canonicalLabel = key.label.getCanonicalForm();
String repoRelativeLabel = "//" + key.label.getPackageName() + ":" + key.label.getName();
if (allowed != null
&& (allowed.contains(pathString)
|| allowed.contains(canonicalLabel)
|| allowed.contains(repoRelativeLabel))) {
return AllowlistCheckResult.ALLOWLISTED;
}
return AllowlistCheckResult.NOT_ALLOWLISTED;
}
}