Post

Setting up a CMake project in Windows using Visual Studio Code

This post will guide you through setting up a CMake project in Windows using Visual Studio Code. The goal is so that in the end, you can build and run a C/C++ project with CMake in Visual Studio Code without the need of a full IDE. You will also learn how to manage libraries and dependencies with vcpkg.

What is CMake?

CMake is an open-source, cross-platform build system generator. It is used to manage the build process of software projects in a compiler-independent manner. CMake generates native build files for your platform, such as Makefiles for Unix systems and Visual Studio project files for Windows systems.

Why use CMake?

CMake is a powerful tool that simplifies the build process of your project. It allows you to define your build process in a single configuration file (CMakeLists.txt) and generate the necessary build files for your platform. This makes it easier to manage your project’s dependencies, build options, and target platforms. The CMakeLists.txt file is platform-independent, which means you can use the same file to generate build files for different platforms.

Here’s a simple diagram that shows how CMake works: CMake

You have your source code and a CMakeLists.txt file. You run CMake to generate the build files for your platform. Then you use the build files to compile and link your source code into an executable. In the above example, CMake generates Makefiles for Unix systems.

What build system to use under Windows?

Now, as we said before, CMake is a build system generator, not a build system itself. This means that CMake generates build files for a specific build system that will actually compile and link your source code.

There are several build systems available for Windows, such as Make, Meson… In this post, we will use Ninja.

What is Ninja?

Ninja is a small build system with a focus on speed. It differs from other build systems in two major respects: it is designed to have its input files generated by a higher-level build system, and it is designed to run builds as fast as possible.

Now why would we choose Ninja over the other build systems? Well, I love 🥷 . I don’t need to give more arguments and you are not here for this. The differences between build systems are noticeable when the projects is quite large. For your personnals projects any of the build systems will feat your needs.

Here’s an overview of the build.ninja file generated by CMake:

# CMAKE generated file: DO NOT EDIT!
# Generated by "Ninja" Generator, CMake Version 3.28

# This file contains all the build statements describing the
# compilation DAG.

# =============================================================================
# Write statements declared in CMakeLists.txt:
# 
# Which is the root file.
# =============================================================================

# =============================================================================
# Project: hello-world
# Configurations: Release
# =============================================================================

#############################################
# Minimal version of Ninja required by this file

ninja_required_version = 1.5


#############################################
# Set configuration variable for custom commands.

CONFIGURATION = Release
# =============================================================================
# Include auxiliary files.


#############################################
# Include rules file.

include CMakeFiles\rules.ninja

# =============================================================================

#############################################
# Logical path to working directory; prefix for absolute paths.

cmake_ninja_workdir = C$:\dev\hello-world\build\
# =============================================================================
# Object build statements for EXECUTABLE target hello-world


#############################################
# Order-only phony target for hello-world

build cmake_object_order_depends_target_hello-world: phony || CMakeFiles\hello-world.dir

build CMakeFiles\hello-world.dir\main.cpp.obj: CXX_COMPILER__hello-world_unscanned_Release C$:\dev\hello-world\main.cpp || cmake_object_order_depends_target_hello-world
  FLAGS = /DWIN32 /D_WINDOWS /EHsc /O2 /Ob2 /DNDEBUG -MD
  INCLUDES = -external:IC:\dev\vcpkg\installed\x64-windows\include -external:IC:\dev\vcpkg\installed\x64-windows\include\SDL2 -external:W0
  OBJECT_DIR = CMakeFiles\hello-world.dir
  OBJECT_FILE_DIR = CMakeFiles\hello-world.dir
  TARGET_COMPILE_PDB = CMakeFiles\hello-world.dir\
  TARGET_PDB = hello-world.pdb

We can see that the build.ninja file contains all the build statements describing the compilation. This file is generated by CMake and used by Ninja to compile and link your source code into an executable.

What about dependencies?

When working on a project, you will often need to use external libraries and dependencies. Managing these dependencies can be a hassle, especially when working on multiple platforms. This is where vcpkg comes in.

What is vcpkg?

vcpkg is a cross-platform package manager for C and C++ libraries. It simplifies the process of installing and managing libraries and dependencies for your project. vcpkg provides precompiled packages for a wide range of libraries, making it easy to add new dependencies to your project.

Unlike other programming languages, C and C++ do not have a built-in package manager. This means that you have to manually download, build, and install libraries and dependencies for your project. vcpkg automates this process by providing precompiled packages for a wide range of libraries. This makes it easy to add new dependencies to your project without having to worry about building and installing them manually.

Like build systems, there are several package managers available for C and C++ libraries, such as Conan, CPM…

vcpkg

The most important element: the compiler

The compiler is the most important element when working with C and C++ projects. The compiler is responsible for compiling and linking your source code into an executable. Remember what is the goal :

Visual Studio without Visual Studio 🙃

