Cross-Compiling with vcpkg

Combine vcpkg with CMake's cross-compilation capabilities. Learn to use triplets to target different architectures and chain-load toolchain files

Greg Filak
Published

In our , we learned that the primary mechanism we use to tell CMake about our target is via the CMAKE_TOOLCHAIN_FILE. However, we've also just learned that vcpkg uses this same mechanism to teach CMake how to use it. So what happens when you need to cross-compile a project using vcpkg?

To solve this, we use a technique called chain loading - where each toolchain file loads the next toolchain file, until we have loaded everything we need.

Practical Example

Let's revisit an earlier example, where we were building a Windows application on a macOS host. Our cross-compilation toolchain file looked like this:

toolchains/mingw-windows-x64.cmake

# 1. Set the target system
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

# 2. Specify the cross-compilers
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_C_FLAGS_INIT "-static-libgcc")

set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_CXX_FLAGS_INIT
  "-static-libgcc -static-libstdc++")

set(CMAKE_EXE_LINKER_FLAGS_INIT
  "-static-libgcc -static-libstdc++")

# 3. Configure search for external dependencies
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

We'll update our minimalist Windows application from earlier to now have it log out to a file. We'll use features provided by spdlog, which is still being provided by vcpkg from the previous lesson:

Files

app
vcpkg.json
Select a file to view its content

The VCPKG_TARGET_TRIPLET and MINGW Variables

When we bootstrapped vcpkg as part of our installation, it automatically scanned our environment to determine it's architecture.

For example, if we're on a recent Mac, vcpkg is probably assuming we want to use arm64-osx libraries. You can check this by viewing the VCPKG_TARGET_TRIPLET variable:

message(STATUS "VCPKG triplet: ${VCPKG_TARGET_TRIPLET}")
-- VCPKG triplet: arm64-osx

After running the configure step, we can also check which libraries vcpkg built within the status file:

build/vcpkg_installed/vcpkg/status

Package: fmt
Version: 11.2.0
Depends: vcpkg-cmake, vcpkg-cmake-config
Architecture: arm64-osx
...

Package: spdlog
Version: 1.15.3
Depends: fmt, vcpkg-cmake, vcpkg-cmake-config
Architecture: arm64-osx
...

These arm64-osx libraries won't work when cross compiling to Windows, so we need to change this VCPKG_TARGET_TRIPLET variable. Things we want to override for our target are in our toolchain file, so that's where we'll update this value.

Our toolchain file is designed for using MinGW, so it should set the triplet to x64-mingw-dynamic for dynamic libraries (DLL), or x64-mingw-static if we want to use static linking. We should also set a MINGW variable to true, as this improves the behavior of vcpkg's toolchain file once we load it:

toolchains/mingw-windows-x64.cmake

# Previous configuration is unchanged
# 1. Set the target system
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

# 2. Specify the cross-compilers
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_C_FLAGS_INIT "-static-libgcc")

set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_CXX_FLAGS_INIT
  "-static-libgcc -static-libstdc++")

set(CMAKE_EXE_LINKER_FLAGS_INIT
  "-static-libgcc -static-libstdc++")

# 3. Configure search for external dependencies
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

# New Additions
set(MINGW TRUE)
set(VCPKG_TARGET_TRIPLET x64-mingw-static)

Chain Loading

We still have to solve the problem where our configuration is spread across two toolchain files - the one we created to define our cross-compilation target, and the one that vcpkg provided.

To solve this, we simply update our toolchain file to include() the vcpkg one. Remember to update /path/to/vcpkg to use whatever directory vcpkg is installed in on your system:

toolchains/mingw-windows-x64.cmake

# Previous configuration is unchanged
# 1. Set the target system
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

# 2. Specify the cross-compilers
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_C_FLAGS_INIT "-static-libgcc")

set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_CXX_FLAGS_INIT "-static-libgcc -static-libstdc++")

