Back to Blog

Using GitLab for Android development

Using GitLab for Android development

When I joined Ocado Technology a few years ago, we weren’t using anything like GitHub or GitLab. Being given access to GitLab was therefore a huge step forward for the mobile team. Previously, our approach consisted of many different tools:

  • Jenkins for Continuous Integration
  • Jira for task management
  • Crucible for code reviews
  • Gitolite repository for source control

If you are ever involved in a discussion about this topic with me, it doesn’t take very long to realise that I am a huge GitHub advocate and that I am an open source contributor. This means that I have seen what life is like on the other side, within the GitHub ecosystem of tools. My normal process includes GitHub for practically everything, supported by Travis CI for my build environment. The way all parts of the development process work together seamlessly is a very desirable way to develop software.

  • Merges are performed by pull request rather than manually.
  • Code reviews via pull request.
  • Code blocked from being merged if the CI job fails.
  • Issues, CI status, merges and code reviews all within same interface.
  • An easily searchable history of merges.

Naturally, I wanted to replicate these processes within GitLab and achieve a similar workflow. This desire lead me to stumble upon GitLab CI and so I started using it for the development of our Android apps.

The job

When it comes to configuring your CI builds, at the core is the .gitlab-ci.yml file. Similarly to Travis CI, this is where you put your build configuration, using some fairly simple YAML syntax.

image: {REPOSITORY}/{PROJECT}/{IMAGE_NAME}

before_script:
  - chmod +x ./gradlew

stages:
  - build

build_app:
  stage: build
  script:
    - ./gradlew assembleDebug artifacts: when: always expire_in: 2 weeks paths: - app/build/outputs/apk/app-debug.apk

The above configuration specifies a single stage pipeline with one job that builds a debug APK. You will likely want more from your CI process than is shown above, however, this is the basic starting point.

image: {REPOSITORY}/{PROJECT}/{IMAGE_NAME}

You start by choosing a Docker image with which to initialise the build environment. The simplest option would be to use a generic Java 8 image and to download the Android dependencies at the start of the build. This obviously adds to the total build time and so later on I will show you how to create an Android-specific image.

before_script:
  - chmod +x ./gradlew

This is where you can perform actions or run commands before the build starts. Usually this is where you would perform any setup and is also where you would download the Android SDK if you needed to. In the example given, it is ensuring the Gradle wrapper is executable.

stages:
  - build

Rather than just running all parts of your build process as a single step, GitLab CI operates around the concept of pipeline stages. All of the jobs within a particular stage can be ran in parallel and then each of the stages run consecutively. A simple example would consist of the stages: build, test and deploy. A further benefit to this approach is you will be able to easily see the point at which the pipeline failed.

build_app:
  stage: build
  script:
    - ./gradlew assembleDebug

This configuration only contains a single job, which is in the build stage. A more complex project would have multiple jobs and stages which all perform different actions.

artifacts:
    when: always
    expire_in: 2 weeks
    paths:
      - app/build/outputs/apk/app-debug.apk

For each job you can specify files to keep after the job completes. You have the option of choosing the conditions for when to keep these files, how long the files are kept and many further options.

That concludes the configuration of the project itself. Thanks for sticking with me. Needless to say you will need to commit and push this up to your repository.

The Docker image

As I mentioned earlier you could skip this step if you don’t think it is worthwhile for your project. However, from my experience it is worth encapsulating the Android dependencies into an image. This prevents the build from needing to download this at the start of each build, which inevitably reduces the total build time. This is clearly a good thing, so we can spend our time developing awesome features and less time waiting for pipelines.

Our project doesn’t have many developers working on it though, is all this complexity really worth it?

Depending on the complexity of your project and the frequency at which it is changed, it may not be necessary. For other projects shaving a couple of minutes from every job within the pipeline can be a huge win.

I will outline a working Android image configuration for anyone interested in this part.

Breakdown

I know that was a lot of ‘code’ to throw at you all at once, however, I was assuming you would probably just want to copy and paste it somewhere. To try and explain what it all means I will proceed to go through it piece-by-piece, so please stick with me.

FROM openjdk:8-jdk

The image is an extension of a publicly accessible Java 8 image.

ENV VERSION_SDK_TOOLS "3859397"

You will need to enter the version of the Android SDK tools you wish to use, this was the latest version as of 30th June 2017.

ENV ANDROID_HOME "/sdk"
ENV PATH "$PATH:${ANDROID_HOME}/tools"
ENV DEBIAN_FRONTEND noninteractive

Setup the location of the Android SDK and add the tools to the executable path.

RUN apt-get -qq update && \
    apt-get install -qqy --no-install-recommends \
    curl \      
    html2text \      
    libc6-i386 \      
    lib32stdc++6 \      
    lib32gcc1 \      
    lib32ncurses5 \      
    lib32z1 \      
    unzip \    
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Install some needed commands to extract and use the SDK.

RUN curl -s https://dl.google.com/android/repository/sdk-tools-linux-${VERSION_SDK_TOOLS}.zip > /sdk.zip && \    
    unzip /sdk.zip -d /sdk && \    
    rm -v /sdk.zip

Download and extract the Android SDK.

ADD android-packages.txt /sdk

The Android packages you wish to install are listed in a separate file, so you need to add this file to the image.

RUN mkdir -p /root/.android && \
    touch /root/.android/repositories.cfg && \      
    ${ANDROID_HOME}/tools/bin/sdkmanager --licenses && \  
    ${ANDROID_HOME}/tools/bin/sdkmanager --update && \  
    (while [ 1 ]; do sleep 5; echo y; done) | ${ANDROID_HOME}/tools/bin/sdkmanager --package_file=/sdk/android-packages.txt
  1. Fix an error which occurs during package installation involving the repositories.cfg file.
  2. Accept all of the SDK licenses.
  3. Update all SDK packages.
  4. Install the required Android SDK packages, which are listed in the android-packages.sdk file.
Hosting

Hopefully that explained the whole file. Obviously, you will now need to build the image and store it on a Docker repository so that your CI machine can access it. This could be a public repository such as DockerHub or your own repository.

Run your pipeline

Now that you have configured the job within the project and setup the image, you will need to setup your GitLab CI runners. I won’t cover this part of the process, as it is completely dependent on the environment you are using GitLab in and on your particular requirements. If you need some help with this part, please feel free to contact me.

You can configure which runners to use within the CI/CD pipelines section of your GitLab repository.

 

Once GitLab detects a valid .gitlab-ci.yml file it will start running your pipelines. You will now see some builds showing up within your repository. Please ignore the failed builds you can see above. We all have failed builds from time to time, the important thing is that these are just for feature branches and not on the master branch!

By clicking on a particular pipeline, you can see the jobs and stages in all their glory. In the example shown above, you can see that if the build had failed at any point you would be able to quickly narrow down at which stage this was. With large and complex projects this could save you a lot of time and effort.

Merge requests

It won’t come as a huge surprise that GitLab CI is seamlessly integrated into the merge request process. When viewing a merge request you will be able to see whether the latest pipeline was successful or not. On top of this you can enable a setting which prevents the merge request from being accepted unless the build passed. You can even go further than this and prevent any merges to the master branch except via merge requests. This is the only surefire way to guarantee the CI passes before merges occur.

By adding GitLab CI and merge requests into our development process, we found that the stability of our main development branch was improved. We can now accept changes, secure in the knowledge that the build is already passing.

Thoughts

There are many similarities between GitHub and GitLab. Likewise, there are many similarities between Travis CI and GitLab CI. The main difference you will see is that GitLab CI requires a lot more configuration. You are, however, rewarded for your efforts in that you receive a much more advanced and configurable CI process.

If you had access to both of these services, deciding between them would come down to context-specific requirements and the pricing between the services. For my open source projects, I use GitHub and Travis CI, it is all free. Funnily enough this is not the case once you have closed source projects, many developers and many concurrent builds. For Ocado Technology, GitLab now starts to look very promising in many different ways.

Anyway, hopefully now you can at least configure GitLab CI for your Android project.

Enjoy and thanks for reading!

Scroll Up