From 700d028a15a09240e5766b35602d95bbc0fe0f97 Mon Sep 17 00:00:00 2001 From: Geoff Martin Date: Wed, 16 Oct 2024 06:56:09 -0700 Subject: [PATCH] Allow multiple versions of the Cyclors library to be loaded simultaneously on Linux systems. (#26) --- .github/workflows/rust.yml | 4 +- Cargo.toml | 2 +- build.rs | 190 ++++++++++++++++++++++++++++++++++++- src/functions.template | 41 ++++++++ src/lib.rs | 37 +------- 5 files changed, 234 insertions(+), 40 deletions(-) create mode 100644 src/functions.template diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 527828c..c1d7e23 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust on: push: - branches: [master] + branches: [master, dev/0.2.1] pull_request: - branches: [master] + branches: [master, dev/0.2.1] env: CARGO_TERM_COLOR: always diff --git a/Cargo.toml b/Cargo.toml index 12c9be5..9ad9647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cyclors" -version = "0.2.1" +version = "0.2.2" authors = ["kydos "] license = "Apache-2.0" readme = "README.md" diff --git a/build.rs b/build.rs index e5dfcae..4310032 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,14 @@ extern crate bindgen; -use std::env; -use std::path::PathBuf; +#[allow(unused_imports)] +use std::collections::HashSet; +use std::fs::File; +#[allow(unused_imports)] +use std::io::{LineWriter, Write}; +use std::path::{Path, PathBuf}; +#[allow(unused_imports)] +use std::process::Command; +use std::{env, fs}; fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); @@ -104,6 +111,8 @@ fn main() { println!("cargo:rustc-link-lib=Iphlpapi"); #[cfg(target_os = "windows")] println!("cargo:rustc-link-lib=DbgHelp"); + #[cfg(target_os = "windows")] + println!("cargo:rustc-link-lib=bcrypt"); // Build cyclocut let cyclocut_dir = out_dir.join("cyclocut-build"); @@ -139,6 +148,57 @@ fn main() { .clang_arg(format!("-I{}", cyclocut_include.to_str().unwrap())) .generate_comments(false); + #[allow(unused)] + let mut prefix = String::from(""); + + // Prefix symbols in Cyclone DDS and Cyclocut libraries to ensure uniqueness + #[cfg(all(target_os = "linux", not(feature = "iceoryx")))] + { + // Prefix = cyclors__ + prefix = env::var("CARGO_PKG_VERSION").unwrap().replace('.', "_"); + prefix.insert_str(0, "cyclors_"); + prefix.push('_'); + + let mut symbols = HashSet::new(); + + let cyclone_symbols = get_defined_symbols(&cyclonedds_lib, "libddsc.a") + .expect("Failed to get symbols from libddsc.a!"); + symbols.extend(cyclone_symbols); + prefix_symbols(&cyclonedds_lib, "libddsc.a", &prefix, &symbols).unwrap(); + + let cyclocut_symbols = get_defined_symbols(&cyclocut_lib, "libcdds-util.a") + .expect("Failed to get symbols from libcdds-util.a!"); + symbols.extend(cyclocut_symbols); + prefix_symbols(&cyclocut_lib, "libcdds-util.a", &prefix, &symbols).unwrap(); + + #[derive(Debug)] + struct PrefixLinkNameCallback { + prefix: String, + symbols: HashSet, + } + + impl bindgen::callbacks::ParseCallbacks for PrefixLinkNameCallback { + fn generated_link_name_override( + &self, + item_info: bindgen::callbacks::ItemInfo<'_>, + ) -> Option { + match self.symbols.contains(item_info.name) { + true => { + let mut prefix = self.prefix.clone(); + prefix.push_str(item_info.name); + Some(prefix) + } + false => None, + } + } + } + + bindings = bindings.parse_callbacks(Box::new(PrefixLinkNameCallback { + prefix: prefix.clone(), + symbols: symbols.clone(), + })); + } + // Add *IMAGE_TLS_DIRECTORY* to blocklist on Windows due to // https://github.com/rust-lang/rust-bindgen/issues/2179 #[cfg(target_os = "windows")] @@ -146,6 +206,9 @@ fn main() { .clang_arg("-Wno-invalid-token-paste") .blocklist_type("^(.*IMAGE_TLS_DIRECTORY.*)$"); + // Set link name prefix on additional wrapStringper functions + generate_template_src(&prefix, &out_dir).unwrap(); + // Generate bindings let bindings = bindings.generate().expect("Unable to generate bindings"); @@ -153,3 +216,126 @@ fn main() { .write_to_file(out_dir.join("bindings.rs")) .expect("Couldn't write bindings!"); } + +#[cfg(all(target_os = "linux", not(feature = "iceoryx")))] +fn get_defined_symbols(lib_dir: &Path, lib_name: &str) -> Result, String> { + let lib_path = lib_dir.to_path_buf().join(lib_name); + + let rc = Command::new("nm") + .arg("--defined-only") + .arg("--print-file-name") + .arg(lib_path) + .output(); + + match rc { + Ok(output) => { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + match stderr.is_empty() { + true => { + let mut result: HashSet = HashSet::new(); + for line in stdout.lines() { + let tokens: Vec<&str> = line.split_whitespace().collect(); + let symbol = *tokens.last().unwrap(); + result.insert(String::from(symbol)); + } + Ok(result) + } + false => Err(format!( + "Failed to run nm on library {} (stderr: {})", + lib_name, + String::from(stderr) + )), + } + } + Err(_) => Err(format!("Failed to run nm on library {}", lib_name)), + } +} + +#[cfg(all(target_os = "linux", not(feature = "iceoryx")))] +fn prefix_symbols( + lib_dir: &Path, + lib_name: &str, + prefix: &str, + symbols: &HashSet, +) -> Result<(), String> { + let mut objcopy_file_name = lib_name.to_owned(); + objcopy_file_name.push_str(".objcopy"); + + let lib_file_path = lib_dir.to_path_buf().join(lib_name); + let symbol_file_path = lib_dir.to_path_buf().join(objcopy_file_name); + + match File::create(symbol_file_path.clone()) { + Ok(symbol_file) => { + let mut symbol_file = LineWriter::new(symbol_file); + + for symbol in symbols { + let mut symbol_arg = symbol.clone(); + symbol_arg.push(' '); + symbol_arg.push_str(prefix); + symbol_arg.push_str(symbol); + symbol_arg.push('\n'); + if symbol_file.write_all(symbol_arg.as_bytes()).is_err() { + return Err(format!( + "Failed to write symbol file for library {}", + lib_name + )); + } + } + + if symbol_file.flush().is_err() { + return Err(format!( + "Failed to write symbol file for library {}", + lib_name + )); + } + let arg = format!("--redefine-syms={}", symbol_file_path.to_str().unwrap()); + match Command::new("objcopy").arg(arg).arg(lib_file_path).output() { + Ok(_) => Ok(()), + Err(_) => Err(format!("Failed to run objcopy on library {}", lib_name)), + } + } + Err(_) => Err(format!( + "Failed to create symbol file for library {}", + lib_name + )), + } +} + +fn generate_template_src(prefix: &str, out_dir: &Path) -> Result<(), String> { + let src_path = Path::new("src/functions.template"); + let dst_path = out_dir.join("functions.rs"); + + match fs::read_to_string(src_path) { + Ok(mut contents) => { + contents = contents.replace("", prefix); + + match File::create(&dst_path) { + Ok(mut file) => { + if file.write_all(contents.as_bytes()).is_err() { + let path = dst_path.to_str().unwrap(); + return Err(format!( + "Failed to write the modified content to the destination file {}", + path + )); + } + + println!("cargo:rerun-if-changed=src/lib.rs"); + println!("cargo:rerun-if-changed=src/functions.template"); + Ok(()) + } + Err(_) => { + let path = dst_path.to_str().unwrap(); + Err(format!( + "Failed to open the destination file ({}) for writing", + path + )) + } + } + } + Err(_) => Err(String::from( + "Failed to read the source file src/functions.template", + )), + } +} diff --git a/src/functions.template b/src/functions.template new file mode 100644 index 0000000..27942ab --- /dev/null +++ b/src/functions.template @@ -0,0 +1,41 @@ +/* Additional wrapper functions for select exported inline functions */ + +extern "C" { + #[link_name = "ddsi_serdata_size"] + pub fn ddsi_serdata_size(d: *const ddsi_serdata) -> u32; +} +extern "C" { + #[link_name = "ddsi_serdata_to_ser_ref"] + pub fn ddsi_serdata_to_ser_ref( + d: *const ddsi_serdata, + off: usize, + sz: usize, + ref_: *mut ddsrt_iovec_t, + ) -> *mut ddsi_serdata; +} +extern "C" { + #[link_name = "ddsi_serdata_unref"] + pub fn ddsi_serdata_unref(serdata: *mut ddsi_serdata); +} +extern "C" { + #[link_name = "ddsi_serdata_to_ser_unref"] + pub fn ddsi_serdata_to_ser_unref(d: *mut ddsi_serdata, ref_: *const ddsrt_iovec_t); +} +extern "C" { + #[link_name = "ddsi_serdata_from_ser_iov"] + pub fn ddsi_serdata_from_ser_iov( + type_: *const ddsi_sertype, + kind: ddsi_serdata_kind, + niov: ddsrt_msg_iovlen_t, + iov: *const ddsrt_iovec_t, + size: usize, + ) -> *mut ddsi_serdata; +} +extern "C" { + #[link_name = "ddsi_serdata_from_sample"] + pub fn ddsi_serdata_from_sample( + type_: *const ddsi_sertype, + kind: ddsi_serdata_kind, + sample: *const ::std::os::raw::c_void, + ) -> *mut ddsi_serdata; +} diff --git a/src/lib.rs b/src/lib.rs index 998d894..3c475a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,38 +47,5 @@ mod bindings { } pub use bindings::*; -/* Additional wrapper functions for select exported inline functions */ - -extern "C" { - pub fn ddsi_serdata_size(d: *const ddsi_serdata) -> u32; -} -extern "C" { - pub fn ddsi_serdata_to_ser_ref( - d: *const ddsi_serdata, - off: usize, - sz: usize, - ref_: *mut ddsrt_iovec_t, - ) -> *mut ddsi_serdata; -} -extern "C" { - pub fn ddsi_serdata_unref(serdata: *mut ddsi_serdata); -} -extern "C" { - pub fn ddsi_serdata_to_ser_unref(d: *mut ddsi_serdata, ref_: *const ddsrt_iovec_t); -} -extern "C" { - pub fn ddsi_serdata_from_ser_iov( - type_: *const ddsi_sertype, - kind: ddsi_serdata_kind, - niov: ddsrt_msg_iovlen_t, - iov: *const ddsrt_iovec_t, - size: usize, - ) -> *mut ddsi_serdata; -} -extern "C" { - pub fn ddsi_serdata_from_sample( - type_: *const ddsi_sertype, - kind: ddsi_serdata_kind, - sample: *const ::std::os::raw::c_void, - ) -> *mut ddsi_serdata; -} +// Include the generated additional wrapper functions from OUT_DIR +include!(concat!(env!("OUT_DIR"), "/functions.rs"));