Gradle Fundamentals


Hi, I am Malathi Boggavarapu working at Volvo Group and i live in Gothenburg, Sweden. I have been working on Java since several years and had vast experience and knowledge across various technologies.


This post teach you the fundamentals of Gradle. So let's get started to know it's definition first and go through different concepts of Gradle.


What is Gradle?



 Build by convention

Gradle is often called build by convention. Like Maven, gradle has a bunch of building conventions that we can follow when we do the build. However these conventions are easily overridable. We will talk more about it later.

Groovy DSL

Gradle is written in java but the build language DSL (domain specific language) is written in Groovy. This language makes it very easy to configure our builds. So we are not writing an XML, we write in a language specific to our build.

Supports dependencies

Just like Maven support dependencies, Gradle also supports dependencies.

Supports multi-project builds

We also have out-of-box support for multi project builds. So if we have a program where we need many different projects to build, it gradually supports it very easily.

Gradle also allows us to build other than java projects. So if we want to manage javascript or C ++ projects, we will be happy to do so.

Highly customizable.

Declarative Build language

It expresses the intent of the build. So we tell Gradle what we would like to happen NOT how we would like it to happen. This makes build scripts easy to read. So in Ant and Maven build scripts, we should write an xml where we tell Ant and Maven what to do. Gradle is so easier to understand either of those build tools. This also makes highly maintainable. Gradle build scripts are readable, understandable and they were very maintainable.

So let's go through the basics of Ant and Maven to know the real usage of Gradle.

Ant

Another neat tool. Probably the first java build tool written when the people are very frustrated to use Make. Ant provides us with an xml build scripts that are hard to read and difficult to maintain.

One of the major problems with Ant is the lack of conventions. So we must specify everything where our source directory lives, where classes will be built, where we will get libraries from, to where we should do everything we create here. Example we have goals that prepare - to prepare those directories, build - to build classes. There are no conventions in Ant. For all the goals, we definitely define everything that we need. Files get very large very quickly and very un-maintainable and very unreadable

Maven

Several advantages over Ant. It have many many conventions in Maven. we don't have to specify where we need to get source files and where to build the output to. Maven already understands that. Maven  also supports dependency management. Maven pom allows to specify the dependencies and resolve the dependencies when we need them.

Highly extensible. configure with plugins where as Ant we have to define everything by us.

Like Ant, Maven is written in xml. Like Ant its hard to read and can be unmaintainable.


So what about Gradle.?



Ex: build.gradle. We write build file in a language specific to this domain, that is build domain.

repositories{
   mavenCentral()
}

apply plugin: "java"
version = 0.1
dependencies{
   testCompile group: 'org.harmcrest', name: 'hamcrest-core', version: '1.3'
   testCompile 'junit:junit:4.12'
   compile 'javax.inject : javax.inject : 1'
}

apply plugin: 'org.flywaydb.flyway'
testSets{
     integrationTest {dirName = 'integration-test'}
}

It is certainly not an xml. Build files because of DSL become largely self-explanatory and relatively easy to manage and maintain and DSL is one of gradle strengths.


Installing gradle



- From the website
     http://gradle.org
- Using gvm
     Groovy environment manager
     http://gvmtool.net

gvm allows us to install many groovy based runtimes. We can install gradle and other groovy frameworks using gvm. The downside is difficult if not impossible to get this working on windows. Using gvm is great on  Linux and Mac but less on Windows.

So to install gradle on windows, Go to gradle.org website and download it.
To use gradle, we have to add Ex: gradle[version]\bin to the path

We can create a link in unix terms between a gradle folder and current release folder. Microsoft have a tool called junction. Use junction to create junction points or repass points to create link from virtual folder to physical folder.

Open command prompt
junction gradle gradle 2.6\ 

This creates a virtual folder called gradle which connects to physical folder gradle 2.6 (where gradle is actually installed). So we can add to the path such as

PATH = ..gradle\bin


Gradle Builds



Gradle has a build file gradle.build. This contain plugins, dependencies and mostly tasks
Now we create a simple gradle build file with a task

task Hello{
doLast{
  println "Hello, Gradle"
}
}

Gradle tasks have a lifecycle such as configuration, initialization phases and have methods called doFirst and doLast. It simple prints out Hello, Gradle. Remember this is a groovy DSL.

Now go to the command prompt and go to the folder where gradle build file is and execute the build file.

> gradle Hello
















If we notice the above example, there is time difference between the both and it is good.

Example 2:

apply plugin: 'java'

Java application follows convention as shown below. So if we execute the gradle build file, it compiles all the java classes within the java folder and put them into the build folder of the project.

Example

JavaProject
->src
     -> main
          -> java
          -> resources

So at the command prompt where the build file exists, if we type
gradle build

