The other answers, while informative, miss a few crucial bits of info:
- If you are targetting .net core 3.1, .net 5, .net 6 or any newer version then you can build and run your app on linux
- If you are targetting .net framework (4.8 or below) then you have to build and run your code on a windows machine.
Now Gitlab runs build jobs using runners (aka build agents), that in turn use executors to specifiy the environment in which the build process happens. Runners can run on the Gitlab infrastructure (Saas runners), or can be installed on premise. There are several types of executors: docker, shell, custom, ...
Most of the gitlab build scripts that can be found around the internet assume a saas runner with a docker executor running on linux, which is based on a specific docker image. This won't work if you want to build your app on windows. For this, either install your own executor, as several other answers instruct to do, or use a windows saas runner which, although in beta, works fine even for production (we have been doing this for months without trouble). While installing your own runner can be useful, e.g. for debugging purposes, I find this defeats the whole purpose of building your software on a hosted cicd platform (github actions, gitlab ci, ...). For debugging, I prefer to base my build script on shell commands which can be run on your local dev box, which minimizes trial and error and makes installing your own runner superfluous.
To get started with a windows runner, see this exemple script. One or two gotchas that I went through:
- You cannot choose the VM image of the build machine, it is managed by Gitlab and its content is not documented. This leaves you vulnerable to breaking changes if they decide to change their image.
- Strangely, at the time of writing, the .net framework 4.8 developer pack is not installed on the windows executor image, but luckily chocolatey is, so if you target net48 you must install it in your before_script section.
- The provisionning of the build machine is somewhat slower than a linux maching running docker, our builds average 15mn which we find acceptable.
That being said, here is a trimmed example based on our build scripts:
variables:
MSBUILD_EXE: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\bin\msbuild.exe'
UPDATE_VERSION: '.\path\to\Update-Version.ps1'
APP_FOLDER: '.\path\to\Artifacts\_PublishedWebsites\MyApp\'
.windows_build_base:
tags:
- shared-windows
- windows
- windows-1809
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- echo ".NET versions already installed"
- Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
| Where {$_.DisplayName -like '*.NET*' -and $_.DisplayVersion -like '4.*'}
| Select-Object DisplayName, DisplayVersion
- echo "Installing .NET 4.8 Developer Pack"
- choco install netfx-4.8-devpack -y
stages:
- build
- publish
build:
only:
- prod
- test
extends:
- .windows_build_base
stage: build
script:
- nuget restore
- '& "${UPDATE_VERSION}" -srcDir . | Set-Variable -Name VERSION' # capture output of UPDATE_PRODUCTINFO script in variable VERSION
- echo "VERSION=${VERSION}"
- Add-Content -Path app_version.env -Value "VERSION=${VERSION}" # store value of VERSION in dotenv file to be passed to dependent stages, as per this workaround https://gitlab.com/gitlab-org/gitlab/-/issues/212629#note_430278657
- Get-Content app_version.env
- '& "${MSBUILD_EXE}" /t:solution_path\to\MyApp /p:Configuration=Release /clp:Summary /verbosity:Minimal /p:OutDir=.\Artifacts'
artifacts:
expire_in: 1 day
paths:
- '${APP_FOLDER}'
reports:
dotenv: app_version.env
publish:
only:
- prod
- test
image:
name: octopusdeploy/octo
entrypoint: [""]
stage: publish
dependencies:
- build
script:
- echo "VERSION=$VERSION"
- octo pack --id="MyApp" --format="zip" --version="$VERSION" --basePath="path/to/Artifacts/_PublishedWebsites/MyApp" --outFolder="/output/"
- octo push --overwrite-mode="OverwriteExisting" --server="https://mycompany.octopus.app" --space "MyOctoSpace" --package="/output/MyApp.$VERSION.zip"
This script:
- Installs .net 4.8 dev pack
- Restores nuget packages
- Manually updates the software version and passes it between the build and publish stages
- Builds the app
- Packs and pushes it to octopus deploy