Windows Docker Container for MSVC Builds with Conan in Gitlab

Setting up a good CI/CD pipeline can be a bit painful and lately I was busy with Windows Docker in Gitlab. Fortunately, a colleague did a lot of work in advance and shared his knowledge with me. Thanks for that.

So this article is a guide to create a Windows Docker CI/CD in Gitlab to run MSVC builds with Conan.

Host Machine

First of all you'll need a Windows host machine were docker is installed. We are using a Windows Sever (with pre-installed Docker) from Amazon (find out more here). So if you're about to set this up professionally, the guys from DevOps probably know how to purchase such a machine. Otherwise install Docker on your Windows system and enable Windows Containers. You can't run Linux and Windows Containers simultaneously.

 

Gitlab Runner

Once the Windows machine is running, download and start Gitlab Runner. Find the binary here on Gitlab under number two in the installation section. Select a directory where you place the runner. To install and start it, run these two commands from the commandline (rename the executable or use the original name):

.\gitlab-runner.exe install
.\gitlab-runner.exe start
 

Docker Image

Let's create the Windows Docker image, which supports MSVC builds. I started with the Dockerfile from Microsoft's Github repo. I did some slight changes and installed some more tools (CMake, Conan, Python, etc.) with chocolatey . The final Dockerfile to create the container is now this:

# Copyright (C) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license. See LICENSE.txt in the project root for license information.

FROM mcr.microsoft.com/windows/servercore:ltsc2022

# Reset the shell.
SHELL ["cmd", "/S", "/C"]

# Set up environment to collect install errors.
COPY Install.cmd C:\TEMP\
ADD https://aka.ms/vscollect.exe C:\TEMP\collect.exe

# Install Node.js LTS
ADD https://nodejs.org/dist/v8.11.3/node-v8.11.3-x64.msi C:\TEMP\node-install.msi
RUN start /wait msiexec.exe /i C:\TEMP\node-install.msi /l*vx "%TEMP%\MSI-node-install.log" /qn ADDLOCAL=ALL

# Download channel for fixed install.
ARG CHANNEL_URL=https://aka.ms/vs/17/release/channel
ADD ${CHANNEL_URL} C:\TEMP\VisualStudio.chman

# Download and install Build Tools for Visual Studio 2022 for native desktop workload.
ADD https://aka.ms/vs/17/release/vs_buildtools.exe C:\TEMP\vs_buildtools.exe
RUN C:\TEMP\Install.cmd C:\TEMP\vs_buildtools.exe --quiet --wait --norestart --nocache `
    --channelUri C:\TEMP\VisualStudio.chman `
    --installChannelUri C:\TEMP\VisualStudio.chman `
    --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended`
    --installPath C:\BuildTools

# ========== setup chocolatey package manager and tools ==========
RUN powershell.exe -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SETX PATH "%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" 

RUN choco install -y git.install
RUN choco install -y python3

RUN py -m pip install cmake
RUN py -m pip install conan

# we'll mount a volume from the host where the conan packages will be installed
ENV CONAN_USER_HOME="c:/cache"
ENV CONAN_USER_HOME_SHORT="c:/cache/conan_shortpaths"
RUN conan profile new default --detect
# adapt msvc runtime if you need dynamic linking or debug symbols
RUN conan profile update settings.compiler.runtime=MT default

# create the docker volume
VOLUME ["c:/cache"] 

ENTRYPOINT ["C:\\BuildTools\\Common7\\Tools\\VsDevCmd.bat", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"]

On my first attempt I ran a build to test the setup and everything went well. After building a project where some dependencies were downloaded and built, the build took around 45 minutes. The most time consuming dependecy was boost there. It basically makes sense to store the dependencies on the host machine. Therefore we'll mount a drive from the host to avoid rebuilding them everytime.

Todo so, I added the two environment variables for conan and a mounted c:/cache.

Let's build Docker image on your host machine and push it to the dockerhub. If your image is not available on Docker this didn't work for me.

# use docker login before... 
# you can also run the container if you want to try it out first

docker build -t your-container-name:latest -m 2GB .
docker tag your-container-name:latest your-docker-account/your-container-name
docker push your-docker-account/your-container-name

Note: I had some strange behaviour with boost. For whatever reason the build went well on the pipeline, but running the same command manually in the docker, boost build failed.

Docker Helper Image

Ultimately, to setup your CI/CD you'll need a Docker Helper Image. This will then start your container and prepares the environment (downloading the git repo, and probably some other stuff). The helper image also has to be available on the host machine, I used this one here: kdeorg/gitlab-windows-runner-helper

 

Register The Gitlab Runner

Now we have everything we need and we can register the runner on your Gitlab. Navigate to your Gitlab repository or group. In the menu under CI/CD -> Runners you'll find a button Register a runner. Copy your registration token create a powershell script (or use the command as is):

& .\gitlab-runner-windows-amd64.exe register `
    --non-interactive `
    --url "https://your-gitlab-url.com/" `
    --registration-token "your registration token" `
    --executor "docker-windows" `
    --request-concurrency 2 `
    --docker-image="your-created-docker-image:latest" `
    --docker-helper-image "kdeorg/gitlab-windows-runner-helper:servercoreltsc2022" `
    --docker-volumes "c:\your\host-dir\to\mount:c:\cache" `
    --cache-dir "c:\cache" `
    --docker-cpus 1 `
    --docker-memory 4g `
    --shell "powershell" `
    --description "AWS Windows Docker MSVC Bulids" `
    --tag-list "some tags" `
    --run-untagged="false" `
    --locked="true" `
    --access-level="not_protected";

--docker-volumes: the syntax here is <host directory>:<direcotry in container>
--cache-dir: Tell Gitlab which directory we want to cache
--docker-image: The docker image we'll use in our build
--docker-helper-image: The docker image which prepares the build

 

Accessing Other Private Git Repos

Since we're running on a private git repo and we manage internal dependencies also with conan, it took me a while to find something generic from the command line. Since Windows manages all credentials with the credential manager, I added the following line in my gitlab script and it works just fine:

- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@your.gitlab.com/".insteadOf "https://your.gitlab.com/"

Of course the gitlab environment needs to provide the token. This also works with the newer oauth2 token.

 

And We're Done

And with this setup my Windows Docker MSVC builds are just running fine. Thanks again to my colleague who helped me setting this up.

That's it for now.

Best Thomas

Previous
Previous

[Typescript/C++]: Coroutines: What are they and what we have in C++20

Next
Next

[C++] A Boost Asio Server-Client Example