Recently, I was able to streamline my CMake-based C++ development workflow quite a bit by defining just six environment variables:

1
2
3
4
5
6
7
8
9
# vcpkg integration
export VCPKG_ROOT="${HOME}/vcpkg"
export PATH="${VCPKG_ROOT}:${PATH}"
export CMAKE_TOOLCHAIN_FILE="${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"

# Ccache
export CMAKE_CUDA_COMPILER_LAUNCHER=ccache
export CMAKE_C_COMPILER_LAUNCHER=ccache
export CMAKE_CXX_COMPILER_LAUNCHER=ccache

This gives me compiler caching for C/C++/Cuda sources everywhere through Ccache and makes installing dependencies via vcpkg a breeze. All I need to do is add such a snippet to my ~/.bashrc or ~/.zshrc et voilà! For the fish shell I do a similar thing 1 and it should not be too hard to port this to other shells.

Please see below for further explanations of these two integrations.

Compiler Caching Everywhere

Ccache is a wrapper that adds a caching layer around many popular compilers. If a piece of code is to be recompiled with the same compiler and flags a second time it might be fetched from a cache instead which can often yield a significant reduction in the overall project compile time, but this can of course vary drastically from project to project. Ccache offers first class support for C/C++ compilation with GCC, Clang, and MSVC as well as CUDA compilation via NVCC on Linux, macOS, and Windows2. Objective-C/C++ and further Unix-like operating systems also see some support.

I have never really found myself using Ccache because I thought it would be annoying to integrate. Fortunately, integrating it with CMake is trivial nowadays. Since CMake version 3.17, the environment variable CMAKE_<LANG>_COMPILER_LAUNCHER can be used, as the name suggests, to specify a program that launches the compiler for any CMake supported language (replace <LANG> with CXX for C++, C for C, etc.).

Another way of setting the compiler launcher is by specifying -DCMAKE_<LANG>_COMPILER_LAUNCHER=... when invoking CMake to configure a build directory. While this is less convenient than any global configuration via the shell environment, it is worth keeping in mind since this flag takes precedence over anything set in the environment variable3.

A few other tools exist which might be useful compiler wrappers:

  • sccache: similar tool to Ccache by Mozilla that can leverage cloud and local storage for the cache
  • distcc: Tool for distributed C/C++ and Objective C/C++ builds on multiple machines

It is quite likely that they can be integrated in a similar way after some initial setup.

C++ Dependency Management with Minimal Boilerplate

For most projects at home and at work, I use vcpkg to manage dependencies. However, it still can be cumbersome to use by either writing custom CMake preset files for the project or having to specify the toolchain file that ships with vcpkg by adding -DCMAKE_TOOLCHAIN_FILE=... to the CMake incantation.

Since CMake 3.21, globally setting the CMAKE_TOOLCHAIN_FILE environment variable alleviates all this friction. I can just run vcpkg add mydep to add a dependency to the project’s manifest and then use something along the lines of find_package(mydep CONFIG REQUIRED)4 in my CMakeLists.txt file to integrate it.

Caveats

There might be some scenarios where these defaults might not be desirable:

  • When having to use a custom CMake toolchain file.
  • The build system plainly does not work with Ccache (I have not yet encountered such a situation).

In such a case, it is straightforward to unset or overwrite the environment variables making it easily revertible.

Conclusion

While none of the above is revolutionary, these small tweaks noticeably reduce friction in my day-to-day C++ development workflow. It is one of those setups that quietly disappears into the background and just works which usually is a good sign.


  1. The equivalent for fish

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # vcpkg integration
    set -gx VCPKG_ROOT $HOME/vcpkg
    fish_add_path $VCPKG_ROOT
    set -gx CMAKE_TOOLCHAIN_FILE $VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
        
    # Ccache
    set -gx CMAKE_CUDA_COMPILER_LAUNCHER ccache
    set -gx CMAKE_C_COMPILER_LAUNCHER ccache
    set -gx CMAKE_CXX_COMPILER_LAUNCHER ccache
    
     ↩︎
  2. Ccache - Supported platforms, compilers and languages ↩︎

  3. Technically, by setting the environment variable, we set the default value of the CMAKE_<LANG>_COMPILER_LAUNCHER variable that is used if it is not explicitly set via -DCMAKE_<LANG>_COMPILER_LAUNCHER. This value is then used to set the default value of the <LANG>_COMPILER_LAUNCHER property for all CMake targets. ↩︎

  4. Some dependencies might still require pkg-config or custom integrations, but that is an upstream problem. ↩︎