name: Mobile Release Artifacts
on:
push:
tags:
- '**[0-9]+.[0-9]+.[0-9]+*'
workflow_dispatch:
inputs:
tag:
description: 'Release tag to upload artifacts to (e.g. v0.21.0)'
required: true
permissions:
contents: write
packages: write
jobs:
mobile-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android
- name: Install cargo-ndk
run: cargo install cargo-ndk --locked
- name: Set ANDROID_NDK_HOME
run: echo "ANDROID_NDK_HOME=$ANDROID_NDK_LATEST_HOME" >> $GITHUB_ENV
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cross-compile Android targets
run: |
cargo ndk \
-t arm64-v8a \
-t armeabi-v7a \
-t x86_64 \
-o minigraf-ffi/android/jniLibs \
build --release -p minigraf-ffi
- name: Generate Kotlin bindings
run: |
cargo run -p minigraf-ffi --bin uniffi-bindgen -- generate \
--library target/aarch64-linux-android/release/libminigraf_ffi.so \
--language kotlin \
--out-dir minigraf-ffi/android/src/main/java/
- name: Assemble AAR
run: |
cd minigraf-ffi/android
./gradlew assembleRelease
- name: Rename artifact
run: |
TAG="${{ inputs.tag || github.ref_name }}"
cp minigraf-ffi/android/build/outputs/aar/minigraf-android-release.aar \
"minigraf-android-${TAG}.aar"
- uses: actions/upload-artifact@v4
with:
name: minigraf-android
path: "minigraf-android-*.aar"
mobile-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios,aarch64-apple-ios-sim
- name: Build iOS device library
run: cargo build --target aarch64-apple-ios --release -p minigraf-ffi
- name: Build iOS simulator library
run: cargo build --target aarch64-apple-ios-sim --release -p minigraf-ffi
- name: Generate Swift bindings
run: |
mkdir -p minigraf-swift/Sources/MinigrafKit
cargo run -p minigraf-ffi --bin uniffi-bindgen -- generate \
--library target/aarch64-apple-ios/release/libminigraf_ffi.a \
--language swift \
--out-dir minigraf-swift/Sources/MinigrafKit/
- name: Prepare headers for xcframework
run: |
mkdir -p minigraf-swift/includes
cp minigraf-swift/Sources/MinigrafKit/*.h minigraf-swift/includes/ 2>/dev/null || true
cp minigraf-swift/Sources/MinigrafKit/*.modulemap minigraf-swift/includes/ 2>/dev/null || true
- name: Assemble xcframework
run: |
TAG="${{ inputs.tag || github.ref_name }}"
xcodebuild -create-xcframework \
-library target/aarch64-apple-ios/release/libminigraf_ffi.a \
-headers minigraf-swift/includes/ \
-library target/aarch64-apple-ios-sim/release/libminigraf_ffi.a \
-headers minigraf-swift/includes/ \
-output "MinigrafKit.xcframework"
zip -r "MinigrafKit-${TAG}.xcframework.zip" MinigrafKit.xcframework
- uses: actions/upload-artifact@v4
with:
name: minigraf-ios
path: "MinigrafKit-*.xcframework.zip"
- uses: actions/upload-artifact@v4
with:
name: swift-sources
path: minigraf-swift/Sources/MinigrafKit/
release-upload-mobile:
needs: [mobile-android, mobile-ios]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: minigraf-android
path: artifacts/
- uses: actions/download-artifact@v4
with:
name: minigraf-ios
path: artifacts/
- uses: actions/download-artifact@v4
with:
name: swift-sources
path: minigraf-swift/Sources/MinigrafKit/
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Wait for GitHub Release to exist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag || github.ref_name }}
run: |
echo "Waiting for release $TAG..."
for i in $(seq 1 40); do
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
echo "Release found on attempt $i"
break
fi
echo "Attempt $i/40: not yet, waiting 15s..."
sleep 15
done
if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
echo "ERROR: Release $TAG not found after 40 attempts (10 minutes). Aborting."
exit 1
fi
- name: Upload artifacts to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag || github.ref_name }}
run: |
gh release upload "$TAG" artifacts/* \
--repo "$GITHUB_REPOSITORY" \
--clobber
- name: Compute xcframework checksum and update Package.swift
env:
TAG: ${{ inputs.tag || github.ref_name }}
run: |
XCFW_ZIP=$(ls artifacts/MinigrafKit-*.xcframework.zip)
CHECKSUM=$(shasum -a 256 "$XCFW_ZIP" | awk '{print $1}')
# The xcframework zip lives on the main release (TAG).
# Package.swift itself lives on the swift-releases branch tagged swift-TAG,
# so SPM consumers use the swift-TAG reference for the Swift package.
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: \"[0-9a-f]*\"|checksum: \"${CHECKSUM}\"|" \
Package.swift
- name: Publish AAR to GitHub Packages
env:
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ inputs.tag || github.ref_name }}"
export VERSION="${TAG#v}"
mkdir -p minigraf-ffi/android/build/outputs/aar/
cp artifacts/minigraf-android-*.aar \
minigraf-ffi/android/build/outputs/aar/minigraf-android-release.aar
cd minigraf-ffi/android
./gradlew publishReleasePublicationToGitHubPackagesRepository
- name: Commit Package.swift update and Swift sources, then create swift-TAG
env:
TAG: ${{ inputs.tag || github.ref_name }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
# Save the files we intend to push (Package.swift already updated by sed,
# swift sources already downloaded by actions/download-artifact above).
TMPDIR_SWIFT=$(mktemp -d)
cp Package.swift "$TMPDIR_SWIFT/Package.swift"
cp -r minigraf-swift/Sources/MinigrafKit/ "$TMPDIR_SWIFT/MinigrafKit"
# Switch to swift-releases branch so we only push Package.swift and Swift
# sources — not the full main HEAD (which includes workflow files, requiring
# `workflows` scope that GITHUB_TOKEN cannot be granted via permissions:).
if git fetch origin swift-releases 2>/dev/null; then
# -f: discard local changes (Package.swift modified by sed, swift sources
# untracked from artifact download) — we've already saved them to TMPDIR.
git checkout -fB swift-releases origin/swift-releases
else
# First-ever release: start a clean orphan branch with no history.
git checkout --orphan swift-releases
git rm -rf . --quiet 2>/dev/null || true
fi
# Apply only the intended changes.
cp "$TMPDIR_SWIFT/Package.swift" Package.swift
mkdir -p minigraf-swift/Sources/MinigrafKit/
cp -r "$TMPDIR_SWIFT/MinigrafKit/." minigraf-swift/Sources/MinigrafKit/
rm -rf "$TMPDIR_SWIFT"
git add Package.swift minigraf-swift/Sources/MinigrafKit/
git commit -m "chore(release): update Package.swift and Swift bindings for ${TAG}" \
|| echo "Nothing to commit"
# Push to the unprotected swift-releases branch instead of main.
# SPM resolves packages by the swift-TAG (e.g. swift-v0.25.0) which points
# to this commit — the original TAG is left on the main branch so that all
# other language workflows (Python, Java, C, Node.js) can check out the
# full source tree.
git push origin swift-releases --force
NEW_SHA=$(git rev-parse HEAD)
# Create (or update) the swift-TAG reference on the swift-releases commit.
SWIFT_TAG="swift-${TAG}"
gh api "repos/${GITHUB_REPOSITORY}/git/refs" \
-X POST \
-f ref="refs/tags/${SWIFT_TAG}" \
-f sha="$NEW_SHA" 2>/dev/null \
|| gh api "repos/${GITHUB_REPOSITORY}/git/refs/tags/${SWIFT_TAG}" \
-X PATCH \
-f sha="$NEW_SHA" \
-F force=true