While CI/CD is easy for .NET if you use Github, it's much more work if you are on Gitlab. While it's possible it's a lot of moving parts and I hope to simplify the process a little bit.
Currently, I want a couple of things out of a basic CI/CD pipeline:
- Show me test results in a merge request
- Show me code coverage in a merge request
- Show me code quality issues in a merge request
- Enforce code style and highlight problems
Gitlab also offers lots of possibilities for security scanning, and that should work mostly out of the box so it's not in the scope of this blog post.
Linux or Windows?
Everything I show here works on Linux and Windows CI/CD runners. Fascinatingly, you can also build stuff for Windows ( net7.0-windows in the Project) on Linux. That's a huge boon because most Gitlab stuff assumes that it runs on a Linux runner.
I published a demo here on Github Example. You can check it out on your Gitlab instance and see what happens. I use this as a basis for this blog post.
Show me test results
Showing test results in a MR is quite easy, you "just" have to provide the results in JUNIT format for Gitlab.
You can do that by adding a library to your tests:
1<PackageReference Include="JunitXml.TestLogger" Version="3.0.114"/>
and then call it during the test execution:
1- 'dotnet test DotnetGitlabExample.sln --collect="XPlat Code Coverage" --logger:"junit;LogFilePath=testresult.xml"' # Run all tests, report them in JUNIT Format for
This dumps all test results into files called testresult.xml. They are created for each test project, so you can have multiple ones. Finally, get Gitlab to collect them:
1artifacts: 2 reports: 3 junit: 4 - "*/testresult.xml"
If you create a MR now, a pipeline will run and you can see the results:
Show me code coverage in a merge request
Building on the last step, we also want to see the code coverage. Gitlab expects a report in the Cobertura format.
There is a Nuget-Package to create this files:
1<PackageReference Include="coverlet.collector" Version="3.2.0"> 2 <PrivateAssets>all</PrivateAssets> 3 <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> 4 </PackageReference>
1'dotnet test DotnetGitlabExample.sln --collect="XPlat Code Coverage" --logger:"junit;LogFilePath=testresult.xml"'
This creates a file at
./coverage/Cobertura.xml which contains the coverage for each test project. Gitlab can only work with ONE Cobertura file, so we need to merge them.
There is a dotnet tool for that: reportgenerator.
On how to install it, see: https://github.com/codecentric/dotnet_gitlab_example#dotnet-tools
Call it in the pipeline like this:
1- 'dotnet tool restore' # Install the local tools 2 - 'dotnet tool run reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"./coverage" --reporttypes:"Cobertura;HtmlInline"'
The merged file can then be picked up by gitlab:
1artifacts: # upload files so Gitlab can show them in the merge view 2 reports: 3 junit: 4 - "*/testresult.xml" 5 coverage_report: 6 coverage_format: cobertura 7 path: "./coverage/Cobertura.xml" 8 paths: 9 - "./coverage/*.*" # collect coverage HTMls
This should now be visible in the code diff view of the merge request but it isn't. As usual, Gitlab gives no error message so I was unable to guess why Gitlab rejects these Cobertura files. At least, we can get the HTMl report just fine as an Artifact:
You can show the total coverage percentage on the merge request. Somewhat baffling, this requires ANOTHER gitlab configuration: https://docs.gitlab.com/ee/ci/pipelines/settings.html#add-test-coverage-results-using-coverage-keyword
The main problem here is that we need to vomit the percentage into the build log which is not supported by coverlet (see: https://github.com/coverlet-coverage/coverlet/issues/681).
So as a hacky workaround:
1script: 2 # - 'Rename-Item .\ci-nuget.config nuget.config' # rename the nuget config file so that it is picked up by Nuget automatically 3 - 'dotnet tool restore' # Install the local tools 4 - 'dotnet test DotnetGitlabExample.sln --collect="XPlat Code Coverage" --logger:"junit;LogFilePath=testresult.xml"' # Run all tests, report them in JUNIT Format for Gitlab. 5 - 'dotnet tool run reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"./coverage" --reporttypes:"Cobertura;HtmlInline;TextSummary"' # Generate Code Coverage Reports 6 - "sed -n '/Summary/,/^$/p' coverage/Summary.txt" 7 coverage: '/Line coverage: \d+/'
Basically we use reportgenerator to create a text summary, write it to the output using sed and finally use a regex to extract the percentage. Finally:
Show me code quality issues in a merge request
Code quality issues are typically reported by analyzers on dotnet. They either dump the information into the build log OR can be forced to create an SARIF summary. This SARIF file is JSON, but Gitlab cannot understand it, so we have to convert it to Gitlab-Code-Quality-JSON.
I also include Roslynator here, which offers additional warnings.
We want to enble analysis for all projects, while you can configure that in each project separately it gets problematic and inconsistent if you have more than a handful of projects. You can apply the relevant settings to ALL projects using
Directory.Build.props . It looks like this:
<Project> <PropertyGroup> <AnalysisLevel>latest-All</AnalysisLevel> # use newest warnings available <ErrorLog>codeanalysis.sarif.json</ErrorLog> # name the code analysis result file, otherwise it gets the name of the project. <AnalysisLevel>preview-recommended</AnalysisLevel> # Use all recommended warnings <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> # Show and create warnings during build <AnalysisMode>All</AnalysisMode> # Use all Analysis </PropertyGroup> </Project>
Again, see the Github repo for details.
Now when you build, each project gets a nice codeanalysis.sarif.json file which includes all issues.
Transform analysis results
I've written a small tool to convert the various formats into Gitlab-style: https://github.com/codecentric/dotnet_gitlab_code_quality
In a pipeline this looks like this:
variables: VERSION: '1.4.0.$CI_PIPELINE_IID' DIR: '$CI_BUILDS_DIR/$CI_JOB_ID/$CI_PROJECT_NAME' ..... script: # - 'mv ci-nuget.config nuget.config' - 'dotnet tool restore' - 'dotnet build' - 'dotnet tool run roslynator analyze -o ./roslynator.xml --severity-level hidden || true' # run roslynator - 'dotnet tool run cq roslynator roslynator.xml gl-code-quality-report1.json $DIR' # convert roslynator into gitlab format - 'dotnet tool run cq sarif ClassLibrary1/codeanalysis.sarif.json gl-code-quality-report2.json $DIR' # convert sarif file into gitlab format - 'dotnet tool run cq sarif ConsoleApp1/codeanalysis.sarif.json gl-code-quality-report3.json $DIR' # convert sarif file into gitlab format - 'dotnet tool run cq sarif TestProject1/codeanalysis.sarif.json gl-code-quality-report4.json $DIR' # convert sarif file into gitlab format - 'dotnet tool run cq merge gl-code-quality-report.json gl-code-quality-report1.json gl-code-quality-report2.json gl-code-quality-report3.json gl-code-quality-report4.json' # merge all files artifacts: paths: - roslynator.xml - gl-code-quality-report.json expose_as: 'code_quality_reports' reports: codequality: gl-code-quality-report.json
This transforms all results into the Gitlab format and then merges them into a single file Gitlab can understand.
Please note that this ONLY works after a code quality report has been created on the target branch, e.g. if you want to merge "Branch1" into "main" it only shows issues if your pipeline has run on "master":
It should be visible from the MR:
Enforce code style and highlight problems
Enforcing the code style is straightforward:
Create an .editorconfig file which is understood by alkl major IDEs and the dotnet build. Configure the style as you like and add this line:
dotnet_analyzer_diagnostic.category-Style.severity = warning
Now issues should surface:
Attention: Only "major" issues will be shown by Gitlab. Many issues are "minor" by default. You can add the command
--all_major to bump all to major.
There is still a lot to do for this project, mostly adding some more features and maybe creating installers. I will look into that next time.
Your job at Codecentric?
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.