ci(ios): publish tagged releases as binary targets via Buildkite#502
Draft
jkmassel wants to merge 2 commits into
Draft
ci(ios): publish tagged releases as binary targets via Buildkite#502jkmassel wants to merge 2 commits into
jkmassel wants to merge 2 commits into
Conversation
XCFramework BuildThis PR's XCFramework is available for testing. Add the following to your .package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/502")Built from 2d598d7 |
27833d2 to
60af26f
Compare
Mirrors the wordpress-rs tag-release flow. `bin/release.sh` stops at bumping versions on trunk; a follow-up Buildkite build kicked off with `NEW_VERSION=v<x.y.z>` then rewrites `Package.swift` to `.release(version:, checksum:)`, tags, and creates the GitHub Release. The tag's commit lives off trunk (parented on the release commit but only reachable via the tag ref), so SPM consumers pinning the tag resolve the prebuilt XCFramework from CDN rather than rebuilding from the local source bundle. This is the precondition for ignoring the committed iOS JS bundle at `ios/Sources/GutenbergKitResources/Gutenberg/` — once a tagged release exists in `.release(...)` mode and WordPress-iOS bumps to it, those files can be dropped from trunk.
60af26f to
00d77af
Compare
Review-feedback follow-up to the publish flow added in this PR. - `validate` lane now passes an explicit `api_token` to `get_github_release` and asserts `GITHUB_API_STATUS_CODE == 200` after the probe. The action returns `nil` for both "no such release" AND for any API failure (401, 404, network), so without the status check an auth-misconfigured probe silently green-lit a publish. - `publish_release_to_github` swaps the `gh release create` shellout for `set_github_release(api_token: …)` (release-toolkit action). No more reliance on the agent's ambient `gh` auth — `use-bot-for-git` only sets `GIT_SSH_COMMAND`, not `GH_TOKEN`. Asset upload, prerelease flag, and auto-generated notes all flow through the action's params. - `release` orchestrator re-invokes `validate(version:, github_token:)` at the top so a misconfigured pipeline can't silently skip it. New `github_token!` helper resolves token from `options` or `ENV`. - Refactored `publish_release_to_github` itself: dropped the staging-branch + draft + flip dance in favour of the simpler `push_git_tags` flow that wordpress-rs uses. `git push <tag>` carries the commit along with the tag ref, so no branch ref ever lives on origin. Tag is the last thing pushed before the GH Release call, so partial failure leaves either nothing (clean re-run) or just the GH Release missing (recoverable manually against the existing tag). - `:s3: Publish XCFramework to S3` step also gated on `build.tag == null` so the auto-triggered tag build doesn't re-upload the same iOS artifact the `:rocket:` step already pushed. Android publish on tag builds is intentionally not gated — it's still load-bearing (produces the canonical `vX.Y.Z` Maven artifact via `--tag-name`). - `bin/release.sh --dry-run` now prints the Buildkite-trigger preview it was previously suppressing, with a `[DRY RUN]` prefix on the leading status line. - Fastfile comment on the `release` lane warns against local invocation (it pushes a tag and creates a real GH Release).
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
bin/release.shstops at "bump versions on trunk and push" — no moregit tag, no moregh release create.:rocket: Publish Swift release $NEW_VERSIONBuildkite step, gated onbuild.env("NEW_VERSION") != null, takes over: builds + signs the XCFramework, uploads to S3, rewritesPackage.swiftto.release(version:, checksum:), tags, and creates the GitHub Release.release/validate/update_swift_package/publish_release_to_githublanes; tag's commit lives offtrunk, only the tag ref is pushed).There's a bit of awkward duplication in this PR – once we can remove the committed JS/HTML files, we can delete a bunch of release code (and align what we have against Ruby/Release Toolkit instead of bash).
Why
Tagged releases currently ship
Package.swiftin.localmode, so SPM consumers (WordPress-iOS pinsv0.15.0) resolve the JS bundle fromios/Sources/GutenbergKitResources/Gutenberg/— which is why those 61 files are still committed despite #495 wiring up XCFramework distribution for PR builds.Once a tag's
Package.swiftpoints at the prebuilt XCFramework on CDN (this PR), and WordPress-iOS bumps to a tag cut under the new flow, the committed iOS bundle and the two commented-out lines at.gitignore:200-202can finally be dropped.How a release works after this
Step 1 — local (same as today, minus the tag/release):
```bash
make release VERSION_TYPE=patch
```
Bumps
package.json/GutenbergKitVersion.swift/GutenbergKitVersion.kt, runsmake build, commits aschore(release): X.Y.Z, pushes totrunk. Done. The script prints the SHA of the commit it just pushed — pin that SHA when triggering the Buildkite build below.Step 2 — Buildkite:
trunkNEW_VERSION=vX.Y.ZThe build runs:
:white_check_mark: Validate Swift release— fast-fails on malformed tag names or if the tag/Release already exists. Tag check usesgit_tag_exists(remote: true); release check usesget_github_releasewith an explicitapi_tokenand asserts the response was a real200(not a swallowed 401/404/network error). Runs early so a badNEW_VERSIONshort-circuits before the XCFramework build.:rocket: Publish Swift release— gated onvalidate-release+ the XCFramework build + lint/test steps. The lane:validateas defense-in-depth — covers a future pipeline edit that drops thevalidate-releasedependency. Cheap no-op when the earlier Buildkite step already passed.Package.swiftto.release(version: "vX.Y.Z", checksum: ...)(reusing therewrite_resources_mode!helper from ci(ios): publish + prune per-PR XCFramework snapshot branches #495).s3://a8c-apps-public-artifacts/gutenbergkit/vX.Y.Z/.release/vX.Y.Zbranch, commits the rewrite, tagsvX.Y.Z, and pushes only the tag viapush_git_tags.git push <tag>carries the commit along with the tag ref, so the tag's commit becomes reachable on origin via the tag alone — no branch ref ever lives on remote.set_github_release(release-toolkit action) against the just-pushed tag — auto-generated notes, marks as prerelease when the version contains-, uploads the XCFramework + checksum as assets. Authenticates with an explicitGITHUB_TOKENso we don't depend on the agent's ambientghauth.The tag is pushed before the GH Release is created. Once the tag is on origin, SPM consumers can already resolve
vX.Y.Zagainst the prebuilt XCFramework on CDN — the GH Release is metadata + an asset mirror on top of that. Ifset_github_releasefails, the tag is unaffected and an operator can recreate the Release manually against the existing tag.The tag's commit is parented on the release commit but unreachable from
trunk's history — same shape as thepr-build/<n>and (deferred)trunk-buildsnapshot branches, just published under a tag ref instead of a branch ref.The
releaselane is the CI orchestrator — don't run it locally: it pushes the tag, uploads to the public S3 bucket, and creates a real GitHub Release. For local diagnosis, invokevalidate,update_swift_package,publish_to_s3, orpublish_release_to_githubindividually.Tag-triggered builds (Android still relies on these)
Pushing the tag triggers a separate Buildkite build (the pipeline has "Build tags" enabled). For Android, that build is still load-bearing —
prepareToPublishToS3resolves the version tovX.Y.Zfrom--tag-nameand produces the canonicalvX.Y.ZMaven artifact (the trunk branch-build only producestrunk-<sha>). The plugin hard-fails if the version is already published, so the trunk vs. tag builds aren't competing.For iOS, the rocket step already uploads to
gutenbergkit/vX.Y.Z/, so the tag-build's:s3:would just re-upload the same bytes. This PR adds&& build.tag == nullto the:s3:gate to skip it on tag builds. A cleaner future refactor would move Android publish into the rocket step too, then "Build tags" can be turned off entirely — out of scope here.Changes
Fastfile
fastlane/Fastfile: Four new lanes —release(orchestrator: validate → rewrite → S3 → tag/Release),validate,update_swift_package,publish_release_to_github. The orchestrator re-invokesvalidateat the top so a future pipeline edit dropping thevalidate-releasedep can't silently bypass validation. GitHub API calls (get_github_release,set_github_release) take an explicitapi_tokenresolved fromENV['GITHUB_TOKEN']— no reliance onghCLI auth (theuse-bot-for-gitscript only setsGIT_SSH_COMMAND).validatecheckslane_context[GITHUB_API_STATUS_CODE]after the release probe so a swallowed 401/404 doesn't silently green-light the publish. Reuses the existingrewrite_resources_mode!,required_version!,xcframework_checksum,xcframework_file_pathhelpers added in ci(ios): publish + prune per-PR XCFramework snapshot branches #495.Pipeline
.buildkite/pipeline.yml: New:white_check_mark: Validate Swift releasestep (gated onNEW_VERSION) and:rocket: Publish Swift release $NEW_VERSIONstep (gated onNEW_VERSION+ trunk + non-PR), with the rocket step depending on validate plus the XCFramework build, swift package tests, lint, JS tests, and the iOS/web E2E suites. Existing:s3: Publish XCFramework to S3step now also gated onNEW_VERSION == null && build.tag == nullso the rocket step owns thevX.Y.Z/namespace and the tag-triggered re-build doesn't re-upload the same iOS artifact. Version arg simplified from${BUILDKITE_TAG:-$BUILDKITE_COMMIT}to$BUILDKITE_COMMITsince the step now never runs on a tag build..buildkite/release.sh(new): Sourcesuse-bot-for-git, downloads the xcframework + checksum artifacts frombuild-xcframework, runsbundle exec fastlane release version:$NEW_VERSION.GITHUB_TOKENfor the API calls comes from the agent environment.Release script
bin/release.sh: Droppedcreate_tag,create_github_release,is_prerelease(unused), and theghdependency check. Push is nowgit push origin trunk(no--tags). Newprint_publish_instructionsprints the Buildkite trigger steps and the just-pushed SHA after the trunk push.--dry-runnow prints the same instructions with a[DRY RUN]prefix so the operator gets a full preview of a real run.Behavior change: prereleases now get a GitHub Release
Previously,
bin/release.shskippedgh release createfor prereleases (anything with a-suffix) — the tag was pushed but no GH Release existed. The new:rocket:flow does create a GH Release for prereleases, marked as prerelease. This is intentional: every tag now has a corresponding Release page with the XCFramework + checksum attached, and consumers who previously pinned prerelease tags via Git revision can keep doing so.Docs
docs/releases.md: Rewrote into a Step 1 / Step 2 flow with a recovering-from-partial-publish section.docs/wordpress-app-integration.md: Updated two stale "make release creates the tag/GH release" lines.Test plan
Most of this can only be exercised end-to-end by cutting an actual release, so most of this would have to be tested post-merge.
Local validation (no CI run required)
Exercised locally against the PR branch:
bash -nonrelease.sh,bin/release.sh,publish-pr-xcframework.sh.ruby -c fastlane/Fastfile.pipeline.ymlYAML parses; everydepends_onreference resolves to a declaredkey:./\Av\d+\.\d+\.\d+(-.+)?\z/— 15 cases (validvX.Y.Z,vX.Y.Z-prerelease; rejects bare0.15.0,v0.15,v0.15.0.1, trailing whitespace, trailing-).rewrite_resources_mode!against a copy ofPackage.swift— correct rewrite, idempotent re-rewrite, errors on zero / multiple matches.git ls-remote --tags origin <tag>andgh release view <tag>againstv0.15.0(exists) andv999.999.999(doesn't) — both distinguish correctly.bin/release.sh patch --dry-runin a throwaway worktree ontrunk— new flow runs end-to-end: pre-flight → version bump → build → commit →git push origin trunk(no--tags) → "Version bump completed successfully!" with the new[DRY RUN]-prefixed Buildkite trigger instructions. OldCreating git tag/Creating GitHub releasesteps confirmed gone.:rocket:stepif:gating walked through 6 scenarios in Ruby — trunk +NEW_VERSIONtriggers; PR +NEW_VERSIONblocked; stale branch +NEW_VERSIONblocked.Pipeline-only checks (pre-merge, require a CI run)
NEW_VERSION) hits the existing:s3:step and uploads undergutenbergkit/<commit-sha>/, same as today.:rocket:step.Publish PR XCFramework, not the new:rocket:step.Release dry-run (against a throwaway version)
trunkwithNEW_VERSION=v0.0.0-test.0. Confirm::white_check_mark: Validate Swift release v0.0.0-test.0step runs and passes.:rocket: Publish Swift release v0.0.0-test.0step runs.gutenbergkit/v0.0.0-test.0/.v0.0.0-test.0is created on the remote.Package.swift:9reads.release(version: "v0.0.0-test.0", checksum: ...).v0.0.0-test.0is created, marked as prerelease, with the xcframework + checksum attached as assets.release/v0.0.0-test.0branch (or any other branch) is pushed to the remote — only the tag ref.:s3:step is skipped on the same build.:s3:(gated out bybuild.tag == null) but still runs:android: Publish Android Libraryand produces thev0.0.0-test.0Maven artifact. This is the regression test for the new tag-gate.swift package resolvedownloads the binary artifact from CDN; checksum validates.validatelane fails fast because the tag already exists.GITHUB_TOKEN→validateerrors out with the "GitHub API returned status … cannot determine whether it exists" message rather than silently passing.Cleanup after dry-run
Related
release/validate/update_swift_package/publish_release_to_githublanes.