use std::{
env,
fmt::{self, Display},
path::PathBuf,
process::{Command, Stdio},
};
#[doc = crate::_TAG_NAMESPACE!()] #[cfg_attr(nightly_doc, doc(cfg(feature = "std")))]
#[derive(Debug)]
pub struct Build;
impl Build {
pub fn crate_name() -> String {
env::var("CARGO_CRATE_NAME").expect("CARGO_CRATE_NAME not set")
}
pub fn out_dir() -> PathBuf {
PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"))
}
pub fn manifest_dir() -> PathBuf {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"))
}
pub fn manifest_path() -> PathBuf {
PathBuf::from(env::var("CARGO_MANIFEST_PATH").expect("CARGO_MANIFEST_PATH not set"))
}
pub fn rerun_if_env_changed(var: &str) {
println!("cargo:rerun-if-env-changed={var}");
}
}
#[rustfmt::skip]
impl Build {
pub fn println(msg: impl Display) { println!("cargo:warning={}", msg); }
pub fn println_heading(msg: &str) {
Self::println("");
Self::println(msg);
Self::println("-".repeat(msg.len()));
}
pub fn println_var(var: &str) {
if let Ok(v) = env::var(var) {
Self::println(fmt::from_fn(|f| write!(f, "ยท {var}: {v}")));
} else {
Self::println(fmt::from_fn(|f| write!(f, "x {var}:")));
}
}
pub fn println_var_encoded(var: &str, new_var_name: &str) {
if let Ok(ev) = env::var(var) {
let v = ev.replace('\x1f', " ");
Self::println(format!["ยท {new_var_name}(*): {v}"]);
} else {
Self::println(format!["x {new_var_name}:"]);
}
}
pub fn println_start_end(name: &str, start: bool) {
let msg = if start { format!("~ Start of {name} ~") }
else { Self::println(""); format!("~ End of {name} ~") };
let line = "~".repeat(msg.len());
Self::println(&line); Self::println(&msg); Self::println(&line);
}
}
#[rustfmt::skip]
impl Build {
pub fn emit_flag(flag: &str) { println!("cargo:rustc-cfg={flag}"); }
pub fn emit_check_cfg(flag: &str) { println!("cargo:rustc-check-cfg=cfg({flag})"); }
pub fn emit_checked_flag(flag: &str) { Self::emit_check_cfg(flag); Self::emit_flag(flag); }
pub fn emit_flag_if(flag: &str, condition: bool) -> bool {
Self::emit_check_cfg(flag);
if condition { Self::emit_flag(flag); }
condition
}
pub fn emit_env(key: &str, value: impl Display) { println!("cargo:rustc-env={key}={value}"); }
pub fn emit_env_marker(key: &str) { println!("cargo:rustc-env={key}="); }
}
#[rustfmt::skip]
impl Build {
#[must_use]
pub fn has_lib(lib: &str) -> bool { Self::lib_found_source(lib).is_some() }
#[must_use]
pub fn emit_flag_if_lib(flag: &str, lib: &str) -> bool {
Self::emit_check_cfg(flag);
Self::rerun_if_lib_env_changed();
let found = Self::lib_found_source(lib);
if found.is_some() { Self::emit_flag(flag); }
#[cfg(feature = "__dbg")]
match &found { Some(source) =>
Self::println(format!("ยท lib {lib}: found via {source}; emitted {flag}")),
None => Self::println(format!("x lib {lib}: not found; skipped {flag}")),
}
found.is_some()
}
fn rerun_if_lib_env_changed() {
for var in [ "PKG_CONFIG", "PKG_CONFIG_PATH", "PKG_CONFIG_LIBDIR",
"PKG_CONFIG_SYSROOT_DIR", "LIBRARY_PATH", "LD_LIBRARY_PATH",
"DYLD_LIBRARY_PATH", "LIB", "PATH", "CC", ] {
Self::rerun_if_env_changed(var);
}
}
fn lib_found_source(lib: &str) -> Option<String> {
if cfg!(feature = "__disable_native_libs") { return None; }
if Self::lib_found_pkg_config(lib) { return Some("pkg-config".to_string()); }
Self::find_lib_file(lib).map(|path| path.display().to_string())
}
fn lib_found_pkg_config(lib: &str) -> bool {
if let Some(cmd) = env::var_os("PKG_CONFIG") { return Self::pkg_config_exists(cmd, lib); }
Self::pkg_config_exists("pkg-config", lib) || Self::pkg_config_exists("pkgconf", lib)
}
fn pkg_config_exists(cmd: impl AsRef<std::ffi::OsStr>, lib: &str) -> bool {
Command::new(cmd).arg("--exists").arg(lib)
.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null())
.status().is_ok_and(|status| status.success())
}
fn find_lib_file(lib: &str) -> Option<PathBuf> {
let filenames = Self::lib_filenames(lib);
Self::lib_search_dirs().into_iter()
.find_map(|dir| filenames.iter().map(|file| dir.join(file)).find(|path| path.is_file()))
}
fn lib_filenames(lib: &str) -> Vec<String> {
let lib = lib.strip_prefix("lib").unwrap_or(lib);
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
match target_os.as_str() {
"windows" => vec![format!("{lib}.lib"), format!("{lib}.dll"), format!("lib{lib}.a")],
"macos" | "ios" => vec![format!("lib{lib}.dylib"), format!("lib{lib}.a")],
_ => vec![format!("lib{lib}.so"), format!("lib{lib}.a")],
}
}
fn lib_search_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
for var in ["LIBRARY_PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", "LIB"] {
if let Some(value) = env::var_os(var) { dirs.extend(env::split_paths(&value)); }
}
Self::push_cc_library_dirs(&mut dirs);
Self::push_common_library_dirs(&mut dirs);
dirs.sort(); dirs.dedup();
dirs
}
fn push_cc_library_dirs(dirs: &mut Vec<PathBuf>) {
let cc = env::var_os("CC").unwrap_or_else(|| "cc".into());
let Ok(output) = Command::new(cc).arg("-print-search-dirs").output() else { return; };
if !output.status.success() { return; }
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if let Some(paths) = line.strip_prefix("libraries: =") {
dirs.extend(env::split_paths(paths));
}
}
}
fn push_common_library_dirs(dirs: &mut Vec<PathBuf>) {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
match target_os.as_str() {
"linux" | "freebsd" | "netbsd" | "openbsd" => {
if let Some(multiarch) = Self::linux_multiarch_dir() {
dirs.push(format!("/usr/lib/{multiarch}").into());
dirs.push(format!("/lib/{multiarch}").into());
}
dirs.extend([
"/usr/local/lib".into(), "/usr/lib".into(), "/lib".into(),
"/usr/lib64".into(), "/lib64".into(), ]);
}
"macos" | "ios" => {
dirs.extend([
"/usr/local/lib".into(), "/opt/homebrew/lib".into(), "/usr/lib".into(), ]);
}
"windows" => {
if let Some(value) = env::var_os("PATH") { dirs.extend(env::split_paths(&value)); }
}
_ => {}
}
}
fn linux_multiarch_dir() -> Option<&'static str> {
let arch = env::var("CARGO_CFG_TARGET_ARCH").ok()?;
match arch.as_str() {
"x86_64" => Some("x86_64-linux-gnu"),
"x86" => Some("i386-linux-gnu"),
"aarch64" => Some("aarch64-linux-gnu"),
"arm" => Some("arm-linux-gnueabihf"),
"riscv64" => Some("riscv64-linux-gnu"),
"powerpc64" => Some("powerpc64-linux-gnu"),
"s390x" => Some("s390x-linux-gnu"),
_ => None,
}
}
}
#[rustfmt::skip]
impl Build {
pub const fn tab(n: usize) -> &'static str {
match n {
0 => "",
1 => " ",
2 => " ",
3 => " ",
4 => " ",
5 => " ",
6 => " ",
7 => " ",
8 => " ",
_ => " ",
}
}
#[doc = "0 tabs, 0 spaces."] pub const TAB0: &str = "";
#[doc = "1 tabs, 4 spaces."] pub const TAB1: &str = " ";
#[doc = "2 tabs, 8 spaces."] pub const TAB2: &str = " ";
#[doc = "3 tabs, 12 spaces."] pub const TAB3: &str = " ";
#[doc = "4 tabs, 16 spaces."] pub const TAB4: &str = " ";
#[doc = "5 tabs, 20 spaces."] pub const TAB5: &str = " ";
#[doc = "6 tabs, 24 spaces."] pub const TAB6: &str = " ";
#[doc = "7 tabs, 28 spaces."] pub const TAB7: &str = " ";
#[doc = "8 tabs, 32 spaces."] pub const TAB8: &str = " ";
#[doc = "9 tabs, 36 spaces."] pub const TAB9: &str = " ";
}