If you are building Android apps, you can’t really go without a continuous integration system (ci) to compile your code, run your tests and automate various other tasks. There are many ci systems out there, but for my personal projects I tend to rely on a Jenkins instance that I host myself. I’m using a little shared library I created to make building Android apps easier, while keeping the Jenkinsfile concise.

A Jekins pipeline

In Jenkins you define pipelines to build your software. The pipeline definition is kept in Jenkinsfile that you check in with your project, effectively “configuration as code” that most other ci systems provide in yaml files.

In this Jenkinsfile you then describe every step that Jenkins should take to build your project.

You can customise the dsl (the definition of the steps) in that Jenkinsfile using shared libraries. This allows for a common way to configure and build your projects. The library itself is pulled from a git repository, so you can update the library independently of your projects. These libraries are configured in the Jenkins main settings.

A common Android pipeline

Looking at my personal (reasonably small) projects, I have a few common steps that I’d like to perform on ci:

  • Make sure the required Android sdk components, like build tools and target plaforms are installed
  • Run tests with reports
  • Run lint checks with reports
  • Build a release version of my build and sign it using credentials that I store in Jenkins
  • Archive the apk, bundle and ProGuard mapping files, as I need those files when uploading to the Play Store or for testing.

All of my Android builds run in a Docker container that I use to setup the correct environment. That has the benefit of it being relatively self contained: all you need is the Dockerfile and you should be able to build the project within that container, using Jenkins or something else.

The library I created for my Jenkins instance captures these common steps.

A sample Jenkinsfile

With the library in place, my Jenkinsfile looks something like this:

pipeline {
    agent any
    stages {
        stage('build') {
            steps {
                android {
                    sdkPackage "build-tools;29.0.2"
                    sdkPackage "platforms;android-29"
                    credential 'myapp-keystore', 'keyStorePassword'
                    target 'testDebug lintDebug bundleRelease'
                }
            }
        }
    }
}

The android block is not a standard Jenkins construct; it’s defined in the pipeline library. When this pipeline runs, a couple of things happen:

  • A Docker container is built. If a Dockerfile is found within the project, that Dockerfile is used, otherwise a Dockerfile that is bundled with the library is used. Allowing me to specify a custom Dockerfile is great when I want to use the ndk. The default Dockerfile doesn’t include that, since I hardly ever need it.
  • sdkPackage allows to specify which sdk components are needed for this build. These packages are passed as an argument to the Dockerfile. When omitted, default components specified in the Dockerfile will be used.
  • credential specifies what credentials (secrets) to use for this build. In Jenkins a credential has an id and a secret text. The second argument is as a project property that is set using an environment variable for the Gradle build, passing in the secret that way.
  • target specifies the targets to pass to ./gradlew, has a default of assemDebug when omitted.

For every build, test and lint reports are collected and artifacts are archived.

Try it yourself

You can find a copy of my current pipeline library here. If you have a working Jenkins setup with pipelines and builds using Docker it should work on your Jenkins instance too.

Please note:

  • While it works for my personal setup, it lacks some checking of values that you’d probably want if you are deploying this to a greater audience.
  • This serves merely as an example, I’m not planning to update or maintain this example!
  • If want to use this library as is, please make sure to fork it. Referencing pipeline libraries on your Jenkins instance that you do not control are considered a security risk as the library can change at any moment, affecting your builds.

I hope this will give you some ideas how to add your own shared library or how you can customise this even further!