use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo:rerun-if-env-changed=FIG_SYS_FORCE_SOURCE");
let cargo_target = env::var("TARGET").expect("Cargo should set TARGET");
let cargo_host = env::var("HOST").expect("Cargo should set HOST");
if env::var_os("FIG_SYS_FORCE_SOURCE").is_none() && features_match_prebuilt() {
if let Some(libdir) = prebuilt_libdir(&cargo_target) {
println!("cargo:rustc-link-search=native={}", libdir.display());
println!("cargo:rustc-link-lib=static=fig");
println!("cargo:rerun-if-changed={}", libdir.display());
return;
}
}
build_from_source(&cargo_target, &cargo_host);
}
fn features_match_prebuilt() -> bool {
let on = |name: &str| env::var_os(format!("CARGO_FEATURE_{name}")).is_some();
on("JSON") && on("YAML") && on("TOML") && on("FIG") && !on("ZON") && !on("XML")
}
fn prebuilt_libdir(target: &str) -> Option<PathBuf> {
let key = payload_env_key(target)?;
let dir = PathBuf::from(env::var_os(format!("DEP_FIG_PREBUILT_{key}_LIBDIR"))?);
let has_archive = ["libfig.a", "fig.lib"]
.iter()
.any(|name| dir.join(name).is_file());
has_archive.then_some(dir)
}
fn payload_env_key(target: &str) -> Option<&'static str> {
Some(match target {
"aarch64-apple-darwin" => "MACOS_ARM64",
"x86_64-unknown-linux-gnu" => "LINUX_X64_GNU",
"aarch64-unknown-linux-gnu" => "LINUX_ARM64_GNU",
"x86_64-pc-windows-msvc" => "WINDOWS_X64_MSVC",
"wasm32-unknown-unknown" => "WASM32",
_ => return None,
})
}
fn build_from_source(cargo_target: &str, cargo_host: &str) {
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_JSON", "-Djson=false"),
("CARGO_FEATURE_YAML", "-Dyaml=false"),
("CARGO_FEATURE_TOML", "-Dtoml=false"),
("CARGO_FEATURE_ZON", "-Dzon=false"),
("CARGO_FEATURE_XML", "-Dxml=false"),
("CARGO_FEATURE_FIG", "-Dfig=false"),
] {
if env::var_os(feature).is_none() {
command.arg(flag);
}
}
let status = command
.arg("--prefix")
.arg(&prefix)
.current_dir(&source_root)
.status()
.unwrap_or_else(|e| {
panic!(
"fig-sys: this build needs to compile the fig core from source \
(target `{cargo_target}` has no prebuilt library, a non-default \
language feature set is active, or FIG_SYS_FORCE_SOURCE is set), \
but running `zig` failed: {e}.\n\
Default-feature builds for these targets need no Zig: \
aarch64-apple-darwin, x86_64-unknown-linux-gnu, \
aarch64-unknown-linux-gnu, x86_64-pc-windows-msvc, \
wasm32-unknown-unknown.\n\
Otherwise install Zig 0.16+ (https://ziglang.org/download/)."
)
});
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("bindings/c/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 `zig build vendor-rust` \
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"
),
}
}