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.

vcpkg logo

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.

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).

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). But that was the least of the problems, as they (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 the 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.

Schematically simplified vcpkg functionality can be visualized like this:

vcpkg functionality schema

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 your one 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.exe on Windows). For convenience, you might want to add /path/to/programs/vcpkg/vcpkg to your PATH. 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:

./fmt
├── 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 the fixing-something.patch after fetching library sources and copies CMakeLists.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
)

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 the SOURCE_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 the latest (or “default”?) versions of all the packages that are available in that registry:

{
    "default":
    {
        "glfw":
        {
            "baseline": "3.3.8",
            "port-version": 0
        },
        "vcpkg-cmake":
        {
            "baseline": "2022-08-18",
            "port-version": 0
        },
        "vcpkg-cmake-config":
        {
            "baseline": "2022-02-06",
            "port-version": 0
        }
    }
}

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:

  1. Set a new Git hash (pointing to a different version tag) in ./ports/glfw/portfile.cmake;
  2. Update the version value in ./ports/glfw/vcpkg.json;
  3. Commit and do the git rev-parse thing;
  4. 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"
            ]
        }
    ]
}

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:

  1. Edit ./ports/decovar-vcpkg-cmake/vcpkg-port-config.cmake and commit this change, but not push yet;
  2. Run git rev-parse HEAD:./ports/decovar-vcpkg-cmake and set the new value to git-tree in ./versions/d-/decovar-vcpkg-cmake.json;
  3. Stage that change and amend the commit from the step 1 by running git commit --amend --no-edit;
  4. 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.

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.

One thing to note here is that 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 tripler, 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

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)

Installing packages 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:

  1. Fetched and installed host dependencies (vcpkg-cmake and vcpkg-cmake-config);
  2. Fetched, configured, built and installed GLFW, both Debug and Release configurations (there will be a note about debug subfolder later);
  3. 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;
  4. Tried to discover/guess CMake statements for using these packages in your project (find_package() and target_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.

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"
            ]
        }
    ],
    "overrides":
    [
        {
            "name": "glad",
            "version": "0.1.36"
        },
        {
            "name": "glfw",
            "version": "3.3.8"
        },
        {
            "name": "dear-imgui",
            "version": "1.88.0"
        }
    ]
}

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.

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. So, if you want to “pin exact versions for individual dependencies”, then you will have to use overrides. In my opinion, this is a bit weird and not intuitive, but okay.

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": []
}

It is also possible to have multiple registries here and specify which packages should be fetched from which one - isn’t that beautiful. Much better than how it is handled in Conan (it’s not, if I got that correctly).

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.

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:

GLFW v3.3.8

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:

GLFW v3.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 KB
  • glfw3.dll - 211.5 KB
  • DearImGui.dll - 889 KB
  • glfw-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.

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:

  1. https://github.com/microsoft/vcpkg/ - probably use this one, for it’s the source;
  2. https://vcpkg.io/;
  3. 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.