Skip to content

chore: update test dependencies and normalize app version parsing #4

chore: update test dependencies and normalize app version parsing

chore: update test dependencies and normalize app version parsing #4

name: Build & Deploy Android (gms + foss) → Open Beta [dev]
on:
push:
branches: [ dev ]
paths:
- "pubspec.yaml"
- "lib/**"
- "android/**"
- "tools/**"
- ".github/workflows/**"
workflow_dispatch:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Detect whether this push should deploy to beta ──
gate:
runs-on: ubuntu-latest
outputs:
should_deploy: ${{ steps.check.outputs.should_deploy }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 1 }
- name: Check for release guard
id: check
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
DEPLOY="false"
# For workflow_dispatch always deploy
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
DEPLOY="true"
echo "Deploying: workflow_dispatch trigger"
fi
# Check the first line of the head commit message
if [[ "$DEPLOY" == "false" ]]; then
FIRST_LINE=$(git log -1 --format='%s' HEAD)
echo "Commit subject: $FIRST_LINE"
if echo "$FIRST_LINE" | grep -qi '^release:'; then
DEPLOY="true"
echo "Deploying: commit message starts with 'release:'"
fi
fi
# Check the PR title that produced this merge commit
if [[ "$DEPLOY" == "false" ]]; then
PR_TITLE=$(gh api "/repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
--jq '.[0].title // empty' 2>/dev/null || true)
echo "PR title: $PR_TITLE"
if echo "$PR_TITLE" | grep -qi '^release:'; then
DEPLOY="true"
echo "Deploying: PR title starts with 'release:'"
fi
fi
if [[ "$DEPLOY" == "false" ]]; then
echo "No 'release:' guard found. Skipping beta upload."
fi
echo "should_deploy=$DEPLOY" >> "$GITHUB_OUTPUT"
# ── GMS build (only when deploying) ──
build-gms:
needs: gate
if: needs.gate.outputs.should_deploy == 'true'
runs-on: ubuntu-latest
env:
JAVA_VERSION: "21"
outputs:
app_version: ${{ steps.ver.outputs.app_version }}
build_number: ${{ steps.ver.outputs.build_number }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Setup Flutter (from .flutter-version)
uses: subosito/flutter-action@v2
with:
flutter-version-file: .flutter-version
channel: stable
cache: true
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Gradle wrapper validation
uses: gradle/actions/wrapper-validation@v3
- name: Write android/local.properties
shell: bash
run: |
set -e
SDK_DIR="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
FLUTTER_SDK="${FLUTTER_HOME:-$(dirname "$(dirname "$(readlink -f "$(which flutter)")")")}"
{
echo "sdk.dir=${SDK_DIR}"
echo "flutter.sdk=${FLUTTER_SDK}"
} > android/local.properties
- name: Use preinstalled NDK
shell: bash
run: |
set -euo pipefail
SDK_DIR="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
NDK_DIR="${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-${ANDROID_NDK:-${ANDROID_NDK_LATEST_HOME:-}}}}"
if [ -z "${NDK_DIR}" ]; then
NDK_DIR=$(ls -d "${SDK_DIR}/ndk/"* 2>/dev/null | sort -V | tail -1 || true)
fi
[ -n "${NDK_DIR}" ] && [ -d "${NDK_DIR}" ] || { echo "::error::No preinstalled NDK found"; exit 1; }
NDK_VER="$(basename "$NDK_DIR")"
GP=android/gradle.properties
touch "${GP}"
if grep -q '^android\.ndkVersion=' "${GP}"; then
sed -i "s/^android\.ndkVersion=.*/android.ndkVersion=${NDK_VER}/" "${GP}"
else
printf '\nandroid.ndkVersion=%s\n' "${NDK_VER}" >> "${GP}"
fi
- name: Read pubspec version
id: ver
shell: bash
run: |
VLINE=$(grep '^version:' pubspec.yaml | head -1 | awk '{print $2}')
APP_VERSION="${VLINE%%+*}"
BUILD_NUMBER="$(git rev-list --count HEAD)"
echo "app_version=$APP_VERSION" >> "$GITHUB_OUTPUT"
echo "build_number=$BUILD_NUMBER" >> "$GITHUB_OUTPUT"
- name: Flutter pub get
run: flutter pub get
- name: Run build_runner
run: dart run build_runner build --delete-conflicting-outputs
- name: Configure Gradle performance flags
run: |
sed -i '/^org\.gradle\.configuration-cache/d' android/gradle.properties || true
cat >> android/gradle.properties <<'EOF'
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configuration-cache=false
EOF
- name: Make gradlew executable
run: chmod +x android/gradlew
- name: Decode keystore
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/dawarich-upload.jks
- name: Create keystore.properties
run: |
cat > android/keystore.properties <<'EOF'
storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
storeFile=../dawarich-upload.jks
EOF
- name: Build GMS AAB (release)
run: |
flutter build appbundle --release --flavor gms \
--build-name=${{ steps.ver.outputs.app_version }} \
--build-number=${{ steps.ver.outputs.build_number }}
- name: Upload GMS artifact
uses: actions/upload-artifact@v4
with:
name: gms-aab
path: build/app/outputs/bundle/gmsRelease/*.aab
retention-days: 30
# ── FOSS build (parallel to GMS, only when deploying) ──
build-foss:
needs: gate
if: needs.gate.outputs.should_deploy == 'true'
runs-on: ubuntu-latest
env:
JAVA_VERSION: "21"
outputs:
app_version: ${{ steps.ver.outputs.app_version }}
build_number: ${{ steps.ver.outputs.build_number }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Setup Flutter (from .flutter-version)
uses: subosito/flutter-action@v2
with:
flutter-version-file: .flutter-version
channel: stable
cache: true
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Gradle wrapper validation
uses: gradle/actions/wrapper-validation@v3
- name: Write android/local.properties
shell: bash
run: |
set -e
SDK_DIR="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
FLUTTER_SDK="${FLUTTER_HOME:-$(dirname "$(dirname "$(readlink -f "$(which flutter)")")")}"
{
echo "sdk.dir=${SDK_DIR}"
echo "flutter.sdk=${FLUTTER_SDK}"
} > android/local.properties
- name: Use preinstalled NDK
shell: bash
run: |
set -euo pipefail
SDK_DIR="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
NDK_DIR="${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-${ANDROID_NDK:-${ANDROID_NDK_LATEST_HOME:-}}}}"
if [ -z "${NDK_DIR}" ]; then
NDK_DIR=$(ls -d "${SDK_DIR}/ndk/"* 2>/dev/null | sort -V | tail -1 || true)
fi
[ -n "${NDK_DIR}" ] && [ -d "${NDK_DIR}" ] || { echo "::error::No preinstalled NDK found"; exit 1; }
NDK_VER="$(basename "$NDK_DIR")"
GP=android/gradle.properties
touch "${GP}"
if grep -q '^android\.ndkVersion=' "${GP}"; then
sed -i "s/^android\.ndkVersion=.*/android.ndkVersion=${NDK_VER}/" "${GP}"
else
printf '\nandroid.ndkVersion=%s\n' "${NDK_VER}" >> "${GP}"
fi
- name: Read pubspec version
id: ver
shell: bash
run: |
VLINE=$(grep '^version:' pubspec.yaml | head -1 | awk '{print $2}')
APP_VERSION="${VLINE%%+*}"
BUILD_NUMBER="$(git rev-list --count HEAD)"
echo "app_version=$APP_VERSION" >> "$GITHUB_OUTPUT"
echo "build_number=$BUILD_NUMBER" >> "$GITHUB_OUTPUT"
- name: Switch pubspec to FOSS
shell: bash
run: |
set -euo pipefail
cp pubspec-foss.yaml pubspec.yaml
cp pubspec-foss.lock pubspec.lock
- name: Flutter pub get
run: flutter pub get
- name: Run build_runner
run: dart run build_runner build --delete-conflicting-outputs
- name: Configure Gradle performance flags
run: |
sed -i '/^org\.gradle\.configuration-cache/d' android/gradle.properties || true
cat >> android/gradle.properties <<'EOF'
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configuration-cache=false
EOF
- name: Make gradlew executable
run: chmod +x android/gradlew
- name: FOSS proprietary dependency check
shell: bash
run: |
chmod +x tools/check_proprietary_deps.sh
tools/check_proprietary_deps.sh fossReleaseRuntimeClasspath
- name: Decode keystore
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/dawarich-upload.jks
- name: Create keystore.properties
run: |
cat > android/keystore.properties <<'EOF'
storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
storeFile=../dawarich-upload.jks
EOF
- name: Build FOSS APK (release)
run: |
flutter build apk --release --flavor foss \
--build-name=${{ steps.ver.outputs.app_version }} \
--build-number=${{ steps.ver.outputs.build_number }}
- name: Upload FOSS artifact
uses: actions/upload-artifact@v4
with:
name: foss-apk
path: build/app/outputs/flutter-apk/app-foss-release.apk
retention-days: 30
# ── Upload to open beta only when 'release:' is in the PR title or commit message ──
upload-beta:
needs: [gate, build-gms, build-foss]
if: needs.gate.outputs.should_deploy == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download GMS artifact
uses: actions/download-artifact@v4
with:
name: gms-aab
path: dist
- name: Ensure whatsnew directory exists
run: mkdir -p distribution/whatsnew
- name: Upload to Play open beta (GMS)
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: ${{ secrets.PACKAGE_NAME_GMS }}
releaseName: ${{ needs.build-gms.outputs.app_version }}+${{ needs.build-gms.outputs.build_number }}
releaseFiles: dist/*.aab
whatsNewDirectory: distribution/whatsnew
track: beta
status: completed