Primarily having used Ant, Gant and Maven before, its really good to know of a tool that uses the best of all that is available and on my current project, I used Gradle. Ant, while good at allowing target definitions in the declarative style of programming, makes use of XML as a implementation vehicle. Gant improves upon this by using Groovy as a implementation vehicle instead of XML, while still using ant underneath. You can read further on streamlining builds with Gant

On the other hand, the dependency management of Maven is really good. I don’t have to “carry” my project dependencies along with the project physically as a checked-in folder or a separate sub-project or in whatever form it may be (read heavy), instead you hold dependency references (read lite). With Maven, you specify the dependencies (Dependency references) in the Project Object Model (or POM) and it then downloads them for you from the defined Maven Repositories. So, when you change the dependency or also the version of your dependency, it will make sure it has it for you prior to compilation.

But, when it comes to customizing either Ant or Maven to do your stuff, you may end up writing either an Ant Task or a custom Maven plugin. With Gant, customizing now becomes easy as you end-up writing that as a method in your script and not as a Ant Task.

Now here is where you would like to use Gradle, employing best of Maven, that is, Dependency Management and the Imperative Programming paradigm by using Groovy. Below is an example build.gradle (Gradle’s equivalent to Maven’s pom.xml) to show –

  • how one can have dependencies specified in a Gradle script and have them downloaded based by the repositories section (where one can specify the various repo urls).
  • how the one can avoid writing custom ant tasks.
    1. The testClient task below uses the startClientTestServer and runClientTests methods to run the start JsTestDriver server and run the JavaScript tests on separate Threads.
    2. Also, the startMongo and the stopMongo tasks use the startMongoDB() and stopMongoDB() methods to start and stop the Mongo DB Server. Further, these calls can be made from Jetty tasks to auto-start the MongoDB to run/test the app.

Gradle build file uses its own DSL called the Gradle Build Language.

apply plugin: 'groovy'
apply plugin: 'war'
apply plugin: 'eclipse'
apply plugin: 'idea'

defaultTasks 'clean', 'distribute'

serverDir = 'server'
serverLibDir = "${serverDir}/lib"
serverSrcDir = "${serverDir}/src"
serverTestDir = "${serverDir}/test" 

clientDir = 'client'
clientSrcDir = "${clientDir}/src"
clientLibDir = "${clientDir}/lib"
clientJavaScriptDir = "${clientDir}/js"
clientTestLibDir = "${clientDir}/test-lib"
clientTestDir = "${clientDir}/test"

webInfDir = 'WEB-INF'
webInfLibDir = new File("${webInfDir}/lib")
webInfClassesDir = new File("${webInfDir}/classes")

firefox = 'C:/Program Files/Mozilla/Firefox/firefox.exe'
ie = 'C:/Program Files/Internet Explorer/IEXPLORE.exe'
browsers = [firefox, ie]
jsTestDriverHome = "$projectDir/$clientTestLibDir/js-test-driver"

//libs
gmongo = [group: 'com.gmongo', name:'gmongo', version:'0.9.3']
groovy = 'org.codehaus.groovy:groovy-all:1.8.3'
j2eeServlet = [group: 'javax.servlet', name: 'servlet-api', version: '2.5']
mongoJavaDriver = 'org.mongodb:mongo-java-driver:2.6.5'

//test libs
junit = 'junit:junit:4.8.1'
spock = 'org.spockframework:spock-core:0.6-groovy-1.8'
cglib = 'cglib:cglib-nodep:2.2.2'
objenesis = 'org.objenesis:objenesis:1.2'

repositories {
  mavenCentral()
  mavenRepo urls: 'http://m2repo.spockframework.org/snapshots'
  mavenRepo urls: 'http://jstd-maven-plugin.googlecode.com/svn/maven2'
}

sourceSets {
  main {
    groovy {
      srcDir serverSrcDir
    }
  }
  test {
    groovy {
      srcDir serverTestDir
    }
  }
}

dependencies {
  compile (j2eeServlet, mongoJavaDriver, gmongo)
  groovy (groovy)
  providedCompile (fileTree(serverLibDir), fileTree(dir:clientDir, include:'**/*.jar', exclude:'**/*/coverage*.jar'))
  testCompile (objenesis, cglib, spock, junit)
}

clean {
  doLast {
    ant.delete(dir:webInfClassesDir, failonerror:false)
    ant.delete(dir:webInfLibDir, failonerror:false)
  }
}

task testClient (description:'Executes Client-Side JavaScript Tests') << {
  def jsTestDriverJar = "$jsTestDriverHome/JsTestDriver-1.3.4.b.jar"
  def jsTestDriverConf = "${projectDir}/jsTestDriver.conf"
  def runtimeClasspath = sourceSets.main.runtimeClasspath
  def slaveServerThread = startClientTestServer(jsTestDriverJar, jsTestDriverConf, runtimeClasspath)
  Thread.sleep(5000) //Allow the slave server to start & run.
  if(!testResultsDir.exists()) {
    testResultsDir.mkdirs()
  }
  def testRunnerThread = runClientTests(jsTestDriverJar, testResultsDir, runtimeClasspath)
  Thread.sleep(1000)
  while(testRunnerThread.isAlive())
}

def runClientTests(String jsTestDriverJar, testResultsDir, runtimeClasspath) {
  def worker = new Thread().start {
    javaexec {
      main = 'com.google.jstestdriver.Main'
      classpath = runtimeClasspath
      args ('-jar', jsTestDriverJar, '--tests', 'all', '--testOutput', testResultsDir, '--runnerMode', 'DEBUG')
    }
  }
}

def startClientTestServer(String jsTestDriverJar, String jsTestDriverConf, runtimeClasspath) {
  def server = new Thread().start {
    def port = 9876
    def allBrowsers = browsers.join(', ')
    javaexec {
      main = 'com.google.jstestdriver.Main'
      classpath = runtimeClasspath
      args ('-jar', jsTestDriverJar, '--config', jsTestDriverConf, '--port', port, '--runnerMode', 'DEBUG', '--browser', allBrowsers)
    }
  }
}

task startMongo(description:'Starts the Mongo DB Server') << {
  startMongoDB()
}

task stopMongo(description:'Stops the Mongo DB Server') << {
  stopMongoDB()
}

def startMongoDB() {
  def cmdString = ['mongod', '--port', dbPort, '--dbpath', dbDir, '--auth'].join(' ')
  println "Starting Mongo Database...$cmdString"
  new Thread().start {
    "$cmdString".execute()
  }
}

def stopMongoDB() {
  Process process = 'mongo admin'.execute()
  def out = process.outputStream
  out.withWriter { writer ->
    writer.write('db.shutdownServer()')
  }
}

war {
  archiveName = distributionFilename + '.war'
  from("$project.buildDir") { // adds a file-set to the root of the archive
    include 'index.html'
    include "${clientDir}/**"
  }

  //WEB-INF/classes is auto-populated if compileGroovy is run before war.
  classpath fileTree(serverLibDir) // adds a file-set to the WEB-INF/lib dir.
  webXml = file("${webInfDir}/web.xml") // copies a file to WEB-INF/web.xml
}

//We want to club the test-reports for client-side and server-side tests
test.dependsOn = ['testClient', 'testClasses']
war.dependsOn = ['test']

You can additionally use various plugins for IDEs like Intellij Idea and Eclipse to generate project specific files and can stay up-to-date with dependencies referenced from project classpath from within IDEs. Also, you can use the Cobertura Coverage plugin and the CodeNarc plugin for code-quality, all out-of-box.