Skip to content

Add per-commit TestFlight builds for WordPress and Jetpack#25674

Open
jkmassel wants to merge 4 commits into
trunkfrom
jkmassel/tf-internal-upload-on-merge
Open

Add per-commit TestFlight builds for WordPress and Jetpack#25674
jkmassel wants to merge 4 commits into
trunkfrom
jkmassel/tf-internal-upload-on-merge

Conversation

@jkmassel

@jkmassel jkmassel commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

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> via xcargs, 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 existing release-build-*.sh setup.
  • Buildkite matrix step (.buildkite/pipeline.yml) — fans out over wordpress / jetpack in parallel, gated to trunk.

Reader is omitted

The lane supports app: 'reader', but Reader is left out of the CI matrix (and the build_all_apps_for_testflight wrapper). Reader's App Store archive has been broken since #25321 (PostHelper moved 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)

  • Build code from the Buildkite number — unique per marketing version and monotonic, which is all App Store Connect requires.
  • Internal-only distribution for now; the a8c-staff and public-beta groups arrive in later phases.
  • VERSION_SHORT left as-is; the versioning / pre-allocation strategy is deferred to go-live.
  • "What's new" is a TBD placeholder (commit metadata) for now.

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:

  • Watch the TestFlight Builds matrix group — the WordPress and Jetpack jobs should each build and upload.
  • Confirm each build appears in App Store Connect → TestFlight → (app) → internal testers, with build code <VERSION_SHORT>.0.<build number> and no external testers notified.

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.
@wpmobilebot

wpmobilebot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number32841
VersionPR #25674
Bundle IDorg.wordpress.alpha
Commitd97c3bb
Installation URL0r87hvudpq6qo
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number32841
VersionPR #25674
Bundle IDcom.jetpack.alpha
Commitd97c3bb
Installation URL23beve1h2pl10
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

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.
@jkmassel jkmassel changed the title Add per-commit TestFlight builds for WordPress, Jetpack, and Reader Add per-commit TestFlight builds for WordPress and Jetpack Jun 25, 2026
@jkmassel jkmassel marked this pull request as ready for review June 25, 2026 21:45
@jkmassel jkmassel requested a review from a team as a code owner June 25, 2026 21:45
@jkmassel jkmassel added the Tooling Build, Release, and Validation Tools label Jun 25, 2026
@jkmassel jkmassel self-assigned this Jun 25, 2026
@jkmassel jkmassel requested a review from oguzkocer June 25, 2026 21:45
@jkmassel jkmassel added this to the 27.1 milestone Jun 25, 2026
@wordpress-mobile wordpress-mobile deleted a comment from wpmobilebot Jun 25, 2026
Comment on lines +3 to +7
# 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really dislike these AI comments that carry context that's only relevant while a certain project is ongoing.

Suggested change
# 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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emoji game ++

Suggested change
echo "--- :hammer_and_wrench: Building and uploading ${APP} to TestFlight"
echo "--- :testflight: Building and uploading ${APP} to TestFlight"

Comment thread .buildkite/pipeline.yml
Comment on lines +34 to +40
#################
# 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.
#################

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#################
# 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
#################

Comment thread .buildkite/pipeline.yml
Comment on lines +51 to +55
# 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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread fastlane/lanes/build.rb
Comment on lines +320 to +324
# 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.
#

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# 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.
#

Comment thread fastlane/lanes/build.rb
Comment on lines +325 to +329
# 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Other apps implement similar strategies relying on Buildkite.

Comment thread fastlane/lanes/build.rb
Comment on lines +330 to +334
#
# "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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#
# "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.

Comment thread fastlane/lanes/build.rb
Comment on lines +346 to +365
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread fastlane/lanes/build.rb
app_identifier = nil
update_certs_and_profiles_app_store_reader
else
UI.user_error!("Unknown app '#{app}'. Expected one of: wordpress, jetpack, reader")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_build

Anyway, 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.

Comment thread fastlane/lanes/build.rb
Comment on lines +376 to +377
sentry_check_cli_installed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move this to the very top to fail early?

Comment thread fastlane/lanes/build.rb
beta_app_description_path: beta_app_description_path
)

# Upload symbols so crashes from these builds symbolicate in Sentry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Upload symbols so crashes from these builds symbolicate in Sentry.

Comment thread fastlane/lanes/build.rb
Comment on lines +413 to +425
# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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

Comment thread fastlane/lanes/build.rb
Comment on lines +545 to +548
# 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# 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.

Comment thread fastlane/lanes/build.rb
# 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]})."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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]})."

Comment thread fastlane/lanes/build.rb
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
distribution_groups: [], # Internal-only for now (RFC D2): no external groups.
distribution_groups: [],

Comment thread fastlane/lanes/build.rb
Comment on lines +561 to +563
ensure
FileUtils.rm_rf(whats_new_path)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered using the Dir.mktmpdir with a block, so that we don't have to manually manage the rm?

@mokagio mokagio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only nitpicks. Approving to unblock.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Tooling Build, Release, and Validation Tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants