For quite some time now I’ve been working inside teams who wereusing Visual Studio to build complex C++ projects.

Because I’ve often been the “buildfarm guy” and because I don’t like GUIsthat much, I had to find ways to build Visual Studio projects from the commandline.

Things mac download. This is the story of everything I’ve tried.

Quick note before we begin: throughout this article, I will be using VisualStudio 2015 on Windows 10 to build the source code of CMake itself. If’s a niceproject for a case study, since it’s neither too big nor too small, and has nodependencies to worry about (and of course, it uses CMake to build itself :)

Using CMake to generate Visual Studio projects #

CMake works by parsing code in CMakeLists.txt files, and then generatingcode that will be used by an other program that would perform the builditself.

Other methods of access include the options under Tools - Command Line in the main menu. Starting a git command prompt through Team Explorer was removed Visual Studio 16.8. A functionality moved message is displayed instead. As of 16.9.1, Visual Studio does not check that a system git installation is present (in C: Program Files Git by default). Devenv allows you to build your solution or project using command line. It also allows related multiple operations such as clean solution, rebuild solution or deploy solution etc. To execute commands, you need to open Visual Studio command prompt available inside visual studio tools under all programs of start menu. Visual Studio 2019 includes two command-line shells for developers: Visual Studio Developer Command Prompt - A standard command prompt with certain environment variables set to make using command-line developer tools easier. Available since Visual Studio 2015. Visual Studio Developer PowerShell - More powerful than a command prompt. See full list on docs.microsoft.com.

When you use CMake, you must specify a generator.On Windows, the default generator will be the most recent Visual Studio found,and after running CMake, you’ll get a .sln file you can open in VisualStudio to edit, build, and debug the project.

So my task was to find a way to build those .sln files from the command line.

Using devenv #

Visual

The most obvious way I found was to use a tool called devenv. In fact, that’sthe probably the answer you’ll find if you look up “Building Visual Studioprojects from the command line” on an internet search engine. You’ll also findplaces where they suggest you use MSBuild.exe.

But, bad luck, if you try to run devenv directly from cmd.exe, you’ll get thefamous error message:

The trick is to use one of the “Command Prompt” you’ll find in the start menu:

I started with the “Developer Command Prompt”:

Visual Studio opened. Hum, that’s not what I wanted. Anti ddos guardian 3.1 crack. Turns out, if you makeany mistake in the command line prompt, Visual Studio will open.

Visual studio command line debugger

The correct way is to add the /build switch:

The output is quite nice:

Using MSBuild #

You can also try using MSBuild.exe, but the output is a bit uglier.(But you get more info, such as the time it took to compile a project, the fullcommand line used, and the number of warnings/errors):

Using CMake to build #

OK, so now I knew how to build Visual Studio projects from command line.

We had a pretty big C++ code base, that we wanted to build on Linux, macOS and Windows.

We were using Jenkins to do continuous integration, so we had to writebuild scripts that would run on the nodes as soon as any developer would make amerge request to make sure the proposed changes will build on all platforms.

On Linux and macOS, the default generator is “Unix Makefiles”, so the code wasstraightforward:

On Windows, we used Batch files:

You may wonder where the weird @call '%VS140COMNTOOLS%VsDevCmd.bat' line comes from.

First, if you go to C:ProgramDataMicrosoftWindowsStart MenuProgramsVisual Studio 2015Visual Studio Tools, you can right-click on the “Developer CommandPrompt” shortcut and open the “Properties” window. There you’ll find that thetarget is:cmd.exe /k 'C:Program Files (x86)Microsoft Visual Studio 14.0Common7ToolsVsDevCmd.bat'

Second, if you are lucky, someone1 will tell you that with any version of VisualStudio, an environment variable called VS<version>COMNTOOLS is set, where<version> is the 2 or 3 digits version number of your Visual Studio install.

Here is a table if you don’t know what I mean:

Visual

