CMake in Visual Studio 2017

CMake in Visual Studio 2017

CMake in Visual Studio 2017

Visual Studio 2017 introduces the ability to open CMake projects directly in the Visual Studio development environment without the need to generate any project files first. In this tutorial, you will create a simple project that uses CMake to define the project configuration. You will also create several build configurations for the application. You will also create launch configurations to determine how the application is executed for debugging the application.

Introduction

CMake is an open-source, cross-platform suite of tools that provide the ability to build, test, and package software. CMake encourages and facilitates the development of open-source, and cross-platform software because the CMake definition language is not designed for a particular platform or operating system. The CMake definition language is also not primarily concerned with the development environment (i.e. Visual Studio) that is be used by the software engineer. This makes developing cross-platform software easier.

There are several advantages to using CMake with your own software projects. In recent years there has been a lot of adoption of distributed source control systems such as Git in the open-source development community. Many of these open-source projects are using CMake to define the project configuration. Integrating these open-source projects in your own software becomes easier if you are already familiar with CMake. Another advantage of using CMake is that other open-source developers will be more inclined to work with your project if they are not required to use a particular development environment tool.

If you are new to open-source, cross-platform development, you may be resistant to learning CMake for the first time. There are several benefits to learning CMake.

  1. You will be able to work with other open-source libraries that use CMake
  2. If you know CMake, project management actually becomes easier
  3. You will be able to share your projects with others without requiring them to use the exact same build toolchain as you
  4. Cross-compilation to other platforms becomes easier
  5. Switching between static (.LIB) and shared (.DLL) libraries is easier
  6. Switching between build systems is easier
  7. Separating the source code from the build files is easier
  8. Creating Unit tests and distributing your application is easier
  9. Visual Studio 2017 provides built-in support for CMake (see: CMake Support in Visual Studio)

The only disadvantage of using CMake is that the generated solution and project files should not be distributed with your source code. This requires that anyone consuming your project must also use CMake to integrate your libraries into their projects.

You will not be required to learn CMake in-depth to follow this lesson. Anything you will need to know to work with CMake will be described step-by-step.

Dependencies

In order to follow this tutorial, you will only need to have the Visual Studio 2017 development environment installed on your computer. Visual Studio comes with the CMake build tool pre-installed (if this is selected during the installation phase). If you do not have Visual Studio installed then head over to https://www.visualstudio.com/downloads/ and download the Visual Studio Community. This version is free for open-source developers.

If you are an early adopter, you can also get a preview release of the latest version of Visual Studio at https://www.visualstudio.com/vs/preview/. Keep in mind the Visual Studio preview release is not intended for production applications.

Installing Visual Studio

After you download and run the installer for Visual Studio, you’ll be presented with a dialog box showing the available workloads for Visual Studio.

Visual Studio Workloads

Visual Studio Workloads

In order to follow this tutorial, you will need to have the Desktop development with C++ workload selected. The The Desktop development with C++ workload includes Visual Studio C++ tools for CMake component. Make sure the Visual Studio C++ tools for CMake component is selected before you install. You can always return to this installer later by rerunning the Visual Studio installer executable that you already downloaded or simply modify the installation using the Add/Remove Programs from the Windows system settings.

Create a New CMake Project

To create a new CMake project, just create a new folder and create a new text file in that folder called CMakeLists.txt.

New CMake Project

New CMake Project

If you right-click on empty space in the new folder in Windows Explorer, you should see the context aware pop-up menu with the option to Open in Visual Studio.

Open in Visual Studio

Open in Visual Studio

Selecting the Open in Visual Studio from the pop-up menu should cause Visual Studio to be launched and the selected folder is set to the current workspace root. Alternatively, you can start Visual Studio and select File > Open Folder (Ctrl+Shift+Alt+O) from the main menu and select the new folder you just created.

Open CMake Folder

Open CMake Folder in Visual Studio

Visual Studio should recognize the folder as a CMake project and start parsing the CMakeLists.txt file.

Open the CMakeLists.txt file by double-clicking on it the Solution Explorer and add the following:

cmake_minimum_required( VERSION 3.9.2 )

As of the time of writing this article, Visual Studio supports CMake 3.9.2. If your version of Visual Studio supports a more recent version of CMake, then change the version number on line 1 of the CMakeLists.txt file to match the version supported by your installation.

