Tuesday, July 9, 2019

Bitbucket pipelines CI/CD integration for android project

Date : 08/07/2019

Hi,

Recently I have been assigned to a new android project and I got the opportunity to integrate CI/CD to this project. I was first searched how to do this with bitbucket, because this project was planned to use bitbucket from the day 1. Even though there were many articles and blogs on how to do this I only found few on android integration. And those articles were 2 or 3 years old and/or not having a complete guide.

So I have to get my hands dirty :) and had to find a solution which works for my project. Here are the steps I had to do for CI/CD integration with bitbucket.

1. Create a repo in Bitbucket
2. Create a pipeline for the project (Bitbuckets' way to do CI/CD)
     There is a selection item called "Pipelines" at the left side panel.
     


   Select Java(Gradle) to generate bitbucket-pipelines.yml file. We are going to edit this file later.



3. Edit bitbucket-pipeline.yml file to test CI

   First open bitbucket-pipeline.yml file in Android Studio or you can edit it online. But I prefer Android Studio because it makes alignments clear.
You need a docker image which contains latest build environment for android. Goto dockerhub and select appropriate one that have your environment requirement, In my case I selected mingc docker image because latest android SDK and Kotlin support.

Change the first line to get docker image

image: mingc/android-build-box:latest

Edit "pipelines" area to integrate CI as follows.


pipelines:
  default:
    - step:
        caches:
          - gradle
        script:
          - chmod +x gradlew
          - echo 'inside default branch. tests should run now.'
          - ./gradlew test
  branches:
    development:
      - step:
          caches:
            - gradle
          script:
            - echo 'development branch building. tests should run now.'
            - chmod +x gradlew
            - ./gradlew test


default tag means if you haven't declare any specific script for a branch, it will run default. As for above case the default script will run if we push a code to master branch or any other feature branch but it will not run if we push a code to development branch. Because there is a script to run if we push to development branch. As per above file gradle will execute unit test for development branch and for any branch that is not defined in the script.

Now I need to run some script when I create a PR (pull request) to master branch.

pull-requests:
  development:
    - step:
        caches:
          - gradle
        script:
          - echo 'PR Script running'
          # - ./gradlew assembleDebug


When PR is accepted the code change will merge to master branch. And that process will run default script since we didn't declare any script for master branch. So I will add a scrip to run when there is a change to master branch.

master:
  - step:
      caches:
        - gradle
      name: Test and build apk
      script:
        - echo 'master branch building.'
        - chmod +x gradlew
        - ./gradlew test
        - ./gradlew assembleRelease



This script is for create a release apk but we need to sign the apk in order to install on a device or distribute on Google play store. For this you need to change the app level gradle file and include keystore file which contains relevant keys.
Now create a keystore file and a keyAlias. Put this jks file on application root folder (where your app build.gradle file resides). Then change app build.gradle file as follows.

android {
    compileSdkVersion 28    defaultConfig {
        applicationId "****.****.****"        minSdkVersion 19        targetSdkVersion 28        versionCode 1        versionName "1.0"
    }
    signingConfigs {
        release {
            // You need to specify either an absolute path or include the            // keystore file in the same directory as the build.gradle file.            storeFile file("**********.jks")
            storePassword "*********"            keyAlias "*********"            keyPassword "**********"            v1SigningEnabled true            v2SigningEnabled true        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'        }
    }
}

Now when there is a change in master branch it will run the test and create a release apk. You can't see the release apk in Bitbucket files. To see the apk you need to define an artifact. Add following code after script tag in master tag.

artifacts:
  - app/release/app-release.apk


After master branch build you will see "Artifacts" tab next to "Build".


Currently this generated artifact will remain for 7 days. To save it permanently in "Downloads" you need to do some additional work. I'll describe them under CD since after saving this release apk we are going to send it  to test users via Slack.

4. Edit bitbucket-pipeline.yml file to test CD

Following image shows the download area in Btbucket.


To write a file in this location you need "App password". Select your image at the bottom left and go to "Bitbucket settings". There you can create app password. Give it a name and enable repositories write permission. Save the generated password because we need it later.



Now we will create repository variable to hold our username and newly generated app password. We need this variable to write the apk to downloads.

Select Settings -> Repository variables


Add new variable. Name is BB_AUTH_STRING and value is YOUR_USERNAME:APP_PASSWORD (This is where we use our saved app password)
eg: mark_twein:7ax3848XYu93933

Then add the following line to master tag.
- curl -X POST "https://${BB_AUTH_STRING}@api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" --form files=@"./app/build/outputs/apk/release/app-release.apk"

Here BITBUCKET_REPO_OWNER  and BITBUCKET_REPO_SLUG default repository variables. Don't worry about that.
So the master tag will now look like below.

master:
  - step:
      caches:
        - gradle
      name: Test and build apk
      script:
        - echo 'master branch building.'
        - chmod +x gradlew
        - ./gradlew test
        - ./gradlew assembleRelease
        - curl -X POST "https://${BB_AUTH_STRING}@api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" --form files=@"./app/build/outputs/apk/release/app-release.apk"
        
      artifacts:
        - app/release/app-release.apk


Now when you push a change to master branch you will find a release apk in the "Downloads" location.

5. Edit bitbucket-pipeline.yml file to send apk to a slack channel

Finally we are going to publish our apk to Slack channel.

For this first go to this link and when you are scrolling down you will see legacy token generator section. Create a token there and save it.

Then Create a new repository variable in Bitbucket. You may name this as "SLACK_TOKEN" and for the value put the generated token value.

Add the following code to master tag.

- curl -F file=@"./app/build/outputs/apk/release/app-release.apk" -F channels=your_channel_name -F token=${SLACK_TOKEN} https://slack.com/api/files.upload

put the channel name where you need to publish the apk.

Here is the final code.


master:
  - step:
      caches:
        - gradle
      name: Test and build apk
      script:
        - echo 'master branch building.'
        - chmod +x gradlew
        - ./gradlew test
        - ./gradlew assembleRelease
        - curl -X POST "https://${BB_AUTH_STRING}@api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" --form files=@"./app/build/outputs/apk/release/app-release.apk"
        - curl -F file=@"./app/build/outputs/apk/release/app-release.apk" -F channels=your_channel_name -F token=${SLACK_TOKEN} https://slack.com/api/files.upload
      artifacts:
        - app/release/app-release.apk


Now when you push the code to development bran the tests will execute. Then you make a PR and your lead approves the PR and development branch merge into master branch. This executes test and make a app-release.apk and put it into downloads area in bitbucket. After that the generated apk will publish into the selected channel where test users can install that to their phones.

phew..... Now that's it from my side. I hope you can take something from this article. I will list some useful links below.
Happy Coding :)


Some useful links

https://proandroiddev.com/bitbucket-pipelines-android-6eeff631f2eb
https://confluence.atlassian.com/bitbucket/get-started-with-bitbucket-pipelines-792298921.html
https://github.com/kigen/bitbucket-pipelines-android
https://api.slack.com/custom-integrations/legacy-tokens
https://confluence.atlassian.com/bitbucket/deploy-build-artifacts-to-bitbucket-downloads-872124574.html
https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html