Thus, you can avoid hard-coding the Visual Studio installation path, and use theVS140COMNTOOLS variable instead. (You still need to hard-code Visual Studioversion, though).

See full list on code.visualstudio.com

So what the command does is concatenate the value of the VS140COMNTOOLSenvironment variable with the basename of the prompt file (VsDevCmd.bat), and run@call on it.

Using Python #

But, as time went by, we wanted to rewrite all the build scripts in Python,so that we could factorize some of the code.(For instance, running git pull to update the sources before building)

On Linux and macOS it was easy:

But on Windows, things were a bit trickier. How were we going to implementthe @call pathtobat_file in Python?

setuptools to the rescue! #

I discovered that setuptools – the module used by Python to run the setup.pyfiles – was able to build things with Visual Studio, without having to use theVisual Studio command prompts.

So I looked at the implementation, and found a solution:

The idea is to run a batch script (that’s why we are using shell=True) that will:

  • Call the .bat file we need
  • Run the built-in set command and parse its output
  • Returns the whole environment in a Python dict.

Indeed, there are several ways to use set on cmd.exe:

  • To set an environment variable: set FOO=BAR
  • To unset an environment variable: set FOO=
  • To see all the environment variable whose name start with prefix: set <prefix>
  • To dump all the environment variables: set

We parse the output of set to find the three variables we need (they areall lists of semi-colon separated paths)

  • PATH: to find the required executables (the devenv command)
  • LIB: where the compiler will look for .lib files
  • INCLUDE: where the compiler will look for headers files.

By the way, if you are wondering why the function is called source_bat, it’sbecause on Unix, to execute a bash script and have your environment updated, youneed to use the source built-in, or, on some other shells, the . command,(but I digress).

Building the .sln file #

There was an other problem, though. On Linux and macOS, the command to build isalways make.

But on Windows, I had to carefully craft the devenv command, and this meantspecifying the path to the .sln file.

At first, I only had a few bad solutions:

  • Hard-code the name of the .sln file
  • Parse the top CMakeLists to find the project() call2
  • List the contents of the build directory, and hope they’ll will be only onefile with the .sln extension.

Luckily, by running cmake --help I discovered there was --build switch Icould use to abstract the command line to run for the project to be built.

So the code looked like:

This meant I could run cmake --build anywhere, without having to deal withthose nasty .bat files.

Using multiple CPUs at once #

By default, Visual Studio projects get built using all the CPU resources, butit’s not the case for the make command.

So the code had to be patched again to have make use all available CPUS:

(The -- argument is here to separate arguments parsed by cmake binary fromthe one sent to the underlying build command. It’s a common practice for command-line tools)

Performance issues #

So we had our Jenkins nodes running Python scripts to build the same source codeon Linux, macOS, and Windows, and everything was fine, except that the buildswould take much longer on Windows.

At first I thought, “Well,it’s a known fact that running executables andaccessing the file system will always be slower on Windows, andthere’s nothing we can do about it”.

But members of my team kept complaining about the long build times, and I wasnot feeling good about it: as someone said once, “When doing continuousintegration, computers should be waiting for humans, and not the other wayaround”.

So, I looked for solutions to improve performance.

Using NMake #

If you look at the size of the files generated by CMake when using VisualStudio, you realize it will not be easy to have good performance.

For instance, to build CMake you have a .sln file with 842 lines, whichreferences 115 .vcxproj files.

Looking at the contents of the files, it’s no wonder parsing them takesquite some time.

Also, if you look at CPU usage during build, you can see you are far from usingall the CPU power:

So I tried to find a CMake generator that would generate simpler code.

During my research, I found out that Microsoft had their own implementation ofthe Make program called NMake, so I decided to use the NMake Makefilegenerator.

This time I’ll be using cl.exe, link.exe and their friends directly.

See Full List On Docs.microsoft.com

I tried using the MSBuild command prompt, but I got:

Here, CMake cannot find cl.exe because it’s not in the %PATH%.And indeed, if you try to run cl.exe from the MSBuild Command Prompt, you’ll getthe same “cl.exe is not recognized …” error.

