Tag Archives: android

Android Product Flavours with Jenkins Job

Hi guys, this blog is for having product flavours in one single Jenkins job.
Instead of having multiple jobs (dev, staging and prod environment) for same project, we can have it one job.

We can use jenkins global properties for giving choice while selecting parameters values. But we are using values from our own workspace, due to following reasons:
1) We don’t want to be dependent on Jenkins Global properties or any other guy to maintain properties in jenkins for our job.
2) It can be changed from our own code base only.
3) We can have multiple combinations of same parameter (using build variants).

So that it becomes easier to maintain.
Also we will be able to perform different actions for same job.
For example,
1) We want to upload Prod-Release flavour APK at your repository and don’t want to for other build types.
2) We want to sign apk only for Prod-Release builds and not for other type of builds.
3) We want to assign different values for same keys/parameters (same like build variants)

There are many blogs where we can read about Android Project Integration with Jenkins.
But I was not able to find a blog which is having implementation of Jenkins with Android product flavours.
And somehow I was able to make it up using Jenkins Plugins only, so would like to share it.

Starting with product flavours in Android project. 
We are having three flavours:

I) Dev (This is for internal development environment).

II) Staging (This is a exact copy of PROD env, its used for testing before releasing).

III) Prod (This is real time env)

And we have implemented for getting values from Jenkins and update or override if needed, using groovy script in gradle.

The following image is for different properties files based on type of config:

To read the values from different properties file based on build variant we choose to build the apk, we have to specify code in build.gradle (root) :


import java.util.regex.Matcher
import java.util.regex.Pattern

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
        maven {
            url 'https://maven.google.com/'
            name 'Google'
        }
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        classpath 'com.google.gms:google-services:4.3.4'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url "https://maven.google.com"
        }
        maven {
            url 'https://maven.google.com/'
            name 'Google'
        }
        
        maven { url "https://jitpack.io" }
    }
}

ext {
    // Get the current flavor of the build Ex: dev, stg, prod

   //If build is made from jenkins
    if (project.hasProperty('CONFIG_TYPE')) {
        flavor = project.property('CONFIG_TYPE')
        def pattern = Pattern.compile("([A-Za-z]+)(Release|Debug)")
        def buildVariant = ""

        Matcher matcher = pattern.matcher(flavor)
        if (matcher.find()) {
            buildVariant = matcher.group(1).toLowerCase()
        }

        // Read the .properties for config
        config = getProps('config/' + buildVariant + '.properties')

        if (project.hasProperty('BASE_URL')) {
            config.setProperty('BASE_URL', project.property('BASE_URL'))
        }
        if (project.hasProperty('BASE_PORT')) {
            config.setProperty('BASE_PORT', project.property('BASE_PORT'))
        }

    } else {
      //If build is executed from local or Android Studio
        flavor = getCurrentFlavor()
        if (flavor.isEmpty()) {
            flavor = "dev"
        }

        // Read the .properties for config
        config = getProps('config/' + flavor + '.properties')
    }

}

def getProps(path) {
    Properties props = new Properties()
    props.load(new FileInputStream(file(path)))
    return props
}

def getCurrentFlavor() {
    Gradle gradle = getGradle()

    // match optional modules followed by the task
    // [a-z]+([A-Za-z]+) will capture the flavor part of the task name onward (e.g., assembleDevRelease --> Dev)
    def pattern = Pattern.compile("([A-Z][A-Za-z]+)(Release|Debug)")
    def flavor = ""

    gradle.getStartParameter().getTaskNames().any { name ->
        Matcher matcher = pattern.matcher(name)
        if (matcher.find()) {
            flavor = matcher.group(1).toLowerCase()
            return true
        }
    }

    return flavor
}

We are first checking whether the property exists or not project.hasProperty(‘CONFIG_TYPE’). This property we are setting from jenkins choice parameter plugin. And then reading each and every property from jenkins only.

And if we are just executing build in local, then it will take first that current flavour and then will read all properties from selected build variant type.

Now to assign properties which used in app, use this following code in build.gradle (app-level) inside android block:

In different properties file, we have assigned different values for keys:

Now lets jump in Jenkins with our job.

Selecting different config will show different values and it will read from properties file we have in our workspace.

Now lets move to configuration part.

First we are setting Branch selection in Git Parameter and Config-Type is taken in Choice Parameter plugin. Config-Type is build variant what we have in our project but we have statically mentioned its choices.

