feat(cpp): the moq C++ package, release archives, interop client, and docs - #4187
Conversation
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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 489c87c9f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (resume) { | ||
| resume.resume(); | ||
| } |
There was a problem hiding this comment.
Synchronize cancellation before resuming coroutines
When one thread destroys a suspended coroutine while its future completes on the executor, the callback can take state->handle and release the mutex, after which the destructor destroys the coroutine frame before this code calls resume.resume(). That resumes a dangling handle and can cause use-after-free or a crash, even though destroying a suspended coroutine is advertised as safe cancellation. The cancellation and resume paths need a lifetime handshake that prevents frame destruction after the callback claims the handle.
Useful? React with 👍 / 👎.
| namespace moq { | ||
|
|
||
| // Every generated `moq::MoqFoo` is also `moq::Foo`. `just cpp check` fails when one is missing. | ||
| using AnnounceConsumer = MoqAnnounceConsumer; |
There was a problem hiding this comment.
Document each exported C++ alias
This starts a large public alias surface with only a block-level implementation comment, leaving aliases such as AnnounceConsumer without the required one-line description of their role. Add a concise API comment to every exported alias so generated/reference documentation explains the newly introduced public names.
AGENTS.md reference: AGENTS.md:L67-L67
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The aliases are a mechanical drop of the generated Moq prefix (using AnnounceConsumer = MoqAnnounceConsumer), and the block comment above them is the contract: every generated moq::MoqFoo is also moq::Foo, which just cpp check enforces. A one-line comment on each alias would restate that rename and drift from the generator. The hand-written surface (expected, Future, Continuation, Executor, Task, inline_executor, set_executor, shutdown, log_level, and operator co_await) is documented in place.
(Written by Grok 4.7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… docs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the probe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sync row Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The continuation resumes a claimed handle before it drops the mutex, so destroying a suspended coroutine cannot free the frame in between. alert.yml lists the new Release C++ workflow. Alias the binary producers the rebased generator now emits. Co-Authored-By: Grok 4.7 <noreply@x.ai>
489c87c to
2cd2126
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cd21260f3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # The generated bindings compile here, in the consumer's project, so they see the same | ||
| # standard and flags as the code that includes <moq/moq.hpp>. | ||
| add_library(moq-cpp STATIC "@PACKAGE_CMAKE_INSTALL_DATADIR@/moq-cpp/moq.cpp") | ||
| target_compile_features(moq-cpp PUBLIC cxx_std_17) |
There was a problem hiding this comment.
Compile the bindings with the consumer's C++ standard
When a consumer enables C++23 only on its application target, such as with target_compile_features(app PRIVATE cxx_std_23), that requirement does not propagate backward to this separate moq-cpp target. The bindings therefore compile with the compiler default, typically C++17, while the application's inclusion of moq.hpp selects C++23 std::expected; the ABI guard then makes an advertised C++23 configuration fail to link. The current tests hide this because they set CMAKE_CXX_STANDARD globally, so the generated source needs to compile in the actual consumer target or the package must otherwise inherit its selected standard.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The link error is the ABI guard working: moq.cpp and every includer must agree on std::expected versus tl::expected, and a mismatch is supposed to fail the link instead of corrupting memory. A separate static library cannot inherit target_compile_features(... PRIVATE cxx_std_23) from a downstream target; that requirement does not propagate backward in CMake. Consumers set the standard with CMAKE_CXX_STANDARD (what the probe does) or on moq-cpp itself. The config comment overstates this, and teaching the package to follow one consumer target is a follow-up, not a second change on this branch.
(Written by Grok 4.7)
|
Squashed onto Rebased onto that quest branch and kept the Left for later: a consumer's (Written by Grok 4.7) |
Problem
The generator quest landed C++ bindings for moq-ffi, but nothing ships them: no wrapper names, no package a C++ project can
find_package, no release archive, no interop coverage, no docs. Completes thequest/m1/cpp/package.mdquest (deleted here).Approach
cpp/moqis the package.CMakeLists.txtrunscargo build -p moq-ffianduniffi-bindgen-cpp --library libmoq_ffi.ain one custom target (no Corrosion, like libmoq), copies the rendered files only when they change, and compiles them asmoq-cpp::moq. Works withadd_subdirectoryorcmake --install(find_package(moq-cpp)+moq-cpp.pc).<moq/moq.hpp>only renames and wires:moq::Fooaliases for all 85 generatedmoq::MoqFootypes,moq::expected<T>,moq::Future<T>,moq::Continuation,moq::Executor/moq::Task,moq::inline_executor,moq::set_executor,moq::shutdown()(moq_ffi_shutdownthen the dispatcher),moq::log_level, and aco_awaitawaiter under__cpp_impl_coroutine. Destroying a suspended coroutine cancels its call.just cpp checkfails if a generated type lacks its alias.uniffi::expectedisstd::expectedon C++23 andtl::expectedbelow. The header referencesmoq_abi_std_expectedormoq_abi_tl_expected, andsrc/moq.cppdefines the one it was built with, so mixing standards fails to link instead of corrupting memory (verified locally: C++17 bindings + C++23 caller is an undefined reference).cpp/ffi/probe.cppmoved tocpp/moq/test/probe.cppand now consumes the installed package: built withfind_packageat C++17 and C++23 (coroutines, continuation, cancel-by-destroy) and with pkg-config at C++17, all with exceptions and RTTI off. The doc samples compile too (samples.sh cpp).release-cpp.ymlmirrorslibmoq.yml(tagcpp-v*, version incpp/moq/VERSION, nightly dry-run) and uploadsmoq-cpp-<version>-<target>.tar.gz/.zipfromcpp/moq/build.shfor the same four targets.test/interop/clients/cpppublishes (Annex-B H.264 + 2.5 ms Opus tone) and subscribes like the Go client, built against the installed package withfind_package(moq-cpp). Joinsjust test interop --all.doc/lib/cpp/index.md, rows indoc/lib/index.mdand the sidebar; the C page now says libmoq is the plain-C ABI and points C++ at the package.Impact
CLAUDE.md(maintainer-approved): thers/moq-ffiCross-Package Sync row now listscpp/moqanddoc/lib/cpp.moq::names above,operator co_awaitonuniffi::Future<T, moq::Error>, and themoq_abi_*_expectedlink symbols.moq-cpp(targetmoq-cpp::moq, plus themoq-cpp::ffiinternal) andmoq-cpp.pc, installed underlib/cmake/moq-cppandshare/moq-cpp. libmoq keepsmoq, so both install into one prefix. The namespace is the package name, the CMake convention (fmt::fmt,Qt6::Core), which keeps it off libmoq'smoq::namespace; the target ismoqto match<moq/moq.hpp>.cpp-v*and artifactmoq-cpp-<version>-<target>.cpp/ffiis gone: itsuniffi.tomland README moved intocpp/moq, andrs/moq-ffi/build.shreads the new path. No wire change, no Rust API change.Alternatives
moq-cpprather than the quest'smoq, so it never collides with libmoq.moq::cppwas the other target name considered; rejected because it borrows libmoq's namespace.std::chrono,string_view, span views at the edges (quest plan) are not in the wrapper: each needs a method-level wrapper, which the plan also forbids. They belong in the generator if wanted.BUILD_RUST_LIB=OFF(prebuilt lib switch) is not ported: nothing uses libmoq's either, and the installed package already covers prebuilt consumption.cpp/ffi/generated(quest plan) stays uncommitted, as the generator PR decided; CMake renders it into the build tree.expectedtype for every consumer.Testing
just cpp check(gcc 15, Nix) and CI'sC++ (clang)pass; the Linux release archive frombuild.shunpacks and builds the probe withfind_package.just test interop --alllocally: everycppcell passes exceptcpp -> js, which failed alongsidepython -> jsandgo -> js(browser timeouts under local load); the PR's Interop CI run passed the full matrix.just checkfailed only inmoq-uringtests (io_uringsetup ENOMEM in this sandbox), unrelated.Follow-ups
nightly.ymlcpp-windows and therelease-cpp.ymldry-run); neither can run locally.moq::Errorhas no message: the generator rendersDisplayonly for objects. The interop client prints the variant's string field; a generator or moq-ffi fix would benefit every binding.cpp-v*release is now a plainRequiredon the vcpkg and Unreal quests, and the vcpkg port and Conan recipe are renamedmoq-cppto match.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code