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