Skip to content

Commit 1d0fe8a

Browse files
authored
Replace symlinks in the output of cargo build scripts (#3067)
#2948 breaks building of rdkafka with `cmake` because of dangling symlinks. When building with latest version we get the following error: ``` ERROR: /home/wincent/.cache/bazel/_bazel_wincent/394c4c1d21c5490d4a70260a2cfccaf5/external/rules_rust~~crate~crates__rdkafka-sys-4.8.0-2.3.0/BUILD.bazel:68:19: error while validating output tree artifact external/rules_rust~~crate~crates__rdkafka-sys-4.8.0-2.3.0/_bs.out_dir: child lib/cmake/RdKafka/FindLZ4.cmake is a dangling symbolic link ERROR: /home/wincent/.cache/bazel/_bazel_wincent/394c4c1d21c5490d4a70260a2cfccaf5/external/rules_rust~~crate~crates__rdkafka-sys-4.8.0-2.3.0/BUILD.bazel:68:19: Running Cargo build script rdkafka-sys failed: not all outputs were created or valid Target @@rules_rust~~crate~crates__rdkafka-0.37.0//:rdkafka failed to build Use --verbose_failures to see the command lines of failed build steps. ERROR: /home/wincent/.cache/bazel/_bazel_wincent/394c4c1d21c5490d4a70260a2cfccaf5/external/rules_rust~~crate~crates__rdkafka-sys-4.8.0-2.3.0/BUILD.bazel:18:13 Compiling Rust rlib rdkafka_sys v4.8.0+2.3.0 (7 files) failed: not all outputs were created or valid ```
1 parent b9f51f5 commit 1d0fe8a

File tree

5 files changed

+191
-7
lines changed

5 files changed

+191
-7
lines changed

cargo/cargo_build_script_runner/bin.rs

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ fn run_buildrs() -> Result<(), String> {
9393
command
9494
.current_dir(&working_directory)
9595
.envs(target_env_vars)
96-
.env("OUT_DIR", out_dir_abs)
96+
.env("OUT_DIR", &out_dir_abs)
9797
.env("CARGO_MANIFEST_DIR", manifest_dir)
9898
.env("RUSTC", rustc)
9999
.env("RUST_BACKTRACE", "full");
@@ -228,9 +228,10 @@ fn run_buildrs() -> Result<(), String> {
228228

229229
// Delete any runfiles that do not need to be propagated to down stream dependents.
230230
if let Some(cargo_manifest_maker) = cargo_manifest_maker {
231-
cargo_manifest_maker.drain_runfiles_dir().unwrap();
231+
cargo_manifest_maker
232+
.drain_runfiles_dir(&out_dir_abs)
233+
.unwrap();
232234
}
233-
234235
Ok(())
235236
}
236237

@@ -568,18 +569,19 @@ impl RunfilesMaker {
568569
}
569570

570571
/// Delete runfiles from the runfiles directory that do not match user defined suffixes
571-
fn drain_runfiles_dir(&self) -> Result<(), String> {
572+
fn drain_runfiles_dir(&self, out_dir: &Path) -> Result<(), String> {
572573
if cfg!(target_family = "windows") {
573574
// If symlinks are supported then symlinks will have been used.
574575
let supports_symlinks = system_supports_symlinks(&self.output_dir)?;
575576
if supports_symlinks {
576-
self.drain_runfiles_dir_unix()
577+
self.drain_runfiles_dir_unix()?;
577578
} else {
578-
self.drain_runfiles_dir_windows()
579+
self.drain_runfiles_dir_windows()?;
579580
}
580581
} else {
581-
self.drain_runfiles_dir_unix()
582+
self.drain_runfiles_dir_unix()?;
582583
}
584+
replace_symlinks_in_out_dir(out_dir)
583585
}
584586
}
585587

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

725+
/// Iterates over the given directory recursively and resolves any symlinks
726+
///
727+
/// Symlinks shouldn't present in `out_dir` as those amy contain paths to sandboxes which doesn't exists anymore.
728+
/// Therefore, bazel will fail because of dangling symlinks.
729+
fn replace_symlinks_in_out_dir(out_dir: &Path) -> Result<(), String> {
730+
if out_dir.is_dir() {
731+
let out_dir_paths = std::fs::read_dir(out_dir).map_err(|e| {
732+
format!(
733+
"Failed to read directory `{}` with {:?}",
734+
out_dir.display(),
735+
e
736+
)
737+
})?;
738+
for entry in out_dir_paths {
739+
let entry =
740+
entry.map_err(|e| format!("Failed to read directory entry with {:?}", e,))?;
741+
let path = entry.path();
742+
743+
if path.is_symlink() {
744+
let target_path = std::fs::read_link(&path).map_err(|e| {
745+
format!("Failed to read symlink `{}` with {:?}", path.display(), e,)
746+
})?;
747+
// we don't want to replace relative symlinks
748+
if target_path.is_relative() {
749+
continue;
750+
}
751+
std::fs::remove_file(&path)
752+
.map_err(|e| format!("Failed remove file `{}` with {:?}", path.display(), e))?;
753+
std::fs::copy(&target_path, &path).map_err(|e| {
754+
format!(
755+
"Failed to copy `{} -> {}` with {:?}",
756+
target_path.display(),
757+
path.display(),
758+
e
759+
)
760+
})?;
761+
} else if path.is_dir() {
762+
replace_symlinks_in_out_dir(&path).map_err(|e| {
763+
format!(
764+
"Failed to normalize nested directory `{}` with {}",
765+
path.display(),
766+
e,
767+
)
768+
})?;
769+
}
770+
}
771+
}
772+
Ok(())
773+
}
774+
723775
fn main() {
724776
std::process::exit(match run_buildrs() {
725777
Ok(_) => 0,
@@ -735,6 +787,9 @@ fn main() {
735787
mod test {
736788
use super::*;
737789

790+
use std::fs;
791+
use std::io::Write;
792+
738793
#[test]
739794
fn rustc_cfg_parsing() {
740795
let macos_output = r#"\
@@ -775,4 +830,67 @@ windows
775830
assert_eq!(tree["CARGO_CFG_WINDOWS"], "");
776831
assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "windows");
777832
}
833+
834+
fn prepare_output_dir_with_symlinks() -> PathBuf {
835+
let test_tmp = PathBuf::from(std::env::var("TEST_TMPDIR").unwrap());
836+
let out_dir = test_tmp.join("out_dir");
837+
fs::create_dir(&out_dir).unwrap();
838+
let nested_dir = out_dir.join("nested");
839+
fs::create_dir(&nested_dir).unwrap();
840+
841+
let temp_dir_file = test_tmp.join("outside.txt");
842+
let mut file = fs::File::create(&temp_dir_file).unwrap();
843+
file.write_all(b"outside world").unwrap();
844+
// symlink abs path outside of the out_dir
845+
symlink(&temp_dir_file, &out_dir.join("outside.txt")).unwrap();
846+
847+
let inside_dir_file = out_dir.join("inside.txt");
848+
let mut file = fs::File::create(inside_dir_file).unwrap();
849+
file.write_all(b"inside world").unwrap();
850+
// symlink relative next to the file in the out_dir
851+
symlink(
852+
&PathBuf::from("inside.txt"),
853+
&out_dir.join("inside_link.txt"),
854+
)
855+
.unwrap();
856+
// symlink relative within a subdir in the out_dir
857+
symlink(
858+
&PathBuf::from("..").join("inside.txt"),
859+
&out_dir.join("nested").join("inside_link.txt"),
860+
)
861+
.unwrap();
862+
863+
out_dir
864+
}
865+
866+
#[cfg(any(target_family = "windows", target_family = "unix"))]
867+
#[test]
868+
fn replace_symlinks_in_out_dir() {
869+
let out_dir = prepare_output_dir_with_symlinks();
870+
super::replace_symlinks_in_out_dir(&out_dir).unwrap();
871+
872+
// this should be replaced because it is an absolute symlink
873+
let file_path = out_dir.join("outside.txt");
874+
assert!(!file_path.is_symlink());
875+
let contents = fs::read_to_string(file_path).unwrap();
876+
assert_eq!(contents, "outside world");
877+
878+
// this is the file created inside the out_dir
879+
let file_path = out_dir.join("inside.txt");
880+
assert!(!file_path.is_symlink());
881+
let contents = fs::read_to_string(file_path).unwrap();
882+
assert_eq!(contents, "inside world");
883+
884+
// this is the symlink in the out_dir
885+
let file_path = out_dir.join("inside_link.txt");
886+
assert!(file_path.is_symlink());
887+
let contents = fs::read_to_string(file_path).unwrap();
888+
assert_eq!(contents, "inside world");
889+
890+
// this is the symlink in the out_dir under another directory which refers to ../inside.txt
891+
let file_path = out_dir.join("nested").join("inside_link.txt");
892+
assert!(file_path.is_symlink());
893+
let contents = fs::read_to_string(file_path).unwrap();
894+
assert_eq!(contents, "inside world");
895+
}
778896
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
load("//cargo:defs.bzl", "cargo_build_script")
2+
load("//rust:defs.bzl", "rust_test")
3+
4+
# We are testing the cargo build script behavior that it correctly resolves absolute path symlinks in the out_dir.
5+
# Additionally, it keeps out_dir relative symlinks intact.
6+
7+
cargo_build_script(
8+
name = "symlink_build_rs",
9+
srcs = ["build.rs"],
10+
data = ["data.txt"],
11+
edition = "2018",
12+
)
13+
14+
rust_test(
15+
name = "test",
16+
srcs = ["test.rs"],
17+
data = [":symlink_build_rs"],
18+
edition = "2018",
19+
deps = [":symlink_build_rs"],
20+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use std::path::{Path, PathBuf};
2+
3+
#[cfg(target_family = "unix")]
4+
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) {
5+
std::os::unix::fs::symlink(original, link).unwrap();
6+
}
7+
8+
#[cfg(target_family = "windows")]
9+
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) {
10+
std::os::windows::fs::symlink_file(original, link).unwrap();
11+
}
12+
13+
fn main() {
14+
let path = "data.txt";
15+
if !PathBuf::from(path).exists() {
16+
panic!("File does not exist in path.");
17+
}
18+
let out_dir = std::env::var("OUT_DIR").unwrap();
19+
let out_dir = PathBuf::from(&out_dir);
20+
let original_cwd = std::env::current_dir().unwrap();
21+
std::fs::copy(&path, &out_dir.join("data.txt")).unwrap();
22+
std::env::set_current_dir(&out_dir).unwrap();
23+
std::fs::create_dir("nested").unwrap();
24+
symlink("data.txt", "relative_symlink.txt");
25+
symlink("../data.txt", "nested/relative_symlink.txt");
26+
std::env::set_current_dir(&original_cwd).unwrap();
27+
println!("{}", out_dir.display());
28+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Resolved symlink file or relative symlink
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#[test]
2+
pub fn test_compile_data_resolved_symlink() {
3+
let data = include_str!(concat!(env!("OUT_DIR"), "/data.txt"));
4+
assert_eq!("Resolved symlink file or relative symlink\n", data);
5+
}
6+
7+
#[test]
8+
pub fn test_compile_data_relative_symlink() {
9+
let data = include_str!(concat!(env!("OUT_DIR"), "/relative_symlink.txt"));
10+
assert_eq!("Resolved symlink file or relative symlink\n", data);
11+
}
12+
13+
#[test]
14+
pub fn test_compile_data_relative_nested_symlink() {
15+
let data = include_str!(concat!(env!("OUT_DIR"), "/nested/relative_symlink.txt"));
16+
assert_eq!("Resolved symlink file or relative symlink\n", data);
17+
}

0 commit comments

Comments
 (0)