Add Swift and Kotlin FFI wrappers with packaging and publishing#1432
Conversation
Adds top-level `swift/` (SPM) and `kt/` (Gradle) packages that wrap
`rs/moq-ffi`'s UniFFI bindings in idiomatic shims: AsyncSequence/Flow
adapters that propagate structured-concurrency cancellation, a
`Moq.connect(url:)` convenience entry point, and small error helpers.
The directory layout mirrors how the future `moq-mux-ffi`/`moq-net-ffi`
split will land (sibling SPM targets + Gradle modules per ffi crate).
Build complexity lives in checked-in scripts (`{swift,kt}/scripts/
{check,package,publish}.sh`) so the workflow YAML only orchestrates
artifact flow. The `moq-ffi-v*` release pipeline gains `package-swift`
(macOS, produces XCFramework.zip + tarball with Package.swift) and
`package-kotlin` (Ubuntu, produces a Maven-local layout with the JVM
JAR + Android AAR + JNI libs at JNA's classpath paths). Both publish
jobs ship today as gated stubs: `vars.PUBLISH_MAVEN` and
`vars.PUBLISH_SPM` flip them on, secret setup is documented in the
package READMEs.
`just ci` now runs `just check-ffi`, which builds moq-ffi for the host,
regenerates the bindings, and runs the wrapper smoke tests. Each leg
skips cleanly if its toolchain is missing (no Swift on Linux, no JDK)
so `just ci` remains runnable on bare-bones machines.
Verified locally: `bash kt/scripts/check.sh` on Linux/x86_64 builds
moq-ffi, populates the JNA-resource layout, and `gradle :moq-jvm:test`
exercises both smoke tests against the real native library; the
end-to-end `kt/scripts/package.sh` flow produces `dev/moq/moq-jvm/
0.0.0-dev/moq-jvm-0.0.0-dev.jar` containing the host slice at
`linux-x86-64/libmoq_ffi.so`.
Replaces the monolithic moq-ffi.yml workflow with three independent workflows (swift.yml, kotlin.yml, python.yml). Each triggers on the shared moq-ffi-v* tag, builds only the targets it needs, and publishes its own artifact. No more moq-ffi GitHub Release: those native-lib tarballs were a stepping stone for the language packages and have no remaining consumers now that the wrappers ship directly. Each language package version echoes moq-ffi's Cargo.toml version via the existing parse-version helper. The build.sh tar/zip step is now opt-in via --archive; the default leaves the staging directories alone so upload-artifact can ingest them without the tar -> upload -> download -> untar round-trip. Same for generate_bindings: bindings stay as plain directories. Kotlin wrapper switches from a two-module (-jvm + -android) layout to a single Kotlin Multiplatform module that publishes `dev.moq:moq` with JVM and Android variants under one coordinate. Source set hierarchy: commonMain -> jvmAndAndroidMain (UniFFI-generated + wrappers) -> jvmMain / androidMain (platform-specific native lib packaging). The Android target is opt-in via -Pandroid.enabled=true so contributors without the Android SDK (or Google maven access) can still build the JVM variant. Verified locally on Linux/x86_64: `bash kt/scripts/check.sh` builds moq-ffi, regenerates bindings, runs :moq:jvmTest (both smoke tests pass). `kt/scripts/package.sh` end-to-end produces the expected KMP publication (dev.moq:moq + dev.moq:moq-jvm) with the host slice at linux-x86-64/libmoq_ffi.so inside the JAR.
Replaces the stub kt/scripts/publish.sh with the com.vanniktech.maven.publish
gradle plugin, which handles the Sonatype Central Portal upload protocol
and GPG signing in a single task (`:moq:publishAndReleaseToMavenCentral`).
The kotlin.yml workflow now merges the package + publish steps into one
job that stages native libs + bindings into the gradle module, runs the
plugin's publish task with credentials supplied as
ORG_GRADLE_PROJECT_{mavenCentralUsername,mavenCentralPassword,signingInMemoryKey,signingInMemoryKeyPassword}
env vars, and uploads the maven-local layout as an inspectable artifact.
The vars.PUBLISH_MAVEN gate is dropped now that the publish path is
real; CI publishes on every moq-ffi-v* tag.
Splits the monolithic root justfile so each language owns its tool invocations. Adds js/justfile, rs/justfile, py/justfile, kt/justfile, swift/justfile, each set to run from its own directory via `set working-directory := '.'` (or `'..'` for rs/, since cargo runs at the workspace root). The root justfile imports them via `mod` so commands like `just kt check`, `just rs ci`, `just py test` work as expected. Cross-cutting recipes (check, ci, fix, test, build, update) now delegate to the per-language modules instead of inlining each language's toolchain. Renames the new release workflows to match the existing release-*.yml convention (release-js, release-py, release-rs already in place): swift.yml -> release-swift.yml, kotlin.yml -> release-kt.yml. python.yml left alone for now pending the moq-ffi/moq-lite consolidation decision. Verified locally: `just --list`, `just --list kt`, `just --list rs`, etc. all show the right recipes; `just kt check` runs from kt/, builds moq-ffi, regenerates bindings, and passes :moq:jvmTest.
…i-vGD5y # Conflicts: # justfile
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - name: Set Cargo.toml version |
There was a problem hiding this comment.
Why is this needed? The moq-ffi version should take priority?
| if: vars.PUBLISH_PYTHON == 'true' | ||
| environment: | ||
| name: pypi | ||
| url: https://pypi.org/p/moq-ffi |
There was a problem hiding this comment.
Can we avoid publishing this moq-ffi package? I'd like to publish just py/moq-net or whatever it's called instead of an intermediate.
Calling `just rs check` from inside rs/justfile would recurse into a nonexistent rs/rs module. The bare `just check` already resolves to the sibling recipe in the same module.
semver/bump/release are pure Rust release-plz operations, so they belong in the per-language module alongside check/ci/fix/test. The root justfile keeps only cross-language orchestration. release-rs.yml is updated to call `just rs release`.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
WalkthroughThis PR consolidates release automation, introduces native language wrappers for Kotlin and Swift, and restructures the build orchestration. The old unified 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (2)
kt/README.md (1)
51-51: ⚡ Quick winAdd language identifiers to fenced code blocks.
Lines 51 and 79 use unlabeled fenced blocks, which commonly triggers markdown lint warnings and hurts editor rendering.
Suggested doc fix
-``` +```text kt/ build.gradle.kts Root config (group, version) settings.gradle.kts include(":moq"), pins AGP version gradle.properties Defaults: version, android.useAndroidX, etc. @@ -``` +```bash gpg --full-generate-key # RSA 4096, 4y expiry gpg --list-secret-keys --keyid-format=long # find <KEYID> gpg --armor --export-secret-keys <KEYID> > signing-key.asc gpg --keyserver keys.openpgp.org --send-keys <KEYID> gpg --keyserver keyserver.ubuntu.com --send-keys <KEYID>Also applies to: 79-79
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kt/README.md` at line 51, Add explicit language identifiers to the unlabeled fenced code blocks in README.md: change the first unlabeled block (the directory listing block around the kt/ example) from ``` to ```text and change the GPG command block later from ``` to ```bash so Markdown linters and editors correctly syntax-highlight those blocks; update the fenced markers around the blocks referenced in the diff accordingly.swift/scripts/check.sh (1)
40-41: ⚡ Quick winRestore
Package.swifton exit after local rewrite.The script overwrites
Package.swiftbut never restores it, which can wipe local edits and leave a dirty tree after smoke checks.Suggested patch
BINDGEN_OUT=$(mktemp -d) -trap 'rm -rf "$BINDGEN_OUT"' EXIT +PKG_FILE="$SWIFT_DIR/Package.swift" +PKG_BACKUP=$(mktemp) +cp "$PKG_FILE" "$PKG_BACKUP" +cleanup() { + cp "$PKG_BACKUP" "$PKG_FILE" + rm -f "$PKG_BACKUP" + rm -rf "$BINDGEN_OUT" +} +trap cleanup EXIT @@ -cat > "$SWIFT_DIR/Package.swift" <<EOF +cat > "$PKG_FILE" <<EOF // swift-tools-version:5.9Also applies to: 63-80
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@swift/scripts/check.sh` around lines 40 - 41, The script creates a temporary dir via BINDGEN_OUT=$(mktemp -d) and registers a trap to clean it on EXIT but never restores an overwritten Package.swift; update the EXIT trap (or add a new trap handler) to save the original Package.swift before the local rewrite and restore it on exit/failure, ensuring any temporary rewrite steps that modify Package.swift (the rewrite section around lines 63-80) revert the file back; reference the BINDGEN_OUT handling and the existing trap to add logic that preserves the original content (e.g., copy Package.swift to a backup like Package.swift.bak before rewriting and move it back in the trap) and remove the backup after successful restore.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/python.yml:
- Around line 11-13: The workflow's concurrency block currently uses "group:
moq-ffi-python-release" with "cancel-in-progress: true", which can abort an
ongoing tag-based release; change "cancel-in-progress: true" to
"cancel-in-progress: false" (or remove the cancel-in-progress key) in the
concurrency block so in-flight tag releases are not canceled; update the
".github/workflows/python.yml" concurrency section that contains the "group:
moq-ffi-python-release" and "cancel-in-progress: true" entries to reflect this
change.
- Line 23: Update the workflow to pin third-party actions to immutable commit
SHAs and disable credential persistence for checkout: replace the tag-pinned
uses of actions/checkout@v6 with the full commit SHA for that release and add
the input persist-credentials: false under the actions/checkout step (the
checkout step symbolized by uses: actions/checkout and its surrounding block),
and likewise replace actions/setup-python@v6 (and any other uses: entries) with
their corresponding full commit SHAs to ensure deterministic, hardened runs.
In @.github/workflows/release-kt.yml:
- Around line 11-13: The workflow's concurrency block uses "group: release-kt"
with "cancel-in-progress: true", which will cancel an in-progress tag release
when a new tag is pushed; remove that risk by deleting the "cancel-in-progress"
key or set it to false so active tag-release runs are not canceled (locate the
concurrency block with the symbols "concurrency:", "group: release-kt" and
"cancel-in-progress" to update).
- Line 23: Replace all tag-pinned GitHub Actions (e.g., the occurrences of
"uses: actions/checkout@v6" and other "uses: ...@v#" entries) with immutable
commit SHAs and add persist-credentials: false to every checkout step;
specifically update each "uses: actions/checkout@..." invocation to use the full
commit SHA and add a with: persist-credentials: false block so the checkout
steps (the occurrences shown as "uses: actions/checkout@v6" and other checkout
lines) do not expose Git credentials.
In @.github/workflows/release-swift.yml:
- Around line 11-13: The concurrency block (concurrency, group,
cancel-in-progress) currently causes a new tag push to cancel an in-progress
release; update the concurrency configuration so releases for different tags
don't cancel each other — either set cancel-in-progress to false or make the
group unique per ref (e.g., include the tag/ref variable like github.ref in the
group name) so each tag release runs independently.
- Line 23: Update every uses: actions/checkout@v6 invocation to include
persist-credentials: false (i.e., set persist-credentials: false under each
checkout step) and replace the tag reference with the immutable commit SHA for
the checkout action; likewise replace tag references for
dtolnay/rust-toolchain@stable, Swatinem/rust-cache@v2,
actions/upload-artifact@v7, and actions/download-artifact@v8 with their exact
commit SHAs so all third‑party actions are pinned to immutable commits. Ensure
you update all occurrences of actions/checkout@v6 in the workflow and that each
checkout step includes the persist-credentials: false key.
In `@kt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Flows.kt`:
- Around line 25-32: The flow builders (e.g., MoqCatalogConsumer.updates()) must
register native cancellation immediately instead of relying on onCompletion;
before entering the loop, get the current coroutine Job via
currentCoroutineContext().job and call job.invokeOnCancellation { cancel() } so
native resources are cancelled promptly if the coroutine is requested to cancel
while a blocking native call (next()/nextGroup()/recvGroup()/readFrame()) is in
progress; add this invokeOnCancellation registration at the top of each flow
builder (before the while(true) loop) and you may keep or remove the existing
onCompletion handler once immediate cancellation is wired up.
In `@kt/README.md`:
- Line 74: Update the stale workflow filename referenced in the docs: replace
the mention of "kotlin.yml" with the current workflow name "release-kt.yml" in
the README sentence that describes the release workflow using
com.vanniktech.maven.publish, so the docs point maintainers to the correct file
for releases; ensure the phrasing still indicates it triggers on moq-ffi-v* tags
and uploads to Sonatype Central.
In `@kt/scripts/package.sh`:
- Around line 57-61: The script uses Bash associative arrays (declare -A) for
ANDROID_ABIS and JVM_LIBS which fail on Bash 3.2 (macOS); add a Bash version
guard at the top (check BASH_VERSINFO[0] >= 4) and emit a clear error message
and exit if too old, or alternatively replace the associative arrays
(ANDROID_ABIS and JVM_LIBS) with POSIX-safe logic (case statements or functions
that map keys to values) so the script works under /usr/bin/env bash on macOS;
update the check near the shebang and touch only the parts that reference
declare -A, ANDROID_ABIS, and JVM_LIBS.
In `@swift/README.md`:
- Line 44: The fenced code block that currently uses only triple backticks
should include a language identifier to satisfy MD040; edit the opening ``` in
the README's example block and append an appropriate language token (e.g., text,
swift, or json) so the block reads like ```text (or ```swift) to ensure the
markdown linter recognizes the code fence language.
- Line 40: Update the README reference that names the workflow file: replace the
outdated mention of "swift.yml" with the actual workflow filename
"release-swift.yml" where the README says the workflow triggers on the
`moq-ffi-v*` tag so the docs correctly point to the Swift release workflow;
ensure the text still mentions the `moq-ffi-v*` tag if intended.
In `@swift/scripts/package.sh`:
- Line 57: Normalize OUTPUT_DIR to an absolute path before any
mkdir/zip/checksum operations: resolve OUTPUT_DIR (e.g. via OUTPUT_DIR="$(cd
"$OUTPUT_DIR" && pwd)" or realpath) immediately before the mkdir -p
"$OUTPUT_DIR" call and use that absolute path for subsequent zip and checksum
steps (the packaging/zip and checksum blocks later in the script). Ensure all
references to OUTPUT_DIR in the zip and checksum commands use the normalized
variable so packaging run from a different CWD cannot read/write the wrong
location.
---
Nitpick comments:
In `@kt/README.md`:
- Line 51: Add explicit language identifiers to the unlabeled fenced code blocks
in README.md: change the first unlabeled block (the directory listing block
around the kt/ example) from ``` to ```text and change the GPG command block
later from ``` to ```bash so Markdown linters and editors correctly
syntax-highlight those blocks; update the fenced markers around the blocks
referenced in the diff accordingly.
In `@swift/scripts/check.sh`:
- Around line 40-41: The script creates a temporary dir via BINDGEN_OUT=$(mktemp
-d) and registers a trap to clean it on EXIT but never restores an overwritten
Package.swift; update the EXIT trap (or add a new trap handler) to save the
original Package.swift before the local rewrite and restore it on exit/failure,
ensuring any temporary rewrite steps that modify Package.swift (the rewrite
section around lines 63-80) revert the file back; reference the BINDGEN_OUT
handling and the existing trap to add logic that preserves the original content
(e.g., copy Package.swift to a backup like Package.swift.bak before rewriting
and move it back in the trap) and remove the backup after successful restore.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 521f1b93-bc5d-4885-9f95-bbdaf03ba079
📒 Files selected for processing (35)
.github/workflows/moq-ffi.yml.github/workflows/python.yml.github/workflows/release-kt.yml.github/workflows/release-swift.ymljs/justfilejustfilekt/.gitignorekt/README.mdkt/build.gradle.ktskt/gradle.propertieskt/justfilekt/moq/android.gradle.ktskt/moq/build.gradle.ktskt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Errors.ktkt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Flows.ktkt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Moq.ktkt/moq/src/jvmAndAndroidTest/kotlin/dev/moq/SmokeTest.ktkt/scripts/check.shkt/scripts/package.shkt/settings.gradle.ktspy/justfilers/justfilers/moq-ffi/build.shswift/.gitignoreswift/Package.swiftswift/README.mdswift/Sources/Moq/AsyncSequences.swiftswift/Sources/Moq/Errors.swiftswift/Sources/Moq/Moq.swiftswift/Sources/Moq/Session.swiftswift/Tests/MoqTests/SmokeTests.swiftswift/justfileswift/scripts/check.shswift/scripts/package.shswift/scripts/publish.sh
💤 Files with no reviewable changes (1)
- .github/workflows/moq-ffi.yml
| version: ${{ steps.parse.outputs.version }} | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v6 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
# Verify non-SHA-pinned actions and checkout credential persistence.
rg -n '^\s*-\s+uses:\s+\S+@v[0-9]+' .github/workflows/python.yml
rg -n -C2 'actions/checkout@' .github/workflows/python.ymlRepository: moq-dev/moq
Length of output: 372
🏁 Script executed:
#!/usr/bin/env bash
# List all uses: entries with line numbers
rg -n '^\s*-\s+uses:\s+\S+' .github/workflows/python.yml
# Show each actions/checkout occurrence with nearby context (for with/persist-credentials)
rg -n -C4 'actions/checkout@' .github/workflows/python.yml
# Check whether persist-credentials is set anywhere in this workflow
rg -n 'persist-credentials' .github/workflows/python.ymlRepository: moq-dev/moq
Length of output: 660
Fix action pinning and disable checkout credential persistence.
In .github/workflows/python.yml, the only uses: entries are actions/checkout@v6 (lines 23 and 55) and actions/setup-python@v6 (line 57). actions/checkout is not configured with with: persist-credentials: false, and none of the uses: entries are pinned to a commit SHA (they’re all tag-pinned like @v6). Pin actions/checkout to the full commit SHA and add:
Suggested hardening pattern
- - uses: actions/checkout@v6
+ - uses: actions/checkout@<full_commit_sha>
+ with:
+ persist-credentials: falseAlso apply SHA pinning to every other uses: entry (e.g., actions/setup-python).
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 23-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/python.yml at line 23, Update the workflow to pin
third-party actions to immutable commit SHAs and disable credential persistence
for checkout: replace the tag-pinned uses of actions/checkout@v6 with the full
commit SHA for that release and add the input persist-credentials: false under
the actions/checkout step (the checkout step symbolized by uses:
actions/checkout and its surrounding block), and likewise replace
actions/setup-python@v6 (and any other uses: entries) with their corresponding
full commit SHAs to ensure deterministic, hardened runs.
| version: ${{ steps.parse.outputs.version }} | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v6 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
rg -n '^\s*-\s+uses:\s+\S+@v[0-9]+' .github/workflows/release-kt.yml
rg -n -C2 'actions/checkout@' .github/workflows/release-kt.ymlRepository: moq-dev/moq
Length of output: 686
🏁 Script executed:
cat -n .github/workflows/release-kt.yml | head -200Repository: moq-dev/moq
Length of output: 6746
🏁 Script executed:
rg -n 'persist-credentials' .github/workflows/release-kt.ymlRepository: moq-dev/moq
Length of output: 37
Pin all workflow actions to immutable SHAs and add persist-credentials: false to checkout steps.
This release workflow uses mutable tag-pinned actions (e.g., @v6, @v4, @v7, @v8, @v3, @v2) which can be retagged or compromised. Additionally, all actions/checkout steps (lines 23, 56, 100, 136) lack persist-credentials: false, unnecessarily exposing Git credentials in the release environment.
Replace all @v# references with immutable commit SHAs, and add persist-credentials: false to each checkout step:
- uses: actions/checkout@<full-commit-sha>
with:
persist-credentials: falseAffected tag-pinned actions: lines 23, 56, 64, 88, 100, 106, 118, 136, 138, 144, 146, 149, 164, 187
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 23-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release-kt.yml at line 23, Replace all tag-pinned GitHub
Actions (e.g., the occurrences of "uses: actions/checkout@v6" and other "uses:
...@v#" entries) with immutable commit SHAs and add persist-credentials: false
to every checkout step; specifically update each "uses: actions/checkout@..."
invocation to use the full commit SHA and add a with: persist-credentials: false
block so the checkout steps (the occurrences shown as "uses:
actions/checkout@v6" and other checkout lines) do not expose Git credentials.
Drop the intermediate moq-ffi PyPI package and the separate python.yml workflow. py/moq-net is now a maturin uniffi project that builds rs/moq-ffi as a cdylib + uniffi bindings, exposed inside the package as the private moq_net._uniffi submodule. Users `pip install moq-net` and import only from moq_net. release-py.yml absorbs python.yml's cross-platform wheel matrix and triggers on moq-ffi-v* tags (the same tag release-plz pushes when it bumps rs/moq-ffi/Cargo.toml). Maturin's dynamic version reads from that Cargo.toml, so the wheel version tracks the Rust crate without a separate Python bump and without release-plz needing to touch pyproject.toml. Other cleanup: - Drop rs/moq-ffi/pyproject.toml (no longer published as moq-ffi) - Drop py/common/release.sh and the py just release recipe (CI-driven now) - Move pytest deps from py/moq-net dev group to root dev group so `just py check/test` only needs one uv sync - Add /py/moq-net/moq_net/_uniffi/ to .gitignore (maturin develop output) - Add exclude rules so ruff and pyright skip the generated uniffi file - Update CLAUDE.md to document the new layout The Rust moq-ffi crate stays on crates.io unchanged. Swift/Kotlin wrappers are untouched (uniffi scaffolding namespace remains "moq" so MoqFFI.xcframework and uniffi.moq.* keep working).
There was a problem hiding this comment.
🧹 Nitpick comments (2)
.github/workflows/release-py.yml (2)
60-60: 💤 Low valueConsider adding
persist-credentials: falsefor hardening.Same as the parse-version job, this checkout doesn't need to persist credentials.
🛡️ Suggested hardening
- uses: actions/checkout@v6 + with: + persist-credentials: false🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release-py.yml at line 60, The checkout step currently uses actions/checkout@v6 without restricting GitHub token exposure; update the checkout step that invokes actions/checkout@v6 to include the input persist-credentials: false so credentials are not persisted to the checked-out repository (same change as the parse-version job). Locate the step that references actions/checkout@v6 and add the persist-credentials: false key under that step's uses to harden the workflow.
28-28: 💤 Low valueConsider adding
persist-credentials: falsefor hardening.The checkout action defaults to persisting Git credentials. Since this job only parses the tag and doesn't push, disabling credential persistence reduces attack surface.
🛡️ Suggested hardening
- uses: actions/checkout@v6 + with: + persist-credentials: false🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release-py.yml at line 28, The checkout step currently uses "uses: actions/checkout@v6" which leaves Git credentials persisted; update the checkout action invocation to include the option persist-credentials: false so credentials are not retained (i.e., modify the step referencing uses: actions/checkout@v6 to add persist-credentials: false under its with: block) to harden the workflow for a read-only tag-parsing job.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.github/workflows/release-py.yml:
- Line 60: The checkout step currently uses actions/checkout@v6 without
restricting GitHub token exposure; update the checkout step that invokes
actions/checkout@v6 to include the input persist-credentials: false so
credentials are not persisted to the checked-out repository (same change as the
parse-version job). Locate the step that references actions/checkout@v6 and add
the persist-credentials: false key under that step's uses to harden the
workflow.
- Line 28: The checkout step currently uses "uses: actions/checkout@v6" which
leaves Git credentials persisted; update the checkout action invocation to
include the option persist-credentials: false so credentials are not retained
(i.e., modify the step referencing uses: actions/checkout@v6 to add
persist-credentials: false under its with: block) to harden the workflow for a
read-only tag-parsing job.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a90e9dc8-6015-4896-88e6-6c7a20c97ca1
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
.github/workflows/release-py.yml.gitignoreCLAUDE.mdpy/common/release.shpy/justfilepy/moq-net/moq_net/client.pypy/moq-net/moq_net/origin.pypy/moq-net/moq_net/publish.pypy/moq-net/moq_net/subscribe.pypy/moq-net/moq_net/types.pypy/moq-net/pyproject.tomlpyproject.tomlrs/moq-ffi/pyproject.toml
💤 Files with no reviewable changes (2)
- py/common/release.sh
- rs/moq-ffi/pyproject.toml
✅ Files skipped from review due to trivial changes (3)
- py/moq-net/moq_net/subscribe.py
- CLAUDE.md
- py/moq-net/moq_net/publish.py
The parse-version and build jobs only need source code, not git push access. Setting persist-credentials: false avoids leaving the GITHUB_TOKEN in .git/config where downstream steps (or the maturin-action container) could read it.
- release-kt.yml, release-swift.yml: switch concurrency cancel-in-progress from true to false so a second moq-ffi-v* tag doesn't abort an in-flight Maven Central / GitHub Release upload mid-publish. - kt/README.md: rename stale kotlin.yml reference to release-kt.yml. - swift/README.md: rename stale swift.yml reference to release-swift.yml; add text language to the Layout fenced block for MD040. - swift/scripts/package.sh: normalize OUTPUT_DIR to an absolute path after mkdir, since the zip and 'swift package compute-checksum' steps later run from cd'd subshells and would otherwise resolve a relative path against the wrong cwd. - kt/scripts/package.sh: fail fast with a clear error on Bash < 4 (macOS default /usr/bin/bash is 3.2), since the script uses associative arrays via declare -A.
This PR adds ergonomic Swift and Kotlin wrappers around the moq-ffi UniFFI bindings, along with complete packaging and publishing infrastructure for both platforms.
Summary
Introduces two new language bindings for Media over QUIC:
Both wrappers provide idiomatic async/await APIs that integrate with their respective platform's concurrency models (Swift's
AsyncSequenceand Kotlin'sFlow), with automatic cleanup and cancellation propagation.Key Changes
Swift wrapper (
swift/):Sources/Moq/- Ergonomic shim layer withAsyncSequenceextensions for streaming APIs (updates,frames,groups,announcements)Package.swift- SPM manifest with binary XCFramework target (URL/checksum rewritten at release time)scripts/check.sh- Local development script that builds moq-ffi, generates bindings, and runsswift testscripts/package.sh- Release packaging script that creates XCFramework from per-target static libs and produces tarballscripts/publish.sh- Stub for publishing to moq-dev/moq-swift mirror repoKotlin wrapper (
kt/):common/src/dev/moq/- Shared wrapper sources withFlowextensions for streaming APIsmoq-jvm/- Kotlin/JVM library for desktop/server with JNA resource layoutmoq-android/- Android library with JNI .so files per ABIscripts/check.sh- Local development script for host targetscripts/package.sh- Release packaging that stages native libs and generates Gradle artifactsscripts/publish.sh- Maven Central publishing via Sonatype APICI/CD integration (
.github/workflows/moq-ffi.yml):x86_64-apple-iostarget for iOS simulator x86_64 supportpackage-swiftjob that assembles XCFramework and stages Swift packagepackage-kotlinjob that stages Maven-local artifacts for both JVM and Androidpublish-mavenjob (gated byPUBLISH_MAVENvariable) for Maven Central publishingpublish-spmjob (gated byPUBLISH_SPMvariable) for Swift Package mirror publishingreleasejob to depend on packaging jobsDevelopment tooling (
justfile):check-ffitarget that runs Swift and Kotlin smoke tests (skips gracefully if toolchain unavailable)citargetImplementation Details
onCompletionhooks (Swift) andonCompletioncallbacks (Kotlin) to forward coroutine/scope cancellation to nativecancel()calls, enabling structured concurrencyBoth wrappers follow platform conventions: Swift uses async/await with
AsyncSequence, Kotlin uses coroutines withFlow, and both provide ergonomic entry points (Moq.connect()) that hide the underlying UniFFI complexity.https://claude.ai/code/session_01NDx1JbhDV6TQApDw6kynWC