Running SonarQube for a .NET Core project in Azure DevOps YAML multi-stage pipelines

We have been looking migrating some of our common .NET Core libraries into new NuGet packages and have taken the chance to change our build process to use Azure DevOps Multi-stage Pipelines. Whilst doing this I hit a problem getting SonarQube analysis working, the documentation I found was a little confusing.

The Problem

As part of the YAML pipeline re-design we were moving away from building Visual Studio SLN solution files, and swapping to .NET Core command line for the build and testing of .CSproj files. Historically we had used the SonarQube Build Tasks that can be found in the Azure DevOps Marketplace to control SonarQube Analysis. However, if we used these tasks in the new YAML pipeline we quickly found that the SonarQube analysis failed saying it could find no projects ##[error]No analysable projects were found. SonarQube analysis will not be performed. Check the build summary report for details. So I next swapped to using use the SonarScanner for .NET Core, assuming the issue was down to not using .NET Core commands. This gave YAML as follows,``` - task: DotNetCoreCLI@2      displayName: 'Install Sonarscanner'      inputs:        command: 'custom' custom: 'tool' arguments: 'install --global dotnet-sonarscanner --version 4.9.0

  • task: DotNetCoreCLI@2 displayName: 'Begin Sonarscanner' inputs: command: 'custom' custom: 'sonarscanner' arguments: 'begin /key:"$(SonarQubeProjectKey)" /name:"$(SonarQubeName)" /d:sonar.host.url="$(SonarQubeUrl)" /d:sonar.login="$(SonarQubeProjectAPIKey)" /version:$(Major).$(Minor)'

…. Build and test the project

  • task: DotNetCoreCLI@2 displayName: 'En Sonarscanner' inputs: command: 'custom' custom: 'sonarscanner' arguments: 'end /key:"$(SonarQubeProjectKey)" '
 1
 2### The Solution
 3
 4The solution it turns out was nothing to do with using the either of the ways to trigger SonarQube analysis, it was down to the fact that the .NET Core .CSProj file did not have a unique GUID. Historically this had not been an issue as if you trigger SonarQube analysis via a Visual Studio solution GUIDs are automatically injected. The move to building using the .NET core command line was the problem, but the fix was simple, just add a unique GUID to each CS project file.```
 5<Project Sdk="MSBuild.Sdk.Extras">
 6  <PropertyGroup>
 7     <TargetFramework>netstandard2.0</TargetFramework>
 8     <PublishRepositoryUrl>true</PublishRepositoryUrl>
 9     <EmbedUntrackedSources>true</EmbedUntrackedSources>
10     <ProjectGuid>e2bb4d3a-879c-4472-8ddc-94b2705abcde</ProjectGuid>
11
1213```Once this was done, either way of running SonarQube worked. After a bit of thought, I decided to stay with the same tasks I have used historically to trigger analysis. This was for a few reasons
14
15*   I can use a central Service Connector to manage credentials to access SonarQube
16*   The tasks manage the installation and update of the SonarQube tools on the agent
17*   I need to pass less parameters about due to the use of the service connector
18*   I can more easily include the SonarQube analysis report in the build
19
20So my YAML now looks like this```
21\- task: [SonarSource.sonarqube.15B84CA1-B62F-4A2A-A403-89B7A063157.SonarQubePrepare@4](mailto:SonarSource.sonarqube.15B84CA1-B62F-4A2A-A403-89B7A063157.SonarQubePrepare@4)
22  displayName: 'Prepare analysis on SonarQube'
23  inputs:
24    SonarQube: Sonarqube
25    projectKey: '$(sonarqubeKey)'
26    projectName: '$(sonarqubeName)'
27    projectVersion: '$(Major).$(Minor)'
28    extraProperties: |
29          # Additional properties that will be passed to the scanner, 
30          # Put one key=value per line, example:
31          # sonar.exclusions=\*\*/\*.bin
32          sonar.dependencyCheck.reportPath=$(Build.SourcesDirectory)/dependency-check-report.xml     
33       sonar.dependencyCheck.htmlReportPath=$(Build.SourcesDirectory)/dependency-check-report.html
34          sonar.cpd.exclusions=\*\*/AssemblyInfo.cs,\*\*/\*.g.cs
35             sonar.cs.vscoveragexml.reportsPaths=$(System.DefaultWorkingDirectory)/\*\*/\*.coveragexml
36         sonar.cs.vstest.reportsPaths=$(System.DefaultWorkingDirectory)/\*\*/\*.trx
37
38… Build & Test with [DotNetCoreCLI](mailto:DotNetCoreCLI@displayName: 'Build the project')
39
40- task: SonarSource.sonarqube.6D01813A-9589-4B15-8491-8164AEB38055.SonarQubeAnalyze@4
41  displayName: 'Run Code Analysis'
42
43- task: SonarSource.sonarqube.291ed61f-1ee4-45d3-b1b0-bf822d9095ef.SonarQubePublish@4
44  displayName: 'Publish Quality Gate Result'

Addendum..

Even though I don’t use it in the YAML, I still found a use for the .NET Core SonarScanner commands. We use the Developer edition of SonarQube, this understands Git branches and PR. This edition has a requirement that you must perform an analysis on the master branch before any analysis on other branches can be done. This is because the branch analysis is measured relative to the quality of the master branch. I have found the easiest way to establish this baseline, even if it is of an empty project, is to run SonarScanner from the command on my PC, just to setup the base for any PR to be measured against.