use std::env;
use std::path::PathBuf;
pub const D_BINDINGS_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/d");
pub const LDC2_SAFETY_FLAGS: &[&str] = &[
"--edition=2025",
"--preview=safer",
"--preview=dip1000",
"--preview=nosharedaccess",
"--preview=fixImmutableConv",
"--preview=systemVariables",
];
pub fn bridge(rs_path: impl Into<PathBuf>) -> CxxDBuilder {
CxxDBuilder {
rs_path: rs_path.into(),
includes: Vec::new(),
d_modules: Vec::new(),
extern_std: "c++17",
}
}
pub struct CxxDBuilder {
rs_path: PathBuf,
includes: Vec<PathBuf>,
d_modules: Vec<PathBuf>,
extern_std: &'static str,
}
impl CxxDBuilder {
pub fn include(mut self, dir: impl Into<PathBuf>) -> Self {
self.includes.push(dir.into());
self
}
pub fn d_module(mut self, path: impl Into<PathBuf>) -> Self {
self.d_modules.push(path.into());
self
}
pub fn extern_std(mut self, std: &'static str) -> Self {
self.extern_std = std;
self
}
pub fn compile(self, name: &str) {
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
let d_objs_dir = out_dir.join(format!("{name}-d-objs"));
std::fs::create_dir_all(&d_objs_dir).expect("create d_objs dir");
let ldc2 = find_ldc2();
let extern_std_flag = format!("--extern-std={}", self.extern_std);
let mut objects: Vec<PathBuf> = Vec::with_capacity(self.d_modules.len());
for src in &self.d_modules {
println!("cargo:rerun-if-changed={}", src.display());
let stem = src.file_stem().and_then(|s| s.to_str()).unwrap_or("dmod");
let obj = d_objs_dir.join(format!("{stem}.o"));
let status = std::process::Command::new(&ldc2)
.args(LDC2_SAFETY_FLAGS)
.arg(&extern_std_flag)
.arg("-relocation-model=pic")
.arg("-c")
.arg(format!("-of={}", obj.display()))
.arg(format!("-I={D_BINDINGS_DIR}"))
.arg("-Id")
.arg(src)
.status()
.unwrap_or_else(|e| panic!("Failed to invoke ldc2: {e}"));
assert!(
status.success(),
"LDC2 compile failed for {}",
src.display()
);
objects.push(obj);
}
let mut build = cxx_build::bridge(&self.rs_path);
for inc in &self.includes {
build.include(inc);
}
build.flag_if_supported(format!("-std={}", self.extern_std));
for obj in &objects {
build.object(obj);
}
build.compile(name);
println!("cargo:rerun-if-changed={}", self.rs_path.display());
println!("cargo:rerun-if-env-changed=DC");
println!("cargo:rerun-if-env-changed=LDC2_PATH");
}
}
pub fn find_ldc2() -> PathBuf {
for var in ["DC", "LDC2_PATH"] {
if let Ok(path) = env::var(var) {
let p = PathBuf::from(&path);
if p.is_file() {
return p;
}
let p2 = p.join("ldc2");
if p2.is_file() {
return p2;
}
}
}
if let Some(p) = which_on_path("ldc2") {
return p;
}
let home = env::var("HOME").unwrap_or_default();
let dlang_dir = PathBuf::from(&home).join(".dlang");
if dlang_dir.is_dir()
&& let Ok(entries) = std::fs::read_dir(&dlang_dir)
{
let mut candidates: Vec<PathBuf> = entries
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.map(|n| n.starts_with("ldc2-"))
.unwrap_or(false)
})
.map(|e| e.path().join("bin").join("ldc2"))
.filter(|p| p.is_file())
.collect();
candidates.sort();
if let Some(p) = candidates.pop() {
return p;
}
}
panic!(
"ldc2 not found; set $DC or $LDC2_PATH, or install LDC2 >= 1.40 via https://dlang.org/download.html"
);
}
fn which_on_path(name: &str) -> Option<PathBuf> {
let path = env::var("PATH").ok()?;
let exts: &[&str] = if cfg!(target_os = "windows") {
&["", ".exe"]
} else {
&[""]
};
for dir in env::split_paths(&path) {
for ext in exts {
let candidate = dir.join(format!("{name}{ext}"));
if candidate.is_file() {
return Some(candidate);
}
}
}
None
}