Introduction

In C++, the workflow and especially the build pipeline is much more complicated than in other languages. Therefore, we start with brief overview of the C++ build pipelines. The following scheme shows the possible build pipelines for C++, starting from Integrated developemnt tools (IDE) and ending with the linker.

C++ Workflow

This guide presents mostly the following workflows:

  • Clion or Visual Studio IDE
  • CMake
  • any sequencing tool (these are discribed only briefly as they are configured automaticvally by CMake)
  • MSVC and GCC compiler toolchains

Appart from the build pipeline, we also cover the dependency management. For this, we focus on the dependency manager vcpkg.

Compiler Toolchains

There are various toolchains available on Windows and Linux, but we limit this guide for only some of them, specifically those which are frequently updated and works great with Clion.

MSYS2 (Windows)

  • download
  • follow the installation guide on the homepage
  • install MinGW64 using: pacman -S mingw-w64-x86_64-gcc

MSVC (Windows)

Common Compiler Flags

  • /nologo: do not print the copyright banner and information messages
  • /EH: exception handeling flags

GCC (Linux/WSL)

Installation

  1. If on Windows, Install the WSL first (WSL 2)
  2. check if GCC is installed by typing gcc --version

If GCC is not installed:

  1. sudo apt-get update
  2. sudo apt-get upgrade
  3. sudo apt-get install gcc-<version>

g++

g++ is the main executable for the GCC compiler. It is botha compiler and a wrapper for the linker. Typical usage is:

  • g++ -o <output file> <cpp files and library files>: compilation and linking
  • g++ -c <cpp files>: compilation only
  • g++ -o <output file> <object files and library files>: linking only

Other frequently used flags are:

  • -g: include debug information
  • -l<library>: link the library named lib<library>. Unlike direct library specification, here the linkers searches for the library in standard directories.

Build Sequencing Tools

Make

Most of the time, make scripts should not be written manually, but generated by build script generators like CMake. Therefore, here, we describe the structure of make scripts as they are generated by CMake. The structure is:

  • Makefile: the main file that contains the rules for building the project
  • CMakeFiles: a directory containing the files referenced in the Makefile
    • <target name>.dir directory for each target
      • link.txt: the file containing the linking command

MSBuild

The MSBuild is a XML-based build sequencing tool. The scripts are typically generated by CMake or directly by Visual Studio.

The main file for each target is the <target name>.vcxproj file. All properties are stored in the root element <Project>. The structure inside the project element is as follows (only the most important elements are listed):

  • <ItemDefinitionGroup Condition="<configuration>">: contains the properties for the given configuration.
  • <ItemGroup><multiple <ClCompile> elements><ItemGroup>: contains the source files that are compiled
  • <ItemGroup><multiple <ProjectReference> elements><ItemGroup>: contains references to other targets (dependencies). Theses targets are built before the current target.

The propertis for each configuration are structured as follows:

  • <ClCompile>: contains the properties for the compilation of the source files (e.g., language standard, runtime library, etc.)
  • <ResourceCompile>:
    • <PreprocessorDefinitions>: the preprocessor definitions used for the target. These are introduced either by the target itself or by the dependencies.
  • <Link>: contains the properties for the linking of the target (e.g., the libraries to link, the output file, etc.)

Cmake

  • Windows: Install CMake from https://cmake.org/download/
    • if your CMake is too old (e.g. error: “CMake 3.15 or higher is required”), update CMake (same as new install)
  • Linux:
    • If cmake is installed already, uninstall it!
    • Do not use the cmake from linux repositories!!
    • Download CMake sh installer from https://cmake.org/download/
    • install:
      1. sudo chmod +x <INSTALLER>
      2. sudo <INSTALLER>
      3. sudo rm <INSTALLER>
    • add cmake executable to path

Other details about CMake can be found in the CMake Manual.

vcpkg

Vcpkg is a package manager for C++ libraries it serves as a developement package manager rather than a system package manager.

Vcpkg can work in two modes:

  • Classic mode: vcpkg is installed centrally. This mode is useful for development and testing.
  • Manifest mode: vcpkg is installed in the project directory. This mode is useful for deployment.

To install vcpkg:

  1. clone the repo to the desired location (project directory for manifest mode, any directory for classic mode)
  2. run the bootstrap script (bootstrap-vcpkg.bat on Windows, bootstrap-vcpkg.sh on Linux)
  3. for classic mode, add the vcpkg directory to PATH, so the program can be run from anywhere

Basic coomands

Install a package

To install a package, use the install <package name> command. Important options are:

  • --triplet <triplet>: the target triplet.

Remove a package

To remove a package, use the remove <package name> command. Important options are:

  • --triplet <triplet>: the target triplet.

Search for a package

To search for a package, use the search <package name> command.

List installed packages

To list the installed packages, use the list command. The first positional argument is the triplet filter, e.g., list x64-windows lists only the packages for the x64-windows triplet.

CMake Integration

documentation

