fredag 18. februar 2011

Gradle friends with Sonar

A little while ago a colleague of mine asked me if I had done a Sonar analysis of my current project. Well I hadn't so I started to investigate a little on what my options were given my build was setup with Gradle. After reading up a bit on the gradle forums, it appeared that it wasn't all that trivial. In particular getting my emma coverage available for sonar seemed to be a stumbling block. The sonar version at the time also seemed to be fairly tied to maven, a build system I sort of left behind last year.

Anyways yesterday I thought I'd check up on the announced plans for providing gradle support for sonar. Not quite there yet, but there was a snapshot version with ant support. Gradle and Ant being pretty good friends, I decided to give it a go.

Short story is that it worked. However I couldn't manage to get the testcoverage from emma in sonar, so I switched my setup to use cobertura (did slow my build quite a bit actually). Another remaining issue is all the annoying error messages from the pmd plugin in sonar. Lots of exceptions related to it for some reason insisting that I'm using jdk 1.4, but the fact is that I'm using jdk 1.6.

My build.gradle setup:
 apply plugin: 'java'  
 configurations {  
   sonarLibs  
    coberturaLibs {extendsFrom testRuntime}  
 }  
 dependencies {  
   //.. other deps omitted  
   coberturaLibs 'net.sourceforge.cobertura:cobertura:1.9.3'  
   sonarLibs "org.codehaus.sonar-plugins:sonar-ant-task:0.1-SNAPSHOT"  
 }  
 def cobSerFile = "${project.buildDir}/cobertura.ser"  
 def srcOriginal = "${sourceSets.main.classesDir}"  
 def srcCopy = "${srcOriginal}-copy"  
 test {  
   useTestNG()  
   maxParallelForks = 2  
   afterTest {descriptor, result ->  
     println "${descriptor.className}.${descriptor.name}"  
   }  
   /** Cobertura setup **/  
   if (project.hasProperty('coverage') && ['on', 'true'].contains(project.properties.coverage)) {  
     systemProperties["net.sourceforge.cobertura.datafile"] = cobSerFile  
     doFirst {  
       ant {  
         // delete data file for cobertura, otherwise coverage would be added  
         delete(file: cobSerFile, failonerror: false)  
         // delete copy of original classes  
         delete(dir: srcCopy, failonerror: false)  
         // import cobertura task, so it is available in the script  
         taskdef(resource: 'tasks.properties', classpath: configurations.coberturaLibs.asPath)  
         // create copy (backup) of original class files  
         copy(todir: srcCopy) {  
           fileset(dir: srcOriginal)  
         }  
         // instrument the relevant classes in-place  
         'cobertura-instrument'(datafile: cobSerFile) {  
           fileset(dir: srcOriginal,  
               includes: "**/*.class",  
               excludes: "**/*Test.class")  
         }  
       }  
     }  
     doLast {  
       if (new File(srcCopy).exists()) {  
         // replace instrumented classes with backup copy again  
         ant {  
           delete(file: srcOriginal)  
           move(file: srcCopy,  
               tofile: srcOriginal)  
         }  
         // create cobertura reports  
         ant.'cobertura-report'(destdir: "${project.buildDirName}/test-results",  
             format: 'html', srcdir: "src/main/java", datafile: cobSerFile)  
         ant.'cobertura-report'(destdir: "${project.buildDirName}/test-results",  
             format: 'xml', srcdir: "src/main/java", datafile: cobSerFile)  
       }  
     }  
   }  
 }  
 task runSonar << {  
   ant.taskdef(name: "sonar", classname: "org.sonar.ant.SonarTask", classpath: configurations.sonarLibs.asPath)  
   ant.sonar(workdir: ".", key: "no.sample:sampleapp", version: '1.0') {  
     sources {  
       path(location: "src/main/java")  
     }  
     tests {  
       path(location: "src/test/java")  
     }  
     // tell sonar to use existing cobertura reports  
     property(key: "sonar.dynamicAnalysis", value: "reuseReports")  
     property(key: "sonar.cobertura.reportPath", value: file("build/test-results/coverage.xml"))  
   }  
 }  

So to run the sonar goal with coverage I basically make the following command;

$gradle build runSonar -Pcoverage=on



And voila:

4 kommentarer:

  1. Adding property(key: "sonar.java.source", value: "1.6") to the runSonar task solved my pmd plugin issue. Big thx to @_godin_

    SvarSlett
  2. looks like sonar plugin will be in gradle 1.0-milestone-2. However no project aggregation it looks like.

    SvarSlett
  3. Nifty!
    I need to do something similar to this with some grails projects of mine.(That is: making test-coverage from grails-projects work within sonar)
    Have U tried a Sonar analysis (w/test coverage) of a grails project?

    SvarSlett
  4. @stigat : Haven't used Gradle with grails (haven't nearly really played enough with Grails as I should). Anyways maybe the gradle grails plugin (https://github.com/grails/grails-gradle-plugin) would allow you to also use the sonar plugin ?
    Let me know if it works !

    SvarSlett