GitLab CI template for .NET¶
This project implements a GitLab CI/CD template to build, test, and analyse your .NET projects.
Supported Features and Tooling:
- SDKs:
- Supported Versions: .net6, .net8 and later can be used as the .NET SDK docker image for the build environment.
- .NET SDK versions required next to the SDK found the container image are installed by the template using the manual scripts including pinned versions from
global.json. - .NET Framework (4.8 and earlier) are not supported.
- Pipeline Runner OS: Linux (Windows runners are not supported)
- Build tool: msbuild
- Dependency management:
- .NET Workload via
global.json - SDK style NuGet
<PackageReference>see msbuild - paket (auto-detected)
- Cross-Compilation: This template runs on Linux. It can only build executables (AOT/R2R) for the Linux runtime of the runner (e.g., linux-x64). Cross-compilation to other RIDs (like win-x64 or osx-arm64) is currently not supported.
- See .NET Build Configuration Best Practices for important configuration recommendations when using this template.
Usage¶
This template can be used both as a CI/CD component
or using the legacy include:project syntax.
Use as a CI/CD component¶
Add the following to your .gitlab-ci.yml:
include:
# 1: include the component
- component: $CI_SERVER_FQDN/to-be-continuous/dotnet/gitlab-ci-dotnet@2.0.0
# 2: set/override component inputs
inputs:
test-enabled: true
Use as a CI/CD template (legacy)¶
Add the following to your .gitlab-ci.yml:
include:
# 1: include the template
- project: 'to-be-continuous/dotnet'
ref: '2.0.0'
file: '/templates/gitlab-ci-dotnet.yml'
variables:
# 2: set/override template variables
# ⚠ this is only an example
DOTNET_TEST_EXTRA_ARGS: "-filter 'Category!=Integration'"
Global configuration¶
The dotnet template uses the following global configuration used throughout all jobs:
| Input / Variable | Description | Default value |
|---|---|---|
image / DOTNET_IMAGE |
The Docker base image used to run the .NET SDK (additional SDKs are installed as needed) (see Microsoft Container Registry for available tags) | mcr.microsoft.com/dotnet/sdk:10.0 |
project-dir / DOTNET_PROJECT_DIR |
The folder where the solution (*.sln, *.slnx) or the project (*.csproj, *.fsproj, *.vbproj) file to build is located. |
. |
build-file / DOTNET_BUILD_FILE |
The name of the solution (*.sln, *.slnx) or the project (*.csproj, *.fsproj, *.vbproj) file to build.Leave empty to enable auto-discovery. |
none (auto-discovery) |
build-props / DOTNET_BUILD_PROPS |
Space separated list of properties injected into the Directory.Build.props or *.*proj files by the build job.Formatted as Property1=Value1 Property2=Value2. |
ErrorLog=bin/report/dotnet-build.sarif,version=2.1 AnalysisLevel=latest AnalysisMode=all CodeAnalysisIgnoreGeneratedCode=true WarningLevel=4 EnforceCodeStyleInBuild=true RunAnalyzersDuringBuild=true RunCodeAnalysis=true TreatWarningsAsErrors=false CodeAnalysisTreatWarningsAsErrors=false PublishRepositoryUrl=true |
nuget-sources / DOTNET_NUGET_SOURCES |
Space separated list of .Net NuGet package sources (formatted as somename:https://some.nuget.registry/some/repo/index.json anothername:https://another.nuget.registry/another/repo/index.json) |
none |
About $DOTNET_IMAGE¶
The jobs in the pipeline will use the defined container image to provider the .NET SDK environment to the job context. The default configuration will always use the most recent LTS version of .NET. You can override this to use specific current or older versions or use the SDK preview channel.
[!important] .NET Framework is not supported.
About $DOTNET_BUILD_PROPS¶
The values provided will be added to an existing ${DOTNET_PROJECT_DIR}/Directory.Build.props file or a new file will be created. Feel free to adjust the compiler analysis levels, but keep the ErrorLog configuration to ensure the integration of the compiler analysis into the GitLab and Sonar reports.
Solution / Project Versioning¶
The template will use the versions set in by the project files and the git tags in the following way:
- Version:
- ${CI_COMMIT_TAG} overrides all Version element configured by the project files.
- Highest Version element configured in the build scope.
- VersionSuffix:
- Empty if ${CI_COMMIT_TAG} is set.
- Empty if for production or integration branches matching the pre-defined PROD_REF and INTEG_REF patterns.
- ${CI_COMMIT_REF_SLUG} for all other branches
Solution / Project Auto-Discovery¶
The template supports auto-discovery of solution and project files for simple project structures where a single solution or project file exists. If your project has a more complex structure or multiple solution files in the root of the repo, then you use the template parameters project-dir / DOTNET_PROJECT_DIR and build-file / DOTNET_BUILD_FILE (relative path to project-dir) to select the correct build context and target solution / project to build.
The combination of DOTNET_PROJECT_DIR and DOTNET_BUILD_FILE is used by the pipeline to identify a unique solution or project file to build:
project-dir/DOTNET_PROJECT_DIR: specifies the folder containing the solution or project to build (defaults to., the root repository folder),build-file/DOTNET_BUILD_FILE: specifies the relative path toproject-dir. When unset or blank (default), the template will search for a single solution or a single project file in the project directory. Must be set explicitly when multiple solutions or projects exist (see Working With Multiple Solutions And Projects).
ℹ️ When DOTNET_BUILD_FILE is unset then the following auto-discovery steps are run:
- A solution (
*.sln) is searched withinDOTNET_PROJECT_DIR. - If a single one is found, then this is used as
DOTNET_BUILD_FILE. - If multiple solution files are found then the template fails with an explicit error message.
- If no solution file is found, then a project file (
*.*proj) is searched withinDOTNET_PROJECT_DIR. - If a single file is found, then this is used as
DOTNET_BUILD_FILE. - If no file or multiple project files are found then the template fails with an explicit error message.
Working With Multiple Solutions and Projects¶
This template supports repositories with multiple solution files and complex project structures through parallel:matrix builds.
You can specify multiple project directories or direct paths to solution or project files.
[!note] Each pipeline instance is a completely independent build with independent artifacts and reports.
include:
- component: $CI_SERVER_FQDN/to-be-continuous/dotnet/gitlab-ci-dotnet@2.0.0
inputs:
test-enabled: true
# multi-instanciate the template with parallel:matrix
.dotnet-base:
parallel:
matrix:
- DOTNET_PROJECT_DIR: "src"
DOTNET_BUILD_FILE: "myapp.sln"
- DOTNET_PROJECT_DIR: "tools"
- DOTNET_PROJECT_DIR: "samples"
DOTNET_BUILD_FILE: "samples.sln"
.NET Environment Restore¶
The template executes the following actions produce a reproducible build environment for each job:
- Install all SDKs referenced by TFM field the projects in the build scope.
- Run
dotnet workspace restore, if aglobal.jsonis found. - Run
dotnet tool restore. - Run
dotnet paket restore, if a paket configuration is found. - Run
dotnet restore.
Supporting Slow / Integration Tests¶
To separate "fast" unit tests (which run on every build) from "slow" integration tests (which might need a database or other services) you can split the execution into separate jobs.
- In the test code:
- Annotate the slow tests using xUnit
[Trait("Category","Integration")], NUnit[Category("Integration")], or MSTest[TestCategory("Integration")].
- Annotate the slow tests using xUnit
- In the
.gitlab-ci.ymlfile:- Set the global
test-extra-args/DOTNET_TEST_EXTRA_ARGSto the exclude the slow tests like this:--filter "Category!=Integration - Add a new job file to run the slow tests in a later test stage (e.g.
package-test):integration-tests: stage: package-test extends: dotnet-build variables: DOTNET_TEST_EXTRA_ARGS: --filter "Category=Integration" script: - dotnet_run_test
- Set the global
[!important] GitLab does not merge multiple coverage reports. The coverage percentage shown in the UI will only reflect the last job that ran (in this case,
integration-tests).
Jobs¶
dotnet-build job¶
This job performs compilation and packaging of your .NET project using dotnet build, and either dotnet pack for libraries or dotnet publish for executables. The job automatically detects the project types and uses the appropriate packaging method for each. See the section dotnet-test below for all test related configuration and actions.
See also .NET Build Configuration Best Practices to control the per project packaging behavior.
It uses the following variables:
| Input / Variable | Description | Default value |
|---|---|---|
build-args / DOTNET_BUILD_ARGS |
Additional arguments used by the build job | none |
build-configuration / DOTNET_BUILD_CONFIGURATION |
The build configuration to use (Debug or Release) | Release |
package-configuration / DOTNET_PACKAGE_CONFIGURATION |
The build configuration to use for packaging (Debug or Release) a library or executable | Release |
package-symbols-disabled / DOTNET_PACKAGE_SYMBOLS_DISABLED |
Disable creation of symbol packages (snupkg) for debugging | false |
With the build-args left unmodified the following report is generated based upon the collected compiler diagnostic and roslyn analyzer results in the dotnet-build.sarif files. The files are assembled to a single report file using sarif-converter:
| Report | Format | Usage |
|---|---|---|
reports/dotnet-build.gitlab-codequality.json |
Code Climate | GitLab integration |
reports/dotnet-*.env |
Gitlab dotenv format of runtime environment variables. | GitLab Integration |
Important notes:
- The job will install any required compiler to build for the runtime id of the current runner. This is only done for Ahead-of-Time and Ready-To-Run build targets.
- See Project / Solution Versioning for the build version selection.
dotnet-test job (running in the dotnet-build step)¶
This job performs unit testing of your .NET project using dotnet test. It runs as part of the build job and uses --no-build to avoid rebuilding.
It uses the following variables:
| Input / Variable | Description | Default value |
|---|---|---|
test-disabled / DOTNET_TEST_DISABLED |
Set to true to disable tests execution | false |
test-extra-args / DOTNET_TEST_EXTRA_ARGS |
Extra arguments used by the dotnet test command | none |
The following test and coverage reports are generated:
| Report | Format | Usage |
|---|---|---|
reports/dotnet-test.junit.xml |
JUnit test report(s) | GitLab integration |
reports/dotnet-test.xunitnet.xml |
xUnit.net v2 test report(s) | SonarQube integration |
reports/dotnet-test.cobertura.xml |
Cobertura coverage report | GitLab integration |
reports/dotnet-test.opencover.xml |
OpenCover coverage report | SonarQube integration |
Unit Tests and Code Coverage reports¶
In order to implement the best GitLab and SonarQube integration, the .NET template requires your project to use some specific tools and plugins:
- Unit Tests report for GitLab integration is generated with JUnit Test Logger
the
JunitXml.TestLogger package shall be added to your test projects (see NuGet)
- Unit Tests report for SonarQube integration is generated with Xunit Test Logger
the
XunitXml.TestLogger package shall be added to your test projects (see NuGet)
only required if you enabled SonarQube analysis
- Code Coverage is computed with Coverlet:
the
coverlet.msbuild package shall be added to your test projects (see NuGet)
dotnet-format job¶
This job performs code formatting validation using dotnet format of the whole repository. It verifies that the code follows the formatting rules defined in .editorconfig.
It uses the following variables:
| Input / Variable | Description | Default value |
|---|---|---|
format-disabled / DOTNET_FORMAT_DISABLED |
Set to true to disable the Dotnet Format code formatting check (enabled by default) | false |
The job will fail the pipeline if the project's .editorconfig styling rules are not respected.
dotnet-sonar job¶
This job performs SonarQube analysis.
It uses the following variable:
| Input / Variable | Description | Default value |
|---|---|---|
sonar-host-url / SONAR_HOST_URL |
SonarQube server url | none (disabled) |
sonar-project-key / SONAR_PROJECT_KEY |
SonarQube Project Key | none |
SONAR_TOKEN |
SonarQube authentication token (depends on your authentication method) | none |
sonar-quality-gate-enabled / SONAR_QUALITY_GATE_ENABLED |
Enables SonarQube Quality Gate verification. Uses sonar.qualitygate.wait parameter (see doc). |
false |
sonar-extra-args / DOTNET_SONAR_EXTRA_ARGS |
Extra arguments used by the SonarScanner | none |
sonar-exclusions / DOTNET_SONAR_EXCLUSIONS |
Files and directories to be excluded from analysis, as a comma-separated list of paths. See documentation for the format. | **/bin/**,**/obj/**,**/packages/**,**/*.g.cs,**/*.g.i.cs,**/*.designer.cs,**/*AssemblyInfo.cs,.sonarqube |
More info:
dotnet-sbom job¶
This job creates a Software Bill Of Materials (SBOM) for the project, libraries and executables using CycloneDX cdxgen.
Execution rules:
- Disabled if
sbom-disabledis set totrue - Runs always if
TBC_SBOM_MODEis set toalways - Runs on release, integration, or production branches when
TBC_SBOM_MODEis set toonrelease(default) - Skipped in all other cases
| Input / Variable | Description | Default value |
|---|---|---|
sbom-disabled / DOTNET_SBOM_DISABLED |
Set to true to disable SBOM generation | false |
sbom-image / DOTNET_SBOM_IMAGE |
The container image to use for SBOM generation using CycloneDX cdxgen | ghcr.io/cyclonedx/cdxgen:master |
sbom-supplier / DOTNET_SBOM_SUPPLIER |
The package supplier name to use in the generated SBOMs | {CI_PROJECT_NAMESPACE} |
sbom-opts / DOTNET_SBOM_OPTS |
Additional options to pass to the SBOM generation tool | --fail-on-error --evidence --deep |
The following SBOM reports are generated:
| Report | Format | Usage |
|---|---|---|
reports/dotnet-sbom-*.cyclonedx.json |
CycloneDX Json test report(s) | GitLab integration |
dotnet-publish job (Upload Artifacts)¶
This job takes the artifacts build in the dotnet-package step and publishes them depending upon the output type configured in the project files:
- Application binaries are zipped and always pushed to the generic GitLab Package Registry.
- Nuget packages get pushed to the configured NuGet target repo using dotnet nuget push. Default repo is the Gitlab Nuget Repo of the project.
See also .NET Build Configuration Best Practices to control the per project behavior of the pipeline template.
Note on Authentication: The job is designed to work seamlessly with the integrated GitLab Package Registry. By default, DOTNET_NUGET_API_KEY uses the CI_JOB_TOKEN, so you do not need to configure any secret variables. You only need to create and set DOTNET_NUGET_API_KEY as a project CI/CD variable if you override nuget-repo to publish to an external repository (like nuget.org or another private registry) that requires a specific API key.
Execution rules:
- Runs automatically on tagged release pipelines with non-prerelease versions.
- Runs manually on any other pipeline instance.
- Automatically configured for GitLab Package Registry using CI_JOB_TOKEN
- Publishes both .nupkg and .snupkg (symbol) packages if symbol package publication is activated.
- Publishes executable binaries for configured frameworks and the runner runtime.
Important notes:
- Automatically configures GitLab Package Registry as the NuGet target feed.
- Uses CI_JOB_TOKEN for authentication (no manual configuration needed to use with the GitLab package repository)
- See Project / Solution Versioning for the build version selection.
Example artifacts published:
- Libraries:
MyLibrary.1.0.0.nupkg,MyLibrary.1.0.0.snupkg - Applications:
MyApp-1.0.0.zipcontaining application binaries
| Input / Variable | Description | Default value |
|---|---|---|
publish-enabled / DOTNET_PUBLISH_ENABLED |
Set to true to enable publishing of artifact to a NuGet feed. | false |
nuget-repo / DOTNET_NUGET_REPO |
Target NuGet package repository url to publish the packages to. (when overriding this, please set DOTNET_NUGET_API_KEY at project CI variable to set the nuget api-key used by dotnet nuget push) defaults to GitLab project's packages repository |
${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/nuget/index.json |
DOTNET_NUGET_API_KEY |
NuGet Repo API token used for authentication. | ${CI_JOB_TOKEN} |
nuget-symbol-repo / DOTNET_NUGET_SYMBOL_REPO |
Target NuGet package symbol repository url to publish the symbol packages to. (when overriding this, please set DOTNET_NUGET_SYMBOL_API_KEY at project CI variable to set the nuget symbol api-key used by dotnet nuget push) defaults to GitLab project's packages repository |
${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/nuget/index.json |
DOTNET_NUGET_SYMBOL_API_KEY |
NuGet Symbol Repo API token used for authentication. | ${CI_JOB_TOKEN} |
Secrets management¶
Here are some advices about your secrets (variables marked with a ):
- Manage them as project or group CI/CD variables:
- In case a secret contains characters that prevent it from being masked,
simply define its value as the Base64 encoded value prefixed with
@b64@: it will then be possible to mask it and the template will automatically decode it prior to using it. - Don't forget to escape special characters (e.g.:
$->$$).
.NET Build Configuration Best Practices¶
The following best practices below were important enough during the implementation of the pipeline to highlight them here again in order that this pipeline template works as designed and you can get the most out of this pipeline template with the lowest amount of configuration.
Global Build Configuration¶
- Reproducible Builds: Pin down the versions of the SDK and package dependencies.
- ℹ️ Use
global.jsonto pin the SDK and other global environmental settings (see dotnet workload sets). - ℹ️ Use
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>in yourDirectories.Build.propsand commit thepackages.lock.jsonfile(s). - You can also use Paket to manage package dependencies. But be sure to commit the tool manifest and
.paketdirectory.
- ℹ️ Use
- Use SDK-style .NET dependency managment using
<PackageReference>for NuGet dependencies.
ℹ️ Legacy packages.config has to be migrated before using this CI/CD template.
3. Use solution files when working with more than one project in order to manage build dependencies and build scopes. Only with solution files the project dependencies can be resolved and the compilation order be determined.
ℹ️ Check dotnet sln for managing the solution file from the CLI.
4. When working with multiple solution file in the same directory, then use the build-file/DOTNET_BUILD_FILE parameter to select the solution to build.
ℹ️ See the working with multiple solutions and projects chapter below.
5. Use semantic versioning (e.g., 1.3.1) in the Version configuration. The value will be detected and updated / overwritten by the template depending upon the context. (default: 1.0.0)
6. Use Directory.Build.props to manage common project settings (e.g. nuget package info).
Project Configuration¶
It is important that each project file contains the necessary configuration so the pipeline and msbuild can decide to do a combination of the following actions:
-
dotnet buildto compile the project<TargetFramework>/<TargetFrameworks>in the project file only to those .NET versions / .netstandards that you actually need.
ℹ️ The pipeline will build each and every framework listed! The number of TFMs directly impacts the pipeline runtime. - For public libraries use the appropriate version of
.netstandardfor maximum compatibility and to reduce the amount of deliverables to create.ℹ️ For internal libraries choose the applicable target TFM(s) to enable the usage of modern runtime features. 2.
dotnet testto compile and execute the tests - Set<IsTestProject>true</IsTestProject>in the configuration of test projects. - This implies<IsPackable>false</IsPackable>and<IsPublishable>false</IsPublishable>. 3.dotnet packto build the nuget package - Default action for library projects<OutputType>Library</OutputType>. - Disable this with<IsPackable>false</IsPackable>. 4.dotnet publishto build the executable - Default action for executable projects<OutputType>Exe</OutputType>or<OutputType>appcontainerexe</OutputType>- Disable this with<IsPublishable>false</IsPublishable>. 5.dotnet nuget pushto push the package in the nuget feed. - Ifpublish-enabled/DONET_PUBLISH_ENABLEDis activated, then every packable project will be pushed to configured nuget target repo. 6. Archive and deliver executable to the GitLab generic artifact repo. - Ifpublish-enabled/DONET_PUBLISH_ENABLEDis activated, then every publishable project will compressed into a zip archive and pushed to GitLab generic artifact repo.
Optimizing executables projects¶
- Ahead-of-Time compilation (
<PublishAot>true</PublishAot>) and Ready-2-Run compilation (<PublishReadyToRun>true</PublishReadyToRun>) both require setting the runtime target id (RID) via<RuntimeIdentifiers>. The template will only publish platform specific executable if the pipeline runner agent's RID is configured in the runtime list configured in<RuntimeIdentifiers>.
ℹ️ Currently this pipeline template can only install the necessary compilers for the RIDs matching the pipeline host. Cross architecture and cross OS compilation is currently not possible. (Implementation Ideas and Merge Requests are welcome!)
2. Use <PublishSelfContained>true</PublishSelfContained> instead of <SelfContained>. This bundles the .NET runtime in the deliverable allowing it to run on any machine, even those without the .NET runtime installed.
3. Reduce deliverable size by:
- Setting <SatelliteResourceLanguages> only to what you need.
- Using <OptimizationPreference>Size</OptimizationPreference> to build an artifact optimized for size.