Workflow file for this run
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: Release Android (signed APK + AAB) | |
| # When a GitHub Release is published, build SIGNED Android artifacts and attach | |
| # them to the release: | |
| # * a signed .apk — for direct sideloading / out-of-store distribution | |
| # * a signed .aab — Android App Bundle, required for Google Play uploads | |
| # Also runnable manually (uploads the artifacts to the workflow run only). | |
| # | |
| # REQUIRED REPOSITORY SECRETS (Settings -> Secrets and variables -> Actions): | |
| # ANDROID_KEYSTORE_BASE64 base64-encoded release keystore (.jks/.keystore) | |
| # ANDROID_KEYSTORE_PASSWORD the keystore (store) password | |
| # ANDROID_KEY_ALIAS the alias of the signing key inside the keystore | |
| # ANDROID_KEY_PASSWORD the password for that key alias | |
| # See docs/release-signing.md for how to generate and upload the keystore. | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write # required to upload assets onto the release | |
| jobs: | |
| build-android: | |
| name: Build & attach signed Android artifacts | |
| runs-on: ubuntu-latest | |
| env: | |
| # Path the keystore is decoded to at build time (runner-local, ephemeral). | |
| KEYSTORE_PATH: ${{ github.workspace }}/release.keystore | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| # .NET for Android (net10.0-android) requires JDK 17 specifically. | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - name: Set up .NET (from global.json) | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Install MAUI Android workload | |
| run: dotnet workload restore src/SharpClient.App/SharpClient.App.csproj | |
| # Acquire any missing Android SDK platform/build-tools for the target API | |
| # into the runner's pre-installed SDK (ANDROID_SDK_ROOT). Accepts licenses. | |
| - name: Install Android SDK dependencies | |
| run: > | |
| dotnet build src/SharpClient.App/SharpClient.App.csproj | |
| -f net10.0-android | |
| -t:InstallAndroidDependencies | |
| -p:AcceptAndroidSdkLicenses=True | |
| -p:JavaSdkDirectory="$JAVA_HOME" | |
| -p:AndroidSdkDirectory="$ANDROID_SDK_ROOT" | |
| # Decode the base64 keystore secret into a real file. Fail loudly if the | |
| # secret is missing/empty so a release never silently ships a debug build. | |
| - name: Decode signing keystore | |
| run: | | |
| if [ -z "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" ]; then | |
| echo "::error::ANDROID_KEYSTORE_BASE64 secret is not set — cannot produce a signed release." >&2 | |
| exit 1 | |
| fi | |
| echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > "$KEYSTORE_PATH" | |
| echo "Decoded keystore to $KEYSTORE_PATH" | |
| # Publish a SIGNED .apk (sideload). AndroidKeyStore=true + the signing | |
| # properties make dotnet sign with our release keystore instead of the | |
| # auto-generated debug key. AndroidPackageFormat=apk is the default but is | |
| # set explicitly for clarity. | |
| - name: Publish signed APK (Release) | |
| run: > | |
| dotnet publish src/SharpClient.App/SharpClient.App.csproj | |
| -c Release | |
| -f net10.0-android | |
| -p:JavaSdkDirectory="$JAVA_HOME" | |
| -p:AndroidSdkDirectory="$ANDROID_SDK_ROOT" | |
| -p:AcceptAndroidSdkLicenses=True | |
| -p:AndroidPackageFormat=apk | |
| -p:AndroidKeyStore=true | |
| -p:AndroidSigningKeyStore="$KEYSTORE_PATH" | |
| -p:AndroidSigningStorePass='${{ secrets.ANDROID_KEYSTORE_PASSWORD }}' | |
| -p:AndroidSigningKeyAlias='${{ secrets.ANDROID_KEY_ALIAS }}' | |
| -p:AndroidSigningKeyPass='${{ secrets.ANDROID_KEY_PASSWORD }}' | |
| # Publish a SIGNED .aab (App Bundle) for Google Play. Same signing inputs, | |
| # AndroidPackageFormat=aab switches the packaging. | |
| - name: Publish signed AAB (Release) | |
| run: > | |
| dotnet publish src/SharpClient.App/SharpClient.App.csproj | |
| -c Release | |
| -f net10.0-android | |
| -p:JavaSdkDirectory="$JAVA_HOME" | |
| -p:AndroidSdkDirectory="$ANDROID_SDK_ROOT" | |
| -p:AcceptAndroidSdkLicenses=True | |
| -p:AndroidPackageFormat=aab | |
| -p:AndroidKeyStore=true | |
| -p:AndroidSigningKeyStore="$KEYSTORE_PATH" | |
| -p:AndroidSigningStorePass='${{ secrets.ANDROID_KEYSTORE_PASSWORD }}' | |
| -p:AndroidSigningKeyAlias='${{ secrets.ANDROID_KEY_ALIAS }}' | |
| -p:AndroidSigningKeyPass='${{ secrets.ANDROID_KEY_PASSWORD }}' | |
| # Locate the produced artifacts. Prefer the *-Signed.apk emitted by the | |
| # signing pipeline; fall back to any .apk. The .aab is always signed. | |
| - name: Locate artifacts | |
| id: artifacts | |
| run: | | |
| base="src/SharpClient.App/bin/Release/net10.0-android" | |
| apk="$(find "$base" -name '*-Signed.apk' | head -n1)" | |
| if [ -z "$apk" ]; then | |
| apk="$(find "$base" -name '*.apk' | head -n1)" | |
| fi | |
| aab="$(find "$base" -name '*.aab' | head -n1)" | |
| if [ -z "$apk" ]; then | |
| echo "::error::No APK found under $base" >&2 | |
| find "$base" -name '*.apk' >&2 || true | |
| exit 1 | |
| fi | |
| if [ -z "$aab" ]; then | |
| echo "::error::No AAB found under $base" >&2 | |
| find "$base" -name '*.aab' >&2 || true | |
| exit 1 | |
| fi | |
| echo "apk=$apk" >> "$GITHUB_OUTPUT" | |
| echo "aab=$aab" >> "$GITHUB_OUTPUT" | |
| echo "Found APK: $apk" | |
| echo "Found AAB: $aab" | |
| # Remove the decoded keystore from the runner as soon as the build is done. | |
| - name: Remove decoded keystore | |
| if: always() | |
| run: rm -f "$KEYSTORE_PATH" | |
| - name: Upload artifacts to the workflow run | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sharpclient-android | |
| path: | | |
| ${{ steps.artifacts.outputs.apk }} | |
| ${{ steps.artifacts.outputs.aab }} | |
| if-no-files-found: error | |
| - name: Attach signed artifacts to the GitHub Release | |
| if: github.event_name == 'release' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: > | |
| gh release upload "${{ github.event.release.tag_name }}" | |
| "${{ steps.artifacts.outputs.apk }}" | |
| "${{ steps.artifacts.outputs.aab }}" | |
| --clobber |