Automate onde-swift release: CI updates Package.swift and onde.swift
- CI now rewrites onde-swift/Package.swift with the release url: and checksum, copies the generated onde.swift, commits, tags, and pushes to onde-swift. - Manual edits to Package.swift are no longer needed after a release. - build-swift-xcframework.yml: add steps for version validation, artifact upload, onde-swift checkout, Package.swift rewrite, onde.swift copy, and tagging. - Update AGENTS.md to document the automated release workflow and required secrets.
Seto Elkahfi committed
Apr 3, 2026 at 09:18 UTC
c52b21216eece667d1c9ab7dfdbfcf79713f5e27
2 files changed
+148
-14
.agents/AGENTS.md
+26
-11
@@ -269,10 +269,14 @@ $BINDGEN generate target/aarch64-apple-ios/release/libonde.a \
269
270
### Updating `onde-swift` after a new release
271
272
-1. Run the build script and collect `OndeFramework.xcframework.zip` + checksum.
273
-2. Create a GitHub Release on the `onde` repo and attach the zip.
274
-3. In `onde-swift/Package.swift`, update the `.binaryTarget` to use the new `url:` and `checksum:`.
275
-4. Commit, tag (`0.x.y`), and push `onde-swift`.
272
+This is fully automated by CI. Publishing a GitHub Release on `onde` triggers the workflow, which:
273
+
274
+1. Builds the XCFramework and attaches it to the release.
275
+2. Rewrites `onde-swift/Package.swift` with the release `url:` + `checksum:`.
276
+3. Copies the freshly generated `onde.swift` into `onde-swift/Sources/Onde/`.
277
+4. Commits, tags, and pushes `onde-swift` — Swift Package Index picks it up automatically.
278
+
279
+Manual intervention is only needed if `ONDE_SWIFT_PAT` has expired or the `onde-swift` push fails.
280
281
---
282
@@ -289,7 +293,7 @@ onde-swift/
293
294
`Package.swift` has two targets:
295
292
-- **`OndeFramework`** — `.binaryTarget` pointing at `OndeFramework.xcframework` (local path during dev, remote URL+checksum for distribution).
296
+- **`OndeFramework`** — `.binaryTarget` using `url:` + `checksum:` for distribution. For local development, swap to the `path:` form documented in the `Package.swift` header comment and run `build-swift-xcframework.sh` first.
297
- **`Onde`** — `.target` depending on `OndeFramework`, used as the public import name in Swift.
298
299
### Swift API Quick Reference
@@ -463,7 +467,7 @@ cargo check
467
### Swift / SDK
468
469
- **Never manually edit `onde-swift/Sources/Onde/onde.swift`** — it is generated by `uniffi-bindgen`. Regenerate by running the build script.
466
-- **`Package.swift` binary target path** must point to the correct `.xcframework` location: local path during development, remote URL+checksum for published releases.
470
+- **`Package.swift` binary target** uses `url:` + `checksum:` in the committed form. For local development, swap to `path:` (instructions are in the file header). Never commit the `path:` form — CI overwrites it on every release.
471
- **iOS/tvOS sandbox:** always call `setupInferenceEnvironment()` at app launch before any `OndeChatEngine` call.
472
473
---
@@ -478,17 +482,28 @@ Steps:
482
1. Install stable Rust (`1.92.0`) with iOS/macOS targets.
483
2. Install nightly Rust with `rust-src` component and tvOS targets.
484
3. Run `build-swift-xcframework.sh`.
481
-4. Upload artifacts: zip, checksum, version, generated `onde.swift`.
482
-5. On release: attach zip and checksum to the GitHub Release.
485
+4. Read `version.txt` and `OndeFramework.checksum.txt` into step outputs (`steps.meta.outputs.version` / `steps.meta.outputs.checksum`).
486
+5. Upload CI artifacts: zip, checksum, version, generated `onde.swift`.
487
+6. On release: attach zip and checksum to the GitHub Release.
488
+7. On release: check out `ondeinference/onde-swift` using `ONDE_SWIFT_PAT`.
489
+8. On release: rewrite the `.binaryTarget` in `onde-swift/Package.swift` with the release download URL and computed checksum.
490
+9. On release: copy the freshly generated `onde.swift` into `onde-swift/Sources/Onde/`.
491
+10. On release: commit both files, tag the commit with the version, and push to `ondeinference/onde-swift`.
492
+
493
+#### Required secret
494
+
495
+`ONDE_SWIFT_PAT` — a GitHub Personal Access Token with **`contents: write`** scope on the `ondeinference/onde-swift` repository. Create it at <https://github.com/settings/tokens> and add it as a repository secret on the `onde` repo.
496
497
### Release Process (end-to-end)
498
499
1. Bump version in `onde/Cargo.toml`.
487
-2. Tag and push `onde` repo → CI builds and attaches `OndeFramework.xcframework.zip`.
488
-3. In `onde-swift`, update `Package.swift` with new `url:` and `checksum:`.
489
-4. Commit, tag, push `onde-swift` → Swift Package Index indexes it automatically.
500
+2. Create and publish a GitHub Release on the `onde` repo (tag = version, e.g. `0.2.0`).
501
+3. CI builds the XCFramework, attaches `OndeFramework.xcframework.zip` + checksum to the release, then **automatically** rewrites `onde-swift/Package.swift` with the correct `url:` + `checksum:`, commits, tags, and pushes `onde-swift`.
502
+4. Swift Package Index picks up the new `onde-swift` tag automatically — no manual step needed.
503
5. `cargo publish` to crates.io.
504
505
+> **Note:** Steps 3–4 are fully automated. Manual edits to `onde-swift/Package.swift` are no longer needed after a release. Only touch it when doing local development (use the `path:` form documented in the file header).
506
+
507
---
508
509
## Distribution Registry Summary
.github/workflows/build-swift-xcframework.yml
+122
-3
@@ -11,8 +11,9 @@ permissions:
11
jobs:
12
build-swift-xcframework:
13
runs-on: macos-15
14
+
15
steps:
15
- - name: Checkout
16
+ - name: Checkout onde
17
uses: actions/checkout@v6
18
19
- name: Install stable Rust toolchain
@@ -21,11 +22,13 @@ jobs:
22
toolchain: 1.92.0
23
targets: aarch64-apple-ios,aarch64-apple-ios-sim,aarch64-apple-darwin
24
25
+ # tvOS targets are tier-3 — no prebuilt std libs available via rustup.
26
+ # The build script uses `cargo +nightly -Z build-std` which compiles std
27
+ # from source using the rust-src component. Do NOT list tvOS in targets:.
28
- name: Install nightly Rust toolchain for tvOS
29
uses: dtolnay/rust-toolchain@nightly
30
with:
31
components: rust-src
28
- targets: aarch64-apple-tvos,aarch64-apple-tvos-sim
32
33
- name: Verify Xcode
34
run: xcodebuild -version
@@ -33,8 +36,36 @@ jobs:
36
- name: Build Swift XCFramework
37
run: .github/scripts/build-swift-xcframework.sh
38
39
+ # ── Read build outputs ────────────────────────────────────────────────
40
+
41
+ - name: Read version and checksum
42
+ id: meta
43
+ run: |
44
+ VERSION=$(cat dist/swift/version.txt)
45
+ CHECKSUM=$(cat dist/swift/OndeFramework.checksum.txt)
46
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
47
+ echo "checksum=$CHECKSUM" >> "$GITHUB_OUTPUT"
48
+
49
+ # ── Validate release tag matches Cargo.toml version ─────────────────
50
+
51
+ - name: Validate version consistency
52
+ if: github.event_name == 'release'
53
+ env:
54
+ CARGO_VERSION: ${{ steps.meta.outputs.version }}
55
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
56
+ run: |
57
+ # Strip optional 'v' prefix from the release tag for comparison.
58
+ NORMALIZED_TAG="${RELEASE_TAG#v}"
59
+ if [ "$NORMALIZED_TAG" != "$CARGO_VERSION" ]; then
60
+ echo "::error::Release tag '$RELEASE_TAG' (normalized: '$NORMALIZED_TAG') does not match Cargo.toml version '$CARGO_VERSION'. Update Cargo.toml or re-tag the release."
61
+ exit 1
62
+ fi
63
+ echo "Version check passed: $CARGO_VERSION"
64
+
65
+ # ── Upload CI artifacts (every run) ──────────────────────────────────
66
+
67
- name: Upload XCFramework artifacts
37
- uses: actions/upload-artifact@v6
68
+ uses: actions/upload-artifact@v7
69
with:
70
name: onde-swift-xcframework
71
path: |
@@ -44,6 +75,8 @@ jobs:
75
dist/swift/Package/Sources/Onde/onde.swift
76
if-no-files-found: error
77
78
+ # ── Attach assets to the GitHub Release (release trigger only) ───────
79
+
80
- name: Upload release assets
81
if: github.event_name == 'release'
82
uses: softprops/action-gh-release@v2
@@ -51,3 +84,89 @@ jobs:
84
files: |
85
dist/swift/OndeFramework.xcframework.zip
86
dist/swift/OndeFramework.checksum.txt
87
+
88
+ # ── Update onde-swift and push a matching tag (release trigger only) ─
89
+
90
+ - name: Checkout onde-swift
91
+ if: github.event_name == 'release'
92
+ uses: actions/checkout@v6
93
+ with:
94
+ repository: ondeinference/onde-swift
95
+ # A PAT with `contents: write` on the onde-swift repo.
96
+ # Create it at https://github.com/settings/tokens and store as
97
+ # a repository secret named ONDE_SWIFT_PAT on the onde repo.
98
+ token: ${{ secrets.ONDE_SWIFT_PAT }}
99
+ path: onde-swift
100
+
101
+ - name: Rewrite Package.swift with release URL and checksum
102
+ if: github.event_name == 'release'
103
+ env:
104
+ CHECKSUM: ${{ steps.meta.outputs.checksum }}
105
+ # Use the actual GitHub release tag for the download URL — this is
106
+ # what GitHub uses to construct the asset download path.
107
+ RELEASE_URL: https://github.com/${{ github.repository }}/releases/download/${{ github.event.release.tag_name }}/OndeFramework.xcframework.zip
108
+ run: |
109
+ python3 - <<'PY'
110
+ import os, re, pathlib
111
+
112
+ pkg = pathlib.Path("onde-swift/Package.swift")
113
+ src = pkg.read_text()
114
+
115
+ url = os.environ["RELEASE_URL"]
116
+ checksum = os.environ["CHECKSUM"].strip()
117
+
118
+ # Replace any existing binaryTarget block (path: or url: form).
119
+ # The pattern matches from .binaryTarget( up to the closing ),
120
+ # anchored on the OndeFramework name so we don't touch other targets.
121
+ pattern = re.compile(
122
+ r'\.binaryTarget\(\s*name:\s*"OndeFramework".*?\)',
123
+ re.DOTALL,
124
+ )
125
+
126
+ replacement = (
127
+ '.binaryTarget(\n'
128
+ ' name: "OndeFramework",\n'
129
+ f' url: "{url}",\n'
130
+ f' checksum: "{checksum}"\n'
131
+ ' )'
132
+ )
133
+
134
+ updated = pattern.sub(replacement, src)
135
+ if updated == src:
136
+ raise RuntimeError("binaryTarget pattern did not match — check Package.swift format")
137
+
138
+ pkg.write_text(updated)
139
+ print("Package.swift updated:")
140
+ print(updated)
141
+ PY
142
+
143
+ - name: Copy generated onde.swift into onde-swift
144
+ if: github.event_name == 'release'
145
+ run: |
146
+ cp dist/swift/Package/Sources/Onde/onde.swift \
147
+ onde-swift/Sources/Onde/onde.swift
148
+
149
+ - name: Commit and tag onde-swift
150
+ if: github.event_name == 'release'
151
+ env:
152
+ VERSION: ${{ steps.meta.outputs.version }}
153
+ run: |
154
+ cd onde-swift
155
+ git config user.name "github-actions[bot]"
156
+ git config user.email "github-actions[bot]@users.noreply.github.com"
157
+ git add Package.swift Sources/Onde/onde.swift
158
+
159
+ # Idempotent: skip commit if nothing changed (safe re-run).
160
+ if git diff --cached --quiet; then
161
+ echo "No changes to commit — Package.swift and onde.swift are already up to date."
162
+ else
163
+ git commit -m "Release $VERSION"
164
+ fi
165
+
166
+ # Create or move the version tag to this commit.
167
+ # --force handles re-runs where the tag already exists.
168
+ git tag -f "$VERSION"
169
+
170
+ # Push to the default branch explicitly (checkout is detached HEAD).
171
+ git push origin HEAD:main
172
+ git push origin "$VERSION" --force