Gant's Build Script

As an example of a working Gant script this is the Gant script that is Gant's own Gant script as at 2008-05-20T08:15:00+00:00. This assumes Gant 1.2.0 or greater. If you find any errors in inefficiencies, do let me know, either on the Gant developer mailing list or directly russel.winder@concertant.com.

//  Gant -- A Groovy build framework based on scripting Ant tasks.
//
//  Copyright © 2006-8 Russel Winder
//
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software distributed under the License is
//  distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
//  implied. See the License for the specific language governing permissions and limitations under the
//  License.
//
//  Author : Russel Winder <russel.winder@concertant.com>

final zipExtension = '.zip'
final tarExtension = '.tar'
final tgzExtension = '.tgz'

ant.property ( file : 'local.build.properties' )
ant.property ( file : 'build.properties' )

gantVersion = ant.project.properties.gantVersion
groovyVersion = ant.project.properties.groovyVersion

try { usingCobertura }
catch ( MissingPropertyException mpe ) { usingCobertura = false }

try { ignoreTestFailures }
catch ( MissingPropertyException mpe ) { ignoreTestFailures = false }

groovyHome = System.properties.'groovy.home'
if ( ( groovyHome == null ) || ( groovyHome == '' ) ) {
  println ( 'Real problem: property groovy.home is not set and this is Gant executing.' )
  return
}

//  The Gant source is structured according to the standard Maven 2 structure.

final sourceDirectory = 'src/main/groovy'
final testsDirectory = 'src/test/groovy'
final jarfilesDirectory = 'jarfiles'
final scriptsDirectory = 'scripts'

final buildDirectory = 'target'
final buildClassesDirectory = buildDirectory + '/classes'
final buildTestClassesDirectory = buildDirectory + '/test-classes'
final buildTestReportsDirectory = buildDirectory + '/test-reports'
final buildCoberturaClassesDirectory = buildDirectory + '/cobertura-classes'
final buildCoberturaReportsDirectory = buildDirectory + '/cobertura-reports'

final buildFilteredScriptsDirectory = buildDirectory + '/filteredScripts'
final buildInstallDirectory = buildDirectory + '/install'

final documentationDirectory = buildDirectory + '/documentation'
final apiDocumentationDirectory = documentationDirectory + '/api'

final buildMetadataDirectory = buildClassesDirectory + '/META-INF'

//  Labels for supporting use of the Maven Ant task.  NB Installing Gant installs the Maven and Ivy Ant
//  tasks jars into the Groovy library.  Should the antlib: or urn: string be used here?

final antlibXMLns = 'antlib:org.apache.maven.artifact.ant'
final mavenPOMId = 'maven.pom'

//  Details of what is going to appear in the distribution.

final distributionTopLevelFiles = [
                             'build.gant' , 'buildMaven.gant' , 'build.properties' , 'build.xml' ,
                             '.classpath' , '.project' ,
                             'LICENCE.txt' ,
                             'README_Distribution.txt' , 'README_Install.txt' ,
                             'releaseNotes.txt'
                             ]
final distributionDirectories = [ 'documentation' , 'examples' , '.settings' , scriptsDirectory , jarfilesDirectory , sourceDirectory , testsDirectory ]

//  The versions of Gant in the Maven repository to build distribution version of Gant against.

//  Deb production assumes that the standalone build is the last one undertaken.  Pragmatically this is the
//  case since iterating over the items in the map happens in lexical order of the keys.  However, this is
//  an assumption, so this really needs changing.

