Skip to content

Commit 61583ea

Browse files
authored
Add per-commit TestFlight builds for WordPress and Jetpack (#25674)
* Add per-commit TestFlight builds for WordPress, Jetpack, and Reader 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. * Drop Reader from the TestFlight matrix 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. * Gate TestFlight builds to trunk
1 parent d7fec87 commit 61583ea

3 files changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash -eu
2+
3+
# Builds one app and uploads it to TestFlight for internal testers.
4+
#
5+
# Part of the "Faster Releases for WordPress and Jetpack" RFC. CI runs one
6+
# invocation per app (in parallel) via the matrix step in pipeline.yml.
7+
8+
APP="${1:?Usage: build-and-upload-testflight.sh <wordpress|jetpack|reader>}"
9+
10+
"$(dirname "${BASH_SOURCE[0]}")/shared-set-up.sh"
11+
"$(dirname "${BASH_SOURCE[0]}")/shared-set-up-distribution.sh"
12+
13+
echo "--- :closed_lock_with_key: Installing Secrets"
14+
bundle exec fastlane run configure_apply
15+
16+
echo "--- :hammer_and_wrench: Building and uploading ${APP} to TestFlight"
17+
bundle exec fastlane build_and_upload_app_for_testflight app:"${APP}"

.buildkite/pipeline.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,32 @@ steps:
3131
- github_commit_status:
3232
context: "Jetpack Prototype Build"
3333

34+
#################
35+
# Continuous Delivery: build & upload each app to TestFlight (internal testers)
36+
#
37+
# Part of the "Faster Releases for WordPress and Jetpack" RFC. On every trunk
38+
# commit, each app builds and uploads to TestFlight in parallel via the matrix
39+
# below. Gated to trunk so it doesn't run on PR branches.
40+
#################
41+
- group: ":testflight: TestFlight Builds"
42+
key: testflight_builds_group
43+
steps:
44+
- label: ":testflight: Build & Upload {{matrix}}"
45+
command: ".buildkite/commands/build-and-upload-testflight.sh {{matrix}}"
46+
plugins: [$CI_TOOLKIT_PLUGIN]
47+
if: "build.branch == 'trunk'"
48+
matrix:
49+
- "wordpress"
50+
- "jetpack"
51+
# Reader is omitted: its App Store archive has been broken since #25321
52+
# (PostHelper moved to the app target without updating the Reader target),
53+
# and went undetected after #25179 removed Reader from CI. Re-add once
54+
# the Reader target archives again.
55+
# - "reader"
56+
notify:
57+
- github_commit_status:
58+
context: "TestFlight Build ({{matrix}})"
59+
3460
#################
3561
# Create Builds for Testing
3662
#################

fastlane/lanes/build.rb

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,116 @@
314314
end
315315
end
316316

317+
# Builds a single app and uploads it to TestFlight for *internal* testers,
318+
# stamping the build code with the Buildkite build number.
319+
#
320+
# This is the per-commit "continuous delivery" build from the "Faster Releases
321+
# for WordPress and Jetpack" RFC. It is intentionally additive: the existing
322+
# release lanes are untouched and remain the source of truth until this flow
323+
# is proven.
324+
#
325+
# The marketing version (`VERSION_SHORT`) is read from `Version.public.xcconfig`
326+
# as-is. The build code is `<marketing version>.0.<buildkite build number>`
327+
# (e.g. `27.0.0.4567`). Buildkite build numbers increase monotonically, so each
328+
# build for a given marketing version gets a unique, higher build code — which
329+
# is all App Store Connect requires.
330+
#
331+
# "Internal-only" means no external groups and no external-tester notifications
332+
# (matching the existing Reader upload). Distributing to the a8c staff beta
333+
# group, and separately to the public beta group, is wired up in later phases
334+
# of the RFC.
335+
#
336+
# @param [String] app One of `wordpress`, `jetpack`, or `reader`.
337+
#
338+
# @called_by CI
339+
#
340+
desc 'Builds one app and uploads it to TestFlight for internal testers'
341+
lane :build_and_upload_app_for_testflight do |app:|
342+
app = app.to_s.downcase
343+
344+
case app
345+
when 'wordpress'
346+
scheme = 'WordPress'
347+
output_name = APP_STORE_CONNECT_BUILD_NAME_WORDPRESS
348+
beta_app_description_path = WORDPRESS_BETA_APP_DESCRIPTION_PATH
349+
sentry_project_slug = SENTRY_PROJECT_SLUG_WORDPRESS
350+
app_identifier = WORDPRESS_BUNDLE_IDENTIFIER
351+
update_certs_and_profiles_wordpress_app_store
352+
when 'jetpack'
353+
scheme = 'Jetpack'
354+
output_name = APP_STORE_CONNECT_BUILD_NAME_JETPACK
355+
beta_app_description_path = JETPACK_BETA_APP_DESCRIPTION_PATH
356+
sentry_project_slug = SENTRY_PROJECT_SLUG_JETPACK
357+
app_identifier = JETPACK_BUNDLE_IDENTIFIER
358+
update_certs_and_profiles_jetpack_app_store
359+
when 'reader'
360+
scheme = 'Reader'
361+
output_name = APP_STORE_CONNECT_BUILD_NAME_READER
362+
beta_app_description_path = BETA_APP_DESCRIPTION_PATH_READER
363+
sentry_project_slug = nil # Reader does not have a Sentry project yet
364+
app_identifier = nil
365+
update_certs_and_profiles_app_store_reader
366+
else
367+
UI.user_error!("Unknown app '#{app}'. Expected one of: wordpress, jetpack, reader")
368+
end
369+
370+
build_number = ENV.fetch('BUILDKITE_BUILD_NUMBER', nil)
371+
UI.user_error!('BUILDKITE_BUILD_NUMBER is not set — this lane is meant to run on CI') if build_number.nil?
372+
373+
build_code = "#{release_version_current}.0.#{build_number}"
374+
UI.important("Building #{scheme} #{release_version_current} (#{build_code}) for internal TestFlight distribution")
375+
376+
sentry_check_cli_installed
377+
378+
build_app(
379+
scheme: scheme,
380+
workspace: WORKSPACE_PATH,
381+
clean: true,
382+
output_directory: BUILD_PRODUCTS_PATH,
383+
output_name: output_name,
384+
derived_data_path: DERIVED_DATA_PATH,
385+
export_team_id: get_required_env('EXT_EXPORT_TEAM_ID'),
386+
xcargs: { VERSION_LONG: build_code },
387+
export_options: { **COMMON_EXPORT_OPTIONS, method: 'app-store' }
388+
)
389+
390+
upload_app_to_testflight_internal(
391+
ipa_path: lane_context[SharedValues::IPA_OUTPUT_PATH],
392+
beta_app_description_path: beta_app_description_path
393+
)
394+
395+
# Upload symbols so crashes from these builds symbolicate in Sentry.
396+
next if sentry_project_slug.nil?
397+
398+
sentry_debug_files_upload(
399+
auth_token: get_required_env('SENTRY_AUTH_TOKEN'),
400+
org_slug: SENTRY_ORG_SLUG,
401+
project_slug: sentry_project_slug,
402+
path: lane_context[SharedValues::DSYM_OUTPUT_PATH]
403+
)
404+
405+
upload_gutenberg_sourcemaps(
406+
sentry_project_slug: sentry_project_slug,
407+
release_version: release_version_current,
408+
build_version: build_code,
409+
app_identifier: app_identifier
410+
)
411+
end
412+
413+
# Convenience wrapper that builds and uploads each app to TestFlight, one after
414+
# another. CI builds each app in parallel via a Buildkite matrix instead; this
415+
# lane is handy for running the whole set locally.
416+
#
417+
# Reader is omitted until its App Store archive is fixed (broken since #25321).
418+
# The per-app lane still supports `app: 'reader'`; just re-add it here once it builds.
419+
#
420+
desc 'Builds and uploads WordPress and Jetpack to TestFlight (internal)'
421+
lane :build_all_apps_for_testflight do
422+
%w[wordpress jetpack].each do |app|
423+
build_and_upload_app_for_testflight(app: app)
424+
end
425+
end
426+
317427
# Builds the WordPress app for a Prototype Build ("WordPress Alpha" scheme), and uploads it to Firebase App Distribution
318428
#
319429
# @called_by CI
@@ -426,6 +536,33 @@ def upload_build_to_testflight(ipa_path:, whats_new_path:, distribution_groups:,
426536
)
427537
end
428538

539+
# Uploads an already-built IPA to TestFlight for internal testers only.
540+
#
541+
# @param [String] ipa_path Path to the built IPA.
542+
# @param [String] beta_app_description_path Path to the beta app description file.
543+
#
544+
def upload_app_to_testflight_internal(ipa_path:, beta_app_description_path:)
545+
# TBD (RFC D4): the "what's new" text for per-commit internal builds is not
546+
# finalized yet. For now, generate a minimal placeholder from the commit
547+
# metadata so the upload has something to show. Public-beta builds will get
548+
# richer notes generated from the PRs merged since the previous public build.
549+
changelog = "Automated build from `#{ENV.fetch('BUILDKITE_BRANCH', 'unknown')}` (#{ENV.fetch('BUILDKITE_COMMIT', 'unknown')[0...7]})."
550+
whats_new_path = File.join(Dir.tmpdir, 'testflight_whats_new.txt')
551+
552+
begin
553+
File.write(whats_new_path, changelog)
554+
555+
upload_build_to_testflight(
556+
ipa_path: ipa_path,
557+
whats_new_path: whats_new_path,
558+
distribution_groups: [], # Internal-only for now (RFC D2): no external groups.
559+
beta_app_description_path: beta_app_description_path
560+
)
561+
ensure
562+
FileUtils.rm_rf(whats_new_path)
563+
end
564+
end
565+
429566
# Send a Slack message to the specified channel
430567
#
431568
# @param [String] message The message to send to the channel

0 commit comments

Comments
 (0)