use std::env;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let workspace_root = manifest_dir
.parent()
.expect("cyclonedds-sys must live under the workspace root");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
let prebuilt = manifest_dir.join("src/prebuilt_bindings.rs");
let lib_found = if let Ok(cyclonedds_src) = try_resolve_cyclonedds_source(workspace_root) {
let cyclonedds_build = resolve_cyclonedds_build_dir(&cyclonedds_src, &out_dir);
if find_ddsc_library(&cyclonedds_build).is_some() {
if let Some((lib_dir, link_kind)) = find_ddsc_library(&cyclonedds_build) {
emit_link_info(&lib_dir, link_kind);
true
} else {
false
}
} else if which_cmake().is_some() {
ensure_cyclonedds_build_ready(&cyclonedds_src, &cyclonedds_build);
if let Some((lib_dir, link_kind)) = find_ddsc_library(&cyclonedds_build) {
emit_link_info(&lib_dir, link_kind);
true
} else {
false
}
} else {
if let Some((lib_dir, link_kind)) = find_system_ddsc_library() {
emit_link_info(&lib_dir, link_kind);
true
} else {
println!("cargo:warning=cmake not found and no system CycloneDDS — generating bindings without linking");
println!("cargo:rustc-link-lib=dylib=ddsc");
false
}
}
} else {
if let Some((lib_dir, link_kind)) = find_system_ddsc_library() {
emit_link_info(&lib_dir, link_kind);
true
} else {
println!("cargo:warning=CycloneDDS library not found — generating bindings without linking");
println!("cargo:rustc-link-lib=dylib=ddsc");
false
}
};
let _ = lib_found;
println!("cargo:rerun-if-env-changed=CYCLONEDDS_SRC");
println!("cargo:rerun-if-env-changed=CYCLONEDDS_BUILD");
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-changed=build.rs");
let bindings_path = out_dir.join("bindings.rs");
if prebuilt.exists() {
let content = std::fs::read_to_string(&prebuilt)
.expect("couldn't read prebuilt bindings");
let stripped = strip_static_assertions_from_str(&content);
std::fs::write(&bindings_path, stripped)
.expect("couldn't write bindings");
} else {
panic!(
"No prebuilt bindings found at {}. Run bindgen on macOS/Linux first.",
prebuilt.display()
);
}
}
fn emit_link_info(lib_dir: &Path, link_kind: &'static str) {
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib={link_kind}=ddsc");
if link_kind == "dylib" {
#[cfg(any(target_os = "macos", target_os = "linux"))]
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir.display());
}
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=bcrypt");
println!("cargo:rustc-link-lib=iphlpapi");
println!("cargo:rustc-link-lib=ws2_32");
}
}
fn find_system_ddsc_library() -> Option<(PathBuf, &'static str)> {
let search_paths = vec![
PathBuf::from("/usr/lib"),
PathBuf::from("/usr/local/lib"),
PathBuf::from("/usr/lib/x86_64-linux-gnu"),
];
for dir in search_paths {
if let Some(result) = find_ddsc_library(&dir) {
return Some(result);
}
}
None
}
fn which_cmake() -> Option<PathBuf> {
let output = Command::new("which")
.arg("cmake")
.output()
.ok()
.filter(|o| o.status.success())
.or_else(|| {
Command::new("where")
.arg("cmake")
.output()
.ok()
.filter(|o| o.status.success())
})?;
let path = String::from_utf8_lossy(&output.stdout);
Some(PathBuf::from(path.lines().next().unwrap_or("").trim()))
}
fn strip_static_assertions_from_str(content: &str) -> String {
let mut result = String::with_capacity(content.len());
let mut depth: i32 = 0;
let mut in_assertion = false;
for line in content.lines() {
if !in_assertion && line.trim().starts_with("const _: () = {") {
in_assertion = true;
depth = 0;
for ch in line.chars() {
match ch {
'{' => depth += 1,
'}' => depth -= 1,
_ => {}
}
}
if depth <= 0 {
in_assertion = false;
}
} else if in_assertion {
for ch in line.chars() {
match ch {
'{' => depth += 1,
'}' => depth -= 1,
_ => {}
}
}
if depth <= 0 {
in_assertion = false;
}
} else {
result.push_str(line);
result.push('\n');
}
}
result
}
fn try_resolve_cyclonedds_source(workspace_root: &Path) -> Result<PathBuf, String> {
if let Some(source) = env::var_os("CYCLONEDDS_SRC") {
let source = PathBuf::from(source);
if !source.exists() {
return Err(format!("CYCLONEDDS_SRC does not exist: {}", source.display()));
}
return Ok(source);
}
let bundled = cyclonedds_src::source_dir();
if bundled.exists() {
return Ok(bundled);
}
let vendor = workspace_root.join("vendor/cyclonedds");
if vendor.exists() {
Ok(vendor)
} else {
Err(format!(
"CycloneDDS source not found. Set CYCLONEDDS_SRC or ensure vendor/cyclonedds exists.",
))
}
}
fn resolve_cyclonedds_build_dir(source_dir: &Path, out_dir: &Path) -> PathBuf {
env::var_os("CYCLONEDDS_BUILD")
.map(PathBuf::from)
.unwrap_or_else(|| {
out_dir.join("cyclonedds-build").join(
source_dir
.file_name()
.unwrap_or_else(|| OsStr::new("cyclonedds")),
)
})
}
fn ensure_cyclonedds_build_ready(source_dir: &Path, build_dir: &Path) {
if find_ddsc_library(build_dir).is_some() {
return;
}
std::fs::create_dir_all(build_dir)
.unwrap_or_else(|err| panic!("failed to create {}: {err}", build_dir.display()));
let shared = if cfg!(target_os = "windows") {
"OFF" } else {
"ON"
};
run(
Command::new("cmake")
.arg("-S")
.arg(source_dir)
.arg("-B")
.arg(build_dir)
.arg(format!("-DBUILD_SHARED_LIBS={}", shared))
.arg("-DBUILD_TESTING=OFF")
.arg("-DBUILD_IDLC=OFF")
.arg("-DBUILD_DDSPERF=OFF")
.arg("-DBUILD_EXAMPLES=OFF")
.arg("-DENABLE_LTO=OFF"),
"configure bundled CycloneDDS",
);
run(
Command::new("cmake")
.arg("--build")
.arg(build_dir)
.arg("--target")
.arg("ddsc")
.arg("--config")
.arg("Release"),
"build bundled CycloneDDS",
);
assert!(
find_ddsc_library(build_dir).is_some(),
"CycloneDDS build finished but no ddsc library was found under {}",
build_dir.display()
);
}
fn find_ddsc_library(build_dir: &Path) -> Option<(PathBuf, &'static str)> {
let mut stack = vec![build_dir.to_path_buf()];
while let Some(dir) = stack.pop() {
let entries = std::fs::read_dir(&dir).ok()?;
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
continue;
}
let name = path.file_name()?;
match name.to_str()? {
"libddsc.dylib" | "libddsc.so" | "ddsc.dll" => {
return Some((path.parent()?.to_path_buf(), "dylib"));
}
"ddsc.lib" => {
let dll = path.with_file_name("ddsc.dll");
let kind = if dll.exists() { "dylib" } else { "static" };
return Some((path.parent()?.to_path_buf(), kind));
}
"libddsc.a" => {
return Some((path.parent()?.to_path_buf(), "static"));
}
_ => {}
}
}
}
None
}
fn run(command: &mut Command, description: &str) {
let status = command
.status()
.unwrap_or_else(|err| panic!("failed to {description}: {err}"));
assert!(status.success(), "failed to {description}: {status}");
}