The output is as follows. After the build, it creates .gradle folder.





















Now it compiles all the java files, create jar, compile any test cases and so on

The third way to install gradle. Once you have a gradle project, this is the recommended way
Just add the following task to the build file

task wrapper(type: Wrapper){
  gradleVersion = '2.6'
}

We set gradle version 2.6. Go to command prompt and run gradle wrapper, it executes task wrapper.
And the output looks like below



If we notice windows explorer, we see new folders created .gradle, gradlew (shell script used in windows) and gradlew.bat

So if we want to build the existing java application, we run

gradlew build

It actually downloads gradle version specified in the build file. The advantage if this feature is, we can carry gradle version in the project itself and all the users who are running the application use the exact version of gradle.


Basic gradle tasks



What is a Task ?


- Basically Task is a code gradle executes for us.
- It has a lifecycle. Initialization, configuration and execution phases
- It has properties. Task may have description or group that belong to are common properties. we can     also define our own properties such as directory to copy files to or directory to copy files from.
- It has actions - This is going to be the code to execute. Task action are broken to two distinct        paths.    first action and last action. If you need code which has to be executed before other code in the task,    we use first action and code that generally executes as part of task, it can be put in last action.
- It has dependencies - One task may be dependant on other task. Gradle work on dependency order        and execute tasks in right order.

Gradle is in groovy and  groovy is a JVM language and groovy like other JVM languages is object oriented language. The top level object of the build file is project. We use project object to define everything within the build script. so we create a task like

project.task("Task1");
task("Task2");
task "Task3"
task Task4
task4.description = "Task 4 description"

Gradle knows project as top level object so we no need to use project object. It is optional.

> gradle tasks

It list all the tasks exists in the build file. All the tasks defined in the build file will be listed under the group 'Other tasks '. See below.





Executing the task



All tasks have a method called doLast. 


project.task("Task1");

task("Task2");
task "Task3"
task Task4
task4.description = "Task 4 description"

Task4.doLast{ // this is the task action

  println "This is Task 4"
}

Task3 << {println "This is Task 3"}

task Task5 << {println "This is Task 5"}
Task5 << {println "Another closure"}
task Task6 {
 description "This is task6"
 doLast{
   println "This is task 6"
  }
}

If we see Task5 and Task6 we see some important things to highlight. If we run Task5 using the command gradle Task5, though we defined two tasks with same name Task5 in the build file, it just executes both the tasks without any exception. In our example, it prints both the text in the command prompt.

If we notice Task6, we added both the description and doLast to the task. If we run command "gradle tasks" in the command prompt, it prints "Task6 - This is task6" and if we run command "gradle Task6", it executes the action and prints "this is task6". Here there are two lifecycle phases of a task. One is configuration phase and another one is an execution phase. 

"description" property for Task6 is executed when we run "gradle tasks". This is the configuration phase of a task.

doLast action is executed when we run "gradle Task6". This is the execution phase of the task.


Build phases



Initialization phase - Typically used to build multi projects. We will see that more later
Configuration phase - Execute code in the task that is not the action

Task myTask{
  description "myTask configuration"
}

Execution phase - Execute the task actions. 
Task myTask{
  doFirst
  doLast
}

Example

task Task6 {
 description "This is task6"
doFirst{
   println "This is task6 - doFirst"
}
 doLast{
   println "This is task 6 - doLast"
  }
}

´doFirst is executed before doLast. And we can also add multiple doFirst and doLast actions. whenever we append actions using left shift operator to the task, they were added as doLast actions to the task. So we can add more doFirst actions to the task we can simply do as shown below.

Task6.doFirst{
   println "Another first"
}


Task dependencies



Task6.dependsOn Task5
Task6 dependsOn Task3
Task5 dependsOn Task4

We use "dependsOn" to create dependencies between tasks. In above example, Task6 depends on Task5 and Task3 and Task5 depends on Task4.

So here, Task4 should be executed before Task5 is executed because Task5 is dependant on Task4. Task5 and Task3 should be executed before Task6 is executed because Task6 is dependant on Task5 and Task3.

We can also add dependsOn property to task directly. See example below.

task Task6 {
description "This is task6"
dependsOn Task5
doFirst{
   println "This is task6 - doFirst"
}
 doLast{
   println "This is task 6 - doLast"
  }
}

So here if we switch back to the phases, dependsOn is executed at Configuration phase similar to the description. When gradle build is run, all hierarchy of dependencies of a task are configured at configuration phase before the actions such as doFirst and doLast are executed for that task.
In the above example, Task5 is executed before Task6 actions are executed.

Setting properties on Tasks


def projectVersion = "2.0"

We can use this projectVersion inside my gradle code. This is a local variable and its scope is limited to the build file. 

doFirst{
   println "This is task6 - doFirst - version $projectVersion"
}

