Environment Variables and Conditional Presets
Learn to make your CMake presets portable by using environment variables and conditional logic to adapt to different machine configurations.
In the last two lessons, we've transformed our build process with CMake Presets. We've replaced long, complex command lines with simple names, and we've used inheritance and user presets to create a scalable, layered configuration that separates team-wide standards from personal preferences.
We've solved one major problem, but another remains. Our presets still contain hardcoded paths to tools like vcpkg. Previously, we've overridden these in a CMakeUserPresets.json
file:
CMakeUserPresets.json
{
"version": 3,
"configurePresets": [{
"name": "user-windows-debug",
"inherits": ["windows-debug"],
"cacheVariables": {
"VCPKG_TOOLCHAIN_FILE":
"D:/vcpkg/scripts/buildsystems/vcpkg.cmake"
}
}]
}
This works, but still requires every developer to create a file with their specific machine's paths. This isn't ideal, especially for automated build servers which don't have a "user" to create such a file.
This lesson introduces the final pieces of the puzzle. We'll learn how to read environment variables to remove hardcoded paths and how to use conditional presets to make our build configurations intelligently adapt to the machine they're running on.
What are Environment Variables?
An environment variable is a key-value pair that is part of your operating system's "environment". It's a global setting that can be accessed by any program or script, including CMake.
They are the standard, universal mechanism for telling tools where to find things without hardcoding paths. For example, your system's PATH
environment variable is a list of directories that your terminal searches to find executables like cmake
, vcpkg
, and conan
.
The convention for a tool like vcpkg is to set an environment variable, often named VCPKG_ROOT
, that points to its installation directory.
Setting Environment Variables
Let's set up the VCPKG_ROOT
environment variable on our system. The process differs slightly between platforms.
On Windows
- In the Start Menu, search for and open "Edit the system environment variables".
- In the "Advanced" tab, click the "Environment Variables..." button.
- In the "System variables" section at the bottom, click "New...".
- For "Variable name", enter
VCPKG_ROOT
. For Variable value, enter the path you installed vcpkg (the directory wherevcpkg.exe
is) - Click OK on all windows to save.
- Important: You must close and reopen any terminal or IDE for the new variable to take effect.

