Setting up Gitlab CI to build an Android project

Hello! My name is Dasha, I’m an Android developer in the PREMIER online cinema team and I want to share with you the story of how we started to put Gitlab CI scripts in order 🙂

There are a lot of build options in our project, and in order not to spend a lot of time waiting and searching for the necessary build, we needed to at least get a rebuff about the completion of the job. And then – solve the inconvenience with copy-paste, so that script support does not cause burnout 🙂 Let’s go!


Let’s start with the simplest script in .gitlab-ci.yml:

image: jangrewe/gitlab-ci-android
  - build

  stage: build # стейдж сборки приложения
  when: manual # запуск только вручную
    - ./gradlew -Pci --console=plain :app:bundleProdGoogleRelease -Pbuildnum="$CI_JOB_ID"
      - sources/app/build/outputs/bundle/prodGoogleRelease/
# путь к артефактам
    expire_in: 1 week
# время жизни артефакта, у нас он должен храниться только 1 неделю

We use image jgrewe/gitlab-ci-android, since it already has the Android SDK and other libraries for building Android apps. All static variables related to security are in Gitlab Variables (Settings -> CI/CD -> Variables).

Here we start building the Google variant of bundleProdGoogleRelease with the Pbuildnum parameter, which we use as the build code (versionCode), and also write it in the name of the build to match it with the job. Pbuildnum is a project property that can be obtained from itself like this:

versionCode ='buildnum').toString().toBigDecimal()

How to use project properties – you can read here.

CI_JOB_ID – Job ID, used to indicate the build version.

CI_JOB_STAGE – job stage, CI_COMMIT_REF_NAME – branch name.

There is also an expire_in parameter here, which indicates the storage time of the finished artifact on the server, here we have 1 week. It is started manually to avoid high load on the server.

In this job, I use the predefined Gitlab CI variables such as CI_JOB_STAGE, CI_COMMIT_REF_NAME, CI_JOB_ID, but there are also many others, which are described in Gitlab CI Documentation.

It looks simple: the job has worked – the artifact is ready. At this stage, the pipeline at work had a negative effect on our psyche, and our eyes turned red while searching for the necessary assembly …

We notify

…or you could just get the link in the messenger right away and download the artifact from there 🙂

You can use any messenger, but I will show you with Slack as an example.

To implement sending messages to Slack, you need to generate a webhook, this process is described in detail in documentation.

  stage: build
    - >-
      curl -X POST --data-urlencode 'payload={"channel": "'$SLACK_CI_CHANNEL'", "username": "AndroidCI", "text": ":white_check_mark: *Premier-Android*: Готов новый артефакт *'${CI_JOB_NAME}'* <'${ARTIFACT_URL}'|'${CI_JOB_ID}'>" }' $SLACK_WEBHOOK

The after_script block is called after the main script block is executed.

Here we send a POST request with the –data-urlencode parameter, where we specified the message template and the previously received SLACK_WEBHOOK.

ARTIFACT_URL is formed as follows:


JOBS_PATH is the path to the list of jobs in Gitlab.

CI_JOB_ID — ID of the selected job.

In order to form a link to the browser in ARTIFACTS_ARCHIVE_PATH, we specify /artifacts/browse/, and for the download link – /artifacts/file/.

And we specify in ARTIFACT_PATH the path to the bundle or apk files.

And here in Slack we get the following message:

And by clicking on the job number, a window will open with an artifact that can be downloaded:

Voila, I launched the build, received a notification with a link, downloaded it and went looking for bugs 🙂

So we set up the assembly and sending of messages: colleagues select the type of assembly, click on the button and wait for notifications.

It seems to look normal, and for small projects this is enough 🙂 But do not rush to rejoice! We were stopped by the copy-paste police and are giving us a fine. For what?

Attentive readers understood that for each type of assembly we need to copy all of the above code and fix a couple of lines 🙂


./gradlew -Pci --console=plain :app:bundleProdGoogleRelease -Pbuildnum="$CI_JOB_ID"

      - sources/app/build/outputs/bundle/prodGoogleRelease/

