1 name: Release SDK Swift
2
3 on:
4 push:
5 tags:
6 - "[0-9]+.[0-9]+.[0-9]+"
7 workflow_dispatch:
8 inputs:
9 publish:
10 description: "Publish Swift release assets from the selected ref"
11 type: boolean
12 default: false
13 release_tag:
14 description: "Existing semver tag to publish on manual runs, for example 1.1.1"
15 type: string
16 required: false
17
18 permissions:
19 contents: write
20
21 jobs:
22 build-and-release:
23 runs-on: macos-26
24
25 env:
26 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
27 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
28 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
29 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
30 HF_TOKEN: ${{ secrets.HF_TOKEN }}
31
32 steps:
33 - name: Checkout onde
34 uses: actions/checkout@v6
35
36 - name: Install stable Rust toolchain
37 uses: dtolnay/rust-toolchain@stable
38 with:
39 toolchain: 1.92.0
40 targets: aarch64-apple-ios,aarch64-apple-ios-sim,aarch64-apple-darwin
41
42 # tvOS targets are tier-3 — no prebuilt std libs available via rustup.
43 # The build script uses `cargo +nightly -Z build-std` which compiles std
44 # from source using the rust-src component. Do NOT list tvOS in targets:.
45 - name: Install nightly Rust toolchain for tvOS
46 uses: dtolnay/rust-toolchain@nightly
47 with:
48 components: rust-src
49
50 - name: Verify Xcode
51 run: xcodebuild -version
52
53 - name: Build Swift XCFramework
54 run: .github/scripts/build-swift-xcframework.sh
55
56 # ── Read build outputs ────────────────────────────────────────────────
57
58 - name: Read version and checksum
59 id: meta
60 run: |
61 VERSION=$(cat dist/swift/version.txt)
62 CHECKSUM=$(cat dist/swift/OndeFramework.checksum.txt)
63 echo "version=$VERSION" >> "$GITHUB_OUTPUT"
64 echo "checksum=$CHECKSUM" >> "$GITHUB_OUTPUT"
65
66 - name: Resolve release tag
67 id: release
68 env:
69 INPUT_RELEASE_TAG: ${{ inputs.release_tag }}
70 run: |
71 if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
72 RELEASE_TAG="${GITHUB_REF_NAME}"
73 else
74 RELEASE_TAG="${INPUT_RELEASE_TAG}"
75 fi
76
77 if [ -n "${RELEASE_TAG}" ]; then
78 echo "release_tag=${RELEASE_TAG}" >> "$GITHUB_OUTPUT"
79 fi
80
81 - name: Validate release tag matches Cargo.toml version
82 if: startsWith(github.ref, 'refs/tags/') || inputs.publish
83 env:
84 CARGO_VERSION: ${{ steps.meta.outputs.version }}
85 RELEASE_TAG: ${{ steps.release.outputs.release_tag }}
86 run: |
87 if [ -z "$RELEASE_TAG" ]; then
88 echo "::error::release_tag is required when publish=true on workflow_dispatch runs."
89 exit 1
90 fi
91
92 if [ "$RELEASE_TAG" != "$CARGO_VERSION" ]; then
93 echo "::error::Release tag '$RELEASE_TAG' does not match Cargo.toml version '$CARGO_VERSION'. Update Cargo.toml or publish the matching tag."
94 exit 1
95 fi
96 echo "Version check passed: $CARGO_VERSION"
97
98 # ── Upload CI artifacts (manual runs only) ───────────────────────────
99
100 - name: Upload XCFramework artifacts
101 if: github.event_name == 'workflow_dispatch'
102 uses: actions/upload-artifact@v7
103 with:
104 name: onde-swift-xcframework
105 path: |
106 dist/swift/OndeFramework.xcframework.zip
107 dist/swift/OndeFramework.checksum.txt
108 dist/swift/version.txt
109 dist/swift/Package/Sources/Onde/onde.swift
110 if-no-files-found: error
111 retention-days: 14
112
113 # ── Create GitHub Release and attach assets ──────────────────────────
114
115 - name: Create GitHub Release
116 if: startsWith(github.ref, 'refs/tags/') || inputs.publish
117 uses: softprops/action-gh-release@v3
118 with:
119 tag_name: ${{ steps.release.outputs.release_tag }}
120 name: ${{ steps.release.outputs.release_tag }}
121 generate_release_notes: true
122 files: |
123 dist/swift/OndeFramework.xcframework.zip
124 dist/swift/OndeFramework.checksum.txt
125
126 # ── Update onde-swift ────────────────────────────────────────────────
127
128 - name: Checkout onde-swift
129 if: startsWith(github.ref, 'refs/tags/') || inputs.publish
130 uses: actions/checkout@v6
131 with:
132 repository: ondeinference/onde-swift
133 token: ${{ secrets.ONDE_SWIFT_PAT }}
134 path: onde-swift
135
136 - name: Rewrite Package.swift release constants
137 if: startsWith(github.ref, 'refs/tags/') || inputs.publish
138 env:
139 CHECKSUM: ${{ steps.meta.outputs.checksum }}
140 RELEASE_URL: https://github.com/${{ github.repository }}/releases/download/${{ steps.release.outputs.release_tag }}/OndeFramework.xcframework.zip
141 run: |
142 python3 - <<'PY'
143 import os, re, pathlib
144
145 pkg = pathlib.Path("onde-swift/Package.swift")
146 src = pkg.read_text()
147
148 url = os.environ["RELEASE_URL"]
149 checksum = os.environ["CHECKSUM"].strip()
150
151 url_pattern = re.compile(
152 r'(let releaseFrameworkURL =\s*\n\s*")([^"]+)(")',
153 re.MULTILINE,
154 )
155 checksum_pattern = re.compile(
156 r'(let releaseFrameworkChecksum =\s*\n\s*")([^"]+)(")',
157 re.MULTILINE,
158 )
159
160 def replace_constant(pattern, value, text, label):
161 updated_text, count = pattern.subn(
162 lambda match: f"{match.group(1)}{value}{match.group(3)}",
163 text,
164 count=1,
165 )
166 if count != 1:
167 raise RuntimeError(f"{label} pattern did not match - check Package.swift format")
168 return updated_text
169
170 updated = replace_constant(url_pattern, url, src, "releaseFrameworkURL")
171 updated = replace_constant(
172 checksum_pattern,
173 checksum,
174 updated,
175 "releaseFrameworkChecksum",
176 )
177
178 pkg.write_text(updated)
179 print("Package.swift updated:")
180 print(updated)
181 PY
182
183 - name: Copy generated onde.swift into onde-swift
184 if: startsWith(github.ref, 'refs/tags/') || inputs.publish
185 run: |
186 cp dist/swift/Package/Sources/Onde/onde.swift \
187 onde-swift/Sources/Onde/onde.swift
188
189 - name: Commit and tag onde-swift
190 if: startsWith(github.ref, 'refs/tags/') || inputs.publish
191 env:
192 VERSION: ${{ steps.release.outputs.release_tag }}
193 run: |
194 cd onde-swift
195 git config user.name "github-actions[bot]"
196 git config user.email "github-actions[bot]@users.noreply.github.com"
197 git add Package.swift Sources/Onde/onde.swift
198
199 if git diff --cached --quiet; then
200 echo "No changes to commit — Package.swift and onde.swift are already up to date."
201 else
202 git commit -m "Release $VERSION"
203 fi
204
205 # Push the commit to main first so the tag points to the latest HEAD
206 git push origin HEAD:main
207
208 # Create an annotated tag only if it doesn't already exist on the remote.
209 # Never force-overwrite — SPM caches the resolved commit SHA per tag,
210 # so moving a tag silently breaks every consumer's Package.resolved.
211 if git ls-remote --tags origin "refs/tags/$VERSION" | grep -q "refs/tags/$VERSION"; then
212 echo "::warning::Tag $VERSION already exists on onde-swift — skipping tag push."
213 echo "To re-release this version, delete the remote tag first:"
214 echo " git push origin :refs/tags/$VERSION"
215 else
216 git tag -a "$VERSION" -m "Release $VERSION"
217 git push origin "refs/tags/$VERSION"
218 fi