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:
- Checks the need for a build
- Performs the build (locally)
- Changes the item status
- Creates a new ‘milestone’ item
- Uploads the build product to the Document Manager
- Attaches the build product and build process output to the milestone item
- Tags the source code repository
- Associates the fixed bugs and implemented features to the milestone build
- Posts a notification
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")
}