[PM-18414] CI restructuring #6 - New build workflows, build-all, ci-bwpm and ci-bwa (#1589)

This commit is contained in:
Álison Fernandes 2025-05-28 12:43:48 +01:00 committed by GitHub
parent 3aacd2538f
commit 9e3a1ca9b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1168 additions and 27 deletions

View File

@ -3,16 +3,268 @@ name: Build App
on:
workflow_call:
inputs:
environment:
description: "Environment"
bw-env:
description: "BW Environment"
type: string
build-mode:
description: "Build Mode"
type: string
version-name:
description: "Version Name Override - e.g. '2024.8.1'"
type: string
version-number:
description: "Version Number Override - e.g. '1021'"
type: string
xcode-version:
description: "Xcode Version Override - e.g. '15.2'"
type: string
compiler-flags:
description: "Compiler Flags - e.g. 'DEBUG_MENU FEATURE2'"
type: string
distribute:
description: "Distribute to TestFlight"
type: boolean
env:
_BW_ENV: ${{ inputs.bw-env || 'bwpm-prod' }}
_BUILD_VARIANT: ${{ inputs.bw-env == 'bwpm-prod' && 'Production' || 'Beta' }}
_BUILD_MODE: ${{ inputs.build-mode || 'Device' }}
_XCODE_VERSION: ${{ inputs.xcode-version }}
_VERSION_NAME: ${{ inputs.version-name }}
_VERSION_NUMBER: ${{ inputs.version-number }}
_COMPILER_FLAGS: ${{ inputs.compiler-flags }}
_GITHUB_ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}
_EXPORT_PATH: 'export'
jobs:
build:
name: Build ${{ inputs.environment }}
name: Build ${{ inputs.bw-env }}
runs-on: macos-15
permissions:
contents: read
env:
MINT_PATH: .mint/lib
MINT_LINK_PATH: .mint/bin
steps:
- name: Echo
- name: Log inputs to job summary
run: |
echo "placeholder"
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: Read Xcode version from file if not provided
run: |
if [ -z "$_XCODE_VERSION" ]; then
echo "_XCODE_VERSION=$(cat .xcode-version | tr -d '\n')" >> "$GITHUB_ENV"
fi
- name: Set Xcode version
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: ${{ env._XCODE_VERSION }}
- name: Configure Ruby
uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1.229.0
with:
bundler-cache: true
- name: Install Homebrew Dependencies and load environment variables
run: |
brew update
brew bundle
bundle exec fastlane load_dotenv_file --env $_BW_ENV
- name: Log in to Azure
if: env._BUILD_MODE == 'Device'
uses: Azure/login@cb79c773a3cfa27f31f25eb3f677781210c9ce3d # v1.6.1
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Setup secrets
if: env._BUILD_MODE == 'Device'
run: |
az_download() {
local container_name=$1
local az_filename=$2
local local_filename=$3
az storage blob download --account-name bitwardenci --container-name $container_name --name $az_filename --file $local_filename --output none --only-show-errors --no-progress
}
mkdir -p $HOME/secrets
profiles_dir_path="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$profiles_dir_path"
IFS=',' read -ra profiles <<< "$_PROVISIONING_PROFILES"
for FILE in "${profiles[@]}"
do
echo "⌛️ Downloading provisioning profile $FILE..."
local_profile_path=$HOME/secrets/$FILE
az_download profiles $FILE $local_profile_path
profile_uuid=$(grep UUID -A1 -a $local_profile_path | grep -io "[-A-F0-9]\{36\}")
cp $local_profile_path "$profiles_dir_path/$profile_uuid.mobileprovision"
done
echo "⌛️ Downloading Google-Services.plist..."
az_download mobile $_AZ_CRASHLYTICS_FILE_NAME $_CRASHLYTICS_PATH
if [[ "$_APP" == "password_manager" ]]; then
echo "⌛️ Downloading Google-Services.plist for watchOS..."
az_download mobile $_AZ_CRASHLYTICS_FILE_NAME "BitwardenWatchApp/GoogleService-Info.plist"
plutil -replace BUNDLE_ID -string '$BUNDLE_ID.watchkitapp' BitwardenWatchApp/GoogleService-Info.plist
fi
echo "⌛️ Downloading fastlane credentials..."
az_download mobile appstoreconnect-fastlane.json $HOME/secrets/appstoreconnect-fastlane.json
echo "⌛️ Downloading distribution certificate..."
mkdir -p $HOME/certificates
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/ios-distribution |
jq -r .value | base64 -d > $HOME/certificates/ios-distribution.p12
echo "✅ All secrets downloaded!"
- name: Configure Keychain Access
if: env._BUILD_MODE == 'Device'
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $HOME/certificates/ios-distribution.p12 -k build.keychain -P "" -T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
- name: Setup code files
run: |
bundle exec fastlane setup_code_files \
--env $_BW_ENV \
build_mode:$_BUILD_MODE \
version_name:$_VERSION_NAME \
version_number:$_VERSION_NUMBER \
bundle exec fastlane update_ci_build_info \
--env $_BW_ENV \
repository:$GITHUB_REPOSITORY \
branch:$GITHUB_REF_NAME \
commit_hash:$GITHUB_SHA \
ci_run_number:$GITHUB_RUN_ID \
ci_run_attempt:$GITHUB_RUN_ATTEMPT \
compiler_flags:"$_COMPILER_FLAGS"
- name: Cache Mint packages
id: mint-cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: .mint
key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }}
restore-keys: |
${{ runner.os }}-mint-
- name: Install Mint packages
if: steps.mint-cache.outputs.cache-hit != 'true'
run: |
mint bootstrap
- name: Build ${{ inputs.bw-env }}
run: |
./Scripts/build.sh $_BUILD_PROJECT_PATH $_BUILD_SCHEME $_BUILD_MODE
- name: Prepare artifacts for upload to GitHub
run: |
mkdir -p $_EXPORT_PATH
mkdir -p $_EXPORT_PATH/dSYMs
bundle exec fastlane post_build \
--env $_BW_ENV \
build_mode:$_BUILD_MODE \
export_path:$_EXPORT_PATH
- name: Get artifact name
id: get_file_paths
run: |
OUTPUT=$(bundle exec fastlane get_artifact_name \
--env $_BW_ENV \
build_mode:$_BUILD_MODE \
version_name:$_VERSION_NAME \
version_number:$_VERSION_NUMBER \
xcode_version:$_XCODE_VERSION \
export_path:$_EXPORT_PATH)
ARTIFACT_NAME=$(echo "$OUTPUT" | grep "artifact_filename: " | cut -d' ' -f3)
EXPORT_FILEPATH=$(echo "$OUTPUT" | grep "export_filepath: " | cut -d' ' -f3)
if [ -z "$ARTIFACT_NAME" ]; then
echo "::error::Failed to get artifact name"
exit 1
fi
if [ -z "$EXPORT_FILEPATH" ]; then
echo "::error::Failed to get export filepath"
exit 1
fi
echo "artifact_filename=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
echo "export_filepath=$EXPORT_FILEPATH" >> $GITHUB_OUTPUT
- name: Upload artifacts to GitHub
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: ${{ steps.get_file_paths.outputs.artifact_filename }}
path: ${{ env._EXPORT_PATH }}
if-no-files-found: error
- name: Set up private auth key
if: env._BUILD_MODE == 'Device'
run: |
mkdir ~/private_keys
cat << EOF > ~/private_keys/AuthKey_J46C83CB96.p8
${{ secrets.APP_STORE_CONNECT_AUTH_KEY }}
EOF
- name: Validate app with App Store Connect
if: env._BUILD_MODE == 'Device'
env:
_EXPORT_FILEPATH: ${{ steps.get_file_paths.outputs.export_filepath }}
run: |
xcrun altool --validate-app \
--type ios \
--file "$_EXPORT_FILEPATH" \
--apiKey "J46C83CB96" \
--apiIssuer "${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }}"
- name: Upload dSYM files to Crashlytics
if: ${{ env._BUILD_MODE == 'Device' && startsWith(env._BW_ENV, 'bwpm') }}
continue-on-error: true
run: |
find $_EXPORT_PATH/dSYMs -name "*.dSYM" \
-exec "build/DerivedData/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols" \
-gsp $_CRASHLYTICS_PATH \
-p ios -- {} +
- name: Upload app to TestFlight with Fastlane
if: ${{ inputs.distribute && env._BUILD_MODE == 'Device' }}
env:
_EXPORT_FILEPATH: ${{ steps.get_file_paths.outputs.export_filepath }}
run: |
CHANGELOG="$(git show -s --format=%s)
$GITHUB_REPOSITORY/$GITHUB_REF_NAME @ $GITHUB_SHA
Xcode $_XCODE_VERSION
Compiler Flags: $_COMPILER_FLAGS
$_GITHUB_ACTION_RUN_URL"
fastlane upload_build \
api_key_path:"$HOME/secrets/appstoreconnect-fastlane.json" \
changelog:"$CHANGELOG" \
ipa_path:"$_EXPORT_FILEPATH"

