use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=OFFGRID_NATIVE");
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let llama_dir = {
let p = manifest_dir.join("llama.cpp");
let p = p.canonicalize().unwrap_or(p);
match p.to_str().and_then(|s| s.strip_prefix(r"\\?\")) {
Some(s) => PathBuf::from(s),
None => p,
}
};
println!("cargo:rerun-if-changed={}", llama_dir.join("include/llama.h").display());
println!("cargo:rerun-if-changed={}", llama_dir.join("ggml/include/ggml-backend.h").display());
for header in ["tools/mtmd/mtmd.h", "tools/mtmd/mtmd-helper.h", "ggml/include/ggml.h"] {
println!("cargo:rerun-if-changed={}", llama_dir.join(header).display());
}
let submodule_head = std::fs::read_to_string(llama_dir.join(".git"))
.ok()
.and_then(|s| s.trim().strip_prefix("gitdir:").map(|d| llama_dir.join(d.trim()).join("HEAD")));
if let Some(head) = submodule_head.filter(|h| h.exists()) {
println!("cargo:rerun-if-changed={}", head.display());
}
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
write_bindings(&manifest_dir, &llama_dir, &target_os, &out_dir.join("bindings.rs"));
if env::var_os("OFFGRID_SYS_WRITE_BINDINGS").is_some() {
println!("cargo:rustc-env=OFFGRID_SYS_BACKEND_DIR=");
return;
}
let vulkan = env::var_os("CARGO_FEATURE_VULKAN").is_some();
let cuda = env::var_os("CARGO_FEATURE_CUDA").is_some();
let dynamic = env::var_os("CARGO_FEATURE_DYNAMIC_BACKENDS").is_some();
let native = env::var("OFFGRID_NATIVE").map(|v| v == "1").unwrap_or(false);
let msvc = env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc");
let unix_like = matches!(target_os.as_str(), "linux" | "freebsd" | "netbsd" | "openbsd" | "dragonfly");
let mut cfg = cmake::Config::new(&llama_dir);
cfg.profile("Release");
cfg.define("LLAMA_BUILD_TESTS", "OFF");
cfg.define("LLAMA_BUILD_EXAMPLES", "OFF");
cfg.define("LLAMA_BUILD_TOOLS", "OFF");
cfg.define("LLAMA_BUILD_SERVER", "OFF");
cfg.define("LLAMA_BUILD_APP", "OFF");
cfg.define("LLAMA_BUILD_COMMON", "OFF");
cfg.define("LLAMA_BUILD_MTMD", "ON");
cfg.define("LLAMA_CURL", "OFF");
cfg.define("GGML_OPENMP", "OFF");
cfg.define("GGML_VULKAN", if vulkan { "ON" } else { "OFF" });
cfg.define("GGML_CUDA", if cuda { "ON" } else { "OFF" });
if dynamic {
cfg.define("BUILD_SHARED_LIBS", "ON");
cfg.define("GGML_BACKEND_DL", "ON");
cfg.define("GGML_NATIVE", "OFF");
cfg.define("GGML_CPU_ALL_VARIANTS", "ON");
if unix_like {
cfg.define("CMAKE_INSTALL_RPATH", "$ORIGIN");
cfg.define("CMAKE_BUILD_WITH_INSTALL_RPATH", "ON");
let flags = if using_zig() { "" } else { "-static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL" };
cfg.define("CMAKE_SHARED_LINKER_FLAGS", flags);
cfg.define("CMAKE_MODULE_LINKER_FLAGS", format!("{flags} -Wl,-z,nodelete").trim());
} else if target_os == "windows" && !msvc {
cfg.define("CMAKE_SHARED_LINKER_FLAGS", "-static");
cfg.define("CMAKE_MODULE_LINKER_FLAGS", "-static");
}
} else {
cfg.define("BUILD_SHARED_LIBS", "OFF");
cfg.define("GGML_NATIVE", if native { "ON" } else { "OFF" });
}
if vulkan && target_os == "windows" && !cfg!(windows) {
cross_windows_vulkan(&mut cfg, &manifest_dir);
}
if has_ninja() {
cfg.generator("Ninja");
}
let dst = cfg.build();
if dynamic {
let lib_dir = find_lib(&dst, "llama", true)
.and_then(|p| p.parent().map(Path::to_path_buf))
.expect("offgrid-sys: libllama shared library not found");
println!("cargo:rustc-link-search=native={}", lib_dir.display());
for name in ["mtmd", "llama", "ggml", "ggml-base"] {
println!("cargo:rustc-link-lib=dylib={name}");
}
let backend_dir = find_lib(&dst, "ggml-cpu-x64", true)
.or_else(|| find_lib(&dst, "ggml-cpu", true))
.and_then(|p| p.parent().map(Path::to_path_buf))
.unwrap_or_else(|| lib_dir.clone());
println!("cargo:rustc-env=OFFGRID_SYS_BACKEND_DIR={}", backend_dir.display());
println!("cargo:lib_dir={}", lib_dir.display());
println!("cargo:backend_dir={}", backend_dir.display());
} else {
println!("cargo:rustc-env=OFFGRID_SYS_BACKEND_DIR=");
link_static(&dst, vulkan, cuda, &target_os);
}
}
#[cfg(not(feature = "bindgen"))]
fn write_bindings(manifest_dir: &Path, _llama_dir: &Path, target_os: &str, out: &Path) {
let os = match target_os {
"windows" => "windows",
"linux" => "linux",
other => panic!("offgrid-sys: no pregenerated bindings for {other}; enable the `bindgen` feature"),
};
let src = manifest_dir.join("src/bindings").join(format!("{os}.rs"));
println!("cargo:rerun-if-changed={}", src.display());
std::fs::copy(&src, out).expect("offgrid-sys: failed to copy pregenerated bindings");
}
#[cfg(feature = "bindgen")]
fn write_bindings(_manifest_dir: &Path, llama_dir: &Path, _target_os: &str, out: &Path) {
println!("cargo:rerun-if-env-changed=OFFGRID_SYS_WRITE_BINDINGS");
let bindings = bindgen::Builder::default()
.header_contents("offgrid_sys.h", "#include \"llama.h\"\n#include \"mtmd.h\"\n#include \"mtmd-helper.h\"\n")
.clang_arg(format!("-I{}", llama_dir.join("include").display()))
.clang_arg(format!("-I{}", llama_dir.join("ggml/include").display()))
.clang_arg(format!("-I{}", llama_dir.join("tools/mtmd").display()))
.allowlist_function("llama_.*|ggml_backend_.*|ggml_log_.*|mtmd_.*")
.allowlist_type("llama_.*|ggml_.*|mtmd_.*")
.allowlist_var("LLAMA_.*")
.blocklist_type("FILE|_IO_.*|_iobuf|__off(64)?_t|__uint64_t")
.raw_line("/// C `FILE`, opaque.\n#[repr(C)]\npub struct FILE {\n _private: [u8; 0],\n}")
.formatter(bindgen::Formatter::Prettyplease)
.generate()
.expect("offgrid-sys: bindgen failed to generate bindings");
bindings.write_to_file(out).expect("offgrid-sys: failed to write bindings.rs");
if let Some(dest) = env::var_os("OFFGRID_SYS_WRITE_BINDINGS") {
std::fs::copy(out, dest).expect("offgrid-sys: failed to write OFFGRID_SYS_WRITE_BINDINGS");
}
}
fn cross_windows_vulkan(cfg: &mut cmake::Config, manifest_dir: &Path) {
println!("cargo:rerun-if-env-changed=OFFGRID_VULKAN_INCLUDE");
println!("cargo:rerun-if-env-changed=OFFGRID_GLSLC");
let def = manifest_dir.join("windows/vulkan-1.def");
println!("cargo:rerun-if-changed={}", def.display());
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let implib = out_dir.join("libvulkan-1.a");
let status = Command::new("llvm-dlltool")
.args(["-m", "i386:x86-64", "-d"])
.arg(&def)
.arg("-l")
.arg(&implib)
.status()
.expect("offgrid-sys: llvm-dlltool not found (source scripts/env-llvm-mingw.sh)");
assert!(status.success(), "offgrid-sys: llvm-dlltool failed");
let include = env::var("OFFGRID_VULKAN_INCLUDE").map(PathBuf::from).unwrap_or_else(|_| {
let dst = out_dir.join("vulkan-include");
for sub in ["vulkan", "vk_video", "spirv"] {
let src = Path::new("/usr/include").join(sub);
if src.is_dir() {
copy_dir(&src, &dst.join(sub));
}
}
dst
});
let glslc = env::var("OFFGRID_GLSLC").unwrap_or_else(|_| "glslc".into());
cfg.define("Vulkan_INCLUDE_DIR", include);
cfg.define("Vulkan_LIBRARY", &implib);
cfg.define("Vulkan_GLSLC_EXECUTABLE", which(&glslc).unwrap_or_else(|| glslc.into()));
println!("cargo:rustc-link-search=native={}", out_dir.display());
}
fn copy_dir(src: &Path, dst: &Path) {
std::fs::create_dir_all(dst).expect("create dir");
for entry in std::fs::read_dir(src).expect("read dir").flatten() {
let (from, to) = (entry.path(), dst.join(entry.file_name()));
if from.is_dir() {
copy_dir(&from, &to);
} else {
std::fs::copy(&from, &to).expect("copy header");
}
}
}
fn which(prog: &str) -> Option<PathBuf> {
env::var_os("PATH").and_then(|paths| env::split_paths(&paths).map(|d| d.join(prog)).find(|p| p.is_file()))
}
fn using_zig() -> bool {
let target = env::var("TARGET").unwrap_or_default().replace('-', "_");
["CXX", &format!("CXX_{target}"), "CC", &format!("CC_{target}")]
.iter()
.filter_map(|k| env::var(k).ok())
.any(|v| v.contains("zig"))
}
fn has_ninja() -> bool {
Command::new("ninja").arg("--version").output().map(|o| o.status.success()).unwrap_or(false)
}
fn find_lib(root: &Path, name: &str, shared: bool) -> Option<PathBuf> {
let candidates = if shared {
vec![format!("lib{name}.so"), format!("lib{name}.dylib"), format!("lib{name}.dll.a"), format!("{name}.lib")]
} else {
vec![format!("lib{name}.a"), format!("{name}.lib"), format!("lib{name}.lib"), format!("{name}.a")]
};
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&dir) else { continue };
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if let Some(file_name) = path.file_name().and_then(|s| s.to_str())
&& candidates.iter().any(|c| c == file_name)
{
return Some(path);
}
}
}
None
}
fn find_vendor_archives(root: &Path) -> Vec<PathBuf> {
let mut found = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&dir) else { continue };
for entry in entries.flatten() {
let path = entry.path();
let name = entry.file_name().to_string_lossy().into_owned();
if path.is_dir() {
stack.push(path);
} else if (name.starts_with("libvendor-") && name.ends_with(".a"))
|| (name.starts_with("vendor-") && name.ends_with(".lib"))
{
found.push(path);
}
}
}
found.sort();
found
}
fn link_static(dst: &Path, vulkan: bool, cuda: bool, target_os: &str) {
let mut static_libs: Vec<&str> = vec!["mtmd", "llama", "ggml"];
if vulkan {
static_libs.push("ggml-vulkan");
}
if cuda {
static_libs.push("ggml-cuda");
}
static_libs.push("ggml-cpu");
static_libs.push("ggml-base");
let mut search_dirs: Vec<PathBuf> = Vec::new();
for name in &static_libs {
match find_lib(dst, name, false) {
Some(path) => {
if let Some(dir) = path.parent()
&& !search_dirs.iter().any(|d| d == dir)
{
search_dirs.push(dir.to_path_buf());
}
}
None => {
println!("cargo:warning=offgrid-sys: static library for `{name}` not found under {}", dst.display());
}
}
}
for dir in &search_dirs {
println!("cargo:rustc-link-search=native={}", dir.display());
}
for name in &static_libs {
println!("cargo:rustc-link-lib=static={name}");
}
for archive in find_vendor_archives(&dst.join("build")) {
let stem = archive.file_stem().unwrap().to_string_lossy();
println!("cargo:rustc-link-search=native={}", archive.parent().unwrap().display());
println!("cargo:rustc-link-lib=static={}", stem.trim_start_matches("lib"));
}
match target_os {
"linux" | "freebsd" | "netbsd" | "openbsd" | "dragonfly" => {
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=m");
println!("cargo:rustc-link-lib=pthread");
println!("cargo:rustc-link-lib=dl");
if vulkan {
println!("cargo:rustc-link-lib=vulkan");
}
}
"macos" | "ios" => {
println!("cargo:rustc-link-lib=c++");
if vulkan {
println!("cargo:rustc-link-lib=vulkan");
}
}
"windows" => {
if vulkan {
println!("cargo:rerun-if-env-changed=VULKAN_SDK");
if let Some(sdk) = env::var_os("VULKAN_SDK") {
println!("cargo:rustc-link-search=native={}", Path::new(&sdk).join("Lib").display());
}
println!("cargo:rustc-link-lib=vulkan-1");
}
println!("cargo:rustc-link-lib=userenv");
println!("cargo:rustc-link-lib=ws2_32");
println!("cargo:rustc-link-lib=advapi32");
}
_ => {}
}
}