use std::process::Command;
fn tree(pkg: &str) -> Option<String> {
let out = Command::new("cargo")
.args(["tree", "--offline", "-p", pkg, "-e", "normal,build"])
.current_dir(env!("CARGO_MANIFEST_DIR"))
.output()
.ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).into_owned())
}
const C_MARKERS: &[&str] = &[
"libz-sys",
"libz-ng-sys",
"cloudflare-zlib-sys",
"zstd-sys",
"bzip2-sys",
"openssl-sys",
"cmake",
"bindgen",
];
fn c_hit(line: &str) -> Option<String> {
let name = line
.trim_start_matches(['├', '─', '│', '└', ' ', '[', ']'])
.split_whitespace()
.next()
.unwrap_or("");
if name == "cc" || C_MARKERS.iter().any(|m| name == *m) {
return Some(name.to_string());
}
None
}
#[test]
fn ldeflate_pulls_no_c_no_cmake_no_sys_crate() {
let Some(t) = tree("ldeflate") else {
eprintln!("SKIP no-C guard: `cargo tree --offline` unavailable here");
return;
};
let hits: Vec<String> = t.lines().filter_map(c_hit).collect();
assert!(
hits.is_empty(),
"ldeflate's resolved dependency graph pulls a C/cmake/sys crate: {hits:?}\n\
This disqualifies it from the appliance. The usual cause is a `flate2` \
feature (zlib / zlib-ng / zlib-ng-compat / cloudflare_zlib) reaching us \
through feature unification. `ldeflate` must stay on \
`default-features = false, features = [\"zlib-rs\"]`.\n\nfull tree:\n{t}"
);
}
#[test]
fn the_c_marker_scan_actually_fires_on_a_crate_that_has_c() {
let Some(t) = tree("zstd-sys-rs") else {
eprintln!("SKIP no-C guard control: `cargo tree --offline` unavailable here");
return;
};
let hits: Vec<String> = t.lines().filter_map(c_hit).collect();
assert!(
!hits.is_empty(),
"the C-marker scan found NOTHING in zstd-sys-rs, which demonstrably \
compiles ~25 vendored .c files via `cc` and generates bindings with \
`bindgen`. The scan is broken, so the ldeflate guard above proves \
nothing.\n\nfull tree:\n{t}"
);
eprintln!("control OK — scan fired on zstd-sys-rs: {hits:?}");
}
#[test]
fn ldeflate_has_no_build_script_and_no_native_sources() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
assert!(
!root.join("build.rs").exists(),
"ldeflate must have no build.rs"
);
let mut stack = vec![root.to_path_buf()];
while let Some(d) = stack.pop() {
let Ok(rd) = std::fs::read_dir(&d) else {
continue;
};
for e in rd.flatten() {
let p = e.path();
if p.is_dir() {
if p.file_name().is_some_and(|n| n == "target") {
continue;
}
stack.push(p);
continue;
}
let ext = p.extension().and_then(|x| x.to_str()).unwrap_or("");
assert!(
!matches!(ext, "c" | "h" | "cc" | "cpp" | "S"),
"ldeflate carries a native source file: {}",
p.display()
);
assert!(
p.file_name().is_none_or(|n| n != "CMakeLists.txt"),
"ldeflate carries a CMakeLists.txt: {}",
p.display()
);
}
}
}