View File

@ -19,6 +19,34 @@ on:
skip_checkout:
description: "Skip checking out the repository"
type: boolean
workflow_call:
inputs:
base_version_number:
description: "Base Version Number - Will be added to the calculated version number"
type: number
default: 0
version_name:
description: "Version Name Override - e.g. '2024.8.1'"
type: string
version_number:
description: "Version Number Override - e.g. '1021'"
type: string
patch_version:
description: "Patch Version Override - e.g. '999'"
type: string
distinct_id:
description: "Unique ID for this dispatch, used by dispatch-and-download.yml"
type: string
skip_checkout:
description: "Skip checking out the repository"
type: boolean
outputs:
version_name:
description: "Version Name"
value: ${{ jobs.calculate-version.outputs.version_name }}
version_number:
description: "Version Number"
value: ${{ jobs.calculate-version.outputs.version_number }}
env:
BASE_VERSION_NUMBER: ${{ inputs.base_version_number || 0 }}
@ -29,6 +57,9 @@ jobs:
runs-on: ubuntu-22.04
permissions:
contents: read
outputs:
version_name: ${{ steps.calc-version-name.outputs.version_name }}
version_number: ${{ steps.calc-version-number.outputs.version_number }}
steps:
- name: Log inputs to job summary
run: |

View File

