use std::env;
use std::path::PathBuf;
use std::process::Command;
fn xcrun() -> Command {
let mut cmd = Command::new("/usr/bin/xcrun");
cmd.env_remove("SDKROOT").env_remove("DEVELOPER_DIR");
cmd
}
fn main() {
if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("macos") {
return;
}
println!("cargo:rerun-if-changed=shim/Package.swift");
println!("cargo:rerun-if-changed=shim/Sources");
let have_swiftc = xcrun()
.args(["--find", "swiftc"])
.output()
.is_ok_and(|o| o.status.success());
assert!(
have_swiftc,
"arcbox-vz: `swiftc` not found. The crate builds its Swift shim (ArcBoxVZShim) with the \
Xcode Command Line Tools, a documented prerequisite (CONTRIBUTING.md -> Prerequisites). \
Install with `xcode-select --install`."
);
let config = if env::var("PROFILE").as_deref() == Ok("release") {
"release"
} else {
"debug"
};
let triple = match env::var("CARGO_CFG_TARGET_ARCH").as_deref() {
Ok("aarch64") => "arm64-apple-macosx13.0",
Ok("x86_64") => "x86_64-apple-macosx13.0",
other => panic!("arcbox-vz: unsupported macOS arch {other:?}"),
};
let pkg = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("shim");
let scratch = PathBuf::from(env::var("OUT_DIR").unwrap()).join("swiftpm-scratch");
let common = [
"--package-path",
pkg.to_str().unwrap(),
"--scratch-path",
scratch.to_str().unwrap(),
"-c",
config,
"--triple",
triple,
"--product",
"ArcBoxVZShim",
];
let out = xcrun()
.args(["swift", "build"])
.args(common)
.output()
.expect("failed to spawn `xcrun swift build`");
assert!(
out.status.success(),
"swift build failed:\n{}\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let bin = xcrun()
.args(["swift", "build"])
.args(common)
.arg("--show-bin-path")
.output()
.expect("failed to spawn `xcrun swift build --show-bin-path`");
assert!(bin.status.success(), "swift build --show-bin-path failed");
let bin_path = PathBuf::from(String::from_utf8(bin.stdout).unwrap().trim());
assert!(
bin_path.join("libArcBoxVZShim.a").exists(),
"libArcBoxVZShim.a missing in {}",
bin_path.display()
);
let swift_stub_dir = env::var("SDKROOT")
.ok()
.map(|s| format!("{s}/usr/lib/swift"))
.filter(|p| PathBuf::from(p).is_dir())
.unwrap_or_else(|| {
let sdk = xcrun()
.args(["--sdk", "macosx", "--show-sdk-path"])
.output()
.expect("failed to spawn `xcrun --show-sdk-path`");
assert!(sdk.status.success(), "xcrun --show-sdk-path failed");
format!(
"{}/usr/lib/swift",
String::from_utf8(sdk.stdout).unwrap().trim()
)
});
println!("cargo:rustc-link-search=native={}", bin_path.display());
println!("cargo:rustc-link-lib=static=ArcBoxVZShim");
println!("cargo:rustc-link-search=native={swift_stub_dir}");
for lib in autolink_swift_libs(&bin_path.join("libArcBoxVZShim.a")) {
println!("cargo:rustc-link-lib=dylib={lib}");
}
println!("cargo:rustc-link-lib=framework=Virtualization");
println!("cargo:rustc-link-lib=framework=Foundation");
}
fn autolink_swift_libs(archive: &std::path::Path) -> Vec<String> {
let out = xcrun()
.args(["otool", "-l"])
.arg(archive)
.output()
.expect("failed to spawn `xcrun otool -l`");
assert!(
out.status.success(),
"otool -l failed on {}",
archive.display()
);
let mut libs: Vec<String> = String::from_utf8_lossy(&out.stdout)
.lines()
.filter_map(|line| {
let opt = line.trim().strip_prefix("string #")?.split_once(' ')?.1;
let lib = opt.strip_prefix("-l")?;
((lib.starts_with("swift") && !lib.starts_with("swiftCompatibility")) || lib == "objc")
.then(|| lib.to_string())
})
.collect();
libs.sort();
libs.dedup();
assert!(
libs.iter().any(|l| l == "swiftCore"),
"no swiftCore autolink entry found in {} — otool parse broke?",
archive.display()
);
libs
}