use cmake::Config;
use glob::glob;
use patch_apply::{Line, Patch};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, fs};
#[cfg(feature = "prebuilt")]
mod prebuilt_download;
macro_rules! debug_log {
($($arg:tt)*) => {
if std::env::var("BUILD_DEBUG").is_ok() {
println!("cargo:warning=[DEBUG] {}", format!($($arg)*));
}
};
}
fn get_cargo_target_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
let profile = std::env::var("PROFILE")?;
let runtime_path_variables: &[&str] = if cfg!(target_os = "windows") {
&["PATH"]
} else if cfg!(target_os = "macos") {
&["DYLD_FALLBACK_LIBRARY_PATH", "DYLD_LIBRARY_PATH"]
} else {
&["LD_LIBRARY_PATH"]
};
for variable in runtime_path_variables {
let Some(paths) = std::env::var_os(variable) else {
continue;
};
for path in std::env::split_paths(&paths) {
if path
.file_name()
.is_some_and(|name| name == std::ffi::OsStr::new(&profile))
{
return Ok(path);
}
if path.file_name().is_some_and(|name| name == "deps") {
if let Some(parent) = path.parent() {
if parent
.file_name()
.is_some_and(|name| name == std::ffi::OsStr::new(&profile))
{
return Ok(parent.to_path_buf());
}
}
}
}
}
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?);
let mut target_dir = None;
let mut sub_path = out_dir.as_path();
while let Some(parent) = sub_path.parent() {
if parent.ends_with(&profile) {
target_dir = Some(parent);
break;
}
sub_path = parent;
}
let target_dir = target_dir.ok_or("not found")?;
Ok(target_dir.to_path_buf())
}
fn patches_hash(patches_dir: &Path) -> String {
if !patches_dir.is_dir() {
return String::new();
}
let mut entries: Vec<_> = std::fs::read_dir(patches_dir)
.map(|rd| rd.filter_map(|e| e.ok()).map(|e| e.path()).collect())
.unwrap_or_default();
entries.sort();
let mut hasher_val: u64 = 0xcbf29ce484222325; for path in &entries {
if path.extension().map(|e| e == "patch").unwrap_or(false) {
if let Ok(bytes) = std::fs::read(path) {
for &b in &bytes {
hasher_val ^= b as u64;
hasher_val = hasher_val.wrapping_mul(0x100000001b3);
}
}
}
}
format!("{:016x}", hasher_val)
}
fn resolve_patch_cmd() -> PathBuf {
for var in ["LLAMA_PATCH", "PATCH"] {
if let Ok(raw) = env::var(var) {
let trimmed = raw.trim();
if !trimmed.is_empty() {
let candidate = PathBuf::from(trimmed.trim_matches('"'));
if candidate.exists() {
println!(
"cargo:warning=Using patch binary from {var}: {}",
candidate.display()
);
return candidate;
}
println!(
"cargo:warning={var} was set to '{}' but that path does not exist",
candidate.display()
);
}
}
}
if command_exists("patch") {
return PathBuf::from("patch");
}
if cfg!(windows) {
let mut candidates: Vec<PathBuf> = Vec::new();
let mut push_unique = |p: PathBuf| {
if !candidates.iter().any(|c| c == &p) {
candidates.push(p);
}
};
if let Ok(output) = Command::new("where").arg("git").output() {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let git = PathBuf::from(line.trim());
if let Some(git_dir) = git.parent() {
if let Some(git_root) = git_dir.parent() {
push_unique(git_root.join("usr").join("bin").join("patch.exe"));
}
}
}
}
}
if let Some(program_files) = env::var_os("ProgramFiles") {
push_unique(PathBuf::from(&program_files).join("Git\\usr\\bin\\patch.exe"));
}
if let Some(program_files_x86) = env::var_os("ProgramFiles(x86)") {
push_unique(PathBuf::from(&program_files_x86).join("Git\\usr\\bin\\patch.exe"));
}
if let Some(local_app_data) = env::var_os("LOCALAPPDATA") {
push_unique(PathBuf::from(&local_app_data).join("Programs\\Git\\usr\\bin\\patch.exe"));
}
push_unique(PathBuf::from("C:\\Program Files\\Git\\usr\\bin\\patch.exe"));
if let Some(found) = candidates.iter().find(|p| p.exists()).cloned() {
println!("cargo:warning=Using patch binary at {}", found.display());
return found;
}
let searched = candidates
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ");
panic!(
"could not locate `patch.exe` on PATH or common Git-for-Windows locations. \
Set LLAMA_PATCH (or PATCH) to the full path to patch.exe, or add Git\\usr\\bin to PATH. \
Searched: [{searched}]"
);
}
panic!(
"could not locate `patch` on PATH. Install patch, or set LLAMA_PATCH (or PATCH) to the full path to the patch binary"
)
}
fn find_sequence(lines: &[&str], pattern: &[&str], start_at: usize) -> Option<usize> {
if pattern.is_empty() {
return Some(start_at.min(lines.len()));
}
if pattern.len() > lines.len() {
return None;
}
(start_at..=lines.len() - pattern.len()).find(|&i| lines[i..i + pattern.len()] == *pattern)
}
fn hunk_side_pattern<'a>(hunk: &'a patch_apply::Hunk<'a>, new_side: bool) -> Vec<&'a str> {
let mut out = Vec::new();
for line in &hunk.lines {
match line {
Line::Context(s) => out.push(*s),
Line::Remove(s) if !new_side => out.push(*s),
Line::Add(s) if new_side => out.push(*s),
_ => {}
}
}
out
}
fn patch_matches_side(lines: &[&str], patch: &Patch<'_>, new_side: bool) -> bool {
let mut cursor = 0usize;
for hunk in &patch.hunks {
let pattern = hunk_side_pattern(hunk, new_side);
let Some(pos) = find_sequence(lines, &pattern, cursor) else {
return false;
};
let consumed = if new_side {
hunk.new_range.count as usize
} else {
hunk.old_range.count as usize
};
cursor = pos.saturating_add(consumed);
}
true
}
fn reanchor_patch_to_old_side(lines: &[&str], patch: &mut Patch<'_>) -> bool {
let mut cursor = 0usize;
for hunk in &mut patch.hunks {
let pattern = hunk_side_pattern(hunk, false);
let Some(pos) = find_sequence(lines, &pattern, cursor) else {
return false;
};
hunk.old_range.start = pos as u64 + 1;
cursor = pos.saturating_add(hunk.old_range.count as usize);
}
true
}
fn strip_patch_path(path: &str, strip_components: usize) -> Option<PathBuf> {
let parts: Vec<&str> = path.split('/').collect();
if parts.len() <= strip_components {
return None;
}
let rel = PathBuf::from(parts[strip_components..].join("/"));
if rel.is_absolute() {
return None;
}
if rel.components().any(|c| {
matches!(
c,
std::path::Component::ParentDir
| std::path::Component::RootDir
| std::path::Component::Prefix(_)
)
}) {
return None;
}
Some(rel)
}
fn patch_target_path(dst: &Path, patch: &Patch<'_>) -> Result<PathBuf, String> {
let raw = if patch.new.path.as_ref() != "/dev/null" {
patch.new.path.as_ref()
} else {
patch.old.path.as_ref()
};
let rel = strip_patch_path(raw, 1)
.ok_or_else(|| format!("unsupported patch path after -p1 stripping: '{raw}'"))?;
Ok(dst.join(rel))
}
fn patch_entries(patches_dir: &Path) -> Vec<PathBuf> {
if !patches_dir.is_dir() {
return Vec::new();
}
let mut entries: Vec<_> = std::fs::read_dir(patches_dir)
.expect("failed to read patches dir")
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| {
path.extension()
.is_some_and(|extension| extension == "patch")
})
.collect();
entries.sort();
entries
}
fn patches_match_new_side(patches_dir: &Path, dst: &Path) -> Result<bool, String> {
for patch_path in patch_entries(patches_dir) {
let patch_text = fs::read_to_string(&patch_path)
.map_err(|error| format!("failed to read patch {}: {error}", patch_path.display()))?;
let patches = Patch::from_multiple(&patch_text)
.map_err(|error| format!("failed to parse patch {}: {error}", patch_path.display()))?;
for patch in patches {
let file_path = patch_target_path(dst, &patch)?;
let creates_file = patch.old.path.as_ref() == "/dev/null";
let deletes_file = patch.new.path.as_ref() == "/dev/null";
let current = match fs::read_to_string(&file_path) {
Ok(contents) => contents,
Err(error) if error.kind() == std::io::ErrorKind::NotFound && deletes_file => {
continue;
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound && creates_file => {
return Ok(false);
}
Err(error) => {
return Err(format!(
"failed to read patched target {}: {error}",
file_path.display()
));
}
};
let current_lines: Vec<&str> = current.lines().collect();
if !patch_matches_side(¤t_lines, &patch, true) {
return Ok(false);
}
}
}
Ok(true)
}
fn apply_patches_via_rust(entries: &[PathBuf], dst: &Path) -> Result<(), String> {
for patch_path in entries {
println!(
"cargo:warning=Applying patch (rust): {}",
patch_path.display()
);
let patch_text = fs::read_to_string(patch_path)
.map_err(|e| format!("failed to read patch {}: {e}", patch_path.display()))?;
let patches = Patch::from_multiple(&patch_text)
.map_err(|e| format!("failed to parse patch {}: {e}", patch_path.display()))?;
for patch in patches {
let file_path = patch_target_path(dst, &patch)?;
let creates_file = patch.old.path.as_ref() == "/dev/null";
let deletes_file = patch.new.path.as_ref() == "/dev/null";
let current = match fs::read_to_string(&file_path) {
Ok(s) => s,
Err(_e) if creates_file => String::new(),
Err(e) => {
return Err(format!(
"failed to read target file {}: {e}",
file_path.display()
));
}
};
let current_lines: Vec<&str> = current.lines().collect();
if patch_matches_side(¤t_lines, &patch, true) {
println!(
"cargo:warning=Patch already applied (rust): {}",
file_path.display()
);
continue;
}
let mut anchored_patch = patch.clone();
if reanchor_patch_to_old_side(¤t_lines, &mut anchored_patch) {
let updated = patch_apply::apply(current.clone(), anchored_patch);
if updated != current {
if deletes_file {
if let Err(e) = fs::remove_file(&file_path) {
return Err(format!(
"failed to delete patched file {}: {e}",
file_path.display()
));
}
} else {
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).map_err(|e| {
format!(
"failed to create parent dir for {}: {e}",
file_path.display()
)
})?;
}
fs::write(&file_path, updated).map_err(|e| {
format!("failed to write patched file {}: {e}", file_path.display())
})?;
}
}
continue;
}
return Err(format!(
"patch hunk mismatch for {} while applying {}",
file_path.display(),
patch_path.display()
));
}
}
Ok(())
}
fn apply_patches_via_cli(entries: &[PathBuf], dst: &Path) -> Result<(), String> {
let patch_cmd = resolve_patch_cmd();
for patch in entries {
println!("cargo:warning=Applying patch (cli): {}", patch.display());
let status = Command::new(&patch_cmd)
.arg("-p1")
.arg("--forward")
.arg("--directory")
.arg(dst)
.arg("--input")
.arg(patch)
.status()
.map_err(|e| {
format!(
"failed to run '{}' for {}: {e}",
patch_cmd.display(),
patch.display()
)
})?;
if !status.success() {
return Err(format!(
"patch command failed for {} with status {}",
patch.display(),
status
));
}
}
Ok(())
}
fn apply_patches(patches_dir: &Path, dst: &Path) {
let entries = patch_entries(patches_dir);
if entries.is_empty() {
return;
}
let engine = env::var("LLAMA_PATCH_ENGINE")
.map(|v| v.to_ascii_lowercase())
.unwrap_or_else(|_| "rust".to_string());
let res = match engine.as_str() {
"cli" => apply_patches_via_cli(&entries, dst),
"rust" => apply_patches_via_rust(&entries, dst),
other => Err(format!(
"unsupported LLAMA_PATCH_ENGINE='{other}', expected 'rust' or 'cli'"
)),
};
if let Err(err) = res {
panic!(
"Patch application failed using engine '{engine}': {err}. \
The patch may need rebasing against the current llama.cpp submodule commit."
);
}
}
fn stage_active_patches(patches_dir: &Path, staged_dir: &Path) -> bool {
if staged_dir.exists() {
std::fs::remove_dir_all(staged_dir).expect("failed to clear staged llama.cpp patches");
}
std::fs::create_dir_all(staged_dir).expect("failed to create staged llama.cpp patches");
let always_active = [
"0003-exact-speculative-state.patch",
"0004-exact-decode-lifecycle-hooks.patch",
"0005-fail-closed-eagle3-process.patch",
];
for name in always_active {
let source = patches_dir.join(name);
assert!(
source.is_file(),
"required llama.cpp patch is absent: {}",
source.display()
);
std::fs::copy(&source, staged_dir.join(name))
.unwrap_or_else(|error| panic!("failed to stage {name}: {error}"));
}
if cfg!(feature = "q1") {
let name = "0001-q1-quantization.patch";
let source = patches_dir.join(name);
if source.exists() {
std::fs::copy(&source, staged_dir.join(name))
.unwrap_or_else(|error| panic!("failed to stage {name}: {error}"));
}
}
true
}
fn llama_src_version(src: &Path, patches_dir: &Path) -> String {
const PATCH_STAGING_VERSION: &str = "2";
let ph = patches_hash(patches_dir);
let git_file = src.join(".git");
if git_file.is_file() {
if let Ok(text) = std::fs::read_to_string(&git_file) {
if let Some(rel) = text.strip_prefix("gitdir:").map(str::trim) {
let head_path = git_file.parent().unwrap().join(rel).join("HEAD");
if let Ok(head) = std::fs::read_to_string(&head_path) {
let head = head.trim();
if head.starts_with("ref:") {
let ref_path = head.strip_prefix("ref:").map(str::trim).unwrap_or(head);
let commit_path = git_file.parent().unwrap().join(rel).join(ref_path);
if let Ok(hash) = std::fs::read_to_string(commit_path) {
return format!("{}:{}:{PATCH_STAGING_VERSION}", hash.trim(), ph);
}
}
return format!("{}:{}:{PATCH_STAGING_VERSION}", head, ph);
}
}
}
}
let base = src
.join("CMakeLists.txt")
.metadata()
.and_then(|m| m.modified())
.map(|t| format!("{t:?}"))
.unwrap_or_else(|_| "unknown".to_owned());
format!(
"{}:{}:{PATCH_STAGING_VERSION}",
base,
patches_hash(patches_dir)
)
}
fn copy_folder(src: &Path, dst: &Path) {
let parent = dst
.parent()
.expect("destination for the llama.cpp copy has no parent directory");
std::fs::create_dir_all(parent).expect("Failed to create dst parent directory");
let staging = parent.join(".llama.cpp.copy-tmp");
if staging.exists() {
std::fs::remove_dir_all(&staging).expect("failed to clear the stale copy staging dir");
}
let status = if cfg!(unix) {
std::process::Command::new("cp")
.arg("-rf")
.arg(src)
.arg(&staging)
.status()
.expect("Failed to execute cp command")
} else {
std::process::Command::new("robocopy.exe")
.arg("/e")
.arg("/nfl")
.arg("/ndl")
.arg("/njh")
.arg("/njs")
.arg(src)
.arg(&staging)
.status()
.expect("Failed to execute robocopy command")
};
let ok = if cfg!(windows) {
status.code().is_some_and(|c| c < 8)
} else {
status.success()
};
assert!(
ok,
"copying {} to {} failed ({status}). The source tree may have been \
changing underneath the copy — a concurrent `git checkout` of the \
llama.cpp submodule will do it.",
src.display(),
staging.display()
);
for required in [
"CMakeLists.txt",
"ggml/src/ggml-version.h.in",
"src/llama-version.h.in",
] {
assert!(
staging.join(required).exists(),
"the copy of llama.cpp is missing `{required}`, so it is incomplete. \
If {} is the vendored submodule, check it is fully checked out; if \
it is a `target/package/...` tree, the file is missing from the \
`include` list in llama-cpp-sys-4/Cargo.toml.",
src.display()
);
}
if dst.exists() {
std::fs::remove_dir_all(dst).expect("failed to remove the previous llama.cpp copy");
}
std::fs::rename(&staging, dst).expect("failed to move the llama.cpp copy into place");
}
fn extract_lib_names(out_dir: &Path, build_shared_libs: bool, target: &str) -> Vec<String> {
let lib_pattern = if target.contains("windows-msvc") {
"*.lib"
} else if target.contains("windows") {
"*.a"
} else if target.contains("apple") {
if build_shared_libs {
"*.dylib"
} else {
"*.a"
}
} else if build_shared_libs {
"*.so"
} else {
"*.a"
};
let libs_dir = out_dir.join("lib*");
let pattern = libs_dir.join(lib_pattern);
debug_log!("Extract libs {}", pattern.display());
let mut lib_names: Vec<String> = Vec::new();
for entry in glob(pattern.to_str().unwrap()).unwrap() {
match entry {
Ok(path) => {
let stem = path.file_stem().unwrap();
let stem_str = stem.to_str().unwrap();
let stem_str = if target.contains("windows")
&& !target.contains("msvc")
&& stem_str.ends_with(".dll")
{
&stem_str[..stem_str.len() - 4]
} else {
stem_str
};
let lib_name = if stem_str.starts_with("lib") {
stem_str.strip_prefix("lib").unwrap_or(stem_str)
} else {
stem_str
};
lib_names.push(lib_name.to_string());
}
Err(e) => println!("cargo:warning=error={}", e),
}
}
lib_names
}
fn make_shared_libs_loader_relative(lib_dirs: &[PathBuf], target: &str) {
if !target.contains("apple") {
return;
}
for dir in lib_dirs {
let Ok(entries) = std::fs::read_dir(dir) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if !path.is_file() || path.symlink_metadata().is_ok_and(|m| m.is_symlink()) {
continue;
}
if path.extension().is_none_or(|e| e != "dylib") {
continue;
}
let Some(filename) = path.file_name().and_then(|f| f.to_str()) else {
continue;
};
run_install_name_tool(&["-id", &format!("@loader_path/{filename}")], &path);
for dep in otool_rpath_dependencies(&path) {
let Some(base) = dep.strip_prefix("@rpath/") else {
continue;
};
run_install_name_tool(&["-change", &dep, &format!("@loader_path/{base}")], &path);
}
}
}
}
fn otool_rpath_dependencies(path: &Path) -> Vec<String> {
let Ok(output) = std::process::Command::new("otool")
.arg("-L")
.arg(path)
.output()
else {
return Vec::new();
};
if !output.status.success() {
return Vec::new();
}
String::from_utf8_lossy(&output.stdout)
.lines()
.skip(1) .filter_map(|line| line.split_whitespace().next())
.filter(|dep| dep.starts_with("@rpath/"))
.map(ToOwned::to_owned)
.collect()
}
fn run_install_name_tool(args: &[&str], path: &Path) {
match std::process::Command::new("install_name_tool")
.args(args)
.arg(path)
.output()
{
Ok(output) if output.status.success() => {}
Ok(output) => {
debug_log!(
"install_name_tool {:?} on {} failed: {}",
args,
path.display(),
String::from_utf8_lossy(&output.stderr).trim()
);
}
Err(error) => {
println!(
"cargo:warning=install_name_tool not runnable ({error}); \
directly executed binaries may fail to find the llama.cpp dylibs"
);
}
}
}
fn extract_lib_assets(out_dir: &Path, target: &str) -> Vec<PathBuf> {
let shared_lib_pattern = if target.contains("windows") {
"*.dll"
} else if target.contains("apple") {
"*.dylib"
} else {
"*.so*"
};
let shared_libs_dir = if target.contains("windows") {
"bin"
} else {
"lib"
};
let libs_dir = out_dir.join(shared_libs_dir);
let pattern = libs_dir.join(shared_lib_pattern);
debug_log!("Extract lib assets {}", pattern.display());
let mut files = Vec::new();
for entry in glob(pattern.to_str().unwrap()).unwrap() {
match entry {
Ok(path) => {
files.push(path);
}
Err(e) => eprintln!("cargo:warning=error={}", e),
}
}
files
}
fn extract_prebuilt_lib_names(
prebuilt_root: &Path,
use_shared_libs: bool,
target: &str,
) -> Vec<String> {
let lib_pattern = if target.contains("windows-msvc") {
"*.lib"
} else if target.contains("windows") {
"*.a"
} else if target.contains("apple") {
if use_shared_libs {
"*.dylib"
} else {
"*.a"
}
} else if use_shared_libs {
"*.so"
} else {
"*.a"
};
let mut lib_names = Vec::new();
for dir in [
prebuilt_root.to_path_buf(),
prebuilt_root.join("lib"),
prebuilt_root.join("lib64"),
prebuilt_root.join("bin"),
] {
if !dir.exists() {
continue;
}
let pattern = dir.join(lib_pattern);
let pattern_s = match pattern.to_str() {
Some(v) => v,
None => continue,
};
for entry in glob(pattern_s).unwrap() {
match entry {
Ok(path) => {
let stem = match path.file_stem().and_then(|s| s.to_str()) {
Some(v) => v,
None => continue,
};
let stem = if target.contains("windows")
&& !target.contains("msvc")
&& stem.ends_with(".dll")
{
&stem[..stem.len() - 4]
} else {
stem
};
let lib_name = if let Some(stripped) = stem.strip_prefix("lib") {
stripped
} else {
stem
};
if !lib_names.iter().any(|n| n == lib_name) {
lib_names.push(lib_name.to_string());
}
}
Err(e) => eprintln!("cargo:warning=error={}", e),
}
}
}
lib_names
}
fn extract_prebuilt_shared_assets(prebuilt_root: &Path, target: &str) -> Vec<PathBuf> {
let shared_pattern = if target.contains("windows") {
"*.dll"
} else if target.contains("apple") {
"*.dylib"
} else {
"*.so*"
};
let mut files = Vec::new();
for dir in [
prebuilt_root.to_path_buf(),
prebuilt_root.join("lib"),
prebuilt_root.join("lib64"),
prebuilt_root.join("bin"),
] {
if !dir.exists() {
continue;
}
let pattern = dir.join(shared_pattern);
let pattern_s = match pattern.to_str() {
Some(v) => v,
None => continue,
};
for entry in glob(pattern_s).unwrap() {
match entry {
Ok(path) => {
if !files.iter().any(|p| p == &path) {
files.push(path);
}
}
Err(e) => eprintln!("cargo:warning=error={}", e),
}
}
}
files
}
fn macos_link_search_path(clang_binary: &str) -> Option<String> {
let output = Command::new(clang_binary)
.arg("--print-search-dirs")
.output()
.ok()?;
if !output.status.success() {
println!(
"failed to run '{clang_binary} --print-search-dirs', continuing without a link search path"
);
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("libraries: =") {
let path = line.split('=').nth(1)?;
return Some(format!("{}/lib/darwin", path));
}
}
println!("failed to determine link search path, continuing without it");
None
}
fn cmake_system_name(target: &str) -> &'static str {
if target.contains("-android") || target.contains("android-") {
"Android"
} else if target.contains("-apple-ios") {
"iOS"
} else if target.contains("-apple-") {
"Darwin"
} else if target.contains("-windows") {
"Windows"
} else if target.contains("-linux") {
"Linux"
} else {
"Linux"
}
}
fn mingw_compiler(target: &str, cxx: bool) -> Option<String> {
if !target.contains("windows-gnu") {
return None;
}
let arch = if target.contains("x86_64") {
"x86_64"
} else if target.contains("i686") || target.contains("i586") {
"i686"
} else if target.contains("aarch64") {
"aarch64"
} else {
target.split('-').next()?
};
let compiler = if target.contains("gnullvm") {
if cxx {
"clang++"
} else {
"clang"
}
} else {
if cxx {
"g++"
} else {
"gcc"
}
};
Some(format!("{}-w64-mingw32-{}", arch, compiler))
}
fn command_exists(cmd: &str) -> bool {
Command::new(cmd)
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn compile_shims(manifest_dir: &Path, llama_dst: &Path) {
const SHIM_DIRS: &[&str] = &["shim_support", "ext_shim", "mtp_shim", "chat_shim", "common_shim"];
let mut build = cc::Build::new();
build
.cpp(true)
.std("c++17")
.include(llama_dst.join("include"))
.include(llama_dst.join("ggml/include"))
.include(llama_dst.join("src"))
.include(llama_dst.join("common"))
.warnings(false);
let mut any = false;
for name in SHIM_DIRS {
let shim_dir = manifest_dir.join(name);
let src = shim_dir.join(format!("{name}.cpp"));
if !src.exists() {
continue;
}
build.file(&src).include(&shim_dir);
println!("cargo:rerun-if-changed={}", src.display());
println!(
"cargo:rerun-if-changed={}",
shim_dir.join(format!("{name}.h")).display()
);
any = true;
}
if any {
build.compile("llama_shims");
}
}
fn apple_vulkan_available() -> bool {
let has_glslc = command_exists("glslc");
let sdk = match env::var("VULKAN_SDK") {
Ok(v) => PathBuf::from(v),
Err(_) => return false,
};
let header_ok = sdk.join("include").join("vulkan").join("vulkan.h").exists();
let lib_dir = sdk.join("lib");
let lib_ok = lib_dir.join("libvulkan.dylib").exists()
|| lib_dir.join("libvulkan.1.dylib").exists()
|| lib_dir.join("libMoltenVK.dylib").exists();
has_glslc && header_ok && lib_ok
}
fn cmake_system_processor(target: &str) -> String {
let arch = target.split('-').next().unwrap_or("unknown");
match arch {
"x86_64" => "x86_64".to_owned(),
"i686" | "i386" => "x86".to_owned(),
"aarch64" | "arm64" => "aarch64".to_owned(),
"armv7" | "armv7s" | "armv7k" => "armv7-a".to_owned(),
"arm" => "arm".to_owned(),
"riscv64gc" | "riscv64" => "riscv64".to_owned(),
"powerpc64le" => "ppc64le".to_owned(),
"powerpc64" => "ppc64".to_owned(),
"s390x" => "s390x".to_owned(),
"wasm32" => "wasm32".to_owned(),
other => other.to_owned(),
}
}
fn find_child_dir_ci(parent: &Path, name: &str) -> Option<PathBuf> {
let lower = name.to_ascii_lowercase();
std::fs::read_dir(parent)
.ok()?
.filter_map(|e| e.ok())
.map(|e| e.path())
.find(|p| {
p.is_dir()
&& p.file_name()
.map(|n| n.to_string_lossy().to_ascii_lowercase() == lower)
.unwrap_or(false)
})
}
fn find_file_ci(dir: &Path, name: &str) -> Option<PathBuf> {
let lower = name.to_ascii_lowercase();
std::fs::read_dir(dir)
.ok()?
.filter_map(|e| e.ok())
.map(|e| e.path())
.find(|p| {
p.is_file()
&& p.file_name()
.map(|n| n.to_string_lossy().to_ascii_lowercase() == lower)
.unwrap_or(false)
})
}
fn find_glslc(sdk: &Path) -> Option<PathBuf> {
let exe_name = if cfg!(windows) { "glslc.exe" } else { "glslc" };
if let Some(bin_dir) = find_child_dir_ci(sdk, "bin") {
if let Some(found) = find_file_ci(&bin_dir, exe_name) {
return Some(found);
}
}
if let Some(found) = find_file_recursive(sdk, exe_name, 3) {
return Some(found);
}
if let Ok(output) = std::process::Command::new("where").arg(exe_name).output() {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(line) = stdout.lines().next() {
let p = PathBuf::from(line.trim());
if p.exists() {
return Some(p);
}
}
}
}
if let Ok(output) = std::process::Command::new("which").arg(exe_name).output() {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(line) = stdout.lines().next() {
let p = PathBuf::from(line.trim());
if p.exists() {
return Some(p);
}
}
}
}
None
}
fn find_file_recursive(dir: &Path, name: &str, max_depth: u32) -> Option<PathBuf> {
if max_depth == 0 {
return None;
}
let lower = name.to_ascii_lowercase();
let entries: Vec<_> = std::fs::read_dir(dir)
.ok()?
.filter_map(|e| e.ok())
.map(|e| e.path())
.collect();
for p in &entries {
if p.is_file()
&& p.file_name()
.map(|n| n.to_string_lossy().to_ascii_lowercase() == lower)
.unwrap_or(false)
{
return Some(p.clone());
}
}
for p in &entries {
if p.is_dir() {
if let Some(found) = find_file_recursive(p, name, max_depth - 1) {
return Some(found);
}
}
}
None
}
fn find_vulkan_sdk_windows() -> Option<PathBuf> {
let is_valid = |p: &Path| -> bool { p.join("Lib").join("vulkan-1.lib").exists() };
if let Ok(sdk) = env::var("VULKAN_SDK") {
let p = PathBuf::from(&sdk);
if is_valid(&p) {
return Some(p);
}
debug_log!(
"VULKAN_SDK env var is set to '{}' but Lib/vulkan-1.lib was not found there; \
trying automatic detection",
sdk
);
}
#[cfg(windows)]
{
use winreg::enums::*;
use winreg::RegKey;
if let Ok(hklm) =
RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey("SOFTWARE\\LunarG\\VulkanSDK")
{
let mut candidates: Vec<(String, PathBuf)> = Vec::new();
for name in hklm.enum_keys().filter_map(Result::ok) {
if let Ok(ver_key) = hklm.open_subkey(&name) {
if let Ok(install_dir) = ver_key.get_value::<String, _>("InstallDir") {
let p = PathBuf::from(&install_dir);
if is_valid(&p) {
candidates.push((name, p));
}
}
}
}
candidates.sort_by(|a, b| b.0.cmp(&a.0));
if let Some((_, p)) = candidates.into_iter().next() {
return Some(p);
}
}
}
let vulkan_base = PathBuf::from("C:\\VulkanSDK");
if vulkan_base.is_dir() {
let mut versions: Vec<PathBuf> = std::fs::read_dir(&vulkan_base)
.ok()
.into_iter()
.flatten()
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.is_dir() && is_valid(p))
.collect();
versions.sort_by(|a, b| b.file_name().cmp(&a.file_name()));
if let Some(p) = versions.into_iter().next() {
return Some(p);
}
}
None
}
fn find_libgomp_lib_dir() -> Option<String> {
if let Ok(out) = Command::new("gcc")
.arg("--print-file-name=libgomp.a")
.output()
{
if out.status.success() {
let path = String::from_utf8_lossy(&out.stdout).trim().to_owned();
if let Some(parent) = std::path::Path::new(&path).parent() {
if parent != std::path::Path::new("") {
return Some(parent.to_string_lossy().into_owned());
}
}
}
}
for version in ["16", "15", "14", "13", "12", "11"] {
let p = format!("/usr/lib/gcc/x86_64-linux-gnu/{}", version);
if std::path::Path::new(&p).join("libgomp.a").exists() {
return Some(p);
}
}
None
}
fn resolve_prebuilt_directory(target: &str, use_shared_libs: bool) -> Option<PathBuf> {
if let Ok(raw) = env::var("LLAMA_PREBUILT_DIR") {
if !raw.is_empty() {
return Some(PathBuf::from(raw));
}
}
#[cfg(feature = "prebuilt")]
{
prebuilt_download::ensure_prebuilt(target, use_shared_libs)
}
#[cfg(not(feature = "prebuilt"))]
{
let _ = (target, use_shared_libs);
None
}
}
fn main() {
let start_time = std::time::Instant::now();
let target = env::var("TARGET").unwrap();
let host = env::var("HOST").unwrap();
let is_cross = host != target;
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let target_dir = get_cargo_target_dir().unwrap();
let llama_dst = out_dir.join("llama.cpp");
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("Failed to get CARGO_MANIFEST_DIR");
let llama_src = Path::new(&manifest_dir).join("llama.cpp");
let build_shared_libs = cfg!(feature = "dynamic-link");
let build_shared_libs = std::env::var("LLAMA_BUILD_SHARED_LIBS")
.map(|v| v == "1")
.unwrap_or(build_shared_libs);
let profile = env::var("LLAMA_LIB_PROFILE").unwrap_or("Release".to_string());
let static_crt = env::var("LLAMA_STATIC_CRT")
.map(|v| v == "1")
.unwrap_or(false);
let crt_static = env::var("CARGO_CFG_TARGET_FEATURE")
.map(|f| f.contains("crt-static"))
.unwrap_or(false);
let cmake_out_dir: PathBuf = if target.contains("windows") {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
out_dir.to_string_lossy().as_ref().hash(&mut hasher);
let hash = hasher.finish() as u32;
let base = std::env::var("LOCALAPPDATA")
.or_else(|_| std::env::var("TEMP"))
.or_else(|_| std::env::var("TMP"))
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("C:\\Temp"));
let short_dir = base.join("llcb").join(format!("{:08x}", hash));
std::fs::create_dir_all(&short_dir)
.expect("Failed to create short cmake output dir (Windows MAX_PATH workaround)");
short_dir
} else {
out_dir.clone()
};
let cmake_out_dir: PathBuf = if !target.contains("windows") {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let src_ver = {
let patches_dir_tmp = Path::new(&manifest_dir).join("patches");
llama_src_version(&llama_src, &patches_dir_tmp)
};
let cpp_features = format!(
"cuda={},metal={},vulkan={},webgpu={},blas={},opencl={},hip={},openmp={},rpc={},q1={},mtmd={},native={},shared={}",
cfg!(feature = "cuda"),
cfg!(feature = "metal"),
cfg!(feature = "vulkan"),
cfg!(feature = "webgpu"),
cfg!(feature = "blas"),
cfg!(feature = "opencl"),
cfg!(feature = "hip"),
cfg!(feature = "openmp"),
cfg!(feature = "rpc"),
cfg!(feature = "q1"),
cfg!(feature = "mtmd"),
cfg!(feature = "native"),
build_shared_libs,
);
let mut hasher = DefaultHasher::new();
src_ver.hash(&mut hasher);
cpp_features.hash(&mut hasher);
target.hash(&mut hasher);
let hash = hasher.finish();
let shared = target_dir
.parent() .unwrap_or(&target_dir)
.join("llama-cmake-cache")
.join(format!("{:016x}", hash));
std::fs::create_dir_all(&shared).expect("failed to create shared cmake cache dir");
debug_log!("Shared cmake dir: {}", shared.display());
shared
} else {
cmake_out_dir };
debug_log!("HOST: {}", host);
debug_log!("TARGET: {}", target);
debug_log!("CROSS_COMPILING: {}", is_cross);
debug_log!("CARGO_MANIFEST_DIR: {}", manifest_dir);
debug_log!("TARGET_DIR: {}", target_dir.display());
debug_log!("OUT_DIR: {}", out_dir.display());
debug_log!("LD_LIBRARY_PATH: {:?}", env::var_os("LD_LIBRARY_PATH"));
debug_log!("BUILD_SHARED: {}", build_shared_libs);
let patches_dir = Path::new(&manifest_dir).join("patches");
let sentinel = out_dir.join(".llama-src-version");
let current_version = llama_src_version(&llama_src, &patches_dir);
let stored_version = std::fs::read_to_string(&sentinel).unwrap_or_default();
let staged_dir = out_dir.join("patches-active");
let staged_any = stage_active_patches(&patches_dir, &staged_dir);
let patches_applied = !staged_any
|| patches_match_new_side(&staged_dir, &llama_dst).unwrap_or_else(|error| {
debug_log!(
"Failed to verify the staged patch postcondition for {}: {}",
llama_dst.display(),
error
);
false
});
let needs_copy =
!llama_dst.exists() || stored_version.trim() != current_version.trim() || !patches_applied;
if needs_copy {
if llama_dst.exists() {
debug_log!(
"Source version or patch postcondition changed — removing stale OUT_DIR copy"
);
std::fs::remove_dir_all(&llama_dst).ok();
}
debug_log!("Copy {} to {}", llama_src.display(), llama_dst.display());
copy_folder(&llama_src, &llama_dst);
if staged_any {
apply_patches(&staged_dir, &llama_dst);
if !patches_match_new_side(&staged_dir, &llama_dst)
.unwrap_or_else(|error| panic!("failed to verify applied patches: {error}"))
{
panic!(
"llama.cpp patch application completed without satisfying the staged postcondition"
);
}
}
std::fs::write(&sentinel, ¤t_version)
.expect("failed to write source version sentinel");
}
let submodule_git = llama_src.join(".git");
if submodule_git.is_file() {
if let Ok(contents) = std::fs::read_to_string(&submodule_git) {
if let Some(gitdir) = contents.strip_prefix("gitdir:").map(|s| s.trim()) {
let head = submodule_git.parent().unwrap().join(gitdir).join("HEAD");
if head.exists() {
println!("cargo:rerun-if-changed={}", head.display());
}
}
}
}
if patches_dir.is_dir() {
println!("cargo:rerun-if-changed={}", patches_dir.display());
}
unsafe {
env::set_var(
"CMAKE_BUILD_PARALLEL_LEVEL",
std::thread::available_parallelism()
.unwrap()
.get()
.to_string(),
)
};
if cfg!(feature = "mpi") && target.contains("apple") {
unsafe { env::set_var("CC", "/opt/homebrew/bin/mpicc") };
unsafe { env::set_var("CXX", "/opt/homebrew/bin/mpicxx") };
}
let mut builder = bindgen::Builder::default()
.header("wrapper.h")
.generate_comments(true)
.clang_arg("-xc++")
.clang_arg("-std=c++17")
.clang_arg(format!("--target={}", target))
.clang_arg(format!("-I{}", llama_dst.join("include").display()))
.clang_arg(format!("-I{}", llama_dst.join("ggml/include").display()))
.clang_arg(format!("-I{}", llama_dst.join("src").display()))
.clang_arg(format!("-I{}", llama_dst.join("common").display()))
.clang_arg(format!(
"-I{}",
Path::new(&manifest_dir).join("mtp_shim").display()
))
.clang_arg(format!(
"-I{}",
Path::new(&manifest_dir).join("ext_shim").display()
))
.clang_arg(format!(
"-I{}",
Path::new(&manifest_dir).join("chat_shim").display()
))
.clang_arg(format!(
"-I{}",
Path::new(&manifest_dir).join("common_shim").display()
))
.clang_arg(format!(
"-I{}",
Path::new(&manifest_dir).join("shim_support").display()
))
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.derive_partialeq(true)
.no_partialeq("__sFILE")
.no_partialeq("ggml_cplan")
.no_partialeq("ggml_type_traits")
.no_partialeq("ggml_type_traits_cpu")
.no_partialeq("ggml_context")
.no_partialeq("ggml_opt_params")
.no_partialeq("llama_model_params")
.no_partialeq("llama_context_params")
.no_partialeq("llama_sampler_i")
.no_partialeq("llama_opt_params")
.allowlist_function("ggml_.*")
.allowlist_type("ggml_.*")
.allowlist_function("llama_.*")
.allowlist_function("llama_lora_.*")
.allowlist_type("llama_.*")
.allowlist_function("mtp_session_.*")
.allowlist_type("mtp_session")
.allowlist_type("mtp_session_config")
.allowlist_type("mtp_state_status")
.allowlist_function("llama_memory_breakdown_collect")
.allowlist_type("llama_memory_breakdown_entry")
.allowlist_function("llama_quant_.*_guarded")
.allowlist_function("common_device_memory_collect")
.allowlist_type("common_device_memory_flat_entry")
.allowlist_type("mtp_spec_type")
.allowlist_item("MTP_SPEC_TYPE_.*")
.allowlist_item("MTP_STATE_STATUS_.*")
.opaque_type("mtp_session")
.allowlist_function("chat_shim_.*")
.allowlist_type("chat_shim_.*")
.allowlist_item("CHAT_SHIM_.*")
.opaque_type("chat_shim_templates")
.allowlist_function("common_json_schema_to_grammar_c")
.allowlist_function("llama_shim_last_error")
.allowlist_type("llama_shim_status")
.allowlist_item("LLAMA_SHIM_.*")
.allowlist_function("common_shim_.*")
.allowlist_type("common_shim_.*")
.allowlist_item("COMMON_SHIM_.*")
.opaque_type("common_shim_sampler")
.opaque_type("common_shim_sampler_params")
.opaque_type("common_shim_ngram_cache")
.opaque_type("common_shim_ngram_map")
.allowlist_function("common_token_to_piece")
.allowlist_function("common_tokenize")
.allowlist_function("common_fit_params")
.allowlist_function("common_fit_print")
.allowlist_function("common_memory_breakdown_print")
.allowlist_type("common_params_fit_status")
.allowlist_item("LLAMA_.*")
.opaque_type("llama_grammar")
.opaque_type("llama_grammar_parser")
.opaque_type("llama_sampler_chain")
.opaque_type("std::.*");
println!(
"cargo:rerun-if-changed={}",
Path::new(&manifest_dir).join("chat_shim").display()
);
println!(
"cargo:rerun-if-changed={}",
Path::new(&manifest_dir).join("common_shim").display()
);
println!(
"cargo:rerun-if-changed={}",
Path::new(&manifest_dir).join("shim_support").display()
);
if cfg!(feature = "rpc") {
builder = builder
.clang_arg("-DRPC_SUPPORT")
.allowlist_function("ggml_backend_rpc_.*")
.allowlist_type("ggml_backend_rpc_.*")
.allowlist_item("GGML_RPC_.*");
}
if cfg!(feature = "mtmd") {
builder = builder
.clang_arg("-DMTMD_SUPPORT")
.clang_arg(format!("-I{}", llama_dst.join("tools/mtmd").display()))
.allowlist_function("mtmd_.*")
.allowlist_type("mtmd_.*")
.allowlist_item("MTMD_.*")
.blocklist_type("mtmd_helper::.*")
.blocklist_item("mtmd_helper::.*")
.no_partialeq("mtmd_context_params");
}
let bindings = builder
.use_core()
.prepend_enum_name(false)
.generate()
.expect("Failed to generate bindings");
let bindings_path = out_dir.join("bindings.rs");
bindings
.write_to_file(bindings_path.clone())
.expect("Failed to write bindings");
let contents = std::fs::read_to_string(bindings_path.clone()).unwrap();
let contents = contents.replace("unsafe extern \"C\" {", " extern \"C\" {");
fs::write(bindings_path, contents).unwrap();
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-env-changed=LLAMA_PREBUILT_DIR");
println!("cargo:rerun-if-env-changed=LLAMA_PREBUILT_SHARED");
println!("cargo:rerun-if-env-changed=LLAMA_PREBUILT_TAG");
println!("cargo:rerun-if-env-changed=LLAMA_PREBUILT_REPO");
println!("cargo:rerun-if-env-changed=LLAMA_PREBUILT_URL");
println!("cargo:rerun-if-env-changed=LLAMA_PREBUILT_OFF");
println!("cargo:rerun-if-env-changed=LLAMA_PATCH_ENGINE");
println!("cargo:rerun-if-env-changed=LLAMA_PATCH");
println!("cargo:rerun-if-env-changed=PATCH");
#[cfg(feature = "prebuilt")]
println!("cargo:rustc-cfg=llama_prebuilt_enabled");
debug_log!("Bindings Created");
if std::env::var("BUILD_DEBUG").is_ok() {
println!(
"cargo:warning=[BUILD] Build configuration completed in {:?}",
start_time.elapsed()
);
}
let prebuilt_dir = if staged_any {
if let Ok(path) = env::var("LLAMA_PREBUILT_DIR") {
panic!(
"LLAMA_PREBUILT_DIR='{path}' cannot be verified against the active llama.cpp patches; use the source build"
);
}
if cfg!(feature = "prebuilt") {
println!(
"cargo:warning=The active llama.cpp patches have no prebuilt identity envelope; falling back to the verified source build"
);
}
None
} else {
resolve_prebuilt_directory(&target, build_shared_libs)
};
if let Some(prebuilt_dir) = prebuilt_dir {
if prebuilt_dir.exists() {
let use_shared_libs = env::var("LLAMA_PREBUILT_SHARED")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("on"))
.unwrap_or(build_shared_libs);
println!(
"cargo:warning=Using prebuilt llama libs from {}",
prebuilt_dir.display()
);
for p in [
prebuilt_dir.clone(),
prebuilt_dir.join("lib"),
prebuilt_dir.join("lib64"),
prebuilt_dir.join("bin"),
] {
if p.exists() {
println!("cargo:rustc-link-search={}", p.display());
}
}
let llama_libs_kind = if use_shared_libs { "dylib" } else { "static" };
let llama_libs = extract_prebuilt_lib_names(&prebuilt_dir, use_shared_libs, &target);
if llama_libs.is_empty() {
panic!(
"LLAMA_PREBUILT_DIR was set to '{}' but no linkable libraries were found",
prebuilt_dir.display()
);
}
for lib in llama_libs {
println!("cargo:rustc-link-lib={llama_libs_kind}={lib}");
}
if cfg!(feature = "openmp") && (target.contains("gnu") || target.contains("musl")) {
println!("cargo:rustc-link-lib=gomp");
}
if target.contains("apple") {
println!("cargo:rustc-link-lib=framework=Foundation");
if cfg!(feature = "metal") {
println!("cargo:rustc-link-lib=framework=Metal");
println!("cargo:rustc-link-lib=framework=MetalKit");
}
println!("cargo:rustc-link-lib=framework=Accelerate");
println!("cargo:rustc-link-lib=c++");
}
if target.contains("linux") {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
if target.contains("windows") && !target.contains("msvc") {
println!("cargo:rustc-link-lib=static=stdc++");
println!("cargo:rustc-link-lib=static=winpthread");
}
if target.contains("windows") {
println!("cargo:rustc-link-lib=advapi32");
}
if target.contains("apple") {
let clang_bin = env::var("CC").unwrap_or_else(|_| "clang".to_owned());
if let Some(path) = macos_link_search_path(&clang_bin) {
println!("cargo:rustc-link-lib=clang_rt.osx");
println!("cargo:rustc-link-search={}", path);
}
}
if use_shared_libs {
make_shared_libs_loader_relative(
&[
prebuilt_dir.clone(),
prebuilt_dir.join("lib"),
prebuilt_dir.join("lib64"),
prebuilt_dir.join("bin"),
],
&target,
);
let libs_assets = extract_prebuilt_shared_assets(&prebuilt_dir, &target);
for asset in libs_assets {
let filename = asset
.file_name()
.and_then(|f| f.to_str())
.expect("invalid prebuilt asset file name");
let src = std::fs::canonicalize(&asset).unwrap_or_else(|_| asset.clone());
for dst in [
target_dir.join(filename),
target_dir.join("examples").join(filename),
target_dir.join("deps").join(filename),
] {
if let Some(parent) = dst.parent() {
let _ = std::fs::create_dir_all(parent);
}
if !dst.exists() {
let _ = std::fs::hard_link(&src, &dst)
.or_else(|_| std::fs::copy(&src, &dst).map(|_| ()));
}
}
}
}
compile_shims(Path::new(&manifest_dir), &llama_dst);
return;
}
if env::var("LLAMA_PREBUILT_DIR").is_ok() {
panic!(
"LLAMA_PREBUILT_DIR was set to '{}' but that path does not exist",
prebuilt_dir.display()
);
}
println!(
"cargo:warning=Prebuilt path '{}' is missing; falling back to local compile",
prebuilt_dir.display()
);
}
if std::env::var("BUILD_DEBUG").is_ok() {
println!("cargo:warning=[BUILD] Starting CMake build...");
}
let mut config = Config::new(&llama_dst);
if command_exists("ninja") {
debug_log!("Ninja detected, using Ninja generator for CMake");
config.generator("Ninja");
let parallel = std::thread::available_parallelism().unwrap().get();
config.build_arg(format!("-j{}", parallel));
} else {
let parallel = std::thread::available_parallelism().unwrap().get();
config.build_arg(format!("-j{}", parallel));
}
if target.contains("apple") && cfg!(feature = "openmp") {
use std::path::Path;
let omp_prefix = if Path::new("/opt/homebrew/opt/libomp").exists() {
"/opt/homebrew/opt/libomp"
} else if Path::new("/usr/local/opt/libomp").exists() {
"/usr/local/opt/libomp"
} else {
println!("cargo:warning=libomp not found in Homebrew default locations. Please install libomp via Homebrew.");
""
};
if !omp_prefix.is_empty() {
println!("cargo:rustc-link-search=native={}/lib", omp_prefix);
println!("cargo:rustc-link-lib=dylib=omp");
config.cflag(format!("-I{}/include", omp_prefix));
config.cxxflag(format!("-I{}/include", omp_prefix));
config.env("LDFLAGS", format!("-L{}/lib", omp_prefix));
config.env("DYLD_LIBRARY_PATH", format!("{}/lib", omp_prefix));
}
}
if env::var("LLAMA_NO_SCCACHE").as_deref() != Ok("1") {
let sccache = env::var("SCCACHE_PATH")
.ok()
.map(PathBuf::from)
.filter(|p| p.exists())
.or_else(|| {
let exe = if cfg!(windows) {
"sccache.exe"
} else {
"sccache"
};
env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.map(|d| d.join(exe))
.find(|p| p.exists())
})
});
if let Some(sc) = sccache {
debug_log!("sccache found at {}", sc.display());
config.define("CMAKE_C_COMPILER_LAUNCHER", sc.to_str().unwrap());
config.define("CMAKE_CXX_COMPILER_LAUNCHER", sc.to_str().unwrap());
}
}
if target.contains("linux") && command_exists("mold") {
debug_log!("Using mold linker for faster linking");
config.define("CMAKE_EXE_LINKER_FLAGS", "-fuse-ld=mold");
config.define("CMAKE_SHARED_LINKER_FLAGS", "-fuse-ld=mold");
config.define("CMAKE_MODULE_LINKER_FLAGS", "-fuse-ld=mold");
}
config.define("LLAMA_BUILD_TESTS", "OFF");
config.define("LLAMA_BUILD_EXAMPLES", "OFF");
config.define("LLAMA_BUILD_SERVER", "OFF");
config.define("LLAMA_BUILD_APP", "OFF");
config.define("CMAKE_SKIP_INSTALL_RPATH", "ON");
config.define("CMAKE_SKIP_RPATH", "ON");
if profile != "Debug" {
config.define("CMAKE_C_FLAGS_RELEASE", "-O3 -DNDEBUG");
config.define("CMAKE_CXX_FLAGS_RELEASE", "-O3 -DNDEBUG");
}
if profile != "Debug" {
config.define("CMAKE_DISABLE_FIND_PACKAGE_Doxygen", "ON");
config.define("CMAKE_DISABLE_FIND_PACKAGE_Python", "ON");
config.define("CMAKE_DISABLE_FIND_PACKAGE_Git", "ON");
}
config.define("LLAMA_BUILD_COMMON", "ON");
if cfg!(feature = "mtmd") {
config.define("LLAMA_BUILD_TOOLS", "ON");
} else {
config.define("LLAMA_BUILD_TOOLS", "OFF");
}
config.define(
"BUILD_SHARED_LIBS",
if build_shared_libs { "ON" } else { "OFF" },
);
if is_cross && !target.contains("android") {
let system_name = cmake_system_name(&target);
let system_processor = cmake_system_processor(&target);
debug_log!("Cross-compiling: CMAKE_SYSTEM_NAME={system_name} CMAKE_SYSTEM_PROCESSOR={system_processor}");
config.define("CMAKE_SYSTEM_NAME", system_name);
config.define("CMAKE_SYSTEM_PROCESSOR", &system_processor);
config.define("CMAKE_CROSSCOMPILING", "TRUE");
if target.contains("apple") {
let osx_arch = if target.contains("aarch64") || target.contains("arm64") {
"arm64"
} else if target.contains("x86_64") {
"x86_64"
} else if target.contains("i686") {
"i386"
} else {
target.split('-').next().unwrap_or("arm64")
};
config.define("CMAKE_OSX_ARCHITECTURES", osx_arch);
debug_log!("Apple cross-arch: CMAKE_OSX_ARCHITECTURES={osx_arch}");
if let Ok(sdk) = env::var("CMAKE_OSX_SYSROOT") {
config.define("CMAKE_OSX_SYSROOT", &sdk);
}
if let Ok(cc) = env::var("CC") {
config.define("CMAKE_C_COMPILER", &cc);
}
if let Ok(cxx) = env::var("CXX") {
config.define("CMAKE_CXX_COMPILER", &cxx);
}
} else {
if let Ok(cc) = env::var("CC") {
config.define("CMAKE_C_COMPILER", &cc);
} else if let Some(cc) = mingw_compiler(&target, false) {
config.define("CMAKE_C_COMPILER", &cc);
} else if !target.contains("windows-msvc") {
config.define("CMAKE_C_COMPILER", format!("{}-gcc", target));
}
if let Ok(cxx) = env::var("CXX") {
config.define("CMAKE_CXX_COMPILER", &cxx);
} else if let Some(cxx) = mingw_compiler(&target, true) {
config.define("CMAKE_CXX_COMPILER", &cxx);
} else if !target.contains("windows-msvc") {
config.define("CMAKE_CXX_COMPILER", format!("{}-g++", target));
}
if let Ok(sysroot) = env::var("CMAKE_SYSROOT") {
config.define("CMAKE_SYSROOT", &sysroot);
}
}
}
if target.contains("apple") && !is_cross {
let osx_arch = if target.contains("aarch64") || target.contains("arm64") {
"arm64"
} else if target.contains("x86_64") {
"x86_64"
} else {
target.split('-').next().unwrap_or("arm64")
};
config.define("CMAKE_OSX_ARCHITECTURES", osx_arch);
debug_log!("Apple native: CMAKE_OSX_ARCHITECTURES={osx_arch}");
}
let want_native = cfg!(feature = "native") && !is_cross;
if is_cross {
config.define("GGML_NATIVE", "OFF");
} else if want_native {
config.define("GGML_NATIVE", "ON");
} else {
config.define("GGML_NATIVE", "OFF");
}
let is_arm_target = target.starts_with("aarch64") || target.starts_with("arm");
if is_arm_target && !want_native && !target.contains("android") {
if env::var("GGML_CPU_ARM_ARCH").is_err() {
config.define("GGML_CPU_ARM_ARCH", "armv8-a");
}
}
let is_x86_target =
target.starts_with("x86_64") || target.starts_with("i686") || target.starts_with("i586");
if is_x86_target && !want_native {
config.define("GGML_SSE42", "ON");
config.define("GGML_AVX", "OFF");
config.define("GGML_AVX2", "OFF");
config.define("GGML_AVX_VNNI", "OFF");
config.define("GGML_FMA", "OFF");
config.define("GGML_F16C", "OFF");
config.define("GGML_BMI2", "OFF");
config.define("GGML_AVX512", "OFF");
config.define("GGML_AVX512_VBMI", "OFF");
config.define("GGML_AVX512_VNNI", "OFF");
config.define("GGML_AVX512_BF16", "OFF");
config.define("GGML_AMX_TILE", "OFF");
config.define("GGML_AMX_INT8", "OFF");
config.define("GGML_AMX_BF16", "OFF");
}
if target.contains("windows") && target.starts_with("arm") && !target.starts_with("aarch64") {
config.define("GGML_OPENMP", "OFF");
}
if target.contains("windows-msvc") {
config.static_crt(static_crt);
}
if target.contains("android") && target.contains("aarch64") {
let android_ndk = env::var("ANDROID_NDK")
.expect("Please install Android NDK and ensure that ANDROID_NDK env variable is set");
config.define(
"CMAKE_TOOLCHAIN_FILE",
format!("{android_ndk}/build/cmake/android.toolchain.cmake"),
);
config.define("ANDROID_ABI", "arm64-v8a");
config.define("ANDROID_PLATFORM", "android-28");
config.define("CMAKE_SYSTEM_PROCESSOR", "arm64");
config.define("CMAKE_C_FLAGS", "-march=armv8.7a");
config.define("CMAKE_CXX_FLAGS", "-march=armv8.7a");
config.define("GGML_OPENMP", "OFF");
config.define("GGML_LLAMAFILE", "OFF");
}
let mut enable_metal = cfg!(feature = "metal");
if cfg!(feature = "vulkan") {
if target.contains("apple") && !apple_vulkan_available() {
println!(
"cargo:warning=Vulkan SDK not found or incomplete on macOS (need VULKAN_SDK, Vulkan headers/library, and glslc). Falling back to Metal backend."
);
config.define("GGML_VULKAN", "OFF");
enable_metal = true;
} else {
config.define("GGML_VULKAN", "ON");
}
if target.contains("windows") {
let vulkan_path = find_vulkan_sdk_windows()
.expect("Could not find Vulkan SDK. Please install it from https://vulkan.lunarg.com/sdk/home and either set the VULKAN_SDK environment variable or install to the default C:\\VulkanSDK\\ location.");
debug_log!("Vulkan SDK: {}", vulkan_path.display());
let vulkan_lib_path =
find_child_dir_ci(&vulkan_path, "lib").unwrap_or_else(|| vulkan_path.join("Lib"));
println!("cargo:rustc-link-search={}", vulkan_lib_path.display());
println!("cargo:rustc-link-lib=vulkan-1");
config.define("VULKAN_SDK", vulkan_path.to_str().unwrap());
unsafe { env::set_var("VULKAN_SDK", &vulkan_path) };
if let Some(inc) = find_child_dir_ci(&vulkan_path, "include") {
config.define("Vulkan_INCLUDE_DIR", inc.to_str().unwrap());
}
if let Some(lib) = find_file_ci(&vulkan_lib_path, "vulkan-1.lib") {
config.define("Vulkan_LIBRARY", lib.to_str().unwrap());
}
if let Some(glslc) = find_glslc(&vulkan_path) {
config.define("Vulkan_GLSLC_EXECUTABLE", glslc.to_str().unwrap());
}
let spirv_headers_dir = vulkan_path.join("SPIRV-Headers");
if spirv_headers_dir.join("SPIRV-HeadersConfig.cmake").exists() {
config.define("SPIRV-Headers_DIR", spirv_headers_dir.to_str().unwrap());
}
config.define("CMAKE_PREFIX_PATH", vulkan_path.to_str().unwrap());
}
if target.contains("linux") {
println!("cargo:rustc-link-lib=vulkan");
}
}
if enable_metal {
config.define("GGML_METAL", "ON");
} else {
config.define("GGML_METAL", "OFF");
}
if cfg!(feature = "webgpu") {
config.define("GGML_WEBGPU", "ON");
} else {
config.define("GGML_WEBGPU", "OFF");
}
if cfg!(feature = "blas") {
config.define("GGML_BLAS", "ON");
} else {
config.define("GGML_BLAS", "OFF");
}
if cfg!(feature = "opencl") {
config.define("GGML_OPENCL", "ON");
} else {
config.define("GGML_OPENCL", "OFF");
}
if cfg!(feature = "hip") {
config.define("GGML_HIP", "ON");
} else {
config.define("GGML_HIP", "OFF");
}
if cfg!(feature = "cuda") && !cfg!(target_os = "macos") {
config.define("GGML_CUDA", "ON");
}
if cfg!(feature = "openmp") {
config.define("GGML_OPENMP", "ON");
} else {
config.define("GGML_OPENMP", "OFF");
}
if cfg!(feature = "mpi") {
config.define("LLAMA_MPI", "ON");
}
if cfg!(feature = "rpc") {
config.define("GGML_RPC", "ON");
}
config
.out_dir(&cmake_out_dir)
.profile(&profile)
.very_verbose(std::env::var("CMAKE_VERBOSE").is_ok()) .always_configure(false);
{
let cmake_build_dir = cmake_out_dir.join("build");
let cache = cmake_build_dir.join("CMakeCache.txt");
if cache.exists() {
let has_makefile = cmake_build_dir.join("Makefile").exists();
let has_ninja = cmake_build_dir.join("build.ninja").exists();
if !has_makefile && !has_ninja {
debug_log!(
"CMakeCache.txt exists but no Makefile/build.ninja found — \
removing cache to force reconfiguration"
);
std::fs::remove_file(&cache).expect("failed to remove stale CMakeCache.txt");
} else {
let desired_native_str = if want_native { "ON" } else { "OFF" };
let cache_contents = std::fs::read_to_string(&cache).unwrap_or_default();
let cached_native_on = cache_contents.contains("GGML_NATIVE:BOOL=ON");
let cached_native_off = cache_contents.contains("GGML_NATIVE:BOOL=OFF");
let native_mismatch =
(want_native && cached_native_off) || (!want_native && cached_native_on);
let common_mismatch = !cache_contents.contains("LLAMA_BUILD_COMMON:BOOL=ON");
let is_arm_target_local =
target.starts_with("aarch64") || target.starts_with("arm");
let we_set_arm_arch = is_arm_target_local
&& !want_native
&& !target.contains("android")
&& env::var("GGML_CPU_ARM_ARCH").is_err();
let cached_arm_arch = cache_contents
.lines()
.find(|l| l.starts_with("GGML_CPU_ARM_ARCH:"))
.and_then(|l| l.split_once('=').map(|x| x.1))
.unwrap_or("");
let arm_arch_mismatch = we_set_arm_arch && cached_arm_arch != "armv8-a";
let is_x86_target_local = target.starts_with("x86_64")
|| target.starts_with("i686")
|| target.starts_with("i586");
let x86_isa_mismatch = is_x86_target_local && !want_native && {
let stale_options = [
"GGML_AVX:BOOL=ON",
"GGML_AVX2:BOOL=ON",
"GGML_FMA:BOOL=ON",
"GGML_F16C:BOOL=ON",
"GGML_BMI2:BOOL=ON",
];
stale_options.iter().any(|opt| cache_contents.contains(opt))
};
let metal_mismatch = {
let cached_metal_on = cache_contents.contains("GGML_METAL:BOOL=ON");
let cached_metal_off = cache_contents.contains("GGML_METAL:BOOL=OFF");
(enable_metal && cached_metal_off) || (!enable_metal && cached_metal_on)
};
let webgpu_mismatch = {
let cached_webgpu_on = cache_contents.contains("GGML_WEBGPU:BOOL=ON");
let cached_webgpu_off = cache_contents.contains("GGML_WEBGPU:BOOL=OFF");
(cfg!(feature = "webgpu") && cached_webgpu_off)
|| (!cfg!(feature = "webgpu") && cached_webgpu_on)
};
let osx_arch_mismatch = target.contains("apple") && {
let want_arch = if target.contains("aarch64") || target.contains("arm64") {
"arm64"
} else if target.contains("x86_64") {
"x86_64"
} else {
""
};
if !want_arch.is_empty() {
cache_contents
.lines()
.find(|l| l.starts_with("CMAKE_OSX_ARCHITECTURES:"))
.and_then(|l| l.split_once('=').map(|x| x.1))
.map(|cached| cached != want_arch)
.unwrap_or(false)
} else {
false
}
};
let mismatch = native_mismatch
|| common_mismatch
|| arm_arch_mismatch
|| x86_isa_mismatch
|| metal_mismatch
|| webgpu_mismatch
|| osx_arch_mismatch;
if mismatch {
debug_log!(
"CMakeCache.txt is stale (GGML_NATIVE: cache={} want={}; \
LLAMA_BUILD_COMMON mismatch={}; \
GGML_CPU_ARM_ARCH: cache={:?} want={}) — removing cache \
to force reconfiguration",
if cached_native_on {
"ON"
} else if cached_native_off {
"OFF"
} else {
"?"
},
desired_native_str,
common_mismatch,
cached_arm_arch,
if we_set_arm_arch {
"armv8-a"
} else {
"(not set)"
},
);
std::fs::remove_file(&cache).expect("failed to remove stale CMakeCache.txt");
}
}
}
}
let build_dir = config.build();
println!(
"cargo:rustc-link-search={}",
cmake_out_dir.join("lib").display()
);
println!(
"cargo:rustc-link-search={}",
cmake_out_dir.join("lib64").display()
);
println!("cargo:rustc-link-search={}", build_dir.display());
let llama_libs_kind = if build_shared_libs { "dylib" } else { "static" };
let llama_libs = extract_lib_names(&cmake_out_dir, build_shared_libs, &target);
assert_ne!(llama_libs.len(), 0);
for lib in &llama_libs {
debug_log!(
"LINK {}",
format!("cargo:rustc-link-lib={}={}", llama_libs_kind, lib)
);
println!("cargo:rustc-link-lib={llama_libs_kind}={lib}");
}
if !build_shared_libs {
let common_build_dir = cmake_out_dir.join("build").join("common");
if common_build_dir.exists() && !llama_libs.iter().any(|l| l == "llama-common-base") {
println!(
"cargo:rustc-link-search=native={}",
common_build_dir.display()
);
println!("cargo:rustc-link-lib=static=llama-common-base");
}
}
compile_shims(Path::new(&manifest_dir), &llama_dst);
let cmake_cache_path = cmake_out_dir.join("build").join("CMakeCache.txt");
let openmp_enabled_in_cmake = std::fs::read_to_string(&cmake_cache_path)
.map(|contents| contents.contains("GGML_OPENMP_ENABLED:INTERNAL=ON"))
.unwrap_or(false);
if (cfg!(feature = "openmp") || openmp_enabled_in_cmake)
&& (target.contains("gnu") || target.contains("musl"))
{
if crt_static && target.contains("linux") {
if let Some(gcc_lib) = find_libgomp_lib_dir() {
println!("cargo:rustc-link-search=native={}", gcc_lib);
}
println!("cargo:rustc-link-lib=static=gomp");
} else {
println!("cargo:rustc-link-lib=gomp");
}
}
if target.contains("apple") {
println!("cargo:rustc-link-lib=framework=Foundation");
if enable_metal {
println!("cargo:rustc-link-lib=framework=Metal");
println!("cargo:rustc-link-lib=framework=MetalKit");
}
println!("cargo:rustc-link-lib=framework=Accelerate");
println!("cargo:rustc-link-lib=c++");
}
if target.contains("linux") {
if crt_static {
println!("cargo:rustc-link-lib=static=stdc++");
} else {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
}
if target.contains("windows") && !target.contains("msvc") {
println!("cargo:rustc-link-lib=static=stdc++");
println!("cargo:rustc-link-lib=static=winpthread");
}
if target.contains("windows") {
println!("cargo:rustc-link-lib=advapi32");
}
if target.contains("apple") {
let clang_bin = env::var("CC").unwrap_or_else(|_| "clang".to_owned());
if let Some(path) = macos_link_search_path(&clang_bin) {
println!("cargo:rustc-link-lib=clang_rt.osx");
println!("cargo:rustc-link-search={}", path);
}
}
if build_shared_libs {
make_shared_libs_loader_relative(
&[
cmake_out_dir.join("lib"),
cmake_out_dir.join("lib64"),
build_dir.clone(),
],
&target,
);
let libs_assets = extract_lib_assets(&cmake_out_dir, &target);
for asset in libs_assets {
let asset_clone = asset.clone();
let filename = asset_clone.file_name().unwrap();
let filename = filename.to_str().unwrap();
let asset = std::fs::canonicalize(&asset).unwrap_or(asset);
let force_hard_link = |src: &Path, dst: &Path| {
if let Some(parent) = dst.parent() {
std::fs::create_dir_all(parent).unwrap_or_else(|error| {
panic!(
"Failed to create shared-library destination {}: {error}",
parent.display()
)
});
}
if dst.symlink_metadata().is_ok() {
let _ = std::fs::remove_file(dst);
}
if let Err(e) = std::fs::hard_link(src, dst) {
debug_log!(
"Hard link failed ({:?}), falling back to copy: {} -> {}",
e,
src.display(),
dst.display()
);
if let Err(copy_err) = std::fs::copy(src, dst) {
panic!("Failed to copy file after hard link failed: {:?}. Original hard link error: {:?}", copy_err, e);
}
}
};
let dst = target_dir.join(filename);
debug_log!("HARD LINK {} TO {}", asset.display(), dst.display());
force_hard_link(&asset, &dst);
if target_dir.join("examples").exists() {
let dst = target_dir.join("examples").join(filename);
debug_log!("HARD LINK {} TO {}", asset.display(), dst.display());
force_hard_link(&asset, &dst);
}
let dst = target_dir.join("deps").join(filename);
debug_log!("HARD LINK {} TO {}", asset.display(), dst.display());
force_hard_link(&asset, &dst);
}
}
if std::env::var("BUILD_DEBUG").is_ok() {
println!(
"cargo:warning=[BUILD] Build completed successfully in {:?}",
start_time.elapsed()
);
println!("cargo:warning=[BUILD] Libraries built: {:?}", llama_libs);
}
}