Add new env vars for use from cmake for colcon integration.

Closes #5
This commit is contained in:
Martin Dahl 2021-05-10 14:33:05 +02:00
parent 71c743f9b2
commit 5427f318f4
9 changed files with 156 additions and 114 deletions

View File

@ -1,7 +1,7 @@
[package]
name = "r2r"
version = "0.0.5"
version = "0.0.6"
authors = ["Martin Dahl <martin.dahl@gmail.com>"]
description = "Minimal ros2 bindings."
license = "Apache-2.0/MIT"
@ -13,15 +13,15 @@ serde_json = "1.0.62"
failure = "0.1.8"
failure_derive = "0.1.8"
lazy_static = "1.4.0"
common = { path = "common", version = "0.0.2" }
rcl = { path = "rcl", version = "0.0.2" }
msg_gen = { path = "msg_gen", version = "0.0.2" }
common = { path = "common", version = "0.0.3" }
rcl = { path = "rcl", version = "0.0.3" }
msg_gen = { path = "msg_gen", version = "0.0.3" }
[dev-dependencies]
serde_json = "1.0.62"
[build-dependencies]
common = { path = "common", version = "0.0.2" }
msg_gen = { path = "msg_gen", version = "0.0.2" }
common = { path = "common", version = "0.0.3" }
msg_gen = { path = "msg_gen", version = "0.0.3" }
[workspace]

View File

@ -1,7 +1,9 @@
R2R - Minimal ROS2 Rust bindings
====================
Minimal bindings for ROS2 that do *not* require hooking in to the ROS2 build infrastructure. If you want a more ROS-oriented approach, see <https://github.com/ros2-rust/ros2_rust>. In these bindings, convenience Rust types are created by calling into the c introspection libraries to circumvent the .msg/.idl pipeline. The convenience types can be ignored when you need to trade convenience for performance, e.g. treating large chunks of data manually.
Minimal bindings for ROS2 that do *not* require hooking in to the ROS2 build infrastructure -- `cargo build` is all you need. Convenience Rust types are created by calling into the c introspection libraries. This circumvents the ROS2 .msg/.idl pipeline by relying on already generated C code. The convenience types can be ignored when you need to trade convenience for performance, e.g. treating large chunks of data manually. By default, the behavior is to build bindings to the RCL and all message types that can be found in the currently sourced ros environment.
When integration with the colcon build system is desired, a CMakeLists.txt file can be used to limit the generation of bindings to only include specific (idl) dependencies. This is done through additional environment variables. A minimal example is available here: <https://github.com/m-dahl/r2r_minimal_node/>.
Manual is available on github pages <https://sequenceplanner.github.io/r2r/>

View File

