Build Android APK #24
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: Build Android APK | |
| on: | |
| workflow_run: | |
| workflows: ["Create Release"] | |
| types: | |
| - completed | |
| workflow_dispatch: | |
| env: | |
| DOTNET_VERSION: '8.0.x' | |
| jobs: | |
| build-android: | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Validate signing secrets | |
| run: | | |
| MISSING="" | |
| if [ -z "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" ]; then MISSING="$MISSING ANDROID_KEYSTORE_BASE64"; fi | |
| if [ -z "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" ]; then MISSING="$MISSING ANDROID_KEYSTORE_PASSWORD"; fi | |
| if [ -z "${{ secrets.ANDROID_KEY_ALIAS }}" ]; then MISSING="$MISSING ANDROID_KEY_ALIAS"; fi | |
| if [ -z "${{ secrets.ANDROID_KEY_PASSWORD }}" ]; then MISSING="$MISSING ANDROID_KEY_PASSWORD"; fi | |
| if [ -n "$MISSING" ]; then | |
| echo "::error::Missing required secrets:$MISSING" | |
| echo "Configure them in: Settings → Secrets and variables → Actions" | |
| exit 1 | |
| fi | |
| echo "✅ All signing secrets are configured" | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: false | |
| - name: Setup .NET 8 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Pin .NET 8 SDK only | |
| run: | | |
| echo '{"sdk":{"version":"8.0.100","rollForward":"latestFeature","allowPrerelease":false}}' > global.json | |
| echo "Installed SDKs:" | |
| dotnet --list-sdks | |
| echo "" | |
| echo "Active SDK (pinned to 8.0.x):" | |
| dotnet --version | |
| - name: Install MAUI Android workload | |
| run: dotnet workload install maui-android --source https://api.nuget.org/v3/index.json | |
| - name: Install GitVersion | |
| uses: gittools/actions/gitversion/setup@v0.10.2 | |
| with: | |
| versionSpec: '5.x' | |
| - name: Determine Version | |
| id: gitversion | |
| uses: gittools/actions/gitversion/execute@v0.10.2 | |
| with: | |
| useConfigFile: true | |
| configFilePath: GitVersion.yml | |
| - name: Display version | |
| run: | | |
| echo "SemVer: ${{ steps.gitversion.outputs.semVer }}" | |
| echo "NuGetVersionV2: ${{ steps.gitversion.outputs.nuGetVersionV2 }}" | |
| - name: Create appsettings.json placeholder | |
| run: cp DevNote/appsettings.example.json DevNote/appsettings.json | |
| - name: Restore dependencies | |
| run: dotnet restore DevNote.sln | |
| - name: Decode keystore | |
| run: | | |
| echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > ${{ github.workspace }}/devnote.keystore | |
| - name: Publish Android APK (signed) | |
| run: | | |
| dotnet publish DevNote/DevNote.csproj \ | |
| -f net8.0-android \ | |
| -c Release \ | |
| -p:AndroidPackageFormat=apk \ | |
| -p:ApplicationDisplayVersion=${{ steps.gitversion.outputs.nuGetVersionV2 }} \ | |
| -p:ApplicationVersion=${{ github.run_number }} \ | |
| -p:AndroidKeyStore=true \ | |
| -p:AndroidSigningKeyStore=${{ github.workspace }}/devnote.keystore \ | |
| -p:AndroidSigningKeyAlias=${{ secrets.ANDROID_KEY_ALIAS }} \ | |
| -p:AndroidSigningKeyPass=${{ secrets.ANDROID_KEY_PASSWORD }} \ | |
| -p:AndroidSigningStorePass=${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| - name: Find APK file | |
| id: find_apk | |
| run: | | |
| echo "All APKs found:" | |
| find DevNote/bin/Release/net8.0-android -name "*.apk" | |
| APK_PATH=$(find DevNote/bin/Release/net8.0-android -name "*-Signed.apk" | head -1) | |
| if [ -z "$APK_PATH" ]; then | |
| echo "No signed APK found, falling back to any APK" | |
| APK_PATH=$(find DevNote/bin/Release/net8.0-android -name "*.apk" | head -1) | |
| fi | |
| echo "Selected APK: $APK_PATH" | |
| echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT | |
| APK_NAME="DevNote-v${{ steps.gitversion.outputs.nuGetVersionV2 }}.apk" | |
| cp "$APK_PATH" "$APK_NAME" | |
| echo "apk_name=$APK_NAME" >> $GITHUB_OUTPUT | |
| - name: Verify APK signature | |
| run: | | |
| echo "Checking APK signature..." | |
| "$ANDROID_HOME/build-tools/$(ls $ANDROID_HOME/build-tools/ | tail -1)/apksigner" verify --print-certs "${{ steps.find_apk.outputs.apk_name }}" || echo "⚠️ APK signature verification failed" | |
| - name: Upload APK artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevNote-v${{ steps.gitversion.outputs.nuGetVersionV2 }} | |
| path: ${{ steps.find_apk.outputs.apk_name }} | |
| retention-days: 90 | |
| - name: Attach APK to GitHub Release | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG_NAME="${GITHUB_REF#refs/tags/}" | |
| gh release upload "$TAG_NAME" "${{ steps.find_apk.outputs.apk_name }}" --clobber | |
| - name: Attach APK to latest release | |
| if: github.event_name == 'workflow_run' || github.event_name == 'workflow_dispatch' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| LATEST_RELEASE=$(gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "") | |
| if [ -n "$LATEST_RELEASE" ]; then | |
| echo "Attaching APK to release $LATEST_RELEASE" | |
| gh release upload "$LATEST_RELEASE" "${{ steps.find_apk.outputs.apk_name }}" --clobber | |
| else | |
| echo "No GitHub Release found, APK available as artifact only" | |
| fi | |
| - name: Summary | |
| run: | | |
| echo "## Android APK Built" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version:** \`${{ steps.gitversion.outputs.nuGetVersionV2 }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Build Number:** \`${{ github.run_number }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- **APK:** \`${{ steps.find_apk.outputs.apk_name }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- **SemVer:** \`${{ steps.gitversion.outputs.semVer }}\`" >> $GITHUB_STEP_SUMMARY |