fn main() {
#[cfg(feature = "llm-llamacpp")]
compile_llama_cpp();
}
#[cfg(feature = "llm-llamacpp")]
fn check_cmake_available() -> bool {
std::process::Command::new("cmake")
.arg("--version")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
#[cfg(feature = "llm-llamacpp")]
fn cmake_install_instructions() -> &'static str {
if cfg!(target_os = "macos") {
"Install CMake:\n brew install cmake"
} else if cfg!(target_os = "linux") {
"Install CMake:\n Ubuntu/Debian: sudo apt install cmake\n Fedora: sudo dnf install cmake\n Arch: sudo pacman -S cmake"
} else if cfg!(target_os = "windows") {
"Install CMake:\n choco install cmake\n or download from https://cmake.org/download/"
} else {
"Install CMake from https://cmake.org/download/"
}
}
#[cfg(feature = "llm-llamacpp")]
struct NdkDetectionResult {
ndk_path: Option<String>,
tried_paths: Vec<String>,
}
#[cfg(feature = "llm-llamacpp")]
fn find_android_ndk() -> NdkDetectionResult {
use std::env;
use std::path::Path;
let mut tried_paths = Vec::new();
let expand_tilde = |path: String| -> String {
if path.starts_with("~") {
env::var("HOME")
.map(|home| path.replacen("~", &home, 1))
.unwrap_or(path)
} else {
path
}
};
for var in ["ANDROID_NDK_HOME", "NDK_HOME"] {
if let Ok(ndk) = env::var(var) {
let expanded = expand_tilde(ndk);
tried_paths.push(format!("${} = {}", var, expanded));
if Path::new(&expanded).exists() {
return NdkDetectionResult {
ndk_path: Some(expanded),
tried_paths,
};
}
}
}
for var in [
"CC_aarch64-linux-android",
"CC_aarch64_linux_android",
"TARGET_CC",
"CC",
] {
if let Ok(cc_path) = env::var(var) {
if cc_path.contains("/ndk/") {
if let Some(ndk_end) = cc_path.find("/toolchains/") {
let ndk = &cc_path[..ndk_end];
tried_paths.push(format!("${} -> extracted: {}", var, ndk));
if Path::new(ndk).exists() {
return NdkDetectionResult {
ndk_path: Some(ndk.to_string()),
tried_paths,
};
}
}
}
}
}
for sdk_var in ["ANDROID_HOME", "ANDROID_SDK_ROOT"] {
if let Ok(sdk) = env::var(sdk_var) {
let sdk_expanded = expand_tilde(sdk);
let ndk_dir = Path::new(&sdk_expanded).join("ndk");
let ndk_path_str = ndk_dir.to_string_lossy().to_string();
tried_paths.push(format!("${}/ndk = {}", sdk_var, ndk_path_str));
if ndk_dir.exists() {
if let Ok(entries) = std::fs::read_dir(&ndk_dir) {
let mut versions: Vec<_> = entries
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.map(|e| e.path())
.collect();
versions.sort();
if let Some(latest) = versions.last() {
return NdkDetectionResult {
ndk_path: Some(latest.to_string_lossy().to_string()),
tried_paths,
};
}
}
}
}
}
let home = env::var("HOME").unwrap_or_default();
let common_locations = [
format!("{}/Library/Android/sdk/ndk", home),
format!("{}/Android/Sdk/ndk", home),
"/opt/android-sdk/ndk".to_string(),
];
for location in &common_locations {
tried_paths.push(format!("common: {}", location));
let ndk_dir = Path::new(location);
if ndk_dir.exists() {
if let Ok(entries) = std::fs::read_dir(ndk_dir) {
let mut versions: Vec<_> = entries
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.map(|e| e.path())
.collect();
versions.sort();
if let Some(latest) = versions.last() {
return NdkDetectionResult {
ndk_path: Some(latest.to_string_lossy().to_string()),
tried_paths,
};
}
}
}
}
NdkDetectionResult {
ndk_path: None,
tried_paths,
}
}
#[cfg(feature = "llm-llamacpp")]
fn compile_llama_cpp() {
use std::env;
use std::path::PathBuf;
use std::process;
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let wrapper_path = manifest_dir.join("vendor/llama_wrapper.cpp");
const LLAMA_CPP_REPO: &str = "https://github.com/ggml-org/llama.cpp";
const LLAMA_CPP_COMMIT: &str = "b46812de78f8fbcb6cf0154947e8633ebc78d9ac";
let in_tree = manifest_dir.join("vendor/llama.cpp");
let llama_cpp_dir = if in_tree.join("CMakeLists.txt").exists() {
in_tree
} else {
let cloned = out_dir.join("llama.cpp");
println!(
"cargo:warning=llama.cpp not vendored in-tree, cloning {}@{} into OUT_DIR...",
LLAMA_CPP_REPO, LLAMA_CPP_COMMIT
);
let dir_str = cloned.to_string_lossy().to_string();
let run = |args: &[&str]| -> bool {
process::Command::new("git")
.args(args)
.status()
.map(|s| s.success())
.unwrap_or(false)
};
let already_initialized = cloned.join(".git").exists()
&& cloned.join("CMakeLists.txt").exists();
let needs_clone = !already_initialized;
if needs_clone && cloned.exists() {
let _ = std::fs::remove_dir_all(&cloned);
}
let ok = if needs_clone {
std::fs::create_dir_all(&cloned).is_ok()
&& run(&["-C", &dir_str, "init", "-q"])
&& run(&["-C", &dir_str, "remote", "add", "origin", LLAMA_CPP_REPO])
&& run(&[
"-C",
&dir_str,
"fetch",
"--depth",
"1",
"origin",
LLAMA_CPP_COMMIT,
])
&& run(&["-C", &dir_str, "checkout", "--detach", "FETCH_HEAD"])
} else {
true
};
if ok {
println!(
"cargo:warning=llama.cpp ready at {} ({})",
cloned.display(),
LLAMA_CPP_COMMIT
);
} else {
println!(
"cargo:warning================================================================="
);
println!("cargo:warning=ERROR: Failed to clone llama.cpp!");
println!(
"cargo:warning================================================================="
);
println!("cargo:warning=Expected location: {}", cloned.display());
println!("cargo:warning=");
println!("cargo:warning=To fix this manually, run:");
println!(
"cargo:warning= git clone {} {} && \\",
LLAMA_CPP_REPO,
cloned.display()
);
println!(
"cargo:warning= git -C {} checkout {}",
cloned.display(),
LLAMA_CPP_COMMIT
);
println!("cargo:warning=");
println!("cargo:warning=Or disable the llm-llamacpp feature:");
println!("cargo:warning= cargo build --no-default-features");
println!(
"cargo:warning================================================================="
);
process::exit(1);
}
cloned
};
if !check_cmake_available() {
println!("cargo:warning=================================================================");
println!("cargo:warning=ERROR: CMake not found!");
println!("cargo:warning=================================================================");
println!("cargo:warning=llama.cpp requires CMake to build.");
println!("cargo:warning=");
println!("cargo:warning={}", cmake_install_instructions());
println!("cargo:warning=");
println!("cargo:warning=Or disable the llm-llamacpp feature:");
println!("cargo:warning= cargo build --no-default-features");
println!("cargo:warning=================================================================");
process::exit(1);
}
let target = env::var("TARGET").unwrap();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let mut metal_enabled = false;
let mut ndk_path_used: Option<String> = None;
println!("cargo:rerun-if-changed=vendor/llama.cpp");
println!("cargo:rerun-if-changed=vendor/llama_wrapper.cpp");
let mut cmake_config = cmake::Config::new(&llama_cpp_dir);
cmake_config
.define("BUILD_SHARED_LIBS", "OFF")
.define("LLAMA_BUILD_EXAMPLES", "OFF")
.define("LLAMA_BUILD_TESTS", "OFF")
.define("LLAMA_BUILD_SERVER", "OFF")
.define("LLAMA_CURL", "OFF")
.define("GGML_OPENMP", "OFF");
if target_os == "android" {
cmake_config
.define("GGML_NATIVE", "OFF") .define("GGML_METAL", "OFF")
.define("GGML_CUDA", "OFF")
.define("GGML_VULKAN", "OFF")
.define("GGML_CPU_HBM", "OFF")
.define("GGML_LLAMAFILE", "OFF");
let ndk_result = find_android_ndk();
if let Some(ref ndk) = ndk_result.ndk_path {
println!("cargo:warning=Android NDK detected: {}", ndk);
ndk_path_used = Some(ndk.clone());
let toolchain_file = format!("{}/build/cmake/android.toolchain.cmake", ndk);
if std::path::Path::new(&toolchain_file).exists() {
cmake_config.define("CMAKE_TOOLCHAIN_FILE", &toolchain_file);
}
let target_arch =
env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_else(|_| "aarch64".to_string());
let android_abi = match target_arch.as_str() {
"aarch64" => "arm64-v8a",
"arm" => "armeabi-v7a",
"x86_64" => "x86_64",
"x86" => "x86",
_ => "arm64-v8a",
};
cmake_config.define("ANDROID_ABI", android_abi);
if android_abi == "arm64-v8a" {
cmake_config.define("GGML_CPU_ARM_ARCH", "armv8.2-a+dotprod");
}
cmake_config.define("ANDROID_PLATFORM", "android-28");
cmake_config.define("ANDROID_STL", "c++_shared");
cmake_config.define("ANDROID_NDK", ndk);
} else {
println!(
"cargo:warning================================================================="
);
println!("cargo:warning=ERROR: Android NDK not found!");
println!(
"cargo:warning================================================================="
);
println!("cargo:warning=Paths tried:");
for path in &ndk_result.tried_paths {
println!("cargo:warning= - {}", path);
}
println!("cargo:warning=");
println!("cargo:warning=To fix this, set one of these environment variables:");
println!("cargo:warning= export ANDROID_NDK_HOME=/path/to/android-ndk");
println!("cargo:warning= export ANDROID_HOME=/path/to/android-sdk (with ndk/ subdirectory)");
println!("cargo:warning=");
println!(
"cargo:warning=Or install Android Studio which sets up the NDK automatically."
);
println!(
"cargo:warning================================================================="
);
process::exit(1);
}
} else if target_os == "macos" || target_os == "ios" {
cmake_config
.define("GGML_METAL", "ON")
.define("GGML_ACCELERATE", "ON")
.define("GGML_BLAS", "OFF");
metal_enabled = true;
} else if target.contains("linux") {
cmake_config
.define("GGML_METAL", "OFF")
.define("GGML_CUDA", "OFF");
} else if target.contains("windows") {
cmake_config
.define("GGML_METAL", "OFF")
.define("GGML_CUDA", "OFF");
cmake_config.profile("Release");
}
println!(
"cargo:warning=llama.cpp build: target={}, metal={}, ndk={}",
target,
if metal_enabled { "yes" } else { "no" },
ndk_path_used.as_deref().unwrap_or("N/A")
);
let dst = cmake_config.build();
println!("cargo:rustc-link-search=native={}/lib", dst.display());
println!("cargo:rustc-link-search=native={}/lib64", dst.display());
println!("cargo:rustc-link-search=native={}", dst.display());
println!("cargo:rustc-link-lib=static=llama");
println!("cargo:rustc-link-lib=static=ggml");
println!("cargo:rustc-link-lib=static=ggml-base");
println!("cargo:rustc-link-lib=static=ggml-cpu");
let mut wrapper_build = cc::Build::new();
wrapper_build
.cpp(true)
.std("c++17")
.file(&wrapper_path)
.include(llama_cpp_dir.join("include"))
.include(llama_cpp_dir.join("ggml/include"))
.include(dst.join("include"));
wrapper_build.compile("llama_wrapper");
if target_os == "android" {
println!("cargo:rustc-link-lib=c++_shared");
println!("cargo:rustc-link-lib=log");
} else if target_os == "linux" {
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=pthread");
} else if target_os == "macos" || target_os == "ios" {
println!("cargo:rustc-link-lib=c++");
println!("cargo:rustc-link-lib=framework=Accelerate");
println!("cargo:rustc-link-lib=framework=Metal");
println!("cargo:rustc-link-lib=framework=Foundation");
println!("cargo:rustc-link-lib=framework=MetalKit");
println!("cargo:rustc-link-lib=static=ggml-metal");
} else if target.contains("windows") {
}
}