@ -405,6 +405,15 @@ jobs:
--apiKey "J46C83CB96" \
--apiIssuer "${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }}"
- name: Upload dSYM files to Crashlytics
if: ${{ env._BUILD_MODE == 'Device' }}
continue-on-error: true
run: |
find export/dSYMs -name "*.dSYM" \
-exec "build/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols" \
-gsp Bitwarden/Application/Support/GoogleService-Info.plist \
-p ios -- {} +
- name: Upload app to TestFlight with Fastlane
if: ${{ inputs.distribute && env._BUILD_MODE == 'Device' }}
run: |

View File

@ -1,15 +1,80 @@
name: CI / Authenticator
run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual - Authenticator ({0})', inputs.build-mode) || 'CI - Authenticator' }}
on:
workflow_dispatch:
inputs:
build-mode:
description: "Build Mode"
required: true
default: "Device"
type: choice
options:
- Device
- Simulator
version-name:
description: "Version Name Override - e.g. '2024.8.1'"
type: string
version-number:
description: "Version Number Override - e.g. '1021'"
type: string
compiler-flags:
description: "Compiler Flags - e.g. 'DEBUG_MENU FEATURE2'"
type: string
patch_version:
description: "Order 999 - Overrides Patch version"
type: boolean
distribute:
description: "Distribute to TestFlight"
type: boolean
default: true
xcode-version:
description: "Xcode Version Override - e.g. '15.2'"
type: string
permissions:
contents: read
jobs:
version:
name: Calculate Version Name and Number
uses: bitwarden/ios/.github/workflows/_version.yml@main
with:
base_version_number: 10
version_name: ${{ inputs.version-name }}
version_number: ${{ inputs.version-number }}
patch_version: ${{ inputs.patch_version && '999' || '' }}
secrets: inherit
build-manual:
name: Build Manual
name: Build Manual - ${{ inputs.build-mode }}
needs: version
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: bitwarden/ios/.github/workflows/_build-any.yml@main
with:
bw-env: bwa_prod
build-mode: ${{ inputs.build-mode }}
version-name: ${{ needs.version.outputs.version_name }}
version-number: ${{ needs.version.outputs.version_number }} #TODO: refactor all inputs to be consistent with - or _
compiler-flags: ${{ inputs.compiler-flags }}
distribute: ${{ inputs.distribute }}
secrets: inherit
build-public:
name: Build CI
needs: version
if: ${{ github.event_name == 'push' }}
uses: bitwarden/ios/.github/workflows/_build-any.yml@main
strategy:
matrix:
env: [bwa_prod]
include:
- bw-env: bwa_prod
build-mode: Device
- bw-env: bwa_prod
build-mode: Simulator
with:
environment: ${{ matrix.env }}
bw-env: ${{ matrix.bw-env }}
build-mode: ${{ matrix.build-mode }}
version-name: ${{ needs.version.outputs.version_name }}
version-number: ${{ needs.version.outputs.version_number }}
secrets: inherit

View File

