use cmake::Config; use glob::glob; use std::env; use std::path::{Path, PathBuf}; use std::process::Command; use walkdir::DirEntry; enum WindowsVariant { Msvc, Other, } enum AppleVariant { MacOS, Other, } enum TargetOs { Windows(WindowsVariant), Apple(AppleVariant), Linux, Android, } macro_rules! debug_log { ($($arg:tt)*) => { if std::env::var("BUILD_DEBUG").is_ok() { println!("cargo:warning=[DEBUG] {}", format!($($arg)*)); } }; } fn parse_target_os() -> Result<(TargetOs, String), String> { let target = env::var("TARGET").unwrap(); if target.contains("windows") { if target.ends_with("-windows-msvc") { Ok((TargetOs::Windows(WindowsVariant::Msvc), target)) } else { Ok((TargetOs::Windows(WindowsVariant::Other), target)) } } else if target.contains("apple") { if target.ends_with("-apple-darwin") { Ok((TargetOs::Apple(AppleVariant::MacOS), target)) } else { Ok((TargetOs::Apple(AppleVariant::Other), target)) } } else if target.contains("android") || target == "aarch64-linux-android" || target == "armv7-linux-androideabi" || target == "i686-linux-android" || target == "x86_64-linux-android" { // Handle both full android targets and short names like arm64-v8a that cargo ndk might use Ok((TargetOs::Android, target)) } else if target.contains("linux") { Ok((TargetOs::Linux, target)) } else { Err(target) } } fn get_cargo_target_dir() -> Result> { let out_dir = env::var("OUT_DIR")?; let path = PathBuf::from(out_dir); let target_dir = path .ancestors() .nth(3) .ok_or("OUT_DIR is not deep enough")?; Ok(target_dir.to_path_buf()) } fn extract_lib_names(out_dir: &Path, build_shared_libs: bool) -> Vec { // Use CARGO_CFG_TARGET_OS to detect TARGET platform, not HOST // This fixes cross-compilation from Linux to Windows let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); let lib_pattern = if target_os == "windows" { "*.lib" } else if target_os == "macos" { 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 = Vec::new(); // Process the libraries based on the pattern 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(); // Remove the "lib" prefix if present let lib_name = if stem_str.starts_with("lib") { stem_str.strip_prefix("lib").unwrap_or(stem_str) } else { if path.extension() == Some(std::ffi::OsStr::new("a")) { let target = path.parent().unwrap().join(format!("lib{}.a", stem_str)); std::fs::rename(&path, &target).unwrap_or_else(|e| { panic!("Failed to rename {path:?} to {target:?}: {e:?}"); }) } stem_str }; lib_names.push(lib_name.to_string()); } Err(e) => println!("cargo:warning=error={}", e), } } lib_names } fn extract_lib_assets(out_dir: &Path) -> Vec { // Use CARGO_CFG_TARGET_OS to detect TARGET platform, not HOST // This fixes cross-compilation from Linux to Windows let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); let shared_lib_pattern = if target_os == "windows" { "*.dll" } else if target_os == "macos" { "*.dylib" } else { "*.so" }; let shared_libs_dir = if target_os == "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 macos_link_search_path() -> Option { let output = Command::new("clang") .arg("--print-search-dirs") .output() .ok()?; if !output.status.success() { println!( "failed to run 'clang --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 validate_android_ndk(ndk_path: &str) -> Result<(), String> { let ndk_path = Path::new(ndk_path); if !ndk_path.exists() { return Err(format!( "Android NDK path does not exist: {}", ndk_path.display() )); } let toolchain_file = ndk_path.join("build/cmake/android.toolchain.cmake"); if !toolchain_file.exists() { return Err(format!( "Android NDK toolchain file not found: {}\n\ This indicates an incomplete NDK installation.", toolchain_file.display() )); } Ok(()) } fn is_hidden(e: &DirEntry) -> bool { e.file_name() .to_str() .map(|s| s.starts_with('.')) .unwrap_or_default() } fn main() { println!("cargo:rerun-if-changed=build.rs"); let (target_os, target_triple) = parse_target_os().unwrap_or_else(|t| panic!("Failed to parse target os {t}")); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let target_dir = get_cargo_target_dir().unwrap(); 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); println!("cargo:rerun-if-env-changed=LLAMA_LIB_PROFILE"); println!("cargo:rerun-if-env-changed=LLAMA_BUILD_SHARED_LIBS"); println!("cargo:rerun-if-env-changed=LLAMA_STATIC_CRT"); debug_log!("TARGET: {}", target_triple); debug_log!("CARGO_MANIFEST_DIR: {}", manifest_dir); debug_log!("TARGET_DIR: {}", target_dir.display()); debug_log!("OUT_DIR: {}", out_dir.display()); debug_log!("BUILD_SHARED: {}", build_shared_libs); // Make sure that changes to the llama.cpp project trigger a rebuild. let rebuild_on_children_of = [ llama_src.join("src"), llama_src.join("ggml/src"), llama_src.join("common"), ]; for entry in walkdir::WalkDir::new(&llama_src) .into_iter() .filter_entry(|e| !is_hidden(e)) { let entry = entry.expect("Failed to obtain entry"); let rebuild = entry .file_name() .to_str() .map(|f| f.starts_with("CMake")) .unwrap_or_default() || rebuild_on_children_of .iter() .any(|src_folder| entry.path().starts_with(src_folder)); if rebuild { println!("cargo:rerun-if-changed={}", entry.path().display()); } } // Speed up build env::set_var( "CMAKE_BUILD_PARALLEL_LEVEL", std::thread::available_parallelism() .unwrap() .get() .to_string(), ); // Bindings let mut bindings_builder = bindgen::Builder::default() .header("wrapper.h") .clang_arg(format!("-I{}", llama_src.join("include").display())) .clang_arg(format!("-I{}", llama_src.join("ggml/include").display())) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .derive_partialeq(true) .allowlist_function("ggml_.*") .allowlist_type("ggml_.*") .allowlist_function("llama_.*") .allowlist_type("llama_.*") .prepend_enum_name(false); // Configure mtmd feature if enabled if cfg!(feature = "mtmd") { bindings_builder = bindings_builder .header("wrapper_mtmd.h") .allowlist_function("mtmd_.*") .allowlist_type("mtmd_.*"); } // Configure Android-specific bindgen settings if matches!(target_os, TargetOs::Android) { // Detect Android NDK from environment variables let android_ndk = env::var("ANDROID_NDK") .or_else(|_| env::var("ANDROID_NDK_ROOT")) .or_else(|_| env::var("NDK_ROOT")) .or_else(|_| env::var("CARGO_NDK_ANDROID_NDK")) .or_else(|_| { // Try to auto-detect NDK from Android SDK if let Some(home) = env::home_dir() { let android_home = env::var("ANDROID_HOME") .or_else(|_| env::var("ANDROID_SDK_ROOT")) .unwrap_or_else(|_| format!("{}/Android/Sdk", home.display())); let ndk_dir = format!("{}/ndk", android_home); if let Ok(entries) = std::fs::read_dir(&ndk_dir) { let mut versions: Vec<_> = entries .filter_map(|e| e.ok()) .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false)) .filter_map(|e| e.file_name().to_str().map(|s| s.to_string())) .collect(); versions.sort(); if let Some(latest) = versions.last() { return Ok(format!("{}/{}", ndk_dir, latest)); } } } Err(env::VarError::NotPresent) }) .unwrap_or_else(|_| { panic!( "Android NDK not found. Please set one of: ANDROID_NDK, NDK_ROOT, ANDROID_NDK_ROOT\n\ Current target: {}\n\ Download from: https://developer.android.com/ndk/downloads", target_triple ); }); // Get Android API level let android_api = env::var("ANDROID_API_LEVEL") .or_else(|_| env::var("ANDROID_PLATFORM").map(|p| p.replace("android-", ""))) .or_else(|_| env::var("CARGO_NDK_ANDROID_PLATFORM").map(|p| p.replace("android-", ""))) .unwrap_or_else(|_| "28".to_string()); // Determine host platform let host_tag = if cfg!(target_os = "macos") { "darwin-x86_64" } else if cfg!(target_os = "linux") { "linux-x86_64" } else if cfg!(target_os = "windows") { "windows-x86_64" } else { panic!("Unsupported host platform for Android NDK"); }; // Map Rust target to Android architecture let android_target_prefix = if target_triple.contains("aarch64") { "aarch64-linux-android" } else if target_triple.contains("armv7") { "arm-linux-androideabi" } else if target_triple.contains("x86_64") { "x86_64-linux-android" } else if target_triple.contains("i686") { "i686-linux-android" } else { panic!("Unsupported Android target: {}", target_triple); }; // Setup Android toolchain paths let toolchain_path = format!("{}/toolchains/llvm/prebuilt/{}", android_ndk, host_tag); let sysroot = format!("{}/sysroot", toolchain_path); // Validate toolchain existence if !std::path::Path::new(&toolchain_path).exists() { panic!( "Android NDK toolchain not found at: {}\n\ Please ensure you have the correct Android NDK for your platform.", toolchain_path ); } // Find clang builtin includes let clang_builtin_includes = { let clang_lib_path = format!("{}/lib/clang", toolchain_path); std::fs::read_dir(&clang_lib_path).ok().and_then(|entries| { entries .filter_map(|e| e.ok()) .find(|entry| { entry.file_type().map(|t| t.is_dir()).unwrap_or(false) && entry .file_name() .to_str() .map(|name| name.chars().next().unwrap_or('0').is_ascii_digit()) .unwrap_or(false) }) .and_then(|entry| { let include_path = format!("{}/{}/include", clang_lib_path, entry.file_name().to_str()?); if std::path::Path::new(&include_path).exists() { Some(include_path) } else { None } }) }) }; // Configure bindgen for Android bindings_builder = bindings_builder .clang_arg(format!("--sysroot={}", sysroot)) .clang_arg(format!("-D__ANDROID_API__={}", android_api)) .clang_arg("-D__ANDROID__"); // Add include paths in correct order if let Some(ref builtin_includes) = clang_builtin_includes { bindings_builder = bindings_builder .clang_arg("-isystem") .clang_arg(builtin_includes); } bindings_builder = bindings_builder .clang_arg("-isystem") .clang_arg(format!("{}/usr/include/{}", sysroot, android_target_prefix)) .clang_arg("-isystem") .clang_arg(format!("{}/usr/include", sysroot)) .clang_arg("-include") .clang_arg("stdbool.h") .clang_arg("-include") .clang_arg("stdint.h"); // Set additional clang args for cargo ndk compatibility if env::var("CARGO_SUBCOMMAND").as_deref() == Ok("ndk") { std::env::set_var( "BINDGEN_EXTRA_CLANG_ARGS", format!("--target={}", target_triple), ); } } // Fix bindgen header discovery on Windows MSVC // Use cc crate to discover MSVC include paths by compiling a dummy file if matches!(target_os, TargetOs::Windows(WindowsVariant::Msvc)) { // Create a minimal dummy C file to extract compiler flags let out_dir = env::var("OUT_DIR").unwrap(); let dummy_c = Path::new(&out_dir).join("dummy.c"); std::fs::write(&dummy_c, "int main() { return 0; }").unwrap(); // Use cc crate to get compiler with proper environment setup let mut build = cc::Build::new(); build.file(&dummy_c); // Get the actual compiler command cc would use let compiler = build.try_get_compiler().unwrap(); // Extract include paths by checking compiler's environment // cc crate sets up MSVC environment internally let env_include = compiler .env() .iter() .find(|(k, _)| k.eq_ignore_ascii_case("INCLUDE")) .map(|(_, v)| v); if let Some(include_paths) = env_include { for include_path in include_paths .to_string_lossy() .split(';') .filter(|s| !s.is_empty()) { bindings_builder = bindings_builder .clang_arg("-isystem") .clang_arg(include_path); debug_log!("Added MSVC include path: {}", include_path); } } // Add MSVC compatibility flags bindings_builder = bindings_builder .clang_arg(format!("--target={}", target_triple)) .clang_arg("-fms-compatibility") .clang_arg("-fms-extensions"); debug_log!( "Configured bindgen with MSVC toolchain for target: {}", target_triple ); } let bindings = bindings_builder .generate() .expect("Failed to generate bindings"); // Write the generated bindings to an output file let bindings_path = out_dir.join("bindings.rs"); bindings .write_to_file(bindings_path) .expect("Failed to write bindings"); println!("cargo:rerun-if-changed=wrapper.h"); println!("cargo:rerun-if-changed=wrapper_mtmd.h"); debug_log!("Bindings Created"); // Build with Cmake let mut config = Config::new(&llama_src); // Would require extra source files to pointlessly // be included in what's uploaded to and downloaded from // crates.io, so deactivating these instead config.define("LLAMA_BUILD_TESTS", "OFF"); config.define("LLAMA_BUILD_EXAMPLES", "OFF"); config.define("LLAMA_BUILD_SERVER", "OFF"); config.define("LLAMA_BUILD_TOOLS", "OFF"); config.define("LLAMA_CURL", "OFF"); if cfg!(feature = "mtmd") { config.define("LLAMA_BUILD_COMMON", "ON"); // mtmd support in llama-cpp is within the tools directory config.define("LLAMA_BUILD_TOOLS", "ON"); } // Pass CMAKE_ environment variables down to CMake for (key, value) in env::vars() { if key.starts_with("CMAKE_") { config.define(&key, &value); } } // extract the target-cpu config value, if specified let target_cpu = std::env::var("CARGO_ENCODED_RUSTFLAGS") .ok() .and_then(|rustflags| { rustflags .split('\x1f') .find(|f| f.contains("target-cpu=")) .and_then(|f| f.split("target-cpu=").nth(1)) .map(|s| s.to_string()) }); if target_cpu == Some("native".into()) { debug_log!("Detected target-cpu=native, compiling with GGML_NATIVE"); config.define("GGML_NATIVE", "ON"); } // if native isn't specified, enable specific features for ggml instead else { // rust code isn't using `target-cpu=native`, so llama.cpp shouldn't use GGML_NATIVE either config.define("GGML_NATIVE", "OFF"); // if `target-cpu` is set set, also set -march for llama.cpp to the same value if let Some(ref cpu) = target_cpu { debug_log!("Setting baseline architecture: -march={}", cpu); config.cflag(&format!("-march={}", cpu)); config.cxxflag(&format!("-march={}", cpu)); } // I expect this env var to always be present let features = std::env::var("CARGO_CFG_TARGET_FEATURE") .expect("Env var CARGO_CFG_TARGET_FEATURE not found."); debug_log!("Compiling with target features: {}", features); // list of rust target_features here: // https://doc.rust-lang.org/reference/attributes/codegen.html#the-target_feature-attribute // GGML config flags have been found by looking at: // llama.cpp/ggml/src/ggml-cpu/CMakeLists.txt for feature in features.split(',') { match feature { "avx" => { config.define("GGML_AVX", "ON"); } "avx2" => { config.define("GGML_AVX2", "ON"); } "avx512bf16" => { config.define("GGML_AVX512_BF16", "ON"); } "avx512vbmi" => { config.define("GGML_AVX512_VBMI", "ON"); } "avx512vnni" => { config.define("GGML_AVX512_VNNI", "ON"); } "avxvnni" => { config.define("GGML_AVX_VNNI", "ON"); } "bmi2" => { config.define("GGML_BMI2", "ON"); } "f16c" => { config.define("GGML_F16C", "ON"); } "fma" => { config.define("GGML_FMA", "ON"); } "sse4.2" => { config.define("GGML_SSE42", "ON"); } _ => { debug_log!( "Unrecognized cpu feature: '{}' - skipping GGML config for it.", feature ); continue; } }; } } config.define( "BUILD_SHARED_LIBS", if build_shared_libs { "ON" } else { "OFF" }, ); if matches!(target_os, TargetOs::Apple(_)) { config.define("GGML_BLAS", "OFF"); } if (matches!(target_os, TargetOs::Windows(WindowsVariant::Msvc)) && matches!( profile.as_str(), "Release" | "RelWithDebInfo" | "MinSizeRel" )) { // Debug Rust builds under MSVC turn off optimization even though we're ideally building the release profile of llama.cpp. // Looks like an upstream bug: // https://github.com/rust-lang/cmake-rs/issues/240 // For now explicitly reinject the optimization flags that a CMake Release build is expected to have on in this scenario. // This fixes CPU inference performance when part of a Rust debug build. for flag in &["/O2", "/DNDEBUG", "/Ob2"] { config.cflag(flag); config.cxxflag(flag); } } config.static_crt(static_crt); if matches!(target_os, TargetOs::Android) { // Android NDK Build Configuration let android_ndk = env::var("ANDROID_NDK") .or_else(|_| env::var("NDK_ROOT")) .or_else(|_| env::var("ANDROID_NDK_ROOT")) .unwrap_or_else(|_| { panic!( "Android NDK not found. Please set one of: ANDROID_NDK, NDK_ROOT, ANDROID_NDK_ROOT\n\ Download from: https://developer.android.com/ndk/downloads" ); }); // Validate NDK installation if let Err(error) = validate_android_ndk(&android_ndk) { panic!("Android NDK validation failed: {}", error); } // Rerun build script if NDK environment variables change println!("cargo:rerun-if-env-changed=ANDROID_NDK"); println!("cargo:rerun-if-env-changed=NDK_ROOT"); println!("cargo:rerun-if-env-changed=ANDROID_NDK_ROOT"); // Set CMake toolchain file for Android let toolchain_file = format!("{}/build/cmake/android.toolchain.cmake", android_ndk); config.define("CMAKE_TOOLCHAIN_FILE", &toolchain_file); // Configure Android platform (API level) let android_platform = env::var("ANDROID_PLATFORM").unwrap_or_else(|_| { env::var("ANDROID_API_LEVEL") .map(|level| format!("android-{}", level)) .unwrap_or_else(|_| "android-28".to_string()) }); println!("cargo:rerun-if-env-changed=ANDROID_PLATFORM"); println!("cargo:rerun-if-env-changed=ANDROID_API_LEVEL"); config.define("ANDROID_PLATFORM", &android_platform); // Map Rust target to Android ABI let android_abi = if target_triple.contains("aarch64") { "arm64-v8a" } else if target_triple.contains("armv7") { "armeabi-v7a" } else if target_triple.contains("x86_64") { "x86_64" } else if target_triple.contains("i686") { "x86" } else { panic!( "Unsupported Android target: {}\n\ Supported targets: aarch64-linux-android, armv7-linux-androideabi, i686-linux-android, x86_64-linux-android", target_triple ); }; config.define("ANDROID_ABI", android_abi); // Configure architecture-specific compiler flags match android_abi { "arm64-v8a" => { config.cflag("-march=armv8-a"); config.cxxflag("-march=armv8-a"); } "armeabi-v7a" => { config.cflag("-march=armv7-a"); config.cxxflag("-march=armv7-a"); config.cflag("-mfpu=neon"); config.cxxflag("-mfpu=neon"); config.cflag("-mthumb"); config.cxxflag("-mthumb"); } "x86_64" => { config.cflag("-march=x86-64"); config.cxxflag("-march=x86-64"); } "x86" => { config.cflag("-march=i686"); config.cxxflag("-march=i686"); } _ => {} } // Android-specific CMake configurations config.define("GGML_LLAMAFILE", "OFF"); // Link Android system libraries println!("cargo:rustc-link-lib=log"); println!("cargo:rustc-link-lib=android"); } if matches!(target_os, TargetOs::Linux) && target_triple.contains("aarch64") && target_cpu != Some("native".into()) { // If the target-cpu is not specified as native, we take off the native ARM64 support. // It is useful in docker environments where the native feature is not enabled. config.define("GGML_NATIVE", "OFF"); config.define("GGML_CPU_ARM_ARCH", "armv8-a"); } if cfg!(feature = "vulkan") { config.define("GGML_VULKAN", "ON"); match target_os { TargetOs::Windows(_) => { let vulkan_path = env::var("VULKAN_SDK").expect( "Please install Vulkan SDK and ensure that VULKAN_SDK env variable is set", ); let vulkan_lib_path = Path::new(&vulkan_path).join("Lib"); println!("cargo:rustc-link-search={}", vulkan_lib_path.display()); println!("cargo:rustc-link-lib=vulkan-1"); // workaround for this error: "FileTracker : error FTK1011: could not create the new file tracking log file" // it has to do with MSBuild FileTracker not respecting the path // limit configuration set in the windows registry. // I'm not sure why that's a thing, but this makes my builds work. // (crates that depend on llama-cpp-rs w/ vulkan easily exceed the default PATH_MAX on windows) env::set_var("TrackFileAccess", "false"); // since we disabled TrackFileAccess, we can now run into problems with parallel // access to pdb files. /FS solves this. config.cflag("/FS"); config.cxxflag("/FS"); } TargetOs::Linux => { // If we are not using system provided vulkan SDK, add vulkan libs for linking if let Ok(vulkan_path) = env::var("VULKAN_SDK") { let vulkan_lib_path = Path::new(&vulkan_path).join("lib"); println!("cargo:rustc-link-search={}", vulkan_lib_path.display()); } println!("cargo:rustc-link-lib=vulkan"); } _ => (), } } if cfg!(feature = "cuda") { config.define("GGML_CUDA", "ON"); if cfg!(feature = "cuda-no-vmm") { config.define("GGML_CUDA_NO_VMM", "ON"); } } // Android doesn't have OpenMP support AFAICT and openmp is a default feature. Do this here // rather than modifying the defaults in Cargo.toml just in case someone enables the OpenMP feature // and tries to build for Android anyway. if cfg!(feature = "openmp") && !matches!(target_os, TargetOs::Android) { config.define("GGML_OPENMP", "ON"); } else { config.define("GGML_OPENMP", "OFF"); } if cfg!(feature = "system-ggml") { config.define("LLAMA_USE_SYSTEM_GGML", "ON"); } // General config .profile(&profile) .very_verbose(std::env::var("CMAKE_VERBOSE").is_ok()) // Not verbose by default .always_configure(false); let build_dir = config.build(); // Search paths println!("cargo:rustc-link-search={}", out_dir.join("lib").display()); println!( "cargo:rustc-link-search={}", out_dir.join("lib64").display() ); println!("cargo:rustc-link-search={}", build_dir.display()); if cfg!(feature = "system-ggml") { // Extract library directory from CMake's found GGML package let cmake_cache = build_dir.join("build").join("CMakeCache.txt"); if let Ok(cache_contents) = std::fs::read_to_string(&cmake_cache) { let mut ggml_lib_dirs = std::collections::HashSet::new(); // Parse CMakeCache.txt to find where GGML libraries were found for line in cache_contents.lines() { if line.starts_with("GGML_LIBRARY:") || line.starts_with("GGML_BASE_LIBRARY:") || line.starts_with("GGML_CPU_LIBRARY:") { if let Some(lib_path) = line.split('=').nth(1) { if let Some(parent) = Path::new(lib_path).parent() { ggml_lib_dirs.insert(parent.to_path_buf()); } } } } // Add each unique library directory to the search path for lib_dir in ggml_lib_dirs { println!("cargo:rustc-link-search=native={}", lib_dir.display()); debug_log!("Added system GGML library path: {}", lib_dir.display()); } } } if cfg!(feature = "cuda") && !build_shared_libs { // Re-run build script if CUDA_PATH environment variable changes println!("cargo:rerun-if-env-changed=CUDA_PATH"); // Add CUDA library directories to the linker search path for lib_dir in find_cuda_helper::find_cuda_lib_dirs() { println!("cargo:rustc-link-search=native={}", lib_dir.display()); } // Platform-specific linking if cfg!(target_os = "windows") { // ✅ On Windows, use dynamic linking. // Static linking is problematic because NVIDIA does not provide culibos.lib, // and static CUDA libraries (like cublas_static.lib) are usually not shipped. println!("cargo:rustc-link-lib=cudart"); // Links to cudart64_*.dll println!("cargo:rustc-link-lib=cublas"); // Links to cublas64_*.dll println!("cargo:rustc-link-lib=cublasLt"); // Links to cublasLt64_*.dll // Link to CUDA driver API (nvcuda.dll via cuda.lib) if !cfg!(feature = "cuda-no-vmm") { println!("cargo:rustc-link-lib=cuda"); } } else { // ✅ On non-Windows platforms (e.g., Linux), static linking is preferred and supported. // Static libraries like cudart_static and cublas_static depend on culibos. println!("cargo:rustc-link-lib=static=cudart_static"); println!("cargo:rustc-link-lib=static=cublas_static"); println!("cargo:rustc-link-lib=static=cublasLt_static"); // Link to CUDA driver API (libcuda.so) if !cfg!(feature = "cuda-no-vmm") { println!("cargo:rustc-link-lib=cuda"); } // culibos is required when statically linking cudart_static println!("cargo:rustc-link-lib=static=culibos"); } } // Link libraries let llama_libs_kind = if build_shared_libs || cfg!(feature = "system-ggml") { "dylib" } else { "static" }; let llama_libs = extract_lib_names(&out_dir, build_shared_libs); assert_ne!(llama_libs.len(), 0); if cfg!(feature = "system-ggml") { println!("cargo:rustc-link-lib={llama_libs_kind}=ggml"); println!("cargo:rustc-link-lib={llama_libs_kind}=ggml-base"); println!("cargo:rustc-link-lib={llama_libs_kind}=ggml-cpu"); } for lib in llama_libs { let link = format!("cargo:rustc-link-lib={}={}", llama_libs_kind, lib); debug_log!("LINK {link}",); println!("{link}",); } // OpenMP if cfg!(feature = "openmp") && target_triple.contains("gnu") { println!("cargo:rustc-link-lib=gomp"); } match target_os { TargetOs::Windows(WindowsVariant::Msvc) => { println!("cargo:rustc-link-lib=advapi32"); if cfg!(debug_assertions) { println!("cargo:rustc-link-lib=dylib=msvcrtd"); } } TargetOs::Linux => { println!("cargo:rustc-link-lib=dylib=stdc++"); } TargetOs::Apple(variant) => { println!("cargo:rustc-link-lib=framework=Foundation"); 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++"); match variant { AppleVariant::MacOS => { // On (older) OSX we need to link against the clang runtime, // which is hidden in some non-default path. // // More details at https://github.com/alexcrichton/curl-rust/issues/279. if let Some(path) = macos_link_search_path() { println!("cargo:rustc-link-lib=clang_rt.osx"); println!("cargo:rustc-link-search={}", path); } } AppleVariant::Other => (), } } _ => (), } // copy DLLs to target if build_shared_libs { let libs_assets = extract_lib_assets(&out_dir); for asset in libs_assets { let asset_clone = asset.clone(); let filename = asset_clone.file_name().unwrap(); let filename = filename.to_str().unwrap(); let dst = target_dir.join(filename); debug_log!("HARD LINK {} TO {}", asset.display(), dst.display()); if !dst.exists() { std::fs::hard_link(asset.clone(), dst).unwrap(); } // Copy DLLs to examples as well if target_dir.join("examples").exists() { let dst = target_dir.join("examples").join(filename); debug_log!("HARD LINK {} TO {}", asset.display(), dst.display()); if !dst.exists() { std::fs::hard_link(asset.clone(), dst).unwrap(); } } // Copy DLLs to target/profile/deps as well for tests let dst = target_dir.join("deps").join(filename); debug_log!("HARD LINK {} TO {}", asset.display(), dst.display()); if !dst.exists() { std::fs::hard_link(asset.clone(), dst).unwrap(); } } } }