Skip to content

Commit 4ae94e7

Browse files
committed
chore: use hardlinks when copying honggfuzz source code
1 parent f9ca63e commit 4ae94e7

File tree

1 file changed

+41
-8
lines changed

1 file changed

+41
-8
lines changed

build.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,49 @@ const GNU_MAKE: &str = "make";
2424
))]
2525
const GNU_MAKE: &str = "gmake";
2626

27-
/// Recursively copy a directory tree from `src` to `dst`.
28-
fn copy_dir_all(src: &Path, dst: &Path) {
27+
/// Directories to skip when mirroring the source tree (they are not needed
28+
/// for the build and would waste a lot of space).
29+
const SKIP_DIRS: &[&str] = &["examples"];
30+
31+
/// Check whether hardlinks work between `src` and `dst` directories by
32+
/// trying to hardlink an existing file from `src` into `dst`.
33+
fn can_hardlink(src: &Path, dst: &Path) -> bool {
34+
// Pick any existing file in src to use as a probe.
35+
let probe_src = match fs::read_dir(src).ok().and_then(|mut d| {
36+
d.find(|e| e.as_ref().is_ok_and(|e| e.file_type().is_ok_and(|t| t.is_file())))
37+
}) {
38+
Some(Ok(entry)) => entry.path(),
39+
_ => return false,
40+
};
41+
let probe_dst = dst.join(".hardlink_probe");
42+
let ok = fs::hard_link(&probe_src, &probe_dst).is_ok();
43+
let _ = fs::remove_file(&probe_dst);
44+
ok
45+
}
46+
47+
/// Recursively mirror a directory tree from `src` to `dst`.
48+
///
49+
/// When `use_hardlinks` is true, regular files are hard-linked instead of
50+
/// copied, saving disk space and I/O.
51+
/// Directories listed in `SKIP_DIRS` are skipped entirely.
52+
fn mirror_dir_all(src: &Path, dst: &Path, use_hardlinks: bool) {
2953
fs::create_dir_all(dst).unwrap();
3054
for entry in fs::read_dir(src).unwrap() {
3155
let entry = entry.unwrap();
56+
let name = entry.file_name();
3257
let ty = entry.file_type().unwrap();
33-
let dst_path = dst.join(entry.file_name());
58+
let dst_path = dst.join(&name);
3459
if ty.is_dir() {
35-
copy_dir_all(&entry.path(), &dst_path);
60+
if SKIP_DIRS.iter().any(|s| *s == name) {
61+
continue;
62+
}
63+
mirror_dir_all(&entry.path(), &dst_path, use_hardlinks);
3664
} else if ty.is_symlink() {
3765
let target = fs::read_link(entry.path()).unwrap();
3866
#[cfg(unix)]
3967
std::os::unix::fs::symlink(&target, &dst_path).unwrap();
68+
} else if use_hardlinks {
69+
fs::hard_link(entry.path(), &dst_path).unwrap();
4070
} else {
4171
fs::copy(entry.path(), &dst_path).unwrap();
4272
}
@@ -66,14 +96,17 @@ fn main() {
6696
let honggfuzz_target = Path::new(&env::var("CRATE_ROOT").unwrap()) // from honggfuzz
6797
.join(honggfuzz_target); // resolve the original honggfuzz_target relative to CRATE_ROOT
6898

69-
// Copy honggfuzz source tree into OUT_DIR so we can build in a writable
70-
// directory. This is required for Nix builds where the source directory
71-
// is read-only.
99+
// Mirror honggfuzz source tree into OUT_DIR so we can build in a
100+
// writable directory. This is required for Nix builds where the source
101+
// directory is read-only. Use hardlinks when possible (same filesystem)
102+
// to save disk space, falling back to copies otherwise.
72103
let build_dir = out_dir.join("honggfuzz");
73104
if build_dir.exists() {
74105
fs::remove_dir_all(&build_dir).unwrap();
75106
}
76-
copy_dir_all(Path::new("honggfuzz"), &build_dir);
107+
fs::create_dir_all(&build_dir).unwrap();
108+
let use_hardlinks = can_hardlink(Path::new("honggfuzz"), &build_dir);
109+
mirror_dir_all(Path::new("honggfuzz"), &build_dir, use_hardlinks);
77110

78111
let build_dir_str = build_dir.to_str().unwrap();
79112

0 commit comments

Comments
 (0)