@ -1,31 +1,31 @@
use common::*;
use msg_gen::*;
use common;
use msg_gen;
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::path::{Path,PathBuf};
fn main() {
println!("cargo:rerun-if-env-changed=AMENT_PREFIX_PATH");
common::print_cargo_watches();
let msgs = get_all_ros_msgs();
let msgs_list = parse_msgs(&msgs);
let msgs = as_map(&msgs_list);
let msg_list = if let Some(cmake_includes) = env::var("CMAKE_INCLUDE_DIRS").ok() {
let packages = cmake_includes.split(":").flat_map(|i| Path::new(i).parent()).collect::<Vec<_>>();
let deps = env::var("CMAKE_IDL_PACKAGES").unwrap_or(String::default());
let deps = deps.split(":").collect::<Vec<_>>();
let msgs = common::get_ros_msgs(&packages);
common::parse_msgs(&msgs).into_iter()
.filter(|msg| deps.contains(&msg.module.as_str())).collect::<Vec<_>>()
} else {
let ament_prefix_var = env::var("AMENT_PREFIX_PATH").expect("Source your ROS!");
let paths = ament_prefix_var.split(":").map(|i| Path::new(i)).collect::<Vec<_>>();
let msgs = common::get_ros_msgs(&paths);
common::parse_msgs(&msgs)
};
let msgs = common::as_map(&msg_list);
let mut modules = String::new();
for (module, prefixes) in &msgs {
println!(
"cargo:rustc-link-lib=dylib={}__rosidl_typesupport_c",
module
);
println!(
"cargo:rustc-link-lib=dylib={}__rosidl_typesupport_introspection_c",
module
);
println!("cargo:rustc-link-lib=dylib={}__rosidl_generator_c", module);
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();
@ -38,11 +38,11 @@ fn main() {
codegen.push_str(&format!(" pub mod {} {{\n", msg));
codegen.push_str(" use super::super::super::*;\n");
codegen.push_str(&generate_rust_service(module, prefix, msg));
codegen.push_str(&msg_gen::generate_rust_service(module, prefix, msg));
for s in &["Request", "Response"] {
let msgname = format!("{}_{}", msg, s);
codegen.push_str(&generate_rust_msg(module, prefix, &msgname));
codegen.push_str(&msg_gen::generate_rust_msg(module, prefix, &msgname));
println!("cargo:rustc-cfg=r2r__{}__{}__{}", module, prefix, msg);
}
codegen.push_str(" }\n");
@ -50,7 +50,7 @@ fn main() {
} else {
codegen.push_str(" use super::super::*;\n");
for msg in msgs {
codegen.push_str(&generate_rust_msg(module, prefix, msg));
codegen.push_str(&msg_gen::generate_rust_msg(module, prefix, msg));
println!("cargo:rustc-cfg=r2r__{}__{}__{}", module, prefix, msg);
}
}
@ -63,7 +63,7 @@ fn main() {
write!(f, "{}", codegen).unwrap();
}
let untyped_helper = generate_untyped_helper(&msgs_list);
let untyped_helper = msg_gen::generate_untyped_helper(&msg_list);
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let msgs_fn = out_path.join("_r2r_generated_msgs.rs");

View File

@ -1,9 +1,8 @@
[package]
name = "common"
version = "0.0.2"
version = "0.0.3"
authors = ["Martin Dahl <martin.dahl@gmail.com>"]
description = "Minimal ros2 bindings."
license = "Apache-2.0/MIT"
edition = "2018"

View File

@ -1,8 +1,14 @@
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::Read;
use std::path::PathBuf;
use std::path::Path;
pub fn print_cargo_watches() {
println!("cargo:rerun-if-env-changed=AMENT_PREFIX_PATH");
println!("cargo:rerun-if-env-changed=CMAKE_INCLUDE_DIRS");
println!("cargo:rerun-if-env-changed=CMAKE_LIBRARIES");
println!("cargo:rerun-if-env-changed=CMAKE_RECURSIVE_DEPENDENCIES");
}
#[derive(Debug)]
pub struct RosMsg {
@ -11,75 +17,67 @@ pub struct RosMsg {
pub name: String, // e.g. "String"
}
// TODO: actions and srv are similiar
pub fn get_all_ros_msgs() -> Vec<String> {
fn get_msgs_from_package(package: &Path) -> Vec<String> {
let resource_index_subfolder = "share/ament_index/resource_index";
let resource_type = "rosidl_interfaces";
let ament_prefix_var_name = "AMENT_PREFIX_PATH";
let ament_prefix_var = env::var(ament_prefix_var_name).expect("Source your ROS!");
let mut msgs: Vec<String> = Vec::new();
let path = package.to_owned();
let path = path.join(resource_index_subfolder);
let path = path.join(resource_type);
for ament_prefix_path in ament_prefix_var.split(":") {
// println!("prefix: {}", ament_prefix_path);
let mut msgs = vec![];
let path = PathBuf::from(ament_prefix_path);
let path = path.join(resource_index_subfolder);
let path = path.join(resource_type);
if let Ok(paths) = fs::read_dir(path) {
if let Ok(paths) = fs::read_dir(path) {
for path in paths {
// println!("PATH Name: {}", path.unwrap().path().display());
for path in paths {
// println!("PATH Name: {}", path.unwrap().path().display());
let path = path.unwrap().path();
let path2 = path.clone();
let file_name = path2.file_name().unwrap();
let path = path.unwrap().path();
let path2 = path.clone();
let file_name = path2.file_name().unwrap();
// println!("Messages for: {:?}", file_name);
if let Ok(mut file) = File::open(path) {
let mut s = String::new();
file.read_to_string(&mut s).unwrap();
let lines = s.lines();
// println!("Messages for: {:?}", file_name);
if let Ok(mut file) = File::open(path) {
let mut s = String::new();
file.read_to_string(&mut s).unwrap();
let lines = s.lines();
lines.for_each(|l| {
if l.starts_with("msg/") && (l.ends_with(".idl") || l.ends_with(".msg")) {
if let Some(file_name_str) = file_name.to_str() {
let substr = &l[4..l.len()-4];
let msg_name = format!("{}/msg/{}", file_name_str, substr);
msgs.push(msg_name);
}
lines.for_each(|l| {
if l.starts_with("msg/") && (l.ends_with(".idl") || l.ends_with(".msg")) {
if let Some(file_name_str) = file_name.to_str() {
let substr = &l[4..l.len()-4];
let msg_name = format!("{}/msg/{}", file_name_str, substr);
msgs.push(msg_name);
}
if l.starts_with("srv/") && (l.ends_with(".idl") || l.ends_with(".srv")) {
if let Some(file_name_str) = file_name.to_str() {
let substr = &l[4..l.len()-4];
let srv_name = format!("{}/srv/{}", file_name_str, substr);
msgs.push(srv_name);
}
}
if l.starts_with("srv/") && (l.ends_with(".idl") || l.ends_with(".srv")) {
if let Some(file_name_str) = file_name.to_str() {
let substr = &l[4..l.len()-4];
let srv_name = format!("{}/srv/{}", file_name_str, substr);
msgs.push(srv_name);
}
});
}
}
});
}
}
}
msgs.sort();
msgs.dedup();
return msgs;
msgs
}
#[test]
fn test_msg_list() {
pub fn get_ros_msgs(paths: &[&Path]) -> Vec<String> {
let mut msgs: Vec<String> = Vec::new();
let msgs = get_all_ros_msgs();
for m in &msgs {
println!("{}", m);
for p in paths {
println!("looking at prefix: {:?}", p);
let package_msgs = get_msgs_from_package(p);
println!("... found {:?}", package_msgs);
msgs.extend(package_msgs)
}
assert!(msgs.contains(&"std_msgs/msg/String".to_string()));
assert!(msgs.contains(&"builtin_interfaces/msg/Time".to_string()));
msgs.sort();
msgs.dedup();
msgs
}
pub fn parse_msgs(msgs: &Vec<String>) -> Vec<RosMsg> {

View File

@ -1,16 +1,16 @@
[package]
name = "msg_gen"
version = "0.0.2"
version = "0.0.3"
authors = ["Martin Dahl <martin.dahl@gmail.com>"]
edition = "2018"
[dependencies]
lazy_static = "1.4.0"
rcl = { path = "../rcl", version = "0.0.2" }
common = { path = "../common", version = "0.0.2" }
rcl = { path = "../rcl", version = "0.0.3" }
common = { path = "../common", version = "0.0.3" }
[build-dependencies]
bindgen = "0.57.0"
rcl = { path = "../rcl", version = "0.0.2" }
common = { path = "../common", version = "0.0.2" }
rcl = { path = "../rcl", version = "0.0.3" }
common = { path = "../common", version = "0.0.3" }
heck = "0.3.2"

View File

@ -1,18 +1,37 @@
extern crate bindgen;
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use common::*;
use std::path::{Path,PathBuf};
use bindgen;
use common;
fn main() {
println!("cargo:rerun-if-env-changed=AMENT_PREFIX_PATH");
common::print_cargo_watches();
let msgs = get_all_ros_msgs();
let msg_list = parse_msgs(&msgs);
let msg_map = as_map(&msg_list);
let mut builder = bindgen::Builder::default();
let msg_list = if let Some(cmake_includes) = env::var("CMAKE_INCLUDE_DIRS").ok() {
let packages = cmake_includes.split(":").flat_map(|i| Path::new(i).parent()).collect::<Vec<_>>();
for p in cmake_includes.split(":") {
builder = builder.clang_arg(format!("-I{}", p));
}
let deps = env::var("CMAKE_IDL_PACKAGES").unwrap_or(String::default());
let deps = deps.split(":").collect::<Vec<_>>();
let msgs = common::get_ros_msgs(&packages);
common::parse_msgs(&msgs).into_iter()
.filter(|msg| deps.contains(&msg.module.as_str())).collect::<Vec<_>>()
} else {
let ament_prefix_var = env::var("AMENT_PREFIX_PATH").expect("Source your ROS!");
for p in ament_prefix_var.split(":") {
builder = builder.clang_arg(format!("-I{}/include", p));
}
let paths = ament_prefix_var.split(":").map(|i| Path::new(i)).collect::<Vec<_>>();
let msgs = common::get_ros_msgs(&paths);
common::parse_msgs(&msgs)
};
let msg_map = common::as_map(&msg_list);
for module in msg_map.keys() {
println!(
@ -72,10 +91,10 @@ fn main() {
let mut f = File::create(introspection_fn).unwrap();
write!(f, "{}", introspecion_map).unwrap();
let mut builder = bindgen::Builder::default()
builder = builder
.header(msg_includes_fn.to_str().unwrap())
.derive_copy(false)
// blacklist types that are handled by rcl bindings
// blacklist types that are handled by rcl bindings
.blacklist_type("rosidl_message_type_support_t")
.blacklist_type("rosidl_service_type_support_t")
.blacklist_type("rosidl_runtime_c__String")
@ -105,14 +124,6 @@ fn main() {
non_exhaustive: false,
});
let ament_prefix_var_name = "AMENT_PREFIX_PATH";
let ament_prefix_var = env::var(ament_prefix_var_name).expect("Source your ROS!");
for ament_prefix_path in ament_prefix_var.split(":") {
builder = builder.clang_arg(format!("-I{}/include", ament_prefix_path));
println!("cargo:rustc-link-search=native={}/lib", ament_prefix_path);
}
let bindings = builder.generate().expect("Unable to generate bindings");
bindings

View File

@ -1,6 +1,6 @@
[package]
name = "rcl"
version = "0.0.2"
version = "0.0.3"
authors = ["Martin Dahl <martin.dahl@gmail.com>"]
edition = "2018"
@ -10,3 +10,5 @@ widestring = "0.4.3"
[build-dependencies]
bindgen = "0.57.0"
itertools = "0.10.0"
common = { path = "../common", version = "0.0.3" }

View File

@ -1,10 +1,12 @@
extern crate bindgen;
use std::env;
use std::path::PathBuf;
use std::path::{Path,PathBuf};
use itertools::Itertools;
use common;
fn main() {
println!("cargo:rerun-if-env-changed=AMENT_PREFIX_PATH");
common::print_cargo_watches();
let mut builder = bindgen::Builder::default()
.header("src/rcl_wrapper.h")
@ -14,12 +16,40 @@ fn main() {
non_exhaustive: false,
});
let ament_prefix_var_name = "AMENT_PREFIX_PATH";
let ament_prefix_var = env::var(ament_prefix_var_name).expect("Source your ROS!");
if let Some(cmake_includes) = env::var("CMAKE_INCLUDE_DIRS").ok() {
// we are running from cmake, do special thing.
let mut includes = cmake_includes.split(":").collect::<Vec<_>>();
includes.sort();
includes.dedup();
for x in &includes {
let clang_arg = format!("-I{}", x);
println!("adding clang arg: {}", clang_arg);
builder = builder.clang_arg(clang_arg);
}
env::var("CMAKE_LIBRARIES").unwrap_or(String::new()).split(":")
.into_iter()
.filter(|s| s.contains(".so") || s.contains(".dylib"))
.flat_map(|l| Path::new(l).parent().and_then(|p| p.to_str()))
.unique()
.for_each(|pp| {
println!("cargo:rustc-link-search=native={}", pp)
// we could potentially do the below instead of hardcoding which libs we rely on.
// let filename = path.file_stem().and_then(|f| f.to_str()).unwrap();
// let without_lib = filename.strip_prefix("lib").unwrap();
// println!("cargo:rustc-link-lib=dylib={}", without_lib);
});
} else {
let ament_prefix_var_name = "AMENT_PREFIX_PATH";
let ament_prefix_var = env::var(ament_prefix_var_name).expect("Source your ROS!");
for ament_prefix_path in ament_prefix_var.split(":") {
builder = builder.clang_arg(format!("-I{}/include", ament_prefix_path));
println!("added include search dir: {}" , format!("-I{}/include", ament_prefix_path));
println!("cargo:rustc-link-search=native={}/lib", ament_prefix_path);
}
for ament_prefix_path in ament_prefix_var.split(":") {
builder = builder.clang_arg(format!("-I{}/include", ament_prefix_path));
println!("cargo:rustc-link-search=native={}/lib", ament_prefix_path);
}
println!("cargo:rustc-link-lib=dylib=rcl");