final distributionVersions = [
                              //  Introducing the Gant Ant Task written in Java means we have to use the
                              //  joint compiler to compile Gant and this means we cannot build Gant against
                              //  Groovy 1.0 since the joint compiler only arrived with 1.5.0.  Opinion on
                              //  the user list indicated that it seemed reasonable to drop support for
                              //  Groovy 1.0.
                              //
                              //  The target distribution assumes that the groovyc fork mode works, which
                              //  means we cannot build against any version prior to 1.5.2.  Using a forked
                              //  Java task is not an option because there is no --classpath option in the
                              //  1.5.0 and 1.5.1 versions of Groovyc and this is essential to get things to
                              //  work correctly.
                              //
                              //  Gant can still be built and distributed against Groovy 1.5.0 and 1.5.1 but
                              //  it has to be done the hard (manual) way.
                              //
                              ( 'groovy-' + groovyVersion ) : [ groupId : 'org.codehaus.groovy' , artifactId : 'groovy' , version : groovyVersion ] ,
                              'groovy-1.6-beta-1-SNAPSHOT' : [ groupId : 'org.codehaus.groovy' , artifactId : 'groovy' , version : '1.6-beta-1-SNAPSHOT' ] ,
                              standalone : [ groupId : 'org.codehaus.groovy' , artifactId : 'groovy-all' , version : groovyVersion ]
                              ]

// As Jürgen Hermann pointed out the trailing / on the URL is very important.

final distributionURL = 'https://dav.codehaus.org/dist/gant/'

 //  The server hierarchy is fixed and known.

final serverDistributionsDirectory = 'distributions'
final serverJarsDirectory = 'jars'
final serverPomsDirectory = 'poms'

//  This is a map with key local source path and value server path.

final serverUploadProducts = [ : ]

//  The various compilation and test actions are parameterized so they can be used with different classpaths.

//  Currently these Closures do not have the GantMetaClass as their metaclass so there is no delegated
//  lookup on Ant so we have to use it explicitly.

final performCompile = { classpathRef , boolean fork ->
  ant.mkdir ( dir : buildClassesDirectory )
  ant.taskdef ( name : 'groovyc' , classname : 'org.codehaus.groovy.ant.Groovyc' , classpathref : classpathRef )
  ant.groovyc ( srcdir : sourceDirectory , destdir : buildClassesDirectory , fork : fork ? 'true' : 'false' , failonerror : 'true' ) {
    classpath {
      pathelement ( location : buildClassesDirectory )
      path ( refid : classpathRef )
    }
    javac ( source : '1.5' , target : '1.5' , debug : 'on' )
  }
}

final performCompileTests = { classpathRef , boolean fork ->
  ant.mkdir ( dir : buildTestClassesDirectory )
  ant.taskdef ( name : 'groovyc' , classname : 'org.codehaus.groovy.ant.Groovyc' , classpathref : classpathRef )
  ant.groovyc ( srcdir : testsDirectory , destdir : buildTestClassesDirectory , fork : fork ? 'true' : 'false' , failonerror : 'true' ) {
    classpath {
      pathelement ( location : buildClassesDirectory )
      path ( refid : classpathRef )
    }
    javac ( source : '1.5' , target : '1.5' , debug : 'on' )
  }
}

final performTests =  { classpathRef ->
  ant.mkdir ( dir : buildTestReportsDirectory )
  if ( usingCobertura ) {
    ant.mkdir ( dir : buildCoberturaClassesDirectory )
    ant.mkdir ( dir : buildCoberturaReportsDirectory )
    ant."${antlibXMLns}:dependencies" ( pathId :  'coberturaPathId' ) {
      dependency ( groupId : 'net.sourceforge.cobertura' , artifactId : 'cobertura' , version : '1.9' )
    }
    ant.taskdef ( resource : 'tasks.properties' , classpathref : 'coberturaPathId' )
    ant.'cobertura-instrument' ( todir : buildCoberturaClassesDirectory ) { fileset ( dir : buildClassesDirectory ) }
  }
  ant.junit ( printsummary : 'yes' , failureproperty : 'testsFailed' , fork : 'true' ) {
    formatter ( type : 'plain' )
    batchtest ( todir : buildTestReportsDirectory ) { fileset ( dir : buildTestClassesDirectory , includes : '**/ant/tests/*_Test.class' ) }
    classpath {
      if ( usingCobertura ) {
        // MUST appear in classpath before the uninstrumented classes.
        pathelement ( location : buildCoberturaClassesDirectory )
        path ( refid : 'coberturaPathId' )
      }
      pathelement ( location : buildTestClassesDirectory )
    }
  }
  ant.junit ( printsummary : 'yes' , failureproperty : 'testsFailed' , fork : 'true' ) {
    formatter ( type : 'plain' )
    batchtest ( todir : buildTestReportsDirectory ) { fileset ( dir : buildTestClassesDirectory , includes : '**/*_Test.class' , excludes : '**/ant/tests/*' ) }
    classpath {
      if ( usingCobertura ) {
        // MUST appear in classpath before the uninstrumented classes.
        pathelement ( location : buildCoberturaClassesDirectory )
        path ( refid : 'coberturaPathId' )
      }
      pathelement ( location : buildTestClassesDirectory )
      pathelement ( location : buildClassesDirectory )
      path ( refid : classpathRef )
    }
  }
  if ( usingCobertura ) { ant.'cobertura-report' ( srcdir : sourceDirectory , destdir : buildCoberturaReportsDirectory ) }
  ant.project.properties.testsFailed == null
}

