Allow doc-only feature to work without ROS

This commit is contained in:
aeon 2023-06-23 13:20:29 +08:00 committed by Martin Dahl
parent e8dd2bf9ab
commit 228f713f14
5 changed files with 110 additions and 10 deletions

View File

@ -29,10 +29,14 @@ fn main() {
let save_dir = manifest_dir.join("bindings");
let mark_file = bindgen_dir.join("done");
if cfg!(feature = "doc-only") {
#[cfg(feature = "doc-only")]
{
// If "doc-only" feature is present, copy from $crate/bindings/* to OUT_DIR
copy_files(&save_dir, &out_dir);
} else {
}
#[cfg(not(feature = "doc-only"))]
{
// If bindgen was done before, use cached files.
if !mark_file.exists() {
eprintln!("Generate bindings in '{}'", bindgen_dir.display());
@ -52,6 +56,7 @@ fn main() {
}
}
#[cfg(not(feature = "doc-only"))]
fn generate_bindings(bindgen_dir: &Path) {
fs::create_dir_all(bindgen_dir).unwrap();

View File

@ -52,6 +52,10 @@ fn run_bindgen() {
}
}
#[cfg(feature = "doc-only")]
fn run_dynlink() {}
#[cfg(not(feature = "doc-only"))]
fn run_dynlink() {
r2r_common::print_cargo_link_search();
println!("cargo:rustc-link-lib=dylib=rcl_action");

View File

@ -2,6 +2,8 @@ use bindgen::Bindings;
use heck::ToSnakeCase;
use itertools::chain;
use itertools::iproduct;
use itertools::Either;
use itertools::Itertools;
use quote::format_ident;
use quote::quote;
use r2r_common::RosMsg;
@ -18,11 +20,13 @@ const MSG_INCLUDES_FILENAME: &str = "msg_includes.h";
const INTROSPECTION_FILENAME: &str = "introspection_functions.rs";
const CONSTANTS_FILENAME: &str = "constants.rs";
const BINDINGS_FILENAME: &str = "msg_bindings.rs";
const BINDINGS_DOC_ONLY_FILENAME: &str = "msg_bindings_doc_only.rs";
const GENERATED_FILES: &[&str] = &[
MSG_INCLUDES_FILENAME,
INTROSPECTION_FILENAME,
CONSTANTS_FILENAME,
BINDINGS_FILENAME,
BINDINGS_DOC_ONLY_FILENAME,
];
const SRV_SUFFICES: &[&str] = &["Request", "Response"];
const ACTION_SUFFICES: &[&str] = &["Goal", "Result", "Feedback", "FeedbackMessage"];
@ -219,11 +223,7 @@ fn generate_introspecion_map(bindgen_dir: &Path, msg_list: &[RosMsg]) {
.map(|(key, func_str)| {
// Generate a hashmap entry
let func_ident = format_ident!("{func_str}");
let tokens = quote! {
#key =>
#func_ident
as unsafe extern "C" fn() -> *const rosidl_message_type_support_t
};
let tokens = quote! { #key => #func_ident as IntrospectionFn };
// force_send to workaround !Send
(key, unsafe { force_send(tokens) })
@ -237,8 +237,13 @@ fn generate_introspecion_map(bindgen_dir: &Path, msg_list: &[RosMsg]) {
// Write the file content
let introspecion_map = quote! {
#[cfg(feature = "doc-only")]
type IntrospectionFn = fn() -> *const rosidl_message_type_support_t;
#[cfg(not(feature = "doc-only"))]
type IntrospectionFn = unsafe extern "C" fn() -> *const rosidl_message_type_support_t;
#[cfg(not(feature = "doc-only"))]
static INTROSPECTION_FNS: phf::Map<&'static str, IntrospectionFn> = phf::phf_map! {
#(#entries),*
};
@ -251,6 +256,7 @@ fn generate_introspecion_map(bindgen_dir: &Path, msg_list: &[RosMsg]) {
fn generate_bindings_file(bindgen_dir: &Path) -> Bindings {
let msg_includes_file = bindgen_dir.join(MSG_INCLUDES_FILENAME);
let bindings_file = bindgen_dir.join(BINDINGS_FILENAME);
let bindings_doc_only_file = bindgen_dir.join(BINDINGS_DOC_ONLY_FILENAME);
let builder = r2r_common::setup_bindgen_builder()
.header(msg_includes_file.to_str().unwrap())
@ -288,15 +294,81 @@ fn generate_bindings_file(bindgen_dir: &Path) -> Bindings {
.size_t_is_usize(true)
.no_debug("_OSUnaligned.*")
.generate_comments(false)
.merge_extern_blocks(true)
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
});
let bindings = builder.generate().expect("Unable to generate bindings");
bindings
.write_to_file(bindings_file)
.write_to_file(&bindings_file)
.expect("Couldn't write bindings!");
// #[cfg(feature = "save-bindgen")]
{
let content = fs::read_to_string(bindings_file).unwrap();
let file = syn::parse_file(&content).expect("syn::parse_file() failed");
let new_items: Vec<syn::Item> = file
.items
.into_iter()
.flat_map(|item| match item {
syn::Item::ForeignMod(foreign_mod) => {
let Some(abi_name) = foreign_mod.abi.name.as_ref() else {
return vec![syn::Item::ForeignMod(foreign_mod)];
};
if abi_name.value() != "C" {
return vec![syn::Item::ForeignMod(foreign_mod)];
}
let (generated_funcs, remaining_items): (Vec<_>, Vec<_>) = foreign_mod
.items
.into_iter()
.partition_map(|item| match item {
syn::ForeignItem::Fn(fn_) => {
let syn::ForeignItemFn {
attrs,
vis,
sig,
semi_token: _,
} = fn_;
let new_fn: syn::ItemFn = syn::parse2(quote! {
#(#attrs)*
#[allow(unused)]
#vis #sig { todo!() }
})
.unwrap();
Either::Left(new_fn)
}
item => Either::Right(item),
});
let new_foreign_mod = syn::Item::ForeignMod(syn::ItemForeignMod {
items: remaining_items,
..foreign_mod
});
let new_func_items = generated_funcs.into_iter().map(syn::Item::Fn);
chain!([new_foreign_mod], new_func_items).collect()
}
item => vec![item],
})
.collect();
let new_file = syn::File {
items: new_items,
..file
};
let new_file = quote! { #new_file };
let mut writer = BufWriter::new(File::create(bindings_doc_only_file).unwrap());
write!(writer, "{}", new_file).expect("Couldn't write bindings!");
writer.flush().unwrap();
}
bindings
}
@ -458,6 +530,7 @@ fn generate_constants(bindgen_dir: &Path, msg_list: &[RosMsg], bindings: &Bindin
// Write the file content.
let constants_map = quote! {
#[cfg(not(feature = "doc-only"))]
static CONSTANTS_MAP: phf::Map<&'static str, &[(&str, &str)]> = phf::phf_map! {
#(#entries),*
};
@ -467,7 +540,11 @@ fn generate_constants(bindgen_dir: &Path, msg_list: &[RosMsg], bindings: &Bindin
writeln!(&mut writer, "{}", constants_map).unwrap();
}
fn run_dynlink(#[allow(unused_variables)] msg_list: &[RosMsg]) {
#[cfg(feature = "doc-only")]
fn run_dynlink(_: &[RosMsg]) {}
#[cfg(not(feature = "doc-only"))]
fn run_dynlink(msg_list: &[RosMsg]) {
r2r_common::print_cargo_link_search();
let msg_map = r2r_common::as_map(msg_list);

View File

@ -5,7 +5,12 @@
#![allow(dead_code)]
#![allow(clippy::all)]
#[cfg(feature = "doc-only")]
include!(concat!(env!("OUT_DIR"), "/msg_bindings_doc_only.rs"));
#[cfg(not(feature = "doc-only"))]
include!(concat!(env!("OUT_DIR"), "/msg_bindings.rs"));
include!(concat!(env!("OUT_DIR"), "/introspection_functions.rs"));
include!(concat!(env!("OUT_DIR"), "/constants.rs"));
@ -148,6 +153,7 @@ unsafe fn introspection<'a>(
)
}
#[cfg(not(feature = "doc-only"))]
pub fn generate_rust_service(
module_: &str,
prefix_: &str,
@ -178,6 +184,7 @@ pub fn generate_rust_service(
)
}
#[cfg(not(feature = "doc-only"))]
pub fn generate_rust_action(module_: &str, prefix_: &str, name_: &str) -> proc_macro2::TokenStream {
let ident = format_ident!(
"rosidl_typesupport_c__\
@ -263,7 +270,7 @@ pub fn generate_rust_action(module_: &str, prefix_: &str, name_: &str) -> proc_m
}
}
// TODO: this is a terrible hack :)
#[cfg(not(feature = "doc-only"))]
pub fn generate_rust_msg(module_: &str, prefix_: &str, name_: &str) -> proc_macro2::TokenStream {
let key = format!("{}__{}__{}", module_, prefix_, name_);
let func = *INTROSPECTION_FNS
@ -721,6 +728,7 @@ pub fn generate_rust_msg(module_: &str, prefix_: &str, name_: &str) -> proc_macr
}
}
#[cfg(not(feature = "doc-only"))]
pub fn generate_untyped_helper(msgs: &[RosMsg]) -> proc_macro2::TokenStream {
let (funcs, entries): (Vec<_>, Vec<_>) = msgs
.into_par_iter()
@ -770,6 +778,7 @@ pub fn generate_untyped_helper(msgs: &[RosMsg]) -> proc_macro2::TokenStream {
}
}
#[cfg(not(feature = "doc-only"))]
pub fn generate_untyped_service_helper(msgs: &[RosMsg]) -> proc_macro2::TokenStream {
let (funcs, entries): (Vec<_>, Vec<_>) = msgs
.into_par_iter()
@ -818,6 +827,7 @@ pub fn generate_untyped_service_helper(msgs: &[RosMsg]) -> proc_macro2::TokenStr
}
}
#[cfg(not(feature = "doc-only"))]
pub fn generate_untyped_action_helper(msgs: &[RosMsg]) -> proc_macro2::TokenStream {
let (funcs, entries): (Vec<_>, Vec<_>) = msgs
.into_par_iter()

View File

@ -56,6 +56,10 @@ fn run_bindgen() {
});
}
#[cfg(feature = "doc-only")]
fn run_dynlink() {}
#[cfg(not(feature = "doc-only"))]
fn run_dynlink() {
r2r_common::print_cargo_link_search();
println!("cargo:rustc-link-lib=dylib=rcl");