use std::borrow::Cow; use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use std::{env, fs}; use walkdir::WalkDir; fn main() { let target = env::var("TARGET").expect("gnurx-sys: Environment variable 'TARGET' was not defined."); if !target.ends_with("-pc-windows-gnu") { return; // Nothing to build for this architecture. } let out_dir = env::var_os("OUT_DIR") .map(PathBuf::from) .expect("gnurx-sys: Environment variable 'OUT_DIR' was not defined."); println!("cargo:root={}", out_dir.display()); let regex_header = if let Some(prefix) = target_env_var_os("GNURX_LIB_DIR_PREFIX", &target) { let prefix = if let Ok(prefix) = dunce::canonicalize(&prefix) { prefix } else { panic!( "gnurx-sys: Failed to canonicalize '{}'.", Path::new(&prefix).display() ); }; use_shared_external_lib(&prefix) } else { build_static_lib(&target, &out_dir) }; let sysroot = target_env_var_os("SYSROOT", &target).map(PathBuf::from); generate_bindings(&target, sysroot.as_deref(), &out_dir, ®ex_header) } fn use_shared_external_lib(prefix: &Path) -> PathBuf { println!("cargo:include={}", prefix.join("include").display()); let regex_header = prefix.join("include").join("regex.h"); println!("cargo:rerun-if-changed={}", regex_header.display()); if regex_header.metadata().is_err() { panic!("gnurx-sys: Failed to find '{}'.", regex_header.display()) }; let shared_lib_dir = prefix.join("bin"); println!("cargo:lib={}", shared_lib_dir.display()); println!( "cargo:rustc-link-search=native={}", shared_lib_dir.display() ); let shared_lib_path = shared_lib_dir.join("libgnurx-0.dll"); println!("cargo:rerun-if-changed={}", shared_lib_path.display()); if shared_lib_path.metadata().is_err() { panic!("gnurx-sys: Failed to find '{}'.", shared_lib_path.display()) }; println!("cargo:rustc-link-lib=dylib=libgnurx-0"); regex_header } fn build_static_lib(target: &str, out_dir: &Path) -> PathBuf { let src_dir = env::var_os("CARGO_MANIFEST_DIR") .map(PathBuf::from) .expect("gnurx-sys: Environment variable 'CARGO_MANIFEST_DIR' was not defined."); let lib_src_dir = src_dir.join("libgnurx"); for &name in &["CC", "CFLAGS", "AR", "ARFLAGS"] { rerun_if_env_changed(name, target); } rerun_if_dir_changed(&lib_src_dir); println!("cargo:lib={}", out_dir.display()); let regex_header = out_dir.join("regex.h"); println!("cargo:include={}", out_dir.display()); fs::copy(lib_src_dir.join("regex.h"), ®ex_header) .expect("gnurx-sys: Failed to copy 'regex.h' from sources to output directory."); if env::var_os("DOCS_RS").is_none() { cc::Build::new() .static_flag(true) .pic(true) .warnings(true) .extra_warnings(true) .flag("-mthreads") .include(&lib_src_dir) .file(lib_src_dir.join("regex.c")) .compile("gnurx"); } regex_header } // See: https://github.com/rust-lang/rust-bindgen/issues/2136 fn translate_rustc_target_to_clang(rustc_target: &str) -> Cow { if let Some(suffix) = rustc_target.strip_prefix("riscv32") { let suffix = suffix.trim_start_matches(|c| c != '-'); Cow::Owned(format!("riscv32{suffix}")) } else if let Some(suffix) = rustc_target.strip_prefix("riscv64") { let suffix = suffix.trim_start_matches(|c| c != '-'); Cow::Owned(format!("riscv64{suffix}")) } else if let Some(suffix) = rustc_target.strip_prefix("aarch64-apple-") { Cow::Owned(format!("arm64-apple-{suffix}")) } else if let Some(prefix) = rustc_target.strip_suffix("-espidf") { Cow::Owned(format!("{prefix}-elf")) } else { Cow::Borrowed(rustc_target) } } fn generate_bindings(target: &str, sysroot: Option<&Path>, out_dir: &Path, regex_header: &Path) { let clang_target = translate_rustc_target_to_clang(target); let mut builder = bindgen::Builder::default() .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .default_enum_style(bindgen::EnumVariation::ModuleConsts) .derive_debug(true) .derive_copy(true) .derive_partialeq(true) .derive_eq(true) .derive_hash(true) .impl_debug(true) .impl_partialeq(true) .size_t_is_usize(true) .default_macro_constant_type(bindgen::MacroTypeVariation::Signed) .clang_arg(format!("--target={clang_target}")); if let Some(sysroot) = sysroot.map(Path::as_os_str).map(OsStr::to_str) { let sysroot = sysroot.expect("SYSROOT is not encoded in UTF-8"); builder = builder.clang_arg(format!("--sysroot={sysroot}")); } builder = builder .allowlist_function("reg(comp|exec|error|free)") .allowlist_type("reg_errcode_t") .allowlist_var("REG_.*") .opaque_type("regex_t") .header(regex_header.to_str().unwrap()); let bindings = builder .generate() .expect("gnurx-sys: Failed to generate Rust bindings for 'regex.h'."); bindings .write_to_file(out_dir.join("gnurx-sys.rs")) .expect("gnurx-sys: Failed to write 'gnurx-sys.rs'.") } fn target_env_var_os(name: &str, target: &str) -> Option { rerun_if_env_changed(name, target); let target_underscores = target.replace('-', "_"); env::var_os(format!("{name}_{target}")) .or_else(|| env::var_os(format!("{name}_{target_underscores}"))) .or_else(|| env::var_os(format!("TARGET_{name}"))) .or_else(|| env::var_os(name)) } fn rerun_if_env_changed(name: &str, target: &str) { let target_underscores = target.replace('-', "_"); println!("cargo:rerun-if-env-changed={name}_{target}"); println!("cargo:rerun-if-env-changed={name}_{target_underscores}"); println!("cargo:rerun-if-env-changed=TARGET_{name}"); println!("cargo:rerun-if-env-changed={name}"); } fn rerun_if_dir_changed(dir: &Path) { for file in WalkDir::new(dir).follow_links(false).same_file_system(true) { if let Ok(file) = file { println!("cargo:rerun-if-changed={}", file.path().display()); } else { panic!( "gnurx-sys: Failed to list directory contents: {}", dir.display() ); } } }