I hope you have been following my previous blog on Github actions and how to set up manual triggers. Also read the second part on how to set up ruby in the CI/CD realm.
In this blog, I am going to focus mainly on Android-related steps, like JDK set up, building aab, and uploading to the Play store.
Let’s get started with setting up the JDK environment!
Setting Up JDK Environment (Android only)
-
Set Up JDK Environment
- name: JDK environment Set-Up uses: actions/[email protected] with: java-version: 11.0.15
-
Decode Keystore
- name: Decode Keystore for Nitor # decode keystore and generate jks/keystore file if: ${{ github.event.inputs.target == 'Nitor' || (github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' ) }} id: decode_keystore_for_Nitor uses: timheuer/base64-to-file@v1 with: fileName: "Nitor.jks" encodedString: ${{ secrets.ANDROID_SIGNING_KEY }}
1. Move keystore android/app
- name: "move keystore Nitor" if: ${{ github.event.inputs.target == 'Nitor' || (github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging') }} run: | mv ${{steps.decode_keystore_for_Nitor.outputs.filePath}} android/app ls android/app
2. Make Gradlew Executable
- name: Make Gradlew Executable run: cd android && chmod +x ./gradlew
3. Generate App Bundle
4. Before moving ahead, we need to modify android/app/build.gradle file as we are also going to pass minor version and major version.
5. You can create function to update the versionCode and versionName from the command line:
def getVersionName = { -> def name = project.hasProperty('versionName') ? versionName : "1.0" return name } def getVersionCode = { -> def code = project.hasProperty('versionCode') ? versionCode.toInteger() : 1 return code }
6. Then in the Android block:
defaultConfig { applicationId "your.app.id" minSdkVersion 15 targetSdkVersion 23 versionCode getVersionCode() versionName geVersionName() -------- -------- }
You can truly just refer to any task as:
./gradlew assembleDebug -PversionCode=483 -PversionName=4.0.3
You can read more about it here: https://web.archive.org/web/20160119183929/https://robertomurray.co.uk/blog/2013/gradle-android-inject-version-code-from-command-line-parameter/
- name: Generate App Bundle for Nitor / staging if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }} run: | cd android ./gradlew bundleNitorstagingRelease -PversionCode=${{ github.event.inputs.minorversion }} -PversionName=${{ github.event.inputs.majorversion }}
-
Sign generated App Bundle
- name: Sign APK For Nitor / staging if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }} id: sign_app_Nitor_staging uses: r0adkll/sign-android-release@v1 with: releaseDirectory: android/app/build/outputs/bundle/NitorstagingRelease/ signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} alias: ${{ secrets.ANDROID_ALIAS }} keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
- The Android APK will be signed using r0adkll/sign-android-release@v1 using the private signing keys we created in the earlier stage.
- The environment variables that must be supplied as Github secrets are listed below:
- The base64-encoded signing key used to sign your app is ANDROID SIGNING KEY.
- On *nix computers, you can create your key by using this command.
- openssl base64 < your-upload-key.keystore | tr -d ‘\n’ | tee my-upload-key.keystore.base64.txt
- Then transfer the text file contents to your private ANDROID SIGNING KEY GH location.
- The alias of your signing key is ANDROID ALIAS.
- ANDROID KEY STORE PASSWORD: Your signing Keystore’s password
- The private key password for your signing Keystore is ANDROID KEY PASSWORD.
- Open your Github project home page > Project Setting > Secrets > Actions > new repository secret to add new GH secrets.
How to Upload the App to Google Play?
- name: Upload App to Google Play Nitor / staging if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }} uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} packageName: com.Nitor.Nitor.staging releaseFiles: android/app/build/outputs/bundle/NitorstagingRelease/*.aab track: internal status: completed inAppUpdatePriority: 2
To upload the app to Google Play, we will largely follow the same procedure, but we will utilize the .aab bundle rather than the APK.
You must create a new service account to upload your .aab to Google Play. Remember:
⚠️ ️Delete the signing of the release aab with the debug credentials from the android/app/build.gradle file.
⚠️ Only Owners can produce service account JSON files.
The process of creating an Android service account is explained in detail here – Generate Service account JSON file
The content of the JSON file can then be uploaded to GitHub secrets with the name ANDROID SERVICE ACCOUNT JSON TEXT.
****************************************************************
Here’s a useful link: https://www.obytes.com/blog/react-native-ci-cd-github-action
Explore how you can add a self-hosted-runner here: https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners
Know how our DevOps implementation improved the CI/CD engine for a leading software company.
Kindly refer to the following .yml file which I have used as an example.
name: Android Build Deployment on: workflow_dispatch: # manually triggers with inputs inputs: target: description: "Target" required: true default: "Nitor" type: choice options: - Nitor - NitorPro flavor: description: "Environment" required: true default: "staging" type: choice options: - staging - development - production minorversion: description: "Minor Version" #versionCode required: true majorversion: description: "Major Version" #versionName required: true jobs: android: name: Publish App To Play Store runs-on: self-hosted # [ubuntu-latest, macOS-latest, self-hosted] steps: - name: Check out Git repository uses: actions/checkout@v2 with: persist-credentials: false # make it false if you are going to clone private repositories in yarn - name: JDK environment Set Up uses: actions/[email protected] with: java-version: 11.0.15 - name: Decode Keystore for Nitor # decode keystore and generate jks/keystore file if: ${{ github.event.inputs.target == 'Nitor' || (github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' ) }} id: decode_keystore_for_Nitor uses: timheuer/base64-to-file@v1 with: fileName: "Nitor.jks" encodedString: ${{ secrets.ANDROID_SIGNING_KEY }} - name: Decode Keystore for NitorPro # decode keystore and generate jks/keystore file if: ${{ github.event.inputs.target == 'NitorPro' && (github.event.inputs.flavor == 'development' || github.event.inputs.flavor == 'production') }} id: decode_keystore_for_NitorPro uses: timheuer/base64-to-file@v1 with: fileName: "NitorPro.jks" encodedString: ${{ secrets.NITORPRO_RELEASE_STORE_FILE }} - name: Decode build.gradle # decode build.gradle and generate build.gradle file id: decode_app_build_gradle uses: timheuer/base64-to-file@v1 with: fileName: "build.gradle" encodedString: ${{ secrets.GRADLE}} - name: Decode .env.Nitor.staging # decode .env.Nitor.staging and generate .env.Nitor.staging file if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging'}} id: decode_Nitor_staging uses: timheuer/base64-to-file@v1 with: fileName: ".env" encodedString: ${{ secrets.NITOR_STAGING}} - name: Decode .env.Nitor.development # decode .env.Nitor.development and generate .env.Nitor.development file if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development'}} id: decode_Nitor_development uses: timheuer/base64-to-file@v1 with: fileName: ".env" encodedString: ${{ secrets.NITOR_DEVELOPMENT}} - name: Decode .env.Nitor.production # decode .env.Nitor.production and generate .env.Nitor.production file if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production'}} id: decode_Nitor_production uses: timheuer/base64-to-file@v1 with: fileName: ".env" encodedString: ${{ secrets.NITOR_PRODUCTION}} - name: Decode .env.NitorPro.staging # decode .env.NitorPro.stagingand generate .env.NitorPro.staging file if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging'}} id: decode_NitorPro_staging uses: timheuer/base64-to-file@v1 with: fileName: ".env" encodedString: ${{ secrets.NITORPRO_STAGING}} - name: Decode .env.NitorPro.development # decode .env.NitorPro.development and generate .env.NitorPro.development file if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development'}} id: decode_NitorPro_development uses: timheuer/base64-to-file@v1 with: fileName: ".env" encodedString: ${{ secrets.NITORPRO_DEVELOPMENT}} - name: Decode .env.NitorPro.production # decode .env.NitorPro.production and generate .env.NitorPro.production file if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production'}} id: decode_NitorPro_production uses: timheuer/base64-to-file@v1 with: fileName: ".env" encodedString: ${{ secrets.NITORPRO_PRODUCTION}} # - name: move "jks" to android/app - name: "move keystore Nitor" if: ${{ github.event.inputs.target == 'Nitor' || (github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging') }} run: | mv ${{steps.decode_keystore_for_Nitor.outputs.filePath}} android/app ls android/app - name: "move keystore NitorPro" if: ${{ github.event.inputs.target == 'NitorPro' && (github.event.inputs.flavor == 'development' || github.event.inputs.flavor == 'production')}} run: | mv ${{steps.decode_keystore_for_NitorPro.outputs.filePath}} android/app ls android/app - name: "move build gradle" run: | mv ${{steps.decode_app_build_gradle.outputs.filePath}} android/app ls cat android/app/build.gradle - name: "move Nitor_staging" if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging'}} run: | mv ${{steps.decode_Nitor_staging.outputs.filePath}} ./ pwd cat .env - name: "move Nitor_development" if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development'}} run: | mv ${{steps.decode_Nitor_development.outputs.filePath}} ./ pwd cat .env - name: "move Nitor_production" if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production'}} run: | mv ${{steps.decode_Nitor_production.outputs.filePath}} ./ pwd cat .env - name: "move NitorPro_staging" if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging'}} run: | mv ${{steps.decode_NitorPro_staging.outputs.filePath}} ./ pwd cat .env - name: "move NitorPro_development" if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development'}} run: | mv ${{steps.decode_NitorPro_development.outputs.filePath}} ./ pwd cat .env - name: "move NitorPro_production" if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production'}} run: | mv ${{steps.decode_NitorPro_production.outputs.filePath}} ./ pwd cat .env - name: git config with PAT_GITHUB run: git config --global url.https://${{ secrets.PAT_GITHUB }} @github.com/.insteadOf https://github.com - name: Set up our node configuration uses: actions/setup-node@v1 with: node-version: 16.x - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - name: yarn Install dependencies if: | steps.cache-yarn-cache.outputs.cache-hit != 'true' || steps.cache-node-modules.outputs.cache-hit != 'true' run: | yarn install --network-concurrency 1 - name: Make Gradlew Executable run: cd android && chmod +x ./gradlew # Generate App Bundle for Nitor / staging - name: Generate App Bundle for Nitor / staging if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }} run: | cd android ./gradlew bundleNitorstagingRelease -PversionCode=${{ github.event.inputs.minorversion }} -PversionName=${{ github.event.inputs.majorversion }} # Generate App Bundle for Nitor / development - name: Generate App Bundle for Nitor / development if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development' }} run: | cd android ./gradlew bundleNitordevelopmentRelease -PversionCode=${{ github.event.inputs.minorversion }} -PversionName=${{ github.event.inputs.majorversion }} # Generate App Bundle for Nitor / production - name: Generate App Bundle for Nitor / production if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production' }} run: | cd android ./gradlew bundleNitorRelease -PversionCode=${{ github.event.inputs.minorversion }} -PversionName=${{ github.event.inputs.majorversion }} # Generate App Bundle for NitorPro / staging - name: Generate App Bundle for NitorPro / staging run: | cd android ./gradlew bundleNitorProstagingRelease -PversionCode=${{ github.event.inputs.minorversion }} -PversionName=${{ github.event.inputs.majorversion }} if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' }} # Generate App Bundle for NitorPro / development - name: Generate App Bundle for NitorPro / development run: | cd android ./gradlew bundleNitorProdevelopmentRelease -PversionCode=${{ github.event.inputs.minorversion }} -PversionName=${{ github.event.inputs.majorversion }} if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development' }} # Generate App Bundle for NitorPro / production - name: Generate App Bundle for NitorPro / production run: | cd android ./gradlew bundleNitorProRelease -PversionCode=${{ github.event.inputs.minorversion }} -PversionName=${{ github.event.inputs.majorversion }} if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production' }} # sign generated aab for Nitor / staging - name: Sign APK For Nitor / staging if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }} id: sign_app_Nitor_staging uses: r0adkll/sign-android-release@v1 with: releaseDirectory: android/app/build/outputs/bundle/NitorstagingRelease/ signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} alias: ${{ secrets.ANDROID_ALIAS }} keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} # sign generated aab for Nitor / development - name: Sign APK For Nitor / development if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development' }} id: sign_app_Nitor_development uses: r0adkll/sign-android-release@v1 with: releaseDirectory: android/app/build/outputs/bundle/NitordevelopmentRelease/ signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} alias: ${{ secrets.ANDROID_ALIAS }} keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} # sign generated aab for Nitor / production - name: Sign APK For Nitor / production if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production' }} id: sign_app_Nitor_production uses: r0adkll/sign-android-release@v1 with: releaseDirectory: android/app/build/outputs/bundle/NitorRelease/ signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} alias: ${{ secrets.ANDROID_ALIAS }} keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} # sign generated aab for NitorPro / staging - name: Sign APK For NitorPro / staging if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' }} id: sign_app_NitorPro_staging uses: r0adkll/sign-android-release@v1 with: releaseDirectory: android/app/build/outputs/bundle/NitorProstagingRelease/ signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} alias: ${{ secrets.ANDROID_ALIAS }} keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} # sign generated aab for NitorPro / development - name: Sign APK For NitorPro / development if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development' }} id: sign_app_NitorPro_development uses: r0adkll/sign-android-release@v1 with: releaseDirectory: android/app/build/outputs/bundle/NitorProdevelopmentRelease/ signingKeyBase64: ${{ secrets.NITORPRO_RELEASE_STORE_FILE }} alias: ${{ secrets.NITORPRO_RELEASE_KEY_ALIAS }} keyStorePassword: ${{ secrets.NITORPRO_RELEASE_STORE_PASSWORD }} keyPassword: ${{ secrets.NITORPRO_RELEASE_KEY_PASSWORD }} # sign generated aab for NitorPro / production - name: Sign APK For NitorPro / production if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production' }} id: sign_app_NitorPro_production uses: r0adkll/sign-android-release@v1 with: releaseDirectory: android/app/build/outputs/bundle/NitorProRelease/ signingKeyBase64: ${{ secrets.NITORPRO_RELEASE_STORE_FILE }} alias: ${{ secrets.NITORPRO_RELEASE_KEY_ALIAS }} keyStorePassword: ${{ secrets.NITORPRO_RELEASE_STORE_PASSWORD }} keyPassword: ${{ secrets.NITORPRO_RELEASE_KEY_PASSWORD }} #Upload App to Google Play For Nitor / staging - name: Upload App to Google Play Nitor / staging if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }} uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} packageName: com.Nitor.Nitor.staging releaseFiles: android/app/build/outputs/bundle/NitorstagingRelease/*.aab track: internal status: completed inAppUpdatePriority: 2 #Upload App to Google Play For Nitor / development - name: Upload App to Google Play Nitor / development if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development' }} uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} packageName: com.Nitor.Nitor.dev releaseFiles: android/app/build/outputs/bundle/NitordevelopmentRelease/*.aab track: internal status: completed inAppUpdatePriority: 2 #Upload App to Google Play For Nitor / production - name: Upload App to Google Play Nitor / production if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production' }} uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} packageName: com.Nitor.Nitor releaseFiles: android/app/build/outputs/bundle/NitorRelease/*.aab track: internal status: completed inAppUpdatePriority: 2 #Upload App to Google Play For NitorPro / staging - name: Upload App to Google Play NitorPro / staging if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' }} uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} # please update this with NitorPro android serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} packageName: com.Nitor.NitorPro.staging releaseFiles: android/app/build/outputs/bundle/NitorProstagingRelease/*.aab track: internal status: draft inAppUpdatePriority: 2 #Upload App to Google Play For NitorPro / development - name: Upload App to Google Play NitorPro / development if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development' }} uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} # please update this with NitorPro android serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} packageName: com.Nitor.NitorPro.dev releaseFiles: android/app/build/outputs/bundle/NitorProdevelopmentRelease/*.aab track: internal status: completed inAppUpdatePriority: 2 #Upload App to Google Play For NitorPro / production - name: Upload App to Google Play NitorPro / production if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production' }} uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} # please update this with NitorPro android serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} packageName: com.Nitor.NitorPro releaseFiles: android/app/build/outputs/bundle/NitorProRelease/*.aab track: internal status: completed inAppUpdatePriority: 2
So, this is how you can go about the Android setup of CI/CD with GitHub Actions! Mail us with your thoughts about this setup and visit us at Nitor Infotech to learn about our offerings.