final makeManifest = { ->
  ant.mkdir ( dir : buildMetadataDirectory )
  ant.copy ( todir : buildMetadataDirectory ,  file : 'LICENCE.txt' )
  ant.manifest ( file : buildMetadataDirectory + '/MANIFEST.MF' ) {
    attribute ( name : 'Built-By' , value : System.properties.'user.name' )
    attribute ( name : 'Extension-Name' , value : 'gant' )
    attribute ( name : 'Specification-Title' , value : 'Gant: scripting Ant tasks with Groovy.' )
    attribute ( name : 'Specification-Version' , value : gantVersion )
    attribute ( name : 'Specification-Vendor' , value : 'The Codehaus' )
    attribute ( name : 'Implementation-Title' , value : 'Gant: Scripting Ant tasks with Groovy.' )
    attribute ( name : 'Implementation-Version' , value : gantVersion )
    attribute ( name : 'Implementation-Vendor' , value : 'The Codehaus' )
  }
}

final setInstallationDirectory = { path ->
  if ( path == null ) {
    println ( 'Property installationDirectory is not set, cannot undertake (un)install actions.' )
    System.exit ( 1 )
  }
  installationDirectory = path
}

final prepareInstallDirectory = { path , gantJarName , isStandalone , item ->
  setInstallationDirectory ( path )
  def installationBinDirectory = installationDirectory + '/bin'
  def groovyJarName = null
  if ( item != null ) { groovyJarName = ( isStandalone ? 'groovy-all-' : 'groovy-' ) + item.version + '.jar' }
  else {
    assert ! isStandalone
    ant.pathconvert ( property : 'groovyJarName' ) {
      //  Have to specify ant.path here for some reason that is not entirely clear.
      ant.path { fileset ( dir : groovyHome + '/lib' , includes : 'groovy-1.*.jar' ) }
      mapper ( type : 'flatten' )
    }
    groovyJarName = ant.project.properties.groovyJarName
  }
  ant.copy ( todir : installationBinDirectory ) {
    fileset ( dir : scriptsDirectory + ( isStandalone ? '/bin_standalone' : '/bin_requiresGroovy' ) )
    fileset ( dir : scriptsDirectory + '/bin' )
    filterset { filter ( token : 'GANT_VERSION' , value : gantVersion ) }
    filterset { filter ( token : 'GROOVYJAR' , value : groovyJarName ) }
   }
  ant.chmod ( perm : 'a+x' ) { fileset ( dir : installationBinDirectory , includes : 'gant*' ) }
  ant.copy ( todir : installationDirectory ) {
    fileset ( dir : scriptsDirectory , includes : 'conf/gant-starter.conf' )
  }
  filesetId = 'filesetId'
  ant."${antlibXMLns}:dependencies" ( filesetId : filesetId ) {
    dependency ( groupId : 'org.apache.ivy' , artifactId : 'ivy' , version : ant.project.properties.ivyVersion )
    if ( isStandalone ) {
        dependency ( groupId : item.groupId , artifactId : item.artifactId , version : item.version )
        dependency ( groupId : 'commons-cli' , artifactId : 'commons-cli' , version : ant.project.properties.commonsCliVersion )
      }
    remoteRepository ( url : 'http://snapshots.repository.codehaus.org' )
    remoteRepository ( url : 'http://repository.codehaus.org' )
    remoteRepository ( url : 'http://repo1.maven.org' )
  }
  ant.copy ( todir : installationDirectory + '/lib' ) {
    fileset ( dir : buildDirectory , includes : gantJarName )
    fileset ( dir : jarfilesDirectory , includes : 'maven*.jar' )
    fileset ( refid : filesetId )
    mapper ( type : 'flatten' )
  }
}

