Shippable Build & Signing #103
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: Shippable Build & Signing | |
| on: | |
| workflow_call: | |
| workflow_dispatch: | |
| inputs: | |
| skipThunderbird: | |
| type: boolean | |
| description: Skip building Thunderbird | |
| skipK9Mail: | |
| type: boolean | |
| description: Skip building K-9 Mail | |
| skipTests: | |
| type: boolean | |
| description: Skip running tests | |
| skipBetaBump: | |
| type: boolean | |
| description: Skip version bump (beta) | |
| skipGooglePlay: | |
| type: boolean | |
| description: Skip Google Play publish | |
| draftGooglePlay: | |
| type: boolean | |
| description: Leave Play Store version in draft state | |
| skipFtp: | |
| type: boolean | |
| description: Skip FTP upload | |
| uploadFtpStage: | |
| type: boolean | |
| description: Upload to FTP stage instead of prod | |
| permissions: | |
| contents: read | |
| jobs: | |
| get_environment: | |
| name: Determine Release Environment | |
| runs-on: ubuntu-latest | |
| outputs: | |
| releaseEnv: ${{ steps.releaseEnv.outputs.result }} | |
| steps: | |
| - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| id: releaseEnv | |
| with: | |
| result-encoding: string | |
| script: | | |
| const RELEASE_ENVS = { | |
| "refs/heads/main": "thunderbird_daily", | |
| "refs/heads/beta": "thunderbird_beta", | |
| "refs/heads/release": "thunderbird_release", | |
| }; | |
| if (context.ref in RELEASE_ENVS) { | |
| return RELEASE_ENVS[context.ref]; | |
| } else { | |
| core.setFailed(`Unknown branch ${context.ref} for shippable builds!`) | |
| return ""; | |
| } | |
| dump_config: | |
| name: Show Release Environment | |
| runs-on: ubuntu-latest | |
| needs: get_environment | |
| environment: ${{ needs.get_environment.outputs.releaseEnv }} | |
| outputs: | |
| matrixInclude: ${{ steps.dump.outputs.matrixInclude }} | |
| releaseDate: ${{ steps.dump.outputs.releaseDate }} | |
| releaseType: ${{ vars.RELEASE_TYPE }} | |
| steps: | |
| - name: Show Environment | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| id: dump | |
| env: | |
| matrixInclude: ${{ vars.MATRIX_INCLUDE }} | |
| releaseType: ${{ vars.RELEASE_TYPE }} | |
| skipThunderbird: ${{ inputs.skipThunderbird }} | |
| skipK9Mail: ${{ inputs.skipK9Mail }} | |
| skipTests: ${{ inputs.skipTests }} | |
| skipBetaBump: ${{ inputs.skipBetaBump }} | |
| skipGooglePlay: ${{ inputs.skipGooglePlay }} | |
| draftGooglePlay: ${{ inputs.draftGooglePlay }} | |
| skipFtp: ${{ inputs.skipFtp }} | |
| uploadFtpStage: ${{ inputs.uploadFtpStage }} | |
| with: | |
| script: | | |
| const NOW = new Date(); | |
| let matrix = JSON.parse(process.env.matrixInclude); | |
| let skipThunderbird = process.env.skipThunderbird == "true"; | |
| let skipK9Mail = process.env.skipK9Mail == "true"; | |
| if (!matrix.every(item => !!item.appName && !!item.packageFormat)) { | |
| core.setFailed("MATRIX_INCLUDE is missing appName or packageFormat"); | |
| } | |
| let matrixFull = matrix.filter(item => { | |
| return !((item.appName == "k9mail" && skipK9Mail) || | |
| (item.appName == "thunderbird" && skipThunderbird)); | |
| }); | |
| if (!matrixFull.length) { | |
| core.setFailed("There are no builds to run"); | |
| return; | |
| } | |
| core.setOutput("releaseDate", NOW.toString()); | |
| core.setOutput("matrixInclude", matrixFull); | |
| await core.summary | |
| .addRaw(`Beginning a <b>${process.env.releaseType}</b> build with the following configurations:`, true) | |
| .addTable([ | |
| [ | |
| { data: "App Name", header: true }, | |
| { data: "Flavor", header: true }, | |
| { data: "Format", header: true }, | |
| { data: "Release Target", header: true }, | |
| { data: "Play Store Track", header: true }, | |
| ], | |
| ...matrixFull.map(item => [ | |
| { data: item.appName }, | |
| { data: item.packageFlavor }, | |
| { data: item.packageFormat }, | |
| { data: item.releaseTarget?.replace(/\|/g, ", ") || "artifact only" }, | |
| { data: item.playTargetTrack || "none" }, | |
| ]) | |
| ]) | |
| .write(); | |
| if (skipThunderbird) { | |
| await core.summary.addList(["Thunderbird build is being skipped"]).write(); | |
| } | |
| if (skipK9Mail) { | |
| await core.summary.addList(["K-9 Mail build is being skipped"]).write(); | |
| } | |
| if (process.env.skipTests == "true") { | |
| await core.summary.addList(["Tests are being skipped"]).write(); | |
| } | |
| if (process.env.skipBetaBump == "true" && process.env.releaseType == "beta") { | |
| await core.summary.addList(["Beta bump is being skipped"]).write(); | |
| } | |
| if (process.env.skipGooglePlay == "true") { | |
| await core.summary.addList(["Play Store upload is being skipped"]).write(); | |
| } | |
| if (process.env.skipGooglePlay != "true" && process.env.draftGooglePlay == "true") { | |
| await core.summary.addList(["Play Store upload is being kept in the draft state"]).write(); | |
| } | |
| if (process.env.skipFtp == "true") { | |
| await core.summary.addList(["FTP upload is being skipped"]).write(); | |
| } else if (process.env.uploadFtpStage == "true") { | |
| await core.summary.addList(["FTP upload destination is stage (ftp.stage.mozaws.net)"]).write(); | |
| } else if (process.env.uploadFtpStage == "false") { | |
| await core.summary.addList(["FTP upload destination is prod (ftp.mozilla.org)"]).write(); | |
| } | |
| notify_build_start: | |
| name: Notify Build Start | |
| runs-on: ubuntu-latest | |
| needs: [ dump_config ] | |
| if: ${{ needs.dump_config.outputs.releaseType == 'beta' || needs.dump_config.outputs.releaseType == 'release' }} | |
| environment: notify_matrix | |
| outputs: | |
| actorLink: ${{ steps.actorLink.outputs.actorLink }} | |
| steps: | |
| - name: Triggering Actor Link | |
| id: actorLink | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| env: | |
| userMap: ${{ vars.MATRIX_NOTIFY_USER_MAP }} | |
| with: | |
| script: | | |
| let userMap = JSON.parse(process.env.userMap || "{}"); | |
| if (Object.hasOwn(userMap, context.actor)) { | |
| let mxid = userMap[context.actor]; | |
| core.setOutput("actorLink", `[${mxid}](https://matrix.to/#/${mxid})`); | |
| } else { | |
| core.setOutput("actorLink", `[@${context.actor}](https://github.com/${context.actor})`); | |
| } | |
| - name: Notify Build Start | |
| if: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| uses: kewisch/action-matrix-notify@3c45d89acd032c84b955b54c8a9001833ac91d17 # v1 | |
| with: | |
| matrixHomeserver: ${{ vars.MATRIX_NOTIFY_HOMESERVER }} | |
| matrixRoomId: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| matrixToken: ${{ secrets.MATRIX_NOTIFY_TOKEN }} | |
| message: >- | |
| 🔵 [${{ vars.RELEASE_TYPE }} build ${{ github.run_number}}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| was started by ${{ steps.actorLink.outputs.actorLink }} | |
| release_commit: | |
| name: Release Bumps | |
| runs-on: ubuntu-latest | |
| needs: [ dump_config, get_environment ] | |
| environment: ${{ needs.get_environment.outputs.releaseEnv }} | |
| strategy: | |
| max-parallel: 1 | |
| matrix: | |
| include: "${{ fromJSON(needs.dump_config.outputs.matrixInclude) }}" | |
| outputs: | |
| k9mail_sha: ${{ steps.commit.outputs.k9mail_sha }} | |
| thunderbird_sha: ${{ steps.commit.outputs.thunderbird_sha }} | |
| k9mail_github_notes: ${{ steps.render_notes.outputs.k9mail_github_notes }} | |
| thunderbird_github_notes: ${{ steps.render_notes.outputs.thunderbird_github_notes }} | |
| old_version_code: ${{ steps.new_version_code.outputs.old_version_code }} | |
| new_version_code: ${{ steps.new_version_code.outputs.new_version_code }} | |
| steps: | |
| - name: App Token Generate | |
| uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 | |
| if: ${{ vars.BOT_CLIENT_ID }} | |
| id: app-token | |
| with: | |
| app-id: ${{ vars.BOT_CLIENT_ID }} | |
| private-key: ${{ secrets.BOT_PRIVATE_KEY }} | |
| - name: Checkout repository | |
| if: ${{ contains(matrix.releaseTarget, 'github') || needs.dump_config.outputs.releaseType == 'daily' }} | |
| uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token || github.token }} | |
| - name: Setup Gradle environment | |
| if: ${{ contains(matrix.releaseTarget, 'github') || needs.dump_config.outputs.releaseType == 'daily' }} | |
| uses: ./.github/actions/setup-gradle | |
| with: | |
| disable-cache: "${{ contains(fromJSON('[\"beta\", \"release\"]'), needs.dump_config.outputs.releaseType) }}" | |
| add-job-summary: never | |
| - name: Get application info | |
| id: appinfo | |
| shell: bash | |
| if: ${{ contains(matrix.releaseTarget, 'github') || needs.dump_config.outputs.releaseType == 'daily' }} | |
| env: | |
| RELEASE_TYPE: ${{ vars.RELEASE_TYPE }} | |
| PACKAGE_FLAVOR: ${{ matrix.packageFlavor }} | |
| APP_NAME: ${{ matrix.appName }} | |
| run: | | |
| if [[ "${APP_NAME}" == "k9mail" && "${RELEASE_TYPE}" == "beta" ]]; then | |
| # k9mail uses release for betas as well. Later on we should align the structures and | |
| # remove this hack | |
| RELEASE_TYPE=release | |
| fi | |
| ./gradlew :app-${APP_NAME}:printVersionInfo${PACKAGE_FLAVOR^}${RELEASE_TYPE^} -PoutputFile=${GITHUB_OUTPUT} | |
| - name: Determine new version code | |
| id: new_version_code | |
| if: ${{ contains(matrix.releaseTarget, 'github') || needs.dump_config.outputs.releaseType == 'daily' }} | |
| shell: bash | |
| env: | |
| APP_NAME: ${{ matrix.appName }} | |
| OLD_VERSION_CODE: ${{ steps.appinfo.outputs.VERSION_CODE }} | |
| RELEASE_TYPE: ${{ vars.RELEASE_TYPE }} | |
| RELEASE_DATE: ${{ needs.dump_config.outputs.releaseDate }} | |
| run: | | |
| date_version_code() { | |
| # Daily uses date-based calculation for version code in yDDDHHmm format | |
| # y: Years since 2023 (e.g. 2024 is 1) | |
| # DDD: Day of the year (3 digits, zero-padded) | |
| # HH: Hour of the day (2 digits, 00-23) | |
| # mm: Minute of the hour (2 digits) | |
| datetime="$1" | |
| year=$(( $(date -d "${datetime}" +"%Y") - 2023 )) | |
| day=$(date -d "${datetime}" +"%j") | |
| time=$(date -d "${datetime}" +"%H%M") | |
| echo "${year}${day}${time}" | |
| } | |
| # Android version codes max out at 2100000000. If we accidentally reach that number then | |
| # all we can do is start with a new application id. These protections are meant to avoid | |
| # unexpected edge cases. If the build fails here please double check the generated version | |
| # code and bump it a year into the future. | |
| if [[ "$RELEASE_TYPE" = "beta" || "$RELEASE_TYPE" = "release" ]]; then | |
| NEW_VERSION_CODE=$(($OLD_VERSION_CODE + 1)) | |
| if [[ "$APP_NAME" = "thunderbird" ]]; then | |
| # Estimated max version code to get through 2025 is "30" | |
| MAX_VERSION_CODE="52" | |
| else | |
| # Estimated max version code to get through 2025 is "39037" | |
| MAX_VERSION_CODE="39059" | |
| fi | |
| elif [[ "$RELEASE_TYPE" = "daily" ]]; then | |
| NEW_VERSION_CODE=$(date_version_code "$RELEASE_DATE") | |
| # Max version code for "2026-11-30 23:59" is "33342359" | |
| MAX_VERSION_CODE="33342359" | |
| fi | |
| if [[ "$NEW_VERSION_CODE" -gt "$MAX_VERSION_CODE" ]]; then | |
| echo "New version code ${NEW_VERSION_CODE} is greater than ${MAX_VERSION_CODE}" | |
| exit 1 | |
| fi | |
| sed "s/versionCode = $OLD_VERSION_CODE/versionCode = $NEW_VERSION_CODE/" app-${APP_NAME}/build.gradle.kts > tmp_gradle_kts | |
| ! diff -u app-${APP_NAME}/build.gradle.kts tmp_gradle_kts # flip return value to force error if no bump | |
| mv tmp_gradle_kts app-${APP_NAME}/build.gradle.kts | |
| echo "old_version_code=${OLD_VERSION_CODE}" | tee -a $GITHUB_OUTPUT | |
| echo "new_version_code=${NEW_VERSION_CODE}" | tee -a $GITHUB_OUTPUT | |
| - name: Bump version suffix | |
| id: bump_version_suffix | |
| if: ${{ !inputs.skipBetaBump && contains(matrix.releaseTarget, 'github') && vars.RELEASE_TYPE == 'beta' }} | |
| shell: bash | |
| env: | |
| APP_NAME: ${{ matrix.appName }} | |
| OLD_VERSION_SUFFIX: ${{ steps.appinfo.outputs.VERSION_NAME_SUFFIX }} | |
| run: | | |
| NEW_VERSION_SUFFIX=b$((${OLD_VERSION_SUFFIX:1} + 1)) | |
| sed "s/versionNameSuffix = \"$OLD_VERSION_SUFFIX\"/versionNameSuffix = \"$NEW_VERSION_SUFFIX\"/" app-${APP_NAME}/build.gradle.kts > tmp_gradle_kts | |
| ! diff -u app-${APP_NAME}/build.gradle.kts tmp_gradle_kts # flip return value to force error if no bump | |
| mv tmp_gradle_kts app-${APP_NAME}/build.gradle.kts | |
| echo "SUFFIX=${NEW_VERSION_SUFFIX}" >> $GITHUB_OUTPUT | |
| cat $GITHUB_OUTPUT | |
| - name: Render Release Notes | |
| id: render_notes | |
| if: "${{ contains(matrix.releaseTarget, 'github') && contains(fromJSON('[\"beta\", \"release\"]'), needs.dump_config.outputs.releaseType) }}" | |
| shell: bash | |
| env: | |
| APP_NAME: ${{ matrix.appName }} | |
| APPLICATION_ID: ${{ steps.appinfo.outputs.APPLICATION_ID }} | |
| APPLICATION_LABEL: ${{ steps.appinfo.outputs.APPLICATION_LABEL }} | |
| VERSION_CODE: ${{ steps.new_version_code.outputs.new_version_code }} | |
| FULL_VERSION_NAME: ${{ steps.appinfo.outputs.VERSION_NAME }}${{ steps.bump_version_suffix.outputs.SUFFIX || steps.appinfo.outputs.VERSION_NAME_SUFFIX }} | |
| run: | | |
| mkdir -p ./app-metadata/${APPLICATION_ID}/en-US/changelogs | |
| GITHUB_NOTES_FILE="$(mktemp -d)/long-notes.txt" | |
| python ./scripts/ci/render-notes.py ${APPLICATION_ID} ${FULL_VERSION_NAME} ${VERSION_CODE} ${GITHUB_NOTES_FILE} | |
| echo "${APP_NAME}_github_notes<<EOF" >> $GITHUB_OUTPUT | |
| cat $GITHUB_NOTES_FILE >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| echo "<h2>${APPLICATION_LABEL} ${FULL_VERSION_NAME} Release Notes (${VERSION_CODE})</h2>" | tee -a $GITHUB_STEP_SUMMARY | |
| echo -e "Summarized Version Notes\n\n\`\`\`" | tee -a $GITHUB_STEP_SUMMARY | |
| cat ./app-metadata/${APPLICATION_ID}/en-US/changelogs/${VERSION_CODE}.txt | tee -a $GITHUB_STEP_SUMMARY | |
| echo -e "\n\`\`\`\n\nLong Version Notes\n\n\`\`\`" | tee -a $GITHUB_STEP_SUMMARY | |
| cat $GITHUB_NOTES_FILE | tee -a $GITHUB_STEP_SUMMARY | |
| echo -e "\`\`\`" | tee -a $GITHUB_STEP_SUMMARY | |
| - name: Validate Release Notes Length | |
| if: "${{ contains(matrix.releaseTarget, 'github') && contains(fromJSON('[\"beta\", \"release\"]'), needs.dump_config.outputs.releaseType) }}" | |
| shell: bash | |
| env: | |
| APPLICATION_ID: ${{ steps.appinfo.outputs.APPLICATION_ID }} | |
| VERSION_CODE: ${{ steps.new_version_code.outputs.new_version_code }} | |
| run: | | |
| wc -c ./app-metadata/${APPLICATION_ID}/en-US/changelogs/${VERSION_CODE}.txt | |
| RELNOTES_LENGTH=$(wc -c ./app-metadata/${APPLICATION_ID}/en-US/changelogs/${VERSION_CODE}.txt | awk '{print $1}') | |
| if [[ "${RELNOTES_LENGTH}" -gt 500 ]]; then | |
| echo "Release Notes are too long. Found ${RELNOTES_LENGTH} characters, need a maximum of 500" | |
| exit 1 | |
| fi | |
| - name: Release Commits | |
| if: "${{ contains(matrix.releaseTarget, 'github') && contains(fromJSON('[\"beta\", \"release\"]'), needs.dump_config.outputs.releaseType) }}" | |
| id: commit | |
| shell: bash | |
| env: | |
| APPLICATION_LABEL: ${{ steps.appinfo.outputs.APPLICATION_LABEL }} | |
| APPLICATION_ID: ${{ steps.appinfo.outputs.APPLICATION_ID }} | |
| APP_NAME: ${{ matrix.appName }} | |
| FULL_VERSION_NAME: ${{ steps.appinfo.outputs.VERSION_NAME }}${{ steps.bump_version_suffix.outputs.SUFFIX || steps.appinfo.outputs.VERSION_NAME_SUFFIX }} | |
| RELEASE_TYPE: ${{ vars.RELEASE_TYPE }} | |
| APP_SLUG: ${{ steps.app-token.outputs.app-slug || 'github-actions'}} | |
| APP_USER_ID: ${{ vars.BOT_USER_ID || '41898282' }} | |
| run: | | |
| git config --global user.name "${APP_SLUG}" | |
| git config --global user.email "${APP_USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com" | |
| # We need the metadata to point to the right application for the release commit | |
| set -x | |
| git pull | |
| ls -l metadata | |
| rm metadata | |
| ln -sf app-metadata/${APPLICATION_ID} metadata | |
| ls -l metadata | |
| # Add changelogs, build version changes and metadata symlink | |
| git add ./app-metadata/${APPLICATION_ID}/en-US/changelogs/* | |
| if [[ "$APP_NAME" = "k9mail" ]]; then | |
| git add ./app-${APP_NAME}/src/main/res/raw/changelog_master.xml | |
| elif [[ "$APP_NAME" = "thunderbird" ]]; then | |
| git add ./app-${APP_NAME}/src/${RELEASE_TYPE}/res/raw/changelog_master.xml | |
| fi | |
| git add ./app-${APP_NAME}/build.gradle.kts | |
| git add metadata | |
| # Ready to commit. There should be no race conditions with max-parallel set to 1. | |
| git status | |
| git commit -m "Release: ${APPLICATION_LABEL} ${FULL_VERSION_NAME}" | |
| git diff HEAD~1 HEAD | |
| git log -n 5 | |
| git push | |
| set +x | |
| echo "${APP_NAME}_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT | |
| echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT | |
| - name: Summary | |
| if: ${{ contains(matrix.releaseTarget, 'github') || needs.dump_config.outputs.releaseType == 'daily' }} | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| env: | |
| bump_sha: ${{ steps.commit.outputs.sha }} | |
| applicationId: ${{ steps.appinfo.outputs.APPLICATION_ID }} | |
| oldFullVersion: ${{ steps.appinfo.outputs.VERSION_NAME }}${{ steps.appinfo.outputs.VERSION_NAME_SUFFIX }} | |
| oldVersionCode: ${{ steps.new_version_code.outputs.old_version_code }} | |
| newFullVersion: ${{ steps.appinfo.outputs.VERSION_NAME }}${{ steps.bump_version_suffix.outputs.SUFFIX || steps.appinfo.outputs.VERSION_NAME_SUFFIX }} | |
| newVersionCode: ${{ steps.new_version_code.outputs.new_version_code }} | |
| releaseType: ${{ vars.RELEASE_TYPE }} | |
| with: | |
| script: | | |
| let env = process.env; | |
| console.log(env); | |
| if (process.env.releaseType == "daily") { | |
| await core.summary | |
| .addRaw(`Version for ${env.applicationId} changed from ${env.oldFullVersion} (${env.oldVersionCode}) to ${env.newFullVersion} (${env.newVersionCode}) (uncommitted) and will build from `) | |
| .addLink(process.env.GITHUB_SHA, `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/commit/${process.env.GITHUB_SHA}`) | |
| .addEOL() | |
| .write(); | |
| } else { | |
| await core.summary | |
| .addRaw(`Version for ${env.applicationId} bumped from ${env.oldFullVersion} (${env.oldVersionCode}) to ${env.newFullVersion} (${env.newVersionCode}) in `) | |
| .addLink(process.env.bump_sha, `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/commit/${env.bump_sha}`) | |
| .addEOL() | |
| .write(); | |
| } | |
| build_unsigned: | |
| name: Build Unsigned | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| if: ${{ !failure() && !cancelled() }} # Run if release_commit is skipped | |
| needs: [ dump_config, get_environment, release_commit ] | |
| strategy: | |
| matrix: | |
| include: "${{ fromJSON(needs.dump_config.outputs.matrixInclude) }}" | |
| environment: ${{ needs.get_environment.outputs.releaseEnv }} | |
| steps: | |
| - name: Get release sha | |
| id: sha | |
| shell: bash | |
| env: | |
| THUNDERBIRD_SHA: ${{ needs.release_commit.outputs.thunderbird_sha }} | |
| K9MAIL_SHA: ${{ needs.release_commit.outputs.k9mail_sha }} | |
| RELEASE_TYPE: ${{ vars.RELEASE_TYPE }} | |
| APP_NAME: ${{ matrix.appName }} | |
| run: | | |
| APP_SHA_NAME=${APP_NAME^^}_SHA | |
| APP_SHA="${!APP_SHA_NAME:-$GITHUB_SHA}" | |
| echo "app_sha=${APP_SHA}" >> $GITHUB_OUTPUT | |
| cat $GITHUB_OUTPUT | |
| - name: Checkout repository | |
| uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 | |
| with: | |
| ref: ${{ steps.sha.outputs.app_sha }} | |
| - name: Setup Gradle environment | |
| uses: ./.github/actions/setup-gradle | |
| with: | |
| disable-cache: "${{ contains(fromJSON('[\"beta\", \"release\"]'), needs.dump_config.outputs.releaseType) }}" | |
| add-job-summary: on-failure | |
| - name: Set Version Code for Daily | |
| if: ${{ needs.dump_config.outputs.releaseType == 'daily' }} | |
| shell: bash | |
| env: | |
| APP_NAME: ${{ matrix.appName }} | |
| OLD_VERSION_CODE: ${{ needs.release_commit.outputs.old_version_code }} | |
| NEW_VERSION_CODE: ${{ needs.release_commit.outputs.new_version_code }} | |
| run: | | |
| sed "s/versionCode = $OLD_VERSION_CODE/versionCode = $NEW_VERSION_CODE/" app-${APP_NAME}/build.gradle.kts > tmp_gradle_kts | |
| ! diff -u app-${APP_NAME}/build.gradle.kts tmp_gradle_kts # flip return value to force error if no bump | |
| mv tmp_gradle_kts app-${APP_NAME}/build.gradle.kts | |
| - name: Build It | |
| shell: bash | |
| env: | |
| PACKAGE_FORMAT: ${{ matrix.packageFormat }} | |
| PACKAGE_FLAVOR: ${{ matrix.packageFlavor }} | |
| APP_NAME: ${{ matrix.appName }} | |
| RELEASE_TYPE: ${{ vars.RELEASE_TYPE }} | |
| run: | | |
| if [[ "$APP_NAME" = "thunderbird" && "$PACKAGE_FORMAT" = "apk" ]]; then | |
| BUILD_COMMAND="assemble${PACKAGE_FLAVOR^}${RELEASE_TYPE^}" | |
| elif [[ "$APP_NAME" = "thunderbird" && "${PACKAGE_FORMAT}" = "aab" ]]; then | |
| BUILD_COMMAND="bundle${PACKAGE_FLAVOR^}${RELEASE_TYPE^}" | |
| elif [[ "$APP_NAME" = "k9mail" ]]; then | |
| BUILD_COMMAND="assemble${PACKAGE_FLAVOR^}Release" | |
| fi | |
| echo "BUILDING: :app-${APP_NAME}:${BUILD_COMMAND}" | |
| ./gradlew clean :app-${APP_NAME}:${BUILD_COMMAND} --no-build-cache --no-configuration-cache | |
| echo "Status: $?" | |
| - name: Test It | |
| if: ${{ !inputs.skipTests }} | |
| shell: bash | |
| run: ./gradlew testsOnCi | |
| - name: Move apps to upload directory | |
| shell: bash | |
| env: | |
| PACKAGE_FORMAT: ${{ matrix.packageFormat }} | |
| PACKAGE_FLAVOR: ${{ matrix.packageFlavor }} | |
| APP_NAME: ${{ matrix.appName }} | |
| RELEASE_TYPE: ${{ vars.RELEASE_TYPE }} | |
| UPLOAD_PATH: "uploads" | |
| run: | | |
| OUT_BASE=app-${APP_NAME}/build/outputs/ | |
| if [[ "$APP_NAME" = "thunderbird" && "$PACKAGE_FORMAT" = "apk" ]]; then | |
| OUT_PATH="${OUT_BASE}/apk/${PACKAGE_FLAVOR}/${RELEASE_TYPE}" | |
| OUT_FILE="app-${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}-unsigned.apk" | |
| UPLOAD_FILE="${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}.apk" | |
| elif [[ "$APP_NAME" = "thunderbird" && "${PACKAGE_FORMAT}" = "aab" ]]; then | |
| OUT_PATH="${OUT_BASE}/bundle/${PACKAGE_FLAVOR}${RELEASE_TYPE^}" | |
| OUT_FILE="app-${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}.aab" | |
| UPLOAD_FILE="${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}.aab" | |
| elif [[ "$APP_NAME" = "k9mail" ]]; then | |
| OUT_PATH="${OUT_BASE}/apk/${PACKAGE_FLAVOR}/release" | |
| OUT_FILE="app-${APP_NAME}-${PACKAGE_FLAVOR}-release-unsigned.apk" | |
| UPLOAD_FILE="${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}.apk" | |
| else | |
| echo "PACKAGE_FORMAT $PACKAGE_FORMAT is unknown. Exiting." | |
| exit 23 | |
| fi | |
| mkdir -p "${UPLOAD_PATH}" | |
| if [[ -f "${OUT_PATH}/${OUT_FILE}" ]]; then | |
| mv -f "${OUT_PATH}/${OUT_FILE}" "${UPLOAD_PATH}/${UPLOAD_FILE}" | |
| else | |
| echo "Build file ${OUT_PATH}/${OUT_FILE} not found. Exiting." | |
| ls -l ${OUT_PATH} | |
| exit 24 | |
| fi | |
| echo "Upload contents:" | |
| ls -l ${UPLOAD_PATH}/ | |
| - name: Upload unsigned | |
| uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | |
| env: | |
| UPLOAD_PATH: "uploads" | |
| with: | |
| name: unsigned-${{ matrix.appName }}-${{ matrix.packageFormat }}-${{ matrix.packageFlavor }} | |
| path: ${{ env.UPLOAD_PATH }}/ | |
| if-no-files-found: error | |
| sign_mobile: | |
| name: Sign Packages | |
| runs-on: ubuntu-latest | |
| if: ${{ !failure() && !cancelled() }} # Run if previous step is skipped | |
| needs: [ build_unsigned, dump_config ] | |
| strategy: | |
| matrix: | |
| include: "${{ fromJSON(needs.dump_config.outputs.matrixInclude) }}" | |
| environment: ${{ matrix.appName }}_${{ needs.dump_config.outputs.releaseType }}_${{ matrix.packageFlavor }} | |
| env: | |
| RELEASE_TYPE: ${{ needs.dump_config.outputs.releaseType }} | |
| steps: | |
| - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 | |
| with: | |
| name: unsigned-${{ matrix.appName }}-${{ matrix.packageFormat }}-${{ matrix.packageFlavor }} | |
| path: uploads/ | |
| - uses: noriban/sign-android-release@5f144321d3c7c2233266e78b42360345d8bbe403 # v5.1 | |
| name: Sign package | |
| with: | |
| releaseDirectory: uploads/ | |
| signingKeyBase64: ${{ secrets.SIGNING_KEY }} | |
| alias: ${{ secrets.KEY_ALIAS }} | |
| keyPassword: ${{ secrets.KEY_PASSWORD }} | |
| keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} | |
| - name: Rename assets | |
| if: ${{ matrix.packageFormat == 'apk' }} | |
| env: | |
| APP_NAME: ${{ matrix.appName }} | |
| PACKAGE_FLAVOR: ${{ matrix.packageFlavor }} | |
| run: | | |
| mv uploads/${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}-signed.apk uploads/${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}.apk | |
| rm uploads/*-aligned.apk | |
| - name: Remove JKS file | |
| shell: bash | |
| run: | | |
| rm -f uploads/*.jks | |
| - name: Upload signed | |
| uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | |
| with: | |
| name: signed-${{ matrix.appName }}-${{ matrix.packageFormat }}-${{ matrix.packageFlavor }} | |
| if-no-files-found: error | |
| path: | | |
| uploads/*.apk | |
| uploads/*.aab | |
| notify_pre_publish: | |
| name: Notify Publish Approval | |
| needs: [ dump_config, sign_mobile, notify_build_start ] | |
| if: "${{ contains(fromJSON('[\"beta\", \"release\"]'), needs.dump_config.outputs.releaseType) }}" | |
| runs-on: ubuntu-latest | |
| environment: notify_matrix | |
| steps: | |
| - uses: kewisch/action-matrix-notify@3c45d89acd032c84b955b54c8a9001833ac91d17 # v1 | |
| with: | |
| matrixHomeserver: ${{ vars.MATRIX_NOTIFY_HOMESERVER }} | |
| matrixRoomId: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| matrixToken: ${{ secrets.MATRIX_NOTIFY_TOKEN }} | |
| message: >- | |
| 🟡 [${{ needs.dump_config.outputs.releaseType }} build ${{ github.run_number}}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| waiting for publish approval (triggered by ${{ needs.notify_build_start.outputs.actorLink }}) | |
| pre_publish: | |
| # This is a holding job meant to require approval before proceeding with the publishing jobs below | |
| # The environment has a deployment protection rule requiring approval from a set of named reviewers | |
| # before proceeding. | |
| name: Wait for Approval | |
| needs: [ dump_config, sign_mobile ] | |
| if: "${{ contains(fromJSON('[\"beta\", \"release\"]'), needs.dump_config.outputs.releaseType) }}" | |
| environment: publish_hold | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Approval | |
| shell: bash | |
| run: | | |
| true | |
| publish_release: | |
| name: Publish Release | |
| needs: [ pre_publish, dump_config, release_commit ] | |
| if: ${{ !failure() && !cancelled() }} # Run if previous step is skipped | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| include: "${{ fromJSON(needs.dump_config.outputs.matrixInclude) }}" | |
| environment: publish_release | |
| outputs: | |
| thunderbird_release_url: ${{ steps.summary.outputs.thunderbird_release_url }} | |
| k9mail_release_url: ${{ steps.summary.outputs.k9mail_release_url }} | |
| thunderbird_full_version_name: ${{ steps.summary.outputs.thunderbird_full_version_name }} | |
| k9mail_full_version_name: ${{ steps.summary.outputs.k9mail_full_version_name }} | |
| env: | |
| RELEASE_DATE: ${{ needs.dump_config.outputs.releaseDate }} | |
| RELEASE_TYPE: ${{ needs.dump_config.outputs.releaseType }} | |
| APP_NAME: ${{ matrix.appName }} | |
| PACKAGE_FLAVOR: ${{ matrix.packageFlavor}} | |
| PACKAGE_FORMAT: ${{ matrix.packageFormat }} | |
| permissions: | |
| id-token: 'write' # For GCS publishing (ftp.mo) | |
| steps: | |
| - name: Get release sha and notes | |
| id: shanotes | |
| shell: bash | |
| env: | |
| THUNDERBIRD_GITHUB_NOTES: ${{ needs.release_commit.outputs.thunderbird_github_notes }} | |
| K9MAIL_GITHUB_NOTES: ${{ needs.release_commit.outputs.k9mail_github_notes }} | |
| THUNDERBIRD_SHA: ${{ needs.release_commit.outputs.thunderbird_sha }} | |
| K9MAIL_SHA: ${{ needs.release_commit.outputs.k9mail_sha }} | |
| APP_NAME: ${{ matrix.appName }} | |
| RELEASE_TYPE: ${{ env.RELEASE_TYPE }} | |
| run: | | |
| app_sha_name=${APP_NAME^^}_SHA | |
| app_sha="${!app_sha_name:-$GITHUB_SHA}" | |
| echo "app_sha=${app_sha}" >> $GITHUB_OUTPUT | |
| if [[ "$RELEASE_TYPE" = "beta" || "$RELEASE_TYPE" = "release" ]]; then | |
| app_relnotes_name=${APP_NAME^^}_GITHUB_NOTES | |
| echo "app_github_notes<<EOF" >> $GITHUB_OUTPUT | |
| echo "${!app_relnotes_name}" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| fi | |
| cat $GITHUB_OUTPUT | |
| - name: Checkout repository | |
| uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 | |
| with: | |
| ref: ${{ steps.shanotes.outputs.app_sha }} | |
| - name: Download Artifacts | |
| uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 | |
| with: | |
| name: signed-${{ matrix.appName }}-${{ matrix.packageFormat }}-${{ matrix.packageFlavor }} | |
| path: "uploads/" | |
| - name: Get Package Info | |
| id: pkginfo | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PKG_FILE="uploads/${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}.${PACKAGE_FORMAT}" | |
| if [[ "${PACKAGE_FORMAT}" == "apk" ]]; then | |
| LATEST_BUILD_TOOLS=$(ls -d ${ANDROID_SDK_ROOT}/build-tools/* | sort -V | tail -n1) | |
| AAPT=${LATEST_BUILD_TOOLS}/aapt | |
| VERSION_NAME=$(${AAPT} dump badging $PKG_FILE | sed -n "s/^package:.*versionName='\([^']*\)'.*$/\1/p") | |
| VERSION_CODE=$(${AAPT} dump badging $PKG_FILE | sed -n "s/^package:.*versionCode='\([^']*\)'.*$/\1/p") | |
| APPLICATION_ID=$(${AAPT} dump badging $PKG_FILE | sed -n "s/^package: name='\([^']*\)'.*$/\1/p") | |
| APPLICATION_LABEL=$(${AAPT} dump badging $PKG_FILE | sed -n "s/^application-label:'\([^']*\)'.*$/\1/p") | |
| elif [[ "${PACKAGE_FORMAT}" == "aab" ]]; then | |
| if [ ! -f bundletool.jar ]; then | |
| gh release download -R google/bundletool -p 'bundletool-all-*.jar' -O bundletool.jar | |
| fi | |
| BUNDLETOOL="java -jar bundletool.jar" | |
| VERSION_NAME=$(${BUNDLETOOL} dump manifest --bundle ${PKG_FILE} --xpath '/manifest/@android:versionName') | |
| VERSION_CODE=$(${BUNDLETOOL} dump manifest --bundle ${PKG_FILE} --xpath '/manifest/@android:versionCode') | |
| APPLICATION_ID=$(${BUNDLETOOL} dump manifest --bundle ${PKG_FILE} --xpath '/manifest/@package') | |
| # Unfortunately no application label in the bundle | |
| case "$APPLICATION_ID" in | |
| net.thunderbird.android) APPLICATION_LABEL="Thunderbird" ;; | |
| net.thunderbird.android.beta) APPLICATION_LABEL="Thunderbird Beta" ;; | |
| net.thunderbird.android.daily) APPLICATION_LABEL="Thunderbird Daily" ;; | |
| com.fsck.k9) APPLICATION_LABEL="K-9 Mail" ;; | |
| esac | |
| fi | |
| echo "TAG_NAME=${APP_NAME^^}_${VERSION_NAME//./_}" >> $GITHUB_OUTPUT | |
| echo "FULL_VERSION_NAME=${APPLICATION_LABEL} ${VERSION_NAME}" >> $GITHUB_OUTPUT | |
| echo "VERSION_NAME=${VERSION_NAME}" >> $GITHUB_OUTPUT | |
| echo "VERSION_CODE=${VERSION_CODE}" >> $GITHUB_OUTPUT | |
| echo "APPLICATION_ID=${APPLICATION_ID}" >> $GITHUB_OUTPUT | |
| cat $GITHUB_OUTPUT | |
| - name: Rename release assets | |
| id: rename | |
| shell: bash | |
| env: | |
| VERSION_NAME: ${{ steps.pkginfo.outputs.VERSION_NAME }} | |
| run: | | |
| PKG_FILE="${APP_NAME}-${PACKAGE_FLAVOR}-${RELEASE_TYPE}.${PACKAGE_FORMAT}" | |
| PKG_FILE_PRETTY="${APP_NAME}-${VERSION_NAME}.${PACKAGE_FORMAT}" | |
| mv uploads/${PKG_FILE} uploads/${PKG_FILE_PRETTY} | |
| echo "PKG_FILE=${PKG_FILE_PRETTY}" >> $GITHUB_OUTPUT | |
| ls -l uploads/${PKG_FILE_PRETTY} | |
| - name: App Token Generate | |
| uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 | |
| if: ${{ contains(matrix.releaseTarget, 'github') && vars.BOT_CLIENT_ID }} | |
| id: app-token | |
| with: | |
| app-id: ${{ vars.BOT_CLIENT_ID }} | |
| private-key: ${{ secrets.BOT_PRIVATE_KEY }} | |
| - name: Publish to GitHub Releases | |
| id: publish_gh | |
| if: ${{ contains(matrix.releaseTarget, 'github') }} | |
| uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 | |
| with: | |
| token: ${{ steps.app-token.outputs.token || github.token }} | |
| target_commitish: ${{ steps.shanotes.outputs.app_sha }} | |
| tag_name: ${{ steps.pkginfo.outputs.TAG_NAME }} | |
| name: ${{ steps.pkginfo.outputs.FULL_VERSION_NAME }} | |
| body: ${{ steps.shanotes.outputs.app_github_notes }} | |
| prerelease: ${{ env.RELEASE_TYPE != 'release' }} | |
| fail_on_unmatched_files: true | |
| files: | | |
| uploads/${{ steps.rename.outputs.PKG_FILE }} | |
| - name: Adjust release notes for play store upload | |
| if: ${{ !inputs.skipGooglePlay && contains(matrix.releaseTarget, 'play') && matrix.playTargetTrack }} | |
| shell: bash | |
| env: | |
| VERSION_CODE: ${{ steps.pkginfo.outputs.VERSION_CODE }} | |
| VERSION_NAME: ${{ steps.pkginfo.outputs.VERSION_NAME }} | |
| APPLICATION_ID: ${{ steps.pkginfo.outputs.APPLICATION_ID }} | |
| REPO: ${{ github.repository }} | |
| APP_SHA: ${{ steps.shanotes.outputs.app_sha }} | |
| RELEASE_TYPE: ${{ env.RELEASE_TYPE }} | |
| APP_NAME: ${{ matrix.appName }} | |
| run: | | |
| # r0adkll/upload-google-play expects the release notes in a different structure | |
| mkdir whatsnew | |
| if [[ "$RELEASE_TYPE" = "beta" || "$RELEASE_TYPE" = "release" ]]; then | |
| FILEPATH=app-metadata/${APPLICATION_ID}/en-US/changelogs/${VERSION_CODE}.txt | |
| wget --header="Accept: application/vnd.github.v3.raw" -O whatsnew/whatsnew-en-US https://api.github.com/repos/${REPO}/contents/${FILEPATH}?ref=${APP_SHA} | |
| elif [[ "$RELEASE_TYPE" = "daily" ]]; then | |
| COMMIT_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}" | |
| WORKFLOW_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" | |
| sed 's/^[[:space:]]*//' > whatsnew/whatsnew-en-US <<EOF | |
| ${APP_NAME^} ${VERSION_NAME} (${VERSION_CODE}) Daily Build | |
| The Daily version is an unstable testing and development platform, make sure you back up important data regularly! | |
| If you find any issues, get in touch at https://github.com/thunderbird/thunderbird-android/issues/ | |
| - Built from GitHub commit: ${COMMIT_URL} | |
| - Built with GitHub workflow: ${WORKFLOW_URL} | |
| EOF | |
| fi | |
| - name: Publish to Google Play | |
| id: publish_play | |
| uses: r0adkll/upload-google-play@935ef9c68bb393a8e6116b1575626a7f5be3a7fb # v1.1.3 | |
| if: ${{ !inputs.skipGooglePlay && contains(matrix.releaseTarget, 'play') && matrix.playTargetTrack }} | |
| with: | |
| serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_ACCOUNT }} | |
| packageName: ${{ steps.pkginfo.outputs.APPLICATION_ID }} | |
| track: ${{ matrix.playTargetTrack }} | |
| releaseName: ${{ steps.pkginfo.outputs.VERSION_NAME }} (${{ steps.pkginfo.outputs.VERSION_CODE }}) | |
| status: completed | |
| changesNotSentForReview: ${{ inputs.draftGooglePlay }} | |
| whatsNewDirectory: whatsnew | |
| releaseFiles: | | |
| uploads/${{ steps.rename.outputs.PKG_FILE }} | |
| - name: Generate source tar | |
| id: generate_tar | |
| if: ${{ !inputs.skipFtp && contains(matrix.releaseTarget, 'ftp') && matrix.packageFormat == 'apk' }} | |
| shell: bash | |
| env: | |
| APP_SHA: ${{ steps.shanotes.outputs.app_sha }} | |
| TAG_NAME: ${{ steps.pkginfo.outputs.TAG_NAME }} | |
| run: | | |
| FTP_TAR_FILENAME=$(echo "${TAG_NAME,,}.tar.gz" | sed 's/_/-/g') | |
| git archive --format=tar.gz --output=uploads/${FTP_TAR_FILENAME} ${APP_SHA} | |
| ls -l uploads | |
| echo "FTP_TAR_FILENAME=${FTP_TAR_FILENAME}" | tee -a $GITHUB_OUTPUT | |
| - name: Prepare for FTP Uploads | |
| id: prepare_ftp | |
| if: ${{ !inputs.skipFtp && contains(matrix.releaseTarget, 'ftp') && matrix.packageFormat == 'apk' }} | |
| shell: bash | |
| env: | |
| RELEASE_TYPE: ${{ env.RELEASE_TYPE }} | |
| VERSION_NAME: ${{ steps.pkginfo.outputs.VERSION_NAME }} | |
| UPLOAD_FTP_STAGE: ${{ inputs.uploadFtpStage }} | |
| PKG_FILE: ${{ steps.rename.outputs.PKG_FILE }} | |
| FTP_TAR_FILENAME: ${{ steps.generate_tar.outputs.FTP_TAR_FILENAME }} | |
| run: | | |
| GCP_ENV=prod | |
| GCP_PROJECT=moz-fx-productdelivery-pr-38b5 | |
| FTP_URL_PREFIX="https://ftp.mozilla.org/pub/thunderbird-mobile/android" | |
| if [[ "$UPLOAD_FTP_STAGE" = "true" ]]; then | |
| GCP_ENV=stage | |
| GCP_PROJECT=moz-fx-productdelivery-no-7d6a | |
| FTP_URL_PREFIX="https://ftp.stage.mozaws.net/pub/thunderbird-mobile/android" | |
| fi | |
| SERVICE_ACCOUNT="thunderbird-team-${GCP_ENV}@${GCP_PROJECT}.iam.gserviceaccount.com" | |
| WORKLOAD_IDENTITY_PROVIDER="projects/324168772199/locations/global/workloadIdentityPools/github-actions/providers/github-actions" | |
| FTP_SUBDIR="releases" | |
| FTP_LOCAL_PATH="ftp/${FTP_SUBDIR}/${VERSION_NAME}" | |
| FTP_DESTINATION="${GCP_PROJECT}-productdelivery/pub/thunderbird-mobile/android/${FTP_SUBDIR}/${VERSION_NAME}" | |
| FTP_URL_PATH="${FTP_URL_PREFIX}/${FTP_SUBDIR}/${VERSION_NAME}/" | |
| if [[ "$RELEASE_TYPE" = "daily" ]]; then | |
| YEAR=$(date -d "${RELEASE_DATE}" +"%Y") | |
| MONTH=$(date -d "${RELEASE_DATE}" +%m) | |
| DAY=$(date -d "${RELEASE_DATE}" +"%d") | |
| HOUR=$(date -d "${RELEASE_DATE}" +"%H") | |
| MIN=$(date -d "${RELEASE_DATE}" +"%M") | |
| SEC=$(date -d "${RELEASE_DATE}" +"%S") | |
| DATETIME_SUBDIR="${YEAR}/${MONTH}/${YEAR}-${MONTH}-${DAY}-${HOUR}-${MIN}-${SEC}-main" | |
| FTP_SUBDIR="nightly" | |
| FTP_LOCAL_PATH="ftp/${FTP_SUBDIR}/${DATETIME_SUBDIR}" | |
| FTP_LOCAL_PATH_NIGHTLY_LATEST="ftp/${FTP_SUBDIR}/latest-main" | |
| FTP_DESTINATION="${GCP_PROJECT}-productdelivery/pub/thunderbird-mobile/android/${FTP_SUBDIR}/${DATETIME_SUBDIR}" | |
| FTP_DESTINATION_NIGHTLY_LATEST="${GCP_PROJECT}-productdelivery/pub/thunderbird-mobile/android/${FTP_SUBDIR}/latest-main" | |
| FTP_URL_PATH="${FTP_URL_PREFIX}/${FTP_SUBDIR}/${DATETIME_SUBDIR}/" | |
| FTP_URL_PATH_NIGHTLY_LATEST="${FTP_URL_PREFIX}/${FTP_SUBDIR}/latest-main/" | |
| fi | |
| mkdir -p "${FTP_LOCAL_PATH}" | |
| cp "uploads/${PKG_FILE}" "${FTP_LOCAL_PATH}" | |
| cp "uploads/${FTP_TAR_FILENAME}" "${FTP_LOCAL_PATH}" | |
| echo "$(pwd)/${FTP_LOCAL_PATH}/" | sed -e "s/\.\///" | |
| ls "${FTP_LOCAL_PATH}" | |
| if [[ "$RELEASE_TYPE" = "daily" ]]; then | |
| mkdir -p "${FTP_LOCAL_PATH_NIGHTLY_LATEST}" | |
| cp "uploads/${PKG_FILE}" "${FTP_LOCAL_PATH_NIGHTLY_LATEST}" | |
| cp "uploads/${FTP_TAR_FILENAME}" "${FTP_LOCAL_PATH_NIGHTLY_LATEST}" | |
| echo "$(pwd)/${FTP_LOCAL_PATH}/" | sed -e "s/\.\///" | |
| ls "${FTP_LOCAL_PATH}" | |
| fi | |
| echo "SERVICE_ACCOUNT=${SERVICE_ACCOUNT}" | tee -a $GITHUB_OUTPUT | |
| echo "WORKLOAD_IDENTITY_PROVIDER=${WORKLOAD_IDENTITY_PROVIDER}" | tee -a $GITHUB_OUTPUT | |
| echo "FTP_LOCAL_PATH=${FTP_LOCAL_PATH}" | tee -a $GITHUB_OUTPUT | |
| echo "FTP_LOCAL_PATH_NIGHTLY_LATEST=${FTP_LOCAL_PATH_NIGHTLY_LATEST}" | tee -a $GITHUB_OUTPUT | |
| echo "FTP_DESTINATION=${FTP_DESTINATION}" | tee -a $GITHUB_OUTPUT | |
| echo "FTP_DESTINATION_NIGHTLY_LATEST=${FTP_DESTINATION_NIGHTLY_LATEST}" | tee -a $GITHUB_OUTPUT | |
| echo "FTP_URL_PATH=${FTP_URL_PATH}" | tee -a $GITHUB_OUTPUT | |
| echo "FTP_URL_PATH_NIGHTLY_LATEST=${FTP_URL_PATH_NIGHTLY_LATEST}" | tee -a $GITHUB_OUTPUT | |
| - name: Auth to GCS for FTP | |
| if: ${{ !inputs.skipFtp && contains(matrix.releaseTarget, 'ftp') && matrix.packageFormat == 'apk' }} | |
| uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 | |
| with: | |
| service_account: ${{ steps.prepare_ftp.outputs.SERVICE_ACCOUNT }} | |
| workload_identity_provider: ${{ steps.prepare_ftp.outputs.WORKLOAD_IDENTITY_PROVIDER }} | |
| gzip: false | |
| - name: GCS Upload of APK Package to FTP | |
| if: ${{ !inputs.skipFtp && contains(matrix.releaseTarget, 'ftp') && matrix.packageFormat == 'apk' }} | |
| uses: google-github-actions/upload-cloud-storage@6397bd7208e18d13ba2619ee21b9873edc94427a # v3.0.0 | |
| with: | |
| path: ${{ steps.prepare_ftp.outputs.FTP_LOCAL_PATH }}/${{ steps.rename.outputs.PKG_FILE }} | |
| destination: ${{ steps.prepare_ftp.outputs.FTP_DESTINATION }} | |
| gzip: false | |
| - name: GCS Upload of APK Package to FTP Nightly Latest | |
| if: ${{ !inputs.skipFtp && contains(matrix.releaseTarget, 'ftp') && matrix.packageFormat == 'apk' && needs.dump_config.outputs.releaseType == 'daily'}} | |
| uses: google-github-actions/upload-cloud-storage@6397bd7208e18d13ba2619ee21b9873edc94427a # v3.0.0 | |
| with: | |
| path: ${{ steps.prepare_ftp.outputs.FTP_LOCAL_PATH_NIGHTLY_LATEST }}/${{ steps.rename.outputs.PKG_FILE }} | |
| destination: ${{ steps.prepare_ftp.outputs.FTP_DESTINATION_NIGHTLY_LATEST }} | |
| gzip: false | |
| - name: GCS Upload of Source Tar to FTP | |
| if: ${{ !inputs.skipFtp && contains(matrix.releaseTarget, 'ftp') && matrix.packageFormat == 'apk' }} | |
| uses: google-github-actions/upload-cloud-storage@6397bd7208e18d13ba2619ee21b9873edc94427a # v3.0.0 | |
| with: | |
| path: ${{ steps.prepare_ftp.outputs.FTP_LOCAL_PATH }}/${{ steps.generate_tar.outputs.FTP_TAR_FILENAME }} | |
| destination: ${{ steps.prepare_ftp.outputs.FTP_DESTINATION }} | |
| gzip: false | |
| - name: GCS Upload of Source Tar to FTP Nightly Latest | |
| if: ${{ !inputs.skipFtp && contains(matrix.releaseTarget, 'ftp') && matrix.packageFormat == 'apk' && needs.dump_config.outputs.releaseType == 'daily'}} | |
| uses: google-github-actions/upload-cloud-storage@6397bd7208e18d13ba2619ee21b9873edc94427a # v3.0.0 | |
| with: | |
| path: ${{ steps.prepare_ftp.outputs.FTP_LOCAL_PATH_NIGHTLY_LATEST }}/${{ steps.generate_tar.outputs.FTP_TAR_FILENAME }} | |
| destination: ${{ steps.prepare_ftp.outputs.FTP_DESTINATION_NIGHTLY_LATEST }} | |
| gzip: false | |
| - name: Summary | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| id: summary | |
| env: | |
| tagName: ${{ steps.pkginfo.outputs.TAG_NAME }} | |
| fullVersionName: ${{ steps.pkginfo.outputs.FULL_VERSION_NAME }} | |
| ghReleaseUrl: ${{ steps.publish_gh.outputs.url }} | |
| playTargetTrack: ${{ matrix.playTargetTrack }} | |
| applicationId: ${{ steps.pkginfo.outputs.APPLICATION_ID }} | |
| releaseTarget: ${{ matrix.releaseTarget }} | |
| releaseType: ${{ env.RELEASE_TYPE }} | |
| appSha: ${{ steps.shanotes.outputs.app_sha }} | |
| appName: ${{ matrix.appName }} | |
| skipGooglePlay: ${{ inputs.skipGooglePlay }} | |
| ftpUrlPath: ${{ steps.prepare_ftp.outputs.FTP_URL_PATH }} | |
| ftpUrlPathNightlyLatest: ${{ steps.prepare_ftp.outputs.FTP_URL_PATH_NIGHTLY_LATEST }} | |
| skipFtp: ${{ inputs.skipFtp }} | |
| packageFormat: ${{ matrix.packageFormat }} | |
| with: | |
| script: | | |
| await core.summary | |
| .addHeading(`${process.env.fullVersionName} (${process.env.applicationId})`, 2) | |
| .write(); | |
| core.setOutput(`${process.env.appName}_full_version_name`, process.env.fullVersionName); | |
| if (!process.env.releaseTarget) { | |
| await core.summary | |
| .addRaw(`Artifact-only build at `) | |
| .addLink(process.env.appSha, `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/commit/${process.env.appSha}`) | |
| .addEOL() | |
| .write(); | |
| } else if (process.env.ghReleaseUrl) { | |
| await core.summary | |
| .addRaw(`Tag ${process.env.tagName} at `) | |
| .addLink(process.env.appSha, `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/commit/${process.env.appSha}`) | |
| .addEOL() | |
| .addRaw(`Released to Github at `) | |
| .addLink(process.env.ghReleaseUrl, process.env.ghReleaseUrl) | |
| .addEOL() | |
| .write(); | |
| core.setOutput(`${process.env.appName}_release_url`, process.env.ghReleaseUrl); | |
| } | |
| if (process.env.skipGooglePlay != "true" && process.env.playTargetTrack && process.env.releaseTarget.includes("play")) { | |
| await core.summary.addRaw(`Released to the <b>${process.env.playTargetTrack}</b> track on Google Play`, true).write(); | |
| } | |
| if (process.env.skipFtp != "true" && process.env.releaseTarget.includes("ftp") && process.env.packageFormat == 'apk') { | |
| await core.summary | |
| .addRaw(`Published to FTP at`) | |
| .addEOL() | |
| .addLink(process.env.ftpUrlPath, process.env.ftpUrlPath) | |
| .addEOL(); | |
| if (process.env.releaseType == 'daily') { | |
| await core.summary | |
| .addLink(process.env.ftpUrlPathNightlyLatest, process.env.ftpUrlPathNightlyLatest) | |
| .addEOL(); | |
| } | |
| await core.summary.write(); | |
| } | |
| notify_build_result: | |
| name: Notify Build Result | |
| if: ${{ always() }} | |
| needs: [ dump_config, release_commit, build_unsigned, sign_mobile, publish_release, notify_build_start ] | |
| runs-on: ubuntu-latest | |
| environment: notify_matrix | |
| steps: | |
| - name: Get previous workflow status | |
| uses: Mercymeilya/last-workflow-status@3418710aefe8556d73b6f173a0564d38bcfd9a43 | |
| id: last_status | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Info | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| id: info | |
| env: | |
| needs: ${{ toJSON(needs) }} | |
| with: | |
| script: | | |
| let needs = JSON.parse(process.env.needs); | |
| let failures = []; | |
| for (let [job, need] of Object.entries(needs)) { | |
| if (need.result == 'failure') { | |
| failures.push(job.replace(/([-_])/g, "\\$1")); | |
| } | |
| } | |
| core.setOutput("fail_steps", failures.join(`, `)); | |
| - name: Notify Failure | |
| if: ${{ vars.MATRIX_NOTIFY_ROOM && contains(needs.*.result, 'failure') }} | |
| uses: kewisch/action-matrix-notify@3c45d89acd032c84b955b54c8a9001833ac91d17 # v1 | |
| with: | |
| matrixHomeserver: ${{ vars.MATRIX_NOTIFY_HOMESERVER }} | |
| matrixRoomId: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| matrixToken: ${{ secrets.MATRIX_NOTIFY_TOKEN }} | |
| message: >- | |
| 🔴 [${{ needs.dump_config.outputs.releaseType }} build ${{ github.run_number}}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| has failed at step ${{ steps.info.outputs.fail_steps }} (triggered by ${{ needs.notify_build_start.outputs.actorLink }}) | |
| - name: Notify Cancelled | |
| if: ${{ vars.MATRIX_NOTIFY_ROOM && !contains(needs.*.result, 'failure') && contains(needs.*.result, 'cancelled') }} | |
| uses: kewisch/action-matrix-notify@3c45d89acd032c84b955b54c8a9001833ac91d17 # v1 | |
| with: | |
| matrixHomeserver: ${{ vars.MATRIX_NOTIFY_HOMESERVER }} | |
| matrixRoomId: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| matrixToken: ${{ secrets.MATRIX_NOTIFY_TOKEN }} | |
| message: >- | |
| ⚪ [${{ needs.dump_config.outputs.releaseType }} build ${{ github.run_number}}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| was cancelled | |
| - name: Notify Success (Beta/Release) | |
| if: "${{ vars.MATRIX_NOTIFY_ROOM && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && contains(fromJSON('[\"beta\", \"release\"]'), needs.dump_config.outputs.releaseType) }}" | |
| uses: kewisch/action-matrix-notify@3c45d89acd032c84b955b54c8a9001833ac91d17 # v1 | |
| with: | |
| matrixHomeserver: ${{ vars.MATRIX_NOTIFY_HOMESERVER }} | |
| matrixRoomId: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| matrixToken: ${{ secrets.MATRIX_NOTIFY_TOKEN }} | |
| message: >- | |
| 🟢 [${{ needs.dump_config.outputs.releaseType }} build ${{ github.run_number}}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| has succeeded (triggered by ${{ needs.notify_build_start.outputs.actorLink }}) | |
| - name: Thunderbird Publish URL (Beta/Release) | |
| if: ${{ vars.MATRIX_NOTIFY_ROOM && needs.publish_release.outputs.thunderbird_release_url && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} | |
| uses: kewisch/action-matrix-notify@3c45d89acd032c84b955b54c8a9001833ac91d17 # v1 | |
| with: | |
| matrixHomeserver: ${{ vars.MATRIX_NOTIFY_HOMESERVER }} | |
| matrixRoomId: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| matrixToken: ${{ secrets.MATRIX_NOTIFY_TOKEN }} | |
| message: >- | |
| ${{ needs.publish_release.outputs.thunderbird_full_version_name }} [is available](${{ needs.publish_release.outputs.thunderbird_release_url }}) | |
| - name: K-9 Mail Publish URL (Beta/Release) | |
| if: ${{ vars.MATRIX_NOTIFY_ROOM && needs.publish_release.outputs.k9mail_release_url && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} | |
| uses: kewisch/action-matrix-notify@3c45d89acd032c84b955b54c8a9001833ac91d17 # v1 | |
| with: | |
| matrixHomeserver: ${{ vars.MATRIX_NOTIFY_HOMESERVER }} | |
| matrixRoomId: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| matrixToken: ${{ secrets.MATRIX_NOTIFY_TOKEN }} | |
| message: >- | |
| ${{ needs.publish_release.outputs.k9mail_full_version_name }} [is available](${{ needs.publish_release.outputs.k9mail_release_url }}) | |
| - name: Notify Success (Daily) | |
| if: ${{ vars.MATRIX_NOTIFY_ROOM && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && needs.dump_config.outputs.releaseType == 'daily' && steps.last_status.outputs.last_status == 'failure' }} | |
| uses: kewisch/action-matrix-notify@3c45d89acd032c84b955b54c8a9001833ac91d17 # v1 | |
| with: | |
| matrixHomeserver: ${{ vars.MATRIX_NOTIFY_HOMESERVER }} | |
| matrixRoomId: ${{ vars.MATRIX_NOTIFY_ROOM }} | |
| matrixToken: ${{ secrets.MATRIX_NOTIFY_TOKEN }} | |
| message: >- | |
| 🟢 [${{ needs.dump_config.outputs.releaseType }} build ${{ github.run_number}}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| has recovered |