Computers, Code, and Stuff

An Example Build System

Created: 2020-03-18

Recently, a friend asked me to help with a project, which He and his client couldn't get to build on Windows. I got it to build, but neither of them were able to build using my build script because their environments were different from mine. So, the client asked me to set up a build server.

After setting a server up, I decided to clean the instructions up for general use, and detail how I got it done. Maybe it will be usefil to somone. A few minor points have been changed. I have multiple toolchains setup on my own system, and the client needed to use an additional collection of libraries which I don't document here.

These instructions are for setting up a development and building system on Windows 10 (1909), and a build system on Windows Server 2019. Some steps are different between the two systems. The differences are noted when they come up.

The Initial Setup

If you don't have a fresh system, It's important to clean up your environment variables, especially the PATH environment variable. Many development tools will add themselves to PATH, modify other environment variables, or add new ones. This makes it easier to use them, but comes at the price that they may interfere with each other. This happened with my friend. CMake would find another toolchain he had installed instead of the Visual C++ toolchain we were trying to use. So, if a tool or library makes a change to the environment variables on the system when I install it, I change the variables back or remove them, as appropriate, and put the changes into a PowerShell script that I can dot-source before a build. An example of such a script is below.

If you need to clean up your environment variables, open File Explorer, right click on This PC, click on Advanced System Settings, select the Advanced tab, and click the Environment Variables button. You can use this window to modify your environment variables. There are two sets of environment variables, the upper section shows environment variable values that apply to your user account, and the lower section shows values that apply to the whole system. If you remove or change anything, make a note of it and consider adding it to a PowerShell script for later use. You shouldn't have any of these variables in either ste of values:

Your PATH variable also shouldn't have paths for any development tools. Ideally, it should only have the default paths it had when Windows was installed. Be careful modifying any other values.

My Foundational Tools

These are the tools I find absolutely essential to develop software on Windows using C++ (and C). The version numbers are the ones I used as of writing these instructions, but differences between versions are minor enough that these instructions should work for later versions.

PowerShell (v7.0.0)
I use PowerShell 7, instead of the built-in PowerShell 5.1 for a couple of reasons. PowerShell 7 has features not present in PowerShell 5.1, and, more importantly, PowerShell 7 also runs on macOS and Linux, so I can use it to automate builds on all three systems. When installing PowerShell, I decline to add PowerShell to the PATH environment variable.
Microsoft Visual Studio (2019)
Visual C++ (v14.25.28610)
Windows 10 SDK (v10.0.18362.0)
Microsoft offers an installer and update manager for Visual Studio and various development tools. The installer is available for three SKUs, Community, Professional, and Enterprise, and also without the IDE components, called Build Tools for Visual Studio 2019. ThemBuild Tools version is avaiable under Tools for Visual Studio under All Downloads on the download page.) I use Visual Studio Community edition on my Windows 10 System, and the Build Tools version on the Server. When installing Visual Studio, make sure to install the Windows 10 SDK, and the MSVC v142 VS 2019 C++ x64/x86 build tools. If you install versions different than those listed here, make sure to adjust the script below to match what you installed. While the Visual Studio Installer offers packages for CMake and Git, I download and maintain these separately.
CMake (v3.16.5)
Since I build cross platform, I use CMake to generate my build systems on every system. When I install CMake, I uncheck the option to add CMake to the PATH.
Git (v 2.25.0)
Git is an essential tool for any programmer or developer. When I Install Git, I select Run Git from Git Bash only, and Enable Symbolic Links.

Optional Tools

These tools aren't essential for every project or can be replaced by other tools.

Ninja 1.10.0
Ninja is a build system that functions like make. It's designed to be fast, and for its input files to be generated by a system like CMake rather than by hand. The only reason I use Ninja specifically is because it's available on macOS, Linux, and Windows, so I can use the same build scripts on all three systems. Instead of Ninja, you can use nmake, which comes with Visual C++, or MSBuild which is available through the Visual Studio installer. Ninja is released as a single file, ninja.exe in a zip file. To install Ninja, you will have to download the zip file, make a directory called C:\Program Files\Ninja\bin and copy the ninja.exe file within the zip file to this directory.
WiX Toolset 3.11.2
The WiX Toolset is a scriptable Microsoft Installer (.msi) file generator. CPack, a package-generation utility inculded with CMake, can use WiX as a backend to generate installer files for your projects. Other options for generating installers and packages are available, such as the Nullsoft Scriptable Install System, or for-pay products like InstallShield. Note that WiX adds a WIX environment variable when it installs, so be sure to clean that up.
Qt 5.14.1
Qt is a cross-platform application framework. It provides its own APIs for interacting with GUIs, networks, databases, the filesystem, etc. Instead of Qt, you can write programs using Windows' own APIs. When Installing Qt, make sure to select the MSVC 2017 64-bit option under 5.14.1. This version is compatible with Visual Studio 2019.

