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