Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/android-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ name: Android Build & Deploy
on:
workflow_dispatch:

permissions:
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v6

- name: set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
Expand Down Expand Up @@ -39,6 +43,7 @@ jobs:
- name: Build & deploy Android release
run: bundle exec fastlane android deploy
env:
ALLOW_LEGACY_PRODUCTION: "true"
ANDROID_KEYSTORE: "../android_keystore.jks"
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS}}
Expand Down
15 changes: 11 additions & 4 deletions .github/workflows/android-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ on:
push:
branches: [ "**" ]

permissions:
contents: read

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
Expand All @@ -23,5 +26,9 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew test
env:
ANDROID_KEYSTORE: "../android_keystore.jks"
- uses: actions/upload-artifact@v6
if: always()
with:
name: android-test-reports
path: app/build/reports/tests/
retention-days: 14
73 changes: 73 additions & 0 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Create Android Release

on:
workflow_dispatch:
inputs:
version:
description: Expected versionName without the v prefix
required: true
type: string
commit_sha:
description: Exact merged master commit to tag
required: true
type: string

permissions:
contents: write

jobs:
create-tag:
runs-on: ubuntu-latest
environment: release
outputs:
tag: ${{ steps.identity.outputs.tag }}
steps:
- uses: actions/checkout@v6
with:
ref: master
fetch-depth: 0
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.6
bundler-cache: true
- name: Verify exact master commit
env:
EXPECTED_SHA: ${{ inputs.commit_sha }}
run: |
git fetch origin master --tags
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
test "$(git rev-parse origin/master)" = "$EXPECTED_SHA"
- name: Resolve and verify identity
id: identity
env:
EXPECTED_VERSION: ${{ inputs.version }}
run: |
python3 scripts/release_tool.py metadata > release-metadata.json
ACTUAL_VERSION=$(python3 -c 'import json; print(json.load(open("release-metadata.json"))["versionName"])')
TAG=$(python3 -c 'import json; print(json.load(open("release-metadata.json"))["tag"])')
test "$ACTUAL_VERSION" = "$EXPECTED_VERSION"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Decode Google Play service account
env:
SERVICE_ACCOUNT_BASE64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
run: printf '%s' "$SERVICE_ACCOUNT_BASE64" | base64 --decode > service-account.json
- name: Repeat repository and Google Play validation
run: >-
python3 scripts/release_tool.py validate --skip-branch --check-play
--service-account service-account.json
- name: Create immutable annotated tag
env:
TAG: ${{ steps.identity.outputs.tag }}
EXPECTED_SHA: ${{ inputs.commit_sha }}
run: |
test -z "$(git tag --list "$TAG")"
git tag -a "$TAG" "$EXPECTED_SHA" -m "Release $TAG"
git push origin "refs/tags/$TAG"

publish:
needs: create-tag
uses: ./.github/workflows/tagged-release.yml
with:
tag: ${{ needs.create-tag.outputs.tag }}
secrets: inherit

54 changes: 54 additions & 0 deletions .github/workflows/promote-production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Promote Android Production

on:
workflow_dispatch:
inputs:
tag:
description: Immutable v<versionName> release tag
required: true
type: string
version_code:
description: Existing internal-track versionCode
required: true
type: number

permissions:
contents: read

jobs:
promote:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.tag }}
fetch-depth: 0
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.6
bundler-cache: true
- name: Verify tag, commit, and versionCode
env:
TAG: ${{ inputs.tag }}
EXPECTED_CODE: ${{ inputs.version_code }}
run: |
git fetch origin --tags
test "$(git rev-parse HEAD)" = "$(git rev-list -n 1 "$TAG")"
test "$(python3 scripts/release_tool.py metadata | python3 -c 'import json,sys; print(json.load(sys.stdin)["tag"])')" = "$TAG"
test "$(python3 scripts/release_tool.py metadata | python3 -c 'import json,sys; print(json.load(sys.stdin)["versionCode"])')" = "$EXPECTED_CODE"
- name: Decode Google Play service account
env:
SERVICE_ACCOUNT_BASE64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
run: printf '%s' "$SERVICE_ACCOUNT_BASE64" | base64 --decode > service-account.json
- name: Require the exact version on internal
run: >-
bundle exec fastlane android assert_track_version_code
version_code:${{ inputs.version_code }} track:internal
json_key:${{ github.workspace }}/service-account.json
- name: Promote existing internal artifact to completed production
run: >-
bundle exec fastlane android promote_release
version_code:${{ inputs.version_code }}
json_key:${{ github.workspace }}/service-account.json

43 changes: 43 additions & 0 deletions .github/workflows/release-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Validate Android Release PR

on:
pull_request:
branches: [master]

permissions:
contents: read

jobs:
validate:
if: startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'hotfix/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
cache: gradle
- name: Validate release identity and localized notes
run: python3 scripts/release_tool.py validate
- name: Test release tooling
run: python3 -m unittest scripts/test_release_tool.py
- name: Test, lint, and assemble release outputs
run: ./gradlew test lint assembleRelease bundleRelease --no-daemon
- name: Verify assembled release identity
run: >-
python3 scripts/release_tool.py verify-build
--manifest app/build/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml
--metadata app/build/intermediates/merged_manifests/release/processReleaseManifest/output-metadata.json
- uses: actions/upload-artifact@v6
if: always()
with:
name: release-pr-validation
path: |
app/build/reports/
app/build/outputs/apk/release/
app/build/outputs/bundle/release/
retention-days: 14
32 changes: 32 additions & 0 deletions .github/workflows/sync-master-to-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Sync Released Master to Dev

