From 8b3f67680a48f4a80203632dd23b254f9428d087 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com> Date: Wed, 8 May 2024 23:27:16 -0400 Subject: [PATCH] PM-7156: Publish Play Store and F-Droid artifacts (#1273) --- .github/workflows/build.yml | 603 ++++++++++++++++++ .github/workflows/run-check.yml | 17 +- .gitignore | 14 +- Gemfile | 5 + Gemfile.lock | 215 ++++++- README.md | 5 + app/build.gradle.kts | 8 + app/proguard-rules.pro | 8 + .../beta/res/values/strings_non_localized.xml | 4 + app/src/standard/google-services.json | 73 --- app/src/standardDebug/google-services.json | 40 ++ fastlane/Appfile | 2 + fastlane/Fastfile | 208 ++++++ fastlane/Pluginfile | 5 + 14 files changed, 1114 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 app/src/beta/res/values/strings_non_localized.xml delete mode 100644 app/src/standard/google-services.json create mode 100644 app/src/standardDebug/google-services.json create mode 100644 fastlane/Appfile create mode 100644 fastlane/Fastfile create mode 100644 fastlane/Pluginfile diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..fabcaed24 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,603 @@ +--- +name: Main Build + +on: + workflow_dispatch: + inputs: + version-name: + description: 'Optional. Version string to use, in X.Y.Z format. Overrides default in the project.' + required: false + type: string + version-code: + description: 'Optional. Build number to use. Overrides default of GitHub run number.' + required: false + type: number + distribute-to-firebase: + description: 'Optional. Distribute artifacts to Firebase.' + required: false + default: false + type: boolean + publish-to-play-store: + description: 'Optional. Deploy bundle artifact to Google Play Store' + required: false + default: false + type: boolean + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JAVA_VERSION: 17 + +jobs: + + # Job responsible for compiling the project and running checks + build: + name: Build + runs-on: ubuntu-22.04 + steps: + + # Checkout project. + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + # Allow subsequent steps to trigger GitHub Actions via git push + # https://github.community/t/push-from-action-even-with-pat-does-not-trigger-action/17622 + persist-credentials: false + + # Validate the gradle wrapper is known and not corrupted. + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1 + + # Configure gradle caching + - name: Cache Gradle Files + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }} + restore-keys: | + ${{ runner.os }}-gradle-v2- + + # Configure build output caching + - name: Cache build output + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: | + ${{ github.workspace }}/build-cache + key: ${{ runner.os }}-build-cache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build- + + # Set the default JDK version. + - name: Configure JDK + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + + # Install required Ruby language version. + - name: Configure Ruby + uses: ruby/setup-ruby@1198b074305f9356bd56dd4b311757cc0dab2f1c # v1.175.1 + with: + bundler-cache: true + + # Install fastlane CLI and dependencies. + - name: Install Fastlane + run: | + gem install bundler:2.2.27 + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + + # Perform build checks, which include code analysis and test execution. + - name: Check + run: bundle exec fastlane test + + # Compile project debug artifacts. + - name: Build + run: bundle exec fastlane assembleDebugApks + + publish_playstore: + name: Publish PlayStore Artifacts + needs: + - build + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + variant: [ "prod", "qa" ] + artifact: [ "apk", "aab" ] + steps: + + # Checkout project + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + # Allow subsequent steps to trigger GitHub Actions via git push + # https://github.community/t/push-from-action-even-with-pat-does-not-trigger-action/17622 + persist-credentials: false + + # Install Ruby + - name: Configure Ruby + uses: ruby/setup-ruby@1198b074305f9356bd56dd4b311757cc0dab2f1c # v1.175.1 + with: + bundler-cache: true + + # Install Fastlane + - name: Install Fastlane + run: | + gem install bundler:2.2.27 + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + + # Login to Azure + - name: Login to Azure - CI Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + # Download keys and secrets needed for signing release and beta builds + - name: Download secrets + env: + ACCOUNT_NAME: bitwardenci + CONTAINER_NAME: mobile + run: | + mkdir -p ${{ github.workspace }}/secrets + mkdir -p ${{ github.workspace }}/app/src/standardBeta + mkdir -p ${{ github.workspace }}/app/src/standardRelease + + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name app_play-keystore.jks --file ${{ github.workspace }}/keystores/app_play-keystore.jks --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name app_upload-keystore.jks --file ${{ github.workspace }}/keystores/app_upload-keystore.jks --output none + 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 app_beta_play-keystore.jks --file ${{ github.workspace }}/keystores/app_beta_play-keystore.jks --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name app_beta_upload-keystore.jks --file ${{ github.workspace }}/keystores/app_beta_upload-keystore.jks --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name google-services.json --file ${{ github.workspace }}/app/src/standardRelease/google-services.json --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name google-services.json --file ${{ github.workspace }}/app/src/standardBeta/google-services.json --output none + shell: bash + + # Download Firebase credentials for publishing to App Distribution on 'prod' variants. + - name: Download Firebase Credentials + if: ${{ matrix.variant == 'prod' && inputs.distribute-to-firebase }} + env: + ACCOUNT_NAME: bitwardenci + CONTAINER_NAME: mobile + run: | + mkdir -p ${{ github.workspace }}/secrets + + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name app_play_prod_firebase-creds.json --file ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json --output none + shell: bash + + # Validate the gradle wrapper is known and not corrupted. + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1 + + # Configure gradle caching + - name: Cache Gradle Files + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }} + restore-keys: | + ${{ runner.os }}-gradle-v2- + + # Configure build output caching + - name: Cache build output + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: | + ${{ github.workspace }}/build-cache + key: ${{ runner.os }}-build-cache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build- + + # Set default JDK + - name: Configure JDK + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + + # Set the latest build version information. Start from 11000 to prevent collisions with mobile + # build version codes. + - name: Increment version + run: | + DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER)) + bundle exec fastlane setBuildVersionInfo \ + versionCode:${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }} \ + versionName:${{ inputs.version-name }} + shell: bash + + # Generate the Release Play Store Bundle (AAB) when building 'prod' variants. + - name: Generate Release Play Store Bundle + if: ${{ matrix.variant == 'prod' && matrix.artifact == 'aab' }} + env: + UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }} + run: | + bundle exec fastlane bundlePlayStoreRelease \ + storeFile:app_upload-keystore.jks \ + storePassword:${{ env.UPLOAD_KEYSTORE_PASSWORD }} \ + keyAlias:upload \ + keyPassword:${{ env.UPLOAD_KEYSTORE_PASSWORD }} + shell: bash + + # Generate the Beta Play Store Bundle (AAB) when building 'prod' variants. + - name: Generate Beta Play Store Bundle + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }} + env: + UPLOAD_BETA_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_BETA_KEYSTORE_PASSWORD }} + UPLOAD_BETA_KEY_PASSWORD: ${{ secrets.UPLOAD_BETA_KEY_PASSWORD }} + run: | + bundle exec fastlane bundlePlayStoreBeta \ + storeFile:app_beta_upload-keystore.jks \ + storePassword:${{ env.UPLOAD_BETA_KEYSTORE_PASSWORD }} \ + keyAlias:bitwarden-beta-upload \ + keyPassword:${{ env.UPLOAD_BETA_KEY_PASSWORD }} + shell: bash + + # Generate the Release Play Store APK when building 'prod' variants. + - name: Generate Release Play Store APK + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }} + env: + PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }} + run: | + bundle exec fastlane assemblePlayStoreReleaseApk \ + storeFile:app_play-keystore.jks \ + storePassword:${{ env.PLAY_KEYSTORE_PASSWORD }} \ + keyAlias:bitwarden \ + keyPassword:${{ env.PLAY_KEYSTORE_PASSWORD }} + shell: bash + + # Generate the Beta Play Store APK when building 'prod' variants. + - name: Generate Beta Play Store APK + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }} + env: + PLAY_BETA_KEYSTORE_PASSWORD: ${{ secrets.PLAY_BETA_KEYSTORE_PASSWORD }} + PLAY_BETA_KEY_PASSWORD: ${{ secrets.PLAY_BETA_KEY_PASSWORD }} + run: | + bundle exec fastlane assemblePlayStoreBetaApk \ + storeFile:app_beta_play-keystore.jks \ + storePassword:${{ env.PLAY_BETA_KEYSTORE_PASSWORD }} \ + keyAlias:bitwarden-beta \ + keyPassword:${{ env.PLAY_BETA_KEY_PASSWORD }} + shell: bash + + # Generate debug version of the Play Store APKs when building variants other than 'prod'. + - name: Generate PlayStore QA APKs + if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }} + run: | + bundle exec fastlane assembleDebugApks + + # Upload Release Play Store AAB to GitHub workflow artifacts when building 'prod' variants. + - name: Upload Release Play Store .aab artifact + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: com.x8bit.bitwarden.aab + path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab + if-no-files-found: error + + # Upload Play Store Beta AAB to GitHub workflow artifacts when building 'prod' variants. + - name: Upload Beta Play Store .aab artifact + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: com.x8bit.bitwarden.beta.aab + path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden-standard-beta.aab + if-no-files-found: error + + # Upload Play Store Release APK to GitHub workflow artifacts when building 'prod' variants. + - name: Upload Release .apk artifact + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: com.x8bit.bitwarden.apk + path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk + if-no-files-found: error + + # Upload Play Store Beta APK to GitHub workflow artifacts when building 'prod' variants. + - name: Upload Beta .apk artifact + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: com.x8bit.bitwarden.beta.apk + path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk + if-no-files-found: error + + # Upload Play Store debug APK to GitHub workflow artifacts when building variants other than 'prod'. + - name: Upload Other .apk artifact + if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: com.x8bit.bitwarden-${{ matrix.variant }}.apk + path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk + if-no-files-found: error + + # Generate SHA256 file for Release Play Store APK when building 'prod' variants. + - name: Create checksum for Release .apk artifact + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }} + run: | + sha256sum "app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk" \ + > ./bw-android-apk-sha256.txt + + # Generate SHA256 file for Beta Play Store APK when building 'prod' variants. + - name: Create checksum for Beta .apk artifact + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }} + run: | + sha256sum "app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk" \ + > ./bw-android-beta-apk-sha256.txt + + # Generate SHA256 file for Release Play Store bundle (AAB) when building 'prod' variants. + - name: Create checksum for Release .aab artifact + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }} + run: | + sha256sum "app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab" \ + > ./bw-android-aab-sha256.txt + + # Generate SHA256 file for Beta Play Store bundle (AAB) when building 'prod' variants. + - name: Create checksum for Beta .aab artifact + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }} + run: | + sha256sum "app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden-standard-beta.aab" \ + > ./bw-android-beta-aab-sha256.txt + + # Generate SHA256 file for debug Play Store APK when building variants other than 'prod'. + - name: Create checksum for Other .apk artifact + if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }} + run: | + sha256sum "app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk" \ + > ./bw-android-${{ matrix.variant }}-apk-sha256.txt + + # Upload SHA256 file for Release Play Store APK when building 'prod' variants. + - name: Upload .apk sha file for Release + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: bw-android-apk-sha256.txt + path: ./bw-android-apk-sha256.txt + if-no-files-found: error + + # Upload SHA256 file for Beta Play Store APK when building 'prod' variants. + - name: Upload .apk sha file for Beta + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: bw-android-beta-apk-sha256.txt + path: ./bw-android-beta-apk-sha256.txt + if-no-files-found: error + + # Upload SHA256 file for Release Play Store bundle (AAB) when building 'prod' variants. + - name: Upload .aab sha file for Release + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: bw-android-aab-sha256.txt + path: ./bw-android-aab-sha256.txt + if-no-files-found: error + + # Upload SHA256 file for Beta Play Store bundle (AAB) when building 'prod' variants. + - name: Upload .aab sha file for Beta + if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: bw-android-beta-aab-sha256.txt + path: ./bw-android-beta-aab-sha256.txt + if-no-files-found: error + + # Upload SHA256 file for Play Store debug APK when building variants other than 'prod'. + - name: Upload .apk sha file for other + if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: bw-android-${{ matrix.variant }}-apk-sha256.txt + path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt + if-no-files-found: error + + # Install Fastlane's Firebase App Distribution plugin when building 'prod' variants. + - name: Install Firebase App Distribution plugin + if: ${{ matrix.variant == 'prod' && github.ref_name == 'main' && inputs.distribute-to-firebase }} + run: bundle exec fastlane add_plugin firebase_app_distribution + + # Publish Release Play Store artifacts to Firebase. + - name: Publish Release artifacts to Firebase + if: ${{ matrix.variant == 'prod' && matrix.artifact == 'apk' && github.ref_name == 'main' && inputs.distribute-to-firebase }} + env: + APP_PLAY_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json + run: | + bundle exec fastlane distributeReleasePlayStoreToFirebase \ + service_credentials_file:${{ env.APP_PLAY_FIREBASE_CREDS_PATH }} + shell: bash + + # Publish Beta Play Store artifacts to Firebase. + - name: Publish Beta artifacts to Firebase + if: ${{ (matrix.variant == 'prod' && matrix.artifact == 'apk') && github.ref_name == 'main' && inputs.distribute-to-firebase }} + env: + APP_PLAY_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json + run: | + bundle exec fastlane distributeBetaPlayStoreToFirebase \ + service_credentials_file:${{ env.APP_PLAY_FIREBASE_CREDS }} + shell: bash + + # Verify play store creds are valid for publication + - name: Verify Play Store creds + if: ${{ matrix.variant == 'prod' && inputs.publish-to-play-store }} + run: | + bundle exec fastlane run validate_play_store_json_key + shell: bash + + # Publish the Play Store bundle (AAB) to Google Play Store Internal testing track. + - name: Publish Play Store bundle to Play Store + if: ${{ matrix.variant == 'prod' && inputs.publish-to-play-store }} + run: bundle exec fastlane publishForInternalTesting + shell: bash + + publish_fdroid: + name: Publish FDroid Artifacts + # Job dependencies ignored while troubleshooting publish jobs. + needs: + - build + runs-on: ubuntu-22.04 + steps: + + # Checkout project + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + # Allow subsequent steps to trigger GitHub Actions via git push + # https://github.community/t/push-from-action-even-with-pat-does-not-trigger-action/17622 + persist-credentials: false + + # Configure Ruby + - name: Configure Ruby + uses: ruby/setup-ruby@1198b074305f9356bd56dd4b311757cc0dab2f1c # v1.175.1 + with: + bundler-cache: true + + # Install fastlane + - name: Install Fastlane + run: | + gem install bundler:2.2.27 + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + + # Azure login + - name: Login to Azure - CI Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + # Download F-Droid secrets + - name: Download secrets + env: + ACCOUNT_NAME: bitwardenci + CONTAINER_NAME: mobile + run: | + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name app_fdroid-keystore.jks --file ${{ github.workspace }}/keystores/app_fdroid-keystore.jks --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name app_beta_fdroid-keystore.jks --file ${{ github.workspace }}/keystores/app_beta_fdroid-keystore.jks --output none + shell: bash + + # Download Firebase credentials for publishing to App Distribution on 'prod' variants. + - name: Download Firebase Credentials + if: ${{ matrix.variant == 'prod' && inputs.distribute-to-firebase }} + env: + ACCOUNT_NAME: bitwardenci + CONTAINER_NAME: mobile + run: | + mkdir -p ${{ github.workspace }}/secrets + + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name app_fdroid_firebase-creds.json --file ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json --output none + shell: bash + + # Verify the gradle wrapper is known and not corrupted + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1 + + # Configure gradle caching + - name: Cache Gradle Files + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }} + restore-keys: | + ${{ runner.os }}-gradle-v2- + + # Configure build output caching + - name: Cache build output + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: | + ${{ github.workspace }}/build-cache + key: ${{ runner.os }}-build-cache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build- + + # Set the JDK to be used + - name: Configure JDK + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + + # Bump the build version name and version code. Start from 11000 to prevent collisions with + # mobile build version codes. + - name: Increment version + run: | + DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER)) + bundle exec fastlane setBuildVersionInfo \ + versionCode:${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }} \ + versionName:${{ inputs.version-name || '' }} + + # Generate the F-Droid APK for publishing + - name: Generate F-Droid Artifacts + env: + FDROID_STORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }} + run: | + bundle exec fastlane assembleFDroidReleaseApk \ + storeFile:app_fdroid-keystore.jks \ + storePassword:"${{ env.FDROID_STORE_PASSWORD }}" \ + keyAlias:bitwarden \ + keyPassword:"${{ env.FDROID_STORE_PASSWORD }}" + + # Generate the F-Droid APK for publishing + - name: Generate F-Droid Beta Artifacts + env: + FDROID_BETA_KEYSTORE_PASSWORD: ${{ secrets.FDROID_BETA_KEYSTORE_PASSWORD }} + FDROID_BETA_KEY_PASSWORD: ${{ secrets.FDROID_BETA_KEY_PASSWORD }} + run: | + bundle exec fastlane assembleFDroidBetaApk \ + storeFile:app_beta_fdroid-keystore.jks \ + storePassword:"${{ env.FDROID_BETA_KEYSTORE_PASSWORD }}" \ + keyAlias:bitwarden-beta \ + keyPassword:"${{ env.FDROID_BETA_KEY_PASSWORD }}" + + # Upload F-Droid APK to GitHub workflow artifacts container + - name: Upload F-Droid .apk artifact + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: com.x8bit.bitwarden-fdroid.apk + path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk + if-no-files-found: error + + # Generate checksum for F-Droid ARK verification + - name: Create checksum for F-Droid artifact + run: | + sha256sum "app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk" \ + > ./bw-fdroid-apk-sha256.txt + + # Upload F-Droid checksum file to GitHub workflow artifacts container + - name: Upload F-Droid sha file + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: bw-fdroid-apk-sha256.txt + path: ./bw-fdroid-apk-sha256.txt + if-no-files-found: error + + # Install Fastlane's Firebase App Distribution plugin when building 'prod' variants. + - name: Install Firebase App Distribution plugin + run: bundle exec fastlane add_plugin firebase_app_distribution + + # Publish Release F-Droid artifacts to Firebase. + - name: Publish Release F-Droid artifacts to Firebase + if: ${{ github.ref_name == 'main' }} + env: + APP_FDROID_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json + run: | + bundle exec fastlane distributeReleaseFDroidToFirebase \ + service_credentials_file:${{ env.APP_FDROID_FIREBASE_CREDS_PATH }} + shell: bash diff --git a/.github/workflows/run-check.yml b/.github/workflows/run-check.yml index 8e69566a2..f53a62d58 100644 --- a/.github/workflows/run-check.yml +++ b/.github/workflows/run-check.yml @@ -47,18 +47,13 @@ jobs: java-version: ${{ env.JAVA_VERSION }} - name: Build and Run Check - # Run checks while excluding release-build tests, which are not configured to work properly - # with the Compose testing library. Also exclude most FDroid-related tasks, as there is no - # significant code difference between builds. + # Run checks on standard variant only because release and beta builds are not configured to + # work properly with the Compose testing library. F-Droid related tasks are also skipped as + # there is no significant code difference between the builds. run: | - ./gradlew check --no-daemon \ - -x testStandardRelease \ - -x testFdroidDebug \ - -x testFdroidRelease \ - -x lintFdroidDebug \ - -x lintFdroidRelease \ - -x detektFdroidDebug \ - -x detektFdroidRelease \ + ./gradlew testStandardDebug \ + lintStandardDebug \ + detektStandardDebug \ koverXmlReportStandardDebug - name: Danger diff --git a/.gitignore b/.gitignore index b3c6c4957..747f4fe18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,15 @@ +# Fastlane generated and temporary files are ignored per suggested best practices: +# https://github.com/fastlane/docs/blob/master/docs/best-practices/source-control.md +fastlane/report.xml +fastlane/README.md + # General .DS_Store Thumbs.db +# Gradle build cache +/build-cache/ + # IDEs and editors .gradle .idea/ @@ -12,5 +20,7 @@ Thumbs.db local.properties user.properties -# Gradle build cache -/build-cache/ +# Secrets +/keystores/*.jks +/app/src/standardDebug/google-services.json +/app/src/standardRelease/google-services.json diff --git a/Gemfile b/Gemfile index db06b4b51..1831d95e1 100644 --- a/Gemfile +++ b/Gemfile @@ -4,3 +4,8 @@ ruby File.read(".ruby-version").strip gem 'danger' gem 'danger-shroud' +gem 'fastlane' +gem 'time' + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index dec1b1aea..4e250936f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,15 +1,41 @@ GEM remote: https://rubygems.org/ specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.3.0) + aws-partitions (1.923.0) + aws-sdk-core (3.194.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.80.0) + aws-sdk-core (~> 3, >= 3.193.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.149.0) + aws-sdk-core (~> 3, >= 3.194.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) base64 (0.2.0) claide (1.1.0) claide-plugins (0.9.2) cork nap open4 (~> 1.3) + colored (1.2) colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) cork (0.3.0) colored2 (~> 3.1) danger (9.4.3) @@ -30,23 +56,154 @@ GEM danger-shroud (0.0.7) danger-plugin-api (~> 1.0) nokogiri - faraday (2.9.0) - faraday-net_http (>= 2.0, < 3.2) + date (3.3.4) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.110.0) + faraday (1.10.3) + 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-http-cache (2.5.1) faraday (>= 0.8) - faraday-net_http (3.1.0) - net-http + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + 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.0) + faraday (~> 1.0) + fastimage (2.3.1) + fastlane (2.220.0) + 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) + 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.3.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-firebase_app_distribution (0.9.1) + google-apis-firebaseappdistribution_v1 (~> 0.3.0) + google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) + gh_inspector (1.1.3) git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) + 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-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) + 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.7.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.4.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.5) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.6.2) + json (2.7.2) + jwt (2.8.1) + base64 kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) + mini_magick (4.12.0) + mini_mime (1.1.5) mini_portile2 (2.8.6) + multi_json (1.15.0) + multipart-post (2.4.0) + nanaimo (0.3.0) nap (1.1.0) - net-http (0.4.1) - uri + naturally (2.2.1) + nkf (0.2.0) no_proxy_fix (0.1.2) nokogiri (1.16.4) mini_portile2 (~> 2.8.2) @@ -56,17 +213,58 @@ GEM faraday (>= 1, < 3) sawyer (~> 0.9) open4 (1.3.4) + optparse (0.5.0) + os (1.1.4) + plist (3.7.1) public_suffix (5.0.5) racc (1.7.3) + rake (13.2.1) rchardet (1.8.0) + 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.2.6) + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) + 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 + terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) + time (0.3.0) + date + 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.5.0) - uri (0.13.0) + word_wrap (1.0.0) + xcodeproj (1.24.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) PLATFORMS ruby @@ -74,6 +272,9 @@ PLATFORMS DEPENDENCIES danger danger-shroud + fastlane + fastlane-plugin-firebase_app_distribution + time RUBY VERSION ruby 3.3.1p55 diff --git a/README.md b/README.md index bd3903977..6eaaf989a 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,11 @@ The following is a list of additional third-party dependencies used as part of t - Purpose: A Danger plugin for enforcing code coverage via Kover / Jacoco. - License: Apache 2.0 +- **Fastlane** + - https://fastlane.tools/ + - Purpose: Automates building, signing, and distributing applications. + - License: MIT + - **Kover** - https://github.com/Kotlin/kotlinx-kover - Purpose: Kotlin code coverage toolset. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cd261ef3b..ec0f941be 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,6 +28,8 @@ android { versionCode = 1 versionName = "2024.05.0" + setProperty("archivesBaseName", "com.x8bit.bitwarden") + ksp { // The location in which the generated Room Database Schemas will be stored in the repo. arg("room.schemaLocation", "$projectDir/schemas") @@ -57,6 +59,12 @@ android { isDebuggable = true isMinifyEnabled = false } + + // Beta and Release variants are identical except beta has a different package name + create("beta") { + initWith(buildTypes.getByName("release")) + applicationIdSuffix = ".beta" + } release { isDebuggable = false isMinifyEnabled = true diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 0fabafc7d..e7a10029e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -109,3 +109,11 @@ -dontwarn com.google.zxing.EncodeHintType -dontwarn com.google.zxing.MultiFormatWriter -dontwarn com.google.zxing.common.BitMatrix + +################################################################################ +# Gradle generated rules +################################################################################ +-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue +-dontwarn com.google.errorprone.annotations.CheckReturnValue +-dontwarn com.google.errorprone.annotations.Immutable +-dontwarn com.google.errorprone.annotations.RestrictedApi diff --git a/app/src/beta/res/values/strings_non_localized.xml b/app/src/beta/res/values/strings_non_localized.xml new file mode 100644 index 000000000..326726c82 --- /dev/null +++ b/app/src/beta/res/values/strings_non_localized.xml @@ -0,0 +1,4 @@ + + + Bitwarden Beta + diff --git a/app/src/standard/google-services.json b/app/src/standard/google-services.json deleted file mode 100644 index eb8252261..000000000 --- a/app/src/standard/google-services.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "project_info": { - "project_number": "1093287226212", - "firebase_url": "https://bitwarden-dev.firebaseio.com", - "project_id": "bitwarden-dev", - "storage_bucket": "bitwarden-dev.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:1093287226212:android:f8d67b786db1b844", - "android_client_info": { - "package_name": "com.x8bit.bitwarden.dev" - } - }, - "oauth_client": [ - { - "client_id": "1093287226212-m4mv8ho387tdgosc9lsltnmruul7ouo0.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyA4Xkn0do7Ky_OLff2L_7MXeNK6s-JVgXg" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:1093287226212:android:f8d67b786db1b844", - "android_client_info": { - "package_name": "com.x8bit.bitwarden" - } - }, - "oauth_client": [ - { - "client_id": "1093287226212-m4mv8ho387tdgosc9lsltnmruul7ouo0.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyA4Xkn0do7Ky_OLff2L_7MXeNK6s-JVgXg" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - } - ], - "configuration_version": "1" -} diff --git a/app/src/standardDebug/google-services.json b/app/src/standardDebug/google-services.json new file mode 100644 index 000000000..5d82b3052 --- /dev/null +++ b/app/src/standardDebug/google-services.json @@ -0,0 +1,40 @@ +{ + "project_info": { + "project_number": "1093287226212", + "firebase_url": "https://bitwarden-dev.firebaseio.com", + "project_id": "bitwarden-dev", + "storage_bucket": "bitwarden-dev.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:1093287226212:android:6ec272cee7c1c17a4b2871", + "android_client_info": { + "package_name": "com.x8bit.bitwarden.dev" + } + }, + "oauth_client": [ + { + "client_id": "1093287226212-m4mv8ho387tdgosc9lsltnmruul7ouo0.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyA4Xkn0do7Ky_OLff2L_7MXeNK6s-JVgXg" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "1093287226212-m4mv8ho387tdgosc9lsltnmruul7ouo0.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 000000000..06554c7a7 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("secrets/play_creds.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one +package_name("com.x8bit.bitwarden") # e.g. com.krausefx.app diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 000000000..6b8101d99 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,208 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + before_all do + ENV["KEYSTORE_DIR"] = ENV["PWD"] + "/keystores/" + end + + desc "Assemble debug APKs." + lane :assembleDebugApks do |options| + gradle( + tasks: ["assembleDebug"], + ) + end + + desc "Assemble FDroid release APK" + lane :assembleFDroidReleaseApk do |options| + buildAndSignBitwarden( + taskName: "assemble", + flavor: "Fdroid", + buildType: "Release", + storeFile: options[:storeFile], + storePassword: options[:storePassword], + keyAlias: options[:keyAlias], + keyPassword: options[:keyPassword], + ) + end + + desc "Assemble F-Droid Beta APK" + lane :assembleFDroidBetaApk do |options| + buildAndSignBitwarden( + taskName: "assemble", + flavor: "Fdroid", + buildType: "Beta", + storeFile: options[:storeFile], + storePassword: options[:storePassword], + keyAlias: options[:keyAlias], + keyPassword: options[:keyPassword], + ) + end + + desc "Assemble Play Store release APK" + lane :assemblePlayStoreReleaseApk do |options| + buildAndSignBitwarden( + taskName: "assemble", + flavor: "Standard", + buildType: "Release", + storeFile: options[:storeFile], + storePassword: options[:storePassword], + keyAlias: options[:keyAlias], + keyPassword: options[:keyPassword], + ) + end + + desc "Assemble Play Store release APK" + lane :assemblePlayStoreBetaApk do |options| + buildAndSignBitwarden( + taskName: "assemble", + flavor: "Standard", + buildType: "Beta", + storeFile: options[:storeFile], + storePassword: options[:storePassword], + keyAlias: options[:keyAlias], + keyPassword: options[:keyPassword], + ) + end + + desc "Bundle Play Store release" + lane :bundlePlayStoreRelease do |options| + buildAndSignBitwarden( + taskName: "bundle", + flavor: "Standard", + buildType: "Release", + storeFile: options[:storeFile], + storePassword: options[:storePassword], + keyAlias: options[:keyAlias], + keyPassword: options[:keyPassword], + ) + end + + desc "Bundle Play Store release" + lane :bundlePlayStoreBeta do |options| + buildAndSignBitwarden( + taskName: "bundle", + flavor: "Standard", + buildType: "Beta", + storeFile: options[:storeFile], + storePassword: options[:storePassword], + keyAlias: options[:keyAlias], + keyPassword: options[:keyPassword], + ) + end + + desc "Runs Standard Debug tests and generates Kover report." + lane :test do + gradle( + tasks: ["testStandardDebug", "lintStandardDebug", "detektStandardDebug", "koverXmlReportStandardDebug"] + ) + end + + desc "Apply build version information" + fastlane_require "time" + lane :setBuildVersionInfo do |options| + + # Read-in app build config file. + buildConfigPath = "../app/build.gradle.kts" + buildConfigFile = File.open(buildConfigPath) + buildConfigText = buildConfigFile.read + buildConfigFile.close + + currentVersionCode = buildConfigText.match(/versionCode = (\d+)/).captures[0] + currentVersionName = buildConfigText.match(/versionName = "(.+)"/).captures[0] + + if options[:versionName].nil? or options[:versionName].to_s.empty? + nextVersionName = currentVersionName + else + nextVersionName = options[:versionName].to_s + end + + # Append "-native" to the builds so they are easily distinguished from MAUI builds while both + # are being distributed. + nextVersionName = nextVersionName + "-native" + + # Replace version information. + puts "Setting version code to #{options[:versionCode]}." + buildConfigText.gsub!("versionCode = #{currentVersionCode}", "versionCode = #{options[:versionCode]}") + puts "Setting version name to #{nextVersionName}." + buildConfigText.gsub!("versionName = \"#{currentVersionName}\"", "versionName = \"#{nextVersionName}\"") + + # Save changes + File.open(buildConfigPath, "w") { |buildConfigFile| buildConfigFile << buildConfigText } + end + + desc "Generate artifacts for the given [build] signed with the provided [keystore] and credentials." + private_lane :buildAndSignBitwarden do |options| + gradle( + task: options[:taskName], + flavor: options[:flavor], + build_type: options[:buildType], + properties: { + "android.injected.signing.store.file" => ENV["KEYSTORE_DIR"] + options[:storeFile], + "android.injected.signing.store.password" => options[:storePassword], + "android.injected.signing.key.alias" => options[:keyAlias], + "android.injected.signing.key.password" => options[:keyPassword] + }, + print_command: false, + ) + end + + desc "Publish Release Play Store artifacts to Firebase App Distribution" + lane :distributeReleasePlayStoreToFirebase do |options| + firebase_app_distribution( + app: "1:64530857057:android:f8d67b786db1b844", + android_artifact_type: "APK", + android_artifact_path: "app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk", + service_credentials_file: options[:service_credentials_file], + groups: "internal-prod-group", + release_notes: "Native Play Store Release from commit #{sh("git rev-parse --short HEAD")} on branch #{sh("git rev-parse --abbrev-ref HEAD")}", + ) + end + + desc "Publish Beta Play Store artifacts to Firebase App Distribution" + lane :distributeBetaPlayStoreToFirebase do |options| + firebase_app_distribution( + app: "1:64530857057:android:54c1ae56b269b959887e20", + android_artifact_type: "APK", + android_artifact_path: "app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk", + service_credentials_file: options[:service_credentials_file], + groups: "internal-prod-group", + release_notes: "Native Play Store Beta from commit #{sh("git rev-parse --short HEAD")} on branch #{sh("git rev-parse --abbrev-ref HEAD")}", + ) + end + + desc "Publish Release F-Droid artifacts to Firebase App Distribution" + lane :distributeReleaseFDroidToFirebase do |options| + firebase_app_distribution( + app: "1:439897860529:android:b143708734b99c0e3fb590", + android_artifact_type: "APK", + android_artifact_path: "app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk", + service_credentials_file: options[:service_credentials_file], + groups: "internal-prod-group", + release_notes: "Native F-Droid Beta from commit #{sh("git rev-parse --short HEAD")} on branch #{sh("git rev-parse --abbrev-ref HEAD")}", + ) + end + + desc "Publish PlayStore bundle to Google Play Store Internal testing track" + lane :publishForInternalTesting do + upload_to_play_store( + track: "internal", + release_status: "draft", + aab: "app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab" + ) + end +end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 000000000..b18539bc9 --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-firebase_app_distribution'