By default, CMake does not see the vcpkg. To set up the appropriate enviroment variables, paths, etc., we need to run cmake commands with path to cmake toolchain file: vcpkg/scripts/buildsystems/vcpkg.cmake. See the IDE and command line section for the detailed instructions how to execute cmake with the path to the vcpkg toolchain file.

The toolchain file is executed early on, so it is safe to assume that the environment will be correctly set up before the commands in yor cmake script.

Directory Structure

vcpkg has the following directory structure:

  • buildtrees: contains the build directories for each installed package. Each build directory contains the build logs.
  • installed: contains the installed packages. It has subdirectories for each triplet. Each triplet directory is than divided into folloeing subdirectories:
    • bin: contains the shared libraries
    • debug: contains the debug version of everything in a similar structure as the triplet directory
    • examples: contains example binaries
    • include: contains the header files
    • lib: contains the static libraries
    • share: contains the cmake scripts and other files needed for the integration of the package into a cmake project
    • tools: contains the executables installed with vcpkg packages
  • ports: Contains the package information for each package from the official vcpkg list. There is no special way how to update just the port dir, so update the whole vcpkg by git pull in case you need to update the list of available packages.
  • scripts: various scripts
    • toolchains: cmake files that configure the toolchains. There is a special file for each platform (windows, linux, etc.)
  • triplets: contains the triplet files.

Modules

Vcpkg has it s own find_package macro in the toolchain file. It executes the script: vcpkg/installed/<tripplet>/share/<package name>/vcpkg-cmake-wrapper.cmake, if exists. Then, it executes the cmake scripts in that directory using the standard find_package, like a cmake config package.

Triplets

documentation

Vcpkg supports installing packages built for multiple platforms and compilers in the same vcpkg installation. To do this, vcpkg uses the concept of triplets. A triplet is definition of target environment. Usually, the triplet defines three things:

  • the target platform (e.g., x64, arm)
  • the target operating system (e.g., windows, linux)
  • the target compiler (e.g., msvc, gcc)

The triplet definition is stored in the triplet file in the <vcpkg root>/triplets directory.

Changing the default triplet

To change the default triplet, add a new system variable VCPKG_DEFAULT_TRIPLET, so your default library version installed with vcpkg will be x64 (like our builds), set it to:

  • x64-linux for Linux Compilers
  • x64-windows for MSVC
  • x64-MinGW for MinGW

Using a custom triplet

If you need to test a specific system environment with vcpkg, you can use a custom triplet.

Typically, you can create such a triplet by copying an existing one and modifying it. Typically, you just modify the triplet variables in the file.

To use the custom triplet add two arguments to the vcpkg command:

  • --triplet <triplet name>: the name of the custom triplet
  • --overlay-triplets=<path to the directory containing the custom triplet>: the path to the directory containing the custom triplet file

To change the compiler, it is a little bit more complicated, as there is no triplet variable for the compiler. Instead, we need to provide a custom toolchain:

  1. copy an existing toolchain file from the <vcpkg root>/scripts/toolchains.
  2. modify the toolchain file to use the desired compiler, e.g., by setting the CMAKE_CXX_COMPILER variable.
  3. in the custom triplet file, set the VCPKG_CHAINLOAD_TOOLCHAIN_FILE variable to point to the custom toolchain file.

Update

  1. git pull
  2. bootstrap vcpkg again
    1. Windows: bootstrap-vcpkg.bat
    2. Linux: bootstrap-vcpkg.sh

Update package

  1. Update vcpkg
  2. vcpkg update to get a list of available updates
  3. vcpkg upgrade --no-dry-run to actually perform the upgrade

All packages are upgraded by default. To upgrade just one package, supply the name of the package (e.g., zlib:x64-windows) as an argument to upgrade command.

Upgrade packages matching a pattern

For libraries that are divided into many interdependent packages (like boost), it is useful to upgrade all packages that match a pattern at once. Unfortunately, the upgrade command does not support the pattern matching. The following command can be used to upgrade all packages that match a pattern in PowerShell:

vcpkg update | sls -pattern "boost-\w+" | foreach-object { vcpkg upgrade $_.Matches.Value --no-dry-run }

Package Features

Some libraries have optional features, which are not installed by default, but we can install them explicitely. For example llvm. After vcpkg install llvm and typing vcpkg search llvm:

llvm                 10.0.0#6         The LLVM Compiler Infrastructure
llvm[clang]                           Build C Language Family Front-end.
llvm[clang-tools-extra]               Build Clang tools.
...
llvm[target-all]                      Build with all backends.
llvm[target-amdgpu]                   Build with AMDGPU backend.
llvm[target-arm]                      Build with ARM backend.

Above, we can see that there are a lot of optional targets. To install the the arm target, for example, we can use vcpkg install llvm[target-arm]. Sometimes, a new build of the main package is required, in that case, we need to type vcpkg install llvm[target-arm] --recurse.

Package Versions

In classic mode, there is no way how to control the version of a package. For that, we have to use the manifest mode

