roslibrust_transforms 0.26.0

A tf2-like transform library for roslibrust providing TransformListener functionality.
Documentation
//! Test to ensure that the pre-generated messages.rs file is up-to-date.
//!
//! This test generates the messages.rs content using roslibrust_codegen and compares
//! it with the current file. If they differ, it overwrites the file and fails the test.
//!
//! To regenerate messages.rs, simply run:
//! ```
//! cargo test -p roslibrust_transforms message_gen
//! ```
//!
//! The first run will update the file, and subsequent runs will pass.

use std::borrow::Cow;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};

const ROS1_GEOMETRY_MSGS: &str = concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../assets/ros1_common_interfaces/common_msgs/geometry_msgs"
);
const ROS1_STD_MSGS: &str = concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../assets/ros1_common_interfaces/std_msgs"
);

const ROS2_GEOMETRY_MSGS: &str = concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../assets/ros2_common_interfaces/geometry_msgs"
);
const ROS2_STD_MSGS: &str = concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../assets/ros2_common_interfaces/std_msgs"
);
const ROS2_BUILTIN_INTERFACES: &str = concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../assets/ros2_required_msgs/rcl_interfaces/builtin_interfaces"
);

fn format_rust_source(source: &str) -> Cow<'_, str> {
    if let Ok(mut process) = Command::new("rustfmt")
        .arg("--emit=stdout")
        .arg("--edition=2021")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()
    {
        {
            let stdin = process.stdin.as_mut().unwrap();
            stdin.write_all(source.as_bytes()).unwrap()
        }
        if let Ok(output) = process.wait_with_output() {
            if output.status.success() {
                return std::str::from_utf8(&output.stdout[..])
                    .unwrap()
                    .to_owned()
                    .into();
            }
        }
    }
    Cow::Borrowed(source)
}

fn generate_messages() -> String {
    let ros1_paths: Vec<PathBuf> = vec![ROS1_GEOMETRY_MSGS.into(), ROS1_STD_MSGS.into()];
    let ros2_paths: Vec<PathBuf> = vec![
        ROS2_GEOMETRY_MSGS.into(),
        ROS2_STD_MSGS.into(),
        ROS2_BUILTIN_INTERFACES.into(),
    ];

    // Generate ROS1 messages
    let (ros1_source, _) =
        roslibrust::codegen::find_and_generate_ros_messages_without_ros_package_path(ros1_paths)
            .expect("Failed to generate ROS1 messages");
    let ros1_source = format_rust_source(&ros1_source.to_string()).to_string();

    // Generate ROS2 messages
    let (ros2_source, _) =
        roslibrust::codegen::find_and_generate_ros_messages_without_ros_package_path(ros2_paths)
            .expect("Failed to generate ROS2 messages");
    let ros2_source = format_rust_source(&ros2_source.to_string()).to_string();

    // Build the combined messages.rs file
    format!(
        r#"//! Message type definitions for tf2_msgs.
//!
//! This module contains pre-generated message types for tf2_msgs/TFMessage.
//! The code is generated by running: `cargo test -p roslibrust_transforms message_gen`
//!
//! Both ROS1 and ROS2 message formats are provided:
//! - `ros1` - ROS1 message format (Header has `seq` field, uses `secs`/`nsecs`)
//! - `ros2` - ROS2 message format (Header has no `seq` field, uses `sec`/`nanosec`)

/// ROS1 message types for tf2 functionality.
/// Header uses `seq` field and `stamp` has `secs`/`nsecs` fields.
pub mod ros1 {{
{ros1_source}

    /// TFMessage is the message type published on /tf and /tf_static topics.
    /// It contains an array of geometry_msgs/TransformStamped messages.
    #[allow(non_snake_case)]
    #[derive(
        ::serde::Deserialize,
        ::serde::Serialize,
        ::roslibrust::codegen::SmartDefault,
        Debug,
        Clone,
        PartialEq,
    )]
    pub struct TFMessage {{
        pub transforms: Vec<geometry_msgs::TransformStamped>,
    }}

    impl ::roslibrust_common::RosMessageType for TFMessage {{
        const DESCRIPTION: ::roslibrust_common::MessageDescriptor =
            ::roslibrust_common::MessageDescriptor::new::<Self>(
                "tf2_msgs/TFMessage",
                "94810edda583a504dfda3829e70d7eec",
                "geometry_msgs/TransformStamped[] transforms",
                "tf2_msgs::msg::dds_::TFMessage_",
                &[
                    0x84, 0x64, 0x1e, 0xf6, 0x1a, 0x5a, 0xa7, 0xf4, 0x9e, 0x8f, 0xc3, 0xca,
                    0x2f, 0x3c, 0xf7, 0x3e, 0x24, 0x3a, 0x50, 0x7c, 0xc6, 0x5a, 0x6b, 0x93,
                    0xe4, 0x1a, 0xa3, 0x57, 0x83, 0xd1, 0x94, 0x71,
                ],
            );
    }}
}}

/// ROS2 message types for tf2 functionality.
/// Header has no `seq` field and `stamp` has `sec`/`nanosec` fields.
pub mod ros2 {{
{ros2_source}

    /// TFMessage is the message type published on /tf and /tf_static topics.
    /// It contains an array of geometry_msgs/TransformStamped messages.
    #[allow(non_snake_case)]
    #[derive(
        ::serde::Deserialize,
        ::serde::Serialize,
        ::roslibrust::codegen::SmartDefault,
        Debug,
        Clone,
        PartialEq,
    )]
    pub struct TFMessage {{
        pub transforms: Vec<geometry_msgs::TransformStamped>,
    }}

    impl ::roslibrust_common::RosMessageType for TFMessage {{
        const DESCRIPTION: ::roslibrust_common::MessageDescriptor =
            ::roslibrust_common::MessageDescriptor::new::<Self>(
                "tf2_msgs/TFMessage",
                "94810edda583a504dfda3829e70d7eec",
                "geometry_msgs/TransformStamped[] transforms",
                "tf2_msgs::msg::dds_::TFMessage_",
                &[
                    0x84, 0x64, 0x1e, 0xf6, 0x1a, 0x5a, 0xa7, 0xf4, 0x9e, 0x8f, 0xc3, 0xca,
                    0x2f, 0x3c, 0xf7, 0x3e, 0x24, 0x3a, 0x50, 0x7c, 0xc6, 0x5a, 0x6b, 0x93,
                    0xe4, 0x1a, 0xa3, 0x57, 0x83, 0xd1, 0x94, 0x71,
                ],
            );
    }}
}}
"#
    )
}

/// Confirms that codegen has been run and changes committed.
/// If the file is out of date, it will be overwritten and the test will fail.
#[test]
fn messages_rs_is_up_to_date() {
    let generated = generate_messages();
    let generated = format_rust_source(&generated).to_string();

    let messages_path = concat!(env!("CARGO_MANIFEST_DIR"), "/src/messages.rs");
    let current_contents =
        std::fs::read_to_string(messages_path).expect("Failed to read current messages.rs");

    if generated.trim() != current_contents.trim() {
        // Overwrite the file with the new content
        std::fs::write(messages_path, &generated).expect("Failed to write messages.rs");

        // Create a diff for display
        let diff = diffy::create_patch(&current_contents, &generated);
        println!("messages.rs was out of date and has been updated.");
        println!("Diff:\n{}", diff);

        panic!(
            "messages.rs was out of date and has been regenerated. \
             Please commit the updated file and re-run the tests."
        );
    }
}