Hello, vcpkg
While CMake can be used to build your project and help to customize its configuration for different platforms and uses, it is not very good at managing packages. For this reason we will be using another tool built for this purpose called vcpkg. It is an open-source project developed at Microsoft that interacts directly with CMake.
Setting Up CMake Project with vcpkg
To get started we are going to create another new directory in our parent directory
projects/
.
$ mkdir hello_vcpkg
$ cd hello_vcpkg
We can then copy all files from the previous pages project into this new directory, these
are the main.cxx
, CMakeLists.txt
and CMakePresets.json
files. We can then
initialise a new vcpkg project using the command.
$ vcpkg new --application
This will create two new files vcpkg.json
and vcpkg-configuration.json
. The
vcpkg.json
will currently be empty but it is used to specify dependencies. It can also
declare available features for downstream users of your project if it is setup as a
library however, this is not relevant to us right now. The vcpkg-configuration.json
is
used to specify the source location of packages as well as lock the version of the source
to a particular version to make reproducible builds easier, which is important in
production software however, we can largely ignore that file.
The next thing we'll do is ensure that CMake is aware of vcpkg so the two tools can work
together. CMake supports a notion of a toolchain file which specifies which underlying
tools CMake must use which assist in building projects across different systems and helps
determine how to build the packages your project requests. We can specify the vcpkg
toolchain in our CMakePresets.json
by adding the file's path to CMake's variable cache.
This can be done by adding the "cacheVariables
object below the "binaryDir"
entry in
our CMakePresets.json
file with an entry in the new object for the variable
CMAKE_TOOLCHAIN_FILE
. This can be seen in Listing 1-6.
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 22,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
]
}
This leverages the $env{}
macro which obtains environment variables, in this case the
variable $VCPKG_ROOT
variable which is where our vcpkg install lives.
Adding Packages
Let us add a package to our project. For this example we are going to use the fast
formatting and I/O library {fmt}
. To add dependencies
we simply need to add an entry into vcpkg.json
called "dependencies"
which is an
array of objects or strings representing our projects dependencies. We can do this with
the following command:
vcpkg add port fmt
We can also more add a minimum version constraint to dependencies by converting the newly added dependency from string a into an object similar to Listing 1-7.
{
"dependencies": [
{
"name": "fmt",
"version>=": "10.1.0"
}
]
}
The full reference for vcpkg.json
contains more details related to controlling your projects setup and dependencies.
Next, we need to tell CMake that {fmt}
exists and we'd like to use it. To do this we
must tell CMake to find the package using the find_package()
in the projects
CMakeLists.txt
file.
cmake_minimum_required(VERSION 3.14)
project(hello_vcpkg
VERSION 0.1.0
DESCRIPTION "Hello, vcpkg from {fmt}!"
LANGUAGES CXX)
find_package(fmt CONFIG REQUIRED)
add_executable(hello_vcpkg main.cxx)
target_compile_features(hello_vcpkg PRIVATE cxx_std_20)
Because {fmt}
will be a required package for our project we must tell CMake to fail if
it cannot be found which is why we pass the REQUIRED
keyword to the function. We also
pass in the CONFIG
keyword to indicate for CMake to use the packages provided
configuration file which is what allows vcpkg to manipulate how the packages CMake
targets are made. We then must add {fmt}
(more specifically, a target from {fmt}
) to
our projects executable target. We can do this with the target_link_libraries()
function. This function first takes the name of the target we want to add libraries to,
we then specify the names of the targets we wish to link using, ensuring to specify a
scope keyword eg. one of PRIVATE
, PUBLIC
or INTERFACE
. Linking a library simply
means adding it to another target. In Listing 1-8 we use PRIVATE
scoping for adding {fmt}
to our executable because the usage of {fmt}
is limited to
the internals of our projects resulting binary and will not be exposed.
# ... rest of CMakeLists.txt
add_executable(hello_vcpkg main.cxx)
target_compile_features(hello_vcpkg PRIVATE cxx_std_20)
target_link_libraries(hello_vcpkg PRIVATE fmt::fmt)
You may also notice we are linking fmt::fmt
not just fmt
in the call to
target_link_libraries()
. This is because the first fmt
is a namespace for the package
found through find_package()
. We then access the target named fmt
and link it to our
executable.
With all that done we can now use {fmt}
in our main.cxx
file.
Listing 1-9 is an example program that uses {fmt}
which you can copy
into the projects main.cxx
.
#include <fmt/core.h>
auto main() -> int
{
fmt::println("Hello, vcpkg from {{fmt}}");
return 0;
}
The use of two braces in the format string of fmt::println()
is so the one pair is
actually printed in th output. Normally braces have a special meaning in {fmt}
but if
we need a literal '{'
or '}'
we use two.
Building and Running with vcpkg
If we copy over the presets file from the bottom of the previous page, we can build our
smalle package using presets. This is because of the "vcpkg"
preset which links CMake
to vcpkg together such that CMake can find packages installed with vcpkg.
$ cmake -S . -B build/<platform> --preset=linux
$ cmake --preset=default
$ cmake --build build/<platform>
$ ./build/<platform>/hello_vcpkg # ... or .\build\windows-x[86|64]\Debug\hello_vcpkg.exe on Windows
Hello, vcpkg from {fmt}