Skip to content

Commit

Permalink
Replace symlinks in the output of cargo build scripts
Browse files Browse the repository at this point in the history
After #2948 we symlink the source files in sandbox for most external repositories.
When a native build system is involved (e.x.: cmake) it is possible that the installed
files will be symlinks especially when header files are installed.

This will be placed in the output dir of cargo build scripts and when copied back to
the repository cache they become dangling symlinks with references to the sandbox.

As a fix we replace symlinks with a copy of them in the output directory.
  • Loading branch information
havasd committed Dec 15, 2024
1 parent bbbf343 commit 10df54a
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 1 deletion.
54 changes: 53 additions & 1 deletion cargo/cargo_build_script_runner/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ fn run_buildrs() -> Result<(), String> {
command
.current_dir(&working_directory)
.envs(target_env_vars)
.env("OUT_DIR", out_dir_abs)
.env("OUT_DIR", out_dir_abs.clone())
.env("CARGO_MANIFEST_DIR", manifest_dir)
.env("RUSTC", rustc)
.env("RUST_BACKTRACE", "full");
Expand Down Expand Up @@ -231,6 +231,8 @@ fn run_buildrs() -> Result<(), String> {
cargo_manifest_maker.drain_runfiles_dir().unwrap();
}

replace_symlinks_in_out_dir(&out_dir_abs)?;

Ok(())
}

Expand Down Expand Up @@ -720,6 +722,56 @@ fn parse_rustc_cfg_output(stdout: &str) -> BTreeMap<String, String> {
.collect()
}

/// Iterates over the given directory recursively and resolves any symlinks
///
/// Symlinks shouldn't present in `out_dir` as those amy contain paths to sandboxes which doesn't exists anymore.
/// Therefore, bazel will fail because of dangling symlinks.
fn replace_symlinks_in_out_dir(out_dir: &Path) -> Result<(), String> {
if out_dir.is_dir() {
let out_dir_paths = std::fs::read_dir(out_dir).map_err(|e| {
format!(
"Failed to read directory `{}` with {:?}",
out_dir.display(),
e
)
})?;
for entry in out_dir_paths {
let entry =
entry.map_err(|e| format!("Failed to read directory entry with {:?}", e,))?;
let path = entry.path();

if path.is_symlink() {
let target_path = std::fs::read_link(&path).map_err(|e| {
format!("Failed to read symlink `{}` with {:?}", path.display(), e,)
})?;
// we don't want to replace relative symlinks
if target_path.is_relative() {
continue;
}
std::fs::remove_file(&path)
.map_err(|e| format!("Failed remove file `{}` with {:?}", path.display(), e))?;
std::fs::copy(&target_path, &path).map_err(|e| {
format!(
"Failed to copy `{} -> {}` with {:?}",
target_path.display(),
path.display(),
e
)
})?;
} else if path.is_dir() {
replace_symlinks_in_out_dir(&path).map_err(|e| {
format!(
"Failed to normalize nested directory `{}` with {}",
path.display(),
e,
)
})?;
}
}
}
Ok(())
}

fn main() {
std::process::exit(match run_buildrs() {
Ok(_) => 0,
Expand Down
19 changes: 19 additions & 0 deletions test/cargo_build_script/resolve_abs_symlink_out_dir/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("//cargo:defs.bzl", "cargo_build_script")
load("//rust:defs.bzl", "rust_test")

# We are testing the cargo build script behavior that it correctly resolves absolute path symlinks in the out_dir.
# Additionally, it keeps out_dir relative symlinks intact.

cargo_build_script(
name = "symlink_build_rs",
srcs = ["build.rs"],
data = ["data.txt"],
edition = "2018",
)

rust_test(
name = "test",
srcs = ["test.rs"],
edition = "2018",
deps = [":symlink_build_rs"],
)
28 changes: 28 additions & 0 deletions test/cargo_build_script/resolve_abs_symlink_out_dir/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::path::{Path, PathBuf};

#[cfg(target_family = "unix")]
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) {
std::os::unix::fs::symlink(original, link).unwrap();
}

#[cfg(target_family = "windows")]
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) {
std::os::windows::fs::symlink_file(original, link).unwrap();
}

fn main() {
let path = "data.txt";
if !PathBuf::from(path).exists() {
panic!("File does not exist in path.");
}
let out_dir = std::env::var("OUT_DIR").unwrap();
let out_dir = PathBuf::from(&out_dir);
let original_cwd = std::env::current_dir().unwrap();
std::fs::copy(&path, &out_dir.join("data.txt")).unwrap();
std::env::set_current_dir(&out_dir).unwrap();
std::fs::create_dir("nested").unwrap();
symlink("data.txt", "relative_symlink.txt");
symlink("../data.txt", "nested/relative_symlink.txt");
std::env::set_current_dir(&original_cwd).unwrap();
println!("{}", out_dir.display());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Resolved symlink file or relative symlink
17 changes: 17 additions & 0 deletions test/cargo_build_script/resolve_abs_symlink_out_dir/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#[test]
pub fn test_compile_data_resolved_symlink() {
let data = include_str!(concat!(env!("OUT_DIR"), "/data.txt"));
assert_eq!("Resolved symlink file or relative symlink\n", data);
}

#[test]
pub fn test_compile_data_relative_symlink() {
let data = include_str!(concat!(env!("OUT_DIR"), "/relative_symlink.txt"));
assert_eq!("Resolved symlink file or relative symlink\n", data);
}

#[test]
pub fn test_compile_data_relative_nested_symlink() {
let data = include_str!(concat!(env!("OUT_DIR"), "/nested/relative_symlink.txt"));
assert_eq!("Resolved symlink file or relative symlink\n", data);
}

0 comments on commit 10df54a

Please sign in to comment.