Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tooling] Migrate Prototype Builds from App Center to Firebase App Distribution #24199

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ gem 'dotenv'
# See failures like https://buildkite.com/automattic/wordpress-ios/builds/24053#019234f2-80a5-40f6-b55e-2f420e6483a8/3840-3915
# and https://github.com/fastlane/fastlane/pull/22256
gem 'fastlane', '~> 2.227'
gem 'fastlane-plugin-appcenter', '~> 2.1'
gem 'fastlane-plugin-firebase_app_distribution', '~> 0.10'
gem 'fastlane-plugin-sentry'
# This comment avoids typing to switch to a development version for testing.
#
# gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', ref: ''
gem 'fastlane-plugin-wpmreleasetoolkit', '~> 12.5'
gem 'fastlane-plugin-wpmreleasetoolkit', '~> 13.0'
gem 'rake'
gem 'rubocop', '~> 1.74'
gem 'rubocop-rake', '~> 0.7'
Expand Down
18 changes: 11 additions & 7 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ GEM
base64
nkf
rexml
activesupport (8.0.1)
activesupport (8.0.2)
base64
benchmark (>= 0.3)
bigdecimal
Expand Down Expand Up @@ -60,7 +60,6 @@ GEM
connection_pool (2.5.0)
cork (0.3.0)
colored2 (~> 3.1)
csv (3.3.2)
danger (9.5.1)
base64 (~> 0.2)
claide (~> 1.0)
Expand Down Expand Up @@ -167,11 +166,12 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-appcenter (2.1.3)
csv
fastlane-plugin-firebase_app_distribution (0.10.0)
google-apis-firebaseappdistribution_v1 (~> 0.3.0)
google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
fastlane-plugin-sentry (1.28.0)
os (~> 1.1, >= 1.1.4)
fastlane-plugin-wpmreleasetoolkit (12.5.0)
fastlane-plugin-wpmreleasetoolkit (13.0.0)
activesupport (>= 6.1.7.1)
buildkit (~> 1.5)
chroma (= 0.2.0)
Expand Down Expand Up @@ -204,6 +204,10 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-apis-firebaseappdistribution_v1 (0.3.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-firebaseappdistribution_v1alpha (0.2.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
Expand Down Expand Up @@ -365,9 +369,9 @@ DEPENDENCIES
danger-dangermattic (~> 1.2)
dotenv
fastlane (~> 2.227)
fastlane-plugin-appcenter (~> 2.1)
fastlane-plugin-firebase_app_distribution (~> 0.10)
fastlane-plugin-sentry
fastlane-plugin-wpmreleasetoolkit (~> 12.5)
fastlane-plugin-wpmreleasetoolkit (~> 13.0)
rake
rmagick (~> 6.1.1)
rubocop (~> 1.74)
Expand Down
1 change: 0 additions & 1 deletion WordPress/Credentials/Secrets-example.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class ApiCredentials: NSObject {
// Other Services
static let tenorApiKey = ""
static let sentryDSN = ""
static let appCenterAppId = ""
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Once this PR lands, we might want to remove that constant from the actual Secrets-*.swift files in ~/.mobile-secrets (see .configure setup), as from a quick grep in the source code, this constant doesn't seem to be used anywhere in the codebase anymore.

static let encryptedLogKey = ""
static let debuggingKey = ""
static let docsBotId = ""
Expand Down
2 changes: 1 addition & 1 deletion docs/feature-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ case localDeveloper // debug
case a8cBranchTest // alpha

/// Beta released internally for Automattic employees
case a8cPrereleaseTesting. // internal - AppCenter / TestFlight
case a8cPrereleaseTesting. // internal - Firebase App Distribution / TestFlight

/// Production build released in the app store
case appStore // release
Expand Down
6 changes: 6 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,9 @@ end
def editorial_branch_name(version: release_version_current)
"release_notes/#{version}"
end

def pull_request_number
# Buildkite sets this env var to the PR number if on a PR, but to 'false' (and not nil) if not on a PR
pr_num = ENV.fetch('BUILDKITE_PULL_REQUEST', 'false')
pr_num == 'false' ? nil : Integer(pr_num)
end
2 changes: 0 additions & 2 deletions fastlane/env/project.env-example
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
INT_EXPORT_TEAM_ID=<Team id for internal distribution>
EXT_EXPORT_TEAM_ID=<Team id for public distribution>

APPCENTER_PUBLIC_ID=<AppCenter Public Id>

Comment on lines -4 to -5
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similarly, once this PR lands, we might want to remove that env var value from the actual project.env file in ~/.mobile-secrets (see .configure setup), as it doesn't seem used anymore either.

FASTLANE_ITC_TEAM_ID=<Team ID for AppStore Connect>

SENTRY_ORG_SLUG=<Org Slug for Sentry>
Expand Down
1 change: 0 additions & 1 deletion fastlane/env/user.env-example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ FASTLANE_USER=<Your Apple ID for fastlane>
DELIVER_USER=<Your Apple ID for fastlane>

GHHELPER_ACCESS=<GitHub access token>
APPCENTER_API_TOKEN=<AppCenter Api Token>
SENTRY_AUTH_TOKEN=<Sentry Auth Token>

BUILDKITE_TOKEN=<Buildkite Personal Access Token>
162 changes: 61 additions & 101 deletions fastlane/lanes/build.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
# frozen_string_literal: true

# Sentry
SENTRY_ORG_SLUG = 'a8c'
SENTRY_PROJECT_SLUG_WORDPRESS = 'wordpress-ios'
SENTRY_PROJECT_SLUG_JETPACK = 'jetpack-ios'
APPCENTER_OWNER_NAME = 'automattic'
APPCENTER_OWNER_TYPE = 'organization'

# Prototype Builds in Firebase App Distribution
PROTOTYPE_BUILD_XCODE_CONFIGURATION = 'Release-Alpha'
FIREBASE_APP_CONFIG_WORDPRESS = {
app_name: 'WordPress',
app_icon: ':wordpress:', # Use Buildkite emoji
app_id: '1:124902176124:ios:ff9714d0b53aac821620f9',
testers_group: 'wordpress-ios---prototype-builds'
}.freeze
FIREBASE_APP_CONFIG_JETPACK = {
app_name: 'Jetpack',
app_icon: ':jetpack:', # Use Buildkite emoji
app_id: '1:124902176124:ios:121c494b82f283ec1620f9',
testers_group: 'jetpack-ios---prototype-builds'
}.freeze

CONCURRENT_SIMULATORS = 2

# Shared options to use when invoking `build_app` (`gym`).
Expand Down Expand Up @@ -251,7 +266,7 @@
)
end

# Builds the WordPress app for a Prototype Build ("WordPress Alpha" scheme), and uploads it to App Center
# Builds the WordPress app for a Prototype Build ("WordPress Alpha" scheme), and uploads it to Firebase App Distribution
#
# @called_by CI
#
Expand All @@ -264,14 +279,13 @@
build_and_upload_prototype_build(
scheme: 'WordPress Alpha',
output_app_name: 'WordPress Alpha',
appcenter_app_name: 'WPiOS-One-Offs',
app_icon: ':wordpress:', # Use Buildkite emoji
firebase_app_config: FIREBASE_APP_CONFIG_WORDPRESS,
sentry_project_slug: SENTRY_PROJECT_SLUG_WORDPRESS,
app_identifier: 'org.wordpress.alpha'
)
end

# Builds the Jetpack app for a Prototype Build ("Jetpack" scheme), and uploads it to App Center
# Builds the Jetpack app for a Prototype Build ("Jetpack" scheme), and uploads it to Firebase App Distribution
#
# @called_by CI
#
Expand All @@ -284,8 +298,7 @@
build_and_upload_prototype_build(
scheme: 'Jetpack',
output_app_name: 'Jetpack Alpha',
appcenter_app_name: 'jetpack-installable-builds',
app_icon: ':jetpack:', # Use Buildkite emoji
firebase_app_config: FIREBASE_APP_CONFIG_JETPACK,
sentry_project_slug: SENTRY_PROJECT_SLUG_JETPACK,
app_identifier: 'com.jetpack.alpha'
)
Expand All @@ -306,49 +319,19 @@
# Helper Functions
#################################################


# Generates a build number for Prototype Builds, based on the PR number and short commit SHA1
# Builds a Prototype Build for WordPress or Jetpack, then uploads it to Firebase App Distribution and comment with a link to it on the PR.
#
# @note This function uses Buildkite-specific ENV vars
#
def generate_prototype_build_number
if ENV['BUILDKITE']
commit = ENV.fetch('BUILDKITE_COMMIT', nil)[0, 7]
branch = ENV.fetch('BUILDKITE_BRANCH', nil)
pr_num = ENV.fetch('BUILDKITE_PULL_REQUEST', nil)

pr_num == 'false' ? "#{branch}-#{commit}" : "pr#{pr_num}-#{commit}"
else
repo = Git.open(PROJECT_ROOT_FOLDER)
commit = repo.current_branch
branch = repo.revparse('HEAD')[0, 7]

"#{branch}-#{commit}"
end
end

# Builds a Prototype Build for WordPress or Jetpack, then uploads it to App Center and comment with a link to it on the PR.
#
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/ParameterLists
def build_and_upload_prototype_build(scheme:, output_app_name:, appcenter_app_name:, app_icon:, sentry_project_slug:, app_identifier:)
configuration = 'Release-Alpha'

# Get the current build version, and update it if needed
version_config_path = File.join(PROJECT_ROOT_FOLDER, 'config', 'Version.public.xcconfig')
versions = Xcodeproj::Config.new(File.new(version_config_path)).to_hash
build_number = generate_prototype_build_number
UI.message("Updating build version to #{build_number}")
versions['VERSION_LONG'] = build_number
new_config = Xcodeproj::Config.new(versions)
new_config.save_as(Pathname.new(version_config_path))
def build_and_upload_prototype_build(scheme:, output_app_name:, firebase_app_config:, sentry_project_slug:, app_identifier:)
build_number = ENV.fetch('BUILDKITE_BUILD_NUMBER', '0')
pr_or_branch = pull_request_number&.then { |num| "PR ##{num}" } || ENV.fetch('BUILDKITE_BRANCH', nil)

# Build
build_app(
scheme: scheme,
workspace: WORKSPACE_PATH,
configuration: configuration,
configuration: PROTOTYPE_BUILD_XCODE_CONFIGURATION,
clean: true,
xcargs: { VERSION_LONG: build_number, VERSION_SHORT: pr_or_branch }.compact,
output_directory: BUILD_PRODUCTS_PATH,
output_name: output_app_name,
derived_data_path: DERIVED_DATA_PATH,
Expand All @@ -357,21 +340,8 @@ def build_and_upload_prototype_build(scheme:, output_app_name:, appcenter_app_na
export_options: { **COMMON_EXPORT_OPTIONS, method: 'enterprise' }
)

# Upload to App Center
commit = ENV.fetch('BUILDKITE_COMMIT', 'Unknown')
pr = ENV.fetch('BUILDKITE_PULL_REQUEST', nil)
release_notes = <<~NOTES
- Branch: `#{ENV.fetch('BUILDKITE_BRANCH', 'Unknown')}`\n
- Commit: [#{commit[0...7]}](https://github.com/#{GITHUB_REPO}/commit/#{commit})\n
- Pull Request: [##{pr}](https://github.com/#{GITHUB_REPO}/pull/#{pr})\n
NOTES

upload_build_to_app_center(
name: appcenter_app_name,
file: lane_context[SharedValues::IPA_OUTPUT_PATH],
dsym: lane_context[SharedValues::DSYM_OUTPUT_PATH],
release_notes: release_notes,
distribute_to_everyone: false
upload_build_to_firebase_app_distribution(
firebase_app_config: firebase_app_config
)

# Upload dSYMs to Sentry
Expand All @@ -388,33 +358,7 @@ def build_and_upload_prototype_build(scheme:, output_app_name:, appcenter_app_na
build_version: build_number,
app_identifier: app_identifier
)

# Post PR Comment
comment_body = prototype_build_details_comment(
app_display_name: output_app_name,
app_icon: app_icon,
app_center_org_name: APPCENTER_OWNER_NAME,
metadata: { Configuration: configuration },
fold: true
)

comment_on_pr(
project: GITHUB_REPO,
pr_number: Integer(ENV.fetch('BUILDKITE_PULL_REQUEST', nil)),
reuse_identifier: "prototype-build-link-#{appcenter_app_name}",
body: comment_body
)

# Attach version information as Buildkite metadata and annotation
appcenter_id = lane_context.dig(SharedValues::APPCENTER_BUILD_INFORMATION, 'id')
metadata = versions.merge(build_type: 'Prototype', 'appcenter:id': appcenter_id)
buildkite_metadata(set: metadata)
appcenter_install_url = "https://install.appcenter.ms/orgs/#{APPCENTER_OWNER_NAME}/apps/#{appcenter_app_name}/releases/#{appcenter_id}"
list = metadata.map { |k, v| " - **#{k}**: #{v}" }.join("\n")
buildkite_annotate(context: "appcenter-info-#{output_app_name}", style: 'info', message: "#{output_app_name} [App Center Build](#{appcenter_install_url}) Info:\n\n#{list}")
Comment on lines -408 to -414
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure how useful this in fact is, but I was wondering if anyone would miss the Buildkite annotation / metadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, in some repos I've kept them, in others I've removed them… not sure about it tbh.

Though this might also depend if people use the new Build UI from Buildkite or the classic one, given how annotations are shown a bit differently on the 2 UIs, and are more discreet on the new UI… which could either be in favor of keeping them (they won't hurt and not take extra space anyway) or removing them (who is going to check the annotations instead of clicking on the link to the GitHub PR to see the link to those in the PR comment anyway?

Ultimately I considered those were not that useful, especially for Prototype Builds (as keeping them would mean every single CI build would have an annotation, since every single commit generates a Prototype Build… for little benefit IMHO).

If I'm not mistaken, ultimately the only place/repos for which I kept those annotations are for Alpha and Betas builds, and only for products which are sending those to FAD (which to this day is only the case for Tumblr iOS & Android), given those alpha/beta builds are not attached to a PR so it felt useful to have the FAD link as annotation for those.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ultimately I considered those were not that useful, especially for Prototype Builds

Makes sense 👍

only the case for Tumblr iOS & Android, given those alpha/beta builds are not attached to a PR so it felt useful to have the FAD link as annotation for those

Indeed 👍

end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/ParameterLists

def inject_buildkite_analytics_environment(xctestrun_path:)
require 'plist'
Expand Down Expand Up @@ -467,23 +411,39 @@ def send_slack_message(message:, channel: '#build-and-ship')
)
end

def upload_build_to_app_center(
name:,
file:,
dsym:,
release_notes:,
distribute_to_everyone:
)
appcenter_upload(
api_token: get_required_env('APPCENTER_API_TOKEN'),
owner_name: APPCENTER_OWNER_NAME,
owner_type: APPCENTER_OWNER_TYPE,
app_name: name,
file: file,
dsym: dsym,
# Uploads a build to Firebase App Distribution and post the corresponding PR comment
#
# @param [Hash<Symbol, String>] firebase_app_config A hash with the app name as the key and the Firebase app ID and testers group as the value
# Typically one of FIREBASE_APP_CONFIG_WORDPRESS or FIREBASE_APP_CONFIG_JETPACK
#
def upload_build_to_firebase_app_distribution(firebase_app_config:)
release_notes = <<~NOTES
Pull Request: ##{pull_request_number || 'N/A'}
Branch: `#{ENV.fetch('BUILDKITE_BRANCH', 'N/A')}`
Commit: #{ENV.fetch('BUILDKITE_COMMIT', 'N/A')[0...7]}
NOTES

firebase_app_distribution(
app: firebase_app_config[:app_id],
service_credentials_json_data: get_required_env('FIREBASE_APP_DISTRIBUTION_ACCOUNT_KEY'),
release_notes: release_notes,
destinations: distribute_to_everyone ? '*' : 'Collaborators',
notify_testers: false
groups: firebase_app_config[:testers_group]
)

return if pull_request_number.nil?

# PR Comment
comment_body = prototype_build_details_comment(
app_display_name: firebase_app_config[:app_name],
app_icon: firebase_app_config[:app_icon],
metadata: { Configuration: PROTOTYPE_BUILD_XCODE_CONFIGURATION },
fold: true
)
comment_on_pr(
project: GITHUB_REPO,
pr_number: pull_request_number,
reuse_identifier: "prototype-build-link-#{firebase_app_config[:app_id]}",
body: comment_body
)
end

Expand Down