@ -1,15 +1,92 @@
name: CI / Password Manager
run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual - Password Manager {0} ({1})', inputs.build-variant, inputs.build-mode) || 'CI - Password Manager' }}
on:
workflow_dispatch:
inputs:
build-variant:
description: "Build Variant"
required: true
default: "Beta"
type: choice
options:
- Beta
- Production
build-mode:
description: "Build Mode"
required: true
default: "Device"
type: choice
options:
- Device
- Simulator
version-name:
description: "Version Name Override - e.g. '2024.8.1'"
type: string
version-number:
description: "Version Number Override - e.g. '1021'"
type: string
compiler-flags:
description: "Compiler Flags - e.g. 'DEBUG_MENU FEATURE2'"
type: string
patch_version:
description: "Order 999 - Overrides Patch version"
type: boolean
distribute:
description: "Distribute to TestFlight"
type: boolean
default: true
xcode-version:
description: "Xcode Version Override - e.g. '15.2'"
type: string
permissions:
contents: read
jobs:
version:
name: Calculate Version Name and Number
uses: bitwarden/ios/.github/workflows/_version.yml@main
with:
base_version_number: 2100
version_name: ${{ inputs.version-name }}
version_number: ${{ inputs.version-number }}
patch_version: ${{ inputs.patch_version && '999' || '' }}
secrets: inherit
build-manual:
name: Build Manual
name: Build Manual - ${{ inputs.build-variant }} (${{ inputs.build-mode }})
needs: version
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: bitwarden/ios/.github/workflows/_build-any.yml@main
with:
bw-env: ${{ (inputs.build-variant == 'Production') && 'bwpm_prod' || 'bwpm_beta' }}
build-mode: ${{ inputs.build-mode }}
version-name: ${{ needs.version.outputs.version_name }}
version-number: ${{ needs.version.outputs.version_number }} #TODO: refactor all inputs to be consistent with - or _
compiler-flags: ${{ inputs.compiler-flags }}
distribute: ${{ inputs.distribute }}
secrets: inherit
build-public:
name: Build CI
needs: version
if: ${{ github.event_name == 'push' }}
uses: bitwarden/ios/.github/workflows/_build-any.yml@main
strategy:
matrix:
env: [bwpm_prod]
include:
- bw-env: bwpm_prod
build-mode: Device
- bw-env: bwpm_prod
build-mode: Simulator
- bw-env: bwpm_beta
build-mode: Device
compiler-flags: DEBUG_MENU
with:
environment: ${{ matrix.env }}
bw-env: ${{ matrix.bw-env }}
build-mode: ${{ matrix.build-mode }}
version-name: ${{ needs.version.outputs.version_name }}
version-number: ${{ needs.version.outputs.version_number }}
compiler-flags: ${{ matrix.compiler-flags }}
secrets: inherit

View File

@ -1 +1 @@
3.2.2
3.4.2

14
Gemfile
View File

@ -1,3 +1,17 @@
# frozen_string_literal: true
source "https://rubygems.org"
ruby file: '.ruby-version'
gem 'dotenv', groups: [:bwpm_prod, :bwpm_beta, :bwa_prod]
gem 'fastlane'
# Since ruby 3.4.0 these are not included in the standard library
gem 'abbrev'
gem 'logger'
gem 'mutex_m'
gem 'csv'
# Starting with Ruby 3.5.0, these are not included in the standard library
gem 'ostruct'

239
Gemfile.lock Normal file
View File

@ -0,0 +1,239 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.7)
base64
nkf
rexml
abbrev (0.1.2)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.2)
aws-partitions (1.1088.0)
aws-sdk-core (3.222.2)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.99.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.183.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
csv (3.3.4)
declarative (0.0.20)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.112.0)
faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.1.0)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.227.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.0.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.5.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.31.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
json (2.10.2)
jwt (2.10.1)
base64
logger (1.7.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.2.1)
nkf (0.2.0)
optparse (0.6.0)
os (1.1.4)
ostruct (0.6.1)
plist (3.7.2)
public_suffix (6.0.1)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.4.1)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unicode-display_width (2.6.0)
word_wrap (1.0.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.4.1)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
arm64-darwin-24
ruby
DEPENDENCIES
abbrev
csv
dotenv
fastlane
logger
mutex_m
ostruct
RUBY VERSION
ruby 3.4.2p28
BUNDLED WITH
2.6.8

17
fastlane/.env.bwa_prod Normal file
View File

@ -0,0 +1,17 @@
_APP="authenticator"
_BUILD_VARIANT=Production
_BUILD_SCHEME=Authenticator
_BUILD_PROJECT_PATH=project-bwa.yml
_PROVISIONING_PROFILES="
dist_authenticator.mobileprovision,
"
_PROVISIONING_PROFILE_PREFIX="Dist:"
_AZ_CRASHLYTICS_FILE_NAME=GoogleService-Info.plist
_CRASHLYTICS_PATH=Authenticator/Application/Support/GoogleService-Info.plist
_PLIST_EXPORT_COMPLIANCE_CODE=ecf076d3-4824-4d7b-b716-2a9a47d7d296
_BUNDLE_ID=com.bitwarden.authenticator
_GROUP_ID=group.com.bitwarden.bitwarden-authenticator
_APP_ICON=AppIcon
_APS_ENVIRONMENT_DEVICE=production
_APS_ENVIRONMENT_SIMULATOR=development
_LOCAL_XCCONFIG_PATH=Configs/Local-bwa.xcconfig

