[r2r] More advanced binding generation

- Add "save-bindgen" feature to store generated bindings in source directory.
- Add "doc-only" to disable bindgen and linking, and use saved bindings
  in source directory instead.
- Bindings are cached to speed on bindgen process.
This commit is contained in:
aeon 2022-11-25 05:41:35 +08:00
parent 8c42f906c7
commit 81896423bb
2 changed files with 193 additions and 91 deletions

View File

@ -34,3 +34,10 @@ rand = "0.8.5"
[build-dependencies]
r2r_common = { path = "../r2r_common", version = "0.3.2" }
r2r_msg_gen = { path = "../r2r_msg_gen", version = "0.3.3" }
[features]
save-bindgen = ["r2r_rcl/save-bindgen", "r2r_msg_gen/save-bindgen", "r2r_actions/save-bindgen"]
doc-only = ["r2r_rcl/doc-only", "r2r_msg_gen/doc-only", "r2r_actions/doc-only"]
[package.metadata.docs.rs]
features = ["doc-only"]

View File

@ -1,121 +1,216 @@
use std::env;
use std::fs::File;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::{env, fs};
const LIST_FILENAME: &str = "files.txt";
const MSGS_FILENAME: &str = "_r2r_generated_msgs.rs";
const UNTYPED_FILENAME: &str = "_r2r_generated_untyped_helper.rs";
const UNTYPED_SERVICE_FILENAME: &str = "_r2r_generated_service_helper.rs";
const UNTYPED_ACTION_FILENAME: &str = "_r2r_generated_action_helper.rs";
const GENERATED_FILES: &[&str] = &[
MSGS_FILENAME,
UNTYPED_FILENAME,
UNTYPED_SERVICE_FILENAME,
UNTYPED_ACTION_FILENAME,
];
fn main() {
r2r_common::print_cargo_watches();
let msg_list = r2r_common::get_wanted_messages();
let env_hash = r2r_common::get_env_hash();
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let bindgen_dir = out_dir.join(env_hash);
let save_dir = manifest_dir.join("bindings");
let mark_file = bindgen_dir.join("done");
if cfg!(feature = "doc-only") {
// If "doc-only" feature is present, copy from $crate/bindings/* to OUT_DIR
copy_files(&save_dir, &out_dir);
} else {
// If bindgen was done before, use cached files.
if !mark_file.exists() {
eprintln!("Generate bindings in '{}'", bindgen_dir.display());
generate_bindings(&bindgen_dir);
touch(&mark_file);
} else {
eprintln!("Used cached files in '{}'", bindgen_dir.display());
}
copy_files(&bindgen_dir, &out_dir);
#[cfg(feature = "save-bindgen")]
{
fs::create_dir_all(&save_dir).unwrap();
copy_files(&bindgen_dir, &save_dir);
}
}
}
fn generate_bindings(bindgen_dir: &Path) {
fs::create_dir_all(&bindgen_dir).unwrap();
let msg_list = r2r_common::get_wanted_messages();
let msgs = r2r_common::as_map(&msg_list);
let mut modules = String::new();
let mod_files: Vec<_> = msgs
.iter()
.map(|(module, prefixes)| {
let mod_text = format!(
r#"pub mod {module}{{include!(concat!(env!("OUT_DIR"), "/{module}.rs"));}}{lf}"#,
module = module,
lf = "\n"
);
modules.push_str(&mod_text);
for (module, prefixes) in &msgs {
modules.push_str(&format!(
r#"pub mod {module}{{include!(concat!(env!("OUT_DIR"), "/{module}.rs"));}}{lf}"#,
module = module,
lf = "\n"
));
let mut codegen = String::new();
let mut codegen = String::new();
for (prefix, msgs) in prefixes {
codegen.push_str(&format!(" pub mod {} {{\n", prefix));
prefixes.into_iter().for_each(|(prefix, msgs)| {
codegen.push_str(&format!(" pub mod {} {{\n", prefix));
if prefix == &"action" {
for msg in msgs {
codegen.push_str("#[allow(non_snake_case)]\n");
codegen.push_str(&format!(" pub mod {} {{\n", msg));
codegen.push_str(" use super::super::super::*;\n");
codegen.push_str(&r2r_msg_gen::generate_rust_action(module, prefix, msg));
for s in &["Goal", "Result", "Feedback"] {
let msgname = format!("{}_{}", msg, s);
codegen.push_str(&r2r_msg_gen::generate_rust_msg(module, prefix, &msgname));
println!("cargo:rustc-cfg=r2r__{}__{}__{}", module, prefix, msg);
}
// "internal" services that implements the action type
for srv in &["SendGoal", "GetResult"] {
if prefix == &"action" {
for msg in msgs {
codegen.push_str("#[allow(non_snake_case)]\n");
codegen.push_str(&format!(" pub mod {} {{\n", srv));
codegen.push_str(" use super::super::super::super::*;\n");
codegen.push_str(&format!(" pub mod {} {{\n", msg));
codegen.push_str(" use super::super::super::*;\n");
let srvname = format!("{}_{}", msg, srv);
codegen.push_str(&r2r_msg_gen::generate_rust_service(
module, prefix, &srvname,
));
codegen.push_str(&r2r_msg_gen::generate_rust_action(module, prefix, msg));
for s in &["Request", "Response"] {
let msgname = format!("{}_{}_{}", msg, srv, s);
for s in &["Goal", "Result", "Feedback"] {
let msgname = format!("{}_{}", msg, s);
codegen.push_str(&r2r_msg_gen::generate_rust_msg(
module, prefix, &msgname,
));
println!("cargo:rustc-cfg=r2r__{}__{}__{}", module, prefix, msg);
}
// "internal" services that implements the action type
for srv in &["SendGoal", "GetResult"] {
codegen.push_str("#[allow(non_snake_case)]\n");
codegen.push_str(&format!(" pub mod {} {{\n", srv));
codegen.push_str(" use super::super::super::super::*;\n");
let srvname = format!("{}_{}", msg, srv);
codegen.push_str(&r2r_msg_gen::generate_rust_service(
module, prefix, &srvname,
));
for s in &["Request", "Response"] {
let msgname = format!("{}_{}_{}", msg, srv, s);
codegen.push_str(&r2r_msg_gen::generate_rust_msg(
module, prefix, &msgname,
));
}
codegen.push_str(" }\n");
}
// also "internal" feedback message type that wraps the feedback type with a uuid
let feedback_msgname = format!("{}_FeedbackMessage", msg);
codegen.push_str(&r2r_msg_gen::generate_rust_msg(
module,
prefix,
&feedback_msgname,
));
codegen.push_str(" }\n");
}
} else if prefix == &"srv" {
for msg in msgs {
codegen.push_str("#[allow(non_snake_case)]\n");
codegen.push_str(&format!(" pub mod {} {{\n", msg));
codegen.push_str(" use super::super::super::*;\n");
codegen.push_str(&r2r_msg_gen::generate_rust_service(module, prefix, msg));
for s in &["Request", "Response"] {
let msgname = format!("{}_{}", msg, s);
codegen.push_str(&r2r_msg_gen::generate_rust_msg(
module, prefix, &msgname,
));
println!("cargo:rustc-cfg=r2r__{}__{}__{}", module, prefix, msg);
}
codegen.push_str(" }\n");
}
// also "internal" feedback message type that wraps the feedback type with a uuid
let feedback_msgname = format!("{}_FeedbackMessage", msg);
codegen.push_str(&r2r_msg_gen::generate_rust_msg(
module,
prefix,
&feedback_msgname,
));
codegen.push_str(" }\n");
}
} else if prefix == &"srv" {
for msg in msgs {
codegen.push_str("#[allow(non_snake_case)]\n");
codegen.push_str(&format!(" pub mod {} {{\n", msg));
codegen.push_str(" use super::super::super::*;\n");
codegen.push_str(&r2r_msg_gen::generate_rust_service(module, prefix, msg));
for s in &["Request", "Response"] {
let msgname = format!("{}_{}", msg, s);
codegen.push_str(&r2r_msg_gen::generate_rust_msg(module, prefix, &msgname));
} else if prefix == &"msg" {
codegen.push_str(" use super::super::*;\n");
for msg in msgs {
codegen.push_str(&r2r_msg_gen::generate_rust_msg(module, prefix, msg));
println!("cargo:rustc-cfg=r2r__{}__{}__{}", module, prefix, msg);
}
codegen.push_str(" }\n");
} else {
panic!("unknown prefix type: {}", prefix);
}
} else if prefix == &"msg" {
codegen.push_str(" use super::super::*;\n");
for msg in msgs {
codegen.push_str(&r2r_msg_gen::generate_rust_msg(module, prefix, msg));
println!("cargo:rustc-cfg=r2r__{}__{}__{}", module, prefix, msg);
}
} else {
panic!("unknown prefix type: {}", prefix);
}
codegen.push_str(" }\n");
}
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let mod_fn = out_path.join(&format!("{}.rs", module));
let mut f = File::create(mod_fn).unwrap();
write!(f, "{}", codegen).unwrap();
codegen.push_str(" }\n");
});
let file_name = format!("{}.rs", module);
let mod_file = bindgen_dir.join(&file_name);
fs::write(&mod_file, codegen).unwrap();
file_name
})
.collect();
// Write helper files
{
let untyped_helper = r2r_msg_gen::generate_untyped_helper(&msg_list);
let untyped_service_helper = r2r_msg_gen::generate_untyped_service_helper(&msg_list);
let untyped_action_helper = r2r_msg_gen::generate_untyped_action_helper(&msg_list);
let msgs_file = bindgen_dir.join(MSGS_FILENAME);
let untyped_file = bindgen_dir.join(UNTYPED_FILENAME);
let untyped_service_file = bindgen_dir.join(UNTYPED_SERVICE_FILENAME);
let untyped_action_file = bindgen_dir.join(UNTYPED_ACTION_FILENAME);
fs::write(&msgs_file, &modules).unwrap();
fs::write(&untyped_file, &untyped_helper).unwrap();
fs::write(&untyped_service_file, &untyped_service_helper).unwrap();
fs::write(&untyped_action_file, &untyped_action_helper).unwrap();
}
let untyped_helper = r2r_msg_gen::generate_untyped_helper(&msg_list);
let untyped_service_helper = r2r_msg_gen::generate_untyped_service_helper(&msg_list);
let untyped_action_helper = r2r_msg_gen::generate_untyped_action_helper(&msg_list);
// Save file list
{
let list_file = bindgen_dir.join(LIST_FILENAME);
let mut writer = File::create(list_file).unwrap();
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let msgs_fn = out_path.join("_r2r_generated_msgs.rs");
let untyped_fn = out_path.join("_r2r_generated_untyped_helper.rs");
let untyped_service_fn = out_path.join("_r2r_generated_service_helper.rs");
let untyped_action_fn = out_path.join("_r2r_generated_action_helper.rs");
for file_name in mod_files {
writeln!(writer, "{}", file_name).unwrap();
}
let mut f = File::create(msgs_fn).unwrap();
write!(f, "{}", modules).unwrap();
let mut f = File::create(untyped_fn).unwrap();
write!(f, "{}", untyped_helper).unwrap();
let mut f = File::create(untyped_service_fn).unwrap();
write!(f, "{}", untyped_service_helper).unwrap();
let mut f = File::create(untyped_action_fn).unwrap();
write!(f, "{}", untyped_action_helper).unwrap();
for file_name in GENERATED_FILES {
writeln!(writer, "{}", file_name).unwrap();
}
}
}
fn copy_files(src_dir: &Path, tgt_dir: &Path) {
eprintln!(
"Copy files from '{}' to '{}'",
src_dir.display(),
tgt_dir.display()
);
let src_list_file = src_dir.join(LIST_FILENAME);
let tgt_list_file = tgt_dir.join(LIST_FILENAME);
fs::read_to_string(&src_list_file)
.unwrap()
.lines()
.for_each(|file_name| {
let src_file = src_dir.join(file_name);
let tgt_file = tgt_dir.join(file_name);
fs::copy(&src_file, &tgt_file).unwrap();
});
fs::copy(&src_list_file, &tgt_list_file).unwrap();
}
fn touch(path: &Path) {
OpenOptions::new()
.create(true)
.write(true)
.open(path)
.unwrap_or_else(|_| panic!("Unable to create file '{}'", path.display()));
}