feat(cpp): generate moq-ffi C++ bindings with a pinned uniffi-bindgen-cpp - #4100
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-cpp Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Pushed a quest-only commit to this branch as part of the spawn-quests follow-up planning (see #4105), so the follow-up rides with the PR that surfaced it. No code changes. (Written by Claude Opus 5.5) |
|
Merged into (Written by Claude Opus 5.5) |
Problem
The cpp questline generates its C++ surface from moq-ffi instead of writing it by hand. That only works if a C++ generator can read a uniffi 0.32 library, gives cancellable futures, and reports errors without exceptions, which Unreal and other
-fno-exceptionsbuilds need. None of the upstream generators does all three. This quest is the go/no-go gate for the line.Verdict: go. The generator holds.
Approach
Generator: kixelated/uniffi-bindgen-cpp, tag
v0.11.0-kixelated.1+v0.32.2, forked from LiveKit'slivekit/uniffi-0.31-async(their PR #1).0.32 port: bumped
uniffi_bindgento 0.32.2, loaded cargo metadata throughCrateConfigSupplier's own constructor, renderedBox<T>asT, and refusedHashSet<T>. It was small: two compile errors. All 25 upstream C++ tests pass on gcc, clang, and MSVC, and under valgrind.error_style = "expected"(off by default, so exceptions stay the upstream default):uniffi::expected<T, E>, and async calls returnuniffi::Future<T, E>, whoseget()andthen()deliver the same.uniffi::expectedisstd::expectedwhen__cpp_lib_expectedis defined, otherwise a bundledtl::expectedv1.1.0 (CC0).message.throw,try, orcatch. A new fork test builds it with-fno-exceptions -fno-rtti(/EHs-c- /GR-on MSVC) and runs it on all three compilers plus valgrind. It covers typed errors, async errors, continuations, cancellation, and dispatcher shutdown.Fixed three existing LiveKit bugs that the fork's CI exposed:
rust_future_completeleaked its result. This happens when the dispatcher rejects its continuation, including after shutdown.#include <string>, which MSVC needs.Each fix has a regression test that fails without it.
This repo:
cpp/ffi/: holdsuniffi.toml(expected style), a CMake file, andprobe.cpp. The probe compiles with exceptions and RTTI off and runs over a real QUIC session. It:kBind) and async (kCancelled)The generated sources are gitignored, like Go's.
just cpp checkbuilds moq-ffi, regenerates the bindings, and builds and runs the probe. It runs injust check(gcc in the dev shell),cpp.yml(clang, on PRs touchingcpp/ffiorrs/moq-ffi), and a newcpp-windowsnightly job (MSVC).flake.nixpins the fork next touniffi-bindgen-go. The comment lists every place that names the tag.rs/moq-ffi/build.shemitsbindings/cppwhen the generator is on PATH.cpp/ffi/README.mddocuments the cancellation contract. NativeTask::runholds anAbortOnDrop, so cancelling or destroying a future aborts the work at its next await point. The handle survives, and the next call works. Every moq-ffi write is synchronous, so a cancel cannot tear a write. That correction retires the quest's "finish a group before dropping its write future" worry.Deleted
quest/m1/cpp/generator.mdand every reference to it. The line README's plan now describes what the generator actually emits.Impact
moq::Moq*, returninguniffi::expected<T, moq::MoqError>anduniffi::Future<T, moq::MoqError>. Themoq::renames and themoq::expectedalias belong to the Package quest.uniffi-bindgen-cpp.MOQ_STRICTnow requires it (plus cmake, c++, jq) for diffs touchingcpp/ffiorrs/moq-ffi.cpp.yml(clang, PR), andcpp-windowsinnightly.yml.Decisions (maintainer's call; I picked the recommended option)
uniffi-0.31-async), as the quest planned. Alternative: PR Bump golang.org/x/net from 0.0.0-20220421235706-1d1ef9303861 to 0.7.0 in /cert #5 (uniffi-0.31-cpp17-async, onlivekit/dev). It is newer, but it runs a worker thread per in-flight future and has nothen()or async callback interfaces, which the OBS migration needs.v0.11.0-kixelated.1+v0.32.2follows the Dart fork's pre-release convention, so it can never collide with an upstream tag. Alternative: plainv0.11.0+v0.32.2, like the Go fork.Ehas no cancelled variant. Alternative: deliverexpected<T, std::variant<E, Cancelled>>, which is noisier on every call site.std::exception_ptr, and redesigning it waits for a consumer.cpp.yml.Verification
MOQ_STRICT=1 just checkin the dev shell:just cpp checkpasses with the flake-pinned generator. So do the lints,just obs check, andnix flake check. The one failure isjust obs compile:target/include/moq.h missing. libmoq's build script derives the header path fromOUT_DIR, which my local build cache relocates. This PR does not touch libmoq or the plugin.just test: 4852 passed.expected-testwith exceptions off and the valgrind runs.Follow-ups
error_style = "expected", and the two leak fixes plus the<string>include.cpp-windowsnightly is the first MSVC build of the moq probe andjust cpp check's Windows paths.moq_ffi_shutdownfrom C++. The Package quest'smoq::layer is the natural place to expose it.doc/lib/cppis not created. The Package quest owns the docs page, andflake.nixnames the places to bump when it lands.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code