23
fastlane/.env.bwpm_beta Normal file
View File

@ -0,0 +1,23 @@
_APP="password_manager"
_BUILD_VARIANT=Beta
_BUILD_SCHEME=Bitwarden
_BUILD_PROJECT_PATH=project-pm.yml
_PROVISIONING_PROFILES="
dist_beta_autofill.mobileprovision,
dist_beta_bitwarden.mobileprovision,
dist_beta_extension.mobileprovision,
dist_beta_share_extension.mobileprovision,
dist_beta_bitwarden_watch_app.mobileprovision,
dist_beta_bitwarden_watch_app_extension.mobileprovision,
dist_beta_bitwarden_watch_widget_extension.mobileprovision
"
_PROVISIONING_PROFILE_PREFIX="Dist: Beta"
_AZ_CRASHLYTICS_FILE_NAME=GoogleService-Info-ios-pm-beta.plist
_CRASHLYTICS_PATH=Bitwarden/Application/Support/GoogleService-Info.plist
_PLIST_EXPORT_COMPLIANCE_CODE=3dd3e32f-efa6-4d99-b410-28aa28b1cb77
_BUNDLE_ID=com.8bit.bitwarden.beta
_GROUP_ID=group.com.bitwarden.bitwarden-authenticator.beta
_APP_ICON=AppIcon-Beta
_APS_ENVIRONMENT_DEVICE=production
_APS_ENVIRONMENT_SIMULATOR=development
_LOCAL_XCCONFIG_PATH=Configs/Local-bwpm.xcconfig

23
fastlane/.env.bwpm_prod Normal file
View File

@ -0,0 +1,23 @@
_APP="password_manager"
_BUILD_VARIANT=Production
_BUILD_SCHEME=Bitwarden
_BUILD_PROJECT_PATH=project-pm.yml
_PROVISIONING_PROFILES="
dist_autofill.mobileprovision,
dist_bitwarden.mobileprovision,
dist_extension.mobileprovision,
dist_share_extension.mobileprovision,
dist_bitwarden_watch_app.mobileprovision,
dist_bitwarden_watch_app_extension.mobileprovision,
dist_bitwarden_watch_widget_extension.mobileprovision
"
_PROVISIONING_PROFILE_PREFIX="Dist:"
_AZ_CRASHLYTICS_FILE_NAME=GoogleService-Info.plist
_CRASHLYTICS_PATH=Bitwarden/Application/Support/GoogleService-Info.plist
_PLIST_EXPORT_COMPLIANCE_CODE=ecf076d3-4824-4d7b-b716-2a9a47d7d296
_BUNDLE_ID=com.8bit.bitwarden
_GROUP_ID=group.com.bitwarden.bitwarden-authenticator
_APP_ICON=AppIcon
_APS_ENVIRONMENT_DEVICE=production
_APS_ENVIRONMENT_SIMULATOR=development
_LOCAL_XCCONFIG_PATH=Configs/Local-bwpm.xcconfig

View File

@ -1 +1 @@
app_identifier "com.8bit.bitwarden"
app_identifier ENV["BUNDLE_ID"] || "com.8bit.bitwarden"

View File

