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:
./dear-imgui
├── 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 (minimum?) versions of all the packages that are available in that registry:
{
"default":
{
"dear-imgui":
{
"baseline": "1.88.0",
"port-version": 0
},
"glad":
{
"baseline": "0.1.36",
"port-version": 0
},
"glfw":
{
"baseline": "3.3.7",
"port-version": 0
},
"...":
{
"...": "..."
}
}
}
What I realized a bit later is that even if you have older versions of a port available in your registry - for example my glfw
port has versions 3.3.7
and 3.3.8
in its versions file - setting baseline
version to 3.3.8
will make version 3.3.7
(and all the older versions) unavailible/undiscoverable (or perhaps this is only the case when using >=
- the minimum version approach?), and that is what I fixed in this commit.
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.
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": "dear-imgui",
"...": "...",
"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": "dear-imgui",
"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:
./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 by vcpkg. The contents of that file should be something like this:
include_guard(GLOBAL)
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, 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")
I don’t know if this is a correct way of shipping and including common CMake modules, but first of all, it does work like this, and secondly, I just didn’t find another way.
I said “it does work”, but actually since I am relying on VCPKG_TARGET_TRIPLET
here, 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 you could perhaps use ${VCPKG_INSTALLED_DIR}/${VCPKG_HOST_TRIPLET}
instead of ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}
, but I decided to just set both to the same value:
-DVCPKG_TARGET_TRIPLET=x64-windows-static -DVCPKG_HOST_TRIPLET=x64-windows-static
And then it works. But! Later I discovered that forcing host and target triplets to match is incorrect, and one shouldn’t do that. Furthermore, it now seems to me that the way I’ve implemented sharing common files through a CMake helper port is not entirely correct either, so perhaps you shouldn’t use it as an example of doing so. I’ll re-investigate this at some point later and will update the article.
Finally, the helper’s portfile.cmake
:
if(VCPKG_CROSSCOMPILING) # if(NOT TARGET_TRIPLET STREQUAL _HOST_TRIPLET)
# make FATAL_ERROR in CI when issue #16773 fixed
message(WARNING "decovar-vcpkg-cmake is a host-only port; mark it as a host port in your dependencies")
endif()
# basically, the only thing this port does is it copies these files,
# so they are available to be included in other packages
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 is automatically included itself 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
)
# what is that
set(VCPKG_POLICY_CMAKE_HELPER_PORT enabled)
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"
)
Partial targets installation
It could be that your project contains several libraries/components/targets, so one can build and install just them, 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.
Surprisingly, vcpkg doesn’t not have out-of-the-box functionality for such 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.
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 (and that would be the least of potential problems).
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.
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.
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": "dear-imgui",
"features":
[
"backend-glfw"
]
}
]
}
The glfw
dependency is not really needed here, because it will still be added through dear-imgui
, 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:
dear-imgui[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 dear-imgui:x64-osx...
Building dear-imgui[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/dear-imgui-9aae45eb4a05a5a1f96be1ef37eb503a12ceb889.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/dear-imgui/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/dear-imgui_x64-osx/share/dear-imgui/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 dear-imgui: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)
dear-imgui 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 dear-imgui
package. That message came from decovar-vcpkg-cmake helper port, which dear-imgui
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:
dear-imgui[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 dear-imgui:x64-osx...
Elapsed time to handle dear-imgui: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)
dear-imgui 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": "dear-imgui",
"features":
[
"backend-glfw"
]
}
],
"overrides":
[
{
"name": "glad",
"version": "0.1.36"
},
{
"name": "glfw",
"version": "3.3.8"
},
{
"name": "dear-imgui",
"version": "1.88.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.
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.
And so if you want to lock/pin exact versions of dependencies, then you have to use overrides. In my opinion, this whole system is a bit weird and not intuitive, but okay.
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 dear-imgui
port manifest, then I would need to do it like this:
{
"name": "dear-imgui",
"...": "...",
"dependencies":
[
"..."
],
"features":
{
"backend-glfw":
{
"description": "Using GLFW as graphics backend",
"dependencies":
[
{
"name": "glfw",
"version>=": "3.3.8"
}
]
}
}
}
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:
dear-imgui[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 dear-imgui:x64-osx
Elapsed time to handle dear-imgui: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 dear-imgui:x64-osx...
Building dear-imgui[backend-glfw,core]:x64-osx...
-- Installing port from location: /Users/USERNAME/.cache/vcpkg/registries/git-trees/c3282e78368406b65f57ef0e3afa7cda2fc26501
-- Using cached /path/to/programs/vcpkg/downloads/dear-imgui-9aae45eb4a05a5a1f96be1ef37eb503a12ceb889.tar.gz
-- Cleaning sources at /path/to/programs/vcpkg/buildtrees/dear-imgui/src/3a12ceb889-c011915ee6.clean. Use --editable to skip cleaning for the packages you specify.
-- Extracting source /path/to/programs/vcpkg/downloads/dear-imgui-9aae45eb4a05a5a1f96be1ef37eb503a12ceb889.tar.gz
-- Using source at /path/to/programs/vcpkg/buildtrees/dear-imgui/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/dear-imgui_x64-osx/share/dear-imgui/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 dear-imgui: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.
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:
# ...
# 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}")
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.
GNU/Linux
Missing executable attribute
When 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}"
# ...
)
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.
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