From 7d827dc9f96393294601565102ab93887c58d2dd Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Sat, 4 Oct 2025 16:08:55 -0500 Subject: [PATCH] refactor(ci): separate release and promotion workflows (#3339) Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- .../workflows/create-or-promote-release.yml | 13 ++- .github/workflows/promote.yml | 104 ++++++++++++++++++ .github/workflows/release.yml | 67 ++--------- fastlane/Fastfile | 70 ------------ 4 files changed, 125 insertions(+), 129 deletions(-) create mode 100644 .github/workflows/promote.yml diff --git a/.github/workflows/create-or-promote-release.yml b/.github/workflows/create-or-promote-release.yml index 049cd069a..50da9d628 100644 --- a/.github/workflows/create-or-promote-release.yml +++ b/.github/workflows/create-or-promote-release.yml @@ -71,10 +71,21 @@ jobs: git push origin ${{ steps.calculate_new_tag.outputs.new_tag }} call-release-workflow: - if: ${{ !inputs.dry_run }} + if: ${{ !inputs.dry_run && inputs.channel == 'internal' }} needs: create-tag uses: ./.github/workflows/release.yml with: tag_name: ${{ needs.create-tag.outputs.new_tag }} channel: ${{ inputs.channel }} + base_version: ${{ inputs.base_version }} + secrets: inherit + + call-promote-workflow: + if: ${{ !inputs.dry_run && inputs.channel != 'internal' }} + needs: create-tag + uses: ./.github/workflows/promote.yml + with: + tag_name: ${{ needs.create-tag.outputs.new_tag }} + channel: ${{ inputs.channel }} + base_version: ${{ inputs.base_version }} secrets: inherit diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml new file mode 100644 index 000000000..20a38a575 --- /dev/null +++ b/.github/workflows/promote.yml @@ -0,0 +1,104 @@ +name: Make Release + +on: + workflow_call: + inputs: + base_version: + description: 'The base version for the release (e.g., 2.3.0)' + required: true + type: string + tag_name: + description: 'The tag that triggered the release' + required: true + type: string + channel: + description: 'The channel to promote to' + required: true + type: string + secrets: + GSERVICES: + required: true + KEYSTORE: + required: true + KEYSTORE_FILENAME: + required: true + KEYSTORE_PROPERTIES: + required: true + DATADOG_APPLICATION_ID: + required: true + DATADOG_CLIENT_TOKEN: + required: true + GOOGLE_MAPS_API_KEY: + required: true + GOOGLE_PLAY_JSON_KEY: + required: true + GRADLE_ENCRYPTION_KEY: + required: true + +concurrency: + group: ${{ github.workflow }}-${{ inputs.tag_name }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: read + id-token: write + attestations: write + +jobs: + prepare-build-info: + runs-on: ubuntu-latest + outputs: + APP_VERSION_NAME: ${{ steps.get_version_name.outputs.APP_VERSION_NAME }} + APP_VERSION_CODE: ${{ steps.calculate_version_code.outputs.versionCode }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + ref: ${{ inputs.tag_name }} + fetch-depth: 0 + submodules: 'recursive' + + - name: Determine Version Name from Tag + id: get_version_name + run: echo "APP_VERSION_NAME=$(echo ${{ inputs.tag_name }} | sed 's/-.*//' | sed 's/v//')" >> $GITHUB_OUTPUT + + - name: Extract VERSION_CODE_OFFSET from config.properties + id: get_version_code_offset + run: | + OFFSET=$(grep '^VERSION_CODE_OFFSET=' config.properties | cut -d'=' -f2) + echo "VERSION_CODE_OFFSET=$OFFSET" >> $GITHUB_OUTPUT + + - name: Calculate Version Code from Git Commit Count + id: calculate_version_code + run: | + COMMIT_COUNT=$(git rev-list --count HEAD) + OFFSET=${{ steps.get_version_code_offset.outputs.VERSION_CODE_OFFSET }} + VERSION_CODE=$((COMMIT_COUNT + OFFSET)) + echo "versionCode=$VERSION_CODE" >> $GITHUB_OUTPUT + shell: bash + + promote-release: + runs-on: ubuntu-latest + needs: [ prepare-build-info ] + steps: + - name: Promote to next channel + uses: kevin-david/promote-play-release@v1 + with: + service-account-json-raw: ${{ secrets.GOOGLE_PLAY_JSON_KEY }} + package-name: 'com.geeksville.mesh' + from-track: 'internal' + to-track: ${{ inputs.channel == 'closed' && 'alpha' || (inputs.channel == 'open' && 'beta' || 'production') }} + + update-github-release: + runs-on: ubuntu-latest + needs: [ prepare-build-info, promote-release ] + steps: + - name: Update GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ inputs.base_version }} + name: ${{ inputs.tag_name }} + generate_release_notes: true + draft: false + prerelease: ${{ inputs.channel != 'production' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7af7a2576..d27814426 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,10 @@ name: Make Release on: workflow_call: inputs: + base_version: + description: 'The base version for the release (e.g., 2.3.0)' + required: true + type: string tag_name: description: 'The tag that triggered the release' required: true @@ -55,13 +59,11 @@ jobs: fetch-depth: 0 submodules: 'recursive' - name: Set up JDK 21 - if: ${{ inputs.channel == 'internal' }} uses: actions/setup-java@v5 with: java-version: '21' distribution: 'jetbrains' - name: Setup Gradle - if: ${{ inputs.channel == 'internal' }} uses: gradle/actions/setup-gradle@v5 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} @@ -100,13 +102,11 @@ jobs: fetch-depth: 0 submodules: 'recursive' - name: Set up JDK 21 - if: ${{ inputs.channel == 'internal' }} uses: actions/setup-java@v5 with: java-version: '21' distribution: 'jetbrains' - name: Setup Gradle - if: ${{ inputs.channel == 'internal' }} uses: gradle/actions/setup-gradle@v5 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} @@ -140,27 +140,13 @@ jobs: ruby-version: '3.2' bundler-cache: true - - name: Determine Fastlane Lane - id: fastlane_lane - run: | - if [[ "${{ inputs.tag_name }}" == *"-internal"* ]]; then - echo "lane=internal" >> $GITHUB_OUTPUT - elif [[ "${{ inputs.tag_name }}" == *"-closed"* ]]; then - echo "lane=closed" >> $GITHUB_OUTPUT - elif [[ "${{ inputs.tag_name }}" == *"-open"* ]]; then - echo "lane=open" >> $GITHUB_OUTPUT - else - echo "lane=production" >> $GITHUB_OUTPUT - fi - - - name: Build and Deploy Google Play Tracks with Fastlane + - name: Build and Deploy Google Play to Internal Track with Fastlane env: VERSION_NAME: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }} VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }} - run: bundle exec fastlane ${{ steps.fastlane_lane.outputs.lane }} + run: bundle exec fastlane internal - name: Upload Google AAB artifact - if: ${{ inputs.channel == 'internal' }} uses: actions/upload-artifact@v4 with: name: google-aab @@ -168,7 +154,6 @@ jobs: retention-days: 1 - name: Upload Google APK artifact - if: ${{ inputs.channel == 'internal' }} uses: actions/upload-artifact@v4 with: name: google-apk @@ -176,7 +161,6 @@ jobs: retention-days: 1 - name: Attest Google artifacts provenance - if: ${{ inputs.channel == 'internal' }} uses: actions/attest-build-provenance@v3 with: subject-path: | @@ -188,29 +172,18 @@ jobs: needs: prepare-build-info environment: Release steps: - - name: Check if build is required - id: check_build - run: | - if [[ "${{ inputs.channel }}" == "internal" ]]; then - echo "should_build=true" >> $GITHUB_OUTPUT - else - echo "should_build=false" >> $GITHUB_OUTPUT - fi - name: Checkout code - if: steps.check_build.outputs.should_build == 'true' uses: actions/checkout@v5 with: ref: ${{ inputs.tag_name }} fetch-depth: 0 submodules: 'recursive' - name: Set up JDK 21 - if: steps.check_build.outputs.should_build == 'true' uses: actions/setup-java@v5 with: java-version: '21' distribution: 'jetbrains' - name: Setup Gradle - if: steps.check_build.outputs.should_build == 'true' uses: gradle/actions/setup-gradle@v5 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} @@ -219,7 +192,6 @@ jobs: build-scan-terms-of-use-agree: 'yes' - name: Load secrets - if: steps.check_build.outputs.should_build == 'true' env: KEYSTORE: ${{ secrets.KEYSTORE }} KEYSTORE_FILENAME: ${{ secrets.KEYSTORE_FILENAME }} @@ -229,21 +201,18 @@ jobs: echo "$KEYSTORE_PROPERTIES" > ./keystore.properties - name: Setup Fastlane - if: steps.check_build.outputs.should_build == 'true' uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' bundler-cache: true - name: Build F-Droid with Fastlane - if: steps.check_build.outputs.should_build == 'true' env: VERSION_NAME: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }} VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }} run: bundle exec fastlane fdroid_build - name: Upload F-Droid APK artifact - if: steps.check_build.outputs.should_build == 'true' uses: actions/upload-artifact@v4 with: name: fdroid-apk @@ -251,7 +220,6 @@ jobs: retention-days: 1 - name: Attest F-Droid APK provenance - if: steps.check_build.outputs.should_build == 'true' uses: actions/attest-build-provenance@v3 with: subject-path: app/build/outputs/apk/fdroid/release/app-fdroid-release.apk @@ -265,29 +233,12 @@ jobs: with: path: ./artifacts - - name: Determine Release Properties - id: release_properties - run: | - if [[ "${{ inputs.channel }}" == "internal" ]]; then - echo "draft=true" >> $GITHUB_OUTPUT - echo "prerelease=true" >> $GITHUB_OUTPUT - elif [[ "${{ inputs.channel }}" == "closed" ]]; then - echo "draft=false" >> $GITHUB_OUTPUT - echo "prerelease=true" >> $GITHUB_OUTPUT - elif [[ "${{ inputs.channel }}" == "open" ]]; then - echo "draft=false" >> $GITHUB_OUTPUT - echo "prerelease=true" >> $GITHUB_OUTPUT - else - echo "draft=false" >> $GITHUB_OUTPUT - echo "prerelease=false" >> $GITHUB_OUTPUT - fi - - name: Create or Update GitHub Release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ inputs.tag_name }} + tag_name: ${{ inputs.base_version }} name: ${{ inputs.tag_name }} generate_release_notes: true files: ./artifacts/*/* - draft: ${{ steps.release_properties.outputs.draft }} - prerelease: ${{ steps.release_properties.outputs.prerelease }} + draft: true + prerelease: true diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 0159eebe5..eafde3ab5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -16,11 +16,6 @@ default_platform(:android) platform :android do - desc "Runs all the tests" - lane :test do - gradle(task: "test") - end - desc "Deploy a new version to the internal track on Google Play" lane :internal do aab_path = build_google_release @@ -35,69 +30,4 @@ platform :android do skip_upload_screenshots: true, ) end - - desc "Promote from internal track to the closed track on Google Play" - lane :closed do - upload_to_play_store( - track: 'internal', - track_promote_to: 'NewAlpha', - release_status: 'completed', - skip_upload_apk: true, - skip_upload_metadata: true, - skip_upload_changelogs: true, - skip_upload_images: true, - skip_upload_screenshots: true, - ) - end - - desc "Promote from closed track to the open track on Google Play" - lane :open do - upload_to_play_store( - track: 'internal', - track_promote_to: 'beta', - release_status: 'draft', - skip_upload_apk: true, - skip_upload_metadata: true, - skip_upload_changelogs: true, - skip_upload_images: true, - skip_upload_screenshots: true, - ) - end - - desc "Promote from open track to the production track on Google Play" - lane :production do - upload_to_play_store( - track: 'internal', - track_promote_to: 'production', - release_status: 'draft', - skip_upload_apk: true, - skip_upload_metadata: true, - skip_upload_changelogs: true, - skip_upload_images: true, - skip_upload_screenshots: true, - ) - end - - desc "Build the F-Droid release" - lane :fdroid_build do - gradle( - task: "clean assembleFdroidRelease", - properties: { - "android.injected.version.name" => ENV['VERSION_NAME'], - "android.injected.version.code" => ENV['VERSION_CODE'] - } - ) - end - - private_lane :build_google_release do - gradle( - task: "clean bundleGoogleRelease assembleGoogleRelease", - print_command: false, - properties: { - "android.injected.version.name" => ENV['VERSION_NAME'], - "android.injected.version.code" => ENV['VERSION_CODE'] - } - ) - lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH] - end end