Skip to content

fix: added generated bindings for ROS Humble, Jazzy, Kilted and Rolling #512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/generate-bindings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Generate bindings

on:
schedule:
# Run the CI at 02:22 UTC every Tuesday
# We pick an arbitrary time outside of most of the world's work hours
# to minimize the likelihood of running alongside a heavy workload.
- cron: '22 2 * * 2'
Comment on lines +5 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says this will run daily, but this looks like it's actually weekly. Is that intentional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I've submitted #515 to fix that

workflow_dispatch:

env:
CARGO_TERM_COLOR: always

jobs:
build:
strategy:
matrix:
ros_distribution:
- humble
- jazzy
- kilted
- rolling
include:
# Humble Hawksbill (May 2022 - May 2027)
- docker_image: rostooling/setup-ros-docker:ubuntu-jammy-ros-humble-ros-base-latest
ros_distribution: humble
ros_version: 2
# Jazzy Jalisco (May 2024 - May 2029)
- docker_image: rostooling/setup-ros-docker:ubuntu-noble-ros-jazzy-ros-base-latest
ros_distribution: jazzy
ros_version: 2
# Kilted Kaiju (May 2025 - Dec 2026)
- docker_image: rostooling/setup-ros-docker:ubuntu-noble-ros-kilted-ros-base-latest
ros_distribution: kilted
ros_version: 2
# Rolling Ridley (June 2020 - Present)
- docker_image: rostooling/setup-ros-docker:ubuntu-noble-ros-rolling-ros-base-latest
ros_distribution: rolling
ros_version: 2
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
container:
image: ${{ matrix.docker_image }}
steps:
- uses: actions/checkout@v4

- name: Setup ROS environment
uses: ros-tooling/[email protected]
with:
required-ros-distributions: ${{ matrix.ros_distribution }}
use-ros2-testing: ${{ matrix.ros_distribution == 'rolling' }}

- name: Setup Rust
uses: dtolnay/[email protected]
with:
components: clippy, rustfmt

- name: Install cargo binstall
run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash

- name: Install bindgen
run: cargo binstall -y bindgen

- name: Generate bindings
run: |
cd rclrs/src
../generate_bindings.py rcl_wrapper.h ${{ matrix.ros_distribution }} .

- name: Submit PR
run: |
if git diff --exit-code; then
exit 0
fi
git config --global user.email "[email protected]"
git config --global user.name "GitHub Action"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git remote update origin
CREATE_PR=0
if !git checkout update-bindings-${{ matrix.ros_distribution }}; then
CREATE_PR=1
git checkout -b update-bindings-${{ matrix.ros_distribution }}
fi
git add rclrs/src/rcl_bindings_generated_${{ matrix.ros_distribution }}.rs
git commit -m "Regenerate bindings for ${{ matrix.ros_distribution }}"
git push -u origin update-bindings-${{ matrix.ros_distribution }}
if [ $CREATE_PR -eq 1 ]; then
gh pr create --base main --head update-bindings-${{ matrix.ros_distribution }} --title "Regenerate bindings for ${{ matrix.ros_distribution }}" --body "This PR regenerates the bindings for ${{ matrix.ros_distribution }}."
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77 changes: 0 additions & 77 deletions rclrs/build.rs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a fair amount being removed from the build script. Is this functionality no longer needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the code that generates the bindings from the build.rs script and moved it to the CI, that way there's only one source of truth for the bindings and they'll be the same for everyone who uses rclrs. If anyone wants to generate the bindings locally, they can use the generate_bindings.py script.

Original file line number Diff line number Diff line change
Expand Up @@ -38,78 +38,8 @@ fn main() {
println!("cargo:rustc-check-cfg=cfg(ros_distro, values(\"humble\", \"jazzy\", \"rolling\"))");
println!("cargo:rustc-cfg=ros_distro=\"{ros_distro}\"");

let mut builder = bindgen::Builder::default()
.header(BINDGEN_WRAPPER)
.derive_copy(false)
.allowlist_type("rcl_.*")
.allowlist_type("rmw_.*")
.allowlist_type("rcutils_.*")
.allowlist_type("rosidl_.*")
.allowlist_function("rcl_.*")
.allowlist_function("rmw_.*")
.allowlist_function("rcutils_.*")
.allowlist_function("rosidl_.*")
.allowlist_var("rcl_.*")
.allowlist_var("rmw_.*")
.allowlist_var("rcutils_.*")
.allowlist_var("rosidl_.*")
.layout_tests(false)
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
})
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));

// Invalidate the built crate whenever this script or the wrapper changes
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={BINDGEN_WRAPPER}");

