#![allow(clippy::uninlined_format_args)] extern crate bindgen; use cmake::Config; use std::env; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; fn main() { let target = env::var("TARGET").unwrap(); // Link C++ standard library if let Some(cpp_stdlib) = get_cpp_link_stdlib(&target) { println!("cargo:rustc-link-lib=dylib={}", cpp_stdlib); } // Link macOS Accelerate framework for matrix calculations if target.contains("apple") { println!("cargo:rustc-link-lib=framework=Accelerate"); #[cfg(feature = "coreml")] { println!("cargo:rustc-link-lib=framework=Foundation"); println!("cargo:rustc-link-lib=framework=CoreML"); } #[cfg(feature = "metal")] { println!("cargo:rustc-link-lib=framework=Foundation"); println!("cargo:rustc-link-lib=framework=Metal"); println!("cargo:rustc-link-lib=framework=MetalKit"); } } #[cfg(feature = "coreml")] println!("cargo:rustc-link-lib=static=whisper.coreml"); #[cfg(feature = "openblas")] { if let Ok(openblas_path) = env::var("OPENBLAS_PATH") { println!( "cargo::rustc-link-search={}", PathBuf::from(openblas_path).join("lib").display() ); } if cfg!(windows) { println!("cargo:rustc-link-lib=libopenblas"); } else { println!("cargo:rustc-link-lib=openblas"); } } #[cfg(feature = "cuda")] { println!("cargo:rustc-link-lib=cublas"); println!("cargo:rustc-link-lib=cudart"); println!("cargo:rustc-link-lib=cublasLt"); println!("cargo:rustc-link-lib=cuda"); cfg_if::cfg_if! { if #[cfg(target_os = "windows")] { let cuda_path = PathBuf::from(env::var("CUDA_PATH").unwrap()).join("lib/x64"); println!("cargo:rustc-link-search={}", cuda_path.display()); } else { println!("cargo:rustc-link-lib=culibos"); println!("cargo:rustc-link-search=/usr/local/cuda/lib64"); println!("cargo:rustc-link-search=/usr/local/cuda/lib64/stubs"); println!("cargo:rustc-link-search=/opt/cuda/lib64"); println!("cargo:rustc-link-search=/opt/cuda/lib64/stubs"); } } } #[cfg(feature = "hipblas")] { println!("cargo:rustc-link-lib=hipblas"); println!("cargo:rustc-link-lib=rocblas"); println!("cargo:rustc-link-lib=amdhip64"); cfg_if::cfg_if! { if #[cfg(target_os = "windows")] { panic!("Due to a problem with the last revision of the ROCm 5.7 library, it is not possible to compile the library for the windows environment.\nSee https://github.com/ggerganov/whisper.cpp/issues/2202 for more details.") } else { println!("cargo:rerun-if-env-changed=HIP_PATH"); let hip_path = match env::var("HIP_PATH") { Ok(path) =>PathBuf::from(path), Err(_) => PathBuf::from("/opt/rocm"), }; let hip_lib_path = hip_path.join("lib"); println!("cargo:rustc-link-search={}",hip_lib_path.display()); } } } #[cfg(feature = "openmp")] { if target.contains("gnu") { println!("cargo:rustc-link-lib=gomp"); } else if target.contains("apple") { println!("cargo:rustc-link-lib=omp"); println!("cargo:rustc-link-search=/opt/homebrew/opt/libomp/lib"); } } println!("cargo:rerun-if-changed=wrapper.h"); let out = PathBuf::from(env::var("OUT_DIR").unwrap()); let whisper_root = out.join("whisper.cpp/"); if !whisper_root.exists() { std::fs::create_dir_all(&whisper_root).unwrap(); fs_extra::dir::copy("./whisper.cpp", &out, &Default::default()).unwrap_or_else(|e| { panic!( "Failed to copy whisper sources into {}: {}", whisper_root.display(), e ) }); } if env::var("WHISPER_DONT_GENERATE_BINDINGS").is_ok() { let _: u64 = std::fs::copy("src/bindings.rs", out.join("bindings.rs")) .expect("Failed to copy bindings.rs"); } else { let bindings = bindgen::Builder::default().header("wrapper.h"); #[cfg(feature = "metal")] let bindings = bindings.header("whisper.cpp/ggml/include/ggml-metal.h"); let bindings = bindings .clang_arg("-I./whisper.cpp/") .clang_arg("-I./whisper.cpp/include") .clang_arg("-I./whisper.cpp/ggml/include") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate(); match bindings { Ok(b) => { let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); b.write_to_file(out_path.join("bindings.rs")) .expect("Couldn't write bindings!"); } Err(e) => { println!("cargo:warning=Unable to generate bindings: {}", e); println!("cargo:warning=Using bundled bindings.rs, which may be out of date"); // copy src/bindings.rs to OUT_DIR std::fs::copy("src/bindings.rs", out.join("bindings.rs")) .expect("Unable to copy bindings.rs"); } } }; // stop if we're on docs.rs if env::var("DOCS_RS").is_ok() { return; } let mut config = Config::new(&whisper_root); config .profile("Release") .define("BUILD_SHARED_LIBS", "OFF") .define("WHISPER_ALL_WARNINGS", "OFF") .define("WHISPER_ALL_WARNINGS_3RD_PARTY", "OFF") .define("WHISPER_BUILD_TESTS", "OFF") .define("WHISPER_BUILD_EXAMPLES", "OFF") .very_verbose(true) .pic(true); if cfg!(target_os = "windows") { config.cxxflag("/utf-8"); } if cfg!(feature = "coreml") { config.define("WHISPER_COREML", "ON"); config.define("WHISPER_COREML_ALLOW_FALLBACK", "1"); } if cfg!(feature = "cuda") { config.define("GGML_CUDA", "ON"); config.define("CMAKE_POSITION_INDEPENDENT_CODE", "ON"); config.define("CMAKE_CUDA_FLAGS", "-Xcompiler=-fPIC"); } if cfg!(feature = "hipblas") { config.define("GGML_HIPBLAS", "ON"); config.define("CMAKE_C_COMPILER", "hipcc"); config.define("CMAKE_CXX_COMPILER", "hipcc"); println!("cargo:rerun-if-env-changed=AMDGPU_TARGETS"); if let Ok(gpu_targets) = env::var("AMDGPU_TARGETS") { config.define("AMDGPU_TARGETS", gpu_targets); } } if cfg!(feature = "vulkan") { config.define("GGML_VULKAN", "ON"); if cfg!(windows) { println!("cargo:rerun-if-env-changed=VULKAN_SDK"); println!("cargo:rustc-link-lib=vulkan-1"); let vulkan_path = match env::var("VULKAN_SDK") { Ok(path) => PathBuf::from(path), Err(_) => panic!( "Please install Vulkan SDK and ensure that VULKAN_SDK env variable is set" ), }; let vulkan_lib_path = vulkan_path.join("Lib"); println!("cargo:rustc-link-search={}", vulkan_lib_path.display()); } else if cfg!(target_os = "macos") { println!("cargo:rerun-if-env-changed=VULKAN_SDK"); println!("cargo:rustc-link-lib=vulkan"); let vulkan_path = match env::var("VULKAN_SDK") { Ok(path) => PathBuf::from(path), Err(_) => panic!( "Please install Vulkan SDK and ensure that VULKAN_SDK env variable is set" ), }; let vulkan_lib_path = vulkan_path.join("lib"); println!("cargo:rustc-link-search={}", vulkan_lib_path.display()); } else { println!("cargo:rustc-link-lib=vulkan"); } } if cfg!(feature = "openblas") { config.define("GGML_BLAS", "ON"); config.define("GGML_BLAS_VENDOR", "OpenBLAS"); if env::var("BLAS_INCLUDE_DIRS").is_err() { panic!("BLAS_INCLUDE_DIRS environment variable must be set when using OpenBLAS"); } config.define("BLAS_INCLUDE_DIRS", env::var("BLAS_INCLUDE_DIRS").unwrap()); println!("cargo:rerun-if-env-changed=BLAS_INCLUDE_DIRS"); } if cfg!(feature = "metal") { config.define("GGML_METAL", "ON"); config.define("GGML_METAL_NDEBUG", "ON"); config.define("GGML_METAL_EMBED_LIBRARY", "ON"); } else { // Metal is enabled by default, so we need to explicitly disable it config.define("GGML_METAL", "OFF"); } if cfg!(debug_assertions) || cfg!(feature = "force-debug") { // debug builds are too slow to even remotely be usable, // so we build with optimizations even in debug mode config.define("CMAKE_BUILD_TYPE", "RelWithDebInfo"); config.cxxflag("-DWHISPER_DEBUG"); } // Allow passing any WHISPER or CMAKE compile flags for (key, value) in env::vars() { let is_whisper_flag = key.starts_with("WHISPER_") && key != "WHISPER_DONT_GENERATE_BINDINGS"; let is_cmake_flag = key.starts_with("CMAKE_"); if is_whisper_flag || is_cmake_flag { config.define(&key, &value); } } if cfg!(not(feature = "openmp")) { config.define("GGML_OPENMP", "OFF"); } let destination = config.build(); add_link_search_path(&out.join("build")).unwrap(); println!("cargo:rustc-link-search=native={}", destination.display()); println!("cargo:rustc-link-lib=static=whisper"); println!("cargo:rustc-link-lib=static=ggml"); println!("cargo:rustc-link-lib=static=ggml-base"); println!("cargo:rustc-link-lib=static=ggml-cpu"); if cfg!(target_os = "macos") || cfg!(feature = "openblas") { println!("cargo:rustc-link-lib=static=ggml-blas"); } if cfg!(feature = "vulkan") { println!("cargo:rustc-link-lib=static=ggml-vulkan"); } if cfg!(feature = "metal") { println!("cargo:rustc-link-lib=static=ggml-metal"); } if cfg!(feature = "cuda") { println!("cargo:rustc-link-lib=static=ggml-cuda"); } if cfg!(feature = "openblas") { println!("cargo:rustc-link-lib=static=ggml-blas"); } println!( "cargo:WHISPER_CPP_VERSION={}", get_whisper_cpp_version(&whisper_root) .expect("Failed to read whisper.cpp CMake config") .expect("Could not find whisper.cpp version declaration"), ); // for whatever reason this file is generated during build and triggers cargo complaining _ = std::fs::remove_file("bindings/javascript/package.json"); } // From https://github.com/alexcrichton/cc-rs/blob/fba7feded71ee4f63cfe885673ead6d7b4f2f454/src/lib.rs#L2462 fn get_cpp_link_stdlib(target: &str) -> Option<&'static str> { if target.contains("msvc") { None } else if target.contains("apple") || target.contains("freebsd") || target.contains("openbsd") { Some("c++") } else if target.contains("android") { Some("c++_shared") } else { Some("stdc++") } } fn add_link_search_path(dir: &std::path::Path) -> std::io::Result<()> { if dir.is_dir() { println!("cargo:rustc-link-search={}", dir.display()); for entry in std::fs::read_dir(dir)? { add_link_search_path(&entry?.path())?; } } Ok(()) } fn get_whisper_cpp_version(whisper_root: &std::path::Path) -> std::io::Result> { let cmake_lists = BufReader::new(File::open(whisper_root.join("CMakeLists.txt"))?); for line in cmake_lists.lines() { let line = line?; if let Some(suffix) = line.strip_prefix(r#"project("whisper.cpp" VERSION "#) { let whisper_cpp_version = suffix.trim_end_matches(')'); return Ok(Some(whisper_cpp_version.into())); } } Ok(None) }