Skip to content

Commit

Permalink
Merge pull request #135 from Carter12s/build-rs-example
Browse files Browse the repository at this point in the history
Add an example for build.rs usage
  • Loading branch information
Carter12s authored Oct 3, 2023
2 parents 33dd0a9 + 99b1568 commit 23e5bc0
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 3 deletions.
32 changes: 30 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]

members = [
"example_package",
"roslibrust",
"roslibrust_codegen",
"roslibrust_codegen_macro",
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ It's used like this:
roslibrust_codegen_macro::find_and_generate_ros_messages!("assets/ros1_common_interfaces/std_msgs");
```

Code generation can also be done with a script using the same code generation backend called by the macro. See `roslibrust_test/src/main.rs` for a demonstration of how this can be done.
Code generation can also be done with a script using the same code generation backend called by the macro. See the contents of `example_package` for a detailed example of how this can be done. While the proc_macros are extremely convenient for getting started
there is currently no (good) way for a proc_macro to inform the compiler that it needs to be re-generated when an external file
changes. Using a build script requires more setup, but can correctly handling re-building when message files are edited.

## Experimental Support for ROS1 Native

Expand Down
22 changes: 22 additions & 0 deletions example_package/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "example_package"
version = "0.1.0"
edition = "2021"

[dependencies]
# This is what we need for using this package in our mono-repo
roslibrust = { path = "../roslibrust" }
# Normally you would have: roslibrust = "0.7"
# TODO in the current state of this example, we don't actually need roslibrust
# These dependencies are needed by the code that roslibrust_codegen autogenerates
# They are silently "leaked dependencies of the crate"
# See https://github.com/Carter12s/roslibrust/issues/72 for resolution path
serde = "1.0"
smart-default = "0.7"
roslibrust_codegen = { path = "../roslibrust_codegen" }

[build-dependencies]
# We depend on codegen as a build dependency as we (should) only need it to generate our types
roslibrust_codegen = { path = "../roslibrust_codegen" }
# This crate is very helpful for build.rs files but not required
cargo-emit = "0.2"
4 changes: 4 additions & 0 deletions example_package/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Example RosLibRust Package
The point of this package is provide a good example of how to integrate roslibrust into a package using a build.rs file.

This package also serves as a testbed for maintainers of roslibrust to refine build.rs integration.
40 changes: 40 additions & 0 deletions example_package/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// This is an example / template build script that other projects looking to incorporate roslibrust into their codebase
// are recommended to adopt. Using build.rs is currently the preferred / recommended approach as there is not currently
// a mechanism for proc_macros to indicate they need to be re-run when an external file changes. With build.rs it is
// possible to robustly trigger re-builds when dependent message files are changed.
// It is highly recommended to read the Cargo Book section on build scripts before attempting this approach as some
// care is needed: https://doc.rust-lang.org/cargo/reference/build-scripts.html
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Define our search paths, for this example we're using out test_msgs and std_msgs for ros1
// These can be pulled either from ROS_PACKAGE_PATH or given manually.
// While ROS_PACKAGE_PATH can be very convenient, it can also be VERY confusing
// We recommend using explicit paths only for more reliable and reproducible builds.
let p = vec![
"../assets/ros1_common_interfaces/std_msgs".into(),
"../assets/ros1_test_msgs".into(),
];

// Actually invoke code generation on our search paths.
// What we get back is a TokenStream the type normally returned by a proc_macro in rust.
// For a build.rs we typically want to serialize this data to a file for later import
// MAJOR TODO need to incorporate error handling here when that PR is merged
let tokens = roslibrust_codegen::find_and_generate_ros_messages_without_ros_package_path(p);

// It is important for build scripts to only output files to OUT_DIR.
// This guidance can be ignored for end applications. However, crates published and downloaded with cargo
// will not work if they rely on output files to other folders.
let out_dir = std::env::var_os("OUT_DIR").unwrap();
let dest_path = std::path::Path::new(&out_dir).join("messages.rs");
// Write the generate code to disk
// Note: it will not be nicely formatted at this point which can affect readability when debugging
std::fs::write(dest_path, tokens.to_string())?;
// Optionally rustfmt could be invoked to format the file at this point
// Or https://github.com/dtolnay/prettyplease used on the TokenStream ahead of writing to disk

// If we stopped at this point, our code would still work, but Cargo would not know to rebuild
// our package when a message file changed.
// MAJOR TODO need to get codegen methods to return list of dependent files
// Also probably want to merge down function names to single function with more args / builder

Ok(())
}
22 changes: 22 additions & 0 deletions example_package/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! This file shows how to correctly import files generated by build.rs:
// This macro trick correctly "imports" messages.rs into our crate
// This should only be invoked once in the crate and other locations can access the
// messages via `use`
include!(concat!(env!("OUT_DIR"), "/messages.rs"));

// Example of 'use' pointing to code created by the include! macro
mod submodule {
#[allow(unused_imports)]
use crate::std_msgs::Header;
}

// Our actual "main" here doesn't do much, just shows the generate types
// are here and real.
fn main() {
// Note: within our assets there is a folder named ros1_test_msgs which contains a ros package
// The ros package in its package.xml refers to the name of that package as test_msgs
// The module that is generated will use the name in package.xml and not the directory
let data = test_msgs::Constants::TEST_STR;
println!("Hello World! {data}");
}
5 changes: 5 additions & 0 deletions roslibrust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
//! let output = find_and_generate_ros_messages(vec!["/path/to/noetic/packages".into()]);
//! ```
//! An example of the output based on message and service files in this repo can be found under `roslibrust_test/src/lib.rs`.
//!
//! A full example of incorporating code generation into a project with a build.rs file can be found in example_project.
//! This is the recommended method of incorporating code generation, as this allows automatic detection and re-building
//! when message files are edited which is not possible with the proc_macro approach.
//!
//! ## How Does It Work?
//! When you create a new client via ClientHandle::new() or ClientHandle::new_with_options() a new connection to rosbridge is created.
//! This new connection is literally opening a new Websocket. A specific "stubborn spin" tokio task is created which handles reading
Expand Down

0 comments on commit 23e5bc0

Please sign in to comment.