Mess, we need to fix this, otherwise our .gitlab-ci.yml will grow to a huge size, and then how to maintain this … Let’s get started!

We are modernizing

First of all, we select groups of pipelines so that each contains only scripts that perform the same thing for each assembly.

To do this, you need to use the include modifier in .gitlab-ci.yml.

After the changes, the .gitlab-ci.yml file began to look like this:

image: jangrewe/gitlab-ci-android

  - local: .gitlab-ci-merge.yml
  - local: .gitlab-ci-build.yml
  - local: .gitlab-ci-build-tv.yml
  - local: .gitlab-ci-deploy.yml
  - local: .gitlab-ci-huawei.yml
  - local: .gitlab-ci-browserstack.yml
  - local: .gitlab-ci-distribution.yml
  - local: .gitlab-ci-code-analyze.yml


  - analyze
  - build
  - deploy
  - test

Next, we will highlight the common actions that are used in all scripts and add them to our gitlab-ci.yml. For this we use reference-tags for declaring common scripts.

We save the variables in variable.env for their further reuse in the next job, you can read more about variables and their use here.

  allow_failure: false
    - echo "LAST_JOB_URL=${CI_JOB_URL}" >> variable.env
    - echo "LAST_JOB_ID=${CI_JOB_ID}" >> variable.env
    - echo "LAST_JOB_NAME=${CI_JOB_NAME}" >> variable.env
    - cat variable.env
      dotenv: variable.env

Select the general build script:

    - ./gradlew -Pbuildnum="${CI_JOB_ID}" --console=plain :${APPLICATION}:bundle${VARIANT}
    - echo "${JOBS_PATH}${CI_JOB_ID}${ARTIFACTS_ARCHIVE_PATH}${ARTIFACT_PATH}" > .var_artifact_url
    - echo "SUCCESS" > .var_state
    - echo "APPLICATION=${APPLICATION}" >> variable.env
    - echo "LAST_JOB_ID=${CI_JOB_ID}" >> variable.env
    - echo "VARIANT=${VARIANT}" >> variable.env
    - echo "ARTIFACT_PATH=${ARTIFACT_PATH}" >> variable.env
    name: "${ARTIFACT_NAME}"
    expire_in: 1 week

More details about passing variables between jobs are described here.

And add code for sending messages with links to assemblies.

  allow_failure: false
    - echo ${SLACK_CI_CHANNEL} > .var_channel
    - echo ${CI_JOB_URL} > .var_artifact_url
    - echo "NONE" > .var_state
    - CHANNEL=$(cat .var_channel)
    - ARTIFACT_URL=$(cat .var_artifact_url)
    - STATE=$(cat .var_state)
    - env
    - >
      if [ "${STATE}" == "SUCCESS" ]; then curl -X POST --data-urlencode 'payload={"channel": "'${CHANNEL}'", "username": "AndroidCI", "text": ":white_check_mark: *Premier-Android*: Готов новый артефакт *'${CI_JOB_NAME}'* <'${ARTIFACT_URL}'|'${CI_JOB_ID}'>" }' $SLACK_WEBHOOK; fi

Further using extends we inherit the job from the above scripts.

As a result, after moving the common code into a separate .gitlab-ci-build.yml file, the pipeline for the Google release build looks like this:

    - .base_task
    - .base_bundle_task
    - .with_notification
  stage: build
  when: manual
    ARTIFACT_PATH: "sources/app/build/outputs/bundle/prodGoogleRelease"
    APPLICATION: "app"
    VARIANT: "ProdGoogleRelease"

Thus, the script has become a little more flexible, for other assemblies, we only need to change the version and the path to the assembly. This pipeline can be further optimized, but that’s another story… 🙂

Although not by much, we have made life easier for us and our testers. To build an artifact and send it to the test, we only need to press one button, that’s all!

ZY: we don’t say goodbye on this, in the following articles we will automate something else 🙂

Similar Posts

Leave a Reply