You will notice that every time you save any changes to the CMakeLists.txt file, Visual Studio regenerates the cache file for the CMake project. The CMakeCache.txt file contains all of the CMake variables that define how the project is generated. You can edit the CMakeCache.txt file by right-clicking on the CMakeLists.txt file in the Solution Explorer and selecting Cache > View CMakeCache.txt from the pop-up menu.

View CMakeCache.txt file.

View CMakeCache.txt file.

You can also force Visual Studio to regenerate the CMakeCache.txt file at any time by selecting Generate Cache from the pop-up menu shown in the previous image. This is useful if you make changes to the CMakeLists.txt file in an external program and Visual Studio did not detect that the file was modified.

Adding Source Code

Add a new source file to the solution by selecting File > New > File… (Ctrl+N) from the main menu in Visual Studio or right-click in the Solution Explorer and select Add > New File from the pop-up menu.

New File Dialog

Visual Studio New File Dialog

Create a new C++ source file in the root folder of the workspace (the same folder that contains the CMakeLists.txt file) and save the file with the name main.cpp.

Copy-paste the following code into the main.cpp file.

#include <iostream>

int main(int argc, char* argv[])
{
    std::cout << "Hello World!" << std::endl;

    system("PAUSE");

    return 0;
}

Although this is a valid C++ program with the correct entry-point and return statements, Visual Studio does not allow you to debug this program yet. This is because an executable target has not yet been defined in the CMakeLists.txt file. So let’s do that first.

Open the CMakeLists.txt file in the workspace root and copy-paste the following text.

cmake_minimum_required(VERSION 3.9.2)

project(LearningCMake)

add_executable( HelloWorld main.cpp )

install( TARGETS HelloWorld DESTINATION bin)

After saving the CMakeLists.txt file, Visual Studio will automatically regenerate the cache for the project. Visual Studio also detects the HelloWorld executable target that is defined in the CMakeLists.txt file. Select the HelloWorld.exe target in the Select Startup Item drop-down menu on the toolbar or select CMake > Debug from Build Folder > HelloWorld.exe from the main menu.

Select Startup Item

Select Startup Item

After you have selected the HelloWorld.exe target, you can use the Debug > Start (F5) command from the main menu to debug the application. You can also just click the Startup Item in the toolbar to start the debugger.

You should see the console window appear with the text “Hello World!” being displayed in the console.

Hello World Application

Hello World Application

Debugging the Application

All of the debugging features should work even without creating a Visual Studio project files and solution file.

Place a breakpoint (F9) on line 5 of the main.cpp source file and start the debugger again.

Debugging the Application

Debugging the Application

You’ll notice that the program stopped on line 5 of the HelloWorld application. All of the debug commands you are accustomed to are still working. You can Step Over (F10), Step Into (F11), Step Out (Shift+F11), or Continue (F5), and Stop Debugging (Shift+F5). Inspecting variables are working and the Call Stack is traversable using the Call Stack window.

Build Configurations

Visual Studio provides several default build configurations. As of the time of writing this, the following build configurations are created by default:

  1. x86-Debug
  2. x86-Release
  3. x64-Debug
  4. x64-Release

The x86 configurations are for building 32-bit applications and the x64 configurations are for building 64-bit applications.

Edit Build Configurations

You can edit the default build configurations by right-clicking on the CMakeLists.txt file in the Solution Explorer and selecting Change CMake Settings from the pop-up menu. You can also edit the build configurations by selecting CMake > Change CMake Settings > CMakeLists.txt from the main menu.

Change CMake Settings

Change CMake Settings

A new file called CMakeSettings.json will be added to the root of the workspace and the file will be opened in the editor.

Delete the x86 build configurations so that only the x64 configurations remain and save the file.

Visual Studio may not remove the x86 configurations from the Project Settings drop-down box on the toolbar until you restart the IDE.
{
  // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file.
  "configurations": [
    {
      "name": "x64-Debug",
      "generator": "Ninja",
      "configurationType": "Debug",
      "inheritEnvironments": [ "msvc_x64_x64" ],
      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
      "cmakeCommandArgs": "",
      "buildCommandArgs": "-v",
      "ctestCommandArgs": ""
    },
    {
      "name": "x64-Release",
      "generator": "Ninja",
      "configurationType": "RelWithDebInfo",
      "inheritEnvironments": [ "msvc_x64_x64" ],
      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
      "cmakeCommandArgs": "",
      "buildCommandArgs": "-v",
      "ctestCommandArgs": ""
    }
  ]
}

