Skip to content
Open
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
58 changes: 58 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,64 @@ jobs:
source /tmp/nix-dev-env.sh
nu scripts/test-pure-rust.nu check-python-stubs

ffi-header-fresh:
name: Generated FFI Header (ubuntu-latest)
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
large-packages: true
tool-cache: true
android: true
dotnet: true
haskell: true
docker-images: true
swap-storage: false

- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
extra-substituters = https://hiroz.cachix.org
extra-trusted-public-keys = hiroz.cachix.org-1:wKJuqEckTG0DL3Df7Ly9OVsg5S5TGBHtvlPGs+vlqrY=

- name: Setup Cachix
uses: cachix/cachix-action@v15
with:
name: hiroz
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
skipPush: ${{ github.event_name == 'pull_request' }}

- name: Install Nushell
run: |
nix profile install nixpkgs#nushell
chmod +x scripts/test-pure-rust.nu

- name: Setup nix environment
run: |
nix print-dev-env '.#pureRust-ci' --accept-flake-config > /tmp/nix-dev-env.sh
echo "export CI=true" >> /tmp/nix-dev-env.sh

- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: ubuntu-latest-ffi-header

# Its own job rather than a step in `go-tests`: that job has no Nix
# environment, and cbindgen has to come from the same dev shell the check
# runs in so a version skew cannot silently change the generated output.
- name: Generated FFI header is up to date
run: |
source /tmp/nix-dev-env.sh
nu scripts/test-pure-rust.nu check-ffi-header

go-tests:
name: Go Tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
Expand Down
56 changes: 52 additions & 4 deletions crates/hiroz-go/hiroz/hiroz_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,29 @@
#define hiroz_ZENOH_EVENT_ID_MAX 11

/**
* Default depth for KEEP_LAST when SYSTEM_DEFAULT (depth=0) is used
* This matches ROS 2 and rmw_zenoh_cpp behavior
* Default depth for `KeepLast` when SYSTEM_DEFAULT (depth=0) is used.
* Matches rclcpp's default of 10. Note this is distinct from
* [`KEEP_ALL_CACHE_DEPTH`] below, which is the rmw_zenoh-aligned cap
* applied to `KeepAll` when mapped to zenoh-ext's `cache.max_samples`.
*/
#define hiroz_DEFAULT_HISTORY_DEPTH 10

/**
* Cache/history depth used when a TransientLocal endpoint carries
* `QosHistory::KeepAll` (no inherent depth) or `KeepLast(0)`.
*
* Matches rmw_zenoh_cpp's `RMW_ZENOH_DEFAULT_HISTORY_DEPTH = 42`
* (`rmw_zenoh_cpp/src/detail/qos.cpp:27`), which is the value
* rmw_zenoh's `best_available_qos` substitutes for a zero-valued
* `qos.depth` before passing it to
* `AdvancedPublisherOptions::CacheOptions::max_samples`.
*
* This is intentionally a *finite* cap that mirrors rmw_zenoh's
* pragmatic behaviour rather than the DDS KEEP_ALL spec's "keep
* everything" semantics.
*/
#define hiroz_KEEP_ALL_CACHE_DEPTH 42

/**
* Default shared memory pool size (10 MB).
*/
Expand All @@ -29,6 +47,31 @@
*/
#define hiroz_DEFAULT_SHM_THRESHOLD 512

/**
* Constant for ListParameters: recursively get parameters with unlimited depth.
*/
#define hiroz_DEPTH_RECURSIVE 0

#define hiroz_NOT_SET 0

#define hiroz_BOOL 1

#define hiroz_INTEGER 2

#define hiroz_DOUBLE 3

#define hiroz_STRING 4

#define hiroz_BYTE_ARRAY 5

#define hiroz_BOOL_ARRAY 6

#define hiroz_INTEGER_ARRAY 7

#define hiroz_DOUBLE_ARRAY 8

#define hiroz_STRING_ARRAY 9

/**
* Opaque action client handle for FFI
*/
Expand Down Expand Up @@ -185,6 +228,11 @@ typedef struct hiroz_context_config_t {
* Whether to enable logging
*/
bool enable_logging;
/**
* Default namespace inherited by nodes created from this context (nullable).
* Added after all pre-existing fields to preserve ABI compatibility.
*/
Comment thread
YuanYuYuan marked this conversation as resolved.
const char *namespace_;
} hiroz_context_config_t;

