8000 ci: consumer-test the Swift package, before tagging and after publishing (v3) by adityamukho · Pull Request #9 · project-minigraf/minigraf-swift · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
8000
Diff view
Diff view
40 changes: 34 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ on:
required: false
type: string
default: ''
version:
description: 'Version in the xcframework zip name (release.yml passes the release version)'
required: false
type: string
default: '0.0.0-ci'

jobs:
rust-test:
Expand All @@ -22,18 +27,32 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- run: cargo test

ios-build:
name: iOS simulator build smoke test
# Builds the xcframework zip and the generated Swift sources as the release
# publishes them.
package:
name: Package
uses: ./.github/workflows/package.yml
with:
ref: ${{ inputs.ref }}
version: ${{ inputs.version || '0.0.0-ci' }}

# An SPM app package depending on MinigrafKit, assembled as swift-releases
# will be, runs the README quick start and the consumer contract on an iOS
# simulator.
consumer:
name: Consumer test (iOS simulator)
needs: package
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: dtolnay/rust-toolchain@stable
- uses: actions/download-artifact@v4
with:
targets: aarch64-apple-ios-sim
- name: Build for iOS simulator
run: cargo build --target aarch64-apple-ios-sim --release
name: dist-xcframework
path: dist-xcframework
- name: Run consumer test
run: bash consumer-test/run.sh local dist-xcframework ${{ inputs.version || '0.0.0-ci' }}