So we will use the Microsoft C++ compiler. The Microsoft C++ compiler is included with Visual Studio, but you can also install it separately using the Visual Studio Build Tools. The Visual Studio Build Tools include the Microsoft C++ compiler, the Visual C++ libraries, and other tools needed to build C and C++ projects.

Compiling Process

Make your choice

I’m starting to see a pattern, there is no standard in the C/C++ world. At the end of the day, it’s up to you to choose the tools that best fit your needs.

joke

Joke aside, now that we have a better understanding of CMake, Ninja, and vcpkg, let’s get started by installing all the tools.

Installing the tools

CMake

First, you need to install CMake. You can download the latest version of CMake from the official website: https://cmake.org/download/. Once you have downloaded the installer, run it and follow the instructions to install CMake on your system.

Make sure to add CMake to your system PATH during the installation process. This will allow you to run CMake from the command line. The path to the binary C:\Program Files\CMake\bin.

By now you should see the following output when running cmake in your terminal.

1
2
3
4
5
6
7
8
9
10
11
12
C:\Users\user>cmake
Usage

  cmake [options] <path-to-source>
  cmake [options] <path-to-existing-build>
  cmake [options] -S <path-to-source> -B <path-to-build>

Specify a source directory to (re-)generate a build system for it in the
current working directory.  Specify an existing build directory to
re-generate its build system.

Run 'cmake --help' for more information.

Ninja

Next, you need to install Ninja. You can download the latest version of Ninja from the official github repository : https://github.com/ninja-build/ninja/releases . Once you have downloaded the binary, extract it to a directory on your system, in my case i created a dev folder in my C drive and extracted the binary there. Add the path to the Ninja binary to your system PATH. This will allow you to run Ninja from the command line.

By now you should see the following output when running ninja --help in your terminal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
C:\Users\user>ninja --help
usage: ninja [options] [targets...]

if targets are unspecified, builds the 'default' target (see manual).

