Skip to content

feat(cpp): the moq C++ package, release archives, interop client, and docs - #4187

Merged
kixelated merged 5 commits into
quest/m1/cpp/READMEfrom
quest/m1/cpp/package
Sep 26, 2026
Merged

kixelated merged 5 commits into
quest/m1/cpp/READMEfrom
quest/m1/cpp/package

Conversation

@kixelated

@kixelated kixelated commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

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 the quest/m1/cpp/package.md quest (deleted here).

Approach

  • cpp/moq is the package. CMakeLists.txt runs cargo build -p moq-ffi and uniffi-bindgen-cpp --library libmoq_ffi.a in one custom target (no Corrosion, like libmoq), copies the rendered files only when they change, and compiles them as moq-cpp::moq. Works with add_subdirectory or cmake --install (find_package(moq-cpp) + moq-cpp.pc).
  • <moq/moq.hpp> only renames and wires: moq::Foo aliases for all 85 generated moq::MoqFoo types, moq::expected<T>, moq::Future<T>, moq::Continuation, moq::Executor/moq::Task, moq::inline_executor, moq::set_executor, moq::shutdown() (moq_ffi_shutdown then the dispatcher), moq::log_level, and a co_await awaiter under __cpp_impl_coroutine. Destroying a suspended coroutine cancels its call. just cpp check fails if a generated type lacks its alias.
  • Standard mismatch guard. The generated bindings ship as source and compile in the consumer's build, since uniffi::expected is std::expected on C++23 and tl::expected below. The header references moq_abi_std_expected or moq_abi_tl_expected, and src/moq.cpp defines 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).
  • Tests. cpp/ffi/probe.cpp moved to cpp/moq/test/probe.cpp and now consumes the installed package: built with find_package at 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. release-cpp.yml mirrors libmoq.yml (tag cpp-v*, version in cpp/moq/VERSION, nightly dry-run) and uploads moq-cpp-<version>-<target>.tar.gz/.zip from cpp/moq/build.sh for the same four targets.
  • Interop. test/interop/clients/cpp publishes (Annex-B H.264 + 2.5 ms Opus tone) and subscribes like the Go client, built against the installed package with find_package(moq-cpp). Joins just test interop --all.
  • Docs. doc/lib/cpp/index.md, rows in doc/lib/index.md and the sidebar; the C page now says libmoq is the plain-C ABI and points C++ at the package.

Impact

  • Root CLAUDE.md (maintainer-approved): the rs/moq-ffi Cross-Package Sync row now lists cpp/moq and doc/lib/cpp.
  • New public C++ API: the moq:: names above, operator co_await on uniffi::Future<T, moq::Error>, and the moq_abi_*_expected link symbols.
  • New CMake package moq-cpp (target moq-cpp::moq, plus the moq-cpp::ffi internal) and moq-cpp.pc, installed under lib/cmake/moq-cpp and share/moq-cpp. libmoq keeps moq, so both install into one prefix. The namespace is the package name, the CMake convention (fmt::fmt, Qt6::Core), which keeps it off libmoq's moq:: namespace; the target is moq to match <moq/moq.hpp>.
  • New release tag family cpp-v* and artifact moq-cpp-<version>-<target>.
  • cpp/ffi is gone: its uniffi.toml and README moved into cpp/moq, and rs/moq-ffi/build.sh reads the new path. No wire change, no Rust API change.

Alternatives

  • Package name. Per maintainer decision, moq-cpp rather than the quest's moq, so it never collides with libmoq. moq::cpp was 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.
  • Committed cpp/ffi/generated (quest plan) stays uncommitted, as the generator PR decided; CMake renders it into the build tree.
  • Precompiled bindings library in the archive: rejected, it would pin one expected type for every consumer.

Testing

  • just cpp check (gcc 15, Nix) and CI's C++ (clang) pass; the Linux release archive from build.sh unpacks and builds the probe with find_package.
  • just test interop --all locally: every cpp cell passes except cpp -> js, which failed alongside python -> js and go -> js (browser timeouts under local load); the PR's Interop CI run passed the full matrix.
  • Local just check failed only in moq-uring tests (io_uring setup ENOMEM in this sandbox), unrelated.

Follow-ups

  • Stays a draft until CI confirms MSVC and macOS (nightly.yml cpp-windows and the release-cpp.yml dry-run); neither can run locally.
  • moq::Error has no message: the generator renders Display only for objects. The interop client prints the variant's string field; a generator or moq-ffi fix would benefit every binding.
  • The first cpp-v* release is now a plain Required on the vcpkg and Unreal quests, and the vcpkg port and Conan recipe are renamed moq-cpp to match.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

@kixelated
kixelated marked this pull request as ready for review September 26, 2026 00:17
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T03:55:55.082763Z 2cd2126 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread cpp/moq/include/moq/moq.hpp Outdated
Comment on lines +216 to +218
if (resume) {
resume.resume();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

kixelated and others added 5 commits September 25, 2026 20:32
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

@kixelated
kixelated merged commit 4d10c9d into quest/m1/cpp/README Sep 26, 2026
35 of 36 checks passed
@kixelated
kixelated deleted the quest/m1/cpp/package branch September 26, 2026 05:07
@kixelated

Copy link
Copy Markdown
Collaborator Author

Squashed onto quest/m1/cpp/README as 4d10c9db.

Rebased onto that quest branch and kept the moq-cpp package. The coroutine awaiter now claims the handle under the mutex and resumes before releasing it, so destroying a suspended coroutine never resumes. alert.yml watches the Release C++ workflow. Generated binary types have moq:: aliases. The quest README keeps the error-message quest and drops the finished package quest.

Left for later: a consumer's PRIVATE cxx_std_23 does not flow into the separate moq-cpp static library, and the config comment overstates that. Alias comments stay as the one block contract. The media monotonic flake on this base was rerun once and passed.

(Written by Grok 4.7)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant