Managing dependencies in a C++ project with vcpkg
More than half a year ago I was trying out Conan package manager for resolving dependencies in our C++ project. The research went well, but we never actually proceeded with switching to Conan for the whole project. And then a couple of weeks ago I started looking into vcpkg.

I’ve heard about vcpkg before and even tried to make a vcpkg package for one library a couple of years ago, but back then I didn’t find documentation for this (as I now understand, that’s because one does not really create a “package”) and abandonned the task. I never thought I’ll be looking at vcpkg again, but recently I discovered that some teams have been switching from Conan to vcpkg, which sounded intriguing and promising, as we still needed a package manager for our projects.
Why we didn’t proceed with Conan
I think, this is because we chose a wrong approach to it. At the first glance it seemed that integrating Conan as a part of our build system would be too disruptive, so we decided to just pack pre-built artifacts from the install
destination with export-pkg -pf. So we were not making source packages and were not implementing the build()
method in recipes.
Effectively, we were prebuilding all our dependencies for all the platforms that we target, and that turned out to be very (very) time-consuming, so we pretty much didn’t progress beyond that small subset of dependencies from the pilot project created during research.
So yeah, perhaps if we chose to integrate Conan into our buildsystem and build dependencies from sources, the result would be better (faster). Why didn’t we do so? Well, we have been already working with other package managers before - APT/deb, NuGet, npm and others that our users were requesting - and these packages are mostly just archives with certain meta-information, and that is how we were looking at Conan too, not expecting it to also be able to manage building. It also didn’t help that we were reading Conan’s documentation rather chaotically: I remember I was googling for something like “Conan package pre-built binaries” and getting directly to those pages without properly studying the rest of the documentation.
Anyway, having covered dependencies of the pilot project (one of our libraries) with Conan, we invited a couple of other teams to test it out, and immediately it turned out that they use a different combination of platforms/compilers, which we didn’t pre-build for, and so they had to force/override specific versions (for example, with -s compiler.toolset="v142"
and -DCONAN_DISABLE_CHECK_COMPILER=1
), as they were not able to build missing binaries (because we did not implement the build()
method in recipes).
But even that was not all, as users (understandably) wanted to fetch 3rd-party dependencies from Conan Center and our packages from our Artifactory, while we were fetching everything from our Artifactory (using different user/channel
values too), and such a situation isn’t handled too nicely with Conan (or we didn’t find a way).
Finally, not everyone in our team liked the idea of complicating the build toolchain by adding yet another tool, because not only one needs to run one more thing before building the project, but also the installation of that thing isn’t very straightforward, as one needs to have Python, pip and only then Conan can be installed. Yes, it is rather a trivial setup, and yet some have managed to struggle even with this. You really shouldn’t underestimate how common the question/challenge of installing pip is.
About vcpkg
In short, vcpkg is a combination of a CMake toolchain and a CLI tool. Together they handle the following:
- figuring out build environment (platform, compiler, linking);
- fetching dependencies sources, patching them if required;
- building or restoring those dependencies before configuring the main project.
Restoring means that if dependencies have been already built before, then instead of building them again vcpkg will restore their pre-built binary packages either from local cache or from a remote storage. This functionality is called binary caching, and as with Conan that’s our main reasoning for using vcpkg for resolving dependencies - to avoid re-building the code that changes only so often.
Here’s also a good talk at NDC Oslo conference about vcpkg, Conan and in general about dependency management for C++ projects.
Schematically simplified vcpkg functionality can be visualized like this:

There will be more details about what’s going on here later, but in short it shows a project that depends on 3 libraries (and 3 CMake helpers), and vcpkg handles fetching and building dependencies before building the main project. If dependencies have been already built before, then they are just restored from cache.
For us local cache on CI/CD buildbots will be absolutely enough, but later we’ll probably also consider optionally storing pre-built dependencies packages in a centralized remote (in-house) storage, perhaps in a form of NuGet packages. In terms of the schema above the only change in that case would be that the cache will be stored not on the same machine but on some other server.
Installation
You need to have one of the latest CMake versions in the system, otherwise vcpkg installer script will download it for you and put it somewhere in the system, so you’ll have more than one CMake on your computer. In my case it required CMake 3.24 as a minimum.
Regarding system environment, I have Windows, Mac OS and GNU/Linux hosts, and while here in the article it is assumed that one uses Mac OS environment, all the steps will be 99% the same on any other platform. And on Windows, as usual, I recommend to use Git BASH.
The installation of vcpkg starts with cloning its repository:
$ cd /path/to/programs/
$ git clone git@github.com:microsoft/vcpkg.git
$ cd ./vcpkg/
That will bring you the first of the two components - the CMake toolchain - it will be placed to /path/to/programs/vcpkg/scripts/buildsystems/vcpkg.cmake
.
Before continuing, set VCPKG_DISABLE_METRICS
environment variable to prevent anal telemetry probing (it’s Microsoft, after all).
Now you can start the “installation” by executing this script:
$ ./bootstrap-vcpkg.sh
It will in turn call ./scripts/bootstrap.sh
script and download the second component - vcpkg CLI tool binary from its repository. In my case it was 2022-09-20 version. It also does some other things, which you can look up in the script file. I would prefer to do all these things myself than letting a script execute a bazillion commands in my system.
As a result you’ll have the tool binary at /path/to/programs/vcpkg/vcpkg
(vcpkg.exe
on Windows). For convenience, you might want to add /path/to/programs/vcpkg
to your PATH
(so you could call vcpkg CLI tool from anywhere). And while you are at it, also set VCPKG_ROOT
environment variable to /path/to/programs/vcpkg
.
What is a vcpkg package
Right away, it is not correct to call them “packages”, because they are more like recipes for configuring and building. In vcpkg’s terminology they are called “ports”.
In its basic form a vcpkg port is just these two files:
./glfw
├── portfile.cmake
└── vcpkg.json
here:
portfile.cmake
- CMake instructions for fetching sources, configuring, building, installing and grooming;vcpkg.json
- information about the package/port: name, description, homepage, version and its own dependencies.
If a library, which you want to add as a dependency to you project, is already has a nice and modern CMake project file and with proper installation too, then you won’t need anything but these two files to make a package for it.
But quite often (way too often) you will need at the very least to patch the original library’s CMakeLists.txt
or even create one from scratch, as the original library’s repository might not have anything for CMake out of the box. Either way, the point is that portfile.cmake
file isn’t a replacement for a potentially missing CMakeLists.txt
, as it’s purpose will always be to get the sources, patch them if needed, configure, build and install the library binaries/artifacts.
Missing/additional files are supposed to be just placed into the package folder alongside and refered to from portfile.cmake
. For example, here’s how a more complex package might look like:
./dearimgui
├── CMakeLists.txt
├── fixing-something.patch
├── portfile.cmake
└── vcpkg.json
here:
CMakeLists.txt
- if you are unlucky enough to require a library without CMake support;fixing-something.patch
- in case there is some problem or a change that you need to fix/apply, and the library maintainer doesn’t want / can’t do it;portfile.cmake
- applies thefixing-something.patch
after fetching library sources and copiesCMakeLists.txt
to the library source directory (or wherever you want);vcpkg.json
- name, version, etc.
Registry
Packages or actually ports need to be stored somewhere, and such place is called a “registry”. As a matter of fact, the repository that you cloned to install vcpkg is at the same time a registry too: packages ports are located in the ./ports
folder. You can take a look at any of them to see for yourself that in the most simple cases it is indeed just a duo of portfile.cmake
and vcpkg.json
files. You’ll also see that in many cases it isn’t so simple.
And so yes, you can use Microsoft’s vcpkg repository as a registry for your project. But we of course wanted to have our own in-house registry on our own server maintained by ourselves. And what would you know, setting up your own vcpkg registry is the fucking easiest thing in the world.
Here’s Microsoft’s own documentation (and also this article) on the matter. Basically, a vcpkg registry is just a Git repository with the following structure:
├── ports
└── versions
That Git repository can be hosted on a GitHub, GitLab, Gitea, whatever other service or just a regular bare Git repository available via SSH/HTTP. Isn’t it fucking great! I was so happy, I almost pissed myself. That is an absolute respect and a huge +1 to Microsoft’s karma.
So let’s create our own vcpkg registry. For starters it will contain just one port - GLFW library - and the structure of the repository will then be the following:
├── ports
│ └── glfw
│ ├── portfile.cmake
│ └── vcpkg.json
└── versions
├── baseline.json
└── g-
└── glfw.json
As an example for the reference, I’ve created my own public registry here (right now it contains several other ports too).
Port structure
With my GLFW port as an example.
vcpkg.json
The vcpkg.json
contents:
{
"name": "glfw",
"version": "3.3.8",
"description": "A multi-platform library for OpenGL/Vulkan/etc, window and input",
"homepage": "https://www.glfw.org/",
"dependencies": [
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
}
]
}
These vcpkg-cmake
and vcpkg-cmake-config
dependencies will be covered in a minute. For now note the "host": true
- it means that these dependencies are so-called “host dependencies”, so they are fetched and installed before the packages that depend on them.
portfile.cmake
The portfile.cmake
looks like this:
# where to get the package sources from
vcpkg_from_git(
OUT_SOURCE_PATH SOURCE_PATH
URL git@github.com:glfw/glfw.git # Git repository cloning URL
REF 7482de6071d21db77a7236155da44c172a7f6c9e # commit hash (pointing to a version tag)
)
# how to configure the project
# thankfully, GLFW already has CMakeLists.txt
vcpkg_cmake_configure(
# where CMakeLists.txt is (here's it's on the root level of the project)
SOURCE_PATH "${SOURCE_PATH}"
# CMake configuration options, just regular -D stuff
OPTIONS
-DGLFW_BUILD_EXAMPLES=0
-DGLFW_BUILD_TESTS=0
-DGLFW_BUILD_DOCS=0
)
# this one actually builds and installs the project
vcpkg_cmake_install()
# this will (try to) fix possible problems with imported targets
vcpkg_cmake_config_fixup(
PACKAGE_NAME "glfw3" # if the project name (glfw3) is different from the port name (glfw)
CONFIG_PATH "lib/cmake/glfw3" # where to find project's CMake configs
)
# don't know what this is used for, I have never needed it yet
#vcpkg_fixup_pkgconfig()
# this one you just need to have, and sometimes you'll need to delete even more things
# feels like a crutch, but okay
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
# vcpkg expects license information to be contained in the file named "copyright"
file(
INSTALL "${SOURCE_PATH}/LICENSE.md"
DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}"
RENAME copyright
)
The ${PORT}
thing is of course not a standard CMake variable. Here you can take a look at the full(?) list of vcpkg variables that can be used in portfiles.
Now, if you, like me, started by reading that blog post, then at first you’ll have the following functions in your portfile.cmake
:
But when you’ll go to read about any of these in the documentation, you’ll discover that they have been already deprecated, and instead you should use these ones:
And that is why our example GLFW package depends on vcpkg-cmake
and vcpkg-cmake-config
packages, which are so-called helper packages, as they provide these functions to GLFW’s portfile.
Why the deprecated variants come out of the box and the new ones are placed into helper packages - I don’t know. Perhaps that is so they could be easier updated to newer versions?
Another thing here is that if we don’t want to use Microsoft’s registry, then we’ll obviously need to copy these packages (vcpkg-cmake and vcpkg-cmake-config) to our registry, and then the structure of the registry will be the following:
├── ports
│ ├── glfw
│ │ ├── portfile.cmake
│ │ └── vcpkg.json
│ ├── vcpkg-cmake
│ │ ├── portfile.cmake
│ │ ├── vcpkg-port-config.cmake
│ │ ├── vcpkg.json
│ │ ├── vcpkg_cmake_build.cmake
│ │ ├── vcpkg_cmake_configure.cmake
│ │ └── vcpkg_cmake_install.cmake
│ └── vcpkg-cmake-config
│ ├── copyright
│ ├── portfile.cmake
│ ├── vcpkg-port-config.cmake
│ ├── vcpkg.json
│ └── vcpkg_cmake_config_fixup.cmake
└── versions
├── baseline.json
├── g-
│ └── glfw.json
└── v-
├── vcpkg-cmake-config.json
└── vcpkg-cmake.json
And now some more details about these functions from the portfile.
vcpkg_from_git
The vcpkg_from_git() function fetches the library sources from a Git repository.
Here I’m using a GitHub repository that is available via SSH, so I need to have the following in my ~/.ssh/config
:
Host github.com
HostName github.com
User git
PreferredAuthentications publickey
IdentityFile ~/.ssh/MY-GITHUB-SSH-KEY
Or you can just as well replace the URL
value with https://github.com/glfw/glfw.git
variant, but then in case of private repositories you’ll need to set-up the credentials thing.
GitHub here is used as an example, and instead there of course can be any other service or just a bare Git repository on your server. Speaking of which, I once again would like to recommend you to clone/mirror all your 3rd-party dependencies sources to your servers, so you don’t rely on 3rd-party services outside of your IT infrastructure.
Another note about vcpkg_from_git()
is that Microsoft’s tutorial examples use different functions for fetching sources: vcpkg_from_github() and vcpkg_from_gitlab(). But first of all, for me those failed (even though I did provide required access tokens); and secondly, what is the point of using these at all, if vcpkg_from_git()
with SSH-key authentication is absolutely enough?
vcpkg_cmake_configure
This function does the regular CMake project configuration. You need to:
- set path to
CMakeLists.txt
folder in theSOURCE_PATH
. Usually it’s the root level of the project sources, which conveniently is stored in the${SOURCE_PATH}
variable (looks a bit confusing, I know); - provide
-D
options (if any). It is recommended to disable building samples/demos/tests and documentation-related stuff.
If the project doesn’t have a CMakeLists.txt
, this command will naturally fail, just like as CMake itself would, so you’ll need to create one yourself and add it to the port.
vcpkg_cmake_install
This function builds and installs the configured project. It actually calls the vcpkg_cmake_build() function and sets TARGET install
.
vcpkg_cmake_config_fixup
That one fixes potential problems with installed CMake targets, as some projects do some really weird shit with their CMake configs. For example, you can get the following error if you won’t call this function after vcpkg_cmake_install()
:
Policy CMP0111 is not set: An imported target missing its location property fails during generation
and many other different problems.
In order for vcpkg_cmake_config_fixup()
function to work it needs to know where the project installs its CMake configs to. If those are installed into an unusual place (they usually are), then it will fail with an error like this:
Building some-library[core]:x86-windows...
-- Installing port from location: /path/to/your/vcpkg-registry/ports/some-library
-- Fetching git@github.come:retifrav/some-library.git cb96b1bd1af1e15eae2660097b4db2ddeaa94313...
-- Extracting source /path/to/programs/vcpkg/downloads/some-library-cb96b1bd1af1e15eae2660097b4db2ddeaa94313.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/some-library/src/ddeaa94313-5e7aebed2b.clean
-- Configuring x64-osx
-- Building x64-osx-dbg
-- Building x64-osx-rel
CMake Error at scripts/cmake/vcpkg_cmake_config_fixup.cmake:81 (message):
'/path/to/programs/vcpkg/packages/some-library_x64-osx/debug/share/some-library'
does not exist.
To fix that you need to go to /path/to/programs/vcpkg/packages/some-library_x64-osx
and find where exactly this project installs its CMake files. For example, if they are in cmake/SomeLibrary
, then your vcpkg_cmake_config_fixup()
should have the following arguments:
vcpkg_cmake_config_fixup(
# if the project name (SomeLibrary) is different from the port name (some-library)
PACKAGE_NAME "SomeLibrary"
# where project's CMake configs are installed by default
CONFIG_PATH "cmake/SomeLibrary"
)
And as you’ve already seen, for GLFW port it’s these:
vcpkg_cmake_config_fixup(
PACKAGE_NAME "glfw3"
CONFIG_PATH "lib/cmake/glfw3"
)
If CMake configs are nowhere to find, then probably that library doesn’t have a proper installation (or mayby no installation at all), and then you’ll need to patch its CMakeLists.txt
.
The vcpkg_cmake_config_fixup()
function also organizes installed CMake configs and puts them into share/PACKAGE-NAME
folder. Without doing this you can get the following warnings:
-- Performing post-build validation
The following cmake files were found outside /share/some-library. Please place cmake files in /share/some-library.
/path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/cmake/SomeLibraryConfig.cmake
/path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/cmake/SomeLibraryConfigVersion.cmake
/path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/cmake/SomeLibraryTargets-release.cmake
/path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/cmake/SomeLibraryTargets.cmake
/path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/debug/cmake/SomeLibraryConfig.cmake
/path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/debug/cmake/SomeLibraryConfigVersion.cmake
/path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/debug/cmake/SomeLibraryTargets-debug.cmake
/path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/debug/cmake/SomeLibraryTargets.cmake
What I haven’t figured yet is how to handle the situation when the library and its vcpkg port have different names, for example the library target name (and CMake config) is SomeLibrary
but the port name is some-library
. In that case the contents of share
folder will end up being like this:
├── SomeLibrary
│ ├── SomeLibraryConfig.cmake
│ ├── SomeLibraryConfigVersion.cmake
│ ├── SomeLibraryTargets-debug.cmake
│ ├── SomeLibraryTargets-release.cmake
│ └── SomeLibraryTargets.cmake
└── some-library
├── copyright
├── vcpkg.spdx.json
└── vcpkg_abi_info.txt
I think this is not a correct structure, as it seems that the intention is to have one folder per port. It all works fine like this, but I’m concerned that it might cause problems in future.
Versions
baseline.json
The baseline.json
, as I understood, lists default versions of all the packages that are available in that registry:
{
"default":
{
"dearimgui":
{
"baseline": "1.88.0",
"port-version": 0
},
"glad":
{
"baseline": "0.1.36",
"port-version": 0
},
"glfw":
{
"baseline": "3.3.7",
"port-version": 0
},
"...":
{
"...": "..."
}
}
}
Then if a project has no overrides, these will be the versions vcpkg will get for it. And if a project lists a dependency with version>=
field, then in case it’s lower than the baselined, vcpkg will skip all higher versions untill the baselined value; otherwise it will take the first versions that is equal or higher.
Versions file
Individual *.json
files establish which version of the port is available in which commit, for example here’s glfw.json
:
{
"versions": [
{
"version": "3.3.8",
"git-tree": "597fa07e1afd57c50dfdbeb0c0d28f4157748564"
}
]
}
To get the commit hash value (for the git-tree
property) of the current version of the glfw
port you need to run the following:
$ cd /path/to/your/vcpkg-registry
$ git rev-parse HEAD:./ports/glfw
597fa07e1afd57c50dfdbeb0c0d28f4157748564
And it might not be obvious, but it means that whenever you make any changes to glfw
port and commit them, you should not immediately push that commit to server. First you need to run git rev-parse
to get the new hash value, set this value to git-tree
, amend (git commit --amend --no-edit
) your commit and only then push your commit to server. Here are some more details about that.
Several versions of a port
A port can have more than one version. To add another version you need to, with my glfw
port as an example:
- Set a new Git hash (pointing to a different version tag) in
./ports/glfw/portfile.cmake
; - Update the
version
value in./ports/glfw/vcpkg.json
; - Commit and do the
git rev-parse
thing; - Add (not replace) a new version to
./versions/g-/glfw.json
:{ "versions": [ { "version": "3.3.8", "git-tree": "597fa07e1afd57c50dfdbeb0c0d28f4157748564" }, { "version": "3.3.7", "git-tree": "45af0346d2ec926146091ab374c475cac5aaf055" } ] }
So at first I had GLFW version 3.3.8
and now I added version 3.3.7
. So even though this commit is “newer”, and the versions order is “wrong”, it doesn’t matter at all, as it’s all just Git hashes pointing to certain “states” of the repository.
If you’ll get this error trying to install a dependency in your project:
-- Running vcpkg install
Fetching registry information from git@github.com:retifrav/vcpkg-registry.git (HEAD)...
git archive failed with message:
error: git failed with exit code: (128).
fatal: not a tree object: cdeffa673205b611d8ced28468cce6a06f1fdffd
then it could be because you didn’t update the git-tree
value in that dependency’s version JSON in the registry, or perhaps the baseline
value in your project’s vcpkg-configuration.json
points to a wrong commit.
Checking versions and hashes
It is quite easy to forget to update the git-tree
value, so you might end up with a somewhat broken port, like it is shown in the previous section when it fails with an error. But there also might be a different situation: when git-tree
points to an existing state of the registry, so there will be no error, but you won’t be getting the actually latest ports state for that version either.
And the bigger your registry will get, the harder it will be to watch for potential mismatches, so you’ll likely want to automate this somehow. For example, with a shell script:
$ ./scripts/check-versions-and-hashes.sh
port | version | |
-------------------------------------------------------------------------------------------------------------------------
curl | 8.1.2 | 308dc1fccea69d04003fef9103c9d7bd13d0bb32 | 308dc1fccea69d04003fef9103c9d7bd13d0bb32
dearimgui | 1.88.0 | fda742f0dd720fcd2af3cb8e946173069d70435f | a8838baa1c0125a8a2aaccc3889f8518ecfeac40
decovar-vcpkg-cmake | 2022-10-15 | 5b70c3ba46b51c88044c335b8ad030f0edc609e5 | 5b70c3ba46b51c88044c335b8ad030f0edc609e5
e57format | 2.3.0 | e01f35b5353848abef4cb47e8ff9f25dbb05ad81 | e01f35b5353848abef4cb47e8ff9f25dbb05ad81
glad | 0.1.36 | 2341f5144ce8e76a256289517d61abb4ab9fb72c | 2341f5144ce8e76a256289517d61abb4ab9fb72c
glfw | 3.3.8 | 597fa07e1afd57c50dfdbeb0c0d28f4157748564 | ff428db2871ef4e409c2ea9f29a866b63ba5b90b
icu | 72.1.0 | 330edeacb91afe9d9aa0007cbacc08f6a2b4a3f3 | 330edeacb91afe9d9aa0007cbacc08f6a2b4a3f3
json-nlohmann | 3.11.2 | 489dbd7358b610e1153e93accf36123a5afe3ce3 | 489dbd7358b610e1153e93accf36123a5afe3ce3
lyra | 1.6.1 | 0cb627c7ee8f8ec2769b41b41f82b335c50e984b | 0cb627c7ee8f8ec2769b41b41f82b335c50e984b
lzma | 22.1.0 | 04277d0cee83f6c4b8ce44f6756ed044b2691fbe | 04277d0cee83f6c4b8ce44f6756ed044b2691fbe
sqlite | 3.41.0 | 765037d4fa9cee6d6c7639006e546cf1ad8d675a | 765037d4fa9cee6d6c7639006e546cf1ad8d675a
vcpkg-cmake | 2022-08-18 | 84c200e8e625d4d99b1649525fcdf81a73197078 | 84c200e8e625d4d99b1649525fcdf81a73197078
vcpkg-cmake-config | 2022-02-06 | e23b39e21f0dd42ecc615262640d211c39696aa1 | e23b39e21f0dd42ecc615262640d211c39696aa1
xerces-c | 3.2.4 | ebfeb33e67607b103c583656109cbfeeb1406260 | ebfeb33e67607b103c583656109cbfeeb1406260
zlib | 1.2.12 | 7feb1b251066f9213881134c320cba2d853d1b45 | 7feb1b251066f9213881134c320cba2d853d1b45
The following ports have a mismatch between their stated and actual Git hashes:
- dearimgui
- glfw
The hashes matching is highlighted with colors, here’s a screenshot:

