Android CI with Jenkins and Firebase Test Lab
Repeatable tested builds are essential to delivering a high quality android app that your users will love. We will look at how we can achieve repeatable tested builds using Jenkins and Firebase Test lab in this month's post. We will even configure our process to be kicked off by a push to master in a Github repository.
Jenkins
The first thing we'll need to do is get Jenkins installed on our build machine. Jenkins is a free and easy to use build automation tool that has a lot of really useful community contributed plugins. We can use it to build pretty much anything and that's why we'll be using it to build our android projects.
I'll show you how to install Jenkins via apt on a Ubuntu machine, but you can install Jenkins on Windows, Mac OS, or your favorite flavor of Linux. The Jenkins download page even includes instructions for running Jenkins in a Docker container.
Let's begin by adding the Jenkins' repository key to our apt-key list.
wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
Next up we'll add their repository to our repository list
sudo add-apt-repository "deb https://pkg.jenkins.io/debian-stable binary/"
.
Now we can install Jenkins via apt-get install and get updates to Jenkins during our normal update process. Let's run sudo apt-get update
to refresh our list of available software and then install Jenkins via sudo apt-get install jenkins
. Once Jenkins has been installed we will need to start it up as a service. We can do that on ubuntu like this:
sudo service jenkins start
Once the service starts successfully we should be able to navigate to our build machine on port 8080 to see the Jenkins setup screen.
You will need to copy the secret key located in the specified file in order to proceed with Jenkins setup. Once you've done that you will be able to choose to install default plugins or install custom plugins. We are gonna choose the custom plugins option because there are a few additional plugins we will want to install in our Jenkins instance. The first one we'll grab from the list of available plugins is the 'embeddable-build-status' plugin. This one is just for fun, it will allow us to place a little indicator of our build status in our project's readme file. We'll also grab the Junit and Github plugins, we'll use the Junit plugin to publish the results of our tests as a graph on our Jenkins project page and the Github plugin is what we'll use to kick off builds every time we push to master.
Once we've grabbed our additional plugins we can proceed with the install, when it completes we will be prompted to create a Jenkins user. Once we've done that our Jenkins instance is ready for us to create our first project.
We'll create a pipeline for this project:
The next step will be to configure our git repo url. Select the GitHub project checkbox and put your project's url into the input field.
The final piece of configuration will be in the pipeline section of configuration. We are going to choose to get our pipeline's definition from a script in our repository. That way our build script is checked into git and can evolve with the project.
The jenkinsfile will contain all the information Jenkins will need to execute our pipeline. You can find lots of example snippets inside of Jenkins for common pipeline tasks that you may need to perform. Jenkins also provides a handy guide on getting started with pipelines and jenkinsfiles here.
Once we've finished configuring our project we can leave Jenkins behind for a little while and move on to setting up the Google Cloud SDK.
Google Cloud SDK
The Google Cloud SDK will be used by our Jenkins build server to upload and run our Android Instrumentation tests in the Firebase Test Lab. You will need to create a Firebase project before you proceed with installing and configuring the SDK.
You can get started with Firebase easily here. I won't walk you through creating your first Firebase project because Google does a pretty good job of it. Once you have your project though we'll want to open up a shell on your build server. We'll be adding the Google Cloud SDK just like we added Jenkins.
First we'll need to define a shell variable
export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)"
then we'll add their repo to our repo list
echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
finally we'll add their key
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
Now we can just use apt to install their tools
sudo apt-get update && sudo apt-get install google-cloud-sdk
Once we have configured our Cloud SDK we will need to enable a couple of API's before we can begin running tests in the Firebase Test Lab. In order to do this you'll want to head over to the API & Services section of your cloud project.
The URL you want should look something like this:
https://console.cloud.google.com/apis/library?project=<your project id>
Once you are there enable the following apis:
Google Cloud Testing API
Cloud Tool Results API
Firebase Rules API
Google Service Management API
We will also want to go to our project's IAM & Admin section and create a service account. The service account will be the account that actually runs the Firebase tests for us whenever Jenkins builds.
We will give this service account the role of "service account user". Google will provide a key file when creating the service account that we will need to save.
We will upload it to our build server later so that we can use it to authorize our service account.
When you have the keyfile on your server you'll want to run the command
gcloud init --console-only
and follow the instructions to initialize the Cloud SDK.
You can initialize it with your own Google account momentarily since we will be switching it to the service account as soon as we have finished initialization.
The process is fairly straight forward and goes in a step by step fashion. You can see the documentation for this process right here.
When the initialization is done we can switch it over from using your personal account to using the service account.
gcloud auth activate-service-account --key-file=<location of key file>
Now when Jenkins builds your android app the service account will be in charge of running the Firebase tests. Let's move on to installing the Android SDK on your server since we can't build an Android app without that :)
Android SDK
First you'll need to download the command line tools from here
If you're following along at home on a Ubuntu machine then we can grab the download link and use wget to save the zip file to our build server
wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
Once the download completes we'll need to extract the files to a place where our Jenkins instance will be able to get to them. I usually like to stick them in /opt/android. The important thing is that you make sure the Jenkins user has read/write/execute permission to wherever you place the Android SDK.
Unzip the SDK and then navigate to the tools/bin folder within it and execute sdkmanager --update
.
That will make sure you have the latest version of the tools. It will also move the files around a little bit so you'll need to find tools/bin again when its done but then we'll run ./sdkmanager --licenses
and accept all of the license prompts. That way jenkins shouldn't ever fail because of a missing license. Next we can install the latest platform, build-tools and google related repositories.
./sdkmanager "platforms;android-26" "build-tools;26.0.1" "extras;google;m2repository" "extras;android;m2repository"
. You'll probably need more than just that before you can build your app but you can find anything you might be missing by running ./sdkmanager --list
and then grabbing anything in the list that you need.(Pro-Tip: If you need something and the output has been truncated, use ./sdkmanager --list --verbose)
Finally we'll just need to edit our /etc/environment file to add a new envrionment variable as well as modify our path.
Our android home variable will be set to wherever we originally unzipped our sdk.
ANDROID_HOME=/opt/android
You'll also want to append
$ANDROID_HOME/tools and $ANDROID_HOME/tools/bin to your $PATH variable.
We should now be all set on the server side of things. All that's left to do is to go to our project and add the Jenkins file that will contain the instructions necessary for building the project.
Jenkinsfile
Let's look at a basic Jenkinsfile and walk through how each piece of it translates into a part of our pipeline.
The first part of the Jenkinsfile that you'll notice is the node block declared at the start of the file. You can think of the node block as just giving us a place to define all of the steps that will be inside of our pipeline.
Each of these are called a "stage" and we can give them a name. They will show up as distinct steps in our Jenkins pipeline UI.
The first stage we've named "Checkout" and it grabs our repository from GitHub. Next we run our unit tests and then proceed to assemble our apks for the android test lab. Note that our pipeline will fail if any of these stages fail and subsequent pipeline steps will not run, that way we avoid doing a lot of unnecessary work if for example our unit tests fail. Once we've assembled our debug apk and our test apk we can upload them to the cloud test lab for execution. The cloud SDK will intelligently report back to our shell so that test failures will actually show up as a failure in Jenkins and halt our pipeline.
The last two stages in our pipeline will build our final apk and then archive it along with our test results.
We can now commit our Jenkinsfile and push it GitHub. At this point you should be able to go to your Jenkins instance and manually build the project. We don't want to have to manually build our project everytime we push to GitHub so in the next section we will look at how we can setup an integration between GitHub and our Jenkins instance that will kick off builds automatically.
Github Integration
We will use GitHub's "Integrations & Services" option in our project's setting page to handle automatically building our project on ever push to the master branch.
Choose add service and search for Jenkins and choose the `Jenkins (GitHub Plugin) option. It should then ask you to provide a hook url for your jenkins instance that it can call whenever it detects a push. You'll need to provide it with some user credentials so ideally you would create a jenkins user with limited permissions capable of just kicking off the build. The url might should look something like this:
http://<jenkinsUsername:jenkinsPassword>@<jenkins-host-address>:<jenkins port>/github-webhook/
Once you've saved and enabled the integration service you should be able to click the "Test Service" button and then navigate back to your Jenkins instance. You should see your build kicking off.
Congratulations! You now have a reproducible build process for your android app that runs all of your app's unit and instrumentation tests automatically on every push.
Helpful Links
https://martinfowler.com/bliki/ReproducibleBuild.html
https://pkg.jenkins.io/debian-stable/
https://cloud.google.com/sdk/downloads#apt-get
https://cloud.google.com/sdk/docs/initializing
https://firebase.google.com/docs/test-lab/
https://github.com/codepath/android_guides/wiki/Installing-Android-SDK-Tools