SonarQube analysis of .NET application with sonar-project.properties in Azure DevOps

SonarQube analysis of .NET application with sonar-project.properties in Azure DevOps

Background

In the past, software was often developed by one person or a very small team, and the same person / team would also test the software. If there was a problem with the software, you’d just contact the developer (maybe per fax) and the problem would hopefully be resolved, eventually. Times have evolved, software development has become a huge industry, and the users expect that the software actually works. With more developers working on a particular piece of software, integration of new lines of code into the product becomes more challenging. Source control, pull requests, and CI/CD to the rescue. (If you find this to be a waste of time, please read up on the Joel Test). In my opinion, a (static) code analysis should be part of any CI/CD pipeline to ensure that the quality of new code meets the set criteria. For code written in .NET, SonarQube is a frequently used code analysis tool.

Problem

Performing a SonarQube analysis of .NET code as part of a CI/CD pipeline is Azure DevOps is rather straight-forward. The steps are: (1) prepare Sonar analysis, (2) build the code and run unit tests (make sure to collect code coverage), (3) perform the actual Sonar analysis, and (4) publish the results. A complete example of how to do this can be found here.

The first step is done using SonarQubePrepare, which takes two interesting arguments, scannerMode and extraProperties. For a .NET application, use the default value of scannerMode, which is ‘MsBuild’ (edit: as of version 7, the default scanner mode has been changed to ‘dotnet‘). If you want certain files to be excluded from code coverage analysis, or from the analysis entirely (e.g. in the case of generated code files), you can add these sonar properties to the extraProperties. See the documentation for more information about the available properties.

Another interesting option of the SonarQubePrepare task is the configFile option. It enables storing Sonar related settings in a separate file, typically “sonar-project.properties“, which removes unnecessary clutter from the pipeline. This can be particular helpful if a team different from the development team manages the pipeline. The only challenging part is that this option is only available if the scannerMode is set to ‘CLI‘, which means that it cannot be used for analyzing a .NET application.

Solution

In order to get the best of both worlds, e.g. using the default value for scannerMode, as well as storing one-off Sonar properties, we can use Powershell to read a settings file and feed the result into the extraProperties:

parameters:
  - name: sonarQube
    type: string
  - name: projectName
    type: string
  - name: projectKey
    type: string
  - name: sonarHostUrl
    type: string  
  - name: pathToSonarQubeConfigFile
    type: string
    default: ''

steps:
  - powershell: | 
      $sqProps = "sonar.host.url=${{ parameters.sonarHostUrl }}"
      $pathToConfigFile = '${{ parameters.pathToSonarQubeConfigFile }}'
      if(($pathToConfigFile  -ne '') -and (Test-Path $pathToConfigFile)){
        Write-Host "SonarQube properties will be read from $pathToConfigFile"
        $contents = Get-Content -Path $pathToConfigFile

        $contents | `
         Where-Object {$_ -match "^\s*sonar"} | `
         ForEach-Object { $trimmed = $_.Trim(); $sqProps = "$sqProps;$trimmedLine"}

      $sqExtraProperties = $sqProps -replace ';', "%0D%0A"

      Write-Host "##vso[task.setvariable variable=sqExtraProperties;]$sqExtraProperties"
    displayName: Determine parameters for SonarQube

  - task: SonarQubePrepare@6
    displayName: Prepare analysis on SonarQube
    inputs:
      SonarQube: ${{ parameters.sonarQube }}
      projectKey: ${{ parameters.projectKey }}
      projectName: ${{ parameters.projectName }}
      extraProperties: |
        $(sqExtraProperties)

The Powershell task will, provided the configuration file exists, read every line that starts with the sonar keyword, and append that line to the variable sqProps, separated by a semi-colon. This creates a single line, that must be encoded as a multi-line string. This is done by replacing the semi-colon with %0D%0A; a more thorough explanation can be found in Thomas Abraham’s answer to this SO question. The final step is to write the encoded multi-line string to a variable, which is being read by SonarQubePrepare.