name: MotorSignal Android CI on: push: branches: [ master ] pull_request: branches: [ master ] workflow_dispatch: permissions: contents: read concurrency: group: android-build-${{ github.ref }} cancel-in-progress: true env: FLUTTER_VERSION: '3.44.6' JAVA_VERSION: '17' ANDROID_COMPILE_SDK: '37' GRADLE_VERSION: '9.1.0' jobs: analyze-and-build: name: Analyze, test and debug build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 1 - name: Set up Java uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} cache: gradle - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable cache: true - name: Provision Android SDK shell: bash run: | set -euo pipefail SDKROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}" test -n "$SDKROOT" || { echo "Android SDK root is not configured"; exit 1; } SDKMANAGER="$(find "$SDKROOT/cmdline-tools" -type f -name sdkmanager -print -quit 2>/dev/null || true)" test -x "$SDKMANAGER" || { echo "Android sdkmanager not found under $SDKROOT/cmdline-tools"; exit 1; } "$SDKMANAGER" --licenses /dev/null 2>&1 || true "$SDKMANAGER" "platform-tools" "platforms;android-${ANDROID_COMPILE_SDK}" "build-tools;37.0.0" yes | "$SDKMANAGER" --licenses >/dev/null 2>&1 || true echo "ANDROID_SDK_ROOT=$SDKROOT" >> "$GITHUB_ENV" - name: Provision Gradle wrapper shell: bash run: | set -euo pipefail if [ ! -f android/gradlew ]; then gradle --version (cd android && gradle wrapper --gradle-version "$GRADLE_VERSION" --distribution-type all) fi chmod +x android/gradlew ./android/gradlew --version - name: Flutter and Android environment run: | flutter --version flutter doctor -v java -version sdkmanager --list_installed | grep -E 'platforms;android-37|build-tools;37|platform-tools' || true - name: Get dependencies run: flutter pub get - name: Analyze run: flutter analyze --no-fatal-infos - name: Run tests run: flutter test - name: Build debug APK run: flutter build apk --debug - name: Verify debug APK shell: bash run: | set -euo pipefail APK="build/app/outputs/flutter-apk/app-debug.apk" test -s "$APK" file "$APK" sha256sum "$APK" | tee build/app/outputs/flutter-apk/app-debug.apk.sha256 - name: Upload debug APK uses: actions/upload-artifact@v5 with: name: motorsignal-debug-apk path: | build/app/outputs/flutter-apk/app-debug.apk build/app/outputs/flutter-apk/app-debug.apk.sha256 if-no-files-found: error retention-days: 14 release: name: Build signed release APK and AAB runs-on: ubuntu-latest needs: analyze-and-build if: github.event_name == 'workflow_dispatch' || github.event_name == 'push' steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 1 - name: Set up Java uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} cache: gradle - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable cache: true - name: Provision Android SDK shell: bash run: | set -euo pipefail SDKROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}" test -n "$SDKROOT" || { echo "Android SDK root is not configured"; exit 1; } SDKMANAGER="$(find "$SDKROOT/cmdline-tools" -type f -name sdkmanager -print -quit 2>/dev/null || true)" test -x "$SDKMANAGER" || { echo "Android sdkmanager not found under $SDKROOT/cmdline-tools"; exit 1; } "$SDKMANAGER" --licenses /dev/null 2>&1 || true "$SDKMANAGER" "platform-tools" "platforms;android-${ANDROID_COMPILE_SDK}" "build-tools;37.0.0" yes | "$SDKMANAGER" --licenses >/dev/null 2>&1 || true echo "ANDROID_SDK_ROOT=$SDKROOT" >> "$GITHUB_ENV" - name: Provision Gradle wrapper shell: bash run: | set -euo pipefail if [ ! -f android/gradlew ]; then gradle --version (cd android && gradle wrapper --gradle-version "$GRADLE_VERSION" --distribution-type all) fi chmod +x android/gradlew ./android/gradlew --version - name: Get dependencies run: flutter pub get - name: Restore release keystore env: KEYSTORE_BASE64: ${{ secrets.MOTORSIGNAL_KEYSTORE_BASE64 }} shell: bash run: | set -euo pipefail test -n "$KEYSTORE_BASE64" || { echo "Missing MOTORSIGNAL_KEYSTORE_BASE64 repository secret"; exit 1; } printf '%s' "$KEYSTORE_BASE64" | base64 --decode > android/app/motorsignal-upload-keystore.jks chmod 600 android/app/motorsignal-upload-keystore.jks test -s android/app/motorsignal-upload-keystore.jks - name: Create signing properties env: STORE_PASSWORD: ${{ secrets.MOTORSIGNAL_STORE_PASSWORD }} KEY_PASSWORD: ${{ secrets.MOTORSIGNAL_KEY_PASSWORD }} KEY_ALIAS: ${{ secrets.MOTORSIGNAL_KEY_ALIAS }} shell: bash run: | set -euo pipefail test -n "$STORE_PASSWORD" || { echo "Missing MOTORSIGNAL_STORE_PASSWORD repository secret"; exit 1; } test -n "$KEY_PASSWORD" || { echo "Missing MOTORSIGNAL_KEY_PASSWORD repository secret"; exit 1; } test -n "$KEY_ALIAS" || { echo "Missing MOTORSIGNAL_KEY_ALIAS repository secret"; exit 1; } cat > android/key.properties </dev/null - name: Build release APK run: flutter build apk --release - name: Build release App Bundle run: flutter build appbundle --release - name: Verify release artifacts and signatures shell: bash run: | set -euo pipefail APK="build/app/outputs/flutter-apk/app-release.apk" AAB="build/app/outputs/bundle/release/app-release.aab" test -s "$APK" test -s "$AAB" file "$APK" "$AAB" APKSIGNER="$(find "$ANDROID_HOME/build-tools" -type f -name apksigner -print | sort -V | tail -n 1)" test -x "$APKSIGNER" || { echo "apksigner not found"; exit 1; } "$APKSIGNER" verify --verbose "$APK" sha256sum "$APK" "$AAB" | tee build/release-artifacts.sha256 - name: Upload release APK uses: actions/upload-artifact@v5 with: name: motorsignal-release-apk path: | build/app/outputs/flutter-apk/app-release.apk build/release-artifacts.sha256 if-no-files-found: error retention-days: 30 - name: Upload release App Bundle uses: actions/upload-artifact@v5 with: name: motorsignal-release-aab path: | build/app/outputs/bundle/release/app-release.aab build/release-artifacts.sha256 if-no-files-found: error retention-days: 30 - name: Remove signing credentials if: always() shell: bash run: | rm -f android/key.properties android/app/motorsignal-upload-keystore.jks