use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
if let Err(err) = try_main() {
eprintln!("pseudoroot/build.rs: {err}");
std::process::exit(1);
}
}
fn try_main() -> Result<(), String> {
let target = require_env("TARGET")?; let profile = require_env("PROFILE")?; let manifest_dir = PathBuf::from(require_env("CARGO_MANIFEST_DIR")?);
let out_dir = PathBuf::from(require_env("OUT_DIR")?);
let cargo = require_env("CARGO")?;
let interpose = manifest_dir.join("interpose");
let gen_dir = out_dir.join("interpose-pkg");
if gen_dir.exists() {
fs::remove_dir_all(&gen_dir).map_err(|e| format!("clean {gen_dir:?}: {e}"))?;
}
copy_dir(&interpose.join("src"), &gen_dir.join("src"))?;
fs::copy(
interpose.join("pseudoroot.lds"),
gen_dir.join("pseudoroot.lds"),
)
.map_err(|e| format!("copy version script: {e}"))?;
let mut manifest = MANIFEST.to_string();
let ws_core = manifest_dir.parent().map(|p| p.join("pseudoroot-core"));
if let Some(core) = ws_core
&& core.join("Cargo.toml").exists()
{
manifest.push_str(&format!(
"\n[patch.crates-io]\npseudoroot-core = {{ path = {:?} }}\n",
core.display()
));
}
fs::write(gen_dir.join("Cargo.toml"), manifest).map_err(|e| format!("write manifest: {e}"))?;
fs::write(gen_dir.join("build.rs"), EMBED_BUILD_SCRIPT)
.map_err(|e| format!("write build script: {e}"))?;
let embed_target_dir = out_dir.join("embed-target");
let mut cmd = Command::new(&cargo);
cmd.args([
"build",
"--manifest-path",
gen_dir.join("Cargo.toml").to_str().ok_or("non-utf8 path")?,
"--target",
&target,
"--target-dir",
])
.arg(&embed_target_dir)
.current_dir(&out_dir);
if profile == "release" {
cmd.arg("--release");
}
let status = cmd
.status()
.map_err(|err| format!("failed to spawn nested build: {err}"))?;
if !status.success() {
return Err(format!("nested interpose build failed ({status})"));
}
let lib_name = if target.contains("apple") {
"libpseudoroot_lib.dylib"
} else {
"libpseudoroot_lib.so"
};
let lib_path = embed_target_dir.join(&target).join(&profile).join(lib_name);
if !lib_path.exists() {
return Err(format!(
"expected {} to exist after nested build",
lib_path.display()
));
}
println!(
"cargo:rustc-env=PSEUDOROOT_LIB_EMBED_PATH={}",
lib_path.display()
);
println!("cargo:rerun-if-changed={}", interpose.join("src").display());
println!(
"cargo:rerun-if-changed={}",
interpose.join("pseudoroot.lds").display()
);
Ok(())
}
const MANIFEST: &str = r#"[package]
name = "pseudoroot-lib-embed"
version = "0.2.1"
edition = "2024"
build = "build.rs"
publish = false
[lib]
name = "pseudoroot_lib"
crate-type = ["cdylib"]
path = "src/lib.rs"
[dependencies]
ctor = "0.2"
libc = "0.2"
pseudoroot-core = "0.2.1"
[workspace]
"#;
const EMBED_BUILD_SCRIPT: &str = r#"fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") {
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let lds = std::path::Path::new(&dir).join("pseudoroot.lds");
println!("cargo:rerun-if-changed={}", lds.display());
println!("cargo:rustc-cdylib-link-arg=-Wl,--version-script={}", lds.display());
println!("cargo:rustc-cdylib-link-arg=-Wl,--export-dynamic");
}
}
"#;
fn copy_dir(src: &Path, dst: &Path) -> Result<(), String> {
fs::create_dir_all(dst).map_err(|e| format!("mkdir {}: {e}", dst.display()))?;
for entry in fs::read_dir(src).map_err(|e| format!("read {}: {e}", src.display()))? {
let entry = entry.map_err(|e| format!("readdir: {e}"))?;
let path = entry.path();
if path.is_dir() {
copy_dir(&path, &dst.join(entry.file_name()))?;
} else {
fs::copy(&path, dst.join(entry.file_name()))
.map_err(|e| format!("copy {}: {e}", path.display()))?;
}
}
Ok(())
}
fn require_env(key: &str) -> Result<String, String> {
env::var(key).map_err(|_| format!("{key} not set (expected to run under `cargo build`)"))
}