We can also define properties in different scope. That is, we can also define at task level too and they would be very useful.

If we want to define a property to be used across the project, we can do it as said below

project.ext.projectVersion = "3.0"
doFirst{
   println "This is task6 - doFirst - version $project.ext.projectVersion"
}

We can also write the above code as follows. Actually there is no need to use project object because project is the default top level object available in the build file. While we use it in the task, we can use the property directly without using project.ext.

ext.projectVersion = "3.0"
doFirst{
   println "This is task6 - doFirst - version projectVersion"
}


Task Ordering



Accoridng to the documentation of gradle, it was said as below.

In some cases it is useful to control the order in which 2 tasks will execute, without introducing an explicit dependency between those tasks. The primary difference between a task ordering and a task dependency is that an ordering rule does not influence which tasks will be executed, only the order in which they will be executed.
Task ordering can be useful in a number of scenarios:
1) Enforce sequential ordering of tasks: e.g. 'build' never runs before 'clean'.
2) Run build validations early in the build: e.g. validate if i have the correct credentials before starting  the work for a release build.
mustRunAfter
   - If two tasks execute, One task must run after the other.
     
shouldRunAfter
   - If two tasks execute one should run after the other
   - This ignores circular dependencies. If one two tasks have circular dependencies, then this will              ignore it and work at its best. But gradle build fails when we use mustRunAfter if two tasks have        circular dependencies
finalizedBy (incubating in 2.6. Means it may change in further release or can be removed even)
   - supports inverted dependency. 

mustRunAfter and shouldRunAfter strictly enforce the exact execution order of dependencies. And we see the real use of mustRunAfter and shouldRunAfter when we run two or more tasks at the same time.

finalizedBy is used for inverted dependencies. If we say TaskA.finalizedBy TaskB, after executing TaskA, TaskB will be executed. we can simply say gradle -i TaskA - It executes TaskA and finally TaskB. 

Finalizer tasks are useful in situations where the build creates a resource that has to be cleaned up regardless of the build failing or succeeding. An example of such a resource is a web container that is started before an integration test task and which should be always shut down, even if some of the tests fail.


There is difference between usages of dependsOn and finalizedBy.



For example, if we consider tasks C and D, if i want C to depend on D if there was a consumption relation between the two tasks (ie C consumes some output of D) or if I wanted to be able to run C independent of B (i.e I do “gradle C” and only the a task runs).

FinalizedBy is a little different in that it typically represents more of a helper relationship between the two. That is, if task C was finalized by task D, D might not necessarily consume something produced by C, but it might do something on behalf of C. In this case, I could not run C independently of D (i.e. I do a “gradle C” and both tasks C and D always run, even if task C fails). This is typically used for cleanup - for instance, stopping a test web server after running an integration test task (even if the integration test task fails).



Some useful tips



flyway - is the plugin available in gradle. It is used to create a database and migrate the data  to the database.
gradle -i (executes the task in info  mode.)
gradle -q (executes the task in quite mode)
gradle --help (USe this command to see all the options available in gradle)


Typed Tasks



Something we want to do something more complex.
Copying files?
  - Open, read, write file
Zipping files?
Would like this to be reusable


What build-in task types are there



https://docs.gradle.org/current/dsl/
Go to the above link, you will find list of task types and examples of their use

Copy - Type tasks

task copyImages(type: Copy){
    from 'src'
    into 'dest'
}

gradle copyImages

task copyImages(type: Copy){
exclude 'IMG_0052.jpg', 'IMG_0054.jpg'
    from 'src'
    into 'dest'
}

Exclude files using exclude.

def contentSpec = copySpec {
    from 'src'
   exclude 'IMG_0052.jpg', 'IMG_0054.jpg'   
   exclude {it.file.name.startsWith('IMG')}
}
task copyImages2(type:Copy){
    with contentSpec
    into 'dest'
}

Can also do renaming files, restructuring directories and expand file (replace text in files)

task copyConfig(type:Copy){
   include 'web.xml'
   from 'src'
   into 'dest'
   expand{
      [resourceRefName: 'jdbc/JacketDB']  // resourceRefName is the placeholder                              ($resourceRefName) in web.xml file
  }  
}


Building Java project



apply plugin 'java'
version ='1.0.SNAPSHOT' // creates jar file of the project with the specified version

If we do the following in the command prompt. it cleans the build and re-compile all the classes and created a jar with the version specified.

gradle clean
gradle build

Details of Java plugin















It shows all the tasks that will be executed in sequence. build is dependant on compileJava, processResources, classes....etc. jar task is dependant on classes and processResources and compileJava.


Gradle daemon



How much time gradle takes to build? Each time when we run commands such as gradle clean or gradle build, gradle re-launches JVM and executes them. So it will be good if we can re-use the existing JVM without re-launching it always

