Publish SharedCore #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish SharedCore | |
| # Cuts a release of the `:kmp:shared-core` XCFramework into | |
| # code-payments/flipcash-shared-core-spm, which is the repo iOS depends on. The | |
| # Kotlin stays here; that repo only ever holds the generated `Package.swift` and | |
| # the release assets it points at. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to publish, e.g. 0.1.0 — also the Swift Package tag' | |
| required: true | |
| summary: | |
| description: 'Optional lede for the release notes. The changelog below it is generated either way.' | |
| required: false | |
| type: string | |
| concurrency: | |
| # Two publishes at once would race on the same tag and release. | |
| group: publish-shared-core | |
| cancel-in-progress: false | |
| env: | |
| CI: true | |
| SPM_REPO: code-payments/flipcash-shared-core-spm | |
| jobs: | |
| publish: | |
| name: Publish SharedCore ${{ inputs.version }} | |
| # `contents: write` is for this repo's `shared-core/*` tag; the Swift Package | |
| # repo is reached with the PAT below, not with GITHUB_TOKEN. | |
| permissions: | |
| contents: write | |
| # Apple targets and `swift package compute-checksum` both need Xcode, so this | |
| # lane can't share the Ubuntu runners the rest of CI uses. | |
| runs-on: macos-15 | |
| steps: | |
| # The release notes are a `git log` between this publish and the previous | |
| # `shared-core/*` tag, so this lane needs history and tags rather than the | |
| # single commit a build would want. | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Validate Gradle wrapper | |
| uses: gradle/actions/wrapper-validation@v4 | |
| - name: Setup Java env | |
| uses: actions/setup-java@v3 | |
| with: | |
| java-version: '21' | |
| distribution: 'corretto' | |
| cache: 'gradle' | |
| # A fine-grained PAT with Contents: read & write on the Swift Package repo. | |
| # The job's own GITHUB_TOKEN can't reach another repository, and a deploy | |
| # key can only push git — it can't create the GitHub release the | |
| # XCFramework is uploaded to — so one PAT covers both halves. | |
| - name: Check out the Swift Package repo | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ env.SPM_REPO }} | |
| path: spm-repo | |
| token: ${{ secrets.SHARED_CORE_PUBLISH_TOKEN }} | |
| # Gradle configures every project in the build, and the Android app applies the | |
| # secrets plugin, which fails configuration outright when `local.properties` is | |
| # absent. This lane only builds the KMP module — nothing here compiles the app or | |
| # talks to any of these services — so obviously-fake values are enough to get | |
| # through configuration without handing this workflow the real secrets. | |
| - name: Write placeholder local.properties | |
| run: | | |
| set -euo pipefail | |
| { | |
| echo 'BUGSNAG_API_KEY="00000000000000000000000000000000"' | |
| echo 'GOOGLE_CLOUD_PROJECT_NUMBER=000000000000' | |
| echo 'MIXPANEL_API_KEY="00000000000000000000000000000000"' | |
| echo 'COINBASE_ONRAMP_API_KEY=00000000-0000-0000-0000-000000000000' | |
| } > ./local.properties | |
| # The Swift half of the package lives here, next to the Kotlin it wraps, so the | |
| # two move in one commit; the Swift Package repo is a publish target, not a place | |
| # to edit. `Package.swift` has to be in place before Gradle runs, since KMMBridge | |
| # only rewrites the variables block inside it. | |
| - name: Stage the Swift package sources | |
| run: | | |
| set -euo pipefail | |
| rm -rf spm-repo/Sources spm-repo/Tests | |
| cp -R kmp/shared-core/spm/Package.swift kmp/shared-core/spm/Sources kmp/shared-core/spm/Tests spm-repo/ | |
| - name: Build the XCFramework, upload it, and update Package.swift | |
| env: | |
| # Gradle reads ORG_GRADLE_PROJECT_-prefixed vars as project properties, | |
| # which keeps the token out of the command line. | |
| ORG_GRADLE_PROJECT_GITHUB_PUBLISH_TOKEN: ${{ secrets.SHARED_CORE_PUBLISH_TOKEN }} | |
| run: | | |
| ./gradlew :kmp:shared-core:kmmBridgePublish \ | |
| -PENABLE_PUBLISHING=true \ | |
| -PsharedCoreVersion=${{ inputs.version }} \ | |
| -PspmRepoDir=$GITHUB_WORKSPACE/spm-repo | |
| # KMMBridge points the binary target at the release asset's *API* URL, which | |
| # serves private repos but is rate limited to 60 requests an hour per IP for | |
| # anyone unauthenticated. This repo is public, so the plain download URL fetches | |
| # the same bytes with no limit and no credentials -- without this, a developer or | |
| # a CI runner that has spent its anonymous quota fails to resolve the package. | |
| - name: Point the binary target at the unauthenticated download URL | |
| working-directory: spm-repo | |
| run: | | |
| set -euo pipefail | |
| download="https://github.com/${SPM_REPO}/releases/download/${{ inputs.version }}/SharedCore.xcframework.zip" | |
| perl -pi -e 's{^let remoteKotlinUrl = ".*"$}{let remoteKotlinUrl = "'"$download"'"}' Package.swift | |
| grep -q "$download" Package.swift | |
| curl -fsSLI "$download" > /dev/null | |
| # The release asset is up by now but the tag still points at the old | |
| # Package.swift, so nothing consumes this build until the next step. Compiling | |
| # the Swift glue against the framework we just uploaded is the last moment a | |
| # mismatch between the two is cheap to fix — after the tag moves, it's a | |
| # published-and-broken version. | |
| - name: Verify the package builds against the uploaded framework | |
| working-directory: spm-repo | |
| run: | | |
| set -euo pipefail | |
| xcodebuild -scheme SharedCore \ | |
| -destination 'generic/platform=iOS Simulator' \ | |
| -clonedSourcePackagesDirPath "$RUNNER_TEMP/spm-verify" \ | |
| -quiet \ | |
| build | |
| - name: Commit and tag the Swift Package | |
| working-directory: spm-repo | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add -A Package.swift Sources Tests | |
| if git diff --cached --quiet; then | |
| echo "Nothing to commit — the upload produced the same URL and checksum, and the Swift sources are unchanged." | |
| exit 1 | |
| fi | |
| git commit -m "SharedCore ${{ inputs.version }}" | |
| git push origin HEAD:main | |
| # The release upload already created this tag, pointing at whatever | |
| # main was before the commit above — i.e. at a Package.swift that | |
| # doesn't mention this release. Move it onto the commit that does, or | |
| # SPM resolves the version to the previous binary. | |
| git tag -f "${{ inputs.version }}" | |
| git push -f origin "${{ inputs.version }}" | |
| # The Swift Package repo has no commits of its own to describe — it holds a | |
| # generated `Package.swift` and the zip it points at — so the notes are | |
| # derived here, from the range between this publish and the last one. | |
| - name: Write the release notes | |
| env: | |
| GH_TOKEN: ${{ secrets.SHARED_CORE_PUBLISH_TOKEN }} | |
| SUMMARY: ${{ inputs.summary }} | |
| run: | | |
| set -euo pipefail | |
| checksum=$(sed -n 's/^let remoteKotlinChecksum = "\(.*\)"$/\1/p' spm-repo/Package.swift) | |
| summary_arg=() | |
| if [ -n "$SUMMARY" ]; then | |
| printf '%s\n' "$SUMMARY" > "$RUNNER_TEMP/summary.md" | |
| summary_arg=(--summary-file "$RUNNER_TEMP/summary.md") | |
| fi | |
| bash scripts/shared-core-release-notes.sh "${{ inputs.version }}" \ | |
| --source "$GITHUB_SHA" \ | |
| --checksum "$checksum" \ | |
| "${summary_arg[@]+"${summary_arg[@]}"}" > "$RUNNER_TEMP/notes.md" | |
| cat "$RUNNER_TEMP/notes.md" | |
| gh release edit "${{ inputs.version }}" \ | |
| --repo "$SPM_REPO" \ | |
| --title "SharedCore ${{ inputs.version }}" \ | |
| --notes-file "$RUNNER_TEMP/notes.md" | |
| # Nothing else records which Kotlin a published framework was built from, and | |
| # without that the next release has no range to generate its notes over. The | |
| # tag is created last so a failed publish doesn't claim a version. | |
| - name: Tag the source commit | |
| run: | | |
| set -euo pipefail | |
| git tag "shared-core/${{ inputs.version }}" "$GITHUB_SHA" | |
| git push origin "shared-core/${{ inputs.version }}" | |
| - name: Summary | |
| run: | | |
| { | |
| echo "### SharedCore ${{ inputs.version }} published" | |
| echo | |
| echo "- Release: https://github.com/${SPM_REPO}/releases/tag/${{ inputs.version }}" | |
| echo '- Consume: `.package(url: "https://github.com/'"${SPM_REPO}"'", from: "${{ inputs.version }}")`' | |
| echo "- Built from \`${GITHUB_SHA}\`, tagged \`shared-core/${{ inputs.version }}\`" | |
| } >> "$GITHUB_STEP_SUMMARY" |