use std::path::{Path, PathBuf};
use std::process::Command;
const ZIG_TRIPLES: &[(&str, &str, &str)] = &[
("aarch64-apple-darwin", "aarch64-macos.11.0", "baseline"),
("x86_64-apple-darwin", "x86_64-macos.11.0", "x86_64_v2"),
(
"x86_64-unknown-linux-gnu",
"x86_64-linux-gnu.2.17",
"x86_64_v2",
),
(
"aarch64-unknown-linux-gnu",
"aarch64-linux-gnu.2.17",
"baseline",
),
(
"x86_64-unknown-linux-musl",
"x86_64-linux-musl",
"x86_64_v2",
),
(
"aarch64-unknown-linux-musl",
"aarch64-linux-musl",
"baseline",
),
(
"x86_64-pc-windows-gnu",
"x86_64-windows.win10_rs4-gnu",
"x86_64_v2",
),
(
"x86_64-pc-windows-gnullvm",
"x86_64-windows.win10_rs4-gnu",
"x86_64_v2",
),
(
"aarch64-pc-windows-gnullvm",
"aarch64-windows.win10_rs4-gnu",
"baseline",
),
(
"x86_64-pc-windows-msvc",
"x86_64-windows.win10_rs4-msvc",
"x86_64_v2",
),
(
"aarch64-pc-windows-msvc",
"aarch64-windows.win10_rs4-msvc",
"baseline",
),
];
const VENDOR_ALIASES: &[(&str, &str)] = &[("x86_64-pc-windows-gnullvm", "x86_64-pc-windows-gnu")];
fn system_libs(target: &str) -> &'static [&'static str] {
if target.contains("-windows") {
&["ntdll"]
} else {
&[]
}
}
fn main() {
println!("cargo:rerun-if-env-changed=IRGX_LIB_DIR");
let target = env("TARGET");
let host = env("HOST");
let crate_dir = PathBuf::from(env("CARGO_MANIFEST_DIR"));
if let Some(dir) = std::env::var_os("IRGX_LIB_DIR") {
let dir = PathBuf::from(dir);
let Some(kind) = library_in(&dir) else {
fail(&format!(
"IRGX_LIB_DIR points at {}, which holds no irregex library. Expected one of \
libirgx.a, libirgx.dylib, libirgx.so, or - on Windows - irgx.lib beside \
the DLL. It is an error rather than a fallback because linking a \
different engine than the one you named would report results from a \
library you did not choose.",
dir.display()
));
};
return link(&dir, kind, &target);
}
let vendored_as = VENDOR_ALIASES
.iter()
.find_map(|(alias, served_by)| (*alias == target).then_some(*served_by))
.unwrap_or(&target);
let vendored = crate_dir.join("vendor").join(vendored_as).join("libirgx.a");
println!("cargo:rerun-if-changed={}", vendored.display());
if vendored.is_file() {
return link(vendored.parent().unwrap(), Kind::Static, &target);
}
let Some(checkout) = engine_checkout(&crate_dir) else {
fail(&unserved(&target, &crate_dir));
};
if target == host {
let built = checkout.join("zig-out").join("lib");
if let Some(kind) = library_in(&built) {
println!("cargo:rerun-if-changed={}", built.display());
return link(&built, kind, &target);
}
}
match zig_build(&checkout, &target) {
Ok((dir, kind)) => link(&dir, kind, &target),
Err(why) => fail(&format!("{}\n\n{why}", unserved(&target, &crate_dir))),
}
}
#[derive(Clone, Copy)]
enum Kind {
Static,
Shared,
}
fn link(dir: &Path, kind: Kind, target: &str) {
for name in [
"libirgx.a",
"libirgx.dylib",
"libirgx.so",
"irgx.lib",
"irgx.dll",
] {
let candidate = dir.join(name);
if candidate.is_file() {
println!("cargo:rerun-if-changed={}", candidate.display());
}
}
for lib in system_libs(target) {
println!("cargo:rustc-link-lib=dylib={lib}");
}
match kind {
Kind::Static => {
let staged = PathBuf::from(env("OUT_DIR")).join("link");
let source = dir.join("libirgx.a");
let linkable = if target.ends_with("-msvc") {
"irgx.lib"
} else {
"libirgx.a"
};
std::fs::create_dir_all(&staged)
.and_then(|()| std::fs::copy(&source, staged.join(linkable)))
.unwrap_or_else(|why| {
fail(&format!(
"could not stage {} into {}: {why}",
source.display(),
staged.display()
))
});
println!("cargo:rustc-link-search=native={}", staged.display());
println!("cargo:rustc-link-lib=static=irgx");
},
Kind::Shared => {
println!("cargo:rustc-link-search=native={}", dir.display());
println!("cargo:rustc-link-lib=dylib=irgx");
if !target.contains("-windows") {
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", dir.display());
}
},
}
}
fn library_in(dir: &Path) -> Option<Kind> {
if dir.join("libirgx.a").is_file() {
return Some(Kind::Static);
}
shared_in(dir)
}
fn shared_in(dir: &Path) -> Option<Kind> {
let found = ["libirgx.dylib", "libirgx.so", "irgx.lib", "libirgx.dll.a"]
.iter()
.any(|name| dir.join(name).is_file());
found.then_some(Kind::Shared)
}
fn engine_checkout(crate_dir: &Path) -> Option<PathBuf> {
crate_dir
.ancestors()
.find(|root| root.join("build.zig").is_file())
.map(Path::to_path_buf)
}
fn zig_build(checkout: &Path, target: &str) -> Result<(PathBuf, Kind), String> {
let Some((_, zig_target, zig_cpu)) = ZIG_TRIPLES.iter().find(|(rust, ..)| *rust == target)
else {
return Err(format!(
"and this crate does not know a Zig triple for {target}, so it cannot build \
the engine from source for it either. Add one to ZIG_TRIPLES in build.rs, or \
build the engine yourself and point IRGX_LIB_DIR at it."
));
};
let prefix = PathBuf::from(env("OUT_DIR")).join("engine");
let run = Command::new("zig")
.current_dir(checkout)
.args([
"build",
"-Doptimize=ReleaseFast",
&format!("-Dtarget={zig_target}"),
&format!("-Dcpu={zig_cpu}"),
"--prefix",
])
.arg(&prefix)
.status();
match run {
Ok(status) if status.success() => {},
Ok(status) => {
return Err(format!(
"and `zig build -Dtarget={zig_target}` in {} exited with {status}.",
checkout.display()
));
},
Err(why) => {
return Err(format!(
"and `zig` could not be run ({why}), so the engine cannot be built from \
the source in {}. Install Zig, or build the engine elsewhere and point \
IRGX_LIB_DIR at the directory holding the library.",
checkout.display()
));
},
}
let dir = prefix.join("lib");
match library_in(&dir) {
Some(kind) => Ok((dir, kind)),
None => Err(format!(
"and `zig build` produced no library under {}.",
dir.display()
)),
}
}
fn unserved(target: &str, crate_dir: &Path) -> String {
let mut served: Vec<String> = Vec::new();
if let Ok(entries) = std::fs::read_dir(crate_dir.join("vendor")) {
for entry in entries.flatten() {
if entry.path().join("libirgx.a").is_file() {
served.push(entry.file_name().to_string_lossy().into_owned());
}
}
}
served.sort();
let msvc = if target.ends_with("-msvc") {
" No archive is vendored for any MSVC target and none can be: Zig cross-compiles \
to every other target from one host, but the MSVC C runtime headers are not \
redistributable, so an MSVC archive can only be produced on a machine that has \
Visual Studio. Install Zig beside it and this build script will build the engine \
from source, or use the -pc-windows-gnu target, which is vendored."
} else {
""
};
format!(
"irregex has no prebuilt engine for {target}. This crate vendors an archive for \
{}, and nothing else. There is no pure-Rust fallback: the engine is Zig, so \
either it links or the crate does not build.{msvc}",
if served.is_empty() {
"no target (the vendor directory is empty)".to_owned()
} else {
served.join(", ")
}
)
}
fn env(key: &str) -> String {
std::env::var(key).unwrap_or_else(|_| panic!("cargo did not set {key}"))
}
fn fail(message: &str) -> ! {
panic!("{message}");
}