options:
  --version      print ninja version ("1.11.1")
  -v, --verbose  show all command lines while building
  --quiet        don't show progress status, just command output

  -C DIR   change to DIR before doing anything else
  -f FILE  specify input build file [default=build.ninja]

  -j N     run N jobs in parallel (0 means infinity) [default=14 on this system]
  -k N     keep going until N jobs fail (0 means infinity) [default=1]
  -l N     do not start new jobs if the load average is greater than N
  -n       dry run (don't run commands but act like they succeeded)

  -d MODE  enable debugging (use '-d list' to list modes)
  -t TOOL  run a subtool (use '-t list' to list subtools)
    terminates toplevel options; further flags are passed to the tool
  -w FLAG  adjust warnings (use '-w list' to list warnings)

vcpkg

Finally, you need to install vcpkg. You can clone the vcpkg repository from the official github repository.

1
2
C:\Users\user>cd C:\dev
C:\dev>git clone https://github.com/microsoft/vcpkg.git

Now that you have cloned the vcpkg repository, navigate to the vcpkg directory and execute the bootstrap script:

1
2
C:\dev>cd vcpkg
C:\dev\vcpkg>bootstrap-vcpkg.bat

Configure the VCPKG_ROOT environment variable.

1
2
C:\dev\vcpkg>set VCPKG_ROOT=C:\dev\vcpkg
C:\dev\vcpkg>set PATH=%VCPKG_ROOT%;%PATH%

Visual Studio Build Tools

You also need to install the Visual Studio Build Tools. You can download the Visual Studio Build Tools from the official website: https://visualstudio.microsoft.com/visual-cpp-build-tools/. Once you have downloaded the installer, run it and follow the instructions to install the Visual Studio Build Tools on your system.

We need to be able to run the cl command in the terminal. To do this, we need to add the path to the Visual Studio Build Tools to the system PATH. The path to the Visual Studio Build Tools is usually something like C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64. So add this path to your system PATH.

Creating a CMake project

First create a folder for your project. In this example, I will create a folder called hello-world in my C:\dev directory.

1
2
C:\dev>mkdir hello-world
C:\dev>cd hello-world

Now create a CMakeLists.txt file in the hello-world directory. This file will define the build process for your project.

1
2
3
4
5
cmake_minimum_required(VERSION 3.20)

project(hello-world)

add_executable(hello-world main.cpp)

Next, create a main.cpp file in the hello-world directory. This file will contain the source code for your project.

1
2
3
4
5
6
#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

We dont want to run the CMake command by hand in order to build the project, instead, we are going to install the CMake Tools extension in Visual Studio Code. This extension provides support for CMake projects in Visual Studio Code, allowing you to configure, build, and run CMake projects directly from the editor.

CMake Tools

After the installation you should be able to do the following inside of your Visual Studio Code.

CMake Configure

That’s the moment where everyone is retaining their breath 😧 …

Guess what it does not work 😅. Instead you get this kind of gibberish in the terminal.

1
2
3
4
5
6
7
8
9
[main] Configuring project: hello-world 
[proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -SC:/dev/hello-world -Bc:/dev/hello-world/build -G "Unix Makefiles"
[cmake] Not searching for unused variables given on the command line.
[cmake] CMake Error: CMake was unable to find a build program corresponding to "Unix Makefiles".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
[cmake] CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
[cmake] CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
[cmake] -- Configuring incomplete, errors occurred!
[proc] The command: "C:\Program Files\CMake\bin\cmake.EXE" --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -SC:/dev/hello-world -Bc:/dev/hello-world/build -G "Unix Makefiles" exited with code: 1
[visual-studio] Patch Windows SDK path from C:\Program Files (x86)\Windows Kits\10\bin\x64 to C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64 for C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat

As we can see, the CMake Tools extension is trying to use Unix Makefiles as the build system, which is not supported on Windows. We need to configure CMake to use Ninja as the build system instead.

To do this, create a settings.json file in the .vscode directory. This file will define the build presets for your project.

1
2
3
4
5
6
7
8
9
10
11
{
    "cmake.generator": "Ninja",
    "cmake.configureOnOpen": true,
    "cmake.configureSettings": {
        "CMAKE_TOOLCHAIN_FILE": "C:/dev/vcpkg/scripts/build/systems/vcpkg.cmake",
        "VCPKG_TARGET_TRIPLET": "x64-windows",
        "CMAKE_BUILD_TYPE": "Release",
        "CMAKE_C_COMPILER": "cl.exe",
        "CMAKE_CXX_COMPILER": "cl.exe"
    },
}

Dont forget to remove the build directory before re-running the CMake Configure command. And if you have any problems make sure to clear the CMake cache even for the following steps.

Now let me explain those 2 lines in the settings.json file.

  • CMAKE_TOOLCHAIN_FILE: This is the path to the vcpkg toolchain file. The vcpkg toolchain file contains the configuration settings for vcpkg, such as the list of installed packages and the target platform.

  • VCPKG_TARGET_TRIPLET: This is the target platform for vcpkg. In this example, we are using the x64-windows target platform.

  • CMAKE_BUILD_TYPE: This is the build type for your project. In this example, we are using the Release build type.

For more information on the available info on vcpgk you can read the documentation here.

After this your project should be configured correctly and you should be able to build and run your project using the CMake Tools extension in Visual Studio Code. 😎

CMake Run

Managing dependencies with vcpkg

Now that you have a basic CMake project set up, let’s see how you can manage dependencies using vcpkg.

First, you need to install the dependencies you want to use in your project. You can search for available packages using the vcpkg search command.

1
2
3
4
5
6
7
8
C:\dev\vcpkg>vcpkg search sdl2
ffmpeg[sdl2]                              Sdl2 support
hello-imgui[sdl2-binding]                 Use SDL2 platform backend
imgui[sdl2-binding]                       Make available SDL2 binding
imgui[sdl2-renderer-binding]              Make available SDL2 Renderer binding
libtcod[sdl]                              Support for SDL2 windows and events with the libtcod context.
magnum[sdl2application]                   Sdl2Application library
sdl2                     2.30.0           Simple DirectMedia Layer is a cross-platform development library designed ...

Once you have found the package you want to install, you can use the vcpkg install command to install it.

1
C:\dev\vcpkg>vcpkg install sdl2

After installing the package, you need create some files needed by vcpgk to integrate the package in your project. You can do this by running the vcpkg integrate install command.

1
C:\dev\hello-world>vcpkg new --application

This will create a manifest file vcpkg.json and a vcpkg.cmake file in your project directory. The vcpkg.json file contains the list of dependencies for your project, and the vcpkg.cmake file contains the CMake configuration for the vcpkg integration.

Next, add the SDL2 dependency.

1
C:\dev\hello-world>vcpkg add port sdl2

Then you need to add the SDL2 dependency to your CMakeLists.txt file.

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.20)

project(hello-world)

find_package(SDL2 REQUIRED)

include_directories(${SDL2_INCLUDE_DIRS})
add_executable(hello-world main.cpp)
target_link_libraries(hello-world ${SDL2_LIBRARIES})

Finally here’s a simple example of how to use the SDL2 library in your project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <SDL2/SDL.h>

#undef main

int main() {
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
        return 1;
    }

    SDL_Window *win = SDL_CreateWindow("Hello World!", 100, 100, 640, 480, SDL_WINDOW_SHOWN);
    if (win == nullptr) {
        std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
        SDL_Quit();
        return 1;
    }

    SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (ren == nullptr) {
        SDL_DestroyWindow(win);
        std::cerr << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
        SDL_Quit();
        return 1;
    }

    for (int i = 0; i < 3; ++i) {
        SDL_RenderClear(ren);
        SDL_RenderPresent(ren);
        SDL_Delay(1000);
    }

    SDL_DestroyRenderer(ren);
    SDL_DestroyWindow(win);
    SDL_Quit();

    return 0;    
}

If everything went well, you should be able to build and run your project with the SDL2 library using the CMake Tools extension in Visual Studio Code 👍

CMake Run SDL2

This post is licensed under CC BY 4.0 by the author.