use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
const LLAMA_CPP_VERSION: &str = "b7542";
const LLAMA_CPP_REPO: &str = "https://github.com/ggml-org/llama.cpp";
fn main() {
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=LLAMA_CUDA");
println!("cargo:rerun-if-env-changed=LLAMA_METAL");
println!("cargo:rerun-if-env-changed=LLAMA_HIPBLAS");
println!("cargo:rerun-if-env-changed=LLAMA_CLBLAST");
println!("cargo:rerun-if-env-changed=LLAMA_CPP_PATH");
setup_platform_specific();
print_dependency_errors();
let llama_cpp_path = get_llama_cpp_path();
if !llama_cpp_path.join("include").join("llama.h").exists() {
panic!(
"llama.h not found in llama.cpp include directory at {:?}. \
The llama.cpp source may be incomplete or corrupted.",
llama_cpp_path
);
}
let dst = build_llama_cpp(&llama_cpp_path);
generate_bindings(&llama_cpp_path, &dst);
}
fn get_llama_cpp_path() -> PathBuf {
if let Ok(path) = env::var("LLAMA_CPP_PATH") {
let path = PathBuf::from(path);
if path.exists() && path.join("include").join("llama.h").exists() {
println!(
"cargo:warning=Using llama.cpp from LLAMA_CPP_PATH: {:?}",
path
);
return path;
}
panic!(
"LLAMA_CPP_PATH is set to {:?} but it doesn't contain valid llama.cpp sources",
path
);
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let submodule_path = manifest_dir.join("llama.cpp");
if submodule_path.exists() && submodule_path.join("include").join("llama.h").exists() {
return submodule_path;
}
if submodule_path.exists() {
println!("cargo:warning=llama.cpp submodule exists but appears empty, attempting to initialize...");
let status = Command::new("git")
.args(["submodule", "update", "--init", "--recursive"])
.current_dir(&manifest_dir)
.status();
if status.is_ok() && submodule_path.join("include").join("llama.h").exists() {
return submodule_path;
}
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let download_path = out_dir.join("llama.cpp");
if download_path.exists() && download_path.join("include").join("llama.h").exists() {
return download_path;
}
println!(
"cargo:warning=Downloading llama.cpp {} from GitHub...",
LLAMA_CPP_VERSION
);
download_llama_cpp(&download_path);
if !download_path.join("include").join("llama.h").exists() {
panic!(
"Failed to download llama.cpp. Please either:\n\
1. Clone with submodules: git clone --recurse-submodules {}\n\
2. Initialize submodule: git submodule update --init --recursive\n\
3. Set LLAMA_CPP_PATH environment variable to a llama.cpp checkout",
env::var("CARGO_PKG_REPOSITORY").unwrap_or_default()
);
}
download_path
}
fn download_llama_cpp(target_path: &PathBuf) {
if target_path.exists() {
fs::remove_dir_all(target_path).ok();
}
let archive_url = format!(
"{}/archive/refs/tags/{}.tar.gz",
LLAMA_CPP_REPO, LLAMA_CPP_VERSION
);
let archive_path = target_path.parent().unwrap().join("llama-cpp.tar.gz");
let download_result = Command::new("curl")
.args(["-L", "-o"])
.arg(&archive_path)
.arg(&archive_url)
.status()
.or_else(|_| {
Command::new("wget")
.args(["-O"])
.arg(&archive_path)
.arg(&archive_url)
.status()
});
if download_result.is_err() || !archive_path.exists() {
panic!(
"Failed to download llama.cpp. Please install curl or wget, or manually download from {}",
archive_url
);
}
fs::create_dir_all(target_path).expect("Failed to create target directory");
let extract_result = Command::new("tar")
.args(["xzf"])
.arg(&archive_path)
.args(["--strip-components=1", "-C"])
.arg(target_path)
.status();
fs::remove_file(&archive_path).ok();
if extract_result.is_err() {
panic!("Failed to extract llama.cpp archive. Please ensure tar is installed.");
}
println!(
"cargo:warning=Successfully downloaded llama.cpp {}",
LLAMA_CPP_VERSION
);
}
fn setup_platform_specific() {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
match target_os.as_str() {
"windows" => setup_windows(),
"macos" => setup_macos(&target_arch),
"linux" => setup_linux(),
_ => println!("cargo:warning=Unsupported target OS: {}", target_os),
}
}
fn setup_windows() {
println!("cargo:rustc-cfg=target_platform=\"windows\"");
println!("cargo:rustc-link-lib=ole32");
println!("cargo:rustc-link-lib=oleaut32");
println!("cargo:rustc-link-lib=winmm");
println!("cargo:rustc-link-lib=dsound");
println!("cargo:rustc-link-lib=dxguid");
println!("cargo:rustc-link-lib=user32");
println!("cargo:rustc-link-lib=kernel32");
if let Ok(vs_path) = env::var("VCINSTALLDIR") {
println!("cargo:rustc-link-search=native={}/lib/x64", vs_path);
}
if env::var("PROFILE").unwrap() == "release" {
println!("cargo:rustc-env=CFLAGS=/O2 /GL /DNDEBUG");
println!("cargo:rustc-env=CXXFLAGS=/O2 /GL /DNDEBUG");
}
}
fn setup_macos(target_arch: &str) {
println!("cargo:rustc-cfg=target_platform=\"macos\"");
println!("cargo:rustc-link-lib=framework=CoreAudio");
println!("cargo:rustc-link-lib=framework=AudioToolbox");
println!("cargo:rustc-link-lib=framework=AudioUnit");
println!("cargo:rustc-link-lib=framework=CoreFoundation");
println!("cargo:rustc-link-lib=framework=CoreServices");
println!("cargo:rustc-link-lib=framework=Accelerate");
if target_arch == "aarch64" {
println!("cargo:rustc-cfg=target_arch_apple_silicon");
println!("cargo:rustc-link-lib=framework=Metal");
println!("cargo:rustc-link-lib=framework=MetalKit");
if env::var("LLAMA_METAL").is_err() {
env::set_var("LLAMA_METAL", "1");
}
}
if env::var("PROFILE").unwrap() == "release" {
if target_arch == "aarch64" {
println!("cargo:rustc-env=CFLAGS=-O3 -mcpu=apple-m1");
println!("cargo:rustc-env=CXXFLAGS=-O3 -mcpu=apple-m1");
} else {
println!("cargo:rustc-env=CFLAGS=-O3 -march=native");
println!("cargo:rustc-env=CXXFLAGS=-O3 -march=native");
}
}
}
fn setup_linux() {
println!("cargo:rustc-cfg=target_platform=\"linux\"");
check_audio_libraries();
if env::var("PROFILE").unwrap() == "release" {
println!("cargo:rustc-env=CFLAGS=-O3 -march=native -mtune=native -DNDEBUG");
println!("cargo:rustc-env=CXXFLAGS=-O3 -march=native -mtune=native -DNDEBUG");
}
if pkg_config::probe_library("numa").is_ok() {
println!("cargo:rustc-cfg=feature=\"numa\"");
println!("cargo:rustc-link-lib=numa");
}
println!("cargo:rustc-link-lib=pthread");
println!("cargo:rustc-link-lib=dl");
println!("cargo:rustc-link-lib=m");
}
fn check_audio_libraries() {
if pkg_config::probe_library("alsa").is_ok() {
println!("cargo:rustc-cfg=feature=\"alsa\"");
println!("cargo:rustc-link-lib=asound");
} else {
println!("cargo:warning=ALSA development libraries not found. Install libasound2-dev");
}
if pkg_config::probe_library("libpulse").is_ok() {
println!("cargo:rustc-cfg=feature=\"pulseaudio\"");
println!("cargo:rustc-link-lib=pulse");
} else {
println!("cargo:warning=PulseAudio development libraries not found. Install libpulse-dev");
}
if pkg_config::probe_library("jack").is_ok() {
println!("cargo:rustc-cfg=feature=\"jack\"");
println!("cargo:rustc-link-lib=jack");
}
for lib in &["flac", "vorbis", "vorbisenc", "opus"] {
if pkg_config::probe_library(lib).is_ok() {
println!("cargo:rustc-cfg=feature=\"{}\"", lib);
}
}
}
fn build_llama_cpp(llama_cpp_path: &PathBuf) -> PathBuf {
let mut cmake_config = cmake::Config::new(llama_cpp_path);
if env::var("PROFILE").unwrap() == "release" {
cmake_config.define("CMAKE_BUILD_TYPE", "Release");
} else {
cmake_config.define("CMAKE_BUILD_TYPE", "Debug");
}
if cfg!(target_os = "windows") {
cmake_config.define("CMAKE_GENERATOR_PLATFORM", "x64");
cmake_config.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL");
}
if env::var("LLAMA_CUDA").is_ok() {
println!("cargo:rustc-cfg=feature=\"cuda\"");
cmake_config.define("GGML_CUDA", "ON");
cmake_config.define("CMAKE_CUDA_ARCHITECTURES", "61;70;75;80;86;89");
configure_cuda_linking();
} else {
cmake_config.define("GGML_CUDA", "OFF");
}
if env::var("LLAMA_METAL").is_ok() {
println!("cargo:rustc-cfg=feature=\"metal\"");
cmake_config.define("GGML_METAL", "ON");
} else {
cmake_config.define("GGML_METAL", "OFF");
}
if env::var("LLAMA_HIPBLAS").is_ok() {
println!("cargo:rustc-cfg=feature=\"rocm\"");
cmake_config.define("GGML_HIP", "ON");
configure_rocm_linking();
} else {
cmake_config.define("GGML_HIP", "OFF");
}
if env::var("LLAMA_CLBLAST").is_ok() {
println!("cargo:rustc-cfg=feature=\"opencl\"");
cmake_config.define("GGML_OPENCL", "ON");
configure_opencl_linking();
} else {
cmake_config.define("GGML_OPENCL", "OFF");
}
cmake_config.define("GGML_NATIVE", "ON");
cmake_config.define("GGML_LTO", "ON");
cmake_config.define("GGML_AVX", "ON");
cmake_config.define("GGML_AVX2", "ON");
cmake_config.define("GGML_FMA", "ON");
cmake_config.define("GGML_F16C", "ON");
cmake_config.define("GGML_OPENMP", "ON");
cmake_config.define("LLAMA_BUILD_TESTS", "OFF");
cmake_config.define("LLAMA_BUILD_EXAMPLES", "OFF");
cmake_config.define("LLAMA_CURL", "OFF");
cmake_config.define("BUILD_SHARED_LIBS", "OFF");
cmake_config.define("GGML_STATIC", "ON");
cmake_config.define("LLAMA_BUILD_COMMON", "ON");
cmake_config.define("LLAMA_BUILD_TOOLS", "ON");
cmake_config.define("LLAMA_TOOLS_INSTALL", "ON");
let dst = cmake_config.build();
println!("cargo:rustc-link-search=native={}/lib", dst.display());
if cfg!(target_os = "windows") {
println!("cargo:rustc-link-lib=static=llama");
println!("cargo:rustc-link-lib=static=ggml_static");
println!("cargo:rustc-link-lib=static=mtmd");
} else if cfg!(target_os = "macos") {
println!("cargo:rustc-link-lib=static:+whole-archive,-bundle=ggml-base");
println!("cargo:rustc-link-lib=static:+whole-archive,-bundle=ggml-cpu");
println!("cargo:rustc-link-lib=static:+whole-archive,-bundle=ggml");
println!("cargo:rustc-link-lib=static:+whole-archive,-bundle=llama");
println!("cargo:rustc-link-lib=static:+whole-archive,-bundle=mtmd");
} else {
println!("cargo:rustc-link-arg=-Wl,--start-group");
println!("cargo:rustc-link-lib=static:+whole-archive=llama");
println!("cargo:rustc-link-lib=static:+whole-archive=mtmd");
println!("cargo:rustc-link-lib=static:+whole-archive=ggml");
println!("cargo:rustc-link-lib=static:+whole-archive=ggml-cpu");
println!("cargo:rustc-link-lib=static:+whole-archive=ggml-base");
println!("cargo:rustc-link-arg=-Wl,--end-group");
}
if cfg!(target_os = "linux") {
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=gomp"); } else if cfg!(target_os = "macos") {
println!("cargo:rustc-link-lib=c++");
} else if cfg!(target_os = "windows") {
println!("cargo:rustc-link-lib=msvcrt");
}
dst
}
fn configure_cuda_linking() {
let cuda_path = env::var("CUDA_PATH")
.or_else(|_| env::var("CUDA_ROOT"))
.unwrap_or_else(|_| {
if cfg!(target_os = "windows") {
"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.0".to_string()
} else {
"/usr/local/cuda".to_string()
}
});
let cuda_lib_path = if cfg!(target_os = "windows") {
format!("{}\\lib\\x64", cuda_path)
} else {
format!("{}/lib64", cuda_path)
};
println!("cargo:rustc-link-search=native={}", cuda_lib_path);
println!("cargo:rustc-link-lib=cudart");
println!("cargo:rustc-link-lib=cublas");
println!("cargo:rustc-link-lib=curand");
if let Ok(output) = Command::new("nvcc").args(["--version"]).output() {
let version_str = String::from_utf8_lossy(&output.stdout);
if version_str.contains("release 12") {
println!("cargo:rustc-cfg=cuda_version=\"12\"");
} else if version_str.contains("release 11") {
println!("cargo:rustc-cfg=cuda_version=\"11\"");
}
}
}
fn configure_rocm_linking() {
let rocm_path = env::var("ROCM_PATH").unwrap_or_else(|_| "/opt/rocm".to_string());
println!("cargo:rustc-link-search=native={}/lib", rocm_path);
println!("cargo:rustc-link-lib=hipblas");
println!("cargo:rustc-link-lib=rocblas");
println!("cargo:rustc-link-lib=amdhip64");
}
fn configure_opencl_linking() {
if cfg!(target_os = "windows") {
println!("cargo:rustc-link-lib=OpenCL");
} else if cfg!(target_os = "macos") {
println!("cargo:rustc-link-lib=framework=OpenCL");
} else if pkg_config::probe_library("OpenCL").is_ok() {
println!("cargo:rustc-link-lib=OpenCL");
} else {
println!("cargo:warning=OpenCL not found. Install opencl-headers and ocl-icd-opencl-dev");
}
if pkg_config::probe_library("clblast").is_ok() {
println!("cargo:rustc-link-lib=clblast");
}
}
fn print_dependency_errors() {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
match target_os.as_str() {
"windows" => {
if !command_exists("cl") && !command_exists("gcc") {
println!("cargo:warning=No C++ compiler found. Install Visual Studio Build Tools or MinGW.");
}
if !command_exists("cmake") {
println!("cargo:warning=CMake not found. Install CMake and add it to PATH.");
}
}
"macos" => {
if !command_exists("clang") {
println!(
"cargo:warning=Xcode command line tools not found. Run: xcode-select --install"
);
}
if !command_exists("cmake") {
println!("cargo:warning=CMake not found. Install with: brew install cmake");
}
}
"linux" => {
if !command_exists("gcc") && !command_exists("clang") {
println!("cargo:warning=No C++ compiler found. Install build-essential or clang.");
}
if !command_exists("cmake") {
println!("cargo:warning=CMake not found. Install with your package manager.");
}
if !command_exists("pkg-config") {
println!("cargo:warning=pkg-config not found. Install with your package manager.");
}
}
_ => {}
}
}
fn command_exists(command: &str) -> bool {
Command::new(command)
.arg("--version")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
fn generate_bindings(llama_cpp_path: &Path, _build_path: &Path) {
let include_path = llama_cpp_path.join("include");
let ggml_include_path = llama_cpp_path.join("ggml").join("include");
let mtmd_include_path = llama_cpp_path.join("tools").join("mtmd");
let mut builder = bindgen::Builder::default()
.header("wrapper.h")
.clang_arg(format!("-I{}", include_path.display()))
.clang_arg(format!("-I{}", ggml_include_path.display()))
.clang_arg(format!("-I{}/ggml/src", llama_cpp_path.display()))
.clang_arg(format!("-I{}", mtmd_include_path.display()))
.clang_arg("-std=c11")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.blocklist_type("max_align_t")
.blocklist_type("__off_t")
.blocklist_type("__off64_t")
.blocklist_type("_IO_lock_t")
.allowlist_function("llama_.*")
.allowlist_function("ggml_.*")
.allowlist_function("mtmd_.*")
.allowlist_function("clip_.*")
.allowlist_type("llama_.*")
.allowlist_type("ggml_.*")
.allowlist_type("mtmd_.*")
.allowlist_type("clip_.*");
#[cfg(target_os = "linux")]
{
if let Ok(output) = Command::new("gcc")
.args(["-E", "-Wp,-v", "-xc", "/dev/null"])
.output()
{
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stderr.lines() {
let line = line.trim();
if line.starts_with('/')
&& !line.contains(' ')
&& std::path::Path::new(line).exists()
{
builder = builder.clang_arg(format!("-isystem{}", line));
}
}
}
}
let bindings = builder.generate().expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}