Creating Automated CI/CD Workflows for Android Apps with GitHub Actions
GitHub Actions offers a solid solution for Continuous Integration and Continuous Deployment (CI/CD) requirements, meeting the growing demand for user-friendly tools in this space. What distinguishes GitHub Actions is its seamless connection with your codebase, which enables more than just typical DevOps chores. This tool allows you to automate workflows that are triggered by events in your repository. For example, you can set up a workflow to assign labels automatically whenever a new issue is opened .Rather of going further into the enormous possibilities of GitHub Actions, let’s compare it to other competitors. Following that, I’ll walk you through the process of configuring GitHub Actions for Android projects
GitHub Actions for Android
GitHub Actions has emerged as a popular choice for Continuous Integration/Continuous Deployment (CI/CD) workflows. In this guide, we will explore the fundamentals of setting up CI/CD pipelines for Android projects using GitHub Actions.
Workflow scripts are stored in the .github/workflows/ directory within the root directory of your repository. For instance, the full path for a file named push.yaml would be .github/workflows/push.yaml. The snippet below represents the initial section of push.yml and illustrates the fundamental configuration of a basic workflow triggered after every push to the master or develop branches.
name: Push
on:
push:
branches: [ "develop", "master" ]
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- run: echo "The job was automatically triggered by a ${{ github.event_name }} event
Explanation of new commands:
- name: This field specifies the name of the workflow, which will be visible on the GitHub Actions page, allowing users to identify the purpose of this specific workflow.
- on: In the ‘on’ section, you define the events that can trigger the workflow. In this example, the workflow is triggered for every push event occurring in the master or develop branches of the repository.
- workflow_dispatch: When this flag is present, it enables manual triggering of the workflow directly from the GitHub interface. Users can initiate this workflow at their discretion.
- jobs: Workflows can contain one or more jobs, each of which can run in parallel (the default behavior) or sequentially. In this case, there is a single job named build with a name parameter set to ‘Build’.
- runs-on: This field specifies the type of machine environment in which the job will execute. For example, ubuntu-latest signifies that the job will run on a machine with the latest version of Ubuntu.
- steps: Each job consists of a series of steps. These steps can include shell commands or actions from the GitHub Marketplace. In this specific example, there is a single step demonstrated, using the run keyword to execute an echo command. The message being echoed contains details about the event that triggered the workflow.
These components collectively form the structure of a GitHub Actions workflow, allowing developers to automate various tasks and processes in their repositories.
Android-Specific GitHub Actions
For Android development, GitHub Actions offers specific steps tailored to the Android ecosystem. Let’s explore a workflow that builds and signs a release build of an Android app: The next example will show the bottom part of the push.yml file with all the steps needed to build the project after every push event:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- run: echo "The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "This job is running on a ${{ runner.os }} server hosted by GitHub!"
- uses: actions/checkout@v3
- run: echo "The ${{ github.repository }} repository has been cloned."
- run: echo "Setting up JDK"
- name: set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: gradle
- run: echo "The workflow is now ready to test your code."
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- run: echo "Building Debug APK."
- name: Build with Gradle
run: ./gradlew build
- run: echo "Build status report=${{ job.status }}."
In the following sequence, the step, uses: actions/checkout#v3, performs the task of checking out the repository onto the machine where the job is being executed.
Continuing, the subsequent action includes a distinctive name field, providing a description of the uses: actions/setup-java@v3 action. Following this, there are additional details provided within the with tag. This section specifies the Java version and the build tool in use. In this specific instance, Gradle is the designated build tool.
Further messages are displayed, along with some adjustments to permissions. Towards the end of the script, the final command run: .gradlew build is executed. This command effectively builds the debug version of the project. A subsequent print statement reports the status of the build process.
In the next example, we’ll walk through a job designed to build the release version of our project and sign it using a keystore stored within GitHub secrets.
jobs:
build:
name: Generate App Bundle
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: gradle
- name: run: chmod +x gradlew
- name: Bundle 'release' with Gradle
run: ./gradlew bundleRelease
- name: Sign AAB
id: sign_aab
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEYSTORE }}
alias: ${{ secrets.SIGNING_ALIAS }}
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.SIGNING_ALIAS_PASSWORD }}
- run: echo "Build status report=${{ job.status }}."
- name: Upload App Bundle
uses: actions/upload-artifact@v1
with:
name: aab
path: ${{steps.sign_aab.outputs.signedReleaseFile}}
Moving on to the new commands introduced in this example, the first one is run: ./gradlew bundleRelease. Unlike the previous build command in our initial example, this triggers the creation of a release build variant and generates an Android App Bundle (.aab) file instead of the regular .apk file. To produce an APK, a simple command like ./gradlew buildRelease can be used.
The next command, uses: r0adkill/sign-android-release@v1, is a community-created action. It takes the freshly generated build file, in this case, the .aab file, and signs it using the specified certificate and variables within the with: container. Notably, GitHub secrets are utilized as a part of this command, enhancing security and confidentiality.
In this second example, the final new command is uses: actions/upload-artifact@v1. This action stores a variable at the specified path and assigns it a given name. This stored artifact can be accessed later within the same workflow, enabling seamless continuity of tasks. Further details on this process will be explored in the next and final example.
Deploying to GitHub Packages
In the final step, we will examine another job that utilizes the previously executed job to obtain the resulting Android App Bundle (AAB) file. This AAB file will then be uploaded to a GitHub Release within the specified repository.
jobs:
release:
name: Release App Bundle
needs: build
runs-on: ubuntu-latest
steps:
- name: Download AAB from build
uses: actions/download-artifact@v1
with:
name: aab
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: Tag Name
release_name: Release Name
- name: Upload Release AAB
id: upload_release_asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: aab/app-release.aab
asset_name: ${{ github.event.repository.name }}.aab
asset_content_type: application/zip
- run: echo "Upload returned with status=${{ job.status }}."
In this section, there are three key functions, culminating in a final status update for the upload job. Before delving into each command individually, let’s address a new addition to the workflow. The ‘needs’ field presupposes the existence of another job with the name ‘build’, which this job relies on. It potentially utilizes any assets the preceding job might have uploaded.
Now, focusing on the commands, the process begins with uses: actions/download-artifact@v1. This command downloads an asset named ‘aab’, the same asset uploaded in our previous example. It assumes that a job similar to the one in the prior example has run earlier, providing the necessary artifacts.
Moving forward, uses: actions/create-release@v1 attempts to create a new GitHub release for the specified repository. It uses the GitHub token stored in secrets for authentication. This function requires both a ‘tag_name’ and ‘release_name’, for which we’ve inserted placeholders to simplify the example.
The final new function introduced in this section is uses: actions/upload-release-asset@1.0.1. This action takes the ‘.aab’ file from our asset using the ‘asset_path’, as defined by the ‘asset_content_type’ variable and named by ‘asset_name’. The action attempts to upload this asset to the specified ‘upload_url’ path. Upon completion, it emits a success status, which we capture and print in the subsequent ‘echo’ command.
Conclusion
GitHub Actions offers a powerful platform for automating Android CI/CD pipelines. This guide provides a foundation to set up basic and advanced workflows. With the support of the GitHub community, customizing actions for specific needs becomes accessible.