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; }
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()
.std("c11")
.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
}
fn translate_rustc_target_to_clang(rustc_target: &str) -> Cow<str> {
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<OsString> {
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()
);
}
}
}