Since a friend showed me SonarQube I loved it: the amount of reports that you can gather from it and the immediate impact on improving the code is simply awesome. As an example in just a couple of weeks at work we’ve been able to gain a +10% of code coverage and remove all the complexity on a critical area.

Configuring TFS and SonarQube

Setting up SonarQube is very easy and it really comes down to unzip 2 files, register it as a service, configure a DB if you don’t want to use the one that is embedded and install the C# plugin in the update center. Now we have to connect your CI Server, I suppose that you’re using TFS 2013 because it’s the most common in .NET shops but I tried Jenkins and works very well (just use the MSBuild and SonarQube plug-ins). There are two ways to integrate TFS with SonarQube: using the Sonar Build Activity from the Community TFS Build Extensions or manually with a custom build event. Contrary to my usual way to handle these things I decided to opt for the manual approach. The Activity does it’s job very well but I really hate when I have to deal with all the TFS’s XML files. I just finished an upgrade from 2010 to 2013 combined with a project update from the Waterfall to Agile template and I still have nightmares at night… With this self imposed “no more XML editing” rule I wanted to see how much does it take to link them manually, surprisingly it only takes two steps.

Creating the properties file

The first one is easy if you know how SonarQube works or it’ll take you a bit to understand how the file has to be configured. As a sample this is what I used successfully:

Project identificationsonar.projectKey=key.TestProject
sonar.projectVersion=1.2
sonar.projectName=TestProject
# Info required for Sonar
sonar.sources=.
sonar.language=cs
sonar.sourceEncoding=UTF-8
sonar.dotnet.visualstudio.solution.file=Solution.sln
sonar.dotnet.visualstudio.testProjectPattern=ClientTest
sonar.fxcop.mode=skip
sonar.gendarme.mode=skip
sonar.gallio.mode=active
sonar.ndeps.mode=skip
sonar.squid.mode=skip
sonar.stylecop.mode=skip
sonar.gallio.it.mode=skip
sonar.gallio.runner=IsolatedProcess

Nothing fancy here: just the project key, version and name followed by the Solution’s file and a couple of in house settings. Place it beside the Solution file and you’re done with it.

Using a post build event to invoke SonarQube

Now it’s time to open the Solution and edit the properties of one of your projects (I used the unit test project). Go in the Post Build Event and paste this command:

if "$(ConfigurationName)"=="Sonar"
(
    "C:\Program Files\sonar-runner\bin\sonar-runner" -Dproject.home="$(SolutionDir)." -Dproject.settings="$(SolutionDir)sonar.properties" -Dsonar.dotnet.assemblies="$(TargetDir)." -Dsonar.dotnet.test.assemblies="$(TargetPath)"
)

As for the properties also this command is really simple: it just invokes the Sonar Runner with the properties file and a couple of extra parameters to set the right folders paths (a TFS build uses a different binaries folder compared to Visual Studio). I used an ad hoc $(ConfigurationName) named “Sonar” so I can trigger the analysis only when the build server is using that specific configuration.

Running SonarQube

With the properties in the correct path and the build event ready to call the Sonar Runner you just have to check-in the changes and wait for the build to finish. Once done go to the SonarQube home page and you’ll see everything you have to know about your code. If this is the first time that you use SonarQube I suggest you to look at the “Code Duplications” and “Code Coverage” pages.

Conclusions

One of the things that delayed the adoption of SonarQube was the impression that it was a lengthy and complex task, especially when it comes down to the integration with TFS. Now that everything is in place I can say that I was wrong; as a comparison it took more time (and effort) to configure a dedicated build server.

Update 1 - 16.7.2014

The Runner’s startup parameters have changed since version 4.3, and, therefore the Build Event should be modified to:

if "$(ConfigurationName)"=="Sonar"
(
    "C:\Program Files\sonar-runner\bin\sonar-runner" -Dsonar.projectBaseDir="$(SolutionDir)." -Dproject.settings="$(SolutionDir)sonar.properties" -Dsonar.dotnet.assemblies="$(TargetDir)." -Dsonar.dotnet.test.assemblies="$(TargetPath)"
)