final prepareZipFromTarAndRegisterForUpload = { String root , Closure closure ->
  path = buildDirectory + System.properties.'file.separator' + root
  closure ( )
  ant.zip ( destfile : path + zipExtension ) { tarfileset ( src : path + tarExtension ) }
  ant.gzip ( src : path + tarExtension , destfile : path + tgzExtension )
  serverUploadProducts [ path + tgzExtension ] = serverDistributionsDirectory + '/' +  root + tgzExtension
  serverUploadProducts [ path + zipExtension ] = serverDistributionsDirectory + '/' +  root + zipExtension
}

//  Create the compilation context of the Groovy with which Gant will be installed.  This is not used for
//  the distributions.
compileJarSet = 'compileJarSet'
ant.path ( id : compileJarSet ) {
  fileset ( dir : groovyHome + '/lib' , includes : 'groovy*.jar' )
  fileset ( dir : groovyHome + '/lib' , includes : 'commons-cli*.jar' )
  fileset ( dir : groovyHome + '/lib' , includes : 'asm*.jar' )
  fileset ( dir : groovyHome + '/lib' , includes : 'ant*.jar' )  // Picks up antlr as well as ant. This is intended.
}

testJarSet = 'testJarSet'
ant.path ( id : testJarSet ) {
  path ( refid : compileJarSet )
  fileset ( dir : groovyHome + '/lib' , includes : 'junit*.jar' )
}

//  Set up all the targets.

includeTargets << gant.targets.Clean
cleanPattern <<  [ '**/*~' , 'cobertura.ser' , 'texput.log' ]
cleanDirectory << buildDirectory

includeTool << gant.tools.Execute

target ( reallyClean : 'Ensure all clean operations for the entire tree get actioned.' ) {
  clobber ( )
  Execute.shell ( "cd packaging/debian && gant clobber" )
}

target ( compile : 'Compile everything needed for a Gant installation.' ) {
  performCompile ( compileJarSet , false )
}

target ( compileTests : 'Compile all the tests for a newly built Gant.' ) {
  depends ( compile )
  performCompileTests ( testJarSet , false )
}

target ( test : 'Test a build.' ) {
  depends ( compileTests )
  def returnCode = performTests ( compileJarSet )
  println ( 'Tests ' + ( returnCode ? 'suceeded' : 'failed' ) + '.' )
  returnCode ? 0 : 1
}

target ( coberturaTest : 'Test a build using Cobertura to get a coverage report.' ) {
  usingCobertura = true
  depends ( tests )
}

target ( 'package' : 'Create the jar file.' ) {
  depends ( compile )
  makeManifest ( )
  jar ( destfile : buildDirectory + "/gant-${gantVersion}.jar" , basedir : buildClassesDirectory , manifest : buildMetadataDirectory + '/MANIFEST.MF' )
}

