In the last part of this blog series we covered the installation and setup of Jenkins on our machine and the creation of a Jenkins job that will connect to the GitHub repository for our Android application. Any time code is pushed to the repository or a PR is created the job will be notified and start a new build.
Now we will cover the configuration of the build process by creating a Jenkinsfile that acts as the configuration file for our pipeline. It sets out how the pipeline should be run, the stages of the pipeline, what actions to perform at each stage and any additional options. We will also show the Dockerfile that Jenkins will use to create a container to run our pipeline in.
The Jenkinsfile will inform Jenkins how we want the job to be run. It will include each step of the pipeline and the actions of each step.
At the top of our Jenkinsfile we declare some constants for branch names, build variants and deployment tracks. We also create some helper methods for getting the build and deployment types based off the branch name. The last method is to determine if the current build is a deployment build; this only occurs on the develop and master branches otherwise we would be performing deployments when code is pushed to feature branches or when PRs are created:
The agent section tells Jenkins how to run the agent, here we specify we are using a Dockerfile which it will then look for at the current directory level.
Here we specify environment variables that will be created for this build. We make use of the credentials plugin to securely retrieve the properties we need in order to sign the app:
The first stage of the pipeline is to build and run the unit tests for the app. If this succeeds, we will progress to the next stage. In the case of any failing test, pipeline build will fail:
If this build is being run off the develop or master branch then we will assemble a bundle of the app and sign it using the credentials we retrieved at the start of the build:
The final stage is to deploy the app to the Play Store. Depending on the branch the build is being run off will determine if the app is deployed to the internal/QA track or the release/production track. We add a confirmation check if it is to be deployed to release. Next, we read the contents of the CHANGELOG.txt for release notes to be added as part of the build. Finally, the Google Play Android Publisher plugin pushes our bundle with the release notes to the appropriate track on the Play Store.
The full Jenkinsfile can be found here:
The Dockerfile we use looks like this:
We will also need to update the local.properties and the app build.gradle file in our Android project so we can inject our keystore credentials for generating our bundle and so we can dynamically generate a version code for our app.
Back in Jenkins we will need to add our keystore credentials. While in your job, navigate to the “Credentials” > “Folder” > “Global Credentials”.
Here we will add secret text credentials for the store password, key password, key alias and a secret file for the keystore. When a build runs it will pull in these credentials to sign the app bundle. Ensure the names of these credentials match the names of the credentials being pulled into the Jenkinsfile.
The last part of the pipeline is using the Google Play Android Publisher plugin. The final stage takes in a new credential, this is a service account token that will allow Jenkins to access our Google Play console to upload a new version of the app. Follow the instructions on the plugin page to generate a new service account and add the credentials.
Overall, the process to implement a CI/CD pipeline for an Android application is relatively straightforward and will help to save a lot of time in the long run allowing developers to focus on the code.
By introducing Docker as well we can help keep our Jenkins instance less cluttered and small. This is especially true if you are using Jenkins to run builds for various parts of your project such as backend and frontend.
The pipeline itself isn’t complex, you can look into adding more stages such as integrating Sonarcube, adding test coverage reporting, linting, or making use of the Android Emulator plugin to run instrumented tests along with unit tests.