Skip to content

Commit 9062172

Browse files
ci: move Swift xcframework build/upload from tag-CI to PR-CI (draft-release flow)
The Swift xcframework was the only artifact this repo built twice — once locally during prepare-release to bake the SHA into Package.swift, once on tag-CI to upload the zip — with the two builds expected to produce byte-identical bytes. Cross-host determinism kept failing (Xcode patch versions, macOS SDK skew), forcing manual zip-clobber rituals post-publish. Single build, single source of truth: - release_swift.yml: new workflow that fires on push to release/v* branches (the convention prepare-release sets up). Builds xcframework + verifies layout + iOS sim test, computes SHA-256, commits the SHA into Package.swift on the PR branch (a bot commit marked [skip swift-release] so it doesn't re-fire), uploads the zip to a draft GitHub release v<version>. - release.yml's create-release: replaced actions/create-release@v1 (deprecated) with gh CLI that promotes the existing draft release to published — the swift zip baked in PR-CI is already attached. Falls through to fresh-create if no draft exists (manual tag without PR). - release.yml's build-and-publish-libs: switched actions/upload-release-asset to `gh release upload --clobber` since the new create-release no longer produces an upload_url output. - release.yml's build-and-publish-swift: deleted. The swift zip ships with the promoted draft. - prepare-release: stripped the xcframework build + checksum bake. No Xcode required locally anymore — versions-only edit, PR-CI fills the SHA when you push.
1 parent 216839a commit 9062172

3 files changed

Lines changed: 135 additions & 88 deletions

File tree

.github/workflows/release.yml

Lines changed: 43 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
# The way this works is the following:
1+
# Release pipeline. Fires on `v*` tag push, after the release PR has merged.
22
#
3-
# The create-release job runs purely to initialize the GitHub release itself
4-
# and to output upload_url for the following job.
5-
#
6-
# The build-release job runs only once create-release is finished. It gets the
7-
# release upload URL from create-release job outputs, then builds the release
8-
# executables for each supported platform and attaches them as release assets
9-
# to the previously created release.
10-
#
11-
# The key here is that we create the release only once.
12-
#
13-
# Reference:
14-
# https://eugene-babichenko.github.io/blog/2020/05/09/github-actions-cross-platform-auto-releases/
15-
# https://github.com/crate-ci/cargo-release/blob/91549dbf9db9915ba5f121890ad0816c7d851679/.github/workflows/post-release.yml
3+
# The Swift xcframework zip is built upstream by release_swift.yml on the
4+
# `release/v*` PR branch and uploaded to a draft GitHub release. This
5+
# workflow's create-release job promotes that draft to published; the other
6+
# jobs then `gh release upload` the rest of the assets to the same release.
7+
# Single Swift build (in PR CI), no cross-host determinism requirement.
168

179
name: release
1810
on:
@@ -25,33 +17,50 @@ env:
2517
PACKAGE_NAME: libiroh
2618
IROH_FORCE_STAGING_RELAYS: "1"
2719

28-
jobs:
20+
# `gh release upload` + `gh release edit --draft=false` both need write
21+
# access to the release.
22+
permissions:
23+
contents: write
24+
25+
# Tag-push publish: PR-CI (release_swift.yml) created a draft release with
26+
# the Swift xcframework zip already attached and the SHA already baked into
27+
# Package.swift on main. Tag time, this job just promotes the draft to
28+
# published (or creates fresh if no draft, e.g. a manual tag without a PR).
29+
# Other jobs then `gh release upload` the rest of the assets.
2930
create-release:
3031
name: create-release
3132
runs-on: ubuntu-latest
33+
permissions:
34+
contents: write
3235
outputs:
33-
upload_url: ${{ steps.release.outputs.upload_url }}
3436
release_version: ${{ env.RELEASE_VERSION }}
3537
steps:
3638
- name: Get the release version from the tag
3739
shell: bash
3840
if: env.RELEASE_VERSION == ''
3941
run: |
40-
# See: https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
4142
echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
42-
echo "version is: ${{ env.RELEASE_VERSION }}"
43+
echo "version is: ${GITHUB_REF#refs/tags/}"
4344
- name: Checkout repository
4445
uses: actions/checkout@v4
4546
with:
4647
fetch-depth: 1
47-
- name: Create GitHub release
48-
id: release
49-
uses: actions/create-release@v1
48+
- name: Promote draft release (or create fresh)
5049
env:
51-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52-
with:
53-
tag_name: ${{ env.RELEASE_VERSION }}
54-
release_name: ${{ env.RELEASE_VERSION }}
50+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51+
TAG: ${{ env.RELEASE_VERSION }}
52+
shell: bash
53+
run: |
54+
set -eu
55+
if gh release view "$TAG" --json isDraft -q .isDraft 2>/dev/null | grep -q true; then
56+
echo "Promoting existing draft $TAG to published"
57+
gh release edit "$TAG" --draft=false
58+
elif ! gh release view "$TAG" >/dev/null 2>&1; then
59+
echo "Creating fresh published release $TAG (no draft from release_swift.yml)"
60+
gh release create "$TAG" --title "$TAG" --notes ""
61+
else
62+
echo "Release $TAG already published"
63+
fi
5564
5665
build-and-publish-libs:
5766
name: Build & publish release libraries
@@ -121,50 +130,17 @@ jobs:
121130
echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV
122131
fi
123132
- name: Upload release archive
124-
uses: actions/upload-release-asset@v1.0.2
125133
env:
126-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
127-
with:
128-
upload_url: ${{ needs.create-release.outputs.upload_url }}
129-
asset_path: ${{ env.ASSET }}
130-
asset_name: ${{ env.ASSET }}
131-
asset_content_type: application/octet-stream
134+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
135+
TAG: ${{ needs.create-release.outputs.release_version }}
136+
shell: bash
137+
run: gh release upload "$TAG" "$ASSET" --clobber
138+
139+
# build-and-publish-swift was here. The Swift xcframework now ships from
140+
# release_swift.yml on the release/v* PR branch (it's already attached to
141+
# the draft release that create-release promotes), so there's nothing to do
142+
# here on tag.
132143

133-
build-and-publish-swift:
134-
name: Build & publish swift libraries
135-
timeout-minutes: 30
136-
runs-on: [self-hosted, macOS, ARM64, xcode16]
137-
needs: create-release
138-
steps:
139-
- uses: actions/checkout@master
140-
- uses: dtolnay/rust-toolchain@stable
141-
with:
142-
targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios,aarch64-apple-darwin
143-
- uses: davidB/rust-cargo-make@v1
144-
- name: Make swift
145-
# Same deterministic zip recipe as local `cargo make prepare-release`,
146-
# so the SHA-256 baked into Package.swift in the release PR matches
147-
# the asset uploaded here.
148-
# Two pre-publish gates: verify-swift-xcframework checks per-slice bundle
149-
# layout (catches iroh-ffi#247-class bugs); verify-swift-consumer
150-
# xcodebuild-builds the iOS device + iOS sim destinations against the
151-
# just-built xcframework, catching link/symbol issues that the macOS-only
152-
# test would miss.
153-
run: |
154-
cargo make swift-xcframework
155-
cargo make verify-swift-xcframework
156-
cargo make verify-swift-consumer
157-
bash scripts/release/zip_xcframework.sh
158-
- name: Upload release archive
159-
uses: actions/upload-release-asset@v1.0.2
160-
env:
161-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
162-
with:
163-
upload_url: ${{ needs.create-release.outputs.upload_url }}
164-
asset_path: IrohLib.xcframework.zip
165-
asset_name: IrohLib.xcframework.zip
166-
asset_content_type: application/octet-stream
167-
168144
# Cross-build the desktop cdylibs for each JVM host the Maven artifact
169145
# should support. JNI libs are loaded by JNA from classpath at
170146
# `<jna-platform>/libname.{so,dylib,dll}`; see the staging step below.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Release Swift artifact
2+
3+
# Triggers on push to release/v* branches (the convention prepare-release sets
4+
# up). Builds the Apple xcframework on CI, verifies its layout + that the iOS
5+
# slices link + that the macOS slice's tests pass, then:
6+
# 1. Bakes the resulting SHA-256 into Package.swift on this branch via a
7+
# bot commit ([skip swift-release] in the message prevents this workflow
8+
# from re-firing on its own commit).
9+
# 2. Uploads IrohLib.xcframework.zip to a draft GitHub release v<version>.
10+
#
11+
# Once the release PR merges + the v<version> tag is pushed, release.yml's
12+
# build-and-publish-swift job promotes that draft to published — no rebuild,
13+
# so the SHA in Package.swift always matches the zip on the release. Cleanly
14+
# sidesteps the cross-host determinism problem the prior local-build flow
15+
# kept hitting.
16+
17+
on:
18+
push:
19+
branches:
20+
- 'release/v*'
21+
22+
permissions:
23+
contents: write
24+
25+
jobs:
26+
build-and-bake:
27+
name: Build xcframework + bake SHA + upload draft
28+
# Don't re-fire on the bot's own SHA-bake commit.
29+
if: "!contains(github.event.head_commit.message, '[skip swift-release]')"
30+
runs-on: [self-hosted, macOS, ARM64, xcode16]
31+
timeout-minutes: 60
32+
steps:
33+
- uses: actions/checkout@v4
34+
with:
35+
# Full history so the bot can commit + push back to this branch.
36+
fetch-depth: 0
37+
token: ${{ secrets.GITHUB_TOKEN }}
38+
- uses: dtolnay/rust-toolchain@stable
39+
with:
40+
targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios,aarch64-apple-darwin
41+
- uses: Swatinem/rust-cache@v2
42+
- uses: davidB/rust-cargo-make@v1
43+
- name: Build xcframework + verify layout + iOS consumer + zip
44+
run: |
45+
cargo make swift-xcframework
46+
cargo make verify-swift-xcframework
47+
cargo make verify-swift-consumer
48+
bash scripts/release/zip_xcframework.sh
49+
- name: Parse version from Package.swift
50+
id: version
51+
run: |
52+
v=$(grep -oE 'let releaseTag = "v[^"]+"' Package.swift | sed -E 's/.*"v([^"]+)"/\1/')
53+
[ -n "$v" ] || { echo "ERROR: couldn't parse releaseTag in Package.swift" >&2; exit 1; }
54+
echo "version=$v" >> "$GITHUB_OUTPUT"
55+
- name: Bake SHA into Package.swift (if changed)
56+
run: |
57+
CURRENT=$(grep -oE 'let releaseChecksum = "[a-f0-9]+"' Package.swift | sed -E 's/.*"([a-f0-9]+)"/\1/')
58+
NEW=$(shasum -a 256 IrohLib.xcframework.zip | awk '{print $1}')
59+
if [ "$CURRENT" = "$NEW" ]; then
60+
echo "Checksum unchanged ($NEW) — skipping commit"
61+
exit 0
62+
fi
63+
python3 scripts/release/bump_version.py --checksum "$NEW"
64+
git config user.name "github-actions[bot]"
65+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
66+
git add Package.swift
67+
git commit -m "chore(release): bake Swift xcframework SHA $NEW [skip swift-release]"
68+
git push
69+
- name: Upload zip to draft GitHub release
70+
env:
71+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72+
run: |
73+
V=${{ steps.version.outputs.version }}
74+
# Create the draft release if it doesn't exist; replace its zip if it
75+
# does (re-pushes to the release branch overwrite the previous draft
76+
# asset, so the asset always matches the latest Package.swift SHA).
77+
if ! gh release view "v$V" >/dev/null 2>&1; then
78+
gh release create "v$V" \
79+
--draft \
80+
--title "v$V" \
81+
--notes "Draft release — promoted to published by release.yml on tag push."
82+
fi
83+
gh release upload "v$V" IrohLib.xcframework.zip --clobber

Makefile.toml

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -645,17 +645,12 @@ echo "pre-release-check passed — safe to run cargo make prepare-release <VERSI
645645
'''
646646

647647
[tasks.prepare-release]
648-
description = "Bump versions everywhere + build a deterministic xcframework zip + bake the checksum into Package.swift. Run on macOS (needs Xcode for the xcframework). Usage: cargo make prepare-release <VERSION>"
648+
description = "Bump version literals across every binding manifest. The Swift xcframework checksum is filled in by PR CI (release_swift.yml) once the release PR is open, so this no longer needs Xcode locally. Usage: cargo make prepare-release <VERSION>"
649649
script_runner = "@shell"
650650
script = '''
651651
set -eu
652652
VERSION="${1:?usage: cargo make prepare-release <VERSION> (no leading v)}"
653653
654-
if [ "$(uname -s)" != "Darwin" ]; then
655-
echo "ERROR: prepare-release must run on macOS (Xcode-required xcframework build)" >&2
656-
exit 1
657-
fi
658-
659654
echo "==> rewrite version literals"
660655
python3 scripts/release/bump_version.py "$VERSION"
661656
@@ -665,28 +660,21 @@ cargo update -p iroh-ffi --precise "$VERSION" >/dev/null
665660
echo "==> refresh iroh-js/yarn.lock"
666661
( cd iroh-js && yarn install --mode update-lockfile >/dev/null )
667662
668-
echo "==> build full xcframework"
669-
cargo make swift-xcframework >/dev/null
670-
671-
echo "==> deterministic zip + checksum"
672-
bash scripts/release/zip_xcframework.sh
673-
CHECKSUM=$(shasum -a 256 IrohLib.xcframework.zip | awk '{print $1}')
674-
675-
echo "==> bake checksum into Package.swift"
676-
python3 scripts/release/bump_version.py --checksum "$CHECKSUM"
677-
678663
cat <<EOF
679664
680-
Prepared release v$VERSION.
681-
zip: IrohLib.xcframework.zip ($(stat -f%z IrohLib.xcframework.zip) bytes)
682-
sha256: $CHECKSUM
665+
Prepared release v$VERSION (versions only; Swift xcframework SHA is CI's job).
683666
684667
Next:
685668
git checkout -b release/v$VERSION
686669
git commit -am "chore(release): v$VERSION"
687-
# PR -> green CI -> merge to main
688-
git tag v$VERSION <merge commit>
670+
git push -u origin release/v$VERSION
671+
# Open PR. release_swift.yml builds the xcframework on a CI runner, bakes
672+
# the resulting SHA into Package.swift on this branch, and uploads the zip
673+
# to a draft GitHub release at v$VERSION. Review the SHA-bake commit + the
674+
# draft release asset, then merge and tag:
675+
git tag v$VERSION <merge-commit-sha>
689676
git push origin v$VERSION
677+
# release.yml promotes the draft release to published.
690678
EOF
691679
'''
692680

0 commit comments

Comments
 (0)