Shippable Build & Signing #16
This file contains 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 | |
jobs: | |
get_environment: | |
name: Determine Release Environment | |
runs-on: ubuntu-latest | |
outputs: | |
releaseEnv: ${{ steps.releaseEnv.outputs.result }} | |
steps: | |
- uses: actions/github-script@v7 | |
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 }} | |
releaseType: ${{ vars.RELEASE_TYPE }} | |
steps: | |
- name: Show Environment | |
uses: actions/github-script@v7 | |
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 }} | |
with: | |
script: | | |
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("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 || "default" }, | |
{ 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(); | |
} | |
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@v7 | |
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@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] | |
if: ${{ needs.dump_config.outputs.releaseType == 'beta' || needs.dump_config.outputs.releaseType == 'release' }} | |
environment: ${{ needs.get_environment.outputs.releaseEnv }} | |
strategy: | |
matrix: | |
include: "${{ fromJSON(needs.dump_config.outputs.matrixInclude) }}" | |
permissions: | |
contents: write | |
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 }} | |
steps: | |
- name: Checkout repository | |
if: ${{ contains(matrix.releaseTarget, 'github') }} | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: Copy CI gradle.properties | |
if: ${{ contains(matrix.releaseTarget, 'github') }} | |
shell: bash | |
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties | |
- uses: actions/setup-java@v4 | |
if: ${{ contains(matrix.releaseTarget, 'github') }} | |
with: | |
distribution: temurin | |
java-version: 17 | |
- name: Setup Gradle | |
uses: gradle/actions/setup-gradle@v4 | |
if: ${{ contains(matrix.releaseTarget, 'github') }} | |
with: | |
cache-disabled: ${{ needs.dump_config.outputs.releaseType == 'beta' || needs.dump_config.outputs.releaseType == 'release' }} | |
add-job-summary: never | |
- name: Get application info | |
id: appinfo | |
shell: bash | |
if: ${{ contains(matrix.releaseTarget, 'github') }} | |
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 -PbuildType=${RELEASE_TYPE} -PflavorName=${PACKAGE_FLAVOR} --configure-on-demand | |
- name: Bump version code | |
id: bump_version_code | |
if: ${{ contains(matrix.releaseTarget, 'github') }} | |
shell: bash | |
env: | |
APP_NAME: ${{ matrix.appName }} | |
OLD_VERSION_CODE: ${{ steps.appinfo.outputs.VERSION_CODE }} | |
run: | | |
NEW_VERSION_CODE=$(($OLD_VERSION_CODE + 1)) | |
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 "CODE=${NEW_VERSION_CODE}" | tee $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') }} | |
shell: bash | |
env: | |
APP_NAME: ${{ matrix.appName }} | |
APPLICATION_ID: ${{ steps.appinfo.outputs.APPLICATION_ID }} | |
APPLICATION_LABEL: ${{ steps.appinfo.outputs.APPLICATION_LABEL }} | |
VERSION_CODE: ${{ steps.bump_version_code.outputs.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/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') }} | |
shell: bash | |
env: | |
APPLICATION_ID: ${{ steps.appinfo.outputs.APPLICATION_ID }} | |
VERSION_CODE: ${{ steps.bump_version_code.outputs.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') }} | |
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 }} | |
run: | | |
git config --global user.name "GitHub Actions Bot" | |
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
# We need the metadata to point to the right application for the release commit | |
rm metadata | |
ln -sf app-metadata/${APPLICATION_ID} metadata | |
# Add changelogs, build version changes and metadata symlink | |
git add ./app-metadata/${APPLICATION_ID}/en-US/changelogs/* | |
git add ./app-${APP_NAME}/src/main/res/raw/changelog_master.xml | |
git add ./app-${APP_NAME}/build.gradle.kts | |
git add metadata | |
# Ready to commit. Make sure to pull again to reduce likelihood of race conditions | |
git status | |
git commit -m "Release: ${APPLICATION_LABEL} ${FULL_VERSION_NAME}" | |
git pull --rebase -X ours | |
git log -n 5 | |
set +e | |
git push | |
GIT_RESULT=$? | |
set -e | |
if [ $GIT_RESULT -gt 0 ]; then | |
echo "Push rejected, trying again once in 5 seconds" | |
sleep 5 | |
git pull --rebase -X ours | |
git log -n 5 | |
git push | |
fi | |
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') }} | |
uses: actions/github-script@v7 | |
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.appinfo.outputs.VERSION_CODE }} | |
newFullVersion: ${{ steps.appinfo.outputs.VERSION_NAME }}${{ steps.bump_version_suffix.outputs.SUFFIX || steps.appinfo.outputs.VERSION_NAME_SUFFIX }} | |
newVersionCode: ${{ steps.bump_version_code.outputs.CODE }} | |
with: | |
script: | | |
let env = process.env; | |
console.log(env); | |
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 }} | |
APP_NAME: ${{ matrix.appName }} | |
run: | | |
case "${APP_NAME}" in | |
thunderbird) APP_SHA=$THUNDERBIRD_SHA ;; | |
k9mail) APP_SHA=$K9MAIL_SHA ;; | |
*) APP_SHA=$GITHUB_SHA ;; | |
esac | |
echo "app_sha=$APP_SHA" >> $GITHUB_OUTPUT | |
cat $GITHUB_OUTPUT | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ steps.sha.outputs.app_sha }} | |
- name: Copy CI gradle.properties | |
shell: bash | |
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties | |
- uses: actions/setup-java@v4 | |
with: | |
distribution: temurin | |
java-version: 17 | |
- name: Setup Gradle | |
uses: gradle/actions/setup-gradle@v4 | |
with: | |
cache-disabled: ${{ needs.dump_config.outputs.releaseType == 'beta' || needs.dump_config.outputs.releaseType == 'release' }} | |
add-job-summary: on-failure | |
- 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="assembleRelease" | |
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/release" | |
OUT_FILE="app-${APP_NAME}-release-unsigned.apk" | |
UPLOAD_FILE="${APP_NAME}-default-${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@v4 | |
env: | |
UPLOAD_PATH: "uploads" | |
with: | |
name: unsigned-${{ matrix.appName }}-${{ matrix.packageFormat }}-${{ matrix.packageFlavor || 'default' }} | |
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 || 'default' }} | |
env: | |
RELEASE_TYPE: ${{ needs.dump_config.outputs.releaseType }} | |
steps: | |
- uses: actions/download-artifact@v4 | |
with: | |
name: unsigned-${{ matrix.appName }}-${{ matrix.packageFormat }}-${{ matrix.packageFlavor || 'default' }} | |
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 || 'default' }} | |
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@v4 | |
with: | |
name: signed-${{ matrix.appName }}-${{ matrix.packageFormat }}-${{ matrix.packageFlavor || 'default' }} | |
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: ${{ needs.dump_config.outputs.releaseType == 'beta' || needs.dump_config.outputs.releaseType == 'release' }} | |
runs-on: ubuntu-latest | |
environment: notify_matrix | |
steps: | |
- uses: kewisch/action-matrix-notify@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: ${{ needs.dump_config.outputs.releaseType == 'beta' || needs.dump_config.outputs.releaseType == 'release' }} | |
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_TYPE: ${{ needs.dump_config.outputs.releaseType }} | |
APP_NAME: ${{ matrix.appName }} | |
PACKAGE_FLAVOR: ${{ matrix.packageFlavor || 'default' }} | |
PACKAGE_FORMAT: ${{ matrix.packageFormat }} | |
steps: | |
- uses: actions/download-artifact@v4 | |
with: | |
name: signed-${{ matrix.appName }}-${{ matrix.packageFormat }}-${{ matrix.packageFlavor || 'default' }} | |
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@v1 | |
if: ${{ contains(matrix.releaseTarget, 'github') && vars.RELEASER_APP_CLIENT_ID }} | |
id: app-token | |
with: | |
app-id: ${{ vars.RELEASER_APP_CLIENT_ID }} | |
private-key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }} | |
- 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 }} | |
run: | | |
app_sha_name=${APP_NAME^^}_SHA | |
echo "app_sha=${!app_sha_name}" >> $GITHUB_OUTPUT | |
app_relnotes_name=${APP_NAME^^}_GITHUB_NOTES | |
echo "app_github_notes<<EOF" >> $GITHUB_OUTPUT | |
echo "${!app_relnotes_name}" >> $GITHUB_OUTPUT | |
echo "EOF" >> $GITHUB_OUTPUT | |
cat $GITHUB_OUTPUT | |
- name: Publish to GitHub Releases | |
id: publish_gh | |
if: ${{ contains(matrix.releaseTarget, 'github') }} | |
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 | |
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 }} | |
APPLICATION_ID: ${{ steps.pkginfo.outputs.APPLICATION_ID }} | |
REPO: ${{ github.repository }} | |
APP_SHA: ${{ steps.shanotes.outputs.app_sha }} | |
run: | | |
# r0adkll/upload-google-play expects the release notes in a different structure | |
FILEPATH=app-metadata/${APPLICATION_ID}/en-US/changelogs/${VERSION_CODE}.txt | |
mkdir whatsnew | |
wget -O whatsnew/whatsnew-en-US https://raw.githubusercontent.com/${REPO}/${APP_SHA}/${FILEPATH} | |
- name: Publish to Google Play | |
id: publish_play | |
uses: r0adkll/upload-google-play@v1 | |
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 }} | |
status: completed | |
changesNotSentForReview: ${{ inputs.draftGooglePlay }} | |
whatsNewDirectory: whatsnew | |
releaseFiles: | | |
uploads/${{ steps.rename.outputs.PKG_FILE }} | |
- name: Summary | |
uses: actions/github-script@v7 | |
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 }} | |
appSha: ${{ steps.shanotes.outputs.app_sha }} | |
appName: ${{ matrix.appName }} | |
skipGooglePlay: ${{ inputs.skipGooglePlay }} | |
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(); | |
} | |
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@v7 | |
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@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@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') && (needs.dump_config.outputs.releaseType == 'beta' || needs.dump_config.outputs.releaseType == 'release') }} | |
uses: kewisch/action-matrix-notify@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@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@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@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 |