set(CMAKE_EXE_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++")

# 3. Configure the search behavior for external dependencies
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

# New Additions
set(MINGW TRUE)
set(VCPKG_TARGET_TRIPLET x64-mingw-static)
include("/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake")

Note that the ordering of this file is important - we set the MINGW and VCPKG_TARGET_TRIPLET variables before we include() the vcpkg.cmake file, such that the commands within that file can read the correct value.

Now, when we tell CMake to use our toolchain file (by setting CMAKE_TOOLCHAIN_FILE to it's path), this file will then chain load the vcpkg one, meaning our build will use both.

This isn't portable right now, as we have hardcoded the location of vcpkg within our specific system, which is unlikely to work for others. The main way to solve problems like this is by using environment variables, which we'll cover later in the course.

Building and Testing

Now that everything is set up, let's try to configure and build our project from a clean /build directory:

cmake .. -DCMAKE_TOOLCHAIN_FILE="../toolchains/mingw-windows-x64.cmake"
cmake --build .

If the build succeeded, we can run our application on Windows or an emulator to confirm that everything worked:

We should also see that spdlog created a log file next to our executable:

log.txt

[2025-08-19 16:12:23.481] [file_logger] [info] Program started
[2025-08-19 16:12:35.050] [file_logger] [info] Program shut down

Chain Loading Using vcpkg

In the previous example, we had our toolchain file chain load vcpkg's version, but we can also do this in the opposite direction and have vcpkg load our toolchain file.

For this workflow, we should first remove the include() command from our toolchain file:

toolchains/mingw-windows-x64.cmake

# 1. Set the target system
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

# 2. Specify the cross-compilers
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_C_FLAGS_INIT "-static -static-libgcc")

set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_CXX_FLAGS_INIT
  "-static -static-libgcc -static-libstdc++")

set(CMAKE_EXE_LINKER_FLAGS_INIT
  "-static-libgcc -static-libstdc++")

# 3. Configure the search for external dependencies
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

set(VCPKG_TARGET_TRIPLET x64-mingw-static)
set(MINGW TRUE)
include("/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake")

The vcpkg toolchain file looks for a VCPKG_CHAINLOAD_TOOLCHAIN_FILE variable which, if defined, it will chain load.

We can set this variable however we like, but let's do it on the command line this time. This means our terminal command needs two arguments:

  1. CMAKE_TOOLCHAIN_FILE: The path to the vcpkg.cmake file that we just removed from our toolchain
  2. VCPKG_CHAINLOAD_TOOLCHAIN_FILE: The path to our mingw-windows-x64.cmake toolchain file.

Running from our /build directory, the command will look something like below, where you have updated the /vcpkg/ and /project strings with the location of your vcpkg and project directories:

Note the \ characters let us add line breaks to a complex command for presentational reasons. The underlying terminal will still treat the input as a single command:

cmake .. \
  -DCMAKE_TOOLCHAIN_FILE=\
    "/vcpkg/scripts/buildsystems/vcpkg.cmake" \
  -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=\
    "/project/toolchains/mingw-windows-x64.cmake"

This approach is more portable than our earlier example of hardcoding our /path/to/vcpkg in our CMakeLists.txt file.

However, this is a horrendously long and error-prone command - it is not something we want to be inputting often. In the next chapter, we'll learn how to save combinations of configuration values like this to a CMakePresets.json file where they can be applied and shared more easily.

For now, though, either of these two approaches to chainloading allow us to combine vcpkg's dependency management with CMake's native cross-compilation support.

Summary

This lesson combined our knowledge of CMake toolchains with the power of vcpkg package management workflow. Both vcpkg and a cross-compilation setup need to be configured via the CMAKE_TOOLCHAIN_FILE, creating a conflict.

The solution to the toolchain conflict is to "chain" the files together. There are two main approaches:

  1. Have your custom toolchain file include() the vcpkg.cmake file.
  2. Use the vcpkg.cmake toolchain and set the VCPKG_CHAINLOAD_TOOLCHAIN_FILE variable to point to your custom toolchain. This is generally more flexible, but requires more complex configuration commands. We'll solve the complex command problem using presets in the next chapter.

We also learned that vcpkg uses "triplets" to define a target architecture (e.g., x64-mingw-static). We tell vcpkg which triplet to use by setting the VCPKG_TARGET_TRIPLET variable, which instructs it to build dependencies for our target platform, not our host.

Next Lesson
Lesson 35 of 37

Using Conan with CMake

Discover Conan, the binary-first C++ package manager, and learn how to integrate it with CMake using profiles and lockfiles for fast, reproducible builds.

Have a question about this lesson?
Answers are generated by AI models and may not be accurate