mirror of
https://github.com/lahfir/agent-desktop.git
synced 2026-08-17 12:37:27 +00:00
ci(ffi): wire integration tests + deterministic header drift path (todos 001, 006)
todo 001: add explicit `cargo test -p agent-desktop-ffi --tests` step so the c_abi_harness and error_lifetime integration tests run on every PR. `cargo test --lib --workspace` skipped them, which meant ABI regressions could land without the harness catching them. todo 006: replace `find target -path '*/agent_desktop.h' | head -1` with a deterministic lookup. build.rs now stamps the absolute path of the just-generated header at target/ffi-header-path.txt, and both CI and scripts/update-ffi-header.sh read that file. Under a warm target/ cache with multiple agent-desktop-ffi-<hash>/ dirs, `find | head -1` picked arbitrarily and could compare a stale header — the stamp always points to the header from the current build.
This commit is contained in:
parent
61172dc8d7
commit
981c108f34
3 changed files with 67 additions and 12 deletions
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
|
|
@ -75,6 +75,13 @@ jobs:
|
|||
- name: Unit tests
|
||||
run: cargo test --lib --workspace
|
||||
|
||||
# The FFI crate ships integration harnesses under crates/ffi/tests/ that
|
||||
# exercise the C-ABI from outside the crate (raw extern "C" decls, enum
|
||||
# fuzzing, out-param zeroing, last-error lifetimes). The workspace --lib
|
||||
# run skips them, so wire them in explicitly so regressions fail PR CI.
|
||||
- name: FFI integration tests
|
||||
run: cargo test -p agent-desktop-ffi --tests
|
||||
|
||||
# Use the ci profile (no LTO, opt-level 1) — fast compile, still checks the binary builds.
|
||||
- name: Build binary (ci profile)
|
||||
run: cargo build --profile ci
|
||||
|
|
@ -95,9 +102,18 @@ jobs:
|
|||
|
||||
- name: FFI header drift check
|
||||
run: |
|
||||
GENERATED=$(find target -path '*/agent-desktop-ffi-*/out/agent_desktop.h' | head -1)
|
||||
if [ -z "$GENERATED" ]; then
|
||||
echo "FAIL: cbindgen did not produce a header in OUT_DIR"
|
||||
# build.rs stamps target/ffi-header-path.txt with the absolute path
|
||||
# to the header produced by the most recent build, so the drift
|
||||
# check always compares against the just-generated artifact rather
|
||||
# than an arbitrary match from `find | head -1` over a warm cache.
|
||||
STAMP=target/ffi-header-path.txt
|
||||
if [ ! -f "$STAMP" ]; then
|
||||
echo "FAIL: $STAMP missing; build.rs did not stamp header path"
|
||||
exit 1
|
||||
fi
|
||||
GENERATED=$(cat "$STAMP")
|
||||
if [ ! -f "$GENERATED" ]; then
|
||||
echo "FAIL: stamped header path does not exist: $GENERATED"
|
||||
exit 1
|
||||
fi
|
||||
if ! diff -u crates/ffi/include/agent_desktop.h "$GENERATED"; then
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=cbindgen.toml");
|
||||
|
|
@ -51,4 +51,34 @@ fn main() {
|
|||
// script auto-copied it, the drift check would self-heal instead of
|
||||
// catching stale headers. Developers update the committed header by
|
||||
// running `scripts/update-ffi-header.sh`.
|
||||
|
||||
// Stamp the absolute path to the just-generated header at a stable,
|
||||
// deterministic location that CI and scripts can read without ever
|
||||
// resorting to `find target | head -1` (which picks arbitrarily when
|
||||
// multiple `agent-desktop-ffi-<hash>/` build dirs are cached). Walking
|
||||
// 4 parents up from OUT_DIR yields the cargo target root:
|
||||
// {target}/{profile}/build/{pkg-hash}/out → {target}
|
||||
if let Some(target_root) = target_root_from_out_dir(Path::new(&out_dir)) {
|
||||
let stamp = target_root.join("ffi-header-path.txt");
|
||||
if let Err(err) = std::fs::write(&stamp, out_path.to_string_lossy().as_bytes()) {
|
||||
println!(
|
||||
"cargo:warning=failed to stamp FFI header path at {:?}: {}",
|
||||
stamp, err
|
||||
);
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"cargo:warning=could not infer cargo target root from OUT_DIR={}; skipping ffi-header-path.txt",
|
||||
out_dir
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn target_root_from_out_dir(out_dir: &Path) -> Option<PathBuf> {
|
||||
// OUT_DIR = .../target/<profile>/build/<pkg-hash>/out
|
||||
let mut current = out_dir;
|
||||
for _ in 0..4 {
|
||||
current = current.parent()?;
|
||||
}
|
||||
Some(current.to_path_buf())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,27 @@
|
|||
set -euo pipefail
|
||||
|
||||
# Regenerate the committed FFI header from source.
|
||||
# Run this after changing any `#[no_mangle] pub extern "C"` signatures
|
||||
# in crates/ffi/src/ and commit the result.
|
||||
#
|
||||
# Run this after changing any `#[no_mangle] pub extern "C"` signatures in
|
||||
# crates/ffi/src/ and commit the result. build.rs stamps the absolute path
|
||||
# to the generated header at target/ffi-header-path.txt so we never have to
|
||||
# guess which of several cached `agent-desktop-ffi-<hash>/` build dirs is
|
||||
# current — `find target | head -1` would pick arbitrarily.
|
||||
|
||||
cargo build -p agent-desktop-ffi 2>/dev/null
|
||||
ROOT=$(git rev-parse --show-toplevel)
|
||||
cd "$ROOT"
|
||||
|
||||
GENERATED=$(find target -path '*/agent-desktop-ffi-*/out/agent_desktop.h' -newer crates/ffi/include/agent_desktop.h | head -1)
|
||||
if [ -z "$GENERATED" ]; then
|
||||
GENERATED=$(find target -path '*/agent-desktop-ffi-*/out/agent_desktop.h' | head -1)
|
||||
cargo build -p agent-desktop-ffi >/dev/null 2>&1
|
||||
|
||||
STAMP=target/ffi-header-path.txt
|
||||
if [ ! -f "$STAMP" ]; then
|
||||
echo "ERROR: $STAMP was not produced by build.rs. Check the build output." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$GENERATED" ]; then
|
||||
echo "ERROR: cbindgen did not produce a header. Check build output." >&2
|
||||
GENERATED=$(cat "$STAMP")
|
||||
if [ ! -f "$GENERATED" ]; then
|
||||
echo "ERROR: stamped header path does not exist: $GENERATED" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue