diff --git a/bindings/rust/sys/build.rs b/bindings/rust/sys/build.rs index 8611200d..20a65476 100644 --- a/bindings/rust/sys/build.rs +++ b/bindings/rust/sys/build.rs @@ -365,6 +365,12 @@ fn emit_link_lines(prefix: &Path, manifest_path: &Path) { let lib_dir = prefix.join(json["lib_dir"].as_str().unwrap_or("lib")); println!("cargo:rustc-link-search=native={}", lib_dir.display()); + // Extra linker search directories recorded at configure time (e.g. the + // CUDA toolkit's library dir, which is rarely on the default path). + for dir in strs("search_dirs") { + println!("cargo:rustc-link-search=native={dir}"); + } + // Archives (static) or the single shared lib. The manifest order is // single-pass-GNU-ld safe (each archive's undefined refs resolve in a // later one: transcribe -> ggml -> backends -> ggml-base). diff --git a/cmake/transcribe-install.cmake b/cmake/transcribe-install.cmake index 586799cb..32a18a00 100644 --- a/cmake/transcribe-install.cmake +++ b/cmake/transcribe-install.cmake @@ -88,6 +88,7 @@ endif() set(_libraries transcribe) set(_library_paths "") set(_system_libs "") +set(_search_dirs "") set(_frameworks "") set(_link_flags "") @@ -137,6 +138,25 @@ if(NOT TRANSCRIBE_BUILD_SHARED) if("vulkan" IN_LIST _kinds) list(APPEND _system_libs vulkan) endif() + if("cuda" IN_LIST _kinds) + # The ggml-cuda archive references the CUDA runtime and cuBLAS and — + # unless VMM was disabled at configure time — the CUDA driver API + # (mirrors ggml/src/ggml-cuda/CMakeLists.txt). Record the toolkit's + # library directory too: it is rarely on the default linker path, so + # names alone leave every non-CMake consumer guessing an install + # root. The stubs directory satisfies `-lcuda` on machines that + # build without a display driver; at runtime the real libcuda + # resolves through the loader as usual. + find_package(CUDAToolkit REQUIRED) + list(APPEND _system_libs cudart cublas) + if(NOT GGML_CUDA_NO_VMM) + list(APPEND _system_libs cuda) + endif() + list(APPEND _search_dirs "${CUDAToolkit_LIBRARY_DIR}") + if(EXISTS "${CUDAToolkit_LIBRARY_DIR}/stubs") + list(APPEND _search_dirs "${CUDAToolkit_LIBRARY_DIR}/stubs") + endif() + endif() if(_frameworks) list(REMOVE_DUPLICATES _frameworks) endif() @@ -184,6 +204,7 @@ endfunction() _transcribe_json_strings(_kinds_json ${_kinds}) _transcribe_json_strings(_libraries_json ${_libraries}) _transcribe_json_strings(_library_paths_json ${_library_paths}) +_transcribe_json_strings(_search_dirs_json ${_search_dirs}) _transcribe_json_strings(_system_libs_json ${_system_libs}) _transcribe_json_strings(_frameworks_json ${_frameworks}) _transcribe_json_strings(_link_flags_json ${_link_flags}) @@ -201,6 +222,7 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/transcribe-link.json" \"lib_dir\": \"${CMAKE_INSTALL_LIBDIR}\", \"libraries\": [${_libraries_json}], \"library_paths\": [${_library_paths_json}], + \"search_dirs\": [${_search_dirs_json}], \"system_libs\": [${_system_libs_json}], \"frameworks\": [${_frameworks_json}], \"link_flags\": [${_link_flags_json}] diff --git a/docs/bindings.md b/docs/bindings.md index 6d993359..07ea692a 100644 --- a/docs/bindings.md +++ b/docs/bindings.md @@ -35,11 +35,14 @@ binding's generated files: - **`lib/transcribe-link.json`** — installed by `cmake --install` (the `TRANSCRIBE_INSTALL` rules): the machine-readable link interface for non-CMake consumers building from source (the Rust `-sys` crate's - `build.rs`). Archive order, system libs, frameworks, flags, and — for - `GGML_BACKEND_DL` installs — `module_dir`, the directory to hand to - `transcribe_init_backends()`. Proven per push by the link-smoke CI lane, - which compiles a toy C consumer from nothing but this manifest in both - static and shared postures. + `build.rs`). Archive order, system libs, `search_dirs` (extra linker + search directories recorded at configure time — e.g. the CUDA toolkit's + library dir and its driver-stub subdirectory, which back the `cudart` / + `cublas` / `cuda` entries CUDA builds add to `system_libs`), frameworks, + flags, and — for `GGML_BACKEND_DL` installs — `module_dir`, the + directory to hand to `transcribe_init_backends()`. Proven per push by + the link-smoke CI lane, which compiles a toy C consumer from nothing but + this manifest in both static and shared postures. ## Result text pointers: copy at the FFI boundary diff --git a/scripts/ci/link_smoke.py b/scripts/ci/link_smoke.py index 6f2a4323..e8d3cd6b 100644 --- a/scripts/ci/link_smoke.py +++ b/scripts/ci/link_smoke.py @@ -49,6 +49,9 @@ def main() -> int: cmd = [args.cc, str(SOURCE), f"-I{include_dir}", "-o", str(out)] cmd += manifest["link_flags"] cmd += [f"-L{lib_dir}"] + # Configure-time search dirs (e.g. the CUDA toolkit's library dir); + # absent in pre-0.1.4 manifests. + cmd += [f"-L{path}" for path in manifest.get("search_dirs", [])] libs = [f"-l{name}" for name in manifest["libraries"]] if manifest["libraries"][1:] and platform.system() == "Linux":