// #############
// # ALGORITHM #
// #############
//
// For each prefix in ${AMENT_PREFIX_PATH}:
// Search through ament index at ${prefix}/share/ament_index/resource_index/packages/ to find packages to include
// The include root will be located at either:
// - ${prefix}/include/ (old style)
// - ${prefix}/include/${package_name} (new style)
// - ${prefix}/include/CycloneDDS (special case, match for this)
// End of loop
// Compiled libraries are always at ${prefix}/lib
//
// See REP 122 for more details: https://www.ros.org/reps/rep-0122.html#filesystem-layout

let ament_prefix_paths = get_env_var_or_abort(AMENT_PREFIX_PATH);
for ament_prefix_path in ament_prefix_paths.split(':').map(Path::new) {
// Locate the ament index
let ament_index = ament_prefix_path.join("share/ament_index/resource_index/packages");
if !ament_index.is_dir() {
continue;
}

// Old-style include directory
let include_dir = ament_prefix_path.join("include");

// Including the old-style packages
builder = builder.clang_arg(format!("-isystem{}", include_dir.display()));

// Search for and include new-style-converted package paths
for dir_entry in read_dir(&ament_index).unwrap().filter_map(|p| p.ok()) {
let package = dir_entry.file_name();
let package_include_dir = include_dir.join(&package);

if package_include_dir.is_dir() {
let new_style_include_dir = package_include_dir.join(&package);

// CycloneDDS is a special case - it needs to be included as if it were a new-style path, but
// doesn't actually have a secondary folder within it called "CycloneDDS"
// TODO(jhdcs): if this changes in future, remove this check
if package == "CycloneDDS" || new_style_include_dir.is_dir() {
builder =
builder.clang_arg(format!("-isystem{}", package_include_dir.display()));
}
}
}

// Link the native libraries
let library_path = ament_prefix_path.join("lib");
println!("cargo:rustc-link-search=native={}", library_path.display());
Expand All @@ -120,11 +50,4 @@ fn main() {
println!("cargo:rustc-link-lib=dylib=rcutils");
println!("cargo:rustc-link-lib=dylib=rmw");
println!("cargo:rustc-link-lib=dylib=rmw_implementation");

let bindings = builder.generate().expect("Unable to generate bindings");

let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("rcl_bindings_generated.rs"))
.expect("Couldn't write bindings!");
}
61 changes: 61 additions & 0 deletions rclrs/generate_bindings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
import ament_index_python
import os
import subprocess
import sys

def main():
if len(sys.argv) != 4:
print("Usage: generate_bindings.py <header_file> <ros_distribution> <output_directory>")
sys.exit(1)

header_file = sys.argv[1]
ros_distribution = sys.argv[2]
output_directory = sys.argv[3]
bindgen_command = []
bindgen_command.append('bindgen')
bindgen_command.append(header_file)
bindgen_command.append('-o')
bindgen_command.append(f'{output_directory}/rcl_bindings_generated_{ros_distribution}.rs')
bindgen_command.append('--rust-edition')
bindgen_command.append('2021')
bindgen_command.append('--rust-target')
bindgen_command.append('1.75')
bindgen_command.append('--no-derive-copy')
bindgen_command.append('--allowlist-type')
bindgen_command.append('rcl_.*')
bindgen_command.append('--allowlist-type')
bindgen_command.append('rmw_.*')
bindgen_command.append('--allowlist-type')
bindgen_command.append('rcutils_.*')
bindgen_command.append('--allowlist-type')
bindgen_command.append('rosidl_.*')
bindgen_command.append('--allowlist-function')
bindgen_command.append('rcl_.*')
bindgen_command.append('--allowlist-function')
bindgen_command.append('rmw_.*')
bindgen_command.append('--allowlist-function')
bindgen_command.append('rcutils_.*')
bindgen_command.append('--allowlist-function')
bindgen_command.append('rosidl_.*')
bindgen_command.append('--allowlist-var')
bindgen_command.append('rcl_.*')
bindgen_command.append('--allowlist-var')
bindgen_command.append('rmw_.*')
bindgen_command.append('--allowlist-var')
bindgen_command.append('rcutils_.*')
bindgen_command.append('--allowlist-var')
bindgen_command.append('rosidl_.*')
bindgen_command.append('--no-layout-tests')
bindgen_command.append('--default-enum-style')
bindgen_command.append('rust')
bindgen_command.append('--')
for package, prefix in ament_index_python.get_packages_with_prefixes().items():
package_include_dir = os.path.join(prefix, 'include', package)
if os.path.isdir(package_include_dir):
bindgen_command.append('-isystem')
bindgen_command.append(package_include_dir)
subprocess.run(bindgen_command)

if __name__ == "__main__":
main()
Loading
Loading