Blog

JavaForge: Remote Build- and Deploy Automation using Groovy
Mar 05, 2010

Repetitive tasks are boring, especially if they take long. And since they are boring, they are inherently error prone. A typical example is a long and complicated build procedure that is performed manually every time. This is where programmatic automation enters the game to accelerate builds, and to reduce costs and errors.

Olaf Davidson of the U.S. Department of Agriculture shows us how his project hosted at JavaForge solves a fairly complicated build scenario with a surprisingly concise script. The script itself is written in Groovy, an alternative language that runs on the JVM, and the solution relies on the Groovy Codebeamer Scripting framework, also maintained by Olaf.

The actual script does the followings:

  1. Checks the need for a build
  2. Performs the build (locally)
  3. Changes the item status
  4. Creates a new ‘milestone’ item
  5. Uploads the build product to the Document Manager
  6. Attaches the build product and build process output to the milestone item
  7. Tags the source code repository
  8. Associates the fixed bugs and implemented features to the milestone build
  9. Posts a notification
Sounds pretty familiar, huh? Although the script included here is primarily for inspiration, you can easily implement your own build procedure, based on the same idea. Plus, an alternative use of this could be relocating your builds to an external server or servers, thus offloading the one that hosts your codeBeamer instance.

One more note: Groovy might not be your cup of tea, but this should not stop you. At the end, all you need is a client application written in your favourite language, accessing the codeBeamer remote API. The primary bindings for the API are Java and C#, but some of our customers have successfully implemented remote clients in PHP and C/C++, too.

package ngmfbuild
 
import cbscript.*
import static cbscript.CB.*
 
def SVN = "c:/cygwin/bin/svn.exe"
def ANT = "c:/java/apache-ant-1.7.1/bin/ant.bat"
def SVN_URL = "http://svn.javaforge.com/svn/oms"
 
def ms = "3.0.11"
def qstatus = "Ready"
def dry = false
 
println "Login JF/OMS..."
def jf = CB.login("http://javaforge.com/remote-api", "odavid", new
File("c:/tmp/jfpass").text)
def prj = jf.projects.find{ it.name == "Object Modeling System" }
 
println "Query bugs and tracker ..."
// query for candidates: fixed bugs
 
def readyBugs = prj.trackers.find {it.name == "Bugs"}.issues.findAll {
    it.status == qstatus
}
 
// query for candidates in Feature requests
def readyFeatures = prj.trackers.find {it.name == "Features"}.issues.findAll {
    it.status == qstatus
}
 
// combine all features and bugs into one single list
def readyList = readyBugs + readyFeatures
 
println "There are ${readyList.size} bug fixes/new features ready for
the build..."
if (!readyList.empty) {    // or require a minimum number of fixes/feature
    println " Bugs:"
    readyBugs.each{println "   ${it.id} -  ${it.summary}"}
    println " Features:"
    readyFeatures.each{println "   ${it.id} -  ${it.summary}"}
 
 
    println "Building ngmf.all ..."
    StringBuffer stdout = new StringBuffer()
    StringBuffer stderr = new StringBuffer()
    p = "${ANT} -Doms.version=${ms} -f c:/od/projects/ngmf.all/build.xml clean jar".execute()
    p.consumeProcessOutput(stdout, stderr)
    p.waitFor()
    if (p.exitValue()) {
        println "ERROR !!!!!!!!!!!!!"
        println stderr
        return
    }
 
    println "Branding Console with ${ms}"
    replaceVar(new File("C:/od/projects/ngmf.dcon/src/ngmfcon/resources/ConApp.properties"), ms)
 
    println "Building ngmf.con ..."
    stdout = new StringBuffer()
    stderr = new StringBuffer()
    p = "${ANT} -f c:/od/projects/ngmf.con/build.xml clean jar".execute()
    p.consumeProcessOutput(stdout, stderr)
    p.waitFor()
    if (p.exitValue()) {
        println "ERROR !!!!!!!!!!!!!"
        println stderr
        return
    }
 
    // dry run is done here.
    if (dry) {
        println "DRY RUN, STOP HERE !!!!!!!!!!!!!!!"
        jf.logout();
        return
    }
 
    // commit new release About
    println "Committing Branding ..."
    svn = """${SVN} ci "../ngmf.dcon/src/ngmfdcon/resources/NgmfdconApp.properties" -m 'Branding ${ms}'""".execute()
    tag_out = svn.text.trim()
    tag_err = svn.err.text
    println tag_out
    println tag_err
 
    // tag the whole trunk
    println "Tagging ${SVN_URL}/tags/${ms} ..."
    svn = "${SVN} copy ${SVN_URL}/trunk ${SVN_URL}/tags/${ms} -m 'tagged ${ms}'".execute()
    tag_out = svn.text.trim()
    tag_err = svn.err.text
 
    String info = "!!!Milestone ${ms} Build Info \n" +
            "; __Build Process Exit Status__: ${p.exitValue()} (see attachments for details)\n" +
            "; __SVN Tag URL__: " +
            "[tags/${ms}|http://javaforge.com/proj/sources/sccBrowse.do?proj_id=1781&dirname=tags/${ms}] " +
            "(external: [${SVN_URL}/tags/${ms}])\n" +
            "; __SVN Log__: " +
            "[${tag_out}|http://javaforge.com/proj/sources/sccFileLog.do?proj_id=1781&filename=tags%2F${ms}&isDir=true]"
 
    println "Creating new Milestone Item ${ms} ... "
    def mt = prj.trackers.find {it.name == "Milestones"}
    def milestone = mt.submit(summary:ms, text:info, priority:NORMAL)
    //
    println "  Attaching build output  ... "
    milestone.attach(name:"stdout.txt", content:stdout.toString(), text:"Build output.")
    println "  Attaching build product ... "
    milestone.attach(name:"oms-"+ms+".zip", file:"c:/od/projects/ngmf.all/oms3.zip", text:"OMS Zip Distribution.")
 
    println "  Uploading to documents ... "
 
    // copy the build to documents
    doc = prj.artifact("Releases/v3/oms-"+ms+".zip")
    doc << new File("c:/od/projects/ngmf.all/oms3.zip")
    //
    println "Associating all bugs/features to the new milestone ..."
    readyList.each{ task -> milestone.relate(task, RELATED) }
 
    println "Closing related bugs/features ..."
    readyList.each{ it.status = "Closed" }
 
    println "Spreading the news ..."
    def news = prj.forums.find {it.name == "News"}
    news.post(subject:"Milestone Release: " + ms, text:"Milestone details : " + CB.link(milestone));
} else {
    println " Nothing is ready for build."
}
 
println "Logout ..."
jf.logout();
 
def replaceVar(File file, String val) {
    file.text = file.text.replaceAll(/(Application.version = )\S+/, "\$1$val")
}

About intland

Provider of Agile ALM solutions. Father of JavaForge. Maintainer of MercurialEclipse. Into all things agile & DVCS. View all posts by intland

Leave a Reply

Your email address will not be published. Required fields are marked *

*


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>