on:
workflow_dispatch:
inputs:
tag:
description: Released v<versionName> tag on master
required: true
type: string

permissions:
contents: write

jobs:
sync:
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/checkout@v6
with:
ref: dev
fetch-depth: 0
- name: Merge released master into dev
env:
TAG: ${{ inputs.tag }}
run: |
git fetch origin master dev --tags
test "$(git rev-list -n 1 "$TAG")" = "$(git rev-parse origin/master)"
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git merge --no-ff origin/master -m "Merge released $TAG back into dev"
git push origin HEAD:dev
109 changes: 109 additions & 0 deletions .github/workflows/tagged-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Publish Tagged Android Release

on:
push:
tags: ['v*']
workflow_call:
inputs:
tag:
required: true
type: string

permissions:
contents: write

jobs:
publish:
runs-on: ubuntu-latest
environment: release
steps:
- name: Resolve requested tag
id: requested
env:
INPUT_TAG: ${{ inputs.tag }}
REF_TAG: ${{ github.ref_name }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$EVENT_NAME" = "workflow_call" ]; then TAG="$INPUT_TAG"; else TAG="$REF_TAG"; fi
case "$TAG" in v[0-9]*.[0-9]*.[0-9]*) ;; *) exit 2 ;; esac
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v6
with:
ref: ${{ steps.requested.outputs.tag }}
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
cache: gradle
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.6
bundler-cache: true
- name: Verify tag and release identity
env:
TAG: ${{ steps.requested.outputs.tag }}
run: |
git fetch origin --tags
test "$(git rev-parse HEAD)" = "$(git rev-list -n 1 "$TAG")"
test "$(git cat-file -t "$TAG")" = "tag"
test "$(python3 scripts/release_tool.py metadata | python3 -c 'import json,sys; print(json.load(sys.stdin)["tag"])')" = "$TAG"
python3 scripts/release_tool.py validate --skip-branch --allow-existing-tag
- name: Detect an existing complete GitHub Release
id: existing
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.requested.outputs.tag }}
run: |
mkdir -p release-artifacts
if gh release view "$TAG" >/dev/null 2>&1; then
gh release download "$TAG" --dir release-artifacts
(cd release-artifacts && sha256sum --check SHA256SUMS)
echo "complete=true" >> "$GITHUB_OUTPUT"
else
echo "complete=false" >> "$GITHUB_OUTPUT"
fi
- name: Decode Android keystore
if: steps.existing.outputs.complete != 'true'
env:
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: printf '%s' "$KEYSTORE_BASE64" | base64 --decode > android-keystore.jks
- name: Build signed APK and AAB once
if: steps.existing.outputs.complete != 'true'
env:
ANDROID_KEYSTORE: ${{ github.workspace }}/android-keystore.jks
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
TAG: ${{ steps.requested.outputs.tag }}
run: |
./gradlew clean test lint assembleRelease bundleRelease --no-daemon
cp app/build/outputs/apk/release/app-release.apk "release-artifacts/TextPad-$TAG.apk"
cp app/build/outputs/bundle/release/app-release.aab "release-artifacts/TextPad-$TAG.aab"
(cd release-artifacts && sha256sum "TextPad-$TAG.apk" "TextPad-$TAG.aab" > SHA256SUMS)
(cd release-artifacts && sha256sum --check SHA256SUMS)
- name: Publish GitHub Release and immutable assets
if: steps.existing.outputs.complete != 'true'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.requested.outputs.tag }}
run: |
CODE=$(python3 scripts/release_tool.py metadata | python3 -c 'import json,sys; print(json.load(sys.stdin)["versionCode"])')
NOTES="fastlane/metadata/android/en-US/changelogs/$CODE.txt"
gh release create "$TAG" release-artifacts/* --verify-tag --title "$TAG" --notes-file "$NOTES"
- name: Decode Google Play service account
env:
SERVICE_ACCOUNT_BASE64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
run: printf '%s' "$SERVICE_ACCOUNT_BASE64" | base64 --decode > service-account.json
- name: Upload the published AAB to Play internal
env:
TAG: ${{ steps.requested.outputs.tag }}
run: |
CODE=$(python3 scripts/release_tool.py metadata | python3 -c 'import json,sys; print(json.load(sys.stdin)["versionCode"])')
bundle exec fastlane android upload_release \
apk:${{ github.workspace }}/release-artifacts/TextPad-$TAG.apk \
aab:${{ github.workspace }}/release-artifacts/TextPad-$TAG.aab \
version_code:$CODE \
metadata_path:${{ github.workspace }}/fastlane/metadata/android \
json_key:${{ github.workspace }}/service-account.json \
track:internal
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@
/releases
images/scaled
scripts/artifacts
scripts/dist
scripts/dist
scripts/__pycache__/
*.pyc
.DS_Store
Loading
Loading