We can do it using daemon. From the command line run the command
gradle --daemon clean

Now it make JVM as daemon and if we try to run gradle --daemon build now, it takes very less time to execute the task when compared to not using daemon

There are two ways to set daemon. One way is to add as an environment variable and another way is to create gradle.properties and add to home directory. The laster case is the best way to follow

gradle.properties

org.gradle.daemon=true

when we run gradle, it should pick up the daemon
Ex gradle clean build // It takes less time to build the project with daemon
gradle --no-daemon clean build // to make daemon OFF

Writing a multi-project build


Here we learn about how to build simple multi-project java application. The java application contains two projects 'Repository' and  'JacketService'.

First we should do two things to build the application. We should add "settings.gradle" and "build.gradle" file to the root of the main project (folder which contains two projects). 

List all the projects that are part of multi-project build in settings.gradle.
 
In build.gradle file we need to configure all the projects of this multi-project build. In this file we can add information common to all projects and we can add dependencies between these projects as well.

settings.gradle

include 'Repository', 'JacketService' // These are the projects of multi-project application

build.gradle

allprojects {
 apply plugin: 'java'
}
project ( ': Repository') {} //relative path
project ( ': Jacket Service') { //relative path 
        dependencies{
        compile project ('Repository')
    }
}

We are applying java plugin to all the projects (Repository and JacketService) in the application using allprojects. As JacketService depends on the Repository, we add Repository as a dependency.

Building an existing Java project



Here we work with an existing project which is old. This project follows a non-gradle project structure.As we all know Maven and gradle follows a standard project structure where our java classes stay inside src/main/java and test classes will stay inside src/main/resources.

build.gradle file

apply plugin: 'java'
apply plugin: 'application' // ability to run applications
sourceSets {
 main{
    java{
      srcDir 'src'
    }
 }
Test{
    srcDir 'test / src'
 }
}

dependencies{
   compile files ('lib/log4j.jar', 'lib/junit.jar') 
}

task wrapper (type: wrapper)


sourceSets are used to add the java source and test classes. Here our project is old and follows non-gradle structure. So in the build file, we need to use sourceSets to tell the build to fetch the java sources and test classes from specific directory. 

If we see the above build.gradle file, we add log4j.jar and junit.jar as dependencies of the project.
log4j.jar and junit.jar are the dependencies located in the local file system not in repository as this application follows the old project structure. We should also mention what kind of operation we want to do on the jar files; whether it is a compilation or execution of the jar files. Here we want to compile the files, so we use compile. And as we are getting the files from the file system, we use files and inside the braces we add all the jar files that need to be compiled.
If the files are to be compiled or executed from repository, we should do it in a different way. Lets see about it more later.

Dependencies


Project will have dependencies. Either the dependencies will be located internally in the project or they may be located externally in some repositories.

java provides following plugins to compile, run the dependencies.

compile
runtime
testCompile
testRuntime

We can list dependencies with in the application

gradle -q dependencies
list all the dependencies of the project

gradle -q dependencies -configuration compile

Using Repositories in gradle


In gradle we can use difft repos.

repositories{
     mavenCentral()
}

repositories{
    jcenter() //maven repository jcenter in this case, any action is done over https
}

If we want to use jcenter over http, then we can specify the url for the repo
repositories{
   url "http://jcenter.bintray.com/"
}

Maven local - we can specify local so it looks for local m2 repo.

repositories{
    mavenLocal()
}

We can also specify our company repos
repositories{
   maven{
       url "http://repo.mycompany.com/maven2"
   } 
}

Example of build.gradle file with configuration of repositories


refer to mvnrepository.com/artifact/junit/junit/4.12

apply plugin: 'java'
apply plugin: 'application' // ability to run applications
sourceSets {
 main{
    java{
      srcDir 'src'
    }
 }
Test{
    srcDir 'test / src'
 }
}

repositories{
    jcenter() //maven repo
}

dependencies{
   compile group: 'log4j', name:'log4j', version: '1.2.17'
   compile  'junit:junit:4.12'
}

task wrapper (type: wrapper)

> gradle clean build
It will download all the dependencies and caches this information in your local system.
If we again do "gradle clean build", project use the cached dependencies

Gradle cache


Dependencies are cached when it gets downloaded. Names of the files in the cache are sha1 based.
We can refresh dependencies using --refresh-dependencies flag

On windows, gradle cache is in Users - .gradle -caches - modules2-  .........

So we came to the end of the course of Gradle Fundamentals. Hope this course is helpful.
Please post your comments and make the session interactive.

Happy Learning!!


Comments

Popular posts from this blog

Bash - Execute Pl/Sql script from Shell script

Load Balancing using Spring Cloud Netflix Ribbon