Skip to main content

llama_cpp_bindings_build/
lib.rs

1//! Build system for llama-cpp-bindings-sys FFI bindings to llama.cpp.
2
3mod android_ndk;
4mod bindgen_config;
5mod cmake_config;
6mod cpp_wrapper;
7mod cpp_wrapper_mtmd;
8mod library_asset_extraction;
9mod library_linking;
10mod library_name_extraction;
11mod rebuild_tracking;
12mod shared_libs;
13mod target_os;
14
15use std::env;
16use std::path::{Path, PathBuf};
17
18use android_ndk::AndroidNdk;
19use target_os::TargetOs;
20
21#[macro_export]
22macro_rules! debug_log {
23    ($($arg:tt)*) => {
24        if std::env::var("BUILD_DEBUG").is_ok() {
25            println!("cargo:warning=[DEBUG] {}", format!($($arg)*));
26        }
27    };
28}
29
30/// Shared state passed between build phases.
31#[derive(Debug)]
32pub struct BuildContext {
33    pub out_dir: PathBuf,
34    pub target_dir: PathBuf,
35    pub llama_src: PathBuf,
36    pub target_os: TargetOs,
37    pub target_triple: String,
38    pub build_shared_libs: bool,
39    pub profile: String,
40    pub static_crt: bool,
41    pub android_ndk: Option<AndroidNdk>,
42}
43
44impl BuildContext {
45    fn detect() -> Self {
46        let target_triple =
47            env::var("TARGET").expect("TARGET env var is required in build scripts");
48        let target_os = TargetOs::from_target_triple(&target_triple)
49            .unwrap_or_else(|error| panic!("Failed to parse target OS: {error}"));
50        let out_dir = PathBuf::from(
51            env::var("OUT_DIR").expect("OUT_DIR env var is required in build scripts"),
52        );
53        let target_dir = cargo_target_dir(&out_dir);
54        let manifest_dir = env::var("CARGO_MANIFEST_DIR")
55            .expect("CARGO_MANIFEST_DIR env var is required in build scripts");
56        let llama_src = Path::new(&manifest_dir).join("llama.cpp");
57
58        let build_shared_libs = env::var("LLAMA_BUILD_SHARED_LIBS")
59            .map_or_else(|_| cfg!(feature = "dynamic-link"), |value| value == "1");
60
61        let profile = env::var("LLAMA_LIB_PROFILE").unwrap_or_else(|_| "Release".to_string());
62
63        let static_crt = env::var("LLAMA_STATIC_CRT")
64            .map(|value| value == "1")
65            .unwrap_or(false);
66
67        let android_ndk = if target_os.is_android() {
68            Some(
69                AndroidNdk::detect(&target_triple)
70                    .unwrap_or_else(|error| panic!("Android NDK detection failed: {error}")),
71            )
72        } else {
73            None
74        };
75
76        debug_log!("TARGET: {}", target_triple);
77        debug_log!("CARGO_MANIFEST_DIR: {}", manifest_dir);
78        debug_log!("TARGET_DIR: {}", target_dir.display());
79        debug_log!("OUT_DIR: {}", out_dir.display());
80        debug_log!("BUILD_SHARED: {}", build_shared_libs);
81
82        Self {
83            out_dir,
84            target_dir,
85            llama_src,
86            target_os,
87            target_triple,
88            build_shared_libs,
89            profile,
90            static_crt,
91            android_ndk,
92        }
93    }
94}
95
96fn cargo_target_dir(out_dir: &Path) -> PathBuf {
97    out_dir
98        .ancestors()
99        .nth(3)
100        .expect("OUT_DIR is not deep enough to determine target directory")
101        .to_path_buf()
102}
103
104fn set_cmake_parallelism() {
105    if let Ok(parallelism) = std::thread::available_parallelism() {
106        // SAFETY: build scripts are single-threaded, so modifying env is safe.
107        unsafe {
108            env::set_var("CMAKE_BUILD_PARALLEL_LEVEL", parallelism.get().to_string());
109        }
110    }
111}
112
113/// Main entry point for the llama.cpp build system.
114///
115/// Call this from `build.rs` in `llama-cpp-bindings-sys`.
116pub fn build() {
117    let context = BuildContext::detect();
118
119    rebuild_tracking::register_rebuild_triggers(&context.llama_src);
120
121    set_cmake_parallelism();
122
123    bindgen_config::generate_bindings(
124        &context.llama_src,
125        &context.out_dir,
126        &context.target_os,
127        &context.target_triple,
128        context.android_ndk.as_ref(),
129        cfg!(feature = "mtmd"),
130    );
131
132    cpp_wrapper::compile_cpp_wrappers(&context.llama_src, &context.target_os);
133
134    let build_dir = cmake_config::configure_and_build(
135        &context.llama_src,
136        &context.target_os,
137        &context.target_triple,
138        context.build_shared_libs,
139        &context.profile,
140        context.static_crt,
141        context.android_ndk.as_ref(),
142    );
143
144    if cfg!(feature = "mtmd") {
145        cpp_wrapper_mtmd::compile_mtmd(&context.llama_src, &context.target_os);
146    }
147
148    library_linking::link_libraries(
149        &context.out_dir,
150        &build_dir,
151        &context.target_os,
152        &context.target_triple,
153        context.build_shared_libs,
154        &context.profile,
155    );
156
157    if context.build_shared_libs {
158        shared_libs::copy_shared_libraries(&context.out_dir, &context.target_dir);
159    }
160}