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 regex::Regex; #[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"))?; Ok(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 && 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()?; } 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 itanium_pat = Regex::new(r"^[\da-f]*\s[A-Z]\s(.*_Z(\d+)(libCZI_.*))$")?; let msvc_pat = Regex::new(r"^[\da-f]*\s[TDB]\s(\?(libCZI_\w+)@@.*)$")?; let mut map = HashMap::new(); for line in std::str::from_utf8(&cmd.stdout)?.lines() { let line = line.trim(); if let Some(caps) = itanium_pat.captures(line) { if let (Some(symbol), Some(n), Some(name)) = (caps.get(1), caps.get(2), caps.get(3)) { let n: usize = n.as_str().parse()?; let name = name.as_str(); let demangled = name[..n].to_string(); let mangled = symbol.as_str().to_string(); 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, mangled); } } } else if let Some(caps) = msvc_pat.captures(line) { let mangled = caps.get(1).unwrap().as_str().to_string(); if let Some(name_match) = caps.get(2) { let demangled = name_match.as_str().to_string(); 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, mangled); } } } } Ok(Self { map }) } } #[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() } }