Add per-commit TestFlight builds for WordPress and Jetpack#25674
Add per-commit TestFlight builds for WordPress and Jetpack#25674jkmassel wants to merge 4 commits into
Conversation
Phase 1 of the "Faster Releases" RFC: every CI build produces a TestFlight build for all three apps, using the Buildkite build number as the build code. - `build_and_upload_app_for_testflight(app:)` builds one app and uploads it to TestFlight for internal testers only. - `.buildkite/commands/build-and-upload-testflight.sh` runs it, fanned out per app via a Buildkite matrix step. - Build code = `<VERSION_SHORT>.0.<BUILDKITE_BUILD_NUMBER>`. Intentionally not gated to trunk yet so the flow can be exercised in CI. The existing release pipeline is untouched.
|
| App Name | WordPress | |
| Configuration | Release-Alpha | |
| Build Number | 32841 | |
| Version | PR #25674 | |
| Bundle ID | org.wordpress.alpha | |
| Commit | d97c3bb | |
| Installation URL | 0r87hvudpq6qo |
|
| App Name | Jetpack | |
| Configuration | Release-Alpha | |
| Build Number | 32841 | |
| Version | PR #25674 | |
| Bundle ID | com.jetpack.alpha | |
| Commit | d97c3bb | |
| Installation URL | 23beve1h2pl10 |
Reader's App Store archive has been broken since #25321 (PostHelper moved to the app target without updating the Reader target), and went undetected after #25179 removed Reader from CI. WordPress and Jetpack build and upload fine. Reader is commented out of the matrix and the local wrapper; the per-app lane still supports `app: 'reader'`, so re-enabling is a one-line change once the Reader target archives again.
| # Builds one app and uploads it to TestFlight for internal testers. | ||
| # | ||
| # Part of the "Faster Releases for WordPress and Jetpack" RFC. CI runs one | ||
| # invocation per app (in parallel) via the matrix step in pipeline.yml. | ||
|
|
There was a problem hiding this comment.
I really dislike these AI comments that carry context that's only relevant while a certain project is ongoing.
| # Builds one app and uploads it to TestFlight for internal testers. | |
| # | |
| # Part of the "Faster Releases for WordPress and Jetpack" RFC. CI runs one | |
| # invocation per app (in parallel) via the matrix step in pipeline.yml. |
| echo "--- :closed_lock_with_key: Installing Secrets" | ||
| bundle exec fastlane run configure_apply | ||
|
|
||
| echo "--- :hammer_and_wrench: Building and uploading ${APP} to TestFlight" |
There was a problem hiding this comment.
Emoji game ++
| echo "--- :hammer_and_wrench: Building and uploading ${APP} to TestFlight" | |
| echo "--- :testflight: Building and uploading ${APP} to TestFlight" |
| ################# | ||
| # Continuous Delivery: build & upload each app to TestFlight (internal testers) | ||
| # | ||
| # Part of the "Faster Releases for WordPress and Jetpack" RFC. On every trunk | ||
| # commit, each app builds and uploads to TestFlight in parallel via the matrix | ||
| # below. Gated to trunk so it doesn't run on PR branches. | ||
| ################# |
There was a problem hiding this comment.
| ################# | |
| # Continuous Delivery: build & upload each app to TestFlight (internal testers) | |
| # | |
| # Part of the "Faster Releases for WordPress and Jetpack" RFC. On every trunk | |
| # commit, each app builds and uploads to TestFlight in parallel via the matrix | |
| # below. Gated to trunk so it doesn't run on PR branches. | |
| ################# | |
| ################# | |
| # Continuous Delivery: build & upload each app to TestFlight (internal testers) from trunk | |
| ################# |
| # Reader is omitted: its App Store archive has been broken since #25321 | ||
| # (PostHelper moved to the app target without updating the Reader target), | ||
| # and went undetected after #25179 removed Reader from CI. Re-add once | ||
| # the Reader target archives again. | ||
| # - "reader" |
There was a problem hiding this comment.
Fair enough. But I guess the technical issues are downstream of Reader not being in the radar for release at the moment. If there was an higher-level issue on this topic, it'd be better to link that, I think. Just an idea, though.
| # This is the per-commit "continuous delivery" build from the "Faster Releases | ||
| # for WordPress and Jetpack" RFC. It is intentionally additive: the existing | ||
| # release lanes are untouched and remain the source of truth until this flow | ||
| # is proven. | ||
| # |
There was a problem hiding this comment.
| # This is the per-commit "continuous delivery" build from the "Faster Releases | |
| # for WordPress and Jetpack" RFC. It is intentionally additive: the existing | |
| # release lanes are untouched and remain the source of truth until this flow | |
| # is proven. | |
| # |
| # The marketing version (`VERSION_SHORT`) is read from `Version.public.xcconfig` | ||
| # as-is. The build code is `<marketing version>.0.<buildkite build number>` | ||
| # (e.g. `27.0.0.4567`). Buildkite build numbers increase monotonically, so each | ||
| # build for a given marketing version gets a unique, higher build code — which | ||
| # is all App Store Connect requires. |
There was a problem hiding this comment.
Sounds good. Other apps implement similar strategies relying on Buildkite.
| # | ||
| # "Internal-only" means no external groups and no external-tester notifications | ||
| # (matching the existing Reader upload). Distributing to the a8c staff beta | ||
| # group, and separately to the public beta group, is wired up in later phases | ||
| # of the RFC. |
There was a problem hiding this comment.
| # | |
| # "Internal-only" means no external groups and no external-tester notifications | |
| # (matching the existing Reader upload). Distributing to the a8c staff beta | |
| # group, and separately to the public beta group, is wired up in later phases | |
| # of the RFC. |
| scheme = 'WordPress' | ||
| output_name = APP_STORE_CONNECT_BUILD_NAME_WORDPRESS | ||
| beta_app_description_path = WORDPRESS_BETA_APP_DESCRIPTION_PATH | ||
| sentry_project_slug = SENTRY_PROJECT_SLUG_WORDPRESS | ||
| app_identifier = WORDPRESS_BUNDLE_IDENTIFIER | ||
| update_certs_and_profiles_wordpress_app_store | ||
| when 'jetpack' | ||
| scheme = 'Jetpack' | ||
| output_name = APP_STORE_CONNECT_BUILD_NAME_JETPACK | ||
| beta_app_description_path = JETPACK_BETA_APP_DESCRIPTION_PATH | ||
| sentry_project_slug = SENTRY_PROJECT_SLUG_JETPACK | ||
| app_identifier = JETPACK_BUNDLE_IDENTIFIER | ||
| update_certs_and_profiles_jetpack_app_store | ||
| when 'reader' | ||
| scheme = 'Reader' | ||
| output_name = APP_STORE_CONNECT_BUILD_NAME_READER | ||
| beta_app_description_path = BETA_APP_DESCRIPTION_PATH_READER | ||
| sentry_project_slug = nil # Reader does not have a Sentry project yet | ||
| app_identifier = nil | ||
| update_certs_and_profiles_app_store_reader |
There was a problem hiding this comment.
update_certs_and_profiles_[wordpress|jetpack]_app_store vs update_certs_and_profiles_app_store_reader
Will be addressed, eventually, in https://linear.app/a8c/issue/AINFRA-2556
| app_identifier = nil | ||
| update_certs_and_profiles_app_store_reader | ||
| else | ||
| UI.user_error!("Unknown app '#{app}'. Expected one of: wordpress, jetpack, reader") |
There was a problem hiding this comment.
Nitpick: this could be reimplemented with something like
options = {
wordpress: {
scheme: '...',
...
code_signing_lambda: # I don't remember how to write lambdas...
},
jetpack: ...
}Then the check could be like
app_to_build = app.to_s.downcase.to_sym
app_to_build_options = options[app_to_build]
UI.user_error!("Unknown app '#{app_to_build}'. Expected one of: #{options.keys.map { |k| k.to_s }.join(', ')}") unless app_to_buildAnyway, this is just a syntax nitpick. It jumped to my mind because of the hardcoded list of accepted options. Might not even be worth implementing given how static the list of targets is anyway. Very low chances of having another app any time soon.
| sentry_check_cli_installed | ||
|
|
There was a problem hiding this comment.
Should we move this to the very top to fail early?
| beta_app_description_path: beta_app_description_path | ||
| ) | ||
|
|
||
| # Upload symbols so crashes from these builds symbolicate in Sentry. |
There was a problem hiding this comment.
| # Upload symbols so crashes from these builds symbolicate in Sentry. |
| # Convenience wrapper that builds and uploads each app to TestFlight, one after | ||
| # another. CI builds each app in parallel via a Buildkite matrix instead; this | ||
| # lane is handy for running the whole set locally. | ||
| # | ||
| # Reader is omitted until its App Store archive is fixed (broken since #25321). | ||
| # The per-app lane still supports `app: 'reader'`; just re-add it here once it builds. | ||
| # | ||
| desc 'Builds and uploads WordPress and Jetpack to TestFlight (internal)' | ||
| lane :build_all_apps_for_testflight do | ||
| %w[wordpress jetpack].each do |app| | ||
| build_and_upload_app_for_testflight(app: app) | ||
| end | ||
| end |
There was a problem hiding this comment.
I debate this given we should never build apps from devs machine. But... never say never, right?
If we keep it, then I'd suggest a visible message regarding Reader not being part of the set, which is against what the name suggests.
| # Convenience wrapper that builds and uploads each app to TestFlight, one after | |
| # another. CI builds each app in parallel via a Buildkite matrix instead; this | |
| # lane is handy for running the whole set locally. | |
| # | |
| # Reader is omitted until its App Store archive is fixed (broken since #25321). | |
| # The per-app lane still supports `app: 'reader'`; just re-add it here once it builds. | |
| # | |
| desc 'Builds and uploads WordPress and Jetpack to TestFlight (internal)' | |
| lane :build_all_apps_for_testflight do | |
| %w[wordpress jetpack].each do |app| | |
| build_and_upload_app_for_testflight(app: app) | |
| end | |
| end | |
| # Convenience wrapper that builds and uploads each app to TestFlight, one after | |
| # another. CI builds each app in parallel via a Buildkite matrix instead; this | |
| # lane is handy for running the whole set locally. | |
| # | |
| # Reader is omitted until its App Store archive is fixed (broken since #25321). | |
| # The per-app lane still supports `app: 'reader'`; just re-add it here once it builds. | |
| # | |
| desc 'Builds and uploads WordPress and Jetpack to TestFlight (internal)' | |
| lane :build_all_apps_for_testflight do | |
| apps_to_build = %[wordpress jetpack] | |
| UI.important("Will only build #{apps_to_build}.join('and '). Reader is omitted because currently broken.") | |
| apps_to_build.each do |app| | |
| build_and_upload_app_for_testflight(app: app) | |
| end | |
| end |
| # TBD (RFC D4): the "what's new" text for per-commit internal builds is not | ||
| # finalized yet. For now, generate a minimal placeholder from the commit | ||
| # metadata so the upload has something to show. Public-beta builds will get | ||
| # richer notes generated from the PRs merged since the previous public build. |
There was a problem hiding this comment.
| # TBD (RFC D4): the "what's new" text for per-commit internal builds is not | |
| # finalized yet. For now, generate a minimal placeholder from the commit | |
| # metadata so the upload has something to show. Public-beta builds will get | |
| # richer notes generated from the PRs merged since the previous public build. | |
| # TODO: the "what's new" text for per-commit internal builds is not | |
| # finalized yet. For now, generate a minimal placeholder from the commit | |
| # metadata so the upload has something to show. Public-beta builds will get | |
| # richer notes generated from the PRs merged since the previous public build. |
As far as I know, TBD is not a common "tag" for looking up leftover work.
| # finalized yet. For now, generate a minimal placeholder from the commit | ||
| # metadata so the upload has something to show. Public-beta builds will get | ||
| # richer notes generated from the PRs merged since the previous public build. | ||
| changelog = "Automated build from `#{ENV.fetch('BUILDKITE_BRANCH', 'unknown')}` (#{ENV.fetch('BUILDKITE_COMMIT', 'unknown')[0...7]})." |
There was a problem hiding this comment.
| changelog = "Automated build from `#{ENV.fetch('BUILDKITE_BRANCH', 'unknown')}` (#{ENV.fetch('BUILDKITE_COMMIT', 'unknown')[0...7]})." | |
| changelog = "Automated build from `#{ENV.fetch('BUILDKITE_BRANCH', 'unknown branch')}` (#{ENV.fetch('BUILDKITE_COMMIT', 'unknown commit')[0...7]})." |
| upload_build_to_testflight( | ||
| ipa_path: ipa_path, | ||
| whats_new_path: whats_new_path, | ||
| distribution_groups: [], # Internal-only for now (RFC D2): no external groups. |
There was a problem hiding this comment.
| distribution_groups: [], # Internal-only for now (RFC D2): no external groups. | |
| distribution_groups: [], |
| ensure | ||
| FileUtils.rm_rf(whats_new_path) | ||
| end |
There was a problem hiding this comment.
Have you considered using the Dir.mktmpdir with a block, so that we don't have to manually manage the rm?
mokagio
left a comment
There was a problem hiding this comment.
Only nitpicks. Approving to unblock.


Description
Phase 1 of the Faster Releases RFC: make every trunk commit produce a TestFlight build for WordPress and Jetpack, so that "releasing" can later become "submit a build that's already been in TestFlight for days."
This is additive — the existing code-freeze / beta / finalize release pipeline is untouched and remains the source of truth until this flow is proven.
What's here
build_and_upload_app_for_testflight(app:)(fastlane/lanes/build.rb) — builds one app for the App Store and uploads it to TestFlight for internal testers only (distribution_groups: [], no external notify). Build code =<VERSION_SHORT>.0.<BUILDKITE_BUILD_NUMBER>viaxcargs, the same mechanism Reader and prototype builds already use. Keeps the Sentry dSYM + Gutenberg sourcemap uploads for WP/JP..buildkite/commands/build-and-upload-testflight.sh— per-app entry point, mirrors the existingrelease-build-*.shsetup..buildkite/pipeline.yml) — fans out overwordpress/jetpackin parallel, gated totrunk.Reader is omitted
The lane supports
app: 'reader', but Reader is left out of the CI matrix (and thebuild_all_apps_for_testflightwrapper). Reader's App Store archive has been broken since #25321 (PostHelpermoved to the app target without updating the Reader target), which went undetected after #25179 removed Reader from CI. Re-add it — a one-line change in both places — once the Reader target archives again.Decisions (from the RFC discussion)
VERSION_SHORTleft as-is; the versioning / pre-allocation strategy is deferred to go-live.Gated to trunk
The matrix step runs only on
trunk(if: "build.branch == 'trunk'"), so it won't run on this or any other PR branch — each merge to trunk produces the internal TestFlight builds. The flow was validated end-to-end on this branch before gating: build #32723 built and uploaded both WordPress and Jetpack to TestFlight.Testing instructions
Because the matrix is gated to
trunk, the TestFlight steps don't run on this PR. They were validated on build #32723 (the last commit before gating), where the WordPress and Jetpack jobs each built and uploaded successfully.After merge, on the next trunk build:
<VERSION_SHORT>.0.<build number>and no external testers notified.