So I tried using the ‘Developer Command Prompt' I already used before back whenI was runnig devenv by hand:

Huzzah, the compiler is found!

You may notice that the path to cl.exe is just VCbincl.exe. (There areother folders in VCbin, but here we are using the default, 32 bits version)

Also, I was pleased to find out that only 30 or so Makefiles files were generated.

So the next step was:

I was quite happy to see the I got the same nice output (with thepercentage of progress) as on Linux and macOS.

But then I discovered that only one CPU was used during compilation.

And running nmake /? gave nothing being able to run multiple jobs in parallel.

Frack!

Using JOM #

Looking at cmake --help output again I discovered there was yet another generatorcalled “NMake Makefiles JOM”

Jom is a tool made by Qt folks. Banned from equestria games. It’s are-implementation of the nmake command, but with support for multiple jobs.The command line switch to build with multiple CPU is also called -j, which isnice because it meant the build script code would get simpler.

That gave quite some good results, but the build was still slower than on Linuxand macOS.

In order to investigate, I decided to keep the Windows resource monitor openedduring a build with JOM:

You can see there’s a drop in CPU usage during build. From what I understand,it happens during linking.

Using Ninja #

Finally, circa 2010, Ninja came out.

As soon as I read the description of the project: “a small build system with afocus on speed”, and the fact there was an experimental support for it inCMake, I was dying to try it out.

And it fact, it gave great results! For the first time in years, I finally hadthe same build times on Windows than on Linux, and the CPU usage was a nicesteady line around 100% for all cores:

I also got the terse output that gave Ninja its name. 3

A story of cross-compiling #

After several years of using the CMake + Ninja combination, I gotan error message during one of our CI builds:

Googling the error lead to:https://support.microsoft.com/en-us/help/2891057/linker-fatal-error-lnk1102-out-of-memory

So I tried to follow the advice in the “Resolution” section and took a closer look atthe list of command line prompts in the start menu:

Below the two prompts on top I already tried, there was a few entries, all ofthem shortcuts to the same .bat file, but with different arguments:

Aha! So, all I have to do was to call the vcvarsall.bat file with the correct arguments, which wasconfirmed when I took a look at the contents of the .bat file:

So the Python code was patched again:

And CMake output was:

Notice the amd64_x86 subfolder.

Code signing breakage #

Everything was OK for a while, until we decided we wanted to sign the executables before shipping them.

It seemed the most obvious way was to use signtool.exe, so I proceeded toinstall the Windows Driver Kit,as instructed on the Windows Dev Center4

But then all our builds started failing with:

Visual Studio Command Line 2017

I looked for a solution but all I got was people telling me to set INCLUDE and LIB by hand.

Well, I already had Python code computing the environment variables, so the fix was:

Note how we have x64 instead of amd64 here :)

Conclusion #

Well, that’s all I’ve got for today.

Building Visual Studio projects with CMake and Ninja works quite well if youhave build scripts in Python, providing you are willing to run .bat scripts,and carefully apply changes to the environment variables.

Soon I’ll try and see how things go with Visual Studio 2017, maybe things willget easier, who knows?

Until then, may the Build be with you!

Update: I’ve decided to cross-post this article ondev.to.Let’s see how it goes!

  1. David, if you read this, thank you so much! ↩︎

  2. More info in the CMake documentation↩︎

  3. It’s just one line of output that disappears quickly after the build is done, do you get it? ↩︎

  4. Turned out I somehow missed this page telling me signtool.exe was already installed when I set up Visual Studio on the node … ↩︎

Thanks for reading this far :)

I'd love to hear what you have to say, so please feel free to leave a comment below, or read the contact page for more ways to get in touch with me.

Note that to get notified when new articles are published, you can either:

  • Subscribe to the RSS feed or the newsletter
  • Or follow me on Mastodon, dev.to, or twitter.

Cheers!