use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::config::HostPlatform;
#[derive(Debug, Clone)]
pub enum CMakeToolchain {
Generic,
Custom(PathBuf),
}
#[derive(Debug, Clone, Default)]
pub struct CrossEnv {
pub cc: Option<String>,
pub cxx: Option<String>,
pub ar: Option<String>,
pub linker: Option<String>,
pub runner: Option<String>,
pub path: Vec<PathBuf>,
pub rustflags: Vec<String>,
pub sdkroot: Option<PathBuf>,
pub sysroot: Option<PathBuf>,
pub library_path: Vec<PathBuf>,
pub cflags: Vec<String>,
pub cxxflags: Vec<String>,
pub ldflags: Vec<String>,
pub build_std: Option<String>,
pub cmake_toolchain: Option<CMakeToolchain>,
pub extra_env: HashMap<String, String>,
}
impl CrossEnv {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn set_cc(&mut self, cc: impl Into<String>) {
self.cc = Some(cc.into());
}
pub fn set_cxx(&mut self, cxx: impl Into<String>) {
self.cxx = Some(cxx.into());
}
pub fn set_ar(&mut self, ar: impl Into<String>) {
self.ar = Some(ar.into());
}
pub fn set_linker(&mut self, linker: impl Into<String>) {
self.linker = Some(linker.into());
}
pub fn set_runner(&mut self, runner: impl Into<String>) {
self.runner = Some(runner.into());
}
pub fn add_path(&mut self, path: impl Into<PathBuf>) {
self.path.push(path.into());
}
pub fn add_rustflag(&mut self, flag: impl Into<String>) {
self.rustflags.push(flag.into());
}
pub fn set_sdkroot(&mut self, path: impl Into<PathBuf>) {
self.sdkroot = Some(path.into());
}
pub fn set_sysroot(&mut self, path: impl Into<PathBuf>) {
self.sysroot = Some(path.into());
}
pub fn add_library_path(&mut self, path: impl Into<PathBuf>) {
self.library_path.push(path.into());
}
pub fn add_cflag(&mut self, flag: impl Into<String>) {
self.cflags.push(flag.into());
}
pub fn add_cxxflag(&mut self, flag: impl Into<String>) {
self.cxxflags.push(flag.into());
}
pub fn add_ldflag(&mut self, flag: impl Into<String>) {
self.ldflags.push(flag.into());
}
pub fn set_build_std(&mut self, crates: impl Into<String>) {
self.build_std = Some(crates.into());
}
pub fn set_generic_cmake_toolchain(&mut self) {
self.cmake_toolchain = Some(CMakeToolchain::Generic);
}
pub fn set_custom_cmake_toolchain(&mut self, path: impl Into<PathBuf>) {
self.cmake_toolchain = Some(CMakeToolchain::Custom(path.into()));
}
pub fn set_env(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.extra_env.insert(key.into(), value.into());
}
#[must_use]
pub fn build_env(&self, target: &str, host: &HostPlatform) -> HashMap<String, String> {
let mut env = HashMap::new();
let target_lower = target.replace('-', "_");
let target_upper = target.to_uppercase().replace('-', "_");
if let Some(ref cc) = self.cc {
env.insert(format!("CC_{target_lower}"), cc.clone());
}
if let Some(ref cxx) = self.cxx {
env.insert(format!("CXX_{target_lower}"), cxx.clone());
}
if let Some(ref ar) = self.ar {
env.insert(format!("AR_{target_lower}"), ar.clone());
}
if let Some(ref linker) = self.linker {
env.insert(
format!("CARGO_TARGET_{target_upper}_LINKER"),
linker.clone(),
);
}
if let Some(ref runner) = self.runner {
env.insert(
format!("CARGO_TARGET_{target_upper}_RUNNER"),
runner.clone(),
);
}
if !self.path.is_empty() {
let sep = host.path_separator();
let current_path = std::env::var("PATH").unwrap_or_default();
let new_path = self
.path
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(sep);
env.insert("PATH".to_string(), format!("{new_path}{sep}{current_path}"));
}
if let Some(ref sdkroot) = self.sdkroot {
env.insert("SDKROOT".to_string(), sdkroot.display().to_string());
}
if !self.library_path.is_empty() {
let sep = host.path_separator();
let lib_path = self
.library_path
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(sep);
let lib_var = if host.is_darwin() {
"DYLD_LIBRARY_PATH"
} else {
"LD_LIBRARY_PATH"
};
let current = std::env::var(lib_var).unwrap_or_default();
if current.is_empty() {
env.insert(lib_var.to_string(), lib_path);
} else {
env.insert(lib_var.to_string(), format!("{lib_path}{sep}{current}"));
}
}
if !self.cflags.is_empty() {
let flags = self.cflags.join(" ");
env.insert(format!("CFLAGS_{target_lower}"), flags.clone());
env.insert("CFLAGS".to_string(), flags);
}
if !self.cxxflags.is_empty() {
let flags = self.cxxflags.join(" ");
env.insert(format!("CXXFLAGS_{target_lower}"), flags.clone());
env.insert("CXXFLAGS".to_string(), flags);
}
if !self.ldflags.is_empty() {
let flags = self.ldflags.join(" ");
env.insert(format!("LDFLAGS_{target_lower}"), flags.clone());
env.insert("LDFLAGS".to_string(), flags);
}
for (key, value) in &self.extra_env {
env.insert(key.clone(), value.clone());
}
env
}
#[must_use]
pub fn rustflags_string(&self) -> Option<String> {
if self.rustflags.is_empty() {
None
} else {
Some(self.rustflags.join(" "))
}
}
}
pub fn set_gcc_lib_paths(env: &mut CrossEnv, compiler_dir: &Path, target_prefix: &str) {
let target_lib = compiler_dir.join(target_prefix).join("lib");
if target_lib.exists() {
env.add_rustflag(format!("-L {}", target_lib.display()));
}
let gcc_lib_base = compiler_dir.join("lib").join("gcc").join(target_prefix);
if let Ok(entries) = std::fs::read_dir(&gcc_lib_base) {
for entry in entries.filter_map(std::result::Result::ok) {
if entry.file_type().is_ok_and(|t| t.is_dir()) {
env.add_rustflag(format!("-L {}", entry.path().display()));
break;
}
}
}
}
pub fn setup_sysroot_env(
env: &mut CrossEnv,
compiler_dir: &Path,
bin_prefix: &str,
rust_target: &str,
) {
let sysroot = compiler_dir.join(bin_prefix);
if !sysroot.exists() {
return;
}
env.set_sysroot(&sysroot);
let target_underscores = rust_target.replace('-', "_");
let mut clang_args = vec![format!("--sysroot={}", sysroot.display())];
let gcc_include_base = compiler_dir.join("lib").join("gcc").join(bin_prefix);
if let Ok(entries) = std::fs::read_dir(&gcc_include_base) {
for entry in entries.filter_map(std::result::Result::ok) {
let include_dir = entry.path().join("include");
if include_dir.exists() {
clang_args.push(format!("-I{}", include_dir.display()));
break;
}
}
}
let usr_include = sysroot.join("usr").join("include");
let include = sysroot.join("include");
if usr_include.exists() {
clang_args.push(format!("-I{}", usr_include.display()));
} else if include.exists() {
clang_args.push(format!("-I{}", include.display()));
}
env.set_env(
format!("BINDGEN_EXTRA_CLANG_ARGS_{target_underscores}"),
clang_args.join(" "),
);
}
#[must_use]
pub const fn get_build_std_config() -> &'static str {
"std,core,alloc,proc_macro,test,panic_abort,panic_unwind"
}
pub fn sanitize_cargo_env() {
if std::env::var("CARGO_TARGET_DIR").is_ok_and(|v| v.is_empty()) {
std::env::remove_var("CARGO_TARGET_DIR");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cross_env_build() {
let mut env = CrossEnv::new();
env.set_cc("aarch64-linux-gnu-gcc");
env.set_cxx("aarch64-linux-gnu-g++");
env.set_ar("aarch64-linux-gnu-ar");
env.set_linker("aarch64-linux-gnu-gcc");
let host = HostPlatform::detect();
let vars = env.build_env("aarch64-unknown-linux-gnu", &host);
assert_eq!(
vars.get("CC_aarch64_unknown_linux_gnu"),
Some(&"aarch64-linux-gnu-gcc".to_string())
);
assert_eq!(
vars.get("CXX_aarch64_unknown_linux_gnu"),
Some(&"aarch64-linux-gnu-g++".to_string())
);
assert_eq!(
vars.get("AR_aarch64_unknown_linux_gnu"),
Some(&"aarch64-linux-gnu-ar".to_string())
);
assert!(!vars.contains_key("CC"));
assert!(!vars.contains_key("CXX"));
assert!(!vars.contains_key("AR"));
assert!(!vars.contains_key("CMAKE_C_COMPILER"));
assert!(!vars.contains_key("CMAKE_CXX_COMPILER"));
assert!(!vars.contains_key("CMAKE_AR"));
assert!(vars.contains_key("CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER"));
}
}