Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.CheckDirectDepsMode;
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.LockfileMode;
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.RepositoryOverride;
import com.google.devtools.build.lib.bazel.repository.RepositoryUtils;
import com.google.devtools.build.lib.bazel.repository.cache.RepositoryCache;
import com.google.devtools.build.lib.bazel.repository.downloader.DownloadManager;
import com.google.devtools.build.lib.bazel.repository.downloader.UrlRewriter;
Expand Down Expand Up @@ -569,7 +568,7 @@ public void beforeCommand(CommandEnvironment env) throws AbruptExitException {
|| externalRoot.getFileSystem() instanceof RemoteExternalOverlayFileSystem) {
try {
FileSystemUtils.ensureSymbolicLink(
externalRoot.getChild(RepositoryUtils.WORKSPACE_SYMLINK_NAME), env.getWorkspace());
externalRoot.getChild(LabelConstants.WORKSPACE_SYMLINK_NAME), env.getWorkspace());
} catch (IOException e) {
env.getReporter().handle(Event.error(e.getMessage()));
env.getBlazeModuleEnvironment()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
/** Utility methods related to repo fetching. */
public class RepositoryUtils {

public static final String WORKSPACE_SYMLINK_NAME = "_main";

private RepositoryUtils() {}

public static boolean isValidRepoRoot(Path directory) {
Expand Down Expand Up @@ -102,7 +100,8 @@ public static boolean replantSymlinks(
boolean portableSymlinksOnly = true;
try {
Collection<Path> symlinks = FileSystemUtils.traverseTree(repoDir, Path::isSymbolicLink);
Path workspaceSymlinkUnderExternal = externalRepoRoot.getChild(WORKSPACE_SYMLINK_NAME);
Path workspaceSymlinkUnderExternal =
externalRepoRoot.getChild(LabelConstants.WORKSPACE_SYMLINK_NAME);
FileSystemUtils.ensureSymbolicLink(workspaceSymlinkUnderExternal, workspace);
for (Path symlink : symlinks) {
PathFragment target = symlink.readSymbolicLink();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/concurrent:thread_safety",
"//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception",
"//src/main/java/com/google/devtools/build/lib/util:os",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//third_party:flogger",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ private void prepare(PackageRoots packageRoots) throws AbruptExitException, Inte
new SymlinkForest(
packageRoots.getPackageRootsMap(),
getExecRoot(),
env.getWorkspace(),
runtime.getProductName(),
request
.getOptions(BuildLanguageOptions.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.OS;
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.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -51,14 +53,28 @@ public class SymlinkForest {

private final ImmutableMap<PackageIdentifier, Root> packageRoots;
private final Path execroot;
@Nullable private final Path workspace;
private final String productName;
private final String prefix;
private final boolean siblingRepositoryLayout;

/** Constructor for a symlink forest creator without non-symlinked directories parameter. */
public SymlinkForest(
ImmutableMap<PackageIdentifier, Root> packageRoots, Path execroot, String productName) {
this(packageRoots, execroot, productName, false);
this(packageRoots, execroot, /* workspace= */ null, productName, false);
}

/**
* Convenience constructor that omits the workspace path. Intended for tests that do not exercise
* the planting of the {@code _main} symlink under the execroot's external directory.
*/
@VisibleForTesting
public SymlinkForest(
ImmutableMap<PackageIdentifier, Root> packageRoots,
Path execroot,
String productName,
boolean siblingRepositoryLayout) {
this(packageRoots, execroot, /* workspace= */ null, productName, siblingRepositoryLayout);
}

/**
Expand All @@ -68,15 +84,20 @@ public SymlinkForest(
*
* @param packageRoots source package roots to which to create symlinks
* @param execroot path where to plant the symlink forest
* @param workspace path to the main workspace, used to materialize a {@code _main} symlink
* alongside other external repository symlinks under the execroot. May be {@code null} in
* test settings where this symlink is not needed.
* @param productName {@code BlazeRuntime#getProductName()}
*/
public SymlinkForest(
ImmutableMap<PackageIdentifier, Root> packageRoots,
Path execroot,
@Nullable Path workspace,
String productName,
boolean siblingRepositoryLayout) {
this.packageRoots = packageRoots;
this.execroot = execroot;
this.workspace = workspace;
this.productName = productName;
this.prefix = productName + "-";
this.siblingRepositoryLayout = siblingRepositoryLayout;
Expand Down Expand Up @@ -127,7 +148,7 @@ private void plantSymlinkForExternalRepo(
throws IOException {
Optional<Path> plantedSymlink =
plantSingleSymlinkForExternalRepo(
repository, source, execroot, siblingRepositoryLayout, externalRepoLinks);
repository, source, execroot, workspace, siblingRepositoryLayout, externalRepoLinks);
plantedSymlink.ifPresent(plantedSymlinks::add);
}

Expand Down Expand Up @@ -452,6 +473,7 @@ public static Optional<Path> plantSingleSymlinkForExternalRepo(
RepositoryName repository,
Path source,
Path execroot,
@Nullable Path workspace,
boolean siblingRepositoryLayout,
Set<Path> alreadyPlantedExternalRepoLinks)
throws IOException {
Expand All @@ -465,8 +487,28 @@ public static Optional<Path> plantSingleSymlinkForExternalRepo(
// to <output_base>/external/<external repo name>
Path execrootLink = execroot.getRelative(repository.getExecPath(siblingRepositoryLayout));

if (!siblingRepositoryLayout && alreadyPlantedExternalRepoLinks.isEmpty()) {
execroot.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX).createDirectoryAndParents();
if (!siblingRepositoryLayout) {
Path externalDir = execroot.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX);
// Always ensure the external directory exists before any repo symlink is planted into it.
// createDirectoryAndParents is idempotent and thus safe under concurrent invocations (as
// in IncrementalPackageRoots).
externalDir.createDirectoryAndParents();
// Materialize <execroot>/external/_main as a symlink to the workspace, mirroring the
// <output_base>/external/_main symlink created during repo fetching (see
// RepositoryUtils.replantSymlinks). This is needed for external repositories whose
// cross-repo symlinks were replanted as relative paths through ../_main/...: on Windows,
// where symlinks resolve using logical paths, such relative targets dangle when the repo
// is accessed via <execroot>/external/<repo>/... unless _main also exists alongside the
// repo there. See https://github.com/bazelbuild/bazel/issues/29515.
if (workspace != null) {
Path workspaceLink = externalDir.getChild(LabelConstants.WORKSPACE_SYMLINK_NAME);
// Gate via the shared set so the symlink is created at most once even under concurrent
// invocations. No external repo can be named "_main" so this entry never collides with
// a real repo's execroot link.
if (alreadyPlantedExternalRepoLinks.add(workspaceLink)) {
createWorkspaceLinkUnderExecroot(workspaceLink, workspace);
}
}
}
// Prevent re-creating existing symlinks.
if (!alreadyPlantedExternalRepoLinks.add(execrootLink)) {
Expand All @@ -476,6 +518,38 @@ public static Optional<Path> plantSingleSymlinkForExternalRepo(
return Optional.of(execrootLink);
}

/**
* Creates the {@code _main} workspace symlink under the execroot's external directory.
*
* <p>On Windows we go through {@link Files#createSymbolicLink} to produce a real symlink rather
* than letting Bazel's {@link Path#createSymbolicLink} fall back to a junction. A junction looks
* like a directory to tools that don't special-case junctions (e.g. Python's {@code shutil},
* most workspace-walkers), so it would introduce a cycle through the {@code bazel-<workspace>}
* convenience symlink and break those tools. If real symlinks aren't available (no Developer
* Mode / symlink privilege), skip silently — that's the same condition under which {@link
* com.google.devtools.build.lib.bazel.repository.RepositoryUtils#replantSymlinks} doesn't
* produce the relative {@code ../_main/...} targets that motivate this symlink in the first
* place.
*/
private static void createWorkspaceLinkUnderExecroot(Path workspaceLink, Path workspace)
throws IOException {
if (OS.getCurrent() != OS.WINDOWS) {
workspaceLink.createSymbolicLink(workspace);
return;
}
var workspaceLinkNioPath =
workspaceLink.getFileSystem().getNioPath(workspaceLink.asFragment());
var workspaceNioPath = workspaceLink.getFileSystem().getNioPath(workspace.asFragment());
if (workspaceLinkNioPath == null || workspaceNioPath == null) {
return;
}
try {
Files.createSymbolicLink(workspaceLinkNioPath, workspaceNioPath);
} catch (IOException | UnsupportedOperationException e) {
// Real symlinks aren't available; see method javadoc.
}
}

private static PackageIdentifier createInRepo(
PackageIdentifier repo, PathFragment packageFragment) {
return PackageIdentifier.create(repo.getRepository(), packageFragment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ public class LabelConstants {
// With this prefix, non-main repositories are symlinked under
// $output_base/execution_root/__main__/external
public static final PathFragment EXTERNAL_PATH_PREFIX = PathFragment.create("external");

/**
* The name of the symlink to the main workspace planted under {@code <output_base>/external/}
* (and, for executions that may resolve symlinks logically, under {@code <execroot>/external/}).
* It allows repo-rule symlinks pointing into the main workspace to be replanted as relative
* paths through {@code ../_main/...}.
*/
public static final String WORKSPACE_SYMLINK_NAME = "_main";
// With this prefix, non-main repositories are sibling symlinks of
// $output_base/execution_root/__main__
public static final PathFragment EXPERIMENTAL_EXTERNAL_PATH_PREFIX = PathFragment.create("..");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ private Void plantSingleSymlinkForPackage(
pkgId.getRepository(),
pkg.sourceRoot().asPath(),
execroot,
singleSourceRoot.asPath(),
useSiblingRepositoryLayout,
lazilyPlantedSymlinksRef);
} else if (!maybeConflictingBaseNamesLowercase.isEmpty()) {
Expand Down
Loading