target ( documentation : 'Create the API documentation.' ) {
  taskdef ( name : 'groovydoc' , classname : 'org.codehaus.groovy.ant.Groovydoc' )
  def javaApiDocumentationDirectory = apiDocumentationDirectory + '/java'
  def groovyApiDocumentationDirectory = apiDocumentationDirectory + '/groovy'
  mkdir ( dir : javaApiDocumentationDirectory )
  mkdir ( dir : groovyApiDocumentationDirectory )
  javadoc (
               sourcepath : sourceDirectory ,
               destdir : javaApiDocumentationDirectory ,
               packagenames : 'gant.*,org.codehaus.gant.*',
               overview : 'overview.html' ,
               'private' : 'true' ,
               encoding : 'UTF-8' ,
               use : 'true' ,
               author : 'true' ,
               windowtitle : "Gant (${gantVersion})" ,
               doctitle : "Gant (${gantVersion})" ,
               header : "Gant (${gantVersion})" ,
               footer : 'Copyright &copy; 2006&ndash;8 The Codehaus.  All rights reserved.' ,
               source : 1.5
               ) {
    classpath ( refid : testJarSet )
  }
  groovydoc (
             destdir : groovyApiDocumentationDirectory ,
             sourcepath : sourceDirectory ,
             packagenames : 'gant.*,org.codehaus.gant.*' ,
             //overview : 'overview.html' ,
             //author : 'true' ,
             //version : 'true' ,
             use : 'true' ,
             'private' : 'false' ,
             windowtitle : "Gant (${gantVersion})" ,
             //doctitle : "Gant (${gantVersion})" ,
             //encoding : 'UTF-8' ,
             //footer : 'Copyright &copy; 2006&ndash;8 The Codehaus.  All rights reserved.'
             )
}

target ( install : "Compile everything and install it." ) {
  depends ( 'package' )
  prepareInstallDirectory ( ant.project.properties.installDirectory , 'gant-' + gantVersion + '.jar' , false , null )
}

target ( uninstall : "Uninstall Gant." ) {
  setInstallationDirectory ( ant.project.properties.installDirectory )
  delete ( dir : installationDirectory )
}

//  Returns a boolean stating whether there were any test failures.

target ( distribution : 'Create the distribution.' ) {
  //  Use the Ant Maven plugin to ensure that the groovy jar of a given release is in the local Maven
  //  repository and ensure that it is the only version of Groovy used in the compilation.
  def prefix = 'gant-' + gantVersion
  def distributionClasspathId = 'classpath'
  clean ( )
  someTestsFailed = false
  distributionVersions.each { label , item ->
    def isStandalone = label == 'standalone'
    //  This is a closure that doesn't have the right metaclass so we have to specify Ant.  Need to
    //  rearrange things to deal with this problem as is done in the target Closure.
    def pathIdentifier = distributionClasspathId + item.version
    ant."${antlibXMLns}:dependencies" ( pathId : pathIdentifier ) {
      dependency ( groupId : item.groupId , artifactId : item.artifactId , version : item.version )
      dependency ( groupId : 'org.apache.ant' , artifactId : 'ant' , version : ant.project.properties.antVersion )
      dependency ( groupId : 'commons-cli' , artifactId : 'commons-cli' , version : ant.project.properties.commonsCliVersion )
      dependency ( groupId : 'junit' , artifactId : 'junit' , version : ant.project.properties.junitVersion )
      remoteRepository ( url : 'http://snapshots.repository.codehaus.org' )
      remoteRepository ( url : 'http://repository.codehaus.org' )
      remoteRepository ( url : 'http://repo1.maven.org' )
    }
    //  Must remove the compilation products of the last compilation!
    ant.delete ( dir : buildClassesDirectory )
    ant.delete ( dir : buildTestClassesDirectory )
    ant.delete ( dir : buildFilteredScriptsDirectory )
    ant.delete ( dir : buildInstallDirectory )
    performCompile ( pathIdentifier , true )
    performCompileTests ( pathIdentifier , true )
    if ( ! performTests ( pathIdentifier ) ) { someTestsFailed = true }
    makeManifest ( )
    def discriminator = prefix + '_groovy-' + item.version
    if ( isStandalone ) { discriminator = prefix }
    def jarName = discriminator + '.jar'
    def jarPath = buildDirectory + System.properties.'file.separator' + jarName
    ant.jar ( destfile : jarPath , basedir : buildClassesDirectory , manifest : buildMetadataDirectory + '/MANIFEST.MF' )
    serverUploadProducts [ jarPath ] = serverJarsDirectory + '/' + jarName
    prepareInstallDirectory ( buildInstallDirectory , jarName , isStandalone , item )
    prepareZipFromTarAndRegisterForUpload ( discriminator ) {
      //  NB Rely on Closures not being closures:  path is only resolved when the Closure is executed.
      ant.tar ( destfile :  path + tarExtension ) {
        tarfileset ( dir : buildInstallDirectory , mode : '511' , prefix : prefix ) { include ( name : 'bin/*' ) }
        tarfileset ( dir : buildInstallDirectory , prefix : prefix ) {
          include ( name : '**' )
          exclude ( name : 'bin/*' )
        }
        tarfileset ( dir : '.' , includes : 'README*' , prefix : prefix )
      }
    }
  }
  //  Create the source distribution.
  prepareZipFromTarAndRegisterForUpload ( 'gant_src-' + gantVersion ) {
    //  NB Rely on Closures not being closures:  path is only resolved when the Closure is executed.
    ant.tar ( destfile :  path + tarExtension ) {
      tarfileset ( dir : '.' , includes : distributionTopLevelFiles.join ( ',' ) , prefix : prefix )
      distributionDirectories.each { directory -> tarfileset ( dir : directory , prefix : prefix + System.properties.'file.separator' + directory ) }
    }
  }
  // Create the API documentation distribution.
  documentation ( )
  prepareZipFromTarAndRegisterForUpload (  'gant_doc-' + gantVersion ) {
    //  NB Rely on Closures not being closures:  path is only resolved when the Closure is executed.
    ant.tar ( destfile :  path + tarExtension ) {
      tarfileset ( dir : documentationDirectory , prefix : prefix + System.properties.'file.separator' + 'doc' )
    }
  }
  //  Return the result of the test executions so that uploadDistributions can choose not to upload a failed
  //  compilation.
  ! someTestsFailed
}

