Sunday, September 28, 2014

Gradle - The next generation ANT (Build management tool adopted by Google)

All those who've worked on automating the builds must have worked on ANT. ANT was a simple tool but extremely effective in making the builds automated. It had a simple XML based structure, allowed you to break building pieces into tasks and easily use external JARS. You could also create your own tasks.

However, I think Ant has not evolved as we would have wanted. The XML syntax, despite being easy to use is tedious. Time to analyze the other players on the block.

A few popular ones out today are:
  • Maven
  • Gulp
  • Grunt
  • NodeJS
  • Gradle
Among the ones listed above, Maven has been around for long and is quite close to Ant and has similar limitations. Among others, I found Gradle to be the most impressive. The best part about Gradle is that it doesn't reinvent the wheel, it makes use of the existing systems and provides a simple language, similar to Groovy to write your build scripts. Gradle readily adds support for or has plugins available for the following:
  • Ant Tasks
  • RequireJS
  • Google Closure Compiler
  • GZipJs
  • ...and many more

The biggest endorsement for Gradle comes from Google itself who've based their new Android IDE, Android studio completely on Gradle, need I say more? The flexibility that Gradle provides is amazing, you can use Gradle to:
  • Build JS Projects
  • Build Android projects
  • .. And also build XCode Projects
This is amazing as I can rely on one tool for all my build management. In this post I will share the approach to automate the builds of a JS project and share some basic info about Android Studio and XCode support.

To understand the examples better, you can read basics about gradle here:
Gradle Homepage
Gradle JS Plugin

Download Gradle at: Gradle 2.1

Let's get started! First create an empty text file and name it "build.gradle". Second thing we will do is define the plugins we are going to use for this
buildscript {
  repositories {
      jcenter()
  }
  dependencies {
      classpath "com.eriwen:gradle-js-plugin:1.12.1"
  }
}
That's all we need, Gradle will itself download and install all plugins defined when build script is run. As we move ahead, some of the things will seem strikingly similar to Ant. Gradle leverages on ANT and the terminology it uses is also quite close. The methods are called tasks and variables are accessed using $(var). Just like Ant, Gradle allows defining configurations in a properties file. The name of the default properties file is gradle.properties. Any property defined in the properties file can be used using the varName syntax. Let's see an example:
Create a new text file "gradle.properties" and add a variable as follows:
message=Hello World

And this is how we use it in the script:
println "Just want to say ${message}";

Simple, ain't it! That's not all. Gradle also provides a powerful mechanism of creating custom properties files and using both the default one and the custom one. This is particularly useful when you want to customize build according to themes but don't want to specify the common properties repeatedly. We will see it in action in the tutorial that follows:

In this tutorial, I will create a Task, which will first combine a list of JS files, combine them and then Obfuscate the same using Google Closure compiler.

First thing we will do is load a properties file
Properties props = new Properties() props.load(new FileInputStream("${theme}.properties"));

Here, we loaded a custom properties file based on the theme parameter. Now, I will create the tasks to Combine the JS files. We will create 2 tasks, just to demonstrate use of custom properties.

task combineCommonJS(type: com.eriwen.gradle.js.tasks.CombineJsTask)  {
    List list = new ArrayList();
    if(flavor == "mobile")
    {
        mobile_common_scripts.split(",").each{String f -> list.add(f)}
    }
    else
    {
        common_scripts.split(",").each{String f -> list.add(f)}
    }
    source = list;
    dest = file("${buildDir}/common.js")
}

task combineCustomJS(type: com.eriwen.gradle.js.tasks.CombineJsTask) {
    List list = new ArrayList();
    if(flavor == "mobile")
    {
        props.mobile_custom_scripts.split(",").each{String f -> list.add(f)}
    }
    else
    {
         props.custom_scripts.split(",").each{String f -> list.add(f)}
    }
    source = list;
    dest = file("${buildDir}/custom.js")
}

So, here, we created 2 tasks, both of type CombineJsTask. The idea here is to fetch a list of JS files from properties and combine them. In the first task, the list of JS files was picked from the default properties and in the second one, it was picked from the custom properties. In the second one, we also used a condition to check whether we are making a build for mobile flavor or not. Here flavor is just another variable which can be anything but when working with Android studio, flavor is special but will talk about that later. Variables can also be provided as input parameters, to a gradle script, but that for later.

By now, we have written code to combine all JS files into 2 files common.js and custom.js. Now, we'd create tasks to minify these using Google Closure Compiler.
task minifyCommonJS(type: com.eriwen.gradle.js.tasks.MinifyJsTask) {
    source = file("${buildDir}/common.js")
    dest = file("${buildDir}/common.min.js")
    closure {
        warningLevel = 'QUIET'
    }
}

task minifyCustomJS(type: com.eriwen.gradle.js.tasks.MinifyJsTask) {
    source = file("${buildDir}/custom.js")
    dest = file("${buildDir}/custom.min.js")
    closure {
        warningLevel = 'QUIET'
    }
}

So, now we have all the tasks ready but we need to define the dependencies so that they execute in the correct order. For that we'd add a few more lines to the gradle script:

minifyCommonJS.dependsOn combineCommonJS minifyCustomJS.dependsOn combineCustomJS combineCommonJS.dependsOn minifyReaderJS

We have defined dependencies in a way that when we call minifyCommonJS task, it will execute all other tasks due to the defined dependencies. That's it, we have the script ready. Before we execute, let us see how to define JS files in the properties file.

gradle.properties:
common_scripts=lib/a.js,lib/b.js,lib/c.js
mobile_common_scripts=lib/a.js,lib/b.js,lib/mobile.js

defaulttheme.properties:
custom_scripts=defaulttheme/1.js,defaulttheme/2.js

customtheme.properties:
custom_scripts=customtheme/1.js,custom=theme/2.js

Here, we have created 3 properties files, one default one and 2 for the themes. Take special care that there is no space between the file names and no trailing spaces. Now let us see how to execute this gradle script.

Assuming you've added gradle to the Path. Open command line, locate to the folder containing the build.gradle file and type:
gradle minifyCommonJS -Ptheme=defaulttheme -Pflavor=mobile

Here we have instructed gradle to run the task minifyCommonJS and provide 2 properties as input, theme and flavor. The properties need to be defined using the -P syntax. Note there is no space after -P.

When this task runs, the defaulttheme.properties file will be loaded and from the scripts, mobile_common_scripts will be compiled in the combineCommonJS task. We can change the input properties to change the behavior as follows:
gradle minifyCommonJS -Ptheme=customtheme -Pflavor=web

Once the task executes, we will have the minimized and obfuscated output for the JS files. That's it! We have successfully created and executed a gradle build.

This concludes the tutorial but we have just about touched Gradle. The possibilities are endless. Gradle for Android brings an interesting concept of flavor. With the help of flavor, you can maintain different assets per flavor and at the time of compilation, it will only copy the chosen flavor. Makes life easier. To explore the full powers of Gradle on Android, use Android Studio.

Gradle for XCODE is something I am yet to touch but you can read about it here: Gradle XCode Plugin

I hope this post helps you in getting familiar with Gradle. For any queries, feel free to email me

No comments:

Post a Comment