use std::path::{Path, PathBuf};
#[derive(Debug)]
struct BindgenWrapperCallbacks {
mapped_names: std::cell::RefCell<Vec<String>>,
}
impl BindgenWrapperCallbacks {
pub fn new() -> Self {
Self {
mapped_names: Default::default(),
}
}
}
impl bindgen::callbacks::ParseCallbacks for BindgenWrapperCallbacks {
fn generated_name_override(
&self,
item_info: bindgen::callbacks::ItemInfo<'_>,
) -> Option<String> {
match (
&item_info.kind,
item_info.name.rsplit_once("_bindgen_wrapper"),
) {
(bindgen::callbacks::ItemKind::Function, Some((start, _))) => {
let name = start.to_string();
self.mapped_names.borrow_mut().push(name.clone());
Some(name)
}
_ => None,
}
}
fn generated_link_name_override(
&self,
item_info: bindgen::callbacks::ItemInfo<'_>,
) -> Option<String> {
let name = item_info.name.into();
match item_info.kind {
bindgen::callbacks::ItemKind::Function
if self.mapped_names.borrow().contains(&name) =>
{
Some(name)
}
_ => None,
}
}
}
fn platform_fixup(target: &Target, bindings_path: &Path) {
if target.os == "macos" {
let contents = std::fs::read_to_string(bindings_path).expect("could not read bindings.rs");
let cleaned = contents.replace("#[link_name = \"\\u{1}dds", "#[link_name = \"dds");
std::fs::write(bindings_path, cleaned).expect("could not write cleaned bindings.rs");
}
}
fn env(var: &'static str) -> Result<String, BuildError> {
std::env::var(var).map_err(|source| BuildError::Env { var, source })
}
pub struct Target {
pub os: String,
pub arch: String,
}
impl Target {
pub fn from_env() -> Result<Self, BuildError> {
let os = env("CARGO_CFG_TARGET_OS")?;
let arch = env("CARGO_CFG_TARGET_ARCH")?;
Ok(Self { os, arch })
}
}
#[derive(Debug)]
pub enum BuildError {
Env {
var: &'static str,
source: std::env::VarError,
},
Bindgen {
source: bindgen::BindgenError,
},
Vendoring {
message: String,
source: VendoringError,
},
}
#[derive(Debug)]
pub enum VendoringError {
Io(std::io::Error),
StripPrefix(std::path::StripPrefixError),
}
#[cfg(not(feature = "vendored"))]
pub fn generate_bindings(
mut bindgen: bindgen::Builder,
_target: &Target,
) -> Result<bindgen::Bindings, BuildError> {
println!("cargo:rerun-if-env-changed=CYCLONEDDS_HOME");
if let Ok(cyclonedds_home) = std::env::var("CYCLONEDDS_HOME") {
let cyclonedds_home = Path::new(&cyclonedds_home);
for lib_dir in &["lib", "lib64"] {
let path = cyclonedds_home.join(lib_dir);
if path.exists() {
println!("cargo:rustc-link-search={}", path.display());
}
}
bindgen = bindgen.clang_arg(format!("-I{}", cyclonedds_home.join("include").display()));
}
bindgen
.generate()
.map_err(|source| BuildError::Bindgen { source })
}
#[cfg(feature = "vendored")]
pub fn generate_bindings(
mut bindgen: bindgen::Builder,
target: &Target,
) -> Result<bindgen::Bindings, BuildError> {
fn for_each_file(
path: &Path,
f: &impl Fn(&Path) -> Result<(), BuildError>,
) -> Result<(), BuildError> {
for entry in std::fs::read_dir(path).map_err(|source| BuildError::Vendoring {
message: format!("failed to read directory: {path:?}"),
source: VendoringError::Io(source),
})? {
let entry = entry.map_err(|source| BuildError::Vendoring {
message: "encountered invalid directory entry".to_string(),
source: VendoringError::Io(source),
})?;
if entry
.file_type()
.map_err(|source| BuildError::Vendoring {
message: "unable to determine file type for directory entry".to_string(),
source: VendoringError::Io(source),
})?
.is_dir()
{
for_each_file(&entry.path(), f)?;
} else {
f(&entry.path())?;
}
}
Ok(())
}
println!("cargo:rerun-if-changed=vendor/cyclonedds-c");
let cyclonedds_c_out_path = PathBuf::from(env("OUT_DIR")?).join("cyclonedds-c-build");
let tempdir = tempfile::tempdir().map_err(|source| BuildError::Vendoring {
message: "failed to create tempdir".to_string(),
source: VendoringError::Io(source),
})?;
let original_vendored_src = Path::new("vendor/cyclonedds-c");
let tempdir_vendored_src = tempdir.path().join("cyclonedds-c-src");
for_each_file(original_vendored_src, &|path| {
let relative =
path.strip_prefix(original_vendored_src)
.map_err(|source| BuildError::Vendoring {
message: format!(
"could not strip prefix: {path:?} from {original_vendored_src:?}"
),
source: VendoringError::StripPrefix(source),
})?;
let destination = tempdir_vendored_src.join(relative);
std::fs::create_dir_all(destination.parent().unwrap()).map_err(|source| {
BuildError::Vendoring {
message: format!("could not create directory for {destination:?}"),
source: VendoringError::Io(source),
}
})?;
std::fs::copy(path, &destination).map_err(|source| BuildError::Vendoring {
message: format!("could not copy sources from {path:?} to {destination:?}"),
source: VendoringError::Io(source),
})?;
Ok(())
})?;
let cross_compiling = env("HOST")? != env("TARGET")?;
if cross_compiling && target.os == "windows" {
let cmakelists = tempdir_vendored_src.join("CMakeLists.txt");
let contents =
std::fs::read_to_string(&cmakelists).map_err(|source| BuildError::Vendoring {
message: format!("could not read CMakeLists.txt from tempdir: {cmakelists:?}"),
source: VendoringError::Io(source),
})?;
let patched = contents.replace("include(CMakeCPack.cmake)", "");
std::fs::write(&cmakelists, patched).map_err(|source| BuildError::Vendoring {
message: format!("could not write cross-compile changes to {cmakelists:?}"),
source: VendoringError::Io(source),
})?;
}
let mut cyclonedds_cmake = cmake::Config::new(&tempdir_vendored_src);
let mut cyclonedds_cmake = cyclonedds_cmake.out_dir(&cyclonedds_c_out_path);
cyclonedds_cmake = cyclonedds_cmake
.out_dir(&cyclonedds_c_out_path)
.define("BUILD_SHARED_LIBS", "OFF")
.define("BUILD_IDLC", "OFF")
.define("BUILD_DDSPERF", "OFF")
.define("ENABLE_SSL", "NO")
.define("ENABLE_SECURITY", "NO")
.define("CMAKE_INSTALL_LIBDIR", "lib");
if cross_compiling {
cyclonedds_cmake = cyclonedds_cmake.define("CMAKE_CROSSCOMPILING", "ON");
}
let cyclonedds_c = cyclonedds_cmake.build();
let time = cyclonedds_c_out_path
.metadata()
.map(|metadata| filetime::FileTime::from_creation_time(&metadata))
.ok()
.flatten()
.unwrap_or(filetime::FileTime::zero());
for_each_file(&cyclonedds_c_out_path.join("include"), &|path| {
filetime::set_file_mtime(path, time).map_err(|source| BuildError::Vendoring {
message: format!("could not reset mtime (to reduce rebuilds) on {path:?}"),
source: VendoringError::Io(source),
})
})?;
println!(
"cargo:rustc-link-search={}",
cyclonedds_c.join("lib").display()
);
bindgen = bindgen.clang_arg(format!("-I{}", cyclonedds_c.join("include").display()));
bindgen
.generate()
.map_err(|source| BuildError::Bindgen { source })
}
fn main() -> Result<(), BuildError> {
let target = Target::from_env()?;
let out_path = PathBuf::from(env("OUT_DIR")?);
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rustc-link-lib=ddsc");
let mut bindgen = bindgen::Builder::default()
.header("wrapper.h")
.parse_callbacks(Box::new(BindgenWrapperCallbacks::new()))
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.wrap_static_fns(true)
.generate_cstr(true)
.derive_default(true)
.disable_untagged_union()
.allowlist_item("dds_.*")
.allowlist_item("ddsi_.*")
.allowlist_item("ddsrt_.*")
.allowlist_var("DDS_.*")
.allowlist_var("DOMAIN_DEFAULT")
.allowlist_var("DURATION_INFINITE")
.allowlist_var("TIME_NEVER")
.formatter(bindgen::Formatter::Prettyplease)
.layout_tests(true);
if target.os == "windows" {
bindgen = bindgen
.blocklist_type("ddsi_sertype_v0_t")
.raw_line("pub type ddsi_sertype_v0_t = Option<std::num::NonZeroUsize>;");
println!("cargo:rustc-link-lib=iphlpapi");
println!("cargo:rustc-link-lib=dbghelp");
println!("cargo:rustc-link-lib=bcrypt");
}
let bindings = generate_bindings(bindgen, &target)?;
let bindings_path = out_path.join("bindings.rs");
bindings
.write_to_file(&bindings_path)
.expect("could not write bindings");
platform_fixup(&target, &bindings_path);
Ok(())
}