Then we are showing for Base URL based on Config-Type selected. In this we are using groovy script to fetch values. We have taken this as Active Choice Reactive Parameter as we are taking reference parameter as Config-Type.

(Please Note: We have to uncheck “Use Groovy Sandbox” Option and should be having admin access for writing the groovy script else we have to request for approval of script then only it will execute)

Lets go through with the groovy code, how it is reading properties.
i) We give the path of config folder in our workspace.
ii) We are loading properties file based on config type (which is taken as reference parameter)
iii) Loading file stream in properties object
iv) Getting value of based on key name
v) We have also handled for property not found case.

try{
Properties props = new Properties()
jenkinsURL=jenkins.model.Jenkins.instance.getRootUrl()
def relPropFileUrl="/var/lib/jenkins/workspace/TestAppJob/config/"
def propertyType = "dev"
if (CONFIG_TYPE.contains("dev")){
 propertyType = "dev"
}else if(CONFIG_TYPE.contains("staging")){
propertyType = "staging"
}else if(CONFIG_TYPE.contains("prod")){
propertyType = "prod"
}
def propAddress="${relPropFileUrl}${propertyType}.properties"
def stream = new FileInputStream(propAddress)
try {
props.load(stream)
} catch(Exception e){
e.printStackTrace()
} finally {
stream.close()
}
def var = props.getProperty("BASE_URL")
if(props.containsKey("BASE_URL") == false){
 return ["not found"]
}
return [var]
}catch (Exception e){
return[e.getMessage()]
}

Same we did for Base port parameter.

In SCM section we have given path of our repository and credentials to access code base (removed in screenshot).

In Build Triggers Section, we have used polling so that it can build APK if any changes found in master branch. You can check in Git Polling Log Section, it will show whether changes found or not and then only it will build for new APK. It will also update workspace while building APK.

We can set the schedulers based on time, date or month also. We have set for every 2 minutes.
Please note that polling will only happen with Master branch instead of any other feature branches.

Build Section

We have provided in task “clean assemble${CONFIG_TYPE}”, as we have to build APK only for selected type of Config Type or Product Flavour instead of all types of flavours which will much time consuming.

For Signing APK for release version only we have used multiple Or conditions.

Please note that we are only signing build which are of Release flavour. And that is why we have only allowed for three types of config type.
Also we have stored keystore file in jenkins -> Credentials section.
And using Id generated by jenkins.

Then we have used shell scripting for renaming the APK.
i) We are splitting config type value.
ii) Moving to specific folder where APK is stored.
iii) As folder can be debug/staging/prod and it can further be debug/release. Therefore we have used config type.
iv) Changing the name APK.

Post Build Actions

We can either upload build to any repository or not based on config type. For that we have used shell scripting.

Finally we are storing APK file in Jenkins so that APK is available else it will not store APK.

And we can download and use it.

Problems we faced:
1) We were not able to take updated workspace before building. As we are taking parameters values from properties file we have in code base, so we require to have updated code base at any given point of time.
Solution: We used SCM Polling feature to get updated code (it will create one build and it can fail due to no params value available or No Such Property issue, but we will get updated properties for params).
We can also use Git Webhook to trigger for new build, as polling is expensive.

2) We were not able to find or update workspace for any other branch (or on branch selection).
For example, we have to build for FB_TEST_TWO branch, but we will not be getting its workspace till we actually build for this branch and it will be having previous workspace properties only.
Solution: For this we might require to use Pipeline plugin and specify steps for this.
Following are the links:
https://support.cloudbees.com/hc/en-us/articles/115003945572-Pipeline-Build-with-git-step-on-a-specific-branch-is-triggered-by-a-different-branch

This blog will be helpful when you have to manually build APK from jenkins job and project is having multiple build variants.

It would be really appreciable if you let me know for any updates/changes or any other better alternative for having product flavours in Android Jenkins job.

Thanks for reading ! 🙂

References:
1) https://www.robosoftin.com/blog/configuring-android-builds
2) https://medium.com/bbc-design-engineering/signing-in-the-rain-or-why-you-should-let-jenkins-do-android-release-builds-c08228213242
3) https://sathishpk.wordpress.com/2016/03/07/jenkinsgroovyactivechoiceparameter-rendering-dynamic-parameter-by-reading-only-keys-from-property-file/
4) https://plugins.jenkins.io/uno-choice/