Sunday, September 11, 2011

Introduction to Gradle Part 2

In part 1, we built project A, a simple Gradle build file that compiled Java class files and built a jar file.  Next, we will build a war project dependent on the jar file generated from project A.  This uses Maven dependency managment.  It is possible to setup your project completely using Maven or Ivy.  Gradle also has a framework to manage it's own dependencies and will be described shortly using subprojects. One interesting extension of the war plugin is the Jetty plugin.  Gradle provides a library that will wrap a war in a self-contained web container.  This is nice if you have a small web app and want to get it up and running quickly.

Build Project B (simple Java war build)


(Project B/build.gradle)


// We can add comments to the build script using typical Java/Groovy notation
// We need to specify the java plugin which by default looks for src/main/java
usePlugin 'java'
usePlugin 'war' // Use the war plugin to build a war file
usePlugin 'maven'

version = 1.0
artifactId='projectB'

repositories { // reference all of the required Maven repositories.
      mavenRepo(urls : "file://localhost/tmp/repos/")
      mavenCentral() // Setup the Maven repository

}


dependencies { // Add external dependencies here
      compile group: 'commons-cli', name: 'commons-cli', version: '1.0'
      compile group: 'projectA', name: 'projectA', version:'1.0'

 }

uploadArchives { // Again upload our final product into the local Maven repository
    repositories.mavenDeployer {
        repository(url: "file://localhost/tmp/repos/")
        pom.groupId = 'projectB'
        pom.version = '1.0'
        pom.artifactId = 'projectB'
    }
}

assemble.dependsOn ':uploadArchives'

sourceSets { // Defines which source files to include in the build
      main {
            java {
            }
            resources {
            }
      }
}


ProjectB/.classpath
ProjectB/.settings/org.eclipse.jdt.core.prefs
ProjectB/bin/com/pac/domain/Manager.class
ProjectB/.project
ProjectB/build.gradle
ProjectB/build/libs/ProjectB-3.1.war
ProjectB/build/classes/main/com/pac/domain/Manager.class
ProjectB/build/classes/main/Manager.class
ProjectB/build/ivy.xml
ProjectB/build/poms/pom-default.xml
ProjectB/src/main/java/com/pac/domain/Manager.java


Build Project C (Groovy Class build)

In project C, we will build a Groovy based project that uses Java libraries and is also dependent on our project A.  This Gradle maven project will again use the Maven repository to dowload dependencies and upload the final artifiact after the build.



(Project C/build.gradle)


// We can add comments to the build script using typical Java/Groovy notation
// We need to specify the java plugin which by default looks for src/main/java

usePlugin 'groovy'
usePlugin 'maven'
usePlugin 'java'

version = 1.0

repositories {
      mavenRepo(urls : "file://localhost/tmp/repos/")
      mavenCentral() // Setup the Maven repository
}


dependencies { // Add external dependencies here
      groovy module('org.codehaus.groovy:groovy-all:1.7.0') {
          dependency('commons-cli:commons-cli:1.0')
          dependency('projectA:projectA:1.0')
       }
 }


uploadArchives {
    repositories.mavenDeployer {
        repository(url: "file://localhost/tmp/repos/")
        pom.groupId = 'projectC'
        pom.version = '1.0'
        pom.artifactId = 'projectC'
    }
}

assemble.dependsOn ':uploadArchives'

sourceSets { // Defines which source files to include in the build
      main {
            groovy {
            }
            resources {
            }
      }
}

Files in project C...
ProjectC/.classpath
ProjectC/.settings/org.eclipse.jdt.core.prefs
ProjectC/bin/com/pac/domain/Company.class
ProjectC/.project
ProjectC/build.gradle
ProjectC/build/libs/ProjectC-1.0.jar
ProjectC/build/classes/main/com/pac/domain/Company.class
ProjectC/build/ivy.xml
ProjectC/build/poms/pom-default.xml
ProjectC/src/main/groovy/com/pac/domain/Company.groovy




Configuring Gradle Multi-Project Builds


Next, let's setup a Gradle multi-project build called UberProject which will contain projectA, project B, and project C.  This mult-project build will contain the following structure:

UberProject/

        settings.gradle
        /ProjectA
        /ProjectA/build.gradle
        /ProjectB
        /ProjectB/build.gradle
        /ProjectC
        /ProjectC/build.gradle

settings.gradle will contain the following line:
include 'ProjectA', 'ProjectB', 'ProjectC'

What is nice about the Gradle multi-project build is that the main settings.gradle and build.gradle (if existing) will be inherited and can be re-used in all of the subprojects.  This is similar in concept to the Maven pom, but much easier to read and appears on the surface more straight-forward.

Next, in each project we need to use the 'dependsOn' call to manage inter-project dependencies.  Remove any reference to projectA, projectB, or projectC in the dependencies section.  Leave all of the third party libraries, so that we can still download these dependencies as needed.

Add the following line to project C:
dependsOn(':ProjectA')

Add the following line to project B:
dependsOn(':ProjectA')

Run gradle assemble on project C.  You should see the following output.

:ProjectA:compileJava
:ProjectA:processResources
:ProjectA:classes
:ProjectA:jar
:ProjectA:assemble
:ProjectC:compileJava
:ProjectC:compileGroovy
:ProjectC:processResources
:ProjectC:classes
:ProjectC:jar
:ProjectC:assemble

BUILD SUCCESSFUL

Total time: 16.298 secs

Notice how building project C now, automatically builds the dependency of project A.  Nice right?

Now, let's have some fun with our projects and flex the depedency management features in Gradle by adding a circular dependency to project A.  One of the best ways to understand how Gradle works is to break the build.

Add the following line to project A:
dependsOn(':ProjectC')

You should see the following output:

FAILURE: Build failed with an exception.

* Where:
Build file '/home/robert/workspace/UberProject/ProjectC/build.gradle' line: 12

* What went wrong:
A problem occurred evaluating project ':ProjectC'.
Cause: Circular referencing during evaluation for project ':ProjectA'.

* Try:
Run with -s or -d option to get more details. Run with -S option to get the full (very verbose) stacktrace.

BUILD FAILED

Total time: 8.698 secs

By adding a reference to projectC to projectA, we have added a circular dependency.  Gradle finds circular dependencies in multi-project builds and does not allow a build to continue once the circular dependency is found.

No comments: