Wednesday, July 20, 2011

Book Review: Building and Testing with Gradle

Overview

Gradle is one of the new "cool kids on the block".  It  is quickly gaining momentum as projects like Hibernate and Spring migrate their build processes over to Gradle.   Gradle can be considered the 3rd generation of Java build tools, with Ant and Maven representing the earlier generations.

Gradle is built on top of Groovy, which provides the ability to define a Domain Specific Language (DSL).  In this case, the Gradle DSL provides a language tailored to the task of building code.  If the DSL does not provide exactly what you need, then you can extend the DSL using plug-ins.  It is possible to learn Gradle without knowing Groovy, but some initial knowledge of Groovy is beneficial.

This book is short (89 pages) .  It brings back memories of the O'Reilly "A Developer's Notebook" series.

Contents

Chapter 1, Hello Gradle  This chapter leads the reader thru the installation and configuration of Gradle and demonstrates the ubiquitous Hello World (build file) example.

Chapter 2, Gradle Tasks  This is the  best chapter in the book.  It drives home the concepts associated with a 'task' and how the tasks can be configured and customized.    The most helpful  suggestion in the book is found in this chapter.  The  recommendation is that you read the Gradle DSL documentation.  I had previously read the Gradle Users Guide but was still a bit fuzzy on what  the properties and methods were and how they were used. .   Reading the DSL documentation provides the full picture.  It also  helps drive home  that you are working with a programming language/DSL and not a set of XML tags like Ant and Maven.

Chapter 3, Ant and Gradle  This chapter compares and contrasts Ant and Gradle.  It also demonstrates that Gradle can run Ant tasks by importing an Ant build.xml or by using the Ant Builder that is part of the Groovy distribution.

Chapter 4, Maven and Gradle  This  is the longest chapter in the book comprising  about 25% of the book.  Like all of the Gradle documentation, this book re-assures the reader that any previous investment in Maven or Ivy will not be lost in a move to Gradle.   Gradle provides support for Ivy, Maven's Central repository and a local repository.

Chapter 5, Testing with Gradle  This chapter is a  good overview of using  JUnit , TestNG, Spock, Geb and EasyB.  Coverage of testing is a bit light given the title of this book. 

Chapter 6,  Multiproject Builds  This chapter  does a nice job of taking a sample project and showing three different approaches to creating the project build structure.  The approaches shown are one master build file, project-specific build files and a hybrid approach.  While the sample projects are very small, they provide the reader a starting point to converting their own multiproject builds.

Summary
This is an excellent book for getting started with Gradle.   There are plenty of working examples provided in the source code download package.   The authors do a very good job of explaining the concepts and presenting alternative ways of expressing the same options within the DSL.

One minor issue is that matching up the downloaded source examples to the examples in the printed chapters is a bit of an exercise because they are not organized in a folder structure by chapter like many other books.
  
Initially the slim size of the book worried me as to it's ability to provide a depth of coverage of Gradle, but the authors stated in the Preface that "Future volumes will cover the Gradle plug-in ecosystem, how to extend Gradle with your own business logic and even more advanced topics."    Sounds good to me, bring it on Tim and Matthew!


Wednesday, July 13, 2011

Gradle dependencies with jars that have no version number

I have seen presentations and read blogs and articles on Gradle. It sounds pretty good.  Gradle is gathering momentum as some of the major players in the open source community (Hibernate and Spring Security to name a few) have switched their builds over to using Gradle.  I figured it is time to give it a try.

The project I work on is fairly old, pre-Maven I suspect.  The project is a multi-layer, multi-component Java project built using Ant.  The project does not use Maven or Ivy.  The project includes a folder that contains all of our dependent jars. These jars are committed to SVN and included as part of the project when it is checked out. This way we have all the dependencies we need to build the project.  We have quite a few dependent jars that do not have version numbers in the jar name.  Examples include a copy of junit.jar and log4j.jar.  This all existed before I started working on the project, so I have no idea how it got that way - it just is.  And since it's not really broken, we haven't attempted to fix it!

Not having version numbers in the jars is a minor inconvienence.  If you read the Gradle User Guide chapter on Dependency Management, they seems to push dependency management for jars with version numbers.  Most of the examples shown in the documentation and elsewhere declare the dependencies as seen in Example 34.1 in Dependency Management chapter.  Great, but what do I do if I don't have version numbered jars in my dependencies?  I did not believe the Gradle guys would leave me hanging...  And they didn't!

I really wanted to figure out how to setup my classpath to include these jars without version numbers.  I figured out how to do this and started writing this blog.   Then as I re-read the Dependency Management chapter, I saw a clue that I must have blown right by the first time I read it.  The clue is in Section 34.3.5 File dependencies.  You can use the FileTree  to help define the classpath to include these non-versioned jars.

I created a small project called GradleTest that did not use the standard Java plugin conventions for the source and resource files and has dependencies in the "libs" folder, none of which have version numbers in the jar names.



I created two different options, which both seem to work.  The first is shown below.  This option handles the dependencies more specifically, assigning just the jars that are needed for either compile or runtime.

apply plugin: 'java'

sourceCompatibility = 1.6
version = '1.0'

sourceSets {
    main {
        java {
            srcDir 'src'
        }
        resources {
            srcDir 'resources'
        }
        compileClasspath += fileTree(dir: './libs', includes: ['**/log4j.jar'])
        runtimeClasspath = classes + sourceSets.main.classes + fileTree(dir: './libs', includes: ['**/log4j.jar'])
        
    }
    test {
        java {
            srcDir 'test'
        }
        resources {
            srcDir 'testresources'
        }
        compileClasspath += fileTree(dir: './libs', includes: ['**/junit.jar'])
        runtimeClasspath += classes + fileTree(dir: './libs', includes: ['**/junit.jar', '**/log4j.jar']) 
        
    }
}

sourceSets.main.classesDir = new File("./build/classes")
sourceSets.test.classesDir = new File("./build/classes")

The second option looks very similiar to the first option,  but uses the dependencies definition and it cheats by assigning all the jars in the "libs" folder to both the compile and runtime classpath, rather than just exactly what is needed.


apply plugin: 'java'

sourceCompatibility = 1.6
version = '1.0'

sourceSets {
    main {
        java {
            srcDir 'src'
        }
        resources {
            srcDir 'resources'
        }
        
    }
    test {
        java {
            srcDir 'test'
        }
        resources {
            srcDir 'testresources'
        }
        
    }
}

sourceSets.main.classesDir = new File("./build/classes")
sourceSets.test.classesDir = new File("./build/classes")

configurations {
    compile {
        description = 'compile classpath'
        transitive = true
    }
    runtime {
        extendsFrom compile
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
    runtime fileTree(dir: 'libs', include: '*.jar')

}

I am sure there are other,  possibly better or cleaner alternatives.  Please feel free to share improvements or other alternatives.   Hope this helps....