use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn link_lib_name() -> String {
env::var("CRISPASR_LIB_NAME").unwrap_or_else(|_| "crispasr".to_string())
}
fn add_link_search(dir: &Path) {
println!("cargo:rustc-link-search=native={}", dir.display());
}
fn add_build_dir_search(build_dir: &Path) {
for sub in ["src", "src/Release", "ggml/src", "ggml/src/Release"] {
add_link_search(&build_dir.join(sub));
}
add_link_search(build_dir);
}
fn print_link_lib(lib_name: &str) {
println!("cargo:rustc-link-lib=dylib={lib_name}");
match env::var("CARGO_CFG_TARGET_OS").unwrap_or_default().as_str() {
"linux" => println!("cargo:rustc-link-lib=dylib=stdc++"),
"macos" => println!("cargo:rustc-link-lib=dylib=c++"),
_ => {}
}
}
fn emit_runtime_rpath(build_dir: &Path) {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let lib_subdir = build_dir.join("src");
let lib_subdir_str = lib_subdir.display();
let ggml_subdir = build_dir.join("ggml").join("src");
let ggml_subdir_str = ggml_subdir.display();
match target_os.as_str() {
"macos" => {
println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_subdir_str}");
println!("cargo:rustc-link-arg=-Wl,-rpath,{ggml_subdir_str}");
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks");
println!("cargo:rustc-link-arg=-Wl,-rpath,@loader_path/../Frameworks");
}
"linux" => {
println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_subdir_str}");
println!("cargo:rustc-link-arg=-Wl,-rpath,{ggml_subdir_str}");
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib");
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
}
_ => {} }
}
fn has_built_lib(build_dir: &Path, lib_name: &str) -> bool {
let candidates = [
build_dir.join("src").join(format!("lib{lib_name}.dylib")),
build_dir.join("src").join(format!("lib{lib_name}.so")),
build_dir.join("src").join("libwhisper.dylib"),
build_dir.join("src").join("libwhisper.so"),
build_dir
.join("src")
.join("Release")
.join(format!("{lib_name}.lib")),
build_dir.join("src").join("Release").join("whisper.lib"),
build_dir.join("src").join(format!("{lib_name}.lib")),
build_dir.join("src").join("whisper.lib"),
build_dir.join(format!("lib{lib_name}.dylib")),
build_dir.join(format!("lib{lib_name}.so")),
build_dir.join(format!("{lib_name}.lib")),
];
candidates.iter().any(|p| p.exists())
}
fn try_existing_build(src_root: &Path, lib_name: &str) -> Option<PathBuf> {
if let Ok(dir) = env::var("CRISPASR_SYS_LIB_DIR") {
let path = PathBuf::from(dir);
if has_built_lib(&path, lib_name) {
return Some(path);
}
}
let candidates = [
src_root.join("build-cuda"),
src_root.join("build-vulkan"),
src_root.join("build-metal"),
src_root.join("build-flutter-bundle"),
src_root.join("build"),
];
candidates.into_iter().find(|p| has_built_lib(p, lib_name))
}
fn run(cmd: &mut Command, what: &str) {
let status = cmd
.status()
.unwrap_or_else(|err| panic!("failed to start {what}: {err}"));
if !status.success() {
panic!("{what} failed with status {status}");
}
}
fn tool_available(tool: &str) -> bool {
Command::new(tool)
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn configure_and_build(src_root: &Path) -> PathBuf {
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
let build_dir = out_dir.join("crispasr-build");
let mut configure = Command::new("cmake");
configure
.arg("-S")
.arg(src_root)
.arg("-B")
.arg(&build_dir)
.arg("-DBUILD_SHARED_LIBS=ON")
.arg("-DCRISPASR_BUILD_TESTS=OFF")
.arg("-DCRISPASR_BUILD_EXAMPLES=OFF")
.arg("-DCRISPASR_BUILD_SERVER=OFF")
.arg("-DCMAKE_BUILD_TYPE=Release");
let cache_exists = build_dir.join("CMakeCache.txt").exists();
if !cache_exists && env::var_os("CMAKE_GENERATOR").is_none() && tool_available("ninja") {
configure.arg("-G").arg("Ninja");
}
if env::var_os("CRISPASR_NO_CCACHE").is_none() && tool_available("ccache") {
configure
.arg("-DCMAKE_C_COMPILER_LAUNCHER=ccache")
.arg("-DCMAKE_CXX_COMPILER_LAUNCHER=ccache");
}
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let cross_build = target_arch != std::env::consts::ARCH;
if env::var_os("CRISPASR_FORCE_GGML_NATIVE").is_none()
&& (cross_build || (target_os == "macos" && target_arch == "x86_64"))
{
configure.arg("-DGGML_NATIVE=OFF");
}
configure.arg("-DGGML_CCACHE=OFF");
configure
.arg("-DCRISPASR_OPUS=OFF")
.arg("-DCRISPASR_AMR=OFF");
configure.arg(format!("-DCRISPASR_LIB_OUTPUT_NAME={}", link_lib_name()));
if cfg!(feature = "cuda") {
configure.arg("-DGGML_CUDA=ON");
}
if cfg!(feature = "metal") {
configure.arg("-DGGML_METAL=ON");
configure.arg("-DGGML_METAL_EMBED_LIBRARY=ON");
}
if cfg!(feature = "vulkan") {
configure.arg("-DGGML_VULKAN=ON");
}
run(&mut configure, "cmake configure");
let mut build = Command::new("cmake");
build
.arg("--build")
.arg(&build_dir)
.arg("--config")
.arg("Release")
.arg("--parallel")
.arg("--target")
.arg("crispasr-lib");
run(&mut build, "cmake build");
build_dir
}
fn add_system_lib_search_paths() {
if let Ok(dir) = env::var("CRISPASR_LIB_DIR") {
add_link_search(Path::new(&dir));
}
for d in &[
"/opt/homebrew/lib",
"/usr/local/lib",
"/usr/lib",
"/usr/lib/x86_64-linux-gnu",
"/usr/lib/aarch64-linux-gnu",
] {
if Path::new(d).is_dir() {
add_link_search(Path::new(d));
}
}
}
fn main() {
println!("cargo:rerun-if-env-changed=CRISPASR_SYS_LIB_DIR");
println!("cargo:rerun-if-env-changed=CRISPASR_LIB_DIR");
println!("cargo:rerun-if-env-changed=CRISPASR_LIB_NAME");
println!("cargo:rerun-if-env-changed=CRISPASR_FORCE_GGML_NATIVE");
if env::var_os("DOCS_RS").is_some() {
return;
}
let lib_name = link_lib_name();
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let src_root = manifest_dir
.parent()
.expect("crispasr-sys must live inside the CrispASR repo root");
let legacy_system_install = env::var("CRISPASR_LIB_DIR").is_ok()
&& env::var("CRISPASR_SYS_LIB_DIR").is_err()
&& try_existing_build(src_root, &lib_name).is_none();
if legacy_system_install {
add_system_lib_search_paths();
print_link_lib(&lib_name);
return;
}
if let Some(build_dir) = try_existing_build(src_root, &lib_name) {
add_build_dir_search(&build_dir);
emit_runtime_rpath(&build_dir);
println!("cargo:LIB_DIR={}", build_dir.display());
print_link_lib(&lib_name);
return;
}
if !src_root.join("CMakeLists.txt").exists() {
panic!(
"crispasr-sys: no prebuilt libcrispasr found and the CrispASR C/C++ \
sources are not present at {} (expected a CMakeLists.txt).\n\
When depending on this crate from crates.io, either:\n \
• set CRISPASR_LIB_DIR to a directory holding a prebuilt \
libcrispasr, or\n \
• depend on it via git so build.rs can build it from source:\n \
crispasr = {{ git = \"https://github.com/CrispStrobe/CrispASR\" }}",
src_root.display()
);
}
let build_dir = configure_and_build(src_root);
add_build_dir_search(&build_dir);
emit_runtime_rpath(&build_dir);
println!("cargo:LIB_DIR={}", build_dir.display());
print_link_lib(&lib_name);
}