blob: 4d77052f11c935b4ef0c5f59fec79284ca0f0ed8 [file] [log] [blame]
// Copyright 2023 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.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment;
import com.google.devtools.build.lib.packages.LabelConverter;
import com.google.devtools.build.lib.packages.PackageArgs;
import com.google.devtools.build.lib.packages.RepoThreadContext;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
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 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.Starlark;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.syntax.ParserInput;
import net.starlark.java.syntax.Program;
import net.starlark.java.syntax.StarlarkFile;
import net.starlark.java.syntax.SyntaxError;
/** The function to evaluate the REPO.bazel file at the root of a repo. */
public class RepoFileFunction implements SkyFunction {
private final BazelStarlarkEnvironment starlarkEnv;
private final Path workspaceRoot;
public RepoFileFunction(BazelStarlarkEnvironment starlarkEnv, Path workspaceRoot) {
this.starlarkEnv = starlarkEnv;
this.workspaceRoot = workspaceRoot;
}
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws SkyFunctionException, InterruptedException {
RepositoryName repoName = (RepositoryName) skyKey.argument();
// First we need to find the REPO.bazel file. How we do this depends on whether this is for the
// main repo or an external repo.
Path repoRoot;
if (repoName.isMain()) {
repoRoot = workspaceRoot;
} else {
RepositoryDirectoryValue repoDirValue =
(RepositoryDirectoryValue) env.getValue(RepositoryDirectoryValue.key(repoName));
if (repoDirValue == null) {
return null;
}
repoRoot = repoDirValue.getPath();
}
RootedPath repoFilePath =
RootedPath.toRootedPath(Root.fromPath(repoRoot), LabelConstants.REPO_FILE_NAME);
FileValue repoFileValue = (FileValue) env.getValue(FileValue.key(repoFilePath));
if (repoFileValue == null) {
return null;
}
if (!repoFileValue.exists()) {
// It's okay to not have a REPO.bazel file.
return RepoFileValue.of(PackageArgs.EMPTY);
}
// Now we can actually evaluate the file.
StarlarkSemantics starlarkSemantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
RepositoryMappingValue repoMapping =
(RepositoryMappingValue) env.getValue(RepositoryMappingValue.key(repoName));
RepositoryMappingValue mainRepoMapping =
(RepositoryMappingValue) env.getValue(RepositoryMappingValue.key(RepositoryName.MAIN));
if (env.valuesMissing()) {
return null;
}
StarlarkFile repoFile = readAndParseRepoFile(repoFilePath.asPath(), env);
PackageArgs packageArgs =
evalRepoFile(
repoFile,
repoName,
getDisplayNameForRepo(repoName, mainRepoMapping.getRepositoryMapping()),
repoMapping.getRepositoryMapping(),
starlarkSemantics,
env.getListener());
return RepoFileValue.of(packageArgs);
}
private static StarlarkFile readAndParseRepoFile(Path path, Environment env)
throws RepoFileFunctionException {
byte[] contents;
try {
contents = FileSystemUtils.readWithKnownFileSize(path, path.getFileSize());
} catch (IOException e) {
throw new RepoFileFunctionException(
new IOException("error reading REPO.bazel file at " + path, e), Transience.TRANSIENT);
}
StarlarkFile starlarkFile =
StarlarkFile.parse(ParserInput.fromUTF8(contents, path.getPathString()));
if (!starlarkFile.ok()) {
Event.replayEventsOn(env.getListener(), starlarkFile.errors());
throw new RepoFileFunctionException(
new BadRepoFileException("error parsing REPO.bazel file at " + path));
}
return starlarkFile;
}
private static String getDisplayNameForRepo(
RepositoryName repoName, RepositoryMapping mainRepoMapping) {
String displayName = repoName.getDisplayForm(mainRepoMapping);
if (displayName.isEmpty()) {
return "the main repo";
}
return displayName;
}
private PackageArgs evalRepoFile(
StarlarkFile starlarkFile,
RepositoryName repoName,
String repoDisplayName,
RepositoryMapping repoMapping,
StarlarkSemantics starlarkSemantics,
ExtendedEventHandler handler)
throws RepoFileFunctionException, InterruptedException {
try (Mutability mu = Mutability.create("repo file", repoName)) {
Module predeclared =
Module.withPredeclared(
starlarkSemantics, starlarkEnv.getStarlarkGlobals().getRepoToplevels());
Program program = Program.compileFile(starlarkFile, predeclared);
// TODO(wyv): check that `program` has no `def`, `if`, etc
StarlarkThread thread = new StarlarkThread(mu, starlarkSemantics);
thread.setPrintHandler(Event.makeDebugPrintHandler(handler));
RepoThreadContext context =
new RepoThreadContext(
new LabelConverter(
PackageIdentifier.create(repoName, PathFragment.EMPTY_FRAGMENT), repoMapping));
context.storeInThread(thread);
Starlark.execFileProgram(program, predeclared, thread);
return context.getPackageArgs();
} catch (SyntaxError.Exception e) {
Event.replayEventsOn(handler, e.errors());
throw new RepoFileFunctionException(
new BadRepoFileException("error parsing REPO.bazel file for " + repoDisplayName, e));
} catch (EvalException e) {
handler.handle(Event.error(e.getMessageWithStack()));
throw new RepoFileFunctionException(
new BadRepoFileException("error evaluating REPO.bazel file for " + repoDisplayName, e));
}
}
/** Thrown when something is wrong with the contents of the REPO.bazel file of a certain repo. */
public static class BadRepoFileException extends Exception {
public BadRepoFileException(String message) {
super(message);
}
public BadRepoFileException(String message, Exception cause) {
super(message, cause);
}
}
static class RepoFileFunctionException extends SkyFunctionException {
private RepoFileFunctionException(IOException e, Transience transience) {
super(e, transience);
}
private RepoFileFunctionException(BadRepoFileException e) {
super(e, Transience.PERSISTENT);
}
}
}