@ -10,25 +10,416 @@
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:ios)
APP_CONFIG_MAP = {
"password_manager" => {
project_filepath: "project-pm.yml",
build_ipa_path: "build/Bitwarden/Bitwarden.ipa",
build_dsyms_path: "build/Bitwarden.xcarchive/dSYMs",
build_app_path: "build/DerivedData/Build/Products/Debug-iphonesimulator/Bitwarden.app",
ci_build_info_filepath: "BitwardenShared/Core/Platform/Utilities/CIBuildInfo.swift",
},
"authenticator" => {
project_filepath: "project-bwa.yml",
build_ipa_path: "build/Authenticator/Authenticator.ipa",
build_dsyms_path: "build/Authenticator.xcarchive/dSYMs",
build_app_path: "build/DerivedData/Build/Products/Debug-iphonesimulator/Authenticator.app",
ci_build_info_filepath: "AuthenticatorShared/Core/Platform/Utilities/CIBuildInfo.swift",
},
}
ARTIFACT_EXTENSION = {
"simulator" => "app",
"device" => "ipa",
}
platform :ios do |options|
before_all do
ensure_env_vars(
env_vars: ['_APP']
)
app = ENV["_APP"]
lane_context[:APP] = app
lane_context[:APP_CONFIG] = APP_CONFIG_MAP[app]
end
lane :setup_code_files do |options|
if(lane_context[:APP] == 'password_manager')
update_plists options
end
create_config_files options
update_version_info options
end
lane :post_build do |options|
prepare_artifacts options
end
lane :get_artifact_name do |options|
required_options = [
:build_mode,
:version_name,
:version_number,
:xcode_version,
:export_path,
]
ensure_required_options(options, required_options)
ensure_env_vars(
env_vars: ['_BUNDLE_ID', '_BUILD_SCHEME']
)
build_mode = ensure_build_mode(options[:build_mode])
version_name = options[:version_name]
version_number = options[:version_number]
xcode_version = options[:xcode_version]
export_path = options[:export_path]
bundle_id = ENV["_BUNDLE_ID"]
build_scheme = ENV["_BUILD_SCHEME"]
ext = ARTIFACT_EXTENSION[build_mode]
app_config = lane_context[:APP_CONFIG]
UI.message "artifact_filename: #{bundle_id}-#{version_name}(#{version_number})-#{xcode_version}.#{ext}"
UI.message "export_filepath: #{export_path}/#{build_scheme}.#{ext}"
end
desc "Load .env variables (underscore prefixed) to GITHUB_ENV"
lane :load_dotenv_file do |options|
envfile = Dotenv.parse(".env.#{lane_context[:ENVIRONMENT]}")
envfile.each do |key, value|
value_cleaned = Shellwords.shellescape(value.to_s.delete(" \t\r\n"))
sh "echo #{key}='#{value_cleaned}' >> $GITHUB_ENV"
end
end
desc "Update CI build info"
lane :update_ci_build_info do |options|
required_options = [
:repository,
:branch,
:commit_hash,
:ci_run_number,
:ci_run_attempt,
]
ensure_required_options(options, required_options)
repository = options[:repository]
branch = options[:branch]
commit_hash = options[:commit_hash]
ci_run_number = options[:ci_run_number]
ci_run_attempt = options[:ci_run_attempt]
compiler_flags = options[:compiler_flags]
app_config = lane_context[:APP_CONFIG]
ci_build_info_filepath = app_config[:ci_build_info_filepath]
git_source = "#{repository}/#{branch}@#{commit_hash}"
ci_run_source = "#{repository}/actions/runs/#{ci_run_number}/attempts/#{ci_run_attempt}"
UI.message("🧱 Updating app CI Build info with:")
UI.message("🧱 commit: #{git_source}")
UI.message("💻 build source: #{ci_run_source}")
UI.message("🛠️ compiler flags: #{compiler_flags}")
Dir.chdir("..") do
FileUtils.mkdir_p(File.dirname(ci_build_info_filepath))
File.write(ci_build_info_filepath, <<~CONTENT)
enum CIBuildInfo {
static let info: KeyValuePairs<String, String> = [
"🧱 commit:": "#{git_source}",
"💻 build source:": "#{ci_run_source}",
"🛠️ compiler flags:": "#{compiler_flags}",
]
}
CONTENT
end
UI.success("🧱 Successfully updated app CI Build info")
end
desc "Push a new build to TestFlight"
lane :upload_build do |options|
upload_to_testflight(
skip_submission: false,
changelog: options[:changelog],
skip_waiting_for_build_processing: false,
api_key_path: options[:api_key_path],
ipa: options[:ipa_path],
localized_build_info: {
"default": {
whats_new: options[:changelog],
upload_to_testflight(
skip_submission: false,
changelog: options[:changelog],
skip_waiting_for_build_processing: false,
api_key_path: options[:api_key_path],
ipa: options[:ipa_path],
localized_build_info: {
"default": {
whats_new: options[:changelog],
}
}
}
)
)
end
desc "Update plists for Password Manager"
private_lane :update_plists do |options|
required_options = [
:build_mode
]
ensure_required_options(options, required_options)
ensure_env_vars(
env_vars: ['_BUNDLE_ID', '_PLIST_EXPORT_COMPLIANCE_CODE', '_APS_ENVIRONMENT_DEVICE', '_APS_ENVIRONMENT_SIMULATOR']
)
build_mode = ensure_build_mode(options[:build_mode])
update_plist(
plist_path: "Bitwarden/Application/Support/Info.plist",
block: proc do |plist|
plist["ITSEncryptionExportComplianceCode"] = ENV['_PLIST_EXPORT_COMPLIANCE_CODE']
end
)
UI.message("✅ Updated Info.plist with ITSEncryptionExportComplianceCode: #{ENV['_PLIST_EXPORT_COMPLIANCE_CODE']}")
aps_env = if(build_mode == 'device')
ENV['_APS_ENVIRONMENT_DEVICE']
else
ENV['_APS_ENVIRONMENT_SIMULATOR']
end
update_plist(
plist_path: "Bitwarden/Application/Support/Bitwarden.entitlements",
block: proc do |plist|
plist["aps-environment"] = aps_env
end
)
UI.message("✅ Updated Bitwarden.entitlements with aps-environment: #{aps_env}")
update_plist(
plist_path: "BitwardenWatchApp/GoogleService-Info.plist",
block: proc do |plist|
plist["BUNDLE_ID"] = ENV["_BUNDLE_ID"] + ".watchkitapp"
end
)
UI.message("✅ Updated GoogleService-Info.plist with BUNDLE_ID: #{ENV['_BUNDLE_ID']}.watchkitapp")
end
desc "Update version info in project yaml file"
private_lane :update_version_info do |options|
#require 'yaml'
required_options = [
:version_name,
:version_number,
]
ensure_required_options(options, required_options)
app_config = lane_context[:APP_CONFIG]
project_filepath = app_config[:project_filepath]
version_name = options[:version_name]
version_number = options[:version_number]
UI.message("Updating #{project_filepath} with version name: #{version_name} and version number: #{version_number}")
update_version_yq(project_filepath, version_name, version_number)
UI.success("Updated #{project_filepath} version to #{version_name}(#{version_number})")
end
desc "Create config files with build variant and compiler flags"
private_lane :create_config_files do |options|
compiler_flags = options[:compiler_flags] || ''
ensure_env_vars(
env_vars: ['_BUNDLE_ID', '_GROUP_ID', '_PROVISIONING_PROFILE_PREFIX', '_APP_ICON', '_LOCAL_XCCONFIG_PATH']
)
export_options_file = "Configs/export_options.plist"
local_xcconfig_file = ENV["_LOCAL_XCCONFIG_PATH"]
bundle_id = ENV["_BUNDLE_ID"]
shared_app_group_id = ENV["_GROUP_ID"]
profile_prefix = ENV["_PROVISIONING_PROFILE_PREFIX"]
app_icon = ENV["_APP_ICON"]
xcconfig_content = generate_xcconfig(lane_context[:APP], bundle_id, shared_app_group_id, app_icon, profile_prefix, compiler_flags)
export_options_plist_content = generate_export_options_plist(lane_context[:APP], bundle_id, profile_prefix)
Dir.chdir("..") do
FileUtils.mkdir_p(File.dirname(local_xcconfig_file))
FileUtils.mkdir_p(File.dirname(export_options_file))
File.write(local_xcconfig_file, xcconfig_content.map { |key, value| "#{key} = #{value}" }.join("\n"))
File.write(export_options_file, export_options_plist_content)
end
if compiler_flags.include?("SUPPORTS_CXP")
sh("./Scripts/alpha_update_cxp_infoplist.sh")
end
UI.success("Successfully created config files for #{bundle_id}")
end
def generate_xcconfig(app, bundle_id, shared_app_group_id, app_icon, profile_prefix, compiler_flags)
case app
when "password_manager"
generate_xcconfig_content_bwpm(bundle_id, shared_app_group_id, app_icon, profile_prefix, compiler_flags)
when "authenticator"
generate_xcconfig_content_bwa(bundle_id, shared_app_group_id, app_icon, profile_prefix, compiler_flags)
end
end
def generate_export_options_plist(app, bundle_id, profile_prefix)
case app
when "password_manager"
generate_export_options_plist_content_bwpm(bundle_id, profile_prefix)
when "authenticator"
generate_export_options_plist_content_bwa(bundle_id, profile_prefix)
end
end
def generate_xcconfig_content_bwpm(bundle_id, shared_app_group_id, app_icon, profile_prefix, compiler_flags)
{
"CODE_SIGN_STYLE" => "Manual",
"CODE_SIGN_IDENTITY" => "Apple Distribution",
"DEVELOPMENT_TEAM" => "LTZ2PFU5D6",
"ORGANIZATION_IDENTIFIER" => "com.8bit",
"BASE_BUNDLE_ID" => bundle_id,
"SHARED_APP_GROUP_IDENTIFIER" => shared_app_group_id,
"APPICON_NAME" => app_icon,
"PROVISIONING_PROFILE_SPECIFIER" => "#{profile_prefix} Bitwarden",
"PROVISIONING_PROFILE_SPECIFIER_ACTION_EXTENSION" => "#{profile_prefix} Extension",
"PROVISIONING_PROFILE_SPECIFIER_AUTOFILL_EXTENSION" => "#{profile_prefix} Autofill",
"PROVISIONING_PROFILE_SPECIFIER_SHARE_EXTENSION" => "#{profile_prefix} Share Extension",
"PROVISIONING_PROFILE_SPECIFIER_WATCH_APP" => "#{profile_prefix} Bitwarden Watch App",
"PROVISIONING_PROFILE_SPECIFIER_WATCH_WIDGET_EXTENSION" => "#{profile_prefix} Bitwarden Watch Widget Extension",
"SWIFT_ACTIVE_COMPILATION_CONDITIONS" => "$(inherited) #{compiler_flags}"
}
end
def generate_export_options_plist_content_bwpm(bundle_id, profile_prefix)
<<~PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>provisioningProfiles</key>
<dict>
<key>#{bundle_id}</key>
<string>#{profile_prefix} Bitwarden</string>
<key>#{bundle_id}.find-login-action-extension</key>
<string>#{profile_prefix} Extension</string>
<key>#{bundle_id}.autofill</key>
<string>#{profile_prefix} Autofill</string>
<key>#{bundle_id}.share-extension</key>
<string>#{profile_prefix} Share Extension</string>
<key>#{bundle_id}.watchkitapp</key>
<string>#{profile_prefix} Bitwarden Watch App</string>
<key>#{bundle_id}.watchkitapp.widget-extension</key>
<string>#{profile_prefix} Bitwarden Watch Widget Extension</string>
</dict>
<key>manageAppVersionAndBuildNumber</key>
<false/>
</dict>
</plist>
PLIST
end
def generate_xcconfig_content_bwa(bundle_id, shared_app_group_id, app_icon, profile_prefix, compiler_flags = "")
{
"CODE_SIGN_STYLE" => "Manual",
"CODE_SIGN_IDENTITY" => "Apple Distribution",
"DEVELOPMENT_TEAM" => "LTZ2PFU5D6",
"ORGANIZATION_IDENTIFIER" => "com.8bit",
"BASE_BUNDLE_ID" => bundle_id,
"SHARED_APP_GROUP_IDENTIFIER" => shared_app_group_id,
"APPICON_NAME" => app_icon,
"PROVISIONING_PROFILE_SPECIFIER" => "#{profile_prefix} Bitwarden Authenticator",
"SWIFT_ACTIVE_COMPILATION_CONDITIONS" => "$(inherited) #{compiler_flags}"
}
end
def generate_export_options_plist_content_bwa(bundle_id, profile_prefix)
<<~PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>provisioningProfiles</key>
<dict>
<key>#{bundle_id}</key>
<string>#{profile_prefix} Bitwarden Authenticator</string>
</dict>
<key>manageAppVersionAndBuildNumber</key>
<false/>
</dict>
</plist>
PLIST
end
desc "Prepare artifacts for upload to GitHub"
private_lane :prepare_artifacts do |options|
required_options = [
:build_mode,
:export_path
]
ensure_required_options(options, required_options)
build_mode = ensure_build_mode(options[:build_mode])
export_path = options[:export_path]
app_config = lane_context[:APP_CONFIG]
build_ipa_path = app_config[:build_ipa_path]
build_dsyms_path = app_config[:build_dsyms_path]
build_app_path = app_config[:build_app_path]
Dir.chdir("..") do
case build_mode
when 'simulator'
sh("cp -r #{build_app_path} #{export_path}")
when 'device'
sh("cp #{build_ipa_path} #{export_path}")
sh("cp -r #{build_dsyms_path}/*dSYM #{export_path}/dSYMs")
else
UI.user_error!("Invalid build mode: #{build_mode}")
end
end
end
def ensure_required_options(options, required_options)
missing_options = required_options.select { |option| options[option].nil? || options[option].empty? }
unless missing_options.empty?
UI.user_error!("Missing required options: #{missing_options.join(', ')}")
end
end
def ensure_build_mode(build_mode)
build_mode_lower = build_mode.downcase()
unless build_mode_lower == 'simulator' || build_mode_lower == 'device'
UI.user_error!("Invalid build mode: #{build_mode}")
end
return build_mode_lower
end
def parse_env_list(env_value)
env_value.to_s.delete(" \t\r\n").split(',')
end
def update_version_yq(project_filepath, version_name, version_number)
Dir.chdir("..") do
sh("yq -i '.settings.MARKETING_VERSION = \"#{version_name}\"' '#{project_filepath}'")
sh("yq -i '.settings.CURRENT_PROJECT_VERSION = \"#{version_number}\"' '#{project_filepath}'")
end
end
def update_version_yaml(project_filepath, version_name, version_number)
project_yaml = YAML.load_file(project_filepath)
project_yaml['settings']['MARKETING_VERSION'] = version_name
project_yaml['settings']['CURRENT_PROJECT_VERSION'] = version_number
File.open(project_filepath, 'w') do |file|
file.write(project_yaml.to_yaml)
end
end
end