swift: split MoqFFI bindings from the Moq wrapper + Swift-native redesign#1561
Merged
Conversation
…apper Mirrors the py split (#1551) for Swift, then spends the freedom on a Swift-native wrapper API. Packaging: the Swift integration now ships as two SPM packages. The raw MoqFFI bindings + prebuilt XCFramework mirror to moq-dev/moq-swift-ffi lockstep with the moq-ffi crate on each moq-ffi-v* tag (release-swift-ffi.yml). The ergonomic Moq wrapper versions independently via swift/VERSION and mirrors to moq-dev/moq-swift when that version is new (release-swift.yml, gated on the mirror's tags like release-plz). The wrapper pins MoqFFI at .upToNextMinor, so a crate patch reaches consumers with no wrapper release. Local dev keeps one monolithic swift/Package.swift; the split lives only in the two release templates. Wrapper redesign: every stateful FFI handle is wrapped in a de-prefixed, Sendable class (Client, Session, BroadcastProducer, ...); MoqFFI's Moq-prefixed classes no longer leak. Consumers conform to AsyncSequence, so `for try await x in consumer` works directly. Plain data records/enums are re-exported under de-prefixed names via typealias, so they track the crate automatically. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
git-tag-exists hard-fails when moq-dev/moq-swift-ffi doesn't exist yet (the bootstrap state). Fall back to exists=false so the verify job skips the cross-package resolve instead of failing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Symmetric pairing with release-swift-ffi.yml: -ffi builds the bindings package, -lib publishes the ergonomic library wrapper. Updates the workflow name, its own path filters, the concurrency group, and the references in release-swift-ffi.yml, release-brew.yml, swift/README.md, and CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Applies the Python split (#1551) to Swift, then redesigns the wrapper into a Swift-native API. Two motivations, both the user's:
MoqFFIvia release-plz so consumers float to the latest patch with no wrapper churn, and version the wrapper independently with a registry-gated release workflow.moq-ffican't express.Packaging split
The Swift integration now ships as two SPM packages, each mirrored to its own repo:
MoqFFI(raw bindings + XCFramework)moq-dev/moq-swift-ffi(new)moq-fficratemoq-ffi-v*tag (release-swift-ffi.yml)Moq(ergonomic wrapper)moq-dev/moq-swift(existing)swift/VERSION)release-swift-lib.yml)MoqFFIat.upToNextMinor(from: <crate version>)(substituted fromrs/moq-ffi/Cargo.tomlat package time, the SPM analog of py'smoq-ffi ~= 0.2.x), so amoq-ffipatch reaches consumers with no wrapper release.release-swift-lib.ymlreadsswift/VERSION, checks whether that tag already exists on the mirror (release.sh git-tag-exists, the same registry-gate model release-plz uses), and publishes only when the version is new.swift/Package.swift(both targets, path-based XCFramework) soswift test/ Xcode are unchanged. The split exists only in the released artifacts, assembled fromPackage.swift.template(wrapper) andffi/Package.swift.template(FFI). The FFI module is namedMoqFFIin both layouts, soimport MoqFFIin the wrapper compiles identically either way.Scripts split along the same lines:
package{,-ffi}.sh,verify{,-ffi}.sh,publish{,-ffi}.sh. Bothverifyjobs build a throwaway SPM consumer before any mirror push; the wrapper'sverifyresolves transitively against the published FFI mirror (and skips gracefully if that FFI version isn't mirrored yet, so the bootstrap doesn't deadlock).Wrapper redesign (
swift/Sources/Moq)Sendableclass:Client,Session,Server,Request,OriginProducer/Consumer,Broadcast*,Track*,Group*,Media*,Audio*,Announced*. MoqFFI'sMoq-prefixed classes no longer appear in the public API.AsyncSequence, sofor try await frame in mediaConsumerworks directly (no.framesadapter);TrackConsumeriterates groups in sequence order with agroupsAsArrivedproperty for arrival order.BroadcastProducer() throws), Swift-friendly names (connect(to:),Request.accept()/reject(code:),session.publisher/consumer), and theMoqError.isShutdownhelper.Frame,Catalog,Audio,Container, …), so new catalog/audio fields track the crate automatically; only new FFI methods need a wrapper method (noted in the Cross-Package Sync table).Targets the new dev FFI surface (
announce,Session.publisher()/consumer()).Test plan
just swift check— buildsmoq-ffi, regenerates bindings, builds the monolithic package, runsswift test(3 tests pass, links against real FFI symbols)../swift/scripts/package.shdry-run — wrapper tarball stages,REPLACE_FFI_VERSIONresolves to the crate version,swift package dump-packageparses.actionlintclean on both workflows;shfmt/shellcheckclean on all scripts;remarkclean on changed docs.publish-dry-runon both workflows (runs on this PR).package/verifyfull xcframework path (CI macOS; the assembly logic is lifted verbatim from the existing CI-provenpackage.sh/verify.sh).Migration notes
moq-dev/moq-swift-ffimust be created (empty) before the first FFI release; the publish script first-pushesmainlike the existing one.swift/VERSIONstarts at 0.3.0, opening a new line above the old lockstep0.2.xwrapper tags (as py'smoq-rsdid). The FFI mirror starts at the current crate version. Existing consumers pinnedfrom: "0.2.0"onmoq-dev/moq-swiftwill float up to0.3.0— the new, breaking API. (Targetsdevper Branch Targeting.)Out of scope
Kotlin and Go still consume
moq-ffi-v*lockstep and are untouched; a follow-up can apply the same pattern if this proves out.(Written by Claude)