extern crate bindgen; use std::env; use std::path::{Path, PathBuf}; use std::fmt::Debug; #[cfg(not(feature = "dynamic"))] use bindgen::callbacks::ItemInfo; #[cfg(not(feature = "dynamic"))] use std::collections::HashMap; use std::fmt::{Display, Formatter}; fn find_static_lib(dir: &Path, base_name: &str, target_is_windows: bool) -> Option { if !dir.is_dir() { return None; } let prefixes = ["", "lib"]; let extensions = if target_is_windows { &[".lib", ".a"][..] } else { &[".a", ".lib"][..] }; for prefix in prefixes { for ext in extensions { let name = format!("{}{}{}", prefix, base_name, ext); let path = dir.join(&name); if path.exists() { return Some(path); } } } None } fn lib_name_from_path(lib_path: &Path) -> Result> { let file_name = lib_path .file_stem() .ok_or(Error::msg("cannot get file stem"))? .to_str() .ok_or(Error::msg("cannot into string"))?; // cargo::rustc-link-lib=static=NAME tells the linker to find libNAME.a / NAME.lib, // so strip any leading "lib" prefix to avoid liblibNAME. Ok(file_name.strip_prefix("lib").unwrap_or(file_name).to_string()) } struct Error(String); impl Debug for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } impl std::error::Error for Error {} impl Error { fn msg(msg: S) -> Box { Box::new(Error(msg.to_string())) } } fn fix_xwin_lib_case() -> Result<(), Box> { let toolchain_key = format!( "CMAKE_TOOLCHAIN_FILE_{}", env::var("TARGET")?.replace('-', "_") ); let toolchain_file = env::var(&toolchain_key) .or_else(|_| env::var("TARGET_CMAKE_TOOLCHAIN_FILE")) .or_else(|_| env::var("CMAKE_TOOLCHAIN_FILE"))?; let toolchain_path = PathBuf::from(&toolchain_file); let cargo_xwin_base = toolchain_path .parent() .and_then(|p| p.parent()) .and_then(|p| p.parent()) .ok_or(Error::msg("cannot determine cargo-xwin base dir"))?; let xwin_sdk_lib = cargo_xwin_base.join("xwin/sdk/lib/um/x86_64"); if !xwin_sdk_lib.exists() { return Ok(()); } #[cfg(unix)] { use std::os::unix::fs::symlink; let fixes = [ ("Windowscodecs.lib", "WINDOWSCODECS.lib"), ("Windowscodecs.lib", "windowscodecs.lib"), ]; for (link_name, target_name) in &fixes { let target = xwin_sdk_lib.join(target_name); let link = xwin_sdk_lib.join(link_name); if target.exists() && !link.exists() { let _ = symlink(&target, &link); } } } Ok(()) } fn main() -> Result<(), Box> { if env::var("DOCS_RS").is_err() { let host = env::var("HOST")?; let target = env::var("TARGET")?; let is_cross = host != target; let is_windows = target.contains("windows"); let _is_macos = target.contains("apple"); let libczi_dir = PathBuf::from("libczi"); let libczi_src = libczi_dir.join("Src/libCZI"); let libcziapi_inc = libczi_dir.join("Src/libCZIAPI/inc"); let libcziapi_src = libczi_dir.join("Src/libCZIAPI/src"); let libcziapi_h = libcziapi_inc.join("libCZIApi.h"); let mut cmake_config = cmake::Config::new(&libczi_dir); cmake_config .cxxflag("-fms-extensions") .define("LIBCZI_BUILD_UNITTESTS", "OFF") .define("LIBCZI_BUILD_CZICMD", "OFF") .define("LIBCZI_BUILD_DYNLIB", "OFF") .define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_EIGEN3", "OFF") .define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_ZSTD", "OFF") .define("LIBCZI_BUILD_CURL_BASED_STREAM", "OFF") .define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_LIBCURL", "OFF") .define("LIBCZI_BUILD_AZURESDK_BASED_STREAM", "OFF") .define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_RAPIDJSON", "OFF") .define("LIBCZI_BUILD_LIBCZIAPI", "ON"); if is_cross { if is_windows { cmake_config .define("CMAKE_SYSTEM_NAME", "Windows") .define("CRASH_ON_UNALIGNED_ACCESS", "FALSE") .define("ADDITIONAL_LIBS_REQUIRED_FOR_ATOMIC", ""); let has_toolchain_file = env::var("TARGET_CMAKE_TOOLCHAIN_FILE").is_ok() || env::var(format!( "CMAKE_TOOLCHAIN_FILE_{}", target.replace('-', "_") )) .is_ok() || env::var("CMAKE_TOOLCHAIN_FILE").is_ok(); if !has_toolchain_file { cmake_config .define("CMAKE_C_COMPILER", "clang") .define("CMAKE_CXX_COMPILER", "clang++") .define("CMAKE_RC_COMPILER", "llvm-rc"); } fix_xwin_lib_case()?; } else if target.contains("apple") { cmake_config .define("CMAKE_SYSTEM_NAME", "Darwin") .define("ADDITIONAL_LIBS_REQUIRED_FOR_ATOMIC", "") .define("CRASH_ON_UNALIGNED_ACCESS", "OFF"); if target.contains("aarch64") { cmake_config.define("NEON_INTRINSICS_CAN_BE_USED", "ON"); } } } if is_cross && is_windows { cmake_config.build_target("libCZIAPIStatic"); } let dst = cmake_config.build(); #[cfg(not(feature = "dynamic"))] let bindings = { let libcziapi_search_dirs = [ dst.join("build/Src/libCZIAPI"), dst.join("Src/libCZIAPI"), dst.join("lib"), dst.join("lib64"), ]; let libcziapi_static = libcziapi_search_dirs .iter() .find_map(|dir| find_static_lib(dir, "libCZIAPIStatic", is_windows)) .ok_or(Error::msg("cannot find libCZIAPIStatic library"))?; bindgen::Builder::default().parse_callbacks(Box::new(DeMangler::new( libcziapi_static, is_cross && is_windows, )?)) }; #[cfg(feature = "dynamic")] let bindings = bindgen::Builder::default(); let bindings = bindings .merge_extern_blocks(true) .clang_args([ "-fms-extensions", "-x", "c++", "-std=c++14", "-I", libcziapi_inc .to_str() .ok_or(Error::msg("cannot into string"))?, "-I", libcziapi_src .to_str() .ok_or(Error::msg("cannot into string"))?, "-I", libczi_src .to_str() .ok_or(Error::msg("cannot into string"))?, ]) .header( libcziapi_h .to_str() .ok_or(Error::msg("cannot into string"))?, ) .generate()?; bindings.write_to_file(dst.join("lib_czi_api.rs"))?; #[cfg(not(feature = "dynamic"))] { let libcziapi_dir = dst.join("build/Src/libCZIAPI"); let libcziapi_dir2 = dst.join("Src/libCZIAPI"); let libczi_dir = dst.join("build/Src/libCZI"); let libczi_dir2 = dst.join("Src/libCZI"); let libcziapi_lib = find_static_lib(&libcziapi_dir, "libCZIAPIStatic", is_windows) .or_else(|| find_static_lib(&libcziapi_dir2, "libCZIAPIStatic", is_windows)) .ok_or(Error::msg("cannot find libCZIAPIStatic for linking"))?; let libcziapi_name = lib_name_from_path(&libcziapi_lib)?; let profile = env::var("PROFILE")?; let libczi_base = match profile.as_str() { "debug" => "libCZIStaticd", "release" => "libCZIStatic", _ => return Err(Error::msg(format!("unsupported profile: {}", profile))), }; let libczi_lib = find_static_lib(&libczi_dir, libczi_base, is_windows) .or_else(|| find_static_lib(&libczi_dir2, libczi_base, is_windows)) .ok_or(Error::msg(format!( "cannot find {} for linking", libczi_base )))?; let libczi_name = lib_name_from_path(&libczi_lib)?; if libcziapi_dir.exists() { println!( "cargo::rustc-link-search=native={}", libcziapi_dir.display() ); } if libcziapi_dir2.exists() { println!( "cargo::rustc-link-search=native={}", libcziapi_dir2.display() ); } println!("cargo::rustc-link-lib=static={}", libcziapi_name); if libczi_dir.exists() { println!( "cargo::rustc-link-search=native={}", libczi_dir.display() ); } if libczi_dir2.exists() { println!( "cargo::rustc-link-search=native={}", libczi_dir2.display() ); } println!("cargo::rustc-link-lib=static={}", libczi_name); println!( "cargo::rustc-link-search=native={}", dst.join("lib").display() ); println!( "cargo::rustc-link-search=native={}", dst.join("lib64").display() ); if is_windows && is_cross { let zstd_dir = dst.join("build/_deps/zstd-build/lib"); let zstd_dir2 = dst.join("_deps/zstd-build/lib"); if zstd_dir.exists() { println!( "cargo::rustc-link-search=native={}", zstd_dir.display() ); } if zstd_dir2.exists() { println!( "cargo::rustc-link-search=native={}", zstd_dir2.display() ); } let zstd_name = find_static_lib(&zstd_dir, "zstd_static", is_windows) .or_else(|| find_static_lib(&zstd_dir2, "zstd_static", is_windows)) .map(|p| lib_name_from_path(&p).unwrap_or_else(|_| "zstd_static".to_string())) .unwrap_or_else(|| "zstd_static".to_string()); println!("cargo::rustc-link-lib=static={}", zstd_name); } else { println!("cargo::rustc-link-lib=static=zstd"); } if is_windows { println!("cargo::rustc-link-lib=kernel32"); println!("cargo::rustc-link-lib=advapi32"); println!("cargo::rustc-link-lib=bcrypt"); println!("cargo::rustc-link-lib=ntdll"); println!("cargo::rustc-link-lib=user32"); println!("cargo::rustc-link-lib=ole32"); println!("cargo::rustc-link-lib=shell32"); println!("cargo::rustc-link-lib=Windowscodecs"); } } #[cfg(feature = "dynamic")] { println!( "cargo::rustc-link-search=native={}", dst.join("build/Src/libCZIAPI").display() ); println!("cargo::rustc-link-lib=libCZIAPI"); } } println!("cargo::rerun-if-changed=build.rs"); Ok(()) } #[cfg(not(feature = "dynamic"))] #[derive(Debug)] struct DeMangler { map: HashMap, } #[cfg(not(feature = "dynamic"))] impl DeMangler { fn new(a_file: PathBuf, use_llvm_nm: bool) -> Result> { let nm_cmd = if use_llvm_nm { "llvm-nm" } else { "nm" }; let cmd = std::process::Command::new(nm_cmd) .arg("--defined-only") .arg(&a_file) .output()?; let mut map = HashMap::new(); for line in std::str::from_utf8(&cmd.stdout)?.lines() { let line = line.trim(); let mangled = if let Some(sym) = line.split_whitespace().last() { sym } else { continue; }; let plain_name = Self::demangle(mangled); if let Some(name) = plain_name { if name.starts_with("libCZI_") { Self::insert_mapping(&mut map, &name, mangled, &a_file)?; } } } Ok(Self { map }) } fn demangle(symbol: &str) -> Option { // Only demangle actual function symbols, not catch/dtor/cppxdata metadata if let Some(rest) = symbol.strip_prefix('?') { // MSVC: real functions start with "libCZI_", not "catch$", "dtor$", etc. if !rest.starts_with("libCZI_") { return None; } let flags = msvc_demangler::DemangleFlags::llvm(); let demangled = msvc_demangler::demangle(symbol, flags).ok()?; Self::extract_msvc_name(&demangled) } else if symbol.starts_with("_Z") { // Itanium: demangle and extract the function name let sym = cpp_demangle::Symbol::new(symbol.as_bytes()).ok()?; let demangled = sym.demangle().ok()?; // e.g. "libCZI_Foo(int, bool)" -> "libCZI_Foo" // e.g. "ns::libCZI_Foo(int)" -> "libCZI_Foo" demangled.split('(').next().and_then(|s| { let s = s.trim(); if let Some(name) = s.rsplit("::").next() { Some(name.to_string()) } else { Some(s.to_string()) } }) } else { None } } fn extract_msvc_name(demangled: &str) -> Option { // Find the identifier that looks like a function name. // MSVC demangled output: "return_type calling_conv name(params)" for word in demangled.split_whitespace() { if let Some(rest) = word.strip_prefix("libCZI_") { let name = format!("libCZI_{}", rest.split('(').next().unwrap_or(rest)); return Some(name); } } None } fn insert_mapping( map: &mut HashMap, demangled: &str, mangled: &str, a_file: &Path, ) -> Result<(), Box> { if let Some(existing_mangled) = map.get(demangled) { if existing_mangled != mangled { return Err(Error::msg(format!( "conflicting mangled symbols for {} in {}: {}, {}", demangled, a_file.to_string_lossy(), existing_mangled, mangled ))); } } else { map.insert(demangled.to_string(), mangled.to_string()); } Ok(()) } } #[cfg(not(feature = "dynamic"))] impl bindgen::callbacks::ParseCallbacks for DeMangler { fn generated_link_name_override(&self, item_info: ItemInfo<'_>) -> Option { self.map.get(item_info.name).cloned() } }