# Compiles the generated Swift bindings with Sources/MinigrafKit and runs
# Tests/swift/main.swift against a macOS build of the shim.
Expand Down Expand Up @@ -62,3 +81,12 @@ jobs:
build/swift/minigraf_ffi.swift Sources/MinigrafKit/*.swift Tests/swift/main.swift \
-o build/swift-test
DYLD_LIBRARY_PATH=target/release ./build/swift-test

readme-sync:
name: README quick start matches the consumer test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- run: python3 -I consumer-test/readme_sync.py README.md consumer-test/App/Tests/ConsumerTests/QuickstartTests.swift
86 changes: 86 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: Package

# Builds the xcframework zip exactly as the release publishes it, plus the
# generated Swift sources the release commits to swift-releases, and uploads
# both as dist-xcframework.

on:
workflow_call:
inputs:
ref:
required: false
type: string
default: ''
version:
required: true
type: string

jobs:
xcframework:
name: Build xcframework
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios

- name: Build iOS device library
run: cargo build --target aarch64-apple-ios --release

- name: Build iOS simulator library
run: cargo build --target aarch64-apple-ios-sim --release

- name: Build iOS x86_64 simulator library
run: cargo build --target x86_64-apple-ios --release

- name: Create universal iOS simulator library
run: |
lipo -create \
target/aarch64-apple-ios-sim/release/libminigraf_ffi.a \
target/x86_64-apple-ios/release/libminigraf_ffi.a \
-output libminigraf_ffi_sim_universal.a

- name: Generate Swift bindings
run: |
mkdir -p generated
cargo run --bin uniffi-bindgen -- generate \
--library target/aarch64-apple-ios/release/libminigraf_ffi.a \
--language swift \
--out-dir generated/

# Clang finds a module in a header directory only through a file named
# module.modulemap. Under its generated name (minigraf_ffiFFI.modulemap)
# `canImport(minigraf_ffiFFI)` is false and minigraf_ffi.swift does not
# compile, which broke every release up to 2.0.8.
- name: Prepare headers for xcframework
run: |
mkdir -p includes
cp generated/*.h includes/
cp generated/minigraf_ffiFFI.modulemap includes/module.modulemap

- name: Assemble xcframework
env:
VERSION: ${{ inputs.version }}
run: |
xcodebuild -create-xcframework \
-library target/aarch64-apple-ios/release/libminigraf_ffi.a \
-headers includes/ \
-library libminigraf_ffi_sim_universal.a \
-headers includes/ \
-output "MinigrafKit.xcframework"
mkdir -p dist/Sources
zip -qr "dist/MinigrafKit-v$VERSION.xcframework.zip" MinigrafKit.xcframework
# Only the Swift file goes into the MinigrafKit target; the header
# and the module map reach the compiler through the xcframework.
cp generated/*.swift dist/Sources/
ls -lR dist

- uses: actions/upload-artifact@v4
with:
name: dist-xcframework
path: dist/
if-no-files-found: error
150 changes: 87 additions & 63 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ jobs:
token: ${{ secrets.MINIGRAF_RELEASE_TOKEN }}

- id: ver
env:
V: ${{ github.event.client_payload.version || github.event.inputs.version }}
run: |
V="${{ github.event.client_payload.version || github.event.inputs.version }}"
[[ "$V" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::version '$V' is not v<semver>"; exit 1; }
echo "tag=$V" >> "$GITHUB_OUTPUT"
echo "semver=${V#v}" >> "$GITHUB_OUTPUT"

Expand Down Expand Up @@ -57,14 +59,20 @@ jobs:
- name: Update Cargo.lock
run: cargo update --precise "${{ steps.ver.outputs.semver }}" minigraf

- name: Stamp the version in the README install snippet
run: |
sed -i 's|\(\.package(url: "https://github.com/project-minigraf/minigraf-swift", from: "\)[^"]*"|\1${{ steps.ver.outputs.semver }}"|' README.md
[ "$(grep -c 'minigraf-swift", from: "${{ steps.ver.outputs.semver }}")' README.md)" -eq 1 ] \
|| { echo "::error::the README install snippet was not stamped"; exit 1; }

# Push the pin commit to a throwaway branch only. The tag and main are
# updated by the tag job, after the test job has passed on this commit.
- name: Commit and push release branch
id: commit
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Cargo.toml Cargo.lock
git add Cargo.toml Cargo.lock README.md
if ! git diff --cached --quiet; then
git commit -m "chore: release ${{ steps.ver.outputs.tag }}"
else
Expand All @@ -81,6 +89,9 @@ jobs:
uses: ./.github/workflows/ci.yml
with:
ref: ${{ needs.prepare.outputs.sha }}
# The xcframework is built and consumer-tested here at the release
# version; publish uploads exactly this zip and these Swift sources.
version: ${{ needs.prepare.outputs.semver }}

# The source commit gets a non-semver `source-vX` tag. The semver `vX` tag is
# what Swift Package Manager resolves, so it goes on the swift-releases commit
Expand Down Expand Up @@ -120,83 +131,73 @@ jobs:
git push origin --delete "release/${{ needs.prepare.outputs.tag }}" \
|| echo "::warning::Could not delete branch release/${{ needs.prepare.outputs.tag }}"

build-and-publish:
name: Build xcframework and publish
needs: [prepare, tag]
runs-on: macos-latest
publish:
name: Publish the tested xcframework
needs: [prepare, test, tag]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: source-${{ needs.prepare.outputs.tag }}
token: ${{ secrets.MINIGRAF_RELEASE_TOKEN }}
fetch-depth: 0

- uses: dtolnay/rust-toolchain@stable
- uses: actions/download-artifact@v4
with:
targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios

- name: Build iOS device library
run: cargo build --target aarch64-apple-ios --release

- name: Build iOS simulator library
run: cargo build --target aarch64-apple-ios-sim --release
name: dist-xcframework
path: ${{ runner.temp }}/dist

- name: Build iOS x86_64 simulator library
run: cargo build --target x86_64-apple-ios --release

- name: Create universal iOS simulator library
run: |
lipo -create \
target/aarch64-apple-ios-sim/release/libminigraf_ffi.a \
target/x86_64-apple-ios/release/libminigraf_ffi.a \
-output libminigraf_ffi_sim_universal.a

- name: Generate Swift bindings
run: |
mkdir -p Sources/MinigrafKit
cargo run --bin uniffi-bindgen -- generate \
--library target/aarch64-apple-ios/release/libminigraf_ffi.a \
--language swift \
--out-dir Sources/MinigrafKit/

- name: Prepare headers for xcframework
run: |
mkdir -p includes
cp Sources/MinigrafKit/*.h includes/ 2>/dev/null || true
cp Sources/MinigrafKit/*.modulemap includes/ 2>/dev/null || true

- name: Assemble xcframework
# A re-run after a partial publish must not move what is already public:
# once the semver tag exists, its Package.swift fixes the checksum, so
# only the same zip may be uploaded, and the branch and tag stay put.
- name: Check for an earlier publish of this version
id: earlier
env:
TAG: ${{ needs.prepare.outputs.tag }}
DIST: ${{ runner.temp }}/dist
run: |
TAG="${{ needs.prepare.outputs.tag }}"
xcodebuild -create-xcframework \
-library target/aarch64-apple-ios/release/libminigraf_f BD0 fi.a \
-headers includes/ \
-library libminigraf_ffi_sim_universal.a \
-headers includes/ \
-output "MinigrafKit.xcframework"
zip -r "MinigrafKit-${TAG}.xcframework.zip" MinigrafKit.xcframework
ZIP="$DIST/MinigrafKit-${TAG}.xcframework.zip"
[ -f "$ZIP" ] || { echo "::error::no $ZIP"; ls -R "$DIST"; exit 1; }
CHECKSUM=$(sha256sum "$ZIP" | awk '{print $1}')
echo "checksum=$CHECKSUM" >> "$GITHUB_OUTPUT"
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" > /dev/null; then
git fetch --quiet origin "refs/tags/$TAG:refs/tags/$TAG"
PUBLISHED=$(git show "$TAG:Package.swift" | sed -n 's/.*checksum: "\([0-9a-f]*\)".*/\1/p')
if [ "$PUBLISHED" != "$CHECKSUM" ]; then
echo "::error::$TAG is already tagged with checksum $PUBLISHED, but the tested zip is $CHECKSUM. The tag is public; do not move it."
exit 1
fi
echo "::warning::$TAG is already tagged with this zip's checksum; only the release asset is (re)checked"
echo "tagged=true" >> "$GITHUB_OUTPUT"
else
echo "tagged=false" >> "$GITHUB_OUTPUT"
fi

