Skip to content
Open
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
30 changes: 14 additions & 16 deletions cargo/private/cargo_build_script_runner/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,12 @@ fn run_buildrs() -> Result<(), String> {
.ok_or_else(|| "Failed while getting file name".to_string())?;
let link = manifest_dir.join(file_name);

symlink_if_not_exists(&path, &link)
.map_err(|err| format!("Failed to symlink {path:?} to {link:?}: {err}"))?;

exec_root_links.push(link)
// An entry that already exists belongs to CARGO_MANIFEST_DIR and must not be cleaned.
if symlink_if_not_exists(&path, &link)
.map_err(|err| format!("Failed to symlink {path:?} to {link:?}: {err}"))?
{
exec_root_links.push(link)
}
}
}

Expand Down Expand Up @@ -383,10 +385,14 @@ fn set_script_runfiles_env(script_path: &Path, command: &mut Command) {
}

/// Create a symlink from `link` to `original` if `link` doesn't already exist.
fn symlink_if_not_exists(original: &Path, link: &Path) -> Result<(), String> {
symlink(original, link)
.or_else(swallow_already_exists)
.map_err(|err| format!("Failed to create symlink: {err}"))
///
/// Returns whether the symlink was created.
fn symlink_if_not_exists(original: &Path, link: &Path) -> Result<bool, String> {
match symlink(original, link) {
Ok(()) => Ok(true),
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => Ok(false),
Err(err) => Err(format!("Failed to create symlink: {err}")),
}
}

fn resolve_rundir(rundir: &str, exec_root: &Path, manifest_dir: &Path) -> Result<PathBuf, String> {
Expand All @@ -406,14 +412,6 @@ fn resolve_rundir(rundir: &str, exec_root: &Path, manifest_dir: &Path) -> Result
Ok(exec_root.join(rundir_path))
}

fn swallow_already_exists(err: std::io::Error) -> std::io::Result<()> {
if err.kind() == std::io::ErrorKind::AlreadyExists {
Ok(())
} else {
Err(err)
}
}

/// A representation of expected command line arguments.
struct Args {
progname: String,
Expand Down
50 changes: 46 additions & 4 deletions cargo/tests/cargo_build_script/symlink_exec_root/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,63 @@ cargo_build_script(

# This is an empty test file, it is only needed to trigger the build script.
write_file(
name = "test_exec_root_access_rs",
out = "test_exec_root_access.rs",
name = "empty",
out = "empty.rs",
content = [""],
)

rust_test(
name = "test_exec_root_access_feature_enabled",
srcs = ["test_exec_root_access.rs"],
srcs = ["empty.rs"],
edition = "2021",
deps = [":test_exec_root_access.build.feature_enabled"],
)

rust_test(
name = "test_exec_root_access_feature_disabled",
srcs = ["test_exec_root_access.rs"],
srcs = ["empty.rs"],
edition = "2021",
deps = [":test_exec_root_access.build.feature_disabled"],
)

###############################################################################
# Test that a CARGO_MANIFEST_DIR entry which collides with an exec root entry
# survives the run.
#
# The data file below puts a real `external` directory in CARGO_MANIFEST_DIR,
# and `external` is always a top-level exec root entry, so the runner cannot
# symlink it. It must then leave that directory alone rather than deleting it
# along with the symlinks it did create.
###############################################################################

write_file(
name = "exec_root_collision_file",
out = "external/exec_root_collision.txt",
content = ["This file makes 'external' a real directory in CARGO_MANIFEST_DIR."],
)

symlink_execroot_cargo_build_script(
name = "test_exec_root_collision.build.feature_enabled",
script = ":test_exec_root_collision.build.feature_disabled",
target_compatible_with = select({
"@platforms//os:windows": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
)

cargo_build_script(
name = "test_exec_root_collision.build.feature_disabled",
srcs = ["test_exec_root_collision.build.rs"],
crate_name = "test_exec_root_collision",
data = [
":exec_root_collision_file",
],
edition = "2021",
)

rust_test(
name = "test_exec_root_collision_feature_enabled",
srcs = ["empty.rs"],
edition = "2021",
deps = [":test_exec_root_collision.build.feature_enabled"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! A Cargo build script binary used in unit tests for the Bazel `cargo_build_script` rule

fn main() {
// `external` is a top-level exec root entry, so the runner will try to symlink it into
// CARGO_MANIFEST_DIR, where this data file has already created a real directory of the
// same name. The pre-existing directory must win, and must still be readable.
let collision = std::path::Path::new("external/exec_root_collision.txt");
assert!(
collision.is_file(),
"'external/exec_root_collision.txt' must be readable from CARGO_MANIFEST_DIR"
);
assert_eq!(
std::fs::read_to_string(collision).unwrap(),
"This file makes 'external' a real directory in CARGO_MANIFEST_DIR."
);
}
Loading