/**
Expand Down Expand Up @@ -630,8 +678,8 @@ int32_t hiroz_service_client_destroy(struct hiroz_service_client_t *client);

/**
* Wait until at least one matching service server is visible in the graph,
* or `timeout_ms` elapses. Returns 0 if ready, -10 (ServiceTimeout) on
* timeout, -1 (NullPointer) if `client_handle` is null.
* or `timeout_ms` elapses. Returns `Success` (0) if ready, `ServiceTimeout`
* (-10) on timeout, `NullPointer` (-1) if `client` is null.
*/
int32_t hiroz_service_client_wait_for_service(struct hiroz_service_client_t *client_handle,
uint64_t timeout_ms);
Expand Down
6 changes: 6 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@
# Ensure python is available since we unwrapped the ROS env
python3
go # Go toolchain (latest stable)
# Generates crates/hiroz-go/hiroz/hiroz_ffi.h from crates/hiroz/src/ffi/
# (crates/hiroz/build.rs) under `--features ffi`. Sits beside `go`
# because that header is what cgo compiles against. Absent from every
# shell until now, so CI never regenerated it and the committed copy
# silently drifted out of sync with the Rust structs -- see #270.
rust-cbindgen
Comment thread
YuanYuYuan marked this conversation as resolved.
];

# Development tools
Expand Down
58 changes: 58 additions & 0 deletions scripts/test-pure-rust.nu
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,62 @@ def check-python-stubs [] {
print $"Generated Python stubs match the message assets."
}

def check-ffi-header [] {
log-step "Generated FFI header is up to date"
# `crates/hiroz-go/hiroz/hiroz_ffi.h` is generated by cbindgen from
# `crates/hiroz/src/ffi/` (see crates/hiroz/build.rs) and committed, because
# cgo needs it at build time. Nothing checked that the committed copy still
# matched the generator, and it had drifted by 52 lines -- including a field
# `namespace_` added to `hiroz_context_config_t`. cgo sizes its struct from
# this header, so Go allocated 80 bytes where Rust reads 88: an
# out-of-bounds read of `cfg.namespace` on every advanced-config
# `ContextBuilder.Build()`. See #270.
#
# cbindgen is checked explicitly rather than left to build.rs, which
# degrades its absence to a non-fatal `cargo:warning=` (build.rs:55-61).
# Without this the build would succeed, write nothing, and the check would
# report on whatever was already in the tree.
if (which cbindgen | is-empty) {
error make {
msg: ("cbindgen not found, so the FFI header cannot be regenerated and this check "
+ "would pass without verifying anything. Install it (`cargo install cbindgen`, "
+ "or enter the `.#pureRust` dev shell) and re-run.")
}
}

# Printed because cbindgen's output is version-dependent -- 0.29.4 emits a
# `CDR_HEADER_LE` constant that 0.29.3 does not -- so a stale-header report
# can also mean "generated by a different cbindgen". The flake pins the
# authoritative one; a developer whose PATH prefers another (e.g. an older
# `cargo install` copy in ~/.cargo/bin) sees which was used here rather
# than an unexplained diff.
print $"cbindgen: (^cbindgen --version | str trim)"

# Deleted first for the same reason the Python-stub check empties its
# directory: a build that generates nothing then leaves the committed copy
# in place and passes. With the file absent, that failure mode surfaces as
# a ` D` entry instead.
#
# If the build below fails, the header is left deleted in the working tree;
# `git checkout -- <header>` restores it.
let header = "crates/hiroz-go/hiroz/hiroz_ffi.h"
rm -f $header
# Forces build.rs to rerun even when cargo considers the crate fresh --
# otherwise a warm target dir makes this a no-op that passes.
touch crates/hiroz/build.rs
run-cmd "cargo build -j4 -p hiroz --features ffi"

let drift = (^git status --porcelain -- $header | complete)
if ($drift.stdout | str trim | is-not-empty) {
print ($drift.stdout | str trim)
print (^git diff -- $header | complete | get stdout)
error make {
msg: $"generated FFI header is stale -- run `cargo build -p hiroz --features ffi` and commit ($header)"
}
}
print $"Generated FFI header matches the Rust FFI surface."
}

def check-examples [] {
log-step "Check all examples (cargo check --examples)"
run-cmd "cargo check --examples"
Expand Down Expand Up @@ -151,6 +207,7 @@ def get-test-map [] {
check-examples: { check-examples }
check-rustdoc-links: { check-rustdoc-links }
check-python-stubs: { check-python-stubs }
check-ffi-header: { check-ffi-header }
check-distro-features: { check-distro-features }
clippy-hiroz-py: { clippy-hiroz-py }
clippy-tests: { clippy-tests }
Expand All @@ -167,6 +224,7 @@ def get-test-pipeline [] {
"check-examples"
"check-rustdoc-links"
"check-python-stubs"
"check-ffi-header"
"check-distro-features"
"clippy-hiroz-py"
"clippy-tests"
Expand Down
Loading