- name: Compute checksum and update Package.swift
if: steps.earlier.outputs.tagged == 'false'
env:
TAG: ${{ needs.prepare.outputs.tag }}
CHECKSUM: ${{ steps.earlier.outputs.checksum }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
CHECKSUM=$(shasum -a 256 "MinigrafKit-${TAG}.xcframework.zip" | awk '{print $1}')
URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/MinigrafKit-${TAG}.xcframework.zip"
sed -i '' \
-e "s|url: \"https://github.com/.*/releases/download/.*/MinigrafKit-.*\\.xcframework\\.zip\"|url: \"${URL}\"|" \
-e "s|checksum: \"[^\"]*\"|checksum: \"${CHECKSUM}\"|" \
Package.swift
perl -0pi -e "s|url: \"[^\"]*\",(\s*)checksum: \"[^\"]*\"|url: \"${URL}\",\1checksum: \"${CHECKSUM}\"|" Package.swift
grep -q "checksum: \"${CHECKSUM}\"" Package.swift || { echo "::error::Package.swift was not updated"; exit 1; }
grep -n 'url:\|checksum:' Package.swift

- name: Commit Package.swift and Swift sources to swift-releases branch
if: steps.earlier.outputs.tagged == 'false'
env:
TAG: ${{ needs.prepare.outputs.tag }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DIST: ${{ runner.temp }}/dist
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"

TMPDIR_SWIFT=$(mktemp -d)
cp Package.swift "$TMPDIR_SWIFT/Package.swift"
cp -r Sources/MinigrafKit/ "$TMPDIR_SWIFT/MinigrafKit"
cp -R Sources/MinigrafKit/. "$TMPDIR_SWIFT/MinigrafKit"
rm -f "$TMPDIR_SWIFT/MinigrafKit/.gitkeep"
cp "$DIST"/Sources/*.swift "$TMPDIR_SWIFT/MinigrafKit/"

if git fetch origin swift-releases 2>/dev/null; then
git checkout -fB swift-releases origin/swift-releases
Expand All @@ -205,9 +206,13 @@ jobs:
git rm -rf . --quiet 2>/dev/null || true
fi

# Replace the target's files outright, so a header or module map
# committed by an older release does not linger.
git rm -rq --ignore-unmatch Sources/MinigrafKit
rm -rf Sources/MinigrafKit
cp "$TMPDIR_SWIFT/Package.swift" Package.swift
mkdir -p Sources/MinigrafKit/
cp -r "$TMPDIR_SWIFT/MinigrafKit/." Sources/MinigrafKit/
cp -R "$TMPDIR_SWIFT/MinigrafKit/." Sources/MinigrafKit/
rm -rf "$TMPDIR_SWIFT"

git add Package.swift Sources/MinigrafKit/
Expand All @@ -221,6 +226,7 @@ jobs:
# SPM resolves semver tags only, so the semver tag must point at the
# swift-releases commit whose Package.swift has the real URL and checksum.
- name: Tag the swift-releases commit with the semver tag
if: steps.earlier.outputs.tagged == 'false'
env:
TAG: ${{ needs.prepare.outputs.tag }}
run: |
Expand All @@ -238,11 +244,29 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
DIST: ${{ runner.temp }}/dist
run: |
gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
--title "MinigrafKit $TAG" \
--notes "Swift/iOS binding for minigraf $TAG."
gh release upload "$TAG" "MinigrafKit-${TAG}.xcframework.zip" \
--repo "$GITHUB_REPOSITORY"
if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
--title "MinigrafKit $TAG" \
--notes "Swift/iOS binding for minigraf $TAG."
fi
# The zip matches the tagged checksum (checked above), so replacing an
# asset left by a partial upload is safe.
gh release upload "$TAG" "$DIST/MinigrafKit-${TAG}.xcframework.zip" \
--repo "$GITHUB_REPOSITORY" --clobber

# Resolves the published version with SPM and runs the consumer test on an
# iOS simulator; on failure the run fails and a release-broken issue is opened.
verify-published:
name: Verify the published package
needs: [prepare, publish]
permissions:
contents: read
issues: write
uses: ./.github/workflows/verify-published.yml
with:
version: ${{ needs.prepare.outputs.semver }}
ref: source-${{ needs.prepare.outputs.tag }}
Loading
Loading
0