On macOS and Linux
Open your shell's configuration file in a text editor. This is usually ~/.zshrc
for Zsh (the default on modern macOS) or ~/.bashrc
for Bash.
Add the following line to the end of the file, replacing the path with your actual vcpkg installation directory.
export VCPKG_ROOT="/path/to/your/vcpkg"
Save the file and restart your terminal, or run source ~/.zshrc
(or source ~/.bashrc
) to apply the changes immediately.
On MSYS2 UCRT64
The process is the same as for Linux. From within the MSYS2 terminal, edit your ~/.bashrc
file and add the export VCPKG_ROOT="..."
line.
Testing Environment Variables
You can verify that the variable is set correctly by opening a new terminal and running echo $VCPKG_ROOT
on Linux/macOS, echo %VCPKG_ROOT%
on Windows Command Prompt, or $env:VCPKG_ROOT
on Windows PowerShell.
The output should be the path you just set. For example:
echo $VCPKG_ROOT
~/vcpkg
Using Environment Variables in Presets
Now that we have a standard way to find vcpkg on any machine, we can update our preset to use it.
CMake Presets provide a special syntax, $env{VARIABLE_NAME}
, to read the value of an environment variable.
Let's refactor our shared _base
preset in CMakePresets.json
to use this:
CMakePresets.json
{
"version": 3,
"configurePresets": [{
"name": "_base",
"hidden": true,
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"VCPKG_TOOLCHAIN_FILE":
"$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
} /* Other presets unchanged */]
}
Any developer, on any machine, can now use a preset than inherits from this and have VCPKG_TOOLCHAIN_FILE
automatically set. CMake will read their local VCPKG_ROOT
environment variable and construct the correct path to the toolchain file at configure time.
They no longer need to create user presets for this - they just need to set their VCPKG_ROOT
environment variable.
Conditional Presets
Our preset is portable, but what happens if a developer tries to use it without setting the VCPKG_ROOT
environment variable? CMake will try to expand $env{VCPKG_ROOT}
, find it's empty, and fail because the path is invalid.
The condition
key allows you to specify a condition that must be true for the preset to be considered valid and be shown to the user.
The following shows two examples of a condition:
- The
windows-only
preset uses anequals
condition to compare two strings, making the preset only available on Windows - The
mac-or-linux
preset uses aninList
condition to make the preset only available on Darwin (macOS) or Linux
CMakePresets.json
{
"version": 3,
"configurePresets": [{
"name": "windows-only",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
}, {
"name": "mac-or-linux",
"condition": {
"type": "inList",
"string": "${hostSystemName}",
"list": ["Darwin", "Linux"]
}
}]
}
If we list our available conditions now then, depending on our system, we should see at most one option is available to us:
cmake --list-presets
Available configure presets:
"mac-or-linux-preset"
Combining Conditions
What if we also need to check for the presence of our VCPKG_ROOT
environment variable? The anyOf
or allOf
condition type can include a conditions
array comprising of multiple checks:
{
"version": 3,
"configurePresets": [{
"name": "windows-vcpkg-preset",
"condition": {
"type": "allOf",
"conditions": [{
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}, {
"type": "notEquals",
"lhs": "$env{VCPKG_ROOT}",
"rhs": ""
}]
}
}]
}
The allOf
type acts like a logical AND. This preset will now only be available if all of the nested conditions
are true:
- the host system is Windows AND
- the
VCPKG_ROOT
environment variable is set - that is, it doesnotEquals
an empty string
The official documentation lists a few other conditional types that are available, as well as further variables that can be used similar to how we're using ${hostSystemName}
in this example.
Environment Variables in CMakeLists.txt
While presets are the modern way to handle environment-specific configuration, it's also possible to read environment variables directly from within a CMakeLists.txt
file.
The syntax is slightly different: $ENV{VARIABLE_NAME}
(note the uppercase ENV
).
Practical Example
Our build currently requires a VCPKG_TOOLCHAIN_FILE
variable to be defined. That definition is expected to come from the command line, either through a -DVCPKG_TOOLCHAIN_FILE
argument or by using a --preset
that includes it.
However, if someone doesn't provide that argument, we could still make our build work simply by setting the variable within our CMakeLists.txt
file. For example:
CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(Greeter)
# Only set VCPKG_TOOLCHAIN_FILE if the user hasn't already
if(NOT DEFINED VCPKG_TOOLCHAIN_FILE AND DEFINED ENV{VCPKG_ROOT})
set(
VCPKG_TOOLCHAIN_FILE
"$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
CACHE FILEPATH "vcpkg toolchain file"
)
message(STATUS
"Using VCPKG_ROOT from environment: $ENV{VCPKG_ROOT}"
)
endif()
add_subdirectory(app)
This script checks if VCPKG_TOOLCHAIN_FILE
has already been set. If not, it checks if the VCPKG_ROOT
environment variable exists. If it does, it sets the toolchain file itself, caching the result.
Note that the NOT DEFINED VCPKG_TOOLCHAIN_FILE
check here is not strictly necessary. As we covered in our earlier lesson on , values provided on the command line will take precedence over our set()
commands, unless we add the FORCE
argument.
However, including this check anyway makes our intent clearer, and it also ensures our message()
only shows when the set()
command is effective.
Summary
In this final lesson on presets, we've learned how to decouple our build configurations from the specific machines they run on, creating portable and user-friendly workflows.
- Environment Variables: They are the standard way to provide machine-specific information, like paths to tools, to your build system.
- Reading Environment Variables in Presets: Use the
$env{VAR_NAME}
syntax inside yourCMakePresets.json
. - Conditional Presets: Use the
"condition"
key to control when a preset is available. This is used for creating presets that only appear when the necessary tools or environment variables are present. - Reading Environment Variables in
CMakeLists.txt
: For fallbacks or older CMake versions, you can use$ENV{VAR_NAME}
directly in your scripts, but the preset approach is generally preferred.
Generator Expressions and Conditional Logic
Learn how to use generator expressions $<...>
for build-time conditional logic.