Aside from the colors and summary in the end of the output, if there is at least one mismatch, the exit code is set to non-zero, so you can also use this script as a pre-commit Git hook.
At the moment the script has a flaw of expecting certain structure of versions file, in particular it will fail to get the git-tree
property if it doesn’t go right on the next line after the version
property (for example, it could be a port-version
property there instead).
Triplets
A vcpkg triplet is a file with a set of values that together “identify” the target platform: CPU architecture, type of linking and so on. One could probably say that it’s like Conan profiles, but with fewer(?) parameters.
Default triplets are located here: /path/to/programs/vcpkg/triplets/
. If you are not happy with the standard collection, you can add your own triplets as well, but don’t edit the default ones, as it will certainly backfire at you at some point later.
I learned about triplets when I noticed that dependencies in one of my Windows projects were built as DLLs (SHARED
libraries), even though I did not set -DBUILD_SHARED_LIBS=1
. And it turned out that on Windows by default vcpkg uses x64-windows.cmake
triplet, which contents are:
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE dynamic)
Like I said, while you can set static
value for the VCPKG_LIBRARY_LINKAGE
here, I would definitely not recommend doing that. Instead, for this particular case, you can use another standard triplet - x64-windows-static.cmake
- and that one will build your libraries as STATIC
. To use that triplet instead of the default one, set -DVCPKG_TARGET_TRIPLET="x64-windows-static"
. And if there wasn’t such triplet in the standard collection, then you could have just created one yourself.
Creating your own ports
A good portion of this topic is already covered in the section about vcpkg registry, but there are some more things to tell about.
The Microsoft’s registry already has a good collection of packages ports. If those are not enough, for instance if you’d like to build some library differently or if your library of interest is simply missing from the registry, then you can always create your own port and store it in your registry or/and publish it to Microsoft’s registry.
When making your own ports, you can use Microsoft’s registry as a great source of configuration options, techniques and workarounds for various problems one might encounter. You can literally go from port to port and study the way people configure, build (and patch) sources, instead of trial-and-erroring that yourself for hours days.
Portfile
Creating a vcpkg port of a library in simple cases is just a matter of writing a portfile for it and listing its version(s). But it’s not often that you’ll have such simple cases. Usually you’ll need to add new files, patch sources or/and introduce features.
Adding files
Many libraries don’t have CMake support out of the box, so often you’ll need to create and add CMakeLists.txt
for them. To do that you just put it into the port folder and add the following command to the portfile.cmake
somewhere before vcpkg_cmake_configure()
:
file(COPY "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt" DESTINATION "${SOURCE_PATH}")
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
)
That’s exactly how I did it for my Dear ImGui port.
Any other files can be added the same way.
Patches
When you don’t need to add new files but would like to change existing ones, you can apply a patch, which is done by simply adding PATCHES
argument to vcpkg_from_git()
. When I saw how easy this is, I almost couldn’t believe it, especially given that with Conan I had to handle this myself (back then I didn’t know that Conan probably can do it as well).
For example, if we want to patch something in GLFW sources:
vcpkg_from_git(
OUT_SOURCE_PATH SOURCE_PATH
URL git@github.com:glfw/glfw.git
REF 45ce5ddd197d5c58f50fdd3296a5131c894e5527
PATCHES
some.patch # ${CMAKE_CURRENT_LIST_DIR}/some.patch
another.patch # ${CMAKE_CURRENT_LIST_DIR}/another.patch
)
Important to note here that some.patch
must be a Git patch, not a bare diff
(from GNU Diffutils) output. To create such a patch, go to your local GLFW’s repository (checked out on that particular commit), edit required files, stage your changes and then:
$ git diff --cached --binary > some.patch
$ mv ./some.patch /path/to/your/vcpkg-registry/ports/glfw/
Features
You can control the way a project is built via so-called “features”, which essentially are a simple mapping to CMake -D
options.
This is done with vcpkg_check_features() function. For example, I have a port of Dear ImGui library and I added a feature to build it with GLFW support:
# mapping features array from vcpkg.json to a list of CMake options
# already prepended with -D and set to ON
# and saving that list to FEATURE_OPTIONS variable
vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
backend-glfw BACKEND_GLFW
)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
OPTIONS
# passing the list of options to CMake configure
${FEATURE_OPTIONS} # it now contains -DBACKEND_GLFW=ON
)
This BACKEND_GLFW
option is used in CMakeLists.txt to add GLFW-specific code to the Dear ImGui build.
For this mapping to work the port’s vcpkg.json contains optional backend-glfw
feature (along with dependency on glfw
port):
{
"name": "dearimgui",
"...": "...",
"features":
{
"backend-glfw":
{
"description": "Using GLFW as graphics backend",
"dependencies":
[
"glfw"
]
}
}
}
And so other projects can now depend on this port like this:
{
"name": "glfw-imgui-example",
"...": "...",
"dependencies":
[
"...",
{
"name": "dearimgui",
"features":
[
"backend-glfw"
]
}
]
}
Platform-specific features
It is possible to enable features only for certain platforms. I discovered that when my Xerces-C++ port failed to build for iOS (using triplet arm64-ios
):
FAILED: src/CMakeFiles/xerces-c.dir/xercesc/util/PlatformUtils.cpp.o
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -DHAVE_CONFIG_H=1 -DXERCES_BUILDING_LIBRARY=1 -D_FILE_OFFSET_BITS=64 -D_THREAD_SAFE=1 -I/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/arm64-ios-dbg -I/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src -I/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/arm64-ios-dbg/src -fPIC -Wall -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wextra -Wformat=2 -Wimplicit-atomic-properties -Wmissing-declarations -Wno-long-long -Woverlength-strings -Woverloaded-virtual -Wredundant-decls -Wreorder -Wswitch-default -Wunused-variable -Wwrite-strings -Wno-variadic-macros -fstrict-aliasing -g -arch arm64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.5.sdk -std=gnu++14 -MD -MT src/CMakeFiles/xerces-c.dir/xercesc/util/PlatformUtils.cpp.o -MF src/CMakeFiles/xerces-c.dir/xercesc/util/PlatformUtils.cpp.o.d -o src/CMakeFiles/xerces-c.dir/xercesc/util/PlatformUtils.cpp.o -c /programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/PlatformUtils.cpp
In file included from /Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/PlatformUtils.cpp:82:
In file included from /Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/XMLNetAccessor.hpp:26:
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/XMLURL.hpp:277:19: warning: cast from 'const xercesc_3_2::XMLURL *' to 'xercesc_3_2::XMLURL *' drops const qualifier [-Wcast-qual]
((XMLURL*)this)->buildFullText();
^
In file included from /Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/PlatformUtils.cpp:119:
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/Transcoders/MacOSUnicodeConverter/MacOSUnicodeConverter.hpp:99:12: error: unknown type name 'TextEncoding'
, TextEncoding textEncoding
^
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/Transcoders/MacOSUnicodeConverter/MacOSUnicodeConverter.hpp:116:2: error: unknown type name 'CollatorRef'
CollatorRef fCollator; // Our collator
^
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/Transcoders/MacOSUnicodeConverter/MacOSUnicodeConverter.hpp:130:2: error: unknown type name 'TextEncoding'
TextEncoding discoverLCPEncoding();
^
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/Transcoders/MacOSUnicodeConverter/MacOSUnicodeConverter.hpp:150:6: error: unknown type name 'TECObjectRef'
TECObjectRef textToUnicode,
^
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/Transcoders/MacOSUnicodeConverter/MacOSUnicodeConverter.hpp:151:6: error: unknown type name 'TECObjectRef'
TECObjectRef unicodeToText,
^
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/Transcoders/MacOSUnicodeConverter/MacOSUnicodeConverter.hpp:199:5: error: unknown type name 'TECObjectRef'
TECObjectRef mTextToUnicode;
^
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/Transcoders/MacOSUnicodeConverter/MacOSUnicodeConverter.hpp:200:5: error: unknown type name 'TECObjectRef'
TECObjectRef mUnicodeToText;
^
/Users/USERNAME/programs/vcpkg/buildtrees/xerces-c/src/a3919277d9-dc0ef85571.clean/src/xercesc/util/PlatformUtils.cpp:485:8: error: incompatible pointer types assigning to 'xercesc_3_2::XMLTransService *' from 'xercesc_3_2::MacOSUnicodeConverter *'
tc = new MacOSUnicodeConverter(fgMemoryManager);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning and 8 errors generated.
The problem here is that for building for iOS Xerces-C++ requires ICU dependency. Okay, let’s add it to dependencies
then, but since it is not needed when building on other platforms, better to do it through an optional feature:
{
"name": "xerces-c",
"...": "...",
"features":
{
"icu":
{
"description": "Enable ICU support",
"dependencies":
[
"icu"
]
}
}
}
But how to make this feature enabled only on iOS platform? As I understood it, one option would be to add a dependency on itself, for example like it is done in FFmpeg port. But I decided to do it differently - in the manifest of a port (E57Format) that depends on Xerces-C++:
{
"name": "e57format",
"...": "...",
"dependencies":
[
{
"...": "..."
},
{
"name": "xerces-c",
"platform": "!ios"
},
{
"name": "xerces-c",
"features":
[
"icu"
],
"platform": "ios"
}
]
}
Now iOS is the only target platform where icu
feature will be enabled, and Xerces-C++ (starting with version 3.2.4) will be able to build with ICU dependency.
Admittedly, this looks crutchy, but I am yet to find a better way.
Feature-dependent features
That is something I haven’t figured out yet.
At some point we’ve got into a situation where one of our projects required JPEG dependency, but we also wanted to have an option to use jpeg-turbo instead. So:
{
"name": "some",
"...": "...",
"dependencies":
[
"..."
],
"default-features":
[
"use-jpeg-turbo"
],
"features":
{
"use-jpeg-original":
{
"description": "Use original JPEG library",
"dependencies":
[
"jpeg"
]
},
"use-jpeg-turbo":
{
"description": "Use jpeg-turbo",
"dependencies":
[
"jpeg-turbo"
]
}
}
}
And in the project’s CMakeLists.txt
:
# ...
option(PREFER_JPEG_TURBO "Use jpeg-turbo instead of original JPEG" 1)
# ...
if(PREFER_JPEG_TURBO)
# if it fails to find it, perhaps implement a fallback to the original JPEG
find_package(jpeg-turbo CONFIG REQUIRED)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE jpeg-turbo::turbojpeg-static)
else()
find_package(jpeg CONFIG REQUIRED)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE jpeg)
endif()
But that project also depends on PDFium, which in turn depends on JPEG/jpeg-turbo too, and in our PDFium port we resolve this the same way via vcpkg (having disabled building those from vendored sources). But the problem now is how to specify which JPEG library should PDFium port use (meaning that it is controlled via PDFium port features)?
I guess, it would be great if one could set dependencies between features within the same manifest, something like this:
{
"name": "some",
"...": "...",
"dependencies":
[
"..."
],
"default-features":
[
"use-jpeg-turbo"
],
"features":
{
"use-jpeg-original":
{
"description": "Use original JPEG library",
"dependencies":
[
"jpeg"
]
},
"use-jpeg-turbo":
{
"description": "Use jpeg-turbo",
"dependencies":
[
"jpeg-turbo"
]
},
"pdf":
{
"description": "Enable PDF capabilities",
"dependencies":
[
{
"name": "pdfium",
"features":
[
{
"if-feature-enabled": "use-jpeg-turbo",
"then": "with-jpeg-turbo",
"else": "with-jpeg-original"
}
]
}
]
},
}
}
But there is no such thing at the moment, and looking at this construction I doubt that it will ever be added. So for now I guess the only option would be to split pdf
feature like this:
{
"name": "some",
"...": "...",
"dependencies":
[
"..."
],
"default-features":
[
"use-jpeg-turbo",
"pdf-jpeg-turbo"
],
"features":
{
"use-jpeg-original":
{
"description": "Use original JPEG library",
"dependencies":
[
"jpeg"
]
},
"use-jpeg-turbo":
{
"description": "Use jpeg-turbo",
"dependencies":
[
"jpeg-turbo"
]
},
"pdf-jpeg-original":
{
"description": "Enable PDF capabilities (using original JPEG)",
"dependencies":
[
{
"name": "pdfium",
"features":
[
"with-jpeg-original"
]
}
]
},
"pdf-jpeg-turbo":
{
"description": "Enable PDF capabilities (using jpeg-turbo)",
"dependencies":
[
{
"name": "pdfium",
"features":
[
"with-jpeg-turbo"
]
}
]
}
}
}
Which of course means that one can mess-up features by seting for example use-jpeg-turbo
and pdf-jpeg-original
at the same time, which will collide on dependencies installation.
CMake helpers
As you saw in portfile.cmake, one can use helper packages, which are not libraries for development but CMake modules for extending portfiles functionality. The vcpkg-cmake
and vcpkg-cmake-config
packages are examples of such helpers.
Naturally, you can create a helper like this yourself. Here’s a one I made (this version was not entirely correct, so it got updated later):
./ports/decovar-vcpkg-cmake
├── Config.cmake.in
├── Installing.cmake
├── decovar_vcpkg_cmake_ololo.cmake
├── license.txt
├── portfile.cmake
├── vcpkg-port-config.cmake
└── vcpkg.json
If the helper is supposed to provide CMake function(s) which could be used in portfiles, then it must contain vcpkg-port-config.cmake
- these files get automatically imported/included by vcpkg. The contents of that file should be something like:
include("${CMAKE_CURRENT_LIST_DIR}/decovar_vcpkg_cmake_ololo.cmake")
The decovar_vcpkg_cmake_ololo.cmake
in turn contains the following:
include_guard(GLOBAL)
function(decovar_vcpkg_cmake_ololo)
message(STATUS "ololo")
endfunction()
So now any package that has decovar-vcpkg-cmake
helper as a dependency will be able to call decovar_vcpkg_cmake_ololo()
function in its portfile (but not in its CMakeLists.txt
).
Apart from this module my helper also contains Installing.cmake
and Config.cmake.in
- my common/shared installation instructions for CMake projects that are lacking those. In order to use them, I thought, a project needs to add the following to its CMakeLists.txt
:
#list(APPEND CMAKE_MODULE_PATH
# "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share/decovar-vcpkg-cmake"
#)
#include(Installing)
include("${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share/decovar-vcpkg-cmake/Installing.cmake")
But that turned out to be not a correct way of shipping and including common CMake modules. It might work like this, but as you see I am relying on VCPKG_TARGET_TRIPLET
here, and so it will fail when host dependencies are installed using a different triplet:
The following packages will be built and installed:
jsoncpp[core]:x64-windows-static -> 1.9.5
pdqsort[core]:x64-windows-static -> 2021.3.14
* vcpkg-cmake[core]:x64-windows -> 2022-08-18
* vcpkg-cmake-config[core]:x64-windows -> 2022-02-06
zstd[core]:x64-windows-static -> 1.5.2
You see how everything but helpers is using x64-windows-static
triplet, and helpers are using x64-windows
? That will cause the include()
statement to fail, because it will expect helper package to have a different path. To resolve this problem I thought that I could just set them both to the same value:
-DVCPKG_TARGET_TRIPLET=x64-windows-static -DVCPKG_HOST_TRIPLET=x64-windows-static
And then it works. But! Forcing host and target triplets to match is absolutely not correct, you should certainly not do that. As I later discovered, there is a CURRENT_HOST_INSTALLED_DIR variable that can be used in a portfile, and that made everything easier, as you’ll soon see.
For now, here’s the helper’s portfile.cmake
:
if(VCPKG_CROSSCOMPILING)
# should be FATAL_ERROR
message(WARNING "${PORT} is a host-only port, mark it as a host dependency in your ports")
endif()
file(
INSTALL
# to be used in other projects CMakeLists.txt project files
"${CMAKE_CURRENT_LIST_DIR}/Installing.cmake"
# to be used in other projects CMakeLists.txt project files
"${CMAKE_CURRENT_LIST_DIR}/Config.cmake.in"
# to be used in other projects portfiles
"${CMAKE_CURRENT_LIST_DIR}/decovar_vcpkg_cmake_ololo.cmake"
# this one just includes decovar_vcpkg_cmake_ololo.cmake
# and itself gets automatically included by vcpkg
"${CMAKE_CURRENT_LIST_DIR}/vcpkg-port-config.cmake"
DESTINATION
"${CURRENT_PACKAGES_DIR}/share/${PORT}"
)
file(
INSTALL
"${CMAKE_CURRENT_LIST_DIR}/license.txt"
DESTINATION
"${CURRENT_PACKAGES_DIR}/share/${PORT}"
RENAME copyright
)
set(VCPKG_POLICY_CMAKE_HELPER_PORT enabled)
The actual port that would be using this helper will be then able to copy the common files into its source directory using that CURRENT_HOST_INSTALLED_DIR
variable, as it will always point to the host dependencies path:
file(COPY
"${CURRENT_HOST_INSTALLED_DIR}/share/decovar-vcpkg-cmake/Installing.cmake"
DESTINATION "${SOURCE_PATH}"
)
file(COPY
"${CURRENT_HOST_INSTALLED_DIR}/share/decovar-vcpkg-cmake/Config.cmake.in"
DESTINATION "${SOURCE_PATH}"
)
And then in project’s CMakeLists.txt
it will be just this:
include("${CMAKE_CURRENT_SOURCE_DIR}/Installing.cmake")
You can take a look at how Dear ImGui port does it.
CMake wrapper
I couldn’t find documentation about this functionality, but it’s not very complex to figure out on your own. Basically, adding a CMake wrapper into your port allows you to “inject” CMake statements before or after find_package()
call.
For example, let’s take Xerces-C++ port. It builds fine, but when you’ll try to actually use it in some project on Mac OS (using x64-osx
or arm64-osx
triplets), then you will get the following linking errors:
Undefined symbols for architecture arm64:
"_CFRelease", referenced from:
xercesc_3_2::MacOSUnicodeConverter::upperCase(char16_t*) in libxerces-c.a(MacOSUnicodeConverter.cpp.o)
xercesc_3_2::MacOSUnicodeConverter::lowerCase(char16_t*) in libxerces-c.a(MacOSUnicodeConverter.cpp.o)
"_CFStringCreateMutableWithExternalCharactersNoCopy", referenced from:
xercesc_3_2::MacOSUnicodeConverter::upperCase(char16_t*) in libxerces-c.a(MacOSUnicodeConverter.cpp.o)
xercesc_3_2::MacOSUnicodeConverter::lowerCase(char16_t*) in libxerces-c.a(MacOSUnicodeConverter.cpp.o)
"_CFStringLowercase", referenced from:
xercesc_3_2::MacOSUnicodeConverter::lowerCase(char16_t*) in libxerces-c.a(MacOSUnicodeConverter.cpp.o)
"_CFStringUppercase", referenced from:
xercesc_3_2::MacOSUnicodeConverter::upperCase(char16_t*) in libxerces-c.a(MacOSUnicodeConverter.cpp.o)
...
This is because on Mac OS your project also needs to link to CoreServices framework (or rather Xerces-C++ requires that and so does your project).
Of course, you could resolve this by adding the following to your project’s CMakeLists.txt
:
if(APPLE) # probably also check that it's exactly Mac OS, otherwise this also applies to iOS and others
target_link_libraries(${CMAKE_PROJECT_NAME}
PRIVATE
"-framework CoreServices"
)
endif()
But then you would need to add that to all your projects that depend on Xerces-C++. And a better option would be to do it once in the CMake wrapper that comes from the port.
As I understand it, the way it works is that you add a file named vcpkg-cmake-wrapper.cmake
to your port, and it will “replace” (or actually wrap) the find_package()
call. Here’s how it looks for Xerces-C++:
# do some stuff before the package will be attempted to be found
# [stuff]
# ...
_find_package(${ARGS})
# or after the package has been found
# [stuff]
# ...
# for instance, add required linking to CoreServices on Mac OS
if(APPLE) # probably also check that it's exactly Mac OS
if(TARGET XercesC::XercesC)
set_target_properties(XercesC::XercesC
PROPERTIES
INTERFACE_LINK_LIBRARIES
"-framework CoreServices"
)
list(APPEND XercesC_LIBRARIES
"-framework CoreServices"
)
endif()
endif()
In this particular case it might be that linking to CoreServices could have been done in a better way, but it does work like this.
And then you install this wrapper into ${CURRENT_PACKAGES_DIR}/share/${PORT}
path. But be careful to check that port name and actual package name are the same. If they are different, then it is important that you install the wrapper exacty into the package folder and not into the port folder, otherwise it won’t be used. For instance, in my registry Xerces-C++ port name is xerces-c
and its package name is XercesC
, and so here’s how I install the wrapper:
file(
INSTALL "${CURRENT_PORT_DIR}/vcpkg-cmake-wrapper.cmake"
DESTINATION "${CURRENT_PACKAGES_DIR}/share/XercesC"
)
Multiple targets/components in one project
I should warn you first that the stuff described in this section is my own understanding of the listed scenarios and how one is supposed to solve them. My understanding might be wrong, and there might be better ways of dealing with this, so perhaps you shouldn’t blindly follow these instructions.
Making CMake configs discoverable
Say, your project has several targets/components and the “main” target depends on some/all of them. For example, here’s Config.cmake.in
template for the main target SomeLibrary
:
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(AnotherLibrary CONFIG REQUIRED)
find_dependency(DifferentLibrary CONFIG REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components(@PROJECT_NAME@)
So there are two internal targets which it depends on: AnotherLibrary
and DifferentLibrary
.
If you will make a port for this project as you normally would:
# ...
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(
PACKAGE_NAME "SomeLibrary"
CONFIG_PATH "cmake" # we install CMake configs here
)
…then projects that depend on this port will fail to configure:
CMake Error at /path/to/vcpkg/scripts/buildsystems/vcpkg.cmake:852 (_find_package):
Could not find a package configuration file provided by "AnotherLibrary" with
any of the following names:
AnotherLibraryConfig.cmake
AnotherLibrary-config.cmake
Add the installation prefix of "AnotherLibrary" to CMAKE_PREFIX_PATH or set
"AnotherLibrary_DIR" to a directory containing one of the above files. If
"AnotherLibrary" provides a separate development package or SDK, be sure it
has been installed.
Call Stack (most recent call first):
/path/to/cmake/share/cmake-3.25/Modules/CMakeFindDependencyMacro.cmake:47 (find_package)
build/vcpkg_installed/x64-windows/share/SomeLibrary/SomeLibraryConfig.cmake:30 (find_dependency)
/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake:852 (_find_package)
CMakeLists.txt:292 (find_package)
…because AnotherLibrary*.cmake
(as well as DifferentLibrary*.cmake
and all other targets/components of your SomeLibrary
project) configs are located inside build/vcpkg_installed/x64-windows/share/SomeLibrary/
folder, while CMake is looking for them one level up - in build/vcpkg_installed/x64-windows/share/COMPONENT-NAME-HERE/
.
I do not know what is the proper way of resolving this. I imagine, the installation procedure of SomeLibrary
project might need to be different, or maybe the main port needs to be split into several ports (one per component), but I don’t like either of these options, as they would require changing the project that installs just fine otherwise. So instead I’ve come up with the following:
# ...
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(
PACKAGE_NAME "SomeLibrary"
CONFIG_PATH "cmake" # we install CMake configs here
)
# internal components/dependencies CMake configs need to be available in ${CURRENT_PACKAGES_DIR}/share/COMPONENT-NAME-HERE,
# otherwise find_dependency() in consuming project will fail to find them. Until a better/proper way is found,
# they will be moved out to those paths "manually"
#
# list of components
list(APPEND SomeLibraryComponents
AnotherLibrary
DifferentLibrary
)
foreach(SomeLibraryComponent ${SomeLibraryComponents})
#message(STATUS "Moving out ${SomeLibraryComponent}")
# collect this component's CMake configs into a list
file(GLOB SomeLibraryComponentFiles "${CURRENT_PACKAGES_DIR}/share/SomeLibrary/${SomeLibraryComponent}*.cmake")
#message(STATUS "${SomeLibraryComponent} files: ${SomeLibraryComponentFiles}")
# create a folder for its configs where vcpkg would expect them to be
file(MAKE_DIRECTORY
"${CURRENT_PACKAGES_DIR}/share/${SomeLibraryComponent}"
)
# and move them there
foreach(SomeLibraryComponentFile ${SomeLibraryComponentFiles})
#message(STATUS "file: ${SomeLibraryComponentFile}")
get_filename_component(SomeLibraryComponentFileName ${SomeLibraryComponentFile} NAME)
file(RENAME ${SomeLibraryComponentFile} "${CURRENT_PACKAGES_DIR}/share/${SomeLibraryComponent}/${SomeLibraryComponentFileName}")
endforeach()
endforeach()
So right after the usual CMake config fixup for the main target I iterate through the list of all other targets/components and move their configs one level up to their own folders. I know, this looks like a dirty hack, but it works.
Partial components installation
There is a different scenario. It could be that your project contains several targets/components, which can be built and installed without building the entire project:
$ cd /path/to/some/project
$ mkdir build && cd $_
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="../install" ..
$ cmake --build . --target SomeLibrary
$ cmake --install ./libraries/SomeLibrary
# or
# $ cmake --install . --component SomeLibrary
And then let’s say one of your customers/users is asking you to make a vcpkg port only for this one library. He doesn’t need your entire project (at the very least because it takes too long to build the whole thing), he only needs this one library. So what do you do?
I’d say, ideally, it might be a good idea to move them out from your super repository into their own repositories and add them as dependencies to the main repository via vcpkg. But that is not always possible, and it wasn’t possible in our case (horrible legacy ways of including internal headers spread around and other stuff like that), so let’s see what else can be done here.
As far as I can tell, vcpkg doesn’t not have an out-of-the-box functionality for this scenario. As I mentioned earlier, vcpkg_cmake_install simply calls the vcpkg_cmake_build() function with TARGET install
parameter, which means that you too can set any build target for vcpkg_cmake_build()
:
# vcpkg_cmake_install() # we don't need the entire project
# build only one of the libraries/components
vcpkg_cmake_build(TARGET SomeLibrary)
But there is no CMake helper to run the installation (the vcpkg_cmake_install()
function doesn’t not have such a parameter), so one just has to fallback to running a bare CMake CLI command:
vcpkg_execute_build_process(
COMMAND cmake --install ./libraries/SomeLibrary
WORKING_DIRECTORY ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg
LOGNAME build-SomeLibrary-${TARGET_TRIPLET}-dbg
)
vcpkg_execute_build_process(
COMMAND cmake --install ./libraries/SomeLibrary
WORKING_DIRECTORY ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel
LOGNAME build-SomeLibrary-${TARGET_TRIPLET}-rel
)
vcpkg_cmake_config_fixup(
PACKAGE_NAME "SomeLibrary"
CONFIG_PATH "cmake" # we install CMake configs here
)
Yes, you need to install both Debug and Release configurations. As you can see, that certainly doesn’t look too nice, but it works. It is probably worth creating a CMake helper to wrap this into something nicer.
As a bonus complexity, some of our libraries/components in the main repository in turn depend on other internal libraries/components within that repository, and those need to be installed too. And the problem here is that the consuming project won’t be able to discover that other library, because that library’s CMake configs for find_package()
will end up in the SomeLibrary
folder. A resolution/workaround for that would be to add even more crutches, so the whole thing would look like this:
# the "main" library
vcpkg_cmake_build(TARGET SomeLibrary)
vcpkg_execute_build_process(
COMMAND cmake --install ./libraries/SomeLibrary
WORKING_DIRECTORY ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg
LOGNAME build-SomeLibrary-${TARGET_TRIPLET}-dbg
)
vcpkg_execute_build_process(
COMMAND cmake --install ./libraries/SomeLibrary
WORKING_DIRECTORY ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel
LOGNAME build-SomeLibrary-${TARGET_TRIPLET}-rel
)
# its dependency - another internal library
vcpkg_cmake_build(TARGET AnotherLibrary)
vcpkg_execute_build_process(
COMMAND cmake --install ./libraries/AnotherLibrary
WORKING_DIRECTORY ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg
LOGNAME build-AnotherLibrary-${TARGET_TRIPLET}-dbg
)
vcpkg_execute_build_process(
COMMAND cmake --install ./libraries/AnotherLibrary
WORKING_DIRECTORY ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel
LOGNAME build-AnotherLibrary-${TARGET_TRIPLET}-rel
)
# collect this other library CMake configs into a list
file(GLOB AnotherLibraryFiles ${CURRENT_PACKAGES_DIR}/cmake/AnotherLibrary*.cmake)
# create folders for its configs in proper paths, where vcpkg would expect them to be
file(MAKE_DIRECTORY
${CURRENT_PACKAGES_DIR}/share/AnotherLibrary
${CURRENT_PACKAGES_DIR}/debug/share/AnotherLibrary
)
# move out every AnotherLibrary's CMake config from SomeLibrary folder
# and copy them to both Debug and Release share folders
foreach(AnotherLibraryFile ${AnotherLibraryFiles})
get_filename_component(AnotherLibraryFileName ${AnotherLibraryFile} NAME)
file(RENAME ${AnotherLibraryFile} ${CURRENT_PACKAGES_DIR}/share/AnotherLibrary/${AnotherLibraryFileName})
file(
COPY ${CURRENT_PACKAGES_DIR}/share/AnotherLibrary/${AnotherLibraryFileName}
DESTINATION ${CURRENT_PACKAGES_DIR}/debug/share/AnotherLibrary/
)
endforeach()
# now fix the "main" library configs
vcpkg_cmake_config_fixup(
PACKAGE_NAME "SomeLibrary"
CONFIG_PATH "cmake"
)
# and the other one's
vcpkg_cmake_config_fixup(
PACKAGE_NAME "AnotherLibrary"
)
So now the portfile looks even worse, but it does work fine, so untill a better alternative comes up, that will be the way.
As I already mentioned, it would probably be better to make AnotherLibrary
its own port, which SomeLibrary
could depend on, but then they might eventually end up having different REF
commit values, causing your users to fetch different snapshots of the same repository (which would be the least of potential problems caused by this).
Sparse checkout
While we are here, when you only need to build just some targets of your big project, it might be an overkill to make your users download your entire repository, especially if it’s rather big (even as a snapshot).
Partial repository cloning can be done with sparse checkout. For example, if I only want to get the SomeLibrary
component/target from one of my projects:
$ git clone --depth 1 --filter=blob:none --sparse git@github.com:retifrav/cmake-library-example.git
$ cd ./cmake-library-example
$ git sparse-checkout set --no-cone internal-project/libraries/SomeLibrary
But default vcpkg CMake functions such as vcpkg_from_git unfortunately do not have an option for sparse cloning and checkout, so I’ll probably implement my own custom function/helper for that (or at least register a feature request for it).
Updating a port
Not once and not twice you’ll need to change something in your existing port, especially when you are working on the very first version of it.
As a concrete example, I forgot to add include_guard(GLOBAL)
in my ports/decovar-vcpkg-cmake
helper package. Here’s what I needed to do in order to fix that:
- Edit
./ports/decovar-vcpkg-cmake/vcpkg-port-config.cmake
and commit this change, but not push yet; - Run
git rev-parse HEAD:./ports/decovar-vcpkg-cmake
and set the new value togit-tree
in./versions/d-/decovar-vcpkg-cmake.json
; - Stage that change and amend the commit from the step 1 by running
git commit --amend --no-edit
; - Push.
You can inspect that commit in my example registry. As you can see, it’s almost the same steps as with adding a new version of a port, except that here the version stays the same.
Also don’t forget to update Git hash value (current HEAD
of the entire registry repository, not just this dependency) for baseline
property in vcpkg-configuration.json of the “consuming” project, otherwise it will not “know” about the updated version in the registry. Moreover, if you force-pushed that fix, then vcpkg will simply fail to resolve dependencies, as that commit would no longer exist.
But actually that last bit about changing baseline
property in the project’s vcpkg-configuration.json
when updating the same version of a port in the registry (when the same port v1.2.3 has different configuration/patches/etc in different registry commits/states) is something I am not very confident about, because I am not really sure how exactly the baseline
value is used for resolving the port version/state:
- Does vcpkg look for a version with
git-tree
value that was available no later than that commit? - Or does vcpkg use that commit hash value only for getting
versions/baseline.json
from that exact commit (to establish a minimum port version), and then it will still find the latest version’sgit-tree
value that might be pointing to a later repository state and take that one? So one cannot rely onbaseline
property to “prevent” vcpkg from getting updated port versions available in the registry?
I was hoping it’s the former, but looks like it’s actually the latter. In that case I now better understand the point of port-version.
Installing ports from a registry
Without a project
You can try to install the GLFW port even without having a project that would depend on it. It is a convenient ability to have when you want to quickly test your new port:
$ cd /path/to/your/vcpkg-registry
$ vcpkg install glfw --overlay-ports=./ports/glfw
Computing installation plan...
The following packages will be built and installed:
glfw[core]:x64-osx -> 3.3.8 -- /path/to/your/vcpkg-registry/./ports/glfw
* vcpkg-cmake[core]:x64-osx -> 2022-08-18
* vcpkg-cmake-config[core]:x64-osx -> 2022-02-06#1
Additional packages (*) will be modified to complete this operation.
Detecting compiler hash for triplet x64-osx...
Restored 1 package(s) from /Users/USERNAME/.cache/vcpkg/archives in 23.44 ms. Use --debug to see more details.
Installing 1/3 vcpkg-cmake:x64-osx...
Elapsed time to handle vcpkg-cmake:x64-osx: 7.694 ms
Installing 2/3 vcpkg-cmake-config:x64-osx...
Building vcpkg-cmake-config[core]:x64-osx...
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/share/vcpkg-cmake-config/vcpkg_cmake_config_fixup.cmake
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/share/vcpkg-cmake-config/vcpkg-port-config.cmake
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/share/vcpkg-cmake-config/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/32/32dd70a7ba46fe85a359ed1b3a9b0c98772888eb59daefe61cb52f6560338044.zip"
Elapsed time to handle vcpkg-cmake-config:x64-osx: 58.43 ms
Installing 3/3 glfw:x64-osx...
Building glfw[core]:x64-osx...
-- Installing port from location: /path/to/your/vcpkg-registry/./ports/glfw
-- Using cached /path/to/programs/vcpkg/downloads/glfw-7482de6071d21db77a7236155da44c172a7f6c9e.tar.gz
-- Cleaning sources at /path/to/programs/vcpkg/buildtrees/glfw/src/172a7f6c9e-d7a30c254e.clean. Use --editable to skip cleaning for the packages you specify.
-- Extracting source /path/to/programs/vcpkg/downloads/glfw-7482de6071d21db77a7236155da44c172a7f6c9e.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/glfw/src/172a7f6c9e-d7a30c254e.clean
-- Found external ninja('1.11.1').
-- Configuring x64-osx
-- Building x64-osx-dbg
-- Building x64-osx-rel
-- Installing: /path/to/programs/vcpkg/packages/glfw_x64-osx/share/glfw/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/c0/c05ed39487e7538dcd2436b1cc00ac256091ec6df15f9dbdd6c71c0f884da654.zip"
Elapsed time to handle glfw:x64-osx: 5.9 s
Total elapsed time: 14.15 s
glfw provides CMake targets:
# this is heuristically generated, and may not be correct
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(main PRIVATE glfw)
Here’s what it did:
- Fetched and installed host dependencies (
vcpkg-cmake
andvcpkg-cmake-config
); - Fetched, configured, built and installed GLFW, both Debug and Release configurations (there will be a note about
debug
subfolder later); - Saved the sources and build artifacts in local cache, so they could be restored for later re-use instead of fetching and building them again;
- Tried to discover/guess CMake statements for using these packages in your project (
find_package()
andtarget_link_libraries()
commands). In most cases it does that correctly.
The resulting binaries are placed here:
/path/to/programs/vcpkg/packages/glfw_x64-osx
/path/to/programs/vcpkg/installed/x64-osx
/Users/USERNAME/.cache/vcpkg/archives/c0/c05ed39487e7538dcd2436b1cc00ac256091ec6df15f9dbdd6c71c0f884da654.zip
(that’s the cached pre-built package)
Note that it installed the artifacts not in the /path/to/your/vcpkg-registry
, but to where vcpkg is installed in your system. Later when we’ll be resolving dependencies for a project, the artifacts will be installed to its build folder.
If we now delete the pre-built artifacts from vcpkg folder (but not from cache) and try to install the package again, this operation will run much faster:
$ rm -r /path/to/programs/vcpkg/packages/glfw_x64-osx
$ rm -r /path/to/programs/vcpkg/installed/*
$ vcpkg install glfw --overlay-ports=./ports/glfw
Computing installation plan...
The following packages will be built and installed:
glfw[core]:x64-osx -> 3.3.8 -- /Users/USERNAME/code/cpp/vcpkg-registry/./ports/glfw
* vcpkg-cmake[core]:x64-osx -> 2022-08-18
* vcpkg-cmake-config[core]:x64-osx -> 2022-02-06#1
Additional packages (*) will be modified to complete this operation.
Detecting compiler hash for triplet x64-osx...
Restored 3 package(s) from /Users/USERNAME/.cache/vcpkg/archives in 24.94 ms. Use --debug to see more details.
Installing 1/3 vcpkg-cmake:x64-osx...
Elapsed time to handle vcpkg-cmake:x64-osx: 3.579 ms
Installing 2/3 vcpkg-cmake-config:x64-osx...
Elapsed time to handle vcpkg-cmake-config:x64-osx: 2.45 ms
Installing 3/3 glfw:x64-osx...
Elapsed time to handle glfw:x64-osx: 6.662 ms
Total elapsed time: 2.254 s
glfw provides CMake targets:
# this is heuristically generated, and may not be correct
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(main PRIVATE glfw)
As you can see, this time it just restored pre-built artifacts that were already available in cache.
Dummy installation
This still doesn’t count as a proper project, because there is no CMake project file or anything at all really, but also this is not exactly the same as installing from within a registry using --overlay-ports
. This option might be convenient when you want to quickly test not one, not all, but a selected set of ports.
Create an empty dummy
folder and put there vcpkg-configuration.json
:
{
"default-registry":
{
"kind": "git",
"repository": "git@github.com:retifrav/vcpkg-registry.git",
"baseline": "4b4ae3fea063fc04c2d5a6089c8aa7c2d0129879"
},
"registries": []
}
and vcpkg.json
:
{
"name": "some",
"version": "0",
"dependencies":
[
"glad",
"glfw"
]
}
Here it will go with default versions and features, but you can of course specify anything you’d need just like you would do it in an actual project.
And now you can install these ports:
$ cd /path/to/dummy
$ tree .
├── vcpkg-configuration.json
└── vcpkg.json
$ vcpkg install --triplet=x64-windows-static --host-triplet=x64-windows-static
Overriding triplets is here just as an example, especially that you probably will never need to override --host-triplet
.
Furthermore, you can now refer to installed libraries from a non-vcpkg project just like you would with any other pre-built dependency:
$ cd /path/to/some
$ mkdir build && cd $_
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="/path/to/dummy/vcpkg_installed/x64-windows-static" \
..
In a project
I’ll use my GLFW Dear ImGui application created in this article as an example. It already has an option of resolving dependencies with Conan, and now I’ll add an option of resolving the same dependencies with vcpkg.
Dependencies are listed in the project’s vcpkg.json:
{
"name": "glfw-imgui-example",
"version": "0",
"dependencies":
[
"glad",
"glfw",
{
"name": "dearimgui",
"features":
[
"backend-glfw"
]
}
]
}
The glfw
dependency is not really needed here, because it will still be added through dearimgui
, as we are saying here that we want it with backend-glfw
feature, and that one adds glfw
dependency. But it wouldn’t hurt to set it explicitly, as the project does depend on it regardless of Dear ImGui.
To tell vcpkg, which registry I want to use, I’ve set the following in the vcpkg-configuration.json:
{
"default-registry":
{
"kind": "git",
"repository": "git@github.com:retifrav/vcpkg-registry.git",
"baseline": "5c1a089c3a542dfbe818625bf4a3dadfb834e2af"
},
"registries": []
}
Now we should be able to get all the dependencies with vcpkg and configure the project:
$ cd /path/to/glfw-imgui-example
$ mkdir build && cd $_
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="../install" \
-DUSING_PACKAGE_MANAGER_VCPKG=1 \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
..
-- Running vcpkg install
Fetching registry information from git@github.com:retifrav/vcpkg-registry.git (HEAD)...
Detecting compiler hash for triplet x64-osx...
The following packages will be built and installed:
dearimgui[backend-glfw,core]:x64-osx -> 1.88.0 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/c3282e78368406b65f57ef0e3afa7cda2fc26501
* decovar-vcpkg-cmake[core]:x64-osx -> 2022-10-15 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/4ceb1e31d28a155bcbeb26382478b024a3d82cd3
glad[core]:x64-osx -> 0.1.36 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/2341f5144ce8e76a256289517d61abb4ab9fb72c
glfw[core]:x64-osx -> 3.3.8 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/597fa07e1afd57c50dfdbeb0c0d28f4157748564
* vcpkg-cmake[core]:x64-osx -> 2022-08-18 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/84c200e8e625d4d99b1649525fcdf81a73197078
* vcpkg-cmake-config[core]:x64-osx -> 2022-02-06 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/e23b39e21f0dd42ecc615262640d211c39696aa1
Additional packages (*) will be modified to complete this operation.
Restored 0 package(s) from /Users/USERNAME/.cache/vcpkg/archives in 33.41 us. Use --debug to see more details.
Installing 1/6 vcpkg-cmake-config:x64-osx...
Building vcpkg-cmake-config[core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/e23b39e21f0dd42ecc615262640d211c39696aa1
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/share/vcpkg-cmake-config/vcpkg_cmake_config_fixup.cmake
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/share/vcpkg-cmake-config/vcpkg-port-config.cmake
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake-config_x64-osx/share/vcpkg-cmake-config/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/5e/5ede033072a78c3aa60ffe343b1248c10ad943b52e64658f524bac40637f500b.zip"
Elapsed time to handle vcpkg-cmake-config:x64-osx: 53.77 ms
Installing 2/6 vcpkg-cmake:x64-osx...
Building vcpkg-cmake[core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/84c200e8e625d4d99b1649525fcdf81a73197078
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake_x64-osx/share/vcpkg-cmake/vcpkg_cmake_configure.cmake
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake_x64-osx/share/vcpkg-cmake/vcpkg_cmake_build.cmake
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake_x64-osx/share/vcpkg-cmake/vcpkg_cmake_install.cmake
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake_x64-osx/share/vcpkg-cmake/vcpkg-port-config.cmake
-- Installing: /path/to/programs/vcpkg/packages/vcpkg-cmake_x64-osx/share/vcpkg-cmake/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/6e/6e6061f9ad1b3a6deb062796316d813c6088ecd79e1a5b317f8d61d77720d6e9.zip"
Elapsed time to handle vcpkg-cmake:x64-osx: 53.31 ms
Installing 3/6 decovar-vcpkg-cmake:x64-osx...
Building decovar-vcpkg-cmake[core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/4ceb1e31d28a155bcbeb26382478b024a3d82cd3
-- Installing: /path/to/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-osx/share/decovar-vcpkg-cmake/Installing.cmake
-- Installing: /path/to/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-osx/share/decovar-vcpkg-cmake/Config.cmake.in
-- Installing: /path/to/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-osx/share/decovar-vcpkg-cmake/decovar_vcpkg_cmake_ololo.cmake
-- Installing: /path/to/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-osx/share/decovar-vcpkg-cmake/vcpkg-port-config.cmake
-- Installing: /path/to/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-osx/share/decovar-vcpkg-cmake/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/aa/aa3c4e402f180f3e686bc6fa970e01c69c12dbcef95897634f58e6158fb194fa.zip"
Elapsed time to handle decovar-vcpkg-cmake:x64-osx: 93.32 ms
Installing 4/6 glfw:x64-osx...
Building glfw[core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/597fa07e1afd57c50dfdbeb0c0d28f4157748564
-- Fetching git@github.com:glfw/glfw.git 7482de6071d21db77a7236155da44c172a7f6c9e...
-- Extracting source /path/to/programs/vcpkg/downloads/glfw-7482de6071d21db77a7236155da44c172a7f6c9e.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/glfw/src/172a7f6c9e-d7a30c254e.clean
-- Found external ninja('1.11.1').
-- Configuring x64-osx
-- Building x64-osx-dbg
-- Building x64-osx-rel
-- Installing: /path/to/programs/vcpkg/packages/glfw_x64-osx/share/glfw/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/02/02113b742df9138712913194efa54f3ef51eb01fd3e434fc4be9b406f44161c0.zip"
Elapsed time to handle glfw:x64-osx: 7.773 s
Installing 5/6 dearimgui:x64-osx...
Building dearimgui[backend-glfw,core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/c3282e78368406b65f57ef0e3afa7cda2fc26501
-- Fetching git@github.com:ocornut/imgui.git 9aae45eb4a05a5a1f96be1ef37eb503a12ceb889...
-- Extracting source /path/to/programs/vcpkg/downloads/dearimgui-9aae45eb4a05a5a1f96be1ef37eb503a12ceb889.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/dearimgui/src/3a12ceb889-c011915ee6.clean
-- Found external ninja('1.11.1').
-- Configuring x64-osx
-- Building x64-osx-dbg
-- Building x64-osx-rel
-- Installing: /path/to/programs/vcpkg/packages/dearimgui_x64-osx/share/dearimgui/copyright
-- ololo
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/c3/c3ba0c72ccdb98a816bd20555ab9cf5399687d6626bc943501b3ac9529f5cba2.zip"
Elapsed time to handle dearimgui:x64-osx: 13.51 s
Installing 6/6 glad:x64-osx...
Building glad[core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/2341f5144ce8e76a256289517d61abb4ab9fb72c
-- Fetching git@github.com:Dav1dde/glad.git 1ecd45775d96f35170458e6b148eb0708967e402...
-- Extracting source /path/to/programs/vcpkg/downloads/glad-1ecd45775d96f35170458e6b148eb0708967e402.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/glad/src/708967e402-a791cb9077.clean
-- Found external ninja('1.11.1').
-- Configuring x64-osx
-- Building x64-osx-dbg
-- Building x64-osx-rel
-- Installing: /path/to/programs/vcpkg/packages/glad_x64-osx/share/glad/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/83/8309811b7ffd5fa5ee71aada208fab78e9e24cd176ba9083576b00c363bec638.zip"
Elapsed time to handle glad:x64-osx: 5.753 s
Total elapsed time: 33.97 s
glfw provides CMake targets:
# this is heuristically generated, and may not be correct
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(main PRIVATE glfw)
dearimgui provides CMake targets:
# this is heuristically generated, and may not be correct
find_package(dearimgui CONFIG REQUIRED)
target_link_libraries(main PRIVATE dearimgui)
glad provides CMake targets:
# this is heuristically generated, and may not be correct
find_package(glad CONFIG REQUIRED)
target_link_libraries(main PRIVATE glad::glad)
-- Running vcpkg install - done
-- The C compiler identification is AppleClang 14.0.0.14000029
-- The CXX compiler identification is AppleClang 14.0.0.14000029
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenGL: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/OpenGL.framework
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/glfw-imgui-example/build
It might be rather unexpected, but for Debug configuration vcpkg creates a debug
subfolder inside build directory (/path/to/glfw-imgui-example/build/vcpkg_installed/x64-osx/debug
) and puts Debug artifacts there, which can be confusing at first and will cause many projects to produce various vcpkg warnings (redundant include
/share
/etc folders inside debug
and so on). Someone told me that this is the only proper/possible way of building several configurations on different platforms, and so that is why vcpkg does it so. I still don’t quite get why then not to just have a folder per configuration instead of nesting debug
, but okay.
Unrelated to that, you might have noticed an ololo
message being printed out during the installation of dearimgui
port (version 1.88.0). That message came from decovar-vcpkg-cmake helper port, which dearimgui
port depends on and calls decovar_vcpkg_cmake_ololo()
function from.
The total time to fetch all dependencies sources, configure and build them and install (and cache) the binaries is 34 seconds. If we now remove everything from the build
folder and try again:
$ rm -r ./*; rm .ninja*; ls -lah
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="../install" \
-DUSING_PACKAGE_MANAGER_VCPKG=1 \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
..
-- Running vcpkg install
Fetching registry information from git@github.com:retifrav/vcpkg-registry.git (HEAD)...
Detecting compiler hash for triplet x64-osx...
The following packages will be built and installed:
dearimgui[backend-glfw,core]:x64-osx -> 1.88.0 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/c3282e78368406b65f57ef0e3afa7cda2fc26501
* decovar-vcpkg-cmake[core]:x64-osx -> 2022-10-15 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/4ceb1e31d28a155bcbeb26382478b024a3d82cd3
glad[core]:x64-osx -> 0.1.36 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/2341f5144ce8e76a256289517d61abb4ab9fb72c
glfw[core]:x64-osx -> 3.3.8 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/597fa07e1afd57c50dfdbeb0c0d28f4157748564
* vcpkg-cmake[core]:x64-osx -> 2022-08-18 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/84c200e8e625d4d99b1649525fcdf81a73197078
* vcpkg-cmake-config[core]:x64-osx -> 2022-02-06 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/e23b39e21f0dd42ecc615262640d211c39696aa1
Additional packages (*) will be modified to complete this operation.
Restored 6 package(s) from /Users/USERNAME/.cache/vcpkg/archives in 74.03 ms. Use --debug to see more details.
Installing 1/6 vcpkg-cmake-config:x64-osx...
Elapsed time to handle vcpkg-cmake-config:x64-osx: 5.364 ms
Installing 2/6 vcpkg-cmake:x64-osx...
Elapsed time to handle vcpkg-cmake:x64-osx: 4.532 ms
Installing 3/6 decovar-vcpkg-cmake:x64-osx...
Elapsed time to handle decovar-vcpkg-cmake:x64-osx: 6.853 ms
Installing 4/6 glfw:x64-osx...
Elapsed time to handle glfw:x64-osx: 14.2 ms
Installing 5/6 dearimgui:x64-osx...
Elapsed time to handle dearimgui:x64-osx: 31.32 ms
Installing 6/6 glad:x64-osx...
Elapsed time to handle glad:x64-osx: 31.76 ms
Total elapsed time: 4.212 s
glfw provides CMake targets:
# this is heuristically generated, and may not be correct
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(main PRIVATE glfw)
dearimgui provides CMake targets:
# this is heuristically generated, and may not be correct
find_package(dearimgui CONFIG REQUIRED)
target_link_libraries(main PRIVATE dearimgui)
glad provides CMake targets:
# this is heuristically generated, and may not be correct
find_package(glad CONFIG REQUIRED)
target_link_libraries(main PRIVATE glad::glad)
-- Running vcpkg install - done
-- The C compiler identification is AppleClang 14.0.0.14000029
-- The CXX compiler identification is AppleClang 14.0.0.14000029
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenGL: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/OpenGL.framework
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/glfw-imgui-example/build
…then there is no ololo
message being printed anymore, and the whole vcpkg stage took only only 4 seconds, because this time it didn’t need to fetch sources and build binaries - it just restored already pre-built ones from cache.
Building the project:
$ time cmake --build .
[3/3] Linking CXX executable glfw-imgui
real 0m0.958s
user 0m1.330s
sys 0m0.194s
$ cat .ninja_log
# ninja log v5
1 585 1666772953298222365 CMakeFiles/glfw-imgui.dir/functions.cpp.o 38458c79e781fe79
1 794 1666772953508206892 CMakeFiles/glfw-imgui.dir/main.cpp.o 41b1d124fcaa4881
794 927 1666772953635024427 glfw-imgui b12d88b40ff7db85
takes 1 second (958 ms) and involves compiling only 3 items, instead of the full 29, as it is demonstrated with the same project in the article about Conan.
That same article also states that building the project and all its dependencies from sources takes 13.5 seconds, while with vcpkg building the dependencies alone took 34 seconds, so it might look like vcpkg doesn’t improve but degrades build time. However, you shouldn’t forget that vcpkg builds both Debug and Release configurations, not just one (which, by the way, will catch up with the time when you’ll be building the project and dependencies in a different configuration), plus it does packing and cashing, which also takes time. And anyway, this matters only once - when dependencies are built for the first time, while without using pre-built dependencies you’ll be wasting time building dependencies over and over again.
Locking on dependencies versions
If you’d like to lock on specific versions of dependencies (you should), it can be done with overrides:
{
"name": "glfw-imgui-example",
"version": "0",
"dependencies":
[
"glad",
"glfw",
{
"name": "dearimgui",
"features":
[
"backend-glfw"
]
}
],
"overrides":
[
{
"name": "glad",
"version": "0.1.36",
"port-version": 0
},
{
"name": "glfw",
"version": "3.3.8",
"port-version": 0
},
{
"name": "dearimgui",
"version": "1.88.0",
"port-version": 0
}
]
}
Here you might think that overrides
block is redundant and you will probably want to set versions right in the dependencies
block like this:
{
"name": "glfw-imgui-example",
"...": "...",
"dependencies":
[
"...",
{
"name": "glfw",
"version": "3.3.8"
}
]
}
But that will fail:
error: while loading /path/to/glfw-imgui-example/vcpkg.json:
$.dependencies[2] (a dependency): unexpected field 'version', did you mean 'version>='?See https://github.com/Microsoft/vcpkg/tree/master/docs/users/manifests.md for more information.
So it just does not accept this, and indeed, setting it like "version>=": "3.3.8"
will succeed, but then it will accept newer versions too, when they become available in the registry and it should not pick up newer versions, because vcpkg uses a minimum version approach. Unless, like I said before, if you set a dependency version with version>=
and that value is lower than baselined version, then vcpkg will skip all the versions between that one and the baselined and will take the baselined version.
And so if you want to pin/lock on exact version of a dependency, then you have to use overrides.
In my opinion, this whole system is quite weird and not intuitive.
Transitive dependencies
…but weirdness doesn’t stop there. You cannot use overrides
in ports manifests (vcpkg.json
) of dependencies of dependencies (transitive dependencies). Or, rather, you can, but it will be ignored. As explained in this answer:
You can only use overrides at the root level and not in a port. Within a port you can only use
"version>=": "..."
.If you could use overrides in a port, you could create two ports with a dependency on
A
where port #1 required version1.0.0
and port #2 required version1.0.1
. This would result in unsolvable version constraints.
So, if I’d want to lock on glfw
version 3.3.8
in dearimgui
port manifest, then I would need to do it like this:
{
"name": "dearimgui",
"...": "...",
"dependencies":
[
"..."
],
"features":
{
"backend-glfw":
{
"description": "Using GLFW as graphics backend",
"dependencies":
[
{
"name": "glfw",
"version>=": "3.3.8"
}
]
}
}
}
To make things more interesting, if you’d like to specify the port version too, this is how you would do it:
{
"name": "glfw",
"version>=": "3.3.8#1"
}
Yeah, no separate field/property - just this #
separator followed by the port version.
Multiple registries
It is also possible to have multiple registries in a project and specify which packages should be fetched from which registry.
For example, let’s say I want my in-house custom registry to be the default one, and for the purpose of testing/comparing my custom ports I’d need to be able to get their variants from Microsoft’s registry:
{
"default-registry":
{
"kind": "git",
"repository": "git@github.com:retifrav/vcpkg-registry.git",
"baseline": "5c1a089c3a542dfbe818625bf4a3dadfb834e2af"
},
"registries":
[
{
"kind": "git",
"repository": "https://github.com/microsoft/vcpkg.git",
"baseline": "71f51b100be2b5d32e3907572d99dc2f97088c8d",
"packages":
[
"glslang"
]
}
]
}
This is what I had in vcpkg-configuration.json
in one of my other projects where I had some problems with linking to HLSL
that was installed from glslang
. So I tried to use its port from Microsoft’s registry to see if it is built differently there (and indeed it was), and while glslang
is listed in the dependencies
list inside vcpkg.json
, here I’m telling vcpkg that it should be actually fetched from the Microsoft’s registry instead of the default one. As you can see below, now vcpkg uses two registries, and even though I already have glslang
pre-built in my local cache (and the same version too), now it will be built anew, for it is a port from a different registry:
...
Fetching registry information from git@github.com:retifrav/vcpkg-registry.git (HEAD)...
Fetching registry information from https://github.com/microsoft/vcpkg.git (HEAD)...
...
Installing 9/19 glslang:x64-windows-static...
Building glslang[core]:x64-windows-static...
-- Installing port from location: C:\Users\USERNAME\AppData\Local\vcpkg\registries\git-trees\4d7780995e9523d16a56714fcef0159f18ecfa52
-- Downloading https://github.com/KhronosGroup/glslang/archive/11.8.0.tar.gz -> KhronosGroup-glslang-11.8.0.tar.gz...
-- Extracting source D:/programs/vcpkg/downloads/KhronosGroup-glslang-11.8.0.tar.gz
-- Applying patch ignore-crt.patch
-- Applying patch always-install-resource-limits.patch
-- Using source at D:/programs/vcpkg/buildtrees/glslang/src/11.8.0-2a85409eb5.clean
-- Configuring x64-windows-static
-- Building x64-windows-static-dbg
-- Building x64-windows-static-rel
-- Installing: D:/programs/vcpkg/packages/glslang_x64-windows-static/share/glslang/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "C:\Users\USERNAME\AppData\Local\vcpkg\archives\8b\8b16920d54a7798ed9edb2c11b8cfe6a824cb4fab31365b9f3fe820486bbd669.zip"
Elapsed time to handle glslang:x64-windows-static: 46.16 s
Isn’t that beautiful. With Conan I couldn’t come up with a working setup for this, and with vcpkg it just works with a minimal effort.
CMake presets
In (relatively) recent versions of CMake a new feature has been added - CMake presets. It first appeared in version 3.19
, and then versions 3.20
and 3.21
further expended it with newer revisions. I am now using presets revision 3
(that’s the one version 3.21
brought in), but later CMake versions added even more.
In short, it is a JSON file, in which you can define “presets” - sets of CMake options for building your project. For example, here are several presets for configuring and building that project, and now instead of that long error-prone CMake command I can just do this:
$ cd /path/to/glfw-imgui-example
$ cmake --preset vcpkg-default-triplet
Preset CMake variables:
CMAKE_BUILD_TYPE:STRING="Release"
CMAKE_INSTALL_PREFIX:PATH="/path/to/glfw-imgui-example/install/vcpkg-default-triplet"
CMAKE_TOOLCHAIN_FILE:FILEPATH="/path/to/programs/vcpkg/scripts/buildsystems/vcpkg.cmake"
USING_PACKAGE_MANAGER_VCPKG:BOOL="TRUE"
-- Running vcpkg install
...
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/glfw-imgui-example/build/vcpkg-default-triplet
$ cmake --build --preset vcpkg-default-triplet
[3/4] Install the project...
-- Install configuration: "Release"
-- Installing: /path/to/glfw-imgui-example/install/vcpkg-default-triplet/bin/glfw-imgui/glfw-imgui
-- Installing: /path/to/glfw-imgui-example/install/vcpkg-default-triplet/bin/glfw-imgui/JetBrainsMono-ExtraLight.ttf
Much more convenient, innit. And that way your team members might even “not know” about vcpkg’s existence (after they install it on their machines, of course).
The Ninja CMake generator and CMAKE_BUILD_TYPE
are set in the presets too, but one can of course override those by explicitly setting them with -G
and -D
when calling CMake.
Updating a dependency version
When you need to update (or downgrade) one of the project dependencies version (given that new version is available in the registry), you can do it in your project’s vcpkg.json
. For example, I have two versions of GLFW in my registry - 3.3.7
and 3.3.8
- and the project depends on version 3.3.8
at the moment:

If I change GLFW dependency version to 3.3.7
in vcpkg.json
:
{
"name": "glfw-imgui-example",
"...": "...",
"overrides":
[
{
"...": "...",
},
{
"name": "glfw",
"version": "3.3.7"
},
{
"...": "..."
}
]
}
and rebuild the project, it will then fetch the GLFW sources for version 3.3.7
and build it (as there is only 3.3.8
pre-built in the cache), and it will also re-build Dear ImGui, because it one depends on GLFW:
...
The following packages will be rebuilt:
dearimgui[backend-glfw,core]:x64-osx -> 1.88.0 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/c3282e78368406b65f57ef0e3afa7cda2fc26501
glfw[core]:x64-osx -> 3.3.7 -- /Users/USERNAME/.cache/vcpkg/registries/git-trees/45af0346d2ec926146091ab374c475cac5aaf055
Removing 1/4 dearimgui:x64-osx
Elapsed time to handle dearimgui:x64-osx: 6.359 ms
Removing 2/4 glfw:x64-osx
Elapsed time to handle glfw:x64-osx: 5.831 ms
Restored 0 package(s) from /Users/USERNAME/.cache/vcpkg/archives in 317 us. Use --debug to see more details.
Installing 3/4 glfw:x64-osx...
Building glfw[core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/45af0346d2ec926146091ab374c475cac5aaf055
-- Fetching git@github.com:glfw/glfw.git 45ce5ddd197d5c58f50fdd3296a5131c894e5527...
-- Extracting source /path/to/programs/vcpkg/downloads/glfw-45ce5ddd197d5c58f50fdd3296a5131c894e5527.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/glfw/src/1c894e5527-e2c903af70.clean
-- Found external ninja('1.11.1').
-- Configuring x64-osx
-- Building x64-osx-dbg
-- Building x64-osx-rel
-- Installing: /path/to/programs/vcpkg/packages/glfw_x64-osx/share/glfw/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/5c/5ce54de2d8e83e86576a55541e988b827c72567a9bfee05717f103cc04193eff.zip"
Elapsed time to handle glfw:x64-osx: 7.613 s
Installing 4/4 dearimgui:x64-osx...
Building dearimgui[backend-glfw,core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/c3282e78368406b65f57ef0e3afa7cda2fc26501
-- Using cached /path/to/programs/vcpkg/downloads/dearimgui-9aae45eb4a05a5a1f96be1ef37eb503a12ceb889.tar.gz
-- Cleaning sources at /path/to/programs/vcpkg/buildtrees/dearimgui/src/3a12ceb889-c011915ee6.clean. Use --editable to skip cleaning for the packages you specify.
-- Extracting source /path/to/programs/vcpkg/downloads/dearimgui-9aae45eb4a05a5a1f96be1ef37eb503a12ceb889.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/dearimgui/src/3a12ceb889-c011915ee6.clean
-- Found external ninja('1.11.1').
-- Configuring x64-osx
-- Building x64-osx-dbg
-- Building x64-osx-rel
-- Installing: /path/to/programs/vcpkg/packages/dearimgui_x64-osx/share/dearimgui/copyright
-- ololo
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: "/Users/USERNAME/.cache/vcpkg/archives/00/00795b05b7197ecba35c066f67adec8cf123fd2d427deeae3921a9f8ec1486c7.zip"
Elapsed time to handle dearimgui:x64-osx: 10.59 s
...
After the new build the application will report GLFW version 3.3.7
:

Static CRT/MSVC linkage
I’ve stumbled upon this problem on Windows with one of the dependencies in one of my other projects. At first that dependency wasn’t being handled with vcpkg, I just added path to its installed artifacts into CMAKE_PREFIX_PATH
. The project configuration succeeded, but building the project exploded with errors at linking stage:
LINK: command "HERE-GOES-LONG-LINKING-COMMAND" failed (exit code 1120) with the following output:
SomeDependency.lib(API.cpp.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MD_DynamicRelease' doesn't match value 'MT_StaticRelease' in some.cpp.obj
SomeDependency.lib(Bitmap.cpp.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MD_DynamicRelease' doesn't match value 'MT_StaticRelease' in some.cpp.obj
...
msvcprt.lib(MSVCP140.dll) : error LNK2005: "public: __cdecl std::_Lockit::_Lockit(int)" (??0_Lockit@std@@QEAA@H@Z) already defined in libcpmt.lib(xlock.obj)
msvcprt.lib(MSVCP140.dll) : error LNK2005: "public: __cdecl std::_Lockit::~_Lockit(void)" (??1_Lockit@std@@QEAA@XZ) already defined in libcpmt.lib(xlock.obj)
msvcprt.lib(MSVCP140.dll) : error LNK2005: "public: struct _Cvtvec __cdecl std::_Locinfo::_Getcvt(void)const " (?_Getcvt@_Locinfo@std@@QEBA?AU_Cvtvec@@XZ) already defined in some.cpp.obj
msvcprt.lib(MSVCP140.dll) : error LNK2005: "public: unsigned short const * __cdecl std::_Locinfo::_W_Getdays(void)const " (?_W_Getdays@_Locinfo@std@@QEBAPEBGXZ) already defined in libcpmt.lib(wlocale.obj)
...
LINK : warning LNK4286: symbol 'tolower' defined in 'libucrt.lib(tolower_toupper.obj)' is imported by 'SomeDependency.lib(SourceWeb.cpp.obj)'
LINK : warning LNK4286: symbol 'calloc' defined in 'libucrt.lib(calloc.obj)' is imported by 'libtiff.lib(tif_win32.c.obj)'
...
SomeDependency.lib(geos.cpp.obj) : error LNK2001: unresolved external symbol __imp_hypot
...
libpng.lib(pngwrite.c.obj) : error LNK2001: unresolved external symbol __imp_strerror
libpng.lib(pngwrite.c.obj) : error LNK2019: unresolved external symbol __imp_ferror referenced in function png_image_write_to_file
...
some-application.exe : fatal error LNK1120: 33 unresolved externals
ninja: build stopped: subcommand failed.
The most important one from all of these:
mismatch detected for 'RuntimeLibrary': value 'MD_DynamicRelease' doesn't match value 'MT_StaticRelease'
As it turned out, that dependency was linking CRT/MSVC dynamically, while x64-windows-static
triplet does that statically, but in order for linking to succeed, all the targets/objects in the project must have the same type of linking to CRT/MSVC.
So I’ve re-built that dependency with static linking to CRT/MSVC by setting /MT
flags, but still got errors on linking, however this time the linking was wrong the other way around:
mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't match value 'MD_DynamicRelease'
Doesn’t make sense, does it, but then I realized that this is because my application is also linking to CRT/MSVC dynamically. This time I decided to google some more, as setting flags didn’t feel entirely right, and soon enough I found this solution on CMake’s forum:
# is set in vcpkg-windows-static preset (when triplet is x64-windows-static)
option(CRT_LINKAGE_STATIC "Link MSVC runtime statically" 0)
# ...
if(WIN32 AND CRT_LINKAGE_STATIC)
#add_compile_options("/MT")
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
set_property(TARGET ${CMAKE_PROJECT_NAME}
PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
)
endif()
Later on I also made a vcpkg port out of that dependency, so vcpkg would configure and build it under the same triplet together with other dependencies. In that case there is no need to set linking flags in that dependency’s CMakeLists.txt
, as linkage to CRT/MSVC would be aligned, but of course I still need to align it for my application, which is what I’m doing by setting CRT_LINKAGE_STATIC
option in vcpkg-windows-static
CMake preset.
Dynamic and static linking
Since we are brought up the subject of linking, in case you are curious, with Windows build as an example, if we build that project with vcpkg-default-triplet
preset, which uses dynamic linking (VCPKG_LIBRARY_LINKAGE
), then resulting artifacts will be all its dependencies as DLLs and a small executable of the application itself:
glad.dll
- 86 KBglfw3.dll
- 211.5 KBdearimgui.dll
- 889 KBglfw-imgui.exe
- 50.5 KB
If we build it with vcpkg-windows-static
preset, which uses static linking (both for dependencies and CRT/MSVC), then we’ll get just an application executable with everything linked into it:
glfw-imgui.exe
: 1268 KB
Finally, using the same vcpkg-windows-static
preset but with VCPKG_CRT_LINKAGE
set to dynamic
(probably redundant for dependencies) and -DCRT_LINKAGE_STATIC=0
, we’ll still get a single executable with dependencies linked in but without CRT/MSVC:
glfw-imgui.exe
- 947 KB
which means that to run on another computer it will require user to have Visual C++ Redistributable installed.
What if a library is static only
Some libraries might not “support” dynamic linking, which in most cases means that you can’t build them as DLLs on Windows, as they don’t export any symbols. That’s okay, you’ll just have to force static linking on them by putting the following in the very beginning of their portfile:
if (VCPKG_LIBRARY_LINKAGE STREQUAL dynamic) # probably also check if it is Windows, as it might be fine on other platforms
message(STATUS "[INFO] ${PORT} doesn't support building as dynamic library, overriding to static")
set(VCPKG_LIBRARY_LINKAGE static)
endif()
It also won’t hurt to check in the library’s CMakeLists.txt
that its authors don’t hardcode the library type in add_library()
statements.
Per-platform specifics
WebAssembly
Chainloading Emscripten toolchain
Just like with other platforms, targetting WebAssembly (WASM) requires you to use an appropriate triplet. There is no suitable triplet among the “official” ones at the moment, but there is a community triplet wasm32-emscripten
.
And while the dependencies (ports) will (hopefully) build just fine, here’s a surprise that will be waiting for you when CMake will try to find_package()
them for your main project:
CMake Error at D:/programs/vcpkg/scripts/buildsystems/vcpkg.cmake:843 (_find_package):
Could not find a configuration file for package "fmt" that is compatible
with requested version "".
The following configuration files were considered but not accepted:
D:/temp/emsc/build/vcpkg-default-triplet/vcpkg_installed/wasm32-emscripten/share/fmt/fmt-config.cmake, version: 8.1.1 (32bit)
Call Stack (most recent call first):
CMakeLists.txt:8 (find_package)
-- Configuring incomplete, errors occurred!
And this is not just about fmt, you’ll get this error for other dependencies too (although not all of them, details below). The actual fuck. Just what am I supposed to do with this?
After some googling I found this question at StackOverflow with some more details. So apparently this happens when a package contains a version config (fmt does contain one) that is generated by write_basic_package_version_file() without ARCH_INDEPENDENT
being set (and I have never seen it being set in any package), because then the following condition is added to the config:
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "4")
math(EXPR installedBits "4 * 8")
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
I also checked what kind of binary I got built, and it was in fact a WebAssembly binary:
$ cd /tmp
$ mkdir fmt-lib-contents && cd $_
$ ar xv /path/to/your-project/build/vcpkg_installed/wasm32-emscripten/lib/libfmt.a
x - format.cc.o
x - os.cc.o
$ ls
format.cc.o os.cc.o
$ file ./*
./format.cc.o: WebAssembly (wasm) binary module version 0x1 (MVP)
./os.cc.o: WebAssembly (wasm) binary module version 0x1 (MVP)
And yet find_package()
was failing, because CMAKE_SIZEOF_VOID_P
equaled 8
. So, whose fault is that CMAKE_SIZEOF_VOID_P
is set to 8
, while the package is built for 4
?
By the way, even if write_basic_package_version_file()
did have ARCH_INDEPENDENT
set, here’s a related CMake bugreport, from which it seems that it wouldn’t work anyway (at least not until CMake 3.26 (or even newer) it wouldn’t). But this is actually not important, more details to follow.
So what can one do? After some more googling I’ve stumbled upon this workaround in Qt sources, where they just clear the CMAKE_SIZEOF_VOID_P
variable before finding a package and then set it back. In my case it would be like this:
#message(STATUS "CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}")
set(BACKUP_CMAKE_SIZEOF_VOID_P "${CMAKE_SIZEOF_VOID_P}")
set(CMAKE_SIZEOF_VOID_P "")
find_package(fmt CONFIG REQUIRED)
set(CMAKE_SIZEOF_VOID_P "${BACKUP_CMAKE_SIZEOF_VOID_P}")
Then the package is found, and configuration succeeds. But I have 30+ dependencies in my project, and they are spread across several different subprojects, so I most definitely don’t want to wrap every single find_package()
like this. And anyway, this doesn’t seem like a correct way to deal with the situation.
And then a couple of days later I finally tried to set the VCPKG_CHAINLOAD_TOOLCHAIN_FILE
in CLI when running CMake:
$ cd /path/to/emsdk
$ source ./emsdk_env.sh
$ cd /path/to/some/project
$ mkdir build && cd $_
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
-DVCPKG_TARGET_TRIPLET="wasm32-emscripten" \
..
$ cmake --build . --target install
I mean, I’ve been always setting it with -DDCMAKE_TOOLCHAIN_FILE="..."
when I was building my projects with Emscripten for WASM, but now with vcpkg the wasm32-emscripten
triplet already does chainload it, and so I thought that I then should not. But as it turns out, counter-intuitively enough, both of these chainloads are required, because (the way I understand it) the one chainloaded inside the triplet is used for building vcpkg ports, and the one set with -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="..."
in CLI is used for building the main project.
And what would you know, having done that, I didn’t get the errors about non-compatible 32bit packages, as this time CMAKE_SIZEOF_VOID_P
equaled 4
.
It certainly wouldn’t hurt to have this documented somewhere.
CMAKE_PREFIX_PATH isn’t used
That’s not exactly related to vcpkg, but since we are on the topic of Emscripten toolchain: when I tried to use vcpkg-resolved dependencies and my library in another project, like so:
$ cd /path/to/some/other/project
$ mkdir build && cd $_
$ cmake -G "Ninja" -DCMAKE_PREFIX_PATH="/path/to/my/library/install" \
-DCMAKE_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
..
I suddenly got errors about not finding packages configs, even though CMAKE_PREFIX_PATH
is clearly set and is correct.
And it turned out that Emscripten toolchain sets certain variables which “disable” using CMAKE_PREFIX_PATH
for packages discovery. There are also some more details (and confusion) in this thread.
Here’s that particular fragment from Emscripten.cmake
:
# Since Emscripten is a cross-compiler, we should never look at the
# system-provided directories like /usr/include and so on. Therefore only
# CMAKE_FIND_ROOT_PATH should be used as a find directory. See
# http://www.cmake.org/cmake/help/v3.0/variable/CMAKE_FIND_ROOT_PATH_MODE_INCLUDE.html
if (NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
endif()
if (NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
if (NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
endif()
So it’s CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
being set to ONLY
what’s causing the problem. And so then there are two “solutions” for that.
Either provide CMAKE_FIND_ROOT_PATH
instead of CMAKE_PREFIX_PATH
:
$ cmake -G "Ninja" -DCMAKE_FIND_ROOT_PATH="/path/to/my/library/install" \
-DCMAKE_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
..
or keep CMAKE_PREFIX_PATH
but set CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
to BOTH
:
$ cmake -G "Ninja" -DCMAKE_PREFIX_PATH="/path/to/my/library/install" \
-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE="BOTH" \
-DCMAKE_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
..
I do not know which one is “correct”. Both of the “solutions” worked (equally?) fine for me, as I haven’t noticed any problems so far.
Enabling pthreads support
Emscripten can compile to WASM with pthreads (POSIX threads) support enabled. For that one needs to set -pthread
compiler flag when building one’s project.
I did set that flag in my main project, started the build, got the ports built, got the project built too, but then I got the following errors on linking:
wasm-ld: error: --shared-memory is disallowed by png.c.o because it was not compiled with 'atomics' or 'bulk-memory' features.
...
wasm-ld: error: --shared-memory is disallowed by 4D_api.cpp.o because it was not compiled with 'atomics' or 'bulk-memory' features.
...
wasm-ld: error: --shared-memory is disallowed by Threads.c.o because it was not compiled with 'atomics' or 'bulk-memory' features.
From these I realized that adding -pthread
flag in the main project is not enough, as it also needs to be set for ports compilation too. To test this theory I’ve patched those ports with errors (as you can see, the first one is from PNG and the second one is from PROJ) to set the flags:
# string(APPEND ... " ...") would do fine too
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
#set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -pthread")
#set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pthread")
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pthread")
And indeed, after that the first two errors went away, but the third one was still remaining, and I had no idea which port is it from and is it from a port at all.
But okay, at least this confirmed that I was on the right track, just needed to figure out how to set a compiler flag for all the ports at once, as one certainly should not patch the flags in every single port.
So how does one set a “global” compilation flag to be applied to building all the ports? My first naive idea was to just set these CMAKE_*_FLAGS
in the triplet, which I naively did, but as it turned out, they were never passed further, because ports didn’t “receive” them (I checked that by printing out all the flags with message()
).
Additional googling led me here, and from there I eventually got to an understanding.
Related vcpkg documentation says that “when not using VCPKG_CHAINLOAD_TOOLCHAIN_FILE
” compiler flags can be set with VCPKG_*_FLAGS variables in the triplet:
set(VCPKG_C_FLAGS "-pthread")
set(VCPKG_CXX_FLAGS "-pthread")
and then the toolchain (one of the /path/to/vcpkg/scripts/toolchains/*.cmake
ones) will use these variables to set the CMAKE_*_FLAGS_INIT variables, which in turn will be used to set the CMAKE_*_FLAGS
variables, which are the flags we are after.
But we are in fact using VCPKG_CHAINLOAD_TOOLCHAIN_FILE
, so setting VCPKG_*_FLAGS
variables in the triplet won’t work (certainly didn’t work for me), so what does one do then?
It is my understanding (which might be wrong) that in this case one would need to set CMAKE_*_FLAGS_INIT
variables in the chainloaded toolchain itself. But we are chainloading the Emscripten toolchain from ${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
, and it won’t be correct to modify the original file, especially that it is still expected to be used for non-pthreads-enabled WASM builds, so I guess we’ll need to copy and modify the original. But then in turn we will need to use a custom triplet too, because the default one chainloads the original Emscripten toolchain.
So, here comes a custom triplet my-wasm32-emscripten-pthreads.cmake
:
# that is required (only on Windows?), otherwise it won't get environment variables
set(VCPKG_ENV_PASSTHROUGH_UNTRACKED EMSCRIPTEN_TOOLCHAIN_FILE_PATH EMSDK PATH)
if(NOT DEFINED ENV{EMSDK})
message(FATAL_ERROR "[ERROR] The EMSDK environment variable isn't set, you probably haven't sourced emsdk_env.sh")
endif()
set(EMSCRIPTEN_TOOLCHAIN_FILE_PATH "$ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake")
# yet to find a better way to pass this value to the triplet, but so far an environment variable seems to be the way
if(NOT DEFINED ENV{EMSCRIPTEN_TOOLCHAIN_FILE_PATH})
message(STATUS "Didn't get EMSCRIPTEN_TOOLCHAIN_FILE_PATH from environment, keeping default value: ${EMSCRIPTEN_TOOLCHAIN_FILE_PATH}")
else()
set(EMSCRIPTEN_TOOLCHAIN_FILE_PATH "$ENV{EMSCRIPTEN_TOOLCHAIN_FILE_PATH}")
message(STATUS "Got EMSCRIPTEN_TOOLCHAIN_FILE_PATH from environment: ${EMSCRIPTEN_TOOLCHAIN_FILE_PATH}")
endif()
if(NOT EXISTS ${EMSCRIPTEN_TOOLCHAIN_FILE_PATH})
message(FATAL_ERROR "[ERROR] Could not find Emscripten.cmake toolchain file, expected it to be at ${EMSCRIPTEN_TOOLCHAIN_FILE_PATH}")
endif()
set(VCPKG_TARGET_ARCHITECTURE wasm32)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE static)
set(VCPKG_CMAKE_SYSTEM_NAME Emscripten)
# required, otherwise "Unable to determine toolchain use for my-wasm32-emscripten-pthreads with CMAKE_SYSTEM_NAME Emscripten"
# and yes, for building the main project you'll yet again need to provide `-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="..."` in CLI
set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE ${EMSCRIPTEN_TOOLCHAIN_FILE_PATH})
And the custom Emscripten toolchain (the one passed in EMSCRIPTEN_TOOLCHAIN_FILE_PATH
environment variable) is a copy of the original one from $EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
, but with the following addition in the end:
# an exact copy of Emscripten.cmake file contents
# ...
# pthreads (https://emscripten.org/docs/porting/pthreads.html)
set(EMSCRIPTEN_PTHREADS_FLAGS "-pthread")
# it is likely that not all of these need to be set, CMAKE_{C,CXX}_FLAGS_INIT seems to be enough
string(APPEND CMAKE_C_FLAGS_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_C_FLAGS_DEBUG_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_C_FLAGS_RELEASE_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
string(APPEND CMAKE_CXX_FLAGS_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_CXX_FLAGS_DEBUG_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_CXX_FLAGS_RELEASE_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_STATIC_LINKER_FLAGS_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_STATIC_LINKER_FLAGS_DEBUG_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_STATIC_LINKER_FLAGS_RELEASE_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_SHARED_LINKER_FLAGS_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_SHARED_LINKER_FLAGS_DEBUG_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_MODULE_LINKER_FLAGS_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_MODULE_LINKER_FLAGS_DEBUG_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_MODULE_LINKER_FLAGS_RELEASE_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
#string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
But actually instead of copying the entire toolchain file contents wouldn’t it be smarter to just include it, right, so here’s a better variant:
include($ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake)
# pthreads (https://emscripten.org/docs/porting/pthreads.html)
set(EMSCRIPTEN_PTHREADS_FLAGS "-pthread")
string(APPEND CMAKE_C_FLAGS_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
string(APPEND CMAKE_CXX_FLAGS_INIT " ${EMSCRIPTEN_PTHREADS_FLAGS}")
Having saved it somewhere at /path/to/custom/toolchains/Emscripten-pthreads.cmake
, we can now configure and build the project like this:
$ cd /path/to/emsdk
$ source ./emsdk_env.sh
$ cd /path/to/some/project
$ mkdir build && cd $_
$ EMSCRIPTEN=${EMSDK}/upstream/emscripten EMSCRIPTEN_TOOLCHAIN_FILE_PATH=/path/to/custom/toolchains/Emscripten-pthreads.cmake \
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DVCPKG_OVERLAY_TRIPLETS="/path/to/custom/triplets" \
-DVCPKG_TARGET_TRIPLET="my-wasm32-emscripten-pthreads" \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
..
$ cmake --build . --target install
Some clarification about all that:
- why I am setting environment variables inline instead of exporting them - because it’s better like this for experimentation (so I don’t forget them being set in the environment from the previous runs);
EMSCRIPTEN_TOOLCHAIN_FILE_PATH
is set because I haven’t found another way to pass the path to my custom Emscripten toolchain into the triplet;EMSCRIPTEN
is set because for whatever reason everything fails, complaining that this variable is missing, even though I certainly did not set it when I used the default toolchain/triplet;
-DVCPKG_OVERLAY_TRIPLETS="..."
is how you tell vcpkg where to look for additional/custom triplets;-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="..."
still points to the default Emscripten toolchain because I am already setting-pthread
compiler flags (plus some linker flags) inside my main project’sCMakeLists.txt
, so I don’t need a custom toolchain that does that. But I probably should use the same custom toolchain (the one that is chainloaded insidemy-wasm32-emscripten-pthreads
triplet for building ports) here too, I will see about that.
Now everything finally builds and links into WASM with pthreads enabled.
Windows
MSVC toolset
In one case I needed to build my project with MSVC 141 toolset, which I did by setting -G "Visual Studio 15 2017" -A "x64"
, but apparently that only applied to the project itself, and looks like vcpkg built the dependencies with a different toolset (likely with MSVC 143, as that’s the latest I have installed), because I got the following errors on linking:
error LNK2001: unresolved external symbol __CxxFrameHandler4
error LNK2001: unresolved external symbol __GSHandlerCheck_EH4
As this StackOverflow thread says, the __CxxFrameHandler4
is a part of the newer FH4 exception handling, which apparently appeared in MSVC 142 toolset and newer, and so the resolution is to tell vcpkg to build dependencies using the same toolset as the one used to build the project.
That can be done on the triplet level, so yet again I’ve created a custom triplet my-x64-windows-v141.cmake
:
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE static)
# here you specify the MSVC toolset
set(VCPKG_PLATFORM_TOOLSET "v141")
# not sure what this one is for
set(VCPKG_DEP_INFO_OVERRIDE_VARS "v141")
And now I build the whole thing like this:
$ cd /path/to/some/project
$ mkdir build && cd $_
$ cmake -G "Visual Studio 15 2017" -A "x64" \
-DVCPKG_OVERLAY_TRIPLETS="/path/to/custom/triplets" \
-DVCPKG_TARGET_TRIPLET="my-x64-windows-v141" \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
..
$ cmake --build . --target install --config Release
As usual, this better to be run from the corresponding MSVC command prompt, so you’ll need to adjust the shell-specific things.
Release binaries size is bigger with vcpkg
Suddenly I noticed that my Windows build artifacts got bigger in size. They were smaller before I started resolving dependencies with vcpkg, but now they got bigger.
I suspected that there might be some optimization flags not set, and I was almost right, although the other way around - turns out, by default vcpkg sets Debug flag /Z7
for Release configuration! See for yourself in the Windows toolchain (/path/to/vcpkg/scripts/toolchains/windows.cmake
):
set(CMAKE_CXX_FLAGS_RELEASE
"${VCPKG_CRT_LINK_FLAG_PREFIX} /O2 /Oi /Gy /DNDEBUG /Z7 ${VCPKG_CXX_FLAGS_RELEASE}"
CACHE STRING ""
)
set(CMAKE_C_FLAGS_RELEASE
"${VCPKG_CRT_LINK_FLAG_PREFIX} /O2 /Oi /Gy /DNDEBUG /Z7 ${VCPKG_C_FLAGS_RELEASE}"
CACHE STRING ""
)
From the documentation for /Z7
flag:
The /Z7
option produces object files that also contain full symbolic debugging information for use with the debugger. These object files and any libraries built from them can be substantially larger than files that have no debugging information.
I mean, sounds useful for Debug configuration, but why is it set for Release configuration too?
Googling this question brought me to this issue in vcpkg repository, so at least I wasn’t the only one to question the sanity of this. And here’s what one of the vcpkg maintainers said in the discussion thread over there:
There's no reason to remove debugging information; we believe CMake's default setting which includes not even stripped symbols in release mode is an incorrect default.
Well, I certainly believe that CMake does this absolutely correctly, as there is no bloody point to have anything of the sort in the Release binaries. If you want to debug, you take Debug binaries; that is why Debug/Release separation exists in the first place, is it not? Why on earth would you want to be able to debug with Release binaries? Sweet suffering Jehovah.
Removing /Z7 flag from Release configuration
So, as we don’t have unlimited storage for our packages and distributives and in general we don’t like wasting network bandwidth transferring useless data, we’d like to remove /Z7
flag from the default Windows toolchain to reduce our dependencies binaries size back to normal.
The procedure is very similar to how I added -pthread
flag to enable pthreads support in Enscripten/WASM builds. There are several options:
- Make patches for every single port sources to override flags in their
CMakeLists.txt
files. That is the worst option out of all listed here; - Modify the flags directly in the default toolchain (
/path/to/vcpkg/scripts/toolchains/windows.cmake
). I would not recommend this, because:- that modification will get overridden on the next vcpkg update/reinstall;
- you’ll need to do that on your every machine or/and every buildbot in your CI/CD;
- other people with default toolchains will be getting different build artifacts even though they’d be using the same triplets as you, and that might backfire at some point;
- Copy the default toolchain into your project repository, modify the flags there and chainload it with
VCPKG_CHAINLOAD_TOOLCHAIN_FILE
. This option is almost good, but dragging around a copy of the default toolchain isn’t ideal, at the very least because it will eventually go out of sync with the default one; - Instead of copying the contents of the default toolchain, simply include it in your custom chainloaded toolchain and override the flags right after that.
I actually had one more option, which is what I tried first: still create a custom toolchain to be chainloaded but without including the default toolchain - just seting new values to the flags and that’s it. But right away I discovered two problems with this:
- you need to somehow get the current values of the flags that have been already set upper in the chain. You can of course just set the flags to whatever you want, but ideally one would like to preserve all the original flags and just remove the
/Z7
. And I didn’t find a way to do it; - the real showstopper, however, is the fact that if you don’t include the default toolchain, then all the flags it sets won’t be set at all (for example,
/utf-8
flag will be missing, so you might gettoo many characters in constant
compilation error). I, for one, was expecting those flags to be set, since my custom toolchain is chainloaded and not loaded instead of the default one, but apparently I’m missing something about the way chainloading works.
So the 4th option is what I went with (include the default toolchain in a custom toolchain and override flags right after). Yes, it seems to be redundant and rather weird to include the exact same toolchain that has been (hasn’t it?) just used upper in the chain, but that is what worked for me.
Here’s my custom toolchain (/path/to/custom/toolchains/windows.cmake
):
# for $ENV{VCPKG_ROOT} to work the triplet should contain `set(VCPKG_ENV_PASSTHROUGH_UNTRACKED VCPKG_ROOT)`
include("$ENV{VCPKG_ROOT}/scripts/toolchains/windows.cmake")
# remove /Z7 from the default set of C flags
string(REPLACE "/Z7" ""
CMAKE_C_FLAGS_RELEASE_WITHOUT_Z7
"${CMAKE_C_FLAGS_RELEASE}"
)
# override Release flags variable with the new value
set(CMAKE_C_FLAGS_RELEASE
"${CMAKE_C_FLAGS_RELEASE_WITHOUT_Z7}"
CACHE STRING ""
# it's important to apply FORCE here, as this variable is CACHE
# and it has been already set in the default toolchain above
FORCE
)
# and the same for CXX flags
string(REPLACE "/Z7" ""
CMAKE_CXX_FLAGS_RELEASE_WITHOUT_Z7
"${CMAKE_CXX_FLAGS_RELEASE}"
)
set(CMAKE_CXX_FLAGS_RELEASE
"${CMAKE_CXX_FLAGS_RELEASE_WITHOUT_Z7}"
CACHE STRING ""
FORCE
)
And here’s my custom triplet (/path/to/custom/triplets/my-x64-windows-static-md.cmake
) that chainloads it:
# default values from the original x64-windows-static-md.cmake
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE static)
# without this the VCPKG_ROOT environment variable
# won't be available in the chainloaded toolchain
set(VCPKG_ENV_PASSTHROUGH_UNTRACKED VCPKG_ROOT)
#
# overriding default flags to remove /Z7 from Release builds,
# because no one (except Microsoft) needs Debug stuff in Release binaries
#
# this could do with a better path instead of ..
set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/../toolchains/windows.cmake)
# on Windows VCPKG_CHAINLOAD_TOOLCHAIN_FILE deactivates VS variables,
# so those need to be loaded again
set(VCPKG_LOAD_VCVARS_ENV 1)
Two things are important to note here:
VCPKG_ROOT
environment variable needs to be passed through, otherwise chainloaded toolchain won’t have it;- unless you’ve read about this in documentation, you wouldn’t know that in case of chainloaded toolchains the
VCPKG_LOAD_VCVARS_ENV
is set toOFF
, which leads to CMake errors likeNo CMAKE_C_COMPILER could be found
and other typical problems of not finding compilers and tools, so you need to set it back toON
/1
.
Now I can configure and build my project like this:
$ cd /path/to/some/project
$ mkdir build && cd $_
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DVCPKG_OVERLAY_TRIPLETS="/path/to/custom/triplets"
-DVCPKG_TARGET_TRIPLET="my-x64-windows-static-md" \
..
$ cmake --build . --target install
For that to work the custom toolchain needs to be in /path/to/custom/toolchains/
, as for triplet the ${CMAKE_CURRENT_LIST_DIR}
will evaluate to /path/to/custom/triplets/
, and so ../toolchains/
with be the right path.
Comparison of binary sizes with and without /Z7
Let’s see some real sizes of Release binaries that are built with and without /Z7
flag. If you’d like to repeat the experiment yourself, here’s a dummy project that I used for it.
First build the libraries with default Release flags (so with /Z7
among them):
$ cd /path/to/z7-binary-size-comparison
$ vcpkg install --triplet x64-windows-static-md
$ du -hs ./vcpkg_installed/x64-windows-static-md/lib
Then build the same libraries but with a custom triplet to remove /Z7
from Release flags:
$ mv ./vcpkg_installed ./vcpkg_installed_z7
$ vcpkg install --overlay-triplets ./triplets --triplet my-x64-windows-static-md
$ du -hs ./vcpkg_installed/my-x64-windows-static-md/lib
In my case the results were:
Debug | Release | ||
---|---|---|---|
With /Z7 |
Without /Z7 |
||
assimp-vc143-mt.lib | 214.9 MB | 195.3 MB | 63.8 MB |
dearimgui.lib | 5.8 MB | 7.4 MB | 3.4 MB |
draco.lib | 109.5 MB | 94.4 MB | 27 MB |
glfw3.lib | 1.9 MB | 2.1 MB | 633.1 KB |
sqlite3.lib | 3.2 MB | 6.5 MB | 2.7 MB |
zlib.lib | 364.1 KB | 441 KB | 186.5 KB |
zstd.lib | 3.8 MB | 6.6 MB | 1.8 MB |
312.74 MB | 99.52 MB |
So not only Release binaries with /Z7
flag are more than 3 times bigger than their variants without it, but also for most libraries they are even bigger than the Debug variants.
I mean, goddamn. The only valid reason for setting this flag by default in Release configurations would be if people behind vcpkg were selling data storage or other hosting services of the kind. Wait… oh shi~
Building with Clang instead of MSVC
Later I needed to build the project and its dependencies with Clang instead of MSVC.
Clang (LLVM) can be downloaded either separately from its website, or you can install it via Visual Studio Installer as a Visual Studio component.
In case of the project, if you installed LLVM yourself, choosing Clang as compiler can be done with Ninja generator:
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER:PATH="d:/path/to/llvm/bin/clang-cl.exe" \
-DCMAKE_CXX_COMPILER:PATH="d:/path/to/llvm/bin/clang-cl.exe" \
..
$ cmake --build .
or with Visual Studio generator:
$ cmake -G "Visual Studio 17 2022" ..
$ cmake --build . --config Release -- -p:CLToolExe=clang-cl.exe -p:CLToolPath="d:/path/to/llvm/bin"
The variant with -p:CLToolExe
might be useful if you have MSVC-specific compiler flags hardcoded in your project.
If you installed LLVM via Visual Studio Installer, then it can be just this:
$ cmake -G "Visual Studio 17 2022" -T "ClangCL" ..
$ cmake --build . --config Release
If it doesn’t find the compilers/tools, you can try to set the VS environment (from cmd
, that is):
> call d:\path\to\visual-studio\VC\Auxiliary\Build\vcvars64.bat
Actually, you might want to do this every time you’d like to use Visual Studio tools.
If you have both Clang that you installed yourself and Clang which is a part of Visual Studio, you might want to make sure that both your project and vcpkg-resolved dependencies are built with the same one.
Anyway, that was the project itself, but how to build vcpkg-resolved dependencies with Clang as well? As in previous cases, you would need to make a custom toolchain for that and chainload it from a custom triplet.
The triplet is simple enough:
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE static)
set(VCPKG_PLATFORM_TOOLSET ClangCL)
# otherwise these environment variables won't be available in the chainloaded toolchain
set(VCPKG_ENV_PASSTHROUGH_UNTRACKED LLVMInstallDir LLVMToolsVersion)
# finding and setting C/CXX compiler to Clang
# this could do with a better path instead of ..
set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/../toolchains/windows-clang.cmake)
# on Windows VCPKG_CHAINLOAD_TOOLCHAIN_FILE deactivates automatic VS variables, so those need to be loaded again
set(VCPKG_LOAD_VCVARS_ENV 1)
Its main purpose is to chainload a custom toolchain, which will set Clang as compiler(s). You can take any other default triplet as a base and just add this chainloading there.
The toolchain, on the other hand, is far from being simple. While the essential thing that it does is “just” setting CMAKE_C_COMPILER
and CMAKE_CXX_COMPILER
to the discovered Clang executable, there are several other things that need to be taken care of, such as setting C/C++ standards and compiler/linker flags, some of which are necessary for successful compilation of certain sources.
I would have never figured out the full set on my own, but luckily I’ve stumbled upon this repository, which seems to be all about using Clang with vcpkg in various combinations. The degree of customization and overriding stuff there seems to be rather overcomplicated, or at least it is so for my low ICQ. Так что я ещё ничего, а вот у парняги реально беды с башкой, при всём уважении, и спасибо ему что он есть, мощный тип.
I took this toolchain of his as a base, removed a lot of stuff from it, which I thought was redundant didn’t understand the purpose of, and combined it with default Microsoft’s Windows toolchain. The resulting toolchain is published in my registry.
The most important part of this toolchain is setting the compiler(s):
find_program(CLANG_CL_EXECUTBALE
NAMES
"clang-cl"
"clang-cl.exe"
PATHS
ENV
LLVMInstallDir
PATH_SUFFIXES
"bin"
# it is found exactly in default paths, as I am not passing LLVMInstallDir,
# but if you will, then uncomment this
#NO_DEFAULT_PATH
)
set(CMAKE_C_COMPILER "${CLANG_CL_EXECUTBALE}" CACHE STRING "")
set(CMAKE_CXX_COMPILER "${CLANG_CL_EXECUTBALE}" CACHE STRING "")
But like I said, setting flags is also important, so I’d recommend you to take a look at the full toolchain.
The whole thing to configure and build the project now becomes this:
> call d:\path\to\visual-studio\VC\Auxiliary\Build\vcvars64.bat
> cd d:\path\to\some\project
> mkdir build
> cd build
> cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ^
-DCMAKE_C_COMPILER:PATH="d:/path/to/llvm/bin/clang-cl.exe" ^
-DCMAKE_CXX_COMPILER:PATH="d:/path/to/llvm/bin/clang-cl.exe" ^
-DCMAKE_TOOLCHAIN_FILE:PATH="%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake" ^
-DVCPKG_OVERLAY_TRIPLETS:PATH="d:/path/to/custom/triplets" ^
-DVCPKG_TARGET_TRIPLET="decovar-x64-windows-static-md-clang" ^
..
> cmake --build .
You’ll probably also need to set LLVMInstallDir
environment variable, just to be sure that vcpkg gets to use the same Clang version for building dependencies as the one you’ve set for the project. But then for this to work you’d need to uncomment NO_DEFAULT_PATH
of Clang executable discovery in the toolchain.
Or, if you installed Clang via Visual Studio Installer, then just use the -T
variant instead:
> call d:\path\to\visual-studio\VC\Auxiliary\Build\vcvars64.bat
> cd d:\path\to\some\project
> mkdir build
> cd build
> cmake -G "Visual Studio 17 2022" -T "ClangCL" ^
-DCMAKE_TOOLCHAIN_FILE:PATH="%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake" ^
-DVCPKG_OVERLAY_TRIPLETS:PATH="d:/path/to/custom/triplets" ^
-DVCPKG_TARGET_TRIPLET="decovar-x64-windows-static-md-clang" ^
..
> cmake --build . --config Release
Then there will be no need to set LLVMInstallDir
(and no need to uncomment NO_DEFAULT_PATH
).
LZMA fails without AVX2 intrinsics
Not strictly related to vcpkg, but still related. As soon as I’ve set Clang for building vcpkg-resolved dependencies, one of them - LZMA - failed to build.
I’ve fetched its sources and tried to build it separately with MSVC, and that still succeeded. But building with Clang was failing:
LzFind.c(641,3): error : use of undeclared identifier '__m256i'
LzFind.c(646,5): error : expected expression
LzFind.c(630,33): message : expanded from macro 'SASUB_256'
It turned out that building this project with Clang requires setting /arch:AVX2
C flag. Why this is not required when building with MSVC - that I don’t know, and I don’t see that flag being set in the default Windows vcpkg toolchain either. Also surprisingly, building LZMA on Mac OS with Clang doesn’t have this problem either.
I wasn’t sure if it is a good idea to set this flag in the toolchain, so I decided to set it in the LZMA’s CMakeLists.txt
and make it a port feature - here’s the commit with this change.
GNU/Linux
Missing executable attribute
Trying to build iconv port on GNU/Linux I got the following rather unexpected error:
/bin/bash: ./../src/133172cac4-7cae2f41cd.clean/configure: Permission denied
And indeed, the configure
script doesn’t have x
attribute/mode, so it is not an executable:
$ ls -l /path/to/vcpkg/buildtrees/iconv/src/133172cac4-7cae2f41cd.clean | grep config
-rw-r--r-- 1 build build 50810 Feb 2 14:09 config.h.in
-rw-r--r-- 1 build build 891440 Feb 2 14:09 configure
-rw-r--r-- 1 build build 6087 Feb 2 14:09 configure.ac
I don’t know how is that this attribute is missing, and why I didn’t get this problem on Mac OS, but to resolve this I had to to call chmod
via vcpkg_execute_build_process()
in the iconv portfile:
if(VCPKG_HOST_IS_LINUX) # OR VCPKG_HOST_IS_OSX
# on GNU/Linux (and Mac OS?) the configure script might(?) be not an executable
vcpkg_execute_build_process(
COMMAND chmod +x ./configure
WORKING_DIRECTORY ${SOURCE_PATH}
LOGNAME config-${PORT}-${TARGET_TRIPLET}-chmod
)
endif()
vcpkg_configure_make(
SOURCE_PATH "${SOURCE_PATH}"
# ...
)
Dependency graph
Conan has a convenient out-of-the-box functionality for generating a dependency graph, both as a plain-text JSON file and as an interactive HTML page.
The vcpkg’s capabilities in that regard are rather modest in comparison. It has a depend-info command, which can list dependencies either in a somewhat structured plain-text format or in two graph formats: DOT and DGML. Neither of these formats can be customized, and all of them output just port names (no version, license or any other information).
Furthermore, when it comes to graphs, it is still your responsibility to visualize them (with Graphviz or some other tool), as vcpkg only produces the text representation. For example, let’s use the DOT format to build a dependency graph for that project from before:
$ vcpkg depend-info glfw-imgui-example \
--overlay-ports=/path/to/project \
--overlay-ports=/path/to/vcpkg-registry/ports \
--dot | nop > ./graph.dot
Here the first --overlay-ports
path points to the project folder (that contains the project’s vcpkg.json
manifest) and the second --overlay-ports
path points to a local clone of my registry. Note that I still needed to provide the glfw-imgui-example
project name. The piped nop
utility is a part of Graphviz tools and what it does is it formats the graph text into a nicer readable representation.
If I wanted to build a graph not for a project but for a port from registry, then I would’ve invoked the command with just one --overlay-ports
path, like this:
$ vcpkg depend-info some-port \
--overlay-ports=/path/to/vcpkg-registry/ports \
--dot | nop > ./graph.dot
But I don’t yet have ports with a lot of dependencies in my registry, so let’s continue with the project graph.
The contents of the generated (and formatted) graph.dot
are this:
digraph G {
graph [overlap=false,
rankdir=LR
];
edge [minlen=3];
dearimgui -> decovar_vcpkg_cmake;
dearimgui -> glfw;
dearimgui -> vcpkg_cmake;
dearimgui -> vcpkg_cmake_config;
glfw -> vcpkg_cmake;
glfw -> vcpkg_cmake_config;
glad -> vcpkg_cmake;
glad -> vcpkg_cmake_config;
glfw_imgui_example -> dearimgui;
glfw_imgui_example -> glfw;
glfw_imgui_example -> glad;
empty [label="3 singletons..."];
}
That can be visualized with Graphviz like so:
$ dot -T svg ./graph.dot \
-Nfontcolor=blue -Nshape=rect -Grankdir=TB -Gsplines=ortho \
-o ./graph.svg
The result will look like this:
So it works, but the visualization could benefit from some improvements, such as removing redundant elements and adding some different colors/accents. To automate the process, modifications in the graph.dot
can be done using regular expressions, and for that purpose it would probably be better to re-generate the graph.dot
without formatting (without piping to nop
):
$ vcpkg depend-info glfw-imgui-example \
--overlay-ports=/path/to/project \
--overlay-ports=/path/to/vcpkg-registry/ports \
--dot > ./graph.dot
First thing to get rid of is the “3 singletons…” label (the fuck is that, I didn’t ask for it). It can be removed with sed
(or gsed
, in case of Mac OS):
$ sed -i 's/empty \[label="[[:digit:]]\+ singletons\.\{3\}"\];//' ./graph.dot
but actually, sed
and its crazy regular expressions flavor can go to hell, so do it with Perl instead:
$ perl -pi -e 's/empty \[label="\d+ singletons\.{3}"\];//' ./graph.dot
Next thing you might want to do is to remove helper ports, because they are not actual C++ dependencies, and also every single port depends on them, so they only add noise to the graph, which becomes a bigger problem on bigger graphs. Either remove them one by one:
$ perl -pi -e 's/[\w]+ -> vcpkg_cmake;//g' ./graph.dot
$ perl -pi -e 's/[\w]+ -> vcpkg_cmake_config;//g' ./graph.dot
$ perl -pi -e 's/[\w]+ -> decovar_vcpkg_cmake;//g' ./graph.dot
or all at once, if they have a common part in their names:
$ perl -pi -e 's/[\w]+ -> (decovar_)?vcpkg_cmake(_config)?;//g' ./graph.dot
Finally, let’s color connections to direct dependencies with blue color, and connections to transitive dependencies will be dashed:
$ perl -pi -e 's/(glfw_imgui_example -> .+?(?=;))/$1 [color=blue]/g' ./graph.dot
$ perl -pi -e 's/((?!glfw_imgui_example\b)\b\w+ -> .+?(?=;))/$1 [style=dashed]/g' ./graph.dot
Now the visualized graph will look like this:
Much cleaner, isn’t it. Although, still not as good as what Conan provides (via a single command without additional massaging), but more or less an okay result.
One last thing to note is that on the graph above the glfw
port is both a direct and a transitive dependency (through dearimgui
), so perhaps the point of having differently styled connections to transitive dependencies isn’t very clear. But here’s a graph for a bigger project:
And here we have (just one but nevertheless) a “purely” transitive dependency - brotli
(through cpp_http
), which is easier to spot thanks to a dashed black connection and no solid blue connections comming to it.
So, how is vcpkg comparing with Conan
Simplicity
Like I said, essentially, vspkg is just a combination of CMake and Git (plus some glue provided by the vcpkg CLI tool), which are very common basic instruments everyone is (should be) already familiar with.
They are fewer components or moving parts than it is with Conan. Especially when it comes to registries: there is no need to involve a complex 3rd-party service such as JFrog Artifactory (even though you can host it in-house), because vcpkg registries are just good old plain Git repositories, which are very simple to create and maintain.
Speed
The “speed” might not be the right term here, but for me creating vcpkg packages ports and integrating vcpkg into a project was easier to understand and simpler to implement than doing the same with Conan.
For instance, the research project that took about two weeks to implement with Conan, with vcpkg it barely took 3 days.
Documentation
Paradoxically enough, vcpkg’s documentation seems worse than Conan’s, or at least less detailed (and sometimes just missing), and yet it was easier to get started with vcpkg than it was with Conan. Things just make sense and many questions/problems you can figure out on your own.
Speaking about documentation, there are 3 different places/domains where you can find it. Certainly, all of them likely have the same source, but still such distribution does not help to inspire confidence:
- https://github.com/microsoft/vcpkg/ - probably use this one, for it’s the source;
- https://vcpkg.io/;
- https://vcpkg.readthedocs.io/.
Overall impression
It seems to me that pretty much everything that vcpkg can do, Conan can do too (or maybe even more, thanks to Python). But somehow with Conan it just didn’t take off. I cannot really list clear and exact reasons why I wasn’t entirely happy with Conan. I guess, vcpkg just feels better, if one can take a “feeling” as a base for making such an important decision about one’s build system.
Yet again I’d like to say once more that our less fortunate experience with Conan might be because we used it wrong, and probably we should have spent some more time reading the documentation. But at the same time with vcpkg we were on the right track from the very beginning, so perhaps one could say that vcpkg is more intuitive to use (when you already have good enough experience with CMake).
I cannot say that Conan is worse. Even though we did not proceed with it for all our projects, I did add it to some of our smaller projects, mostly tools and demos, where it wasn’t so time-consuming to integrate, and there it was doing the job quite nicely. But now we have decided to go with integrating vcpkg in all our projects.
With that said, it’s surely nice to have alternatives, and so it’s only great that C++ developers have more than one package manager to choose from.
Updates
2023-09-01 | Build time improvements
Some months later we finished the work of moving all of our project’s bundled/vendored dependencies from the repository and making ports for them in our vcpkg registry. Along the way we also stopped producing object libraries and made all of the dependencies normal libraries.
The total amount of ports in the registry ended up being more than 60 - that is both our direct dependencies and transitive dependencies (dependencies of dependencies). Of course, after excluding platform-specific and feature-specific dependencies the number becomes about 30-40 in average, but still, that is quite a number.
So, naturally, we expected a big improvement in build times, since all the dependencies are now restored from local cache instead of being built every time from sources. And we did get an improvement, but not as big as we thought we would:

Some clarification:
- the “bundled” column shows project build time with dependencies built from bundled sources, and “vcpkg” column shows project build time when dependencies are restored pre-built from local cache;
- not all platforms are building the same amount of components. For example, Windows Clang and some other configurations do not build tools, samples and demos, so they have less work to do;
- the project is built on several different buildbots, and not all of them have the same performance (for instance, mac-1 buildbot is considerably faster than mac-8).
But why most of the Windows builds got so little improvement - that I cannot explain. Using Ninja instead of Visual Studio would certainly result in faster builds, but unfortunately we are stuck with the latter for now, and anyway the ratio would likely still be the same.
The only guess I have is based on the fact that all of those “slow” configurations (less than 10% improvement) are building everything (not just our SDK libraries but also tools, samples and demos), and configurations with big improvements (30% and more) are building just our SDK libraries, so no tools, samples or demos. And I am guessing that while compiling time has improved consistently(?) across all platforms, one should not forget that there is also linking, and apparently that process actually degraded in those configurations that build applications (tools/samples/demos). Like I said, we were building part of our dependencies as object libraries, and so now when all of the dependencies became normal libraries, that added more work for the linker (did it really?). I guess. If not that, I don’t know what then.
To test that theory I’ve run one of the Windows builds on my machine, first with everything enabled and then with disabled tools/samples/demos:

Surprisingly, even with the full build there was a 24% improvement on my machine versus 7% improvement on buildbot. Why the hell is it so? Well, I did use Ninja instead of Visual Studio generator, which certainly speeded up the build, but that applies to both build times (with and without vcpkg), so the ratio shouldn’t be affected, should it?
If you are curious why the total build time is 15/14 minutes on buildbot and only 10/8 minutes on my machine, that is because buildbots also run certain preparation steps and then do packing and publishing after the build, and all that adds up to the total time.
And then the build without tools/samples/demos got 41% improvement! So that would confirm my theory about “degraded” linking times, but I am still not sure if it really is the actual reason.
Either way, while it’s nice to get faster builds, for us that was not the main goal of using a package manager. We are mostly happy about the fact that we can finally stop bundling/vendoring 3rd-party sources in our repository and resolve the project dependencies like adults.
Social networks
Zuck: Just ask
Zuck: I have over 4,000 emails, pictures, addresses, SNS
smb: What? How'd you manage that one?
Zuck: People just submitted it.
Zuck: I don't know why.
Zuck: They "trust me"
Zuck: Dumb fucks