target ( uploadDistribution : 'Upload a complete distribution.' ) {
  if (  ! distribution ( ) && ! ignoreTestFailures ) {
    println ( 'Not uploading as some tests failed.' )
    return
  }
  //
  //  Slide has been retired by the Apache Foundation in favour of Jackrabbit, but we use Slide anyway.
  //
  "${antlibXMLns}:dependencies" ( pathId :  'slidePathId' ) {
    //dependency ( groupId : 'org.apache.jackrabbit' , artifactId : 'jackrabbit-webdav' , version : '1.3.3' )
    dependency ( groupId : 'slide' , artifactId : 'slide-webdavlib' , version : '2.1' )
  }
  //
  //  Is there a way of extracting the username and password information from the Maven Ant Task?  For the
  //  moment simply read the ~/.m2/settings.xml file.
  //
  def loader = getClass ( ).getClassLoader ( )
  def path = ant.path ( refid : 'slidePathId' )
  ( path.list ( ) as List ).each { location -> loader.addURL ( new URL ( 'file://' + location ) ) }
  def settings =  ( new XmlSlurper ( ) ).parse ( System.properties.'user.home' + '/.m2/settings.xml' ).servers.server.find { item -> item.id == 'dav.codehaus.org' }
  def credentials =  loader.loadClass ( 'org.apache.commons.httpclient.UsernamePasswordCredentials' ).getConstructor ( String , String ).newInstance ( settings.username.toString ( ) , settings.password.toString ( ) )
  //  Have to put resource in the binding so that the Closures can access it.
  resource = loader.loadClass ( 'org.apache.webdav.lib.WebdavResource' ).getConstructor ( String ,  loader.loadClass ( 'org.apache.commons.httpclient.Credentials' ) , boolean ).newInstance ( distributionURL , credentials , true )
  if ( resource.statusCode != 200 ) { println ( 'Failed to open ' + distributionURL ) }
  else {
    serverUploadProducts [ 'pom.xml' ] = serverPomsDirectory + '/gant-' + gantVersion + '.pom'
    serverUploadProducts.each { source , destination ->
      def serverPath = resource.path + '/' + destination
      print ( 'Uploading ' + source + ' -> ' + destination + ' : ' )
      def result = 'Failed.'
      if ( resource.putMethod ( serverPath , new File ( source ) ) ) { result = 'OK.' }
      println ( result + ' Status : ' + resource.statusMessage )
    }
  }
  if ( resource != null ) { resource.close ( ) }
}

setDefaultTarget ( test )

Labels

 
(None)