mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
[BRE-768] Automate Google Play publishing (#5256)
Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
This commit is contained in:
parent
6454dc1a58
commit
f22643fec1
144
.github/workflows/publish-store.yml
vendored
144
.github/workflows/publish-store.yml
vendored
@ -1,16 +1,146 @@
|
||||
|
||||
name: Publish
|
||||
|
||||
name: Publish to Google Play
|
||||
run-name: "Promoting ${{ inputs.product }} ${{ inputs.version-code }} from ${{ inputs.track-from }} to ${{ inputs.track-target }}"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
product:
|
||||
description: "Which app is being released."
|
||||
type: choice
|
||||
options:
|
||||
- Password Manager
|
||||
- Authenticator
|
||||
version-name:
|
||||
description: "Version name to promote to production ex 2025.1.1"
|
||||
type: string
|
||||
version-code:
|
||||
description: "Build number to promote to production."
|
||||
required: true
|
||||
type: string
|
||||
rollout-percentage:
|
||||
description: "Percentage of users who will receive this version update."
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- 10%
|
||||
- 30%
|
||||
- 50%
|
||||
- 100%
|
||||
default: 10%
|
||||
release-notes:
|
||||
description: "Change notes to be included with this release."
|
||||
type: string
|
||||
default: "Bug fixes."
|
||||
required: true
|
||||
track-from:
|
||||
description: "Track to promote from."
|
||||
type: choice
|
||||
options:
|
||||
- internal
|
||||
- Fastlane Automation Source
|
||||
required: true
|
||||
default: "internal"
|
||||
track-target:
|
||||
description: "Track to promote to."
|
||||
type: choice
|
||||
options:
|
||||
- production
|
||||
- Fastlane Automation Target
|
||||
required: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_ACTION_RUN_URL: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
permissions: {}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
promote:
|
||||
runs-on: ubuntu-24.04
|
||||
name: Promote build to Production in Play Store
|
||||
|
||||
steps:
|
||||
- name: TEST STEP
|
||||
run: exit 0
|
||||
- name: Log inputs to job summary
|
||||
run: |
|
||||
echo "<details><summary>Job Inputs</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo '${{ toJson(inputs) }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install Fastlane
|
||||
run: |
|
||||
gem install bundler:2.2.27
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
env:
|
||||
ACCOUNT_NAME: bitwardenci
|
||||
CONTAINER_NAME: mobile
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/secrets
|
||||
mkdir -p ${{ github.workspace }}/app/src/standardRelease
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name play_creds.json --file ${{ github.workspace }}/secrets/play_creds.json --output none
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name authenticator_play_store-creds.json --file ${{ github.workspace }}/secrets/authenticator_play_store-creds.json --output none
|
||||
|
||||
- name: Format Release Notes
|
||||
run: |
|
||||
FORMATTED_MESSAGE="$(echo "${{ inputs.release-notes }}" | sed 's/ /\n/g')"
|
||||
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
|
||||
echo "$FORMATTED_MESSAGE" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
- name: Promote Play Store version to production
|
||||
env:
|
||||
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_BETA_KEYSTORE_PASSWORD }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_BETA_KEY_PASSWORD }}
|
||||
VERSION_CODE_INPUT: ${{ inputs.version-code }}
|
||||
VERSION_NAME: ${{inputs.version-name}}
|
||||
ROLLOUT_PERCENTAGE: ${{ inputs.rollout-percentage }}
|
||||
PRODUCT: ${{ inputs.product }}
|
||||
TRACK_FROM: ${{ inputs.track-from }}
|
||||
TRACK_TARGET: ${{ inputs.track-target }}
|
||||
run: |
|
||||
if [ "$PRODUCT" = "Password Manager" ]; then
|
||||
PACKAGE_NAME="com.x8bit.bitwarden"
|
||||
elif [ "$PRODUCT" = "Authenticator" ]; then
|
||||
PACKAGE_NAME="com.bitwarden.authenticator"
|
||||
else
|
||||
echo "Unsupported product: $PRODUCT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION_CODE=$(echo "${VERSION_CODE_INPUT}" | tr -d ',')
|
||||
|
||||
decimal=$(echo "scale=2; ${ROLLOUT_PERCENTAGE/\%/} / 100" | bc)
|
||||
|
||||
bundle exec fastlane updateReleaseNotes \
|
||||
releaseNotes:"$RELEASE_NOTES" \
|
||||
versionCode:"$VERSION_CODE"
|
||||
|
||||
bundle exec fastlane promoteToProduction \
|
||||
versionCode:"$VERSION_CODE" \
|
||||
versionName:"$VERSION_NAME" \
|
||||
rolloutPercentage:"$decimal" \
|
||||
packageName:"$PACKAGE_NAME" \
|
||||
releaseNotes:"$RELEASE_NOTES" \
|
||||
track:"$TRACK_FROM" \
|
||||
trackPromoteTo:"$TRACK_TARGET"
|
||||
|
||||
@ -13,6 +13,9 @@
|
||||
# Uncomment the line if you want fastlane to automatically update itself
|
||||
# update_fastlane
|
||||
|
||||
require_relative 'patches/supply_custom_promote_config'
|
||||
require_relative 'patches/supply_custom_promote'
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
@ -418,4 +421,74 @@ platform :android do
|
||||
mapping: "authenticator/build/outputs/mapping/release/mapping.txt",
|
||||
)
|
||||
end
|
||||
|
||||
desc "Retrieve build from Github releases"
|
||||
lane :retrieveBuildFromGithub do |options|
|
||||
|
||||
version_codes = Actions.lane_context[SharedValues::GOOGLE_PLAY_TRACK_VERSION_CODES]
|
||||
UI.message("Version codes in beta track: #{version_codes}")
|
||||
end
|
||||
|
||||
desc "Update release notes for all locales."
|
||||
lane :updateReleaseNotes do |options|
|
||||
changelog = options[:releaseNotes]
|
||||
version_code = options[:versionCode]
|
||||
|
||||
auth_locales = ["en-US"]
|
||||
pw_manager_locales = ["ca", "cs-CZ", "da-DK", "de-DE", "en-US", "es-ES", "et", "fr-FR", "hr", "hu-HU", "id", "it-IT", "iw-IL", "ja-JP", "nl-NL", "pl-PL", "pt-BR", "pt-PT", "ro", "ru-RU", "sk", "sv-SE", "tr-TR", "uk", "vi", "zh-CN", "zh-TW"]
|
||||
if options[:packageName] == "com.bitwarden.authenticator"
|
||||
locales = auth_locales
|
||||
else
|
||||
locales = pw_manager_locales
|
||||
end
|
||||
locales.each do |locale|
|
||||
dir = "metadata/android/#{locale}/changelogs"
|
||||
FileUtils.mkdir_p(dir)
|
||||
File.write("#{dir}/#{version_code}.txt", changelog)
|
||||
end
|
||||
end
|
||||
|
||||
desc "Promote to production."
|
||||
lane :promoteToProduction do |options|
|
||||
release_options = {
|
||||
package_name: options[:packageName],
|
||||
version_code: options[:versionCode].to_i,
|
||||
version_name: options[:versionName],
|
||||
track: options[:track],
|
||||
track_promote_to: options[:trackPromoteTo],
|
||||
skip_release_verification: true,
|
||||
skip_upload_apk: true,
|
||||
skip_upload_aab: true,
|
||||
}
|
||||
|
||||
if options[:releaseNotes].nil? or options[:releaseNotes].to_s.empty?
|
||||
release_options[:skip_upload_metadata] = true
|
||||
else
|
||||
release_options[:skip_upload_metadata] = false
|
||||
end
|
||||
|
||||
if options[:rolloutPercentage].to_f < 1
|
||||
release_options[:track_promote_release_status] = "inProgress"
|
||||
release_options[:rollout] = options[:rolloutPercentage]
|
||||
else
|
||||
release_options[:release_status] = "completed"
|
||||
end
|
||||
|
||||
begin
|
||||
UI.message("🚀 Starting release to #{options[:trackPromoteTo]}...")
|
||||
|
||||
supply(release_options)
|
||||
|
||||
rescue => error
|
||||
message = error.to_s
|
||||
|
||||
if message.include?("You cannot rollout this release because it does not allow any existing users to upgrade to the newly added APKs")
|
||||
UI.error("❌ App Store Error: Cannot rollout release because no existing users can upgrade to the new build.")
|
||||
UI.error("This might mean the version is older than what is currently available on the track, pick a newer build.")
|
||||
else
|
||||
UI.error("❌ Unexpected error during release: #{message}")
|
||||
end
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
97
fastlane/patches/supply_custom_promote.rb
Normal file
97
fastlane/patches/supply_custom_promote.rb
Normal file
@ -0,0 +1,97 @@
|
||||
# Patch Description:
|
||||
# Fixes issue where Fastlane 'Supply' doesn't recognize previous builds
|
||||
# when promoting to another track.
|
||||
#
|
||||
# Source: https://github.com/artsy/eigen/pull/10262
|
||||
# Author: Brian Beckerle (@brainbicycle)
|
||||
#
|
||||
|
||||
module Supply
|
||||
class Uploader
|
||||
alias_method :original_promote_track, :promote_track
|
||||
|
||||
def promote_track
|
||||
if Supply.config[:skip_release_verification]
|
||||
custom_promote_track
|
||||
else
|
||||
original_promote_track
|
||||
end
|
||||
end
|
||||
|
||||
def custom_promote_track
|
||||
UI.message("Using custom promotion logic")
|
||||
track_from = client.tracks(Supply.config[:track]).first
|
||||
unless track_from
|
||||
UI.user_error!("Cannot promote from track '#{Supply.config[:track]}' - track doesn't exist")
|
||||
end
|
||||
|
||||
releases = track_from.releases
|
||||
UI.message("Track contents: #{track_from.to_json}")
|
||||
|
||||
version_code = Supply.config[:version_code].to_s
|
||||
if !Supply.config[:skip_release_verification]
|
||||
if version_code != ""
|
||||
releases = releases.select do |release|
|
||||
release.version_codes.include?(version_code)
|
||||
end
|
||||
else
|
||||
releases = releases.select do |release|
|
||||
release.status == Supply.config[:release_status]
|
||||
end
|
||||
end
|
||||
|
||||
if releases.size == 0
|
||||
if version_code != ""
|
||||
UI.user_error!("Cannot find release with version code '#{version_code}' to promote in track '#{Supply.config[:track]}'")
|
||||
else
|
||||
UI.user_error!("Track '#{Supply.config[:track]}' doesn't have any releases")
|
||||
end
|
||||
elsif releases.size > 1
|
||||
UI.user_error!("Track '#{Supply.config[:track]}' has more than one release - use :version_code to filter the release to promote")
|
||||
end
|
||||
else
|
||||
UI.message("Skipping release verification as per configuration.")
|
||||
if version_code == ""
|
||||
UI.user_error!("Must provide a version code when release verification is skipped.")
|
||||
end
|
||||
if Supply.config[:version_name].nil?
|
||||
UI.user_error!("To force promote a :version_code, it is mandatory to enter the :version_name")
|
||||
end
|
||||
release = AndroidPublisher::TrackRelease.new(
|
||||
name: Supply.config[:version_name],
|
||||
version_codes: [version_code],
|
||||
status: Supply.config[:track_promote_release_status] || Supply::ReleaseStatus::COMPLETED
|
||||
)
|
||||
end
|
||||
|
||||
release = releases.first unless Supply.config[:skip_release_verification]
|
||||
track_to = client.tracks(Supply.config[:track_promote_to]).first || AndroidPublisher::Track.new(
|
||||
track: Supply.config[:track_promote_to],
|
||||
releases: []
|
||||
)
|
||||
|
||||
rollout = (Supply.config[:rollout] || 0).to_f
|
||||
if rollout > 0 && rollout < 1
|
||||
release.status = Supply::ReleaseStatus::IN_PROGRESS
|
||||
release.user_fraction = rollout
|
||||
else
|
||||
release.status = Supply.config[:track_promote_release_status]
|
||||
release.user_fraction = nil
|
||||
end
|
||||
|
||||
if track_to
|
||||
# Its okay to set releases to an array containing the newest release
|
||||
# Google Play will keep previous releases there this release is a partial rollout
|
||||
track_to.releases = [release]
|
||||
else
|
||||
track_to = AndroidPublisher::Track.new(
|
||||
track: Supply.config[:track_promote_to],
|
||||
releases: [release]
|
||||
)
|
||||
end
|
||||
|
||||
client.update_track(Supply.config[:track_promote_to], track_to)
|
||||
UI.message("confirmed that update_track was reached: #{Supply.config[:track_promote_to]} #{release}")
|
||||
end
|
||||
end
|
||||
end
|
||||
38
fastlane/patches/supply_custom_promote_config.rb
Normal file
38
fastlane/patches/supply_custom_promote_config.rb
Normal file
@ -0,0 +1,38 @@
|
||||
# Patch Description:
|
||||
# Fixes issue where Fastlane 'Supply' doesn't recognize previous builds
|
||||
# when promoting to another track.
|
||||
#
|
||||
# Source: https://github.com/artsy/eigen/pull/10262
|
||||
# Author: Brian Beckerle (@brainbicycle)
|
||||
#
|
||||
|
||||
module Supply
|
||||
class Options
|
||||
class << self
|
||||
alias_method :original_available_options, :available_options
|
||||
|
||||
def available_options
|
||||
original_options = original_available_options
|
||||
custom_options = [
|
||||
FastlaneCore::ConfigItem.new(
|
||||
key: :skip_release_verification,
|
||||
env_name: "SUPPLY_SKIP_RELEASE_VERIFICATION",
|
||||
description: "If set to true, skips checking if the version code exists in the track before promoting",
|
||||
type: Boolean,
|
||||
default_value: false,
|
||||
optional: true
|
||||
)
|
||||
]
|
||||
|
||||
# Only add custom options if they aren't already present
|
||||
custom_options.each do |custom_option|
|
||||
unless original_options.any? { |option| option.key == custom_option.key }
|
||||
original_options << custom_option
|
||||
end
|
||||
end
|
||||
|
||||
original_options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user