Integrate your library to vcpkg

For complete integration of your library to vcpkg, the following steps are needed:

  1. Configure and test the CMake installation
  2. Crate the port and test it locally (vcpkg installation)
  3. Submit the port to the vcpkg repository (publishing)

Resources:

Create the Port

Vcpkg works with ports which are special directories containing all files describing a C++ package. The usuall process is: The usual port contain these files:

  • portfile.cmake: the main file containing the calls to cmake functions that install the package
  • vcpkg.json: metadata file containing the package name, version, dependencies, etc.
  • usage: a file containing the usage instructions for the package. These instructions are displayed at the end of the installation process.

A simple portfile.cmake can look like this:


# download the source code
vcpkg_from_github(
    OUT_SOURCE_PATH SOURCE_PATH
    REPO <reo owner>/<repo name>
    REF <tag name>
    SHA512 <hash of the files>
    HEAD_REF <branch name>
)

# configure the source code
vcpkg_cmake_configure(
    SOURCE_PATH <path to source dir>
)

# build the source code and install it
vcpkg_cmake_install()

# fix the cmake generaed files for vcpkg
vcpkg_cmake_config_fixup(PACKAGE_NAME <package name>)

# install the license
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt")

# install the usage file
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")

Explanation:

  • vcpkg_from_github: downloads the source code from the github repository
    • the <path to source dir> is the directory where the CMakeLists.txt file is located. It is usually the directory where the source code is downloaded, so we can set it to ${SOURCE_PATH}
    • the <hash of the files> can be easily obtained by:
      1. setting the ` to 0
      2. running the vcpkg install <port name>
      3. copying the hash from the error message
  • vcpkg_cmake_configure: configures the source code using cmake (wraps the cmake command)
  • vcpkg_cmake_install: builds and installs the source code (wraps the cmake --build . --target install command)
    • the majority of code is in the subroutine vcpkg_cmake_build
    • if we need some libraries installed with vcpkg at runtime during the build of the package, we need to use the ADD_BIN_TO_PATH option in the vcpkg_cmake_install function. This is needed as the automatic dll copy to the output dir (VCPKG_APPLOCAL_DEPS) is disabelled by the vcpkg_cmake_build function. This option solve the problem by prepending the PATH environment variable with the path to the vcpkg installed libraries (<vcpkg root>/installed/<triplet>/bin for release and <vcpkg root>/installed/<triplet>/debug/bin for debug).
  • vcpkg_cmake_config_fixup: fixes the cmake generated files for vcpkg. This is needed because the cmake generated files are not compatible with vcpkg. The function fixes the CMakeConfig.cmake and CMakeConfigVersion.cmake files.
    • the <package name> is the name of the package, usually the same as the port name
  • vcpkg_install_copyright installs the license files listed in the FILE_LIST argument to share/<port name>/copyright file. The copyright file is obligatory for the package to be accepted to the vcpkg repository.

The vcpkg.json file can look like this:

{
    {
    "name": "fconfig",
    "version-string": "0.1.0",
    "description": "C++ implementation of the fconfig configuration system",
    "homepage": "https://github.com/F-I-D-O/Future-Config",
    "license": "MIT",
    "dependencies": [
        {
            "name" : "vcpkg-cmake",
            "host" : true
        },
        "yaml-cpp",
        "spdlog",
        "inja"
    ]
}
}

Here:

  • the license key is obligatory and has to match the license file of the package
  • The dependencies with the host key set to true are the dependencies that are required for the build, but not for the runtime.

Variables and Functions available in the portfile.cmake

The variables and functions available in the portfile.cmake are described in the create command documentation. The most important variables are:

  • CURRENT_PACKAGES_DIR: the directory where the package is installed: <vcpkg root>/installed/<triplet>/<port name>

Installation

To install the port locally, run:

vcpkg install <port name>

For this command to work, the port has to be located in <vcpkg root>/ports/<port name>. If we want to install the port from an alternative location, we can use the --overlay-ports option. For example, if we have the port stored in the C:/custom_ports/our_new_port directory, we can install it by:

vcpkg install our_new_port --overlay-ports=C:/custom_ports

If the port installation is failing and the reason is not clear from stdout, check the logs located in <vcpkg root>/buildtrees/<port name>/

Reinistallation after changes

During testing, we can reach a scenario where a) we successfully installed the port, b) we need to make some changes. In this case, we need to reinstall the port. However, it is not completely straightforward due to binary caching. The following steps are needed to reinstall the port:

  1. uninstall the port: vcpkg remove <port name>
  2. disable the binary cache by setting the VCPKG_BINARY_SOURCES environment variable to clear
    • in PowerShell: $env:VCPKG_BINARY_SOURCES = "clear"
    • in bash: export VCPKG_BINARY_SOURCES=clear
    • if setting the environment variable does not work (WSL), we can specify the --binarysource=clear option in the next step
  3. install the port again: vcpkg install <port name>

Executable installation

In general vcpgk does not allow to install executables, as it is a dependency manager rather than a package manager for OS. However, it is possible to install executables that are intedned to be used as tools (to the installed/<triplet>/tools directory) used in the build process. To do so, you have to add the vcpgk_copy_tools call to the portfile.cmake file:

vcpkg_copy_tools(
    TOOL_NAMES <tool target name>
    AUTO_CLEAN
)

The AUTO_CLEAN option ensures that the tools are deleted from the bin directory. Without it the tools will be kept in the bin directory, resulting in warnings and non-complicance with the vcpkg rules.

The vcpgk_copy_tools function also automatically copies the runtime dependencies of the tools to the tools directory.

Executing installed tools from cmake

The installed tools can be executed from cmake using cmake comands specified in the CMake manual.

To specify the path to the tools directory, use the VCPKG_INSTALLED_DIR and VCPKG_TARGET_TRIPLET variables:

execute_process(
    COMMAND ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/${PROJECT_NAME}/<tool name>
)

Publishing

official guide

Before publishing the port, we should check for the following:

  • all dependencies in CMakelists.txt are required (find_package(<package name> REQUIRED)) and listed in the vcpkg.json file in the dependencies array
  • the port follows the maintainer guide, especially:
    • the port name does not clash with existing packages (check at repology)
    • the port should work for both Windows and Linux and on both platforms, the port should support both static and dynamic linking.
  • the PR checklist is followed

Then, the submission process is as follows (The emphezised steps are not needed in case of fixing a failed release, i.e., when the release was rejected by vcpkg):

  1. create a fork of the vcpkg repository
  2. commit and push the changes to the project repository if not done yet
  3. create or replace a release in the projects GitHub repository
  4. update the verison in the vcpkg_from_github call in portfile.cmake
  5. update the version in the vcpkg.json file
  6. pull from the vcpgk fork repository
  7. copy the port (portfile.cmake, vcpkg.json and usage) to the vcpkg repository
  8. remove the local SOURCE_PATH overrides and uncomment the vcpkg_from_github call
  9. in portfile, assign the correct hash to the vcpkg_from_github call
  10. test the port installation locally without the --overlay-ports option
  11. format the vcpkg.json file using the vcpkg format-manifest <path to the vcpkg.json file> command
  12. create a new branch for the package
  13. commit the changes to the package branch in the vcpkg repository
  14. update the port version using the vcpkg x-add-version <port name> command
  15. commit again to the package branch in the vcpkg repository
  16. push the branch to the forked vcpkg repository
  17. open the forked repository in the browser and create a new pull request to the main vcpkg repository

IDE

Clion

Configuration

Set default layout

Window -> Layouts -> Save changes in current layout

Set up the new Nova engine

The new Nova engine is a new Clion's engine that is faster and have more features. It is the engine that is used in Visual Studio Resharper C++ plugin. To enable it, go to settings -> Advanced Settings and under Clion check the Use the ReSharper C++ language engine (Clion Nova).

Apart from the new features, the Nova engine can prevent some false errors that are caused by the clangd engine used by the old Clion engine.

Set up new surround with template

In Clion, there are two types of surround with templates: surrond with and surround with live template. The first type use simple predefined templates and cannot be modified. However, the second type can be modified and new templates can be added.

Toolchain configuration

Go to settings -> Build, Execution, Deployment -> toolchain, add new toolchain and set:

  • Name to whatever you want
  • The environment should point to your toolchain:
    • MSVC: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
    • MSYS: C:\MSYS2
    • WSL: From the drop-down list, choose the environment you configured for using with CLion in the previous steps
  • Credentials (WSL) click to the setting button next to the credentials and fill
    • host: localhost
    • port: 2222
    • user and password according to your WSL system credentials
  • Architecture (non WSL): amd64
  • CMake: C:\Program Files\CMake\bin\cmake.exe, for WSL, leave it as it is
  • other fields should be filled automatically

Multiple WSL toolchains

When using multiple WSL toolchains, we need to manually set the compilers. To do so, fill also the C Compiler and C++ Compiler fields in the toolchain settings with the path to the compiler executable.

Project configuration

Most project settings resides (hereinafter Project settings) in settings -> Build, Execution, Deployment -> CMake. For each build configuration, add a new template and set:

  • Name to whatever you want
  • Build type to debug
  • To Cmake options, add:
    • path to vcpkg toolchain file:
      • Linux: -DCMAKE_TOOLCHAIN_FILE=/opt/vcpkg/scripts/buildsystems/vcpkg.cmake
      • Windows: -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
    • Set the correct vcpkg triplet
      • MSVC: -DVCPKG_TARGET_TRIPLET=x64-windows
      • MinGW: -DVCPKG_TARGET_TRIPLET=x64-MinGW
      • Linux: -DVCPKG_TARGET_TRIPLET=x64-linux

WSL extra configuration

The CLion does not see the WSL's environment variables (as of 2023-03, see here). To fix it, go to Project settings and set add the necessary environment variables to Environment field.

Configuring only some CMake profiles

When we click on the CMake reconiguration button, all profiles are reconfigured. Unfortunately, there is no way how to configure only some profiles. To work around this, we can deactivate the profiles we do not want to configure. To do so:

  1. go to settings -> Build, Execution, Deployment -> CMake
  2. select the profile you want to deactivate
  3. uncheck the Enable profile checkbox located at the top of the profile settings

Problems

Editor reports errors despite the code compiles in all compilers

This can be caused by the clangd engine used by the old Clion engine. To fix it, enable the new Nova engine.

Visual Studio

Installation

  1. Install Visual Studio
  2. Open/Create a CMake project
  3. Install ReSharper C++

Setting Synchronization

  1. Sign-in in Visual Studio using a Mictosoft account. A lot of settings should be synchronized automatically.
  2. Apply the layout: Window -> Apply Window Layout -> <Layout Name>
  3. Sync ReSharper settings: you can share the file: %APPDATA%\JetBrains\Shared\vAny\ (~\AppData\Roaming\JetBrains\Shared\vAny\).
    • This does not work good though as the files are changed on both sides constantly.
    • unfortunately, as of 01/2023, there is no good way how to share resharper settings
  4. Install roamed plugins

Basic Configuration

  1. Add 120 char guideline
    • install the extension
    • add the guideline in command window: Edit.AddGuideline 120
    • if there is an error extension ... did not load properly, you need to install the developer analytic tools package to the Visual Studio:
      • Visual Studio Installer -> modify
      • Go to the Individual Components tab
      • search for the extension and select it
      • proceed with the Visual Studio Modification
  2. If you need to use the system CMake, configure it now (described below)
  3. If you use *.tpp file, configure a support for them (described below).

installation

Enable template implementation files (.*tpp) syntax highlighting:

  • Go to Tools -> Options -> Text Editor -> File Extension
  • select Microsoft Visual C++
  • write tpp to the field and click add
  • (reopen the file to see changes)

To Change the Build Verbosity

  1. Go to Tools -> Options -> Projects and Solutions -> Build and Run
  2. Change the value of the MSBuild project build output verbosity.

Project Setting

Configure Visual Studio to use system CMake:

  • Go to Project -> CMake Settings
  • it should open the CMakeSettings.json file
  • Scroll to the bottom and click on show advanced settings
  • Set the CMake executable to point to the cmake.exe file of your system CMake

Build Setting and Enviromental Variables

The build configuration is in the file CMakePresets.json, located in the root of the project. The file can be also opened by right clicking on CMakeLists.txt ad selecting Edit CMake presets.

Set the CMake Toolchain File

To set the vcpkg toolchain file add the following value to the base configuration cacheVariables dictionary:

"CMAKE_TOOLCHAIN_FILE": {
    "value": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake",
    "type": "FILEPATH"
}
Set the Compiler

The MSVC toolchain has two compiler executables, default one, and clang. The default compiler configuration looks like this:

"cacheVariables": {
    ...
    "CMAKE_C_COMPILER": "cl.exe",
    "CMAKE_CXX_COMPILER": "cl.exe"
    ...
},

To change the compiler to clang, replace cl.exe by clang-cl.exe in both rows.

Old Method Using CMakeSettings.json

We can open the build setting by right click on CMakeList.txt -> Cmake Settings

To configure configure vcpkg toolchain file: Under General, fill to the Cmake toolchain file the following: C:/vcpkg/scripts/buildsystems/vcpkg.cmake

To configure the enviromental variable, edit the CmakeSettings.json file directly. The global variables can be set in the environments array, the per configuration ones in <config object>/environments (exmaple).

Launch Setting

The launch settings determins the launch configuration, most importantly, the run arguments. To modify the run arguments: 1. open the launch.vs.json file: - use the context menu: - Right-click on CMakeLists.txt -> Add Debug Configuration - select default - or open the file directly, it is stored in <PROJECT DIR>/.vs/ 2. in launch.vs.json configure: - type: default for MSVC or cppgdb for WSL - projectTarget: the name of the target (executable) - name: the display name in Visual Studio - args: json array with arguments as strings - arguments with spaces have to be quoted with escaped quotes 3. Select the launch configuration in the drop-down menu next to the play button

If the configuration is not visible in the drop-down menu, double-check the launch.vs.json file. The file is not validated, so it is easy to make a typo. If there is any problem, insted of an error, the launch configuration is not available. The following problems are common:

  • syntax error in the json (should be marked by red squiggly line)
  • typo in the target name
Other launch.vs.json options
  • cwd: the working directory

Microsoft reference for launch.vs.json

WSL Configuration

For using GCC 10:

  • go to CmakeSettings.json -> CMake variables and cache
  • select show advanced variables checkbox
  • set CMAKE_CXX_COMPILER variable to /usr/bin/g++-10

Other Configuration

  • show white spaces: Edit -> Advanced -> View White Space.
  • configure indentation: described here

Determine Visual Studio version

At total, there are 5 different versionigs related to Visual Studio.

The version which the compiler support table refers to is the version of the compiler (cl.exe). we can find it be examining the compiler executable stored in C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\bin\Hostx64\x64.

Problems & solutions

Cannot regenerate Cmake cache

go to ./vs and look for file named CmakeWorkspaceSettings. It most likelz contains a line with disable = true. Just delete the file, or the specific line.

Typical directory structure

The typical directory structure for a C++ project is as follows:

  • data: the directory containing the data files, including test data and default configuration files
  • include: the directory containing public header files
    • this directory is for the headers that are meant to be included by other projects. Typically, only library projects have this directory
    • the projects own headers should be in the include/<project name> directory to mimic the installation directory structure
  • install: the directory containing the installation scripts. Only needed for library projects or projects that are meant to be distributed
  • port: the directory containing the vcpkg port files. Only needed for libraries that are meant to be distributed via vcpkg
  • src: the directory containing the source files and private headers
  • test: the directory containing the test files
  • <various build dirs>: the directories containing the build files.

The root directory typically contains the following files:

  • .gitignore: the git ignore file
  • CMakeLists.txt: the main cmake file
  • CTestConfig.cmake: the CTest configuration file

Dependencies

Unlike in Python and Java, that have pip and maven, C++ does not have a standard package manager. Instead, there are plenty of ways how to install the dependencies and CMake then tries to find them. In the following table, we list the most common ways how to install the dependencies together with theri pros and cons.

Method Platform-independent Automatic install of the dependency tree Can install build configuration tools Support easily determined
CMake --install yes no yes no
vcpkg yes yes yes yes
CMake FetchContent yes no no no
CMake ExternalProject yes

Vcpkg Libraries

  1. type vcpkg list, if the library you need is not listed, continue to the next steps
  2. type vcpkg search <library simple name> and inspect the result to determine the exact name of the package you need
    • if the library is not listed, check the presence in vcpkg repo
    • if the library is in repo, but search does not find it, update vcpkg
  3. type vcpkg install <exact name> to install the package
  4. at the end of the installation log, there will be a cmake command needed to integrate the library, put it to the appropriate place to your CMakeList.txt file

To display the cmake commands for the installed libraries, just run vcpkg install <exact name> again.

Boost

With boost, we should install only the necessary components. Then to include boost, we need:

  • find_package(Boost REQUIRED)
    • with all compiled components listed
  • target_include_directories(<YOUR TARGET NAME> PUBLIC ${Boost_INCLUDE_DIRS})

Sometimes, it may be usefull to find out which boost components require linking. The list in the boost documentation, both for Unix and Windows.

JNI

for JNI, a JAVA_HOME system property needs to be set to the absolute path to the JDK, e.g., C:\Program Files\Java\jdk-15.0.1

Gurobi

  1. If you don’t have Gurobi installed, do it now, and check that the installation is working
  2. Linux only: it is necessary to build the C++ library for your version of the compiler. Steps: 1. cd <GUROBI DIR>/linux64/src/build/ 2. make 3. mv libgurobi_c++.a ../../lib/libgurobi_c++_<some id for you, like version>.a 4. cd ../../lib/ 5. ln -sf ./libgurobi_c++<some id for you, like version>.a libgurobi_c++.a
  3. Follow this guide, specifically: 1. put the attached our custom FindGUROBI script to: - Windows: C:\Program Files\CMake\share\cmake-<your cmake version>\Modules/ - Linux: /opt/<CMAKNAME>/share/cmake-<VERSION>/Modules 2. to your CMakeLists.txt, add: - find_package(GUROBI REQUIRED) - target_include_directories(<your executable> PRIVATE ${GUROBI_INCLUDE_DIRS}) - target_link_libraries(<your executable> PRIVATE ${GUROBI_LIBRARY}) - target_link_libraries(<your executable> PRIVATE optimized ${GUROBI_CXX_LIBRARY} debug ${GUROBI_CXX_DEBUG_LIBRARY}) 3. try to load the cmake projects (i.e., generate the build scripts using cmake). 4. if the C++ library is not found (Gurobi c++ library not found), check whether the correct C++ library is in the gurobi home, the file <library name>.lib has to be in the lib directory of the gurobi installation. If the file is not there, it is possible that your gurobi version is too old

Update Gurobi

  • Updating is done by installing the new version and generating and using new licence key.
  • after update, you need to delete your build dir in order to prevent using of cached path to old Gurobi install
  • Also, you need to update the library name on line 10 of the FindGUROBI.cmake script.

Other Libraries Not Available in vcpkg

Test Library linking/inclusion

For testing purposes, we can follow this simple pattern:

  1. build the library
  2. include the library: target_include_directories(<target name> PUBLIC <path to include dir>), where include dir is the directory with the main header file of the library.
  3. if the library is not the header only library, we need to: 3.1 link the library: target_link_libraries(<target name> PUBLIC <path to lib file>), where path to lib file is the path to the dynamic library file used for linking (.so on Linux, .lib on Windows). 3.2. add the dynamic library to some path visible for the executable - here the library file is .so on Linux and .dll on Windows - there are plenty options for the visible path, the most common being the system PATH variable, or the directory with the executable.

Dependencies with WSL and CLion

In WSL, when combined with CLion, some find scripts does not work, because they depend on system variables, that are not correctly passed from CLIon SSH connection to CMake. Therefore, it is necessary to add hints with absolute path to these scripts. Some of them can be downloaded here. Package that require these hints:

  • JNI
  • Gurobi

Refactoring

The refactoring of C++ code is a complex process, so the number of supported refactoring operations is limited. In Visual Studio, the only supported refactoring operation is renaming. In IntelliJ tools (CLion, ReSharper C++), there are more tools available, but still, the refactoring is not as powerful nor reliable as in Java or Python.

Other alternative is to implement the refactoring manually, with a help of some compiler tools like clang Refactoring Engine (example project).

Changing Method Signature

As of 2023-10, there is no reliable way how to change the method signature in C++. The most efficient tool is the method signature refactorin in either CLion or ReSharper C++. However, it does not work in all cases, so it is necessary to check and fix the code manually.

Standard Library

The strucure and organization of the standard library is different on Windows and Linux.

On Windows (specificly when using MSVC) the library is called MSVC C Runtime (CRT). It is divided between:

  • VCRuntime<version>.dll: contains the parts that interacts with the OS
    • new versions are released with new versions of Visual Studio
  • ucrtbase.dll: contains other parts of the CRT
    • non-versioned, the same for all versions of Visual Studio
  • many other libraries

On Linux, the library is divided between C and C++ standard libraries:

  • C++ Standard Library: contains the standard C++ features described in the C++ standard.
    • GCC: GNU C++ Library (libstdc++)
      • the backward compatibility is maintained by the exported symbols marked as GLIBCXX_<version>. The max GLIBCXX version per GCC version is listed in the ABI page
    • LLVM: libc++
  • C Standard Library (glibc): contains the standard C features described in the C standard.
    • To get the version, run ldd --version

Linking to the Standard Library

On both platforms:

  • the standard library is linked automatically
  • the standard library is linked dynamically by default

However, Windows and Linux have different attitudes towards static linking of the standard library:

  • Windows: static linking is pretty common and easy to setup:
    • pros: easy way to distribute the application to older systems, possibly decades old
    • cons: even begginers have to check that the executable and all its dependencies link to the same (static vs dynamic) version of the CRT
  • Linux: static linking is not common and not recommended:
    • pros: the mismatch of the standard library linking between the executable and the dependencies is not a problem
    • cons: the executable is typically not portable to other systems, unless the same version of the standard library is installed (glibc is usually stable, but libstdc++ and libc++ are not)

Setting up the correct CRT with MSVC

official documentation

It is critical to set the runtime library for the executable we are building so that it matches the runtime library used by the dependencies. If the mismatch occurs, it is manifested depending on the situation:

  • when we link to a dynamic CRT, but dependencies link to a static CRT, the linker will throw an error, typically for each such dependency plaintext LNK2038 mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ..
  • when we link to a staticCRT, but dependencies link to a dynamic CRT, the linker proceeds without errors, but the program crashes at runtime. Typically, some allocation/deallocation error occurs.

The CRT is set by compiler flags (see the table below).

  • These flags are typically passed to the compiler by the build system based on the build configuration files (e.g., MSBuild files).
  • They affect all parts of the CRT

If we generate thes files by the IDE (Visual Studio), we have to set the CRT in the IDE. If they are generated by CMake, there are three possible situations:

  • we use the dynamic CRT (default): nothing has to be done
  • the build is handled by vcpkg (libraries installed with vcpgk install): the runtime library is set by the VCPKG_CRT_LINKAGE variable in the triplet file. Nothing has to be done.
  • we use the static CRT: we have to set the CMAKE_MSVC_RUNTIME_LIBRARY variable in the CMakeLists.txt file. if we use vcpgk libraries, we should set it based on the triplet used: cmake if (VCPKG_TARGET_TRIPLET MATCHES "-static") set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") endif()

To determine if the type of the runtime library used by the target, we can explore the build script for the target, For MSBuild, the runtime library is set by the RuntimeLibrary property. To determine the runtime library used by a library, we can use the dumpbin tool. This tool is part of the Visual Studio installation and can be run from the Developer Command Prompt or Developer PowerShell. The following command will display the runtime library used by the library:

dumpbin /directives <path to the library>

The following compiler flags are available:

Option Copiler flag MSBuild name Description Linged library
Multi-threaded Dynamic /MD MultiThreadedDLL The default option. The application uses the dynamic version of the runtime library. msvcrt.lib
Multi-threaded Dynamic Debug /MDd MultiThreadedDebugDLL The debug version of the dynamic runtime library. msvcrtd.lib
Multi-threaded Static /MT MultiThreaded The application uses the static version of the runtime library. libcmt.lib
Multi-threaded Static Debug /MTd MultiThreadedDebug The debug version of the static runtime library. libcmtd.lib
DLL /LD The application is compiled as a DLL. -
DLL Debug /LDd The debug version of the DLL. -

By default, the /MD//MDd flags are used depending on the build type.

Resouces

Exporting symbols for shared libraries

When creating a shared library, we have to specify which symbols are exported. These are the only symbols that can be directly used from the client code. This is done using special keywords. Because the keywords are different for different compilers, usually, some macros are used instead. Typically, these macros:

  • use the correct keyword for the compiler
  • support disabling the keyword for building static libraries or executables

The macros are typically defined in a dedicated header file called export header. This file is then included in every header file that defines a symbol that should be exported.

For the whole export machinery to work, we need to:

  • create the export header file and include it in every header file that defines an exported symbol
  • mark the symbols that should be exported with the export macro
  • use CMake to supply the correct compiler flags used in the export header

Creating the Export Header

We can generate the export header file using the GenerateExportHeader module. We can get it by the following code:

add_library(<target name> SHARED <files>)
generate_export_header(<target name>)

This will generate the export header file in the build directory. However, the export file is different for different compilers. Therefore, it is best to copy the file for each compiler and then merge the macros to create a universal export header file. Alternatively, we can use some proven export header file.

Finally, we can store the export header file in the source directory and include it in every header file that defines an exported symbol.

Marking the Symbols

Usually, we mark the following symbols for export:

  • classes: class <export macro> MyClass{...}
  • functions: <export macro> <return type> my_function(...)

Other symbols does not have to be exported as they are automatically exported by the compiler:

  • enums and enum classes
  • constants
  • templates

Additionaly, only the symbols needed by external code should be exported. The exeption is when the the interface use templates. In this case, all symbols used by the template (but not the template itself) should be exported.

The <export macro> from the GenerateExportHeader is named <target name>_EXPORT, we can check the exact name in the export header file.

CMake Configuration

Now the shared library build should work correctly. However, the static library build will be full of warnings, because the export macro is not intended for static libraries. The same is true for executables. Therefore, we need to set a special property for each target that uses the export header and is not a shared library:

target_compile_definitions(<static/executable target name> PUBLIC <static condition macro >)

The static condition macro from the GenerateExportHeader is named <target name>_STATIC_DEFINE. If not clear, we can find the exact macros' names in the export header file.

Resouces

Compilation for a specific CPU

MSVC

MSVC cannot compile for a specific CPU or CPU series. It can, however, use new instructions sets more efficiently if it compiles the code without the support for CPUs thad does not support these instruction sets.

The command for the compiler is: `/arch: (see MSVC documentation for details).