Each configuration of the configurations array contains the following properties:

  • name: The name used to refer to the build configuration. This is the name that appears in the Project Settings drop-down menu in the main toolbar.
  • generator: The generator used to create the build scripts. The value of this property maps to the -G command to the CMake tool. Currently, Visual Studio supports the following generators:
    • Ninja: A light-weight build system with a focus on speed.
    • Visual Studio 14 2015 [ARM|Win64]: Use the Visual Studio 2015 build system (MSBuild) and toolchain. If no platform is specified in the name of the generator, the application will be built for 32-bit platforms. Append ARM to the generator string to target ARM devices or Win64 to target Intel 64-bit platforms.
    • Visual Studio 15 2017 [ARM|Win64]: Use the Visual Studio 2017 build system (MSBuild) and toolchain. If no platform is specified in the name of the generator, the application will be built for 32-bit platforms. Append ARM to the generator string to target ARM devices or Win64 to target Intel 64-bit platforms.
    • Unix Makefiles: This generator may only be available if the Linux development with C++ workload was selected when installing Visual Studio. This generator allows you to create applications that can run in a Linux environment.
  • configurationType: Specifies the build type for this configuration. Possible values are:
    • Debug: Typical debug build. Debug symbols will be generated allowing you to debug the application. No optimizations will be enabled (/Od).
    • Release: Typical release build. Optimizations that maximize speed (/O2) will be enabled but no debug symbols will be generated so trying to debug an application using this build configuration will be unreliable.
    • RelWithDebInfo: Similar to the Release build, optimizations that maximize speed will be enabled (/O2). Debug symbols will also be generated for this build so debugging applications using this build configuration will be more reliable.
    • MinSizeRel: Similar to the Release build but optimizations that minimize the size of the executable (/O1) will enabled.
  • inheritEnvironments: An array of strings that specifies which environment variables are inherited when the configuration is run. This property is only required when using the Ninja generator. When using one of the Visual Studio generators, the build environment is specified using the platform specification that is appended to the generator name. The following predefined environments are available:
    • msvc_x86: This environment specifies the variables that are defined when building 32-bit applications.
    • msvc_x64: This environment specifies the variables that are defined when building 64-bit applications.
  • buildRoot: The folder where the generated build scripts will be stored. The generated build scripts should not be distributed with your source code. It is a good idea to make sure the build scripts are generated outside of your source tree.
  • installRoot: The folder where the build targets will be placed when the install command is executed. The CMAKE_INSTALL_PREFIX variable will be set to the value of this property when generating the configurations files. This folder is used to prefix the destination folders when using the install command in the CMakeLists.txt file.
  • cmakeCommandArgs: Additional command-line arguments to pass to the CMake tool when generating the build configuration scripts.
  • buildCommandArgs: Additional command-line arguments to pass to the build system when building the targets. For example, the -v command-line argument causes Ninja to display all of the commands being executed while building (verbose logging). For a list of command-line arguments that can be used for MSBuild generators,
    refer to the MSBuild Command-Line Reference.
  • ctestCommandArgs: Additional command-line arguments to pass to CTest when running tests.
  • variables: An array of name, value pairs that are passed to CMake when generating the build scripts. The variables will be sent to CMake as -D<name>=<value> command-line arguments and can be used to specify values for cache variables that are used to generate the CMakeCache.txt file.

Additional Environment Variables

Additional environment variables can be defined in CMakeSettings.json file that will be available when the build scripts are generated and when the build tasks are executed. Additional environment variables can be specified globally or local to a configuration.

