diff --git a/r2r/src/msg_types.rs b/r2r/src/msg_types.rs index 96d2141..dbd2557 100644 --- a/r2r/src/msg_types.rs +++ b/r2r/src/msg_types.rs @@ -770,4 +770,14 @@ mod tests { // the message should contain something (default msg) assert!(!json_request.to_string().is_empty()); } + + + #[cfg(r2r__action_msgs__msg__GoalStatus)] + #[test] + fn test_msg_constants() { + use action_msgs::msg::GoalStatus; + let gs = GoalStatus::default(); + + assert_eq!(gs.status, GoalStatus::STATUS_UNKNOWN as i8); + } } diff --git a/r2r_msg_gen/build.rs b/r2r_msg_gen/build.rs index 7d8ab58..f28445d 100644 --- a/r2r_msg_gen/build.rs +++ b/r2r_msg_gen/build.rs @@ -4,13 +4,16 @@ use r2r_common::RosMsg; use std::fs::OpenOptions; use std::path::{Path, PathBuf}; use std::{env, fs}; +use std::collections::HashMap; 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 GENERATED_FILES: &[&str] = &[ MSG_INCLUDES_FILENAME, INTROSPECTION_FILENAME, + CONSTANTS_FILENAME, BINDINGS_FILENAME, ]; @@ -76,6 +79,7 @@ fn run_bindgen(msg_list: &[RosMsg]) { fn generate_bindings(bindgen_dir: &Path, msg_list: &[RosMsg]) { let msg_includes_file = bindgen_dir.join(MSG_INCLUDES_FILENAME); let introspection_file = bindgen_dir.join(INTROSPECTION_FILENAME); + let constants_file = bindgen_dir.join(CONSTANTS_FILENAME); let bindings_file = bindgen_dir.join(BINDINGS_FILENAME); let mut includes = String::new(); @@ -170,6 +174,55 @@ fn generate_bindings(bindgen_dir: &Path, msg_list: &[RosMsg]) { let bindings = builder.generate().expect("Unable to generate bindings"); + // Let's add a hack to generate constants. + let str_bindings = bindings.to_string(); + // find all lines which look suspiciosly like a constant. + let mut constants: HashMap> = HashMap::new(); + for msg in msg_list { + let prefix = &format!("pub const {}__{}__{}__", &msg.module, &msg.prefix, &msg.name); + let mut lines = str_bindings.lines(); + while let Some(line) = lines.next() { + if let Some(constant) = line.strip_prefix(prefix) { + if let Some((con, typ)) = constant.split_once(":") { + // These are generated automatically for arrays and strings, we don't need to expose them. + if con.ends_with("__MAX_SIZE") || con.ends_with("__MAX_STRING_SIZE") { + continue; + } + let key = format!("{}__{}__{}", msg.module, msg.prefix, msg.name); + if let Some((t, _)) = typ.split_once("=") { + constants.entry(key).or_default().push((con.to_string(), t.trim().to_string())); + } else if let Some(next_line) = lines.next() { + // type has moved down to the next line. (bindgen has a max line width) + if let Some((t, _)) = next_line.split_once("=") { + constants.entry(key).or_default().push((con.to_string(), t.trim().to_string())); + } else { + panic!("Code generation failure. Type not found in line! {}", next_line); + } + } else { + panic!("Code generation failure. Type not found in line! {}", line); + } + } + } + } + } + // generate a constant which holds all constants. + let mut constants_map = String::from( + "\ + lazy_static! { + static ref CONSTANTS_MAP: HashMap<&'static str, Vec<(String,String)>> = { + let mut m = HashMap::new();\ +"); + for (msg,msg_constants) in constants { + let msg_constants_str = format!("vec![{}]", + msg_constants.iter() + .map(|(c,t)| format!("(\"{c}\".to_string(), \"{t}\".to_string())")) + .collect::>().join(",")); + + constants_map.push_str(&format!("m.insert(\"{}\", {});\n", msg, msg_constants_str)); + } + constants_map.push_str("m \n }; }\n\n"); + fs::write(&constants_file, constants_map).unwrap(); + bindings .write_to_file(bindings_file) .expect("Couldn't write bindings!"); diff --git a/r2r_msg_gen/src/lib.rs b/r2r_msg_gen/src/lib.rs index 8c730ee..a4c9bd8 100644 --- a/r2r_msg_gen/src/lib.rs +++ b/r2r_msg_gen/src/lib.rs @@ -6,6 +6,7 @@ #![allow(clippy::all)] include!(concat!(env!("OUT_DIR"), "/msg_bindings.rs")); include!(concat!(env!("OUT_DIR"), "/introspection_functions.rs")); +include!(concat!(env!("OUT_DIR"), "/constants.rs")); #[macro_use] extern crate lazy_static; @@ -602,6 +603,23 @@ pub fn generate_rust_msg(module_: &str, prefix_: &str, name_: &str) -> String { msgname = name ); + + let constants = CONSTANTS_MAP.get(key.as_str()).cloned().unwrap_or_default(); + let mut constant_strings = vec![]; + for (c, typ) in constants { + constant_strings.push(format!(" pub const {c}: {typ} = {key}__{c};")); + } + let impl_constants = format!(" + #[allow(non_upper_case_globals)] + impl {msgname} {{ + {constants} + }} + ", + msgname = name, + constants = constant_strings.join("\n") + ); + + let module_str = format!( " #[derive(Clone,Debug,PartialEq,Serialize,Deserialize)] @@ -610,12 +628,14 @@ pub fn generate_rust_msg(module_: &str, prefix_: &str, name_: &str) -> String { {fields} }}\n {typesupport}\n - {default}\n\n + {default}\n + {constants}\n\n ", msgname = name, fields = fields, typesupport = typesupport, - default = impl_default + default = impl_default, + constants = impl_constants, ); module_str