## GCC In GCC, the march option enables compilation for a specific hardware. ml) option enables compilation for a specific hardware.

pects that you use vcpkg in a per-project configuration. To make it work, add: -DCMAKE_TOOLCHAIN_FILE=<vcpkg location>/scripts/buildsystems/vcpkg.cmake - To change build options (option in CMakeLists.txt), run cmake with -D <option name>=<option value> <build dir>. Example: cmake -D BUILD_TESTING=OFF .

Building

For building, use:

cmake --build <build dir>

where build dir is the directory containing the build scripts (CmakeFiles folder).

To list the build options:

cmake -L

Specify the build type (Debug, Release)

To build in release mode, or any other build mode except for the default, we need to specify the parameters for CMake. Unfortunately, these parameters depends on the build system:

  • Single-configuration systems (Unix, MinGW)
  • Multi-configuration systems (Visual Studio)

Single-configuration systems

Single configuration systems have the build type hardcoded in the build scripts. Therefore, we need to specify the build type for CMake when we generate the build scripts:

cmake ../ -DCMAKE_BUILD_TYPE=Release

By default, the build type is Release.

Multi-configuration systems

In multi-configuration systems, the -DCMAKE_BUILD_TYPE parameter is ignored, because the build configuration is supposed to be determined when building the code (i.e., same build scripts for debug and for release). Therefore, we omit it, and instead specify the --config parameter when building the code:

cmake --build . --config Release

Specify the target

We can use the --target parameter for that:

cmake --build . --target <TARGET NAME>

Clean the source files

Run:

cmake --build . --target clean

Handling Case Insensitivity

Windows builds are, in line with the OS, case insensitive. Moreover, the Visual Studio does some magic with names internally, so the build is case insensitive even on VS WSL builds.

The case insensitivity can bring inconsistencies that later breake Unix builds. Therefore, it is desirable to have the build case sensitive even on Windows. Fortunatelly, we can toggle the case sensitivity at the OS level using this PowerShell command:

Get-ChildItem <PROJECT ROOT PATH> -Recurse -Directory | ForEach-Object { fsutil.exe file setCaseSensitiveInfo $_.FullName enable }

Note that this can break the git commits, so it is necessary to also configure git in your case-sensitive repo:

git config core.ignorecase false