System Configuration

Windows 10 has a security measure built in to prevent malicious code from running on a system. So, on Windows 10, open a PowerShell window using the newer PowerShell mentioned above, and run the following command:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

This will allow you to run PowerShell scripts that are signed or that you've written locally. This setting is the default on Windows Server 2019.

Windows Server 2019 should have one or more additional users, separate from the Administrator account, for building software. Whether you use a single dedicated user or setup separate users for each member of your team is up to you.

The Configuration Script

Finally, you will need a script to setup the build environment. The script below is adapted from my own scripts. Be sure to modify it to set $Cfg["VsSku"] to "BuildTools" if you used the Build Tools installer for Visual Studio.

You will have to dot-source the script before you can build. So, if you save the script as BuildConf.ps1 in your user directory, you would run it like so:

. ~/BuildConf.ps1

If, for some reason, PowerShell gives you an error saying that execution policy doesn't allow running the script, run the following:

Unblock-File ~/BuildConf.ps1


$Cfg = @{}
$Cfg['WinDir'] = "C:\Windows"
$Cfg['Prog64Dir'] = 'C:\Program Files'
$Cfg['Prog32Dir'] = 'C:\Program Files (x86)'

$Cfg['MsvcArch'] ="x64"
$Cfg['QtBuild'] = "msvc2017_64"

# Visual C++ Toolchain Versions

# Visual Studio Paths
$Cfg["KitBase"]="$($Cfg.Prog32Dir)\Windows Kits\10"
$Cfg["VsBase"]="$($Cfg.Prog32Dir)\Microsoft Visual Studio"

# Qt Values
$Cfg['QtBaseDir'] = 'C:\Qt'
$Cfg['QtVersion'] = '5.14.1'
$Cfg['QtDir'] = "$($Cfg.QtBaseDir)\$($Cfg.QtVersion)\$($Cfg.QtBuild)"

# Locations of Other Tools
$Cfg['GitDir'] = "$($Cfg.Prog64Dir)\Git"
$Cfg['CmakeDir'] = "$($Cfg.Prog64Dir)\CMake"
$Cfg['NinjaDir'] = "$($Cfg.Prog64Dir)\Ninja"
$Cfg['WixDir'] = "$($Cfg.Prog32Dir)\WiX Toolset v3.11"


if(![string]::IsNullOrEmpty($ENV:PATH) -and !($ENV:PATH -match '.+?;$'))
	$ENV:PATH += ";"

# Setup Visual C++
$ENV:PATH += "$($Cfg.VcDir)\bin\HostX64\$($Cfg.MsvcArch);"
$ENV:PATH += "$($Cfg.KitBase)\bin\$($Cfg.WinSdkVer)\$($Cfg.MsvcArch);"

$ENV:INCLUDE += "$($Cfg.VcDir)\include;"
$ENV:INCLUDE += "$($Cfg.KitBase)\include\$($Cfg.WinSdkVer)\ucrt;"
$ENV:INCLUDE += "$($Cfg.KitBase)\include\$($Cfg.WinSdkVer)\um;"
$ENV:INCLUDE += "$($Cfg.KitBase)\include\$($Cfg.WinSdkVer)\shared;"
$ENV:INCLUDE += "$($Cfg.KitBase)\include\$($Cfg.WinSdkVer)\winrt;"
$ENV:INCLUDE += "$($Cfg.KitBase)\include\$($Cfg.WinSdkVer)\cppwinrt;"

$ENV:LIB += "$($Cfg.VcDir)\lib\$($Cfg.MsvcArch);"
$ENV:LIB += "$($Cfg.KitBase)\lib\$($Cfg.WinSdkVer)\ucrt\$($Cfg.MsvcArch);"
$ENV:LIB += "$($Cfg.KitBase)\lib\$($Cfg.WinSdkVer)\um\$($Cfg.MsvcArch);"

# Setup Qt
$ENV:PATH += "$($Cfg.QtDir)\bin;"
$ENV:INCLUDE += "$($Cfg.QtDir)\include;"
$ENV:LIB += "$($Cfg.QtDir)\lib;"

# Setup Other Tools
$ENV:PATH += "$($Cfg.GitDir)\cmd;"
$ENV:PATH += "$($Cfg.CmakeDir)\bin;"
$ENV:PATH += "$($Cfg.NinjaDir)\bin;"
$ENV:PATH += "$($Cfg.WixDir)\bin;"
$Env:WIX = "$($Cfg.WixDir)\"


At this point, I would suggest writing a simple project and testing that it works. How to write C++ and CMake code, and how to use CMake and Qt are beyond the scope of this document, however.