Last active
November 22, 2023 19:36
-
-
Save limkhashing/bca46ed2165c7be7d44c32d2d16bfa2b to your computer and use it in GitHub Desktop.
GitHub Actions Android workflow with creating GitHub tagging and release
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: Android Production CI/CD | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| on: | |
| pull_request: | |
| types: [synchronize, opened, reopened, ready_for_review] | |
| branches: [master, development] | |
| paths-ignore: | |
| - '.idea/**' | |
| - '.gitattributes' | |
| - '.github/**.json' | |
| - '.gitignore' | |
| - '.gitmodules' | |
| - '**.md' | |
| - 'LICENSE' | |
| - 'NOTICE' | |
| # Allows you to run this workflow manually from the Actions tab | |
| workflow_dispatch: | |
| inputs: | |
| new_server_endpoint: | |
| description: 'Override the default server endpoint' | |
| required: false | |
| default: 'https://api.myfave.com' | |
| env: | |
| # Keystore Password | |
| SIGNING_KEYSTORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} | |
| # Keystore Alias | |
| SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} | |
| # Keystore Alias Password | |
| SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} | |
| # Token and Key needed for Core BuildConfig | |
| APPSFLYER_DEV_KEY: ${{ secrets.APPSFLYER_DEV_KEY }} | |
| COUNTRY_STATE_API_KEY: ${{ secrets.COUNTRY_STATE_API_KEY }} | |
| ADYEN_ENV_TOKEN: ${{ secrets.ADYEN_ENV_TOKEN }} | |
| HUAWEI_APP_ID: ${{ secrets.HUAWEI_APP_ID }} | |
| GOOGLE_BACKEND_CID: ${{ secrets.GOOGLE_BACKEND_CID }} | |
| ZENDESK_MESSAGING_CHANNEL_KEY: ${{ secrets.ZENDESK_MESSAGING_CHANNEL_KEY }} | |
| permissions: | |
| contents: write # Required in order to submit the graph via the GitHub API | |
| pull-requests: write # Requires write permission to comment on PRs | |
| jobs: | |
| build-and-distribute-android: | |
| if: ${{ !github.event.pull_request.draft }} | |
| runs-on: macos-latest-xl | |
| environment: production | |
| steps: | |
| - name: Checkout the branch | |
| uses: actions/checkout@v4 | |
| - name: Setup JDK 18 | |
| uses: actions/setup-java@v3 | |
| with: | |
| distribution: zulu | |
| java-version: 18 | |
| - name: Setup Gradle | |
| id: setup-gradle | |
| uses: gradle/gradle-build-action@v2 | |
| with: | |
| dependency-graph: generate-and-submit | |
| cache-disabled: false | |
| # Only write to the cache for builds of the PRs that target 'master' branch | |
| # Other PRs and workflow dispatch will read the caches from the 'master' branch instead | |
| cache-read-only: ${{ github.base_ref != 'master' }} | |
| # cache-write-only: true | |
| - name: Validate Gradle Wrapper | |
| uses: gradle/wrapper-validation-action@v1 | |
| - name: Create Services Account and configuration files | |
| run: | | |
| mkdir app/src/release | |
| jq -n ${{ secrets.SERVICE_CREDENTIALS_FIREBASE_APP_DISTRIBUTION }} > fave-production-service-account-firebase-app-distribution.json | |
| jq -n ${{ secrets.AGCONNECT_SERVICES }} > app/src/release/agconnect-services.json | |
| jq -n ${{ secrets.GOOGLE_SERVICES }} > app/src/release/google-services.json | |
| - name: Decode Keystore from Base64 and save it to a file | |
| env: | |
| ENCODED_KEYSTORE: ${{ secrets.SIGNING_KEYSTORE }} | |
| run: | | |
| tmp_keystore_directory="${RUNNER_TEMP}"/keystore | |
| mkdir "${tmp_keystore_directory}" | |
| echo $ENCODED_KEYSTORE | base64 -D > "${tmp_keystore_directory}"/keystore.jks | |
| echo "SIGNING_KEYSTORE_FILE_PATH="${tmp_keystore_directory}"/keystore.jks" >> $GITHUB_ENV | |
| # We only run this job if we trigger workflow manually and the new server endpoint is not empty | |
| - name: Replace the default server endpoint with the new server endpoint | |
| if: ${{ ( github.event_name == 'workflow_dispatch') && (inputs.new_server_endpoint != '') }} | |
| run: | | |
| sed -i '' -e "s#https://api.myfave.com#${{ inputs.new_server_endpoint }}#g" core/src/main/java/com/kfit/fave/core/utils/NetworkUtils.kt | |
| cat core/src/main/java/com/kfit/fave/core/utils/NetworkUtils.kt # Print the file contents after changing | |
| - name: Run unit tests | |
| env: | |
| SIGNING_KEYSTORE_FILE_PATH: ${{ env.SIGNING_KEYSTORE_FILE_PATH }} | |
| SIGNING_KEYSTORE_PASSWORD: ${{ env.SIGNING_KEYSTORE_PASSWORD }} | |
| SIGNING_KEY_ALIAS: ${{ env.SIGNING_KEY_ALIAS }} | |
| SIGNING_KEY_PASSWORD: ${{ env.SIGNING_KEY_PASSWORD }} | |
| APPSFLYER_DEV_KEY: ${{ env.APPSFLYER_DEV_KEY }} | |
| COUNTRY_STATE_API_KEY: ${{ env.COUNTRY_STATE_API_KEY }} | |
| ADYEN_ENV_TOKEN: ${{ env.ADYEN_ENV_TOKEN }} | |
| HUAWEI_APP_ID: ${{ env.HUAWEI_APP_ID }} | |
| GOOGLE_BACKEND_CID: ${{ env.GOOGLE_BACKEND_CID }} | |
| ZENDESK_MESSAGING_CHANNEL_KEY: ${{ env.ZENDESK_MESSAGING_CHANNEL_KEY }} | |
| VERSION_CODE: ${{ github.run_number }} | |
| run: ./gradlew testRelease --stacktrace | |
| - name: Build release app bundle | |
| env: | |
| SIGNING_KEYSTORE_FILE_PATH: ${{ env.SIGNING_KEYSTORE_FILE_PATH }} | |
| SIGNING_KEYSTORE_PASSWORD: ${{ env.SIGNING_KEYSTORE_PASSWORD }} | |
| SIGNING_KEY_ALIAS: ${{ env.SIGNING_KEY_ALIAS }} | |
| SIGNING_KEY_PASSWORD: ${{ env.SIGNING_KEY_PASSWORD }} | |
| APPSFLYER_DEV_KEY: ${{ env.APPSFLYER_DEV_KEY }} | |
| COUNTRY_STATE_API_KEY: ${{ env.COUNTRY_STATE_API_KEY }} | |
| ADYEN_ENV_TOKEN: ${{ env.ADYEN_ENV_TOKEN }} | |
| HUAWEI_APP_ID: ${{ env.HUAWEI_APP_ID }} | |
| GOOGLE_BACKEND_CID: ${{ env.GOOGLE_BACKEND_CID }} | |
| ZENDESK_MESSAGING_CHANNEL_KEY: ${{ env.ZENDESK_MESSAGING_CHANNEL_KEY }} | |
| VERSION_CODE: ${{ github.run_number }} | |
| run: ./gradlew bundleRelease --stacktrace | |
| - name: Export and unzip APK from app bundle | |
| id: export-apk | |
| uses: mukeshsolanki/bundletool-action@v1.0.2 | |
| with: | |
| aabFile: app/build/outputs/bundle/release/app-release.aab | |
| base64Keystore: ${{ secrets.SIGNING_KEYSTORE }} | |
| keystorePassword: ${{ env.SIGNING_KEYSTORE_PASSWORD }} | |
| keystoreAlias: ${{ env.SIGNING_KEY_ALIAS }} | |
| keyPassword: ${{ env.SIGNING_KEY_PASSWORD }} | |
| - name: Upload aab to Firebase App Distribution | |
| run: | | |
| echo "${{ github.event.pull_request.title }}" > releaseNotes.txt | |
| echo "" >> releaseNotes.txt | |
| echo "${{ github.event.pull_request.body }}" >> releaseNotes.txt | |
| echo "Author: ${{ github.event.pull_request.user.login }}" >> releaseNotes.txt | |
| ./gradlew appDistributionUploadRelease --artifactType="AAB" --serviceCredentialsFile="fave-production-service-account-firebase-app-distribution.json" --artifactPath="app/build/outputs/bundle/release/app-release.aab" --appId="${{ secrets.FIREBASE_APP_ID }}" --groups="Fave-Engineer, Fave-QA, Fave-Product" --releaseNotesFile=releaseNotes.txt | |
| - name: Upload to BrowserStack for QA automation if is Regression testing | |
| if: ${{ github.base_ref == 'development' }} | |
| uses: Gildofj/upload-app-to-app-live-browserstack@master | |
| with: | |
| app-path: ${{ steps.export-apk.outputs.apkPath }} | |
| browserstack-username: ${{ secrets.BROWSERSTACK_USERNAME }} | |
| browserstack-accesskey: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} | |
| - name: Upload release app bundle and apk to artifacts | |
| uses: actions/upload-artifact@v3 | |
| with: | |
| name: release-android-app-bundle-and-apk | |
| path: | | |
| app/build/outputs/bundle/release/app-release.aab | |
| ${{ steps.export-apk.outputs.apkPath }} | |
| app/build/outputs/mapping/release/mapping.txt | |
| app/build/intermediates/merged_native_libs/release/out/lib | |
| retention-days: 5 | |
| - name: Set a custom version number that is offset by 30000 (run_number + 30000) | |
| id: set-version-number | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| script: | | |
| const runNumber = ${{ github.run_number }}; | |
| const versionNumber = runNumber + 30000; | |
| core.setOutput('versionNumber', versionNumber); | |
| core.setOutput('versionCode', runNumber); | |
| - name: Print build number summary | |
| run: | | |
| { | |
| echo "## Build and distribute Android success! 🚀 " | |
| echo "The build number is ${{steps.set-version-number.outputs.versionNumber}}" | |
| } >> $GITHUB_STEP_SUMMARY | |
| deployment: | |
| if: ${{ github.base_ref == 'master' }} | |
| runs-on: ubuntu-latest | |
| environment: production | |
| needs: build-and-distribute-android | |
| steps: | |
| - name: Checkout the branch | |
| uses: actions/checkout@v4 | |
| - name: Download artifacts from build job | |
| uses: actions/download-artifact@v3 | |
| with: | |
| name: release-android-app-bundle-and-apk | |
| - name: Create Services Account and configuration file | |
| run: | | |
| jq -n ${{ secrets.SERVICE_CREDENTIALS_GOOGLE_PLAY }} > fave-production-service-account-upload-google-play.json | |
| - name: Upload release app bundle to Google Play Store and submit review | |
| uses: r0adkll/upload-google-play@v1 | |
| with: | |
| serviceAccountJson: fave-production-service-account-upload-google-play.json | |
| packageName: com.kfit.fave | |
| releaseFiles: app/build/outputs/bundle/release/app-release.aab | |
| track: production | |
| status: completed | |
| mappingFile: app/build/outputs/mapping/release/mapping.txt | |
| debugSymbols: app/build/intermediates/merged_native_libs/release/out/lib | |
| - name: Upload release app bundle to Huawei AppGallery and submit review | |
| uses: kienlv58/appgallery-deply-action@main | |
| with: | |
| client-id: ${{ secrets.HUAWEI_CLIENT_ID }} | |
| client-key: ${{ secrets.HUAWEI_CLIENT_KEY }} | |
| app-id: ${{secrets.HUAWEI_APP_ID}} | |
| file-extension: "aab" | |
| file-path: "app/build/outputs/bundle/release/app-release.aab" | |
| file-name: "app-release.aab" | |
| submit: true | |
| - name: Upload release app bundle and apk to artifacts | |
| uses: actions/upload-artifact@v3 | |
| with: | |
| name: release-android-app-bundle-and-apk | |
| path: | | |
| app/build/outputs/bundle/release/app-release.aab | |
| app-release.apk | |
| app/build/outputs/mapping/release/mapping.txt | |
| app/build/intermediates/merged_native_libs/release/out/lib | |
| retention-days: 5 | |
| github-release: | |
| if: ${{ github.base_ref == 'master' }} | |
| runs-on: ubuntu-latest | |
| environment: production | |
| needs: deployment | |
| steps: | |
| - name: Checkout the branch | |
| uses: actions/checkout@v4 | |
| - name: Download artifacts from build job | |
| uses: actions/download-artifact@v3 | |
| with: | |
| name: release-android-app-bundle-and-apk | |
| - name: Create a new tag | |
| id: tag-version | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| script: | | |
| const fs = require('fs'); | |
| const filePath = 'app/build.gradle.kts'; | |
| const fileContent = fs.readFileSync(filePath, 'utf-8'); | |
| const regexPattern = /versionName = "([0-9\.]{6}([-\+][\w\.0-9]+)?)"/; | |
| const match = fileContent.match(regexPattern); | |
| const versionName = match ? match[1] : null; | |
| const prefixedVersionName = versionName ? `v${versionName}` : null; | |
| if (prefixedVersionName == null) { | |
| throw new Error('Version name not found in file: ' + filePath); | |
| } | |
| console.log('Version name: ' + prefixedVersionName); | |
| return prefixedVersionName; | |
| - name: Install archiver | |
| run: npm install archiver | |
| - name: Zip files for release artifacts | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const archiver = require('archiver'); | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const outputFilePath = 'artifacts.zip'; | |
| const currentWorkingDirectory = process.cwd(); | |
| // Create a writable stream for the zip file | |
| const output = fs.createWriteStream(outputFilePath); | |
| const archive = archiver('zip'); | |
| output.on('close', function () { | |
| console.log(archive.pointer() + ' total bytes'); | |
| console.log('archiver has been finalized and the output file descriptor has closed.'); | |
| }); | |
| output.on('end', function () { | |
| console.log('Data has been drained'); | |
| }); | |
| // Pipe the output file stream to the archive | |
| archive.pipe(output); | |
| // Add files to the zip archive | |
| const filesToAdd = [ | |
| 'app/build/outputs/bundle/release/app-release.aab', | |
| 'app-release.apk', | |
| 'app/build/outputs/mapping/release/mapping.txt', | |
| 'app/build/intermediates/merged_native_libs/release/out/lib', | |
| ]; | |
| filesToAdd.forEach((file) => { | |
| const fullPath = path.resolve(currentWorkingDirectory, file); | |
| const stat = fs.statSync(fullPath); | |
| if (stat.isFile()) { | |
| archive.file(fullPath, { name: path.basename(file) }); | |
| } else if (stat.isDirectory()) { | |
| archive.directory(fullPath, path.basename(file)); | |
| } | |
| }); | |
| // Finalize the archive | |
| archive.finalize(); | |
| - name: Push the new tag and new release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh release create ${{steps.tag-version.outputs.result}} --target development --generate-notes ./artifacts.zip | |
| review-dependency-graph: | |
| if: ${{ github.event_name == 'pull_request' }} | |
| needs: build-and-distribute-android | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout the branch | |
| uses: actions/checkout@v4 | |
| - name: Perform dependency review | |
| uses: actions/dependency-review-action@v3 | |
| with: | |
| fail-on-severity: critical | |
| comment-summary-in-pr: on-failure |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment