use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let cargo_target = env::var("TARGET").expect("Cargo should set TARGET");
let cargo_host = env::var("HOST").expect("Cargo should set HOST");
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let source_root = zig_source_root(&manifest_dir);
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let prefix = out_dir.join("zig-prefix");
let mut command = Command::new("zig");
command
.arg("build")
.arg("install-c-lib")
.arg("-Doptimize=ReleaseFast")
.arg("-Dstrip=true")
.arg("-Dcpu=baseline");
if let Some(zig_target) = zig_target_for_cargo_target(&cargo_target, &cargo_host) {
command.arg(format!("-Dtarget={zig_target}"));
}
for (feature, flag) in [
("CARGO_FEATURE_YAML", "-Dyaml=false"),
("CARGO_FEATURE_TOML", "-Dtoml=false"),
("CARGO_FEATURE_ZON", "-Dzon=false"),
("CARGO_FEATURE_XML", "-Dxml=false"),
] {
if env::var_os(feature).is_none() {
command.arg(flag);
}
}
let status = command
.arg("--prefix")
.arg(&prefix)
.current_dir(&source_root)
.status()
.expect("failed to run `zig build`");
if !status.success() {
panic!("`zig build` failed with status {status}");
}
if cargo_target.contains("apple") {
repack_archive_for_apple_ld(&prefix.join("lib").join("libfig.a"));
}
println!(
"cargo:rustc-link-search=native={}",
prefix.join("lib").display()
);
println!("cargo:rustc-link-lib=static=fig");
println!("cargo:rerun-if-env-changed=FIG_ZIG_ROOT");
println!(
"cargo:rerun-if-changed={}",
source_root.join("build.zig").display()
);
println!(
"cargo:rerun-if-changed={}",
source_root.join("src").display()
);
println!(
"cargo:rerun-if-changed={}",
source_root.join("include/fig.h").display()
);
}
fn zig_source_root(manifest_dir: &Path) -> PathBuf {
if let Some(dir) = env::var_os("FIG_ZIG_ROOT") {
let dir = PathBuf::from(dir);
assert!(
is_fig_zig_root(&dir),
"FIG_ZIG_ROOT={} is not a fig Zig source tree (no build.zig + src/c_api.zig)",
dir.display()
);
return dir;
}
for ancestor in manifest_dir.ancestors() {
if is_fig_zig_root(ancestor) {
return ancestor.to_path_buf();
}
}
let vendored = manifest_dir.join("zig");
if is_fig_zig_root(&vendored) {
return vendored;
}
panic!(
"could not locate fig's Zig source. In a checkout it is found by walking up \
from {}; in a published crate it is vendored at ./zig — run ./vendor-zig.sh \
to populate it, or set FIG_ZIG_ROOT to a fig source tree.",
manifest_dir.display()
);
}
fn is_fig_zig_root(dir: &Path) -> bool {
dir.join("build.zig").is_file()
&& dir.join("build.zig.zon").is_file()
&& dir.join("src/c_api.zig").is_file()
}
fn repack_archive_for_apple_ld(lib_path: &Path) {
let work = lib_path
.parent()
.expect("library path has a parent")
.join("repack");
let _ = fs::remove_dir_all(&work);
fs::create_dir_all(&work).expect("create repack work dir");
run(Command::new("ar").arg("x").arg(lib_path).current_dir(&work));
let mut objects = Vec::new();
for entry in fs::read_dir(&work).expect("read repack work dir") {
let path = entry.expect("dir entry").path();
if path.extension().is_some_and(|ext| ext == "o") {
run(Command::new("chmod").arg("u+rw").arg(&path));
objects.push(path);
}
}
assert!(
!objects.is_empty(),
"no object files extracted from {}",
lib_path.display()
);
let mut libtool = Command::new("libtool");
libtool
.arg("-static")
.arg("-o")
.arg(lib_path)
.args(&objects);
run(&mut libtool);
let _ = fs::remove_dir_all(&work);
}
fn run(command: &mut Command) {
let status = command
.status()
.unwrap_or_else(|e| panic!("failed to run {command:?}: {e}"));
if !status.success() {
panic!("{command:?} failed with status {status}");
}
}
fn zig_target_for_cargo_target(target: &str, host: &str) -> Option<&'static str> {
match target {
"x86_64-pc-windows-msvc" => return Some("x86_64-windows-msvc"),
"aarch64-pc-windows-msvc" => return Some("aarch64-windows-msvc"),
_ => {}
}
if target == host {
return None;
}
match target {
"aarch64-apple-darwin" => Some("aarch64-macos"),
"x86_64-apple-darwin" => Some("x86_64-macos"),
"aarch64-apple-ios" => Some("aarch64-ios"),
"aarch64-apple-ios-sim" => Some("aarch64-ios-simulator"),
"x86_64-apple-ios" => Some("x86_64-ios-simulator"),
"aarch64-pc-windows-gnu" => Some("aarch64-windows-gnu"),
"x86_64-pc-windows-gnu" => Some("x86_64-windows-gnu"),
"i686-pc-windows-gnu" => Some("x86-windows-gnu"),
"aarch64-unknown-linux-gnu" => Some("aarch64-linux-gnu"),
"aarch64-unknown-linux-musl" => Some("aarch64-linux-musl"),
"arm-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"),
"arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"),
"arm-unknown-linux-musleabi" => Some("arm-linux-musleabi"),
"arm-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"),
"i686-unknown-linux-gnu" => Some("x86-linux-gnu"),
"i686-unknown-linux-musl" => Some("x86-linux-musl"),
"powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu"),
"powerpc64le-unknown-linux-musl" => Some("powerpc64le-linux-musl"),
"riscv64gc-unknown-linux-gnu" => Some("riscv64-linux-gnu"),
"riscv64gc-unknown-linux-musl" => Some("riscv64-linux-musl"),
"wasm32-unknown-unknown" => Some("wasm32-freestanding"),
"x86_64-unknown-linux-gnu" => Some("x86_64-linux-gnu"),
"x86_64-unknown-linux-musl" => Some("x86_64-linux-musl"),
_ => panic!(
"unsupported Rust target `{target}` for fig's bundled Zig static library; \
add a Cargo-to-Zig target mapping in bindings/rust/build.rs"
),
}
}