Add the following code snippet to the top of the CMakeSettings.json file (inside the first brace ({) but above the “configurations” block.

  "environments": [
    {
      "NINJA_STATUS": "[%f/%t %es] "
    }
  ],

This defines an environment variable in global scope called NINJA_STATUS which is used to specify the progress status message that is printed on the command-line before a Ninja build rule is executed. In this case, the status message will print the number of finished rules (%f), the total number of rules that need to run to complete the build (%t), and the elapsed time in seconds (%e). For more information regarding the placeholders that can be specified to control the progress status message, refer to the Ninja manual.

Environment variables declared in the global scope can also be overridden in the local configurations scope by providing an environments block inside the individual configurations. For example, if you wanted to change the Ninja progress status message when building release builds, you can define the environments array inside the x64-Release configuration block.

    {
      "name": "x64-Release",
      "generator": "Ninja",
      "configurationType": "RelWithDebInfo",
      "inheritEnvironments": [ "msvc_x64" ],
      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
      "cmakeCommandArgs": "",
      "buildCommandArgs": "-v",
      "ctestCommandArgs": "",
      "environments": [
        {
          "NINJA_STATUS": "[Release %f/%t %es] "
        }
      ]
    }

When you build the application using the x64-Release configuration, the Ninja progress status should be displayed with the word Release in the message.

  [Release 1/2 0.555s] Building CXX object CMakeFiles\HelloWorld.dir\main.cpp.obj
  [Release 2/2 0.822s] Linking CXX executable HelloWorld.exe

System environment variables are also available to the CMakeSettings.json file. System environment variables can be accessed using the ${env.SYSTEM_VARIABLE} notation.

Predefined Variables

Several variables can be used within the CMakeSettings.json file.

  • ${name}: The name of the current configuration block.
  • ${generator}: The name of the CMake generator used in the current configuration block.
  • ${workspaceRoot}: The full path to the workspace folder.
  • ${workspaceHash}: Hash of the workspace folder. Useful for creating a unique identifier for the current workspace.
  • ${projectFile}: Full path to the root CMakeLists.txt file.
  • ${projectDir}: Full path to the folder containing the root CMakeLists.txt file.
  • ${thisFile}: Full path to the CMakeSettings.json file.

Using these variables, the configuration settings can be simplified.

{
  // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file.
  "environments": [
    {
      "NINJA_STATUS": "[${name} %f/%t %es] ",
      "BUILD_ROOT": "${env.USERPROFILE}\\CMakeBuilds\\LearningCMake\\build\\${name}",
      "INSTALL_ROOT": "${env.USERPROFILE}\\CMakeBuilds\\LearningCMake\\install\\${name}"
    }
  ],
  "configurations": [
    {
      "name": "x64-Debug",
      "generator": "Ninja",
      "configurationType": "Debug",
      "inheritEnvironments": [ "msvc_x64" ],
      "buildRoot": "${env.BUILD_ROOT}",
      "installRoot": "${env.INSTALL_ROOT}",
      "cmakeCommandArgs": "",
      "buildCommandArgs": "-v",
      "ctestCommandArgs": ""
    },
    {
      "name": "x64-Release",
      "generator": "Ninja",
      "configurationType": "RelWithDebInfo",
      "inheritEnvironments": [ "msvc_x64" ],
      "buildRoot": "${env.BUILD_ROOT}",
      "installRoot": "${env.INSTALL_ROOT}",
      "cmakeCommandArgs": "",
      "buildCommandArgs": "-v",
      "ctestCommandArgs": ""
    }
  ]
}

In this case, the build root and the install root folders are specified in the global environments scope and can be used in each of the different configuration blocks allowing the folder path to be defined once but it will expand to the full path according to the properties of the specific configuration and thus reducing code duplication.

Launch Configurations

By default, Visual Studio exposes every executable target defined in the CMakeSettings.txt file in the Startup Item drop-down menu on the main toolbar.

Select Startup Item

Select Startup Item

Edit Launch Configurations

The launch.vs.json file is used to provide additional settings that are passed to the debugger when running the application in the debugger. To edit the launch.vs.json file, right-click on the root CMakeLists.txt file in the Solution Explorer and select Debug and Launch Settings > HelloWorld.exe from the pop-up menu that appears.

Debug and Launch Settings

Debug and Launch Settings

The launch.vs.json file should open in the editor.

{
  "version": "0.2.1",
  "defaults": {},
  "configurations": [
    {
      "type": "default",
      "project": "CMakeLists.txt",
      "projectTarget": "${buildRoot}\\HelloWorld.exe",
      "name": "${buildRoot}\\HelloWorld.exe"
    }
  ]
}

Each launch configuration in the configurations array contains the following properties:

  • type: The type of the launch configuration. Possible values are:
    • default: The target being debugged is an executable.
    • dll: The target being debugged is a DLL.
  • project: The CMakeLists.txt file where the target executable is specified.
  • projectTarget: The path to the executable target to be debugged.
  • name: The name that is used in the Startup Item drop-down menu in the main toolbar.
  • args: An array of strings that are passed to the application being debugged.
  • currentDir: The current working directory during debugging. Hard-coded file paths in the source code should be relative to the current working directory.

Predefined Variables

All of the predefined variables that can be specified in the CMakeSettings.json file (as explained in Predefined Variables above) can also be used in the launch.vs.json file with the addition of the following predefined variables:

  • ${buildRoot}: The root folder where the build scripts are stored. This is the value of the buildRoot property specified in the CMakeSettings.json file.
  • ${installRoot}: This is the value of the installRoot property specified in the CMakeSettings.json file.

Command-line Arguments

If the application requires arguments to be specified on the command-line during debugging, these arguments can be specified using the args array in the launch configuration.

Edit the launch.vs.json file and specify the following launch configurations:

{
  "version": "0.2.1",
  "defaults": {},
  "configurations": [
    {
      "type": "default",
      "project": "CMakeLists.txt",
      "projectTarget": "${buildRoot}\\HelloWorld.exe",
      "name": "HelloWorld"

    },
    {
      "type": "default",
      "project": "CMakeLists.txt",
      "projectTarget": "${buildRoot}\\HelloWorld.exe",
      "name": "HelloWorld with Args",
      "args": [ "arg1", "arg2", "arg3" ],
      "currentDir": "${projectDir}"
    }
  ]
}

The highlighted text on lines 12-19 show an additional launch configuration that specify both the command-line arguments and the current working directory during debugging.

Edit the main.cpp file and copy-paste the following code:

#include <iostream>
#include <filesystem>

namespace fs = std::experimental::filesystem::v1;

int main(int argc, char* argv[])
{
    std::cout << "Hello World!" << std::endl;

    std::cout << fs::current_path() << std::endl;

    for (int i = 1; i < argc; ++i)
    {
        std::cout << argv[i] << std::endl;
    }

    system("PAUSE");

    return 0;
}

The highlighted lines have been added to the main.cpp file.

Make sure the current Startup Item is set to HelloWorld with Args and run the application. You should see the following output displayed in the console.

Hello World Application (with arguments)

Hello World Application (with arguments)

As can be seen in the output, the current working directory matches that specified in the launch configuration and all of the arguments specified in the args array have been printed to the console.

Unlike the CMakeSettings.json file, the launch.vs.json file is not saved in the workspace root. It is stored in the hidden .vs folder in the workspace root. If you want to distribute the launch configurations with the project (and check-in to the version control system) copy the launch.vs.json file to the root of the source folder (where the root CMakeLists.txt file is stored).

Conclusion

In this article, I described the CMake integration that has been introduced in Visual Studio 2017. I showed you how to specify the various build configurations and how to modify the launch configurations to control how the application is debugged. I did not mention the

This article is not intended to be an in-depth explanation on using CMake but just a brief introduction to using CMake with Visual Studio 2017. Since the CMake integration tools are still being developed by Microsoft, you can expect that the tools will change. Perhaps GUI tools will be added to Visual Studio that allow the user to specify the build configurations and specify the CMake options that you may already be familiar with when using the CMake-GUI tool.

References

[1] M. Luparu, E. Battalio and W. Buik, “Visual C++ Team Blog | C++ tutorials, C and C++ news, and information about the C++ IDE Visual Studio from the Microsoft C++ team.”, Blogs.msdn.microsoft.com, 2017. [Online]. Available: https://blogs.msdn.microsoft.com/vcblog/tag/cmake/. [Accessed: 18-Oct-2017].

2 thoughts on “CMake in Visual Studio 2017

  1. Hello,
    Thanks for your explanation, this is a good complement to Microsoft explanation.

    I want to start a github project in OpenGL/Vulkan C++ but sadly I am a not a real developer, I am a Java Developper ^^ so I have not a lot of experience in C++ environnement.

    After several research during this week, I go to an Visual Studio Cmake environnement on Windows to integrate later and easier Vulkan sdk but I didn’t want all the visual studio stuff like .sln… on my github so their last update on Visual Studio to integrate cmake project is really interesting.

    I want to share with Linux Developer and don’t let them get all the external dependencies by themselves ( like glfw3 or glm) and when I saw the CMakeSettings.json that was completely what I wanted but if I well understand it’s only for Visual Studio.

    Do you know something equivalent to CMakeSettings.json to customize build for Cmake for an Linux or Mac context?

    Best Regards,

Leave a Reply

Your email address will not be published. Required fields are marked *