Skip to content

Commit 0ff6b88

Browse files
tjgqcopybara-github
authored andcommitted
Support remote symlink outputs when building without the bytes.
Fixes #13355. PiperOrigin-RevId: 534008963 Change-Id: Ia4f22f16960bcdae86c1a8820e3d47a3689876d8
1 parent 997be1e commit 0ff6b88

File tree

2 files changed

+73
-83
lines changed

2 files changed

+73
-83
lines changed

src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java

Lines changed: 34 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -866,12 +866,6 @@ private void injectRemoteArtifacts(
866866

867867
for (Map.Entry<Path, DirectoryMetadata> entry : metadata.directories()) {
868868
DirectoryMetadata directory = entry.getValue();
869-
if (!directory.symlinks().isEmpty()) {
870-
throw new IOException(
871-
"Symlinks in action outputs are not yet supported by "
872-
+ "--experimental_remote_download_outputs=minimal");
873-
}
874-
875869
for (FileMetadata file : directory.files()) {
876870
remoteActionFileSystem.injectRemoteFile(
877871
file.path().asFragment(),
@@ -1162,7 +1156,8 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
11621156

11631157
ImmutableList.Builder<ListenableFuture<FileMetadata>> downloadsBuilder =
11641158
ImmutableList.builder();
1165-
boolean downloadOutputs = shouldDownloadOutputsFor(action, result, metadata);
1159+
1160+
boolean downloadOutputs = shouldDownloadOutputsFor(action, result);
11661161

11671162
// Download into temporary paths, then move everything at the end.
11681163
// This avoids holding the output lock while downloading, which would prevent the local branch
@@ -1182,10 +1177,6 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
11821177
checkState(
11831178
result.getExitCode() == 0,
11841179
"injecting remote metadata is only supported for successful actions (exit code 0).");
1185-
checkState(
1186-
metadata.symlinks.isEmpty(),
1187-
"Symlinks in action outputs are not yet supported by"
1188-
+ " --remote_download_outputs=minimal");
11891180
}
11901181

11911182
FileOutErr tmpOutErr = outErr.childOutErr();
@@ -1219,38 +1210,6 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
12191210

12201211
if (downloadOutputs) {
12211212
moveOutputsToFinalLocation(downloads, realToTmpPath);
1222-
1223-
List<SymlinkMetadata> symlinksInDirectories = new ArrayList<>();
1224-
for (Entry<Path, DirectoryMetadata> entry : metadata.directories()) {
1225-
for (SymlinkMetadata symlink : entry.getValue().symlinks()) {
1226-
// Symlinks should not be allowed inside directories because their semantics are unclear:
1227-
// tree artifacts are defined as a collection of regular files, and resolving a remotely
1228-
// produced symlink against the local filesystem is asking for trouble.
1229-
//
1230-
// Sadly, we started permitting relative symlinks at some point, so we have to allow them
1231-
// until the --incompatible_remote_disallow_symlink_in_tree_artifact flag is flipped.
1232-
// Absolute symlinks, on the other hand, have never been allowed.
1233-
//
1234-
// See also https://github.com/bazelbuild/bazel/issues/16361 for potential future work
1235-
// to allow *unresolved* symlinks in a tree artifact.
1236-
boolean isAbsolute = symlink.target().isAbsolute();
1237-
if (remoteOptions.incompatibleRemoteDisallowSymlinkInTreeArtifact || isAbsolute) {
1238-
throw new IOException(
1239-
String.format(
1240-
"Unsupported symlink '%s' inside tree artifact '%s'",
1241-
symlink.path().relativeTo(entry.getKey()),
1242-
entry.getKey().relativeTo(execRoot)));
1243-
}
1244-
symlinksInDirectories.add(symlink);
1245-
}
1246-
}
1247-
1248-
Iterable<SymlinkMetadata> symlinks =
1249-
Iterables.concat(metadata.symlinks(), symlinksInDirectories);
1250-
1251-
// Create the symbolic links after all downloads are finished, because dangling symlinks
1252-
// might not be supported on all platforms.
1253-
createSymlinks(symlinks);
12541213
} else {
12551214
ActionInput inMemoryOutput = null;
12561215
Digest inMemoryOutputDigest = null;
@@ -1285,6 +1244,37 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
12851244
}
12861245
}
12871246

1247+
List<SymlinkMetadata> symlinksInDirectories = new ArrayList<>();
1248+
for (Entry<Path, DirectoryMetadata> entry : metadata.directories()) {
1249+
for (SymlinkMetadata symlink : entry.getValue().symlinks()) {
1250+
// Symlinks should not be allowed inside directories because their semantics are unclear:
1251+
// tree artifacts are defined as a collection of regular files, and resolving a remotely
1252+
// produced symlink against the local filesystem is asking for trouble.
1253+
//
1254+
// Sadly, we started permitting relative symlinks at some point, so we have to allow them
1255+
// until the --incompatible_remote_disallow_symlink_in_tree_artifact flag is flipped.
1256+
// Absolute symlinks, on the other hand, have never been allowed.
1257+
//
1258+
// See also https://github.com/bazelbuild/bazel/issues/16361 for potential future work
1259+
// to allow *unresolved* symlinks in a tree artifact.
1260+
boolean isAbsolute = symlink.target().isAbsolute();
1261+
if (remoteOptions.incompatibleRemoteDisallowSymlinkInTreeArtifact || isAbsolute) {
1262+
throw new IOException(
1263+
String.format(
1264+
"Unsupported symlink '%s' inside tree artifact '%s'",
1265+
symlink.path().relativeTo(entry.getKey()), entry.getKey().relativeTo(execRoot)));
1266+
}
1267+
symlinksInDirectories.add(symlink);
1268+
}
1269+
}
1270+
1271+
Iterable<SymlinkMetadata> symlinks =
1272+
Iterables.concat(metadata.symlinks(), symlinksInDirectories);
1273+
1274+
// Create the symbolic links after all downloads are finished, because dangling symlinks
1275+
// might not be supported on all platforms.
1276+
createSymlinks(symlinks);
1277+
12881278
if (result.success()) {
12891279
// Check that all mandatory outputs are created.
12901280
for (ActionInput output : action.getSpawn().getOutputFiles()) {
@@ -1325,8 +1315,7 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
13251315
return null;
13261316
}
13271317

1328-
private boolean shouldDownloadOutputsFor(
1329-
RemoteAction action, RemoteActionResult result, ActionResultMetadata metadata) {
1318+
private boolean shouldDownloadOutputsFor(RemoteAction action, RemoteActionResult result) {
13301319
if (remoteOptions.remoteOutputsMode.downloadAllOutputs()) {
13311320
return true;
13321321
}
@@ -1335,16 +1324,6 @@ private boolean shouldDownloadOutputsFor(
13351324
if (result.getExitCode() != 0) {
13361325
return true;
13371326
}
1338-
// Symlinks in actions output are not yet supported with BwoB.
1339-
if (!metadata.symlinks().isEmpty()) {
1340-
report(
1341-
Event.warn(
1342-
String.format(
1343-
"Symlinks in action outputs are not yet supported by --remote_download_minimal,"
1344-
+ " falling back to downloading all action outputs due to output symlink %s",
1345-
Iterables.get(metadata.symlinks(), 0).path())));
1346-
return true;
1347-
}
13481327

13491328
checkNotNull(remoteOutputChecker, "remoteOutputChecker must not be null");
13501329
for (var output : action.getSpawn().getOutputFiles()) {

src/test/shell/bazel/remote/build_without_the_bytes_test.sh

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ EOF
370370
# Test that --remote_download_toplevel fetches inputs to symlink actions. In
371371
# particular, cc_binary links against a symlinked imported .so file, and only
372372
# the symlink is in the runfiles.
373-
function test_downloads_toplevel_symlinks() {
373+
function test_downloads_toplevel_symlink_action() {
374374
if [[ "$PLATFORM" == "darwin" ]]; then
375375
# TODO(b/37355380): This test is disabled due to RemoteWorker not supporting
376376
# setting SDKROOT and DEVELOPER_DIR appropriately, as is required of
@@ -419,49 +419,60 @@ EOF
419419
./bazel-bin/a/foo${EXE_EXT} || fail "bazel-bin/a/foo${EXE_EXT} failed to run"
420420
}
421421

422-
function test_symlink_outputs_warning_with_minimal() {
423-
mkdir -p a
424-
touch a/file1.txt a/file2.txt
425-
cat > a/defs.bzl <<'EOF'
426-
def _impl(ctx):
427-
commands = []
428-
outputs = []
429-
for target, name in ctx.attr.symlink_map.items():
430-
sym = ctx.actions.declare_symlink(name)
431-
file = target.files.to_list()[0]
432-
outputs.append(sym)
433-
commands.append("ln -s {} {}".format(file.path, sym.path))
422+
function setup_symlink_output() {
423+
mkdir -p pkg
434424

425+
cat > pkg/defs.bzl <<EOF
426+
def _impl(ctx):
427+
sym = ctx.actions.declare_symlink(ctx.label.name)
435428
ctx.actions.run_shell(
436-
outputs = outputs,
437-
command = " && ".join(commands),
429+
outputs = [sym],
430+
command = "ln -s {} {}".format(ctx.attr.target, sym.path),
438431
)
432+
return DefaultInfo(files = depset([sym]))
439433
440-
return DefaultInfo(files = depset(outputs))
441-
442-
symlinks = rule(
434+
symlink = rule(
443435
implementation = _impl,
444436
attrs = {
445-
"symlink_map": attr.label_keyed_string_dict(allow_files = True),
437+
"target": attr.string(),
446438
},
447439
)
448440
EOF
449-
cat > a/BUILD <<'EOF'
450-
load(":defs.bzl", "symlinks")
451-
symlinks(
441+
442+
cat > pkg/BUILD <<EOF
443+
load(":defs.bzl", "symlink")
444+
symlink(
452445
name = "sym",
453-
symlink_map = {
454-
"file1.txt": "sym1",
455-
"file2.txt": "sym2",
456-
},
446+
target = "target.txt",
457447
)
458448
EOF
449+
}
450+
451+
function test_downloads_toplevel_non_dangling_symlink_output() {
452+
setup_symlink_output
453+
touch pkg/target.txt
454+
455+
bazel build \
456+
--remote_executor=grpc://localhost:${worker_port} \
457+
--remote_download_toplevel \
458+
//pkg:sym >& $TEST_log || fail "Expected build of //pkg:sym to succeed"
459+
460+
if [[ "$(readlink bazel-bin/pkg/sym)" != "target.txt" ]]; then
461+
fail "Expected bazel-bin/pkg/sym to be a symlink pointing to target.txt"
462+
fi
463+
}
464+
465+
function test_downloads_toplevel_dangling_symlink_output() {
466+
setup_symlink_output
459467

460468
bazel build \
461469
--remote_executor=grpc://localhost:${worker_port} \
462470
--remote_download_minimal \
463-
//a:sym >& $TEST_log || fail "Expected build of //a:sym to succeed"
464-
expect_log "Symlinks in action outputs are not yet supported"
471+
//pkg:sym >& $TEST_log || fail "Expected build of //pkg:sym to succeed"
472+
473+
if [[ "$(readlink bazel-bin/pkg/sym)" != "target.txt" ]]; then
474+
fail "Expected bazel-bin/pkg/sym to be a symlink pointing to target.txt"
475+
fi
465476
}
466477

467478
# Regression test that --remote_download_toplevel does not crash when the

0 commit comments

Comments
 (0)