1

I'm missing something in my understanding of CMake+vcpkg, and I'm also missing proper keywords to search for a solution. (Plus I'm new to both CMake and vcpkg, unfortunately.)

I want to have a public repo for a C++ project that uses CMake as its build system and vcpkg as its package manager.

At my currently level of understanding the user needs to have CMake and vcpkg already installed before he can type cmake and build the repo. I'd like to make it as simple as possible to build the repo and not have a bunch of instructions telling him how to get set up even before he can build.

  • Is this right?

I'd like a one-step solution: After cloning the repo user types ... something ... and the repo gets built.

I am willing in this day-and-age to assume he's got CMake installed ... plus that it can find the right toolchain. So maybe all he needs to type is 'cmake' ...

  • Is it a reasonable assumption that the user has CMake installed and configured with his preferred toolchain?

I am not willing to assume he's got vcpkg installed.

  • Is it a reasonable assumption that the user does not have vcpkg installed and configured?

(TBH, I don't even know if it is CMake or vcpkg that configures the toolchain - I assumed CMake but one of the suggested questions suggests it is vcpkg ...)

What are the reasonable assumptions today, and what is the minimal-step solution?

davidbak
  • 5,775
  • 3
  • 34
  • 50
  • This falls into opinion territory and SO is not the right place for this question. It is ok to assume that the user has CMake (you also assume the presence of some C/C++ compiler). Once you have that a user should be able to build and install your project directly, without `vcpkg` (clone, configure, build, install). If your project is available via `vcpkg` a user can install it directly using `vcpkg`. So you have two methods of installing the project. – icebp May 04 '21 at 09:41
  • I didn't think it was opinion. If there is some way to do it that meets the objective that's not an opinion. And I don't get what you're saying: If the CMake files list vcpkg as the place to get external dependencies then you need vcpkg. An answer would be a way to arrange that CMake itself installs vcpkg first (if it isn't present), and configures it, and then uses it to get the dependencies, I think. But how? – davidbak May 04 '21 at 14:22
  • I think it falls into opinion category because there is no one right way of doing it, and different people will have different way of solving this. You want to use `vcpkg` during the CMake configuration phase to download dependencies? – icebp May 04 '21 at 14:25
  • I'm just looking really for any way to do this - should I edit the question to make it clearer? Yes: I want to clone the repo, type `cmake`, and the build happens. – davidbak May 04 '21 at 14:37
  • One option when using CMake to download dependencies is to use [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html). It should work without much problems if you're dependencies also use CMake. This will avoid the need to invoke a third party tool, but, depending on your dependencies, it might be easier said than done. An edit with some more specific details would be helpful. – icebp May 04 '21 at 17:43

2 Answers2

1

There's nothing wrong in assuming that the user has certain tools installed.

Let's say you are developing libfoo which depends on libbar and you want to make it as easy as possible for your users to install libfoo.

With a package manager

If libfoo and libbar are available via the same package manager all your users have to do is:

vcpkg install libbar libfoo

You don't have to do anything special in libfoo for this, just instruct the user to install all dependencies in your readme.

It doesn't really matter what package manager is used.

Without a package manager

You will still want to make it easy for people to build and install your project directly. It may seem that invoking a package manager during the build or configuration phase of your project and solving all dependencies is user friendly because the user no longer has to deal with installing those, but it isn't for a number of reasons, including:

  • you or someone else may want to add your project to another package manager (like conan, spack, etc)
  • someone may want to consume libfoo with FetchContent, CPM, directly with add_subdirectory, etc
  • someone may not be a user of vcpkg - there's no need to force them to use it, if possible
  • you may want to add another dependency, libbaz, which is not available on vcpkg
  • a user may have the right version of libbar already installed (not necessarily through vcpkg)

This list is not exhaustive. If you're not writing a library some points don't really apply.

This means that someone who has all the dependencies already installed should be able to use libfoo like this:

git clone your-repo
cd your-repo
cmake -Bbuild
cmake --build build
cmake --install build

Resolving dependencies without a package manager

However, it may be desirable to solve dependencies automatically. If your dependencies are using CMake the easiest way of doing this is with FetchContent. For some of the reasons outlined above you should provide an escape hatch so people can still use the already installed dependencies. This can be done with an option. For example, something like FOO_USE_EXTERNAL_BAR. This can be set either to yes or no by default, there's no right answer. As long as the user can control this I don't think it matters that much. You should namespace your options to avoid possible conflicts with options used by other projects.

In this case your build script could do this:

if (FOO_USE_EXTERNAL_BAR)
    find_package(bar REQUIRED)
else ()
    FetchContent_Declare(
        bar
        GIT_REPOSITORY bar-repo
        GIT_TAG        release-tag
    )
    FetchContent_MakeAvailable(bar)
endif ()

target_link_libraries(foo PRIVATE bar::bar)

Depending on how libbar's CMakeLists.txt is written and organized the if and else branches may get more complicated. See Effective CMake for some details and tips.

Now I can either let libfoo resolve the libbar by setting FOO_USE_EXTERNAL_BAR to ON when I configure your project, or I can set it to OFF to have more control over how it is resolved. I may even use libfoo as a dependency for a project that already depends on libbar. If you always pull it in I can't avoid conflicts in this case.

Using CMake to update dependencies

You may still find it easy for you to be able to update all the project's dependencies using CMake without downloading them via FetchContent. While this will probably raise some eyebrows you could add a custom target for solving dependencies with a package manager. This should also be controllable by an option. Unlike in the above case I strongly believe that if you do this the option should be set to off by default:

if (FOO_AUTO_USE_VCPKG)
    add_custom_target(
        update_deps
        COMMAND vcpkg install libbar
    )
    
    add_dependencies(foo update_deps)
endif ()

This will invoke vcpkg every time you build foo so it will make your builds slower. If you remove the add_dependencies call you would have to manually run the update_deps target whenever you need to (which shouldn't be that often anyway).

Notes

Using options is a great way of providing options to your users. It should be noted that they increase the cognitive load, so picking strong defaults can help with that.

FetchContent is a nice way of taking the care away from the user, but at the same time multiple projects that use it will end up re-downloading the same libraries over and over again. It is still more user friendly than invoking a package manager at build time and as long as the users can disable this behavior there's nothing to worry about.

Some parts of this answer may be regarded more as opinion and less as facts. As I said, there is no one right way of doing this, different people will have different ways of solving this problem. Different projects and different environments will have different constraints.

I already recommended the Effective CMake talk above, other useful recourses are available here. If you're a library author you may also want to take a look at Deep CMake for Library Authors.

icebp
  • 1,608
  • 1
  • 14
  • 24
  • I am going to have to digest this! Back in a bit ... – davidbak May 05 '21 at 14:48
  • 1
    Thanks! Very nice and very helpful. To follow-up: Would you recommend CPM which seems to have the ability to defer to the user's existing package managers (via `CPM_USE_LOCAL_PACKAGES`) and where it can also [be arranged that cmake installs it if it isn't already there](https://github.com/cpm-cmake/CPM.cmake/wiki/Downloading-CPM.cmake-in-CMake)? Seems like between [ModernCppStarter](https://github.com/TheLartians/ModernCppStarter) and [CPM.cmake](https://github.com/cpm-cmake/CPM.cmake) there are active updates tracking cmake (with Kitware cooperation) and an active community. Do you agree? – davidbak May 07 '21 at 20:04
  • You're welcome. Don't take any of these as concrete rules, just as a starting point. I never used CPM so I can't comment on it. It certainly looks useful but I never tried using it. – icebp May 08 '21 at 08:54
1

I had this same question. For my part, I am not willing to assume that the user has either CMake or vcpkg preinstalled.

Here is my solution so far, as a Windows batch file:

@REM Bootstrap...
set VCKPG_PARENT_DIR=C:\Projects
set CMAKE_VERSION="3.20.2"

mkdir "%VCKPG_PARENT_DIR%"
pushd "%VCKPG_PARENT_DIR%"
git clone https://github.com/Microsoft/vcpkg.git
.\vcpkg\bootstrap-vcpkg.bat -disableMetrics
set PATH=%PATH%;%VCKPG_PARENT_DIR%\vcpkg\downloads\tools\cmake-%CMAKE_VERSION%-windows\cmake-%CMAKE_VERSION%-windows-i386\bin
set VCPKG_DEFAULT_TRIPLET=x64-windows
set PYTHONHOME=%VCKPG_PARENT_DIR%\vcpkg\packages\python3_x64-windows\tools\python3
popd

@REM Build the project...
cmake -B build -S .\engine\ -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DUSE_PYTHON_3=ON
cmake --build .\build\ --config Release
mkdir bin
xcopy .\build\Release\*.* .\bin\
xcopy .\build\objconv\Release\*.* .\bin\
xcopy .\build\setup\Release\*.* .\bin\

It could use some improvement, but hopefully this gives you an idea of one route you could take.

Stephen G Tuggy
  • 991
  • 10
  • 16
  • Oh, this assumes you're building on Windows. Is that not a fair assumption? – Stephen G Tuggy May 24 '21 at 21:22
  • 2
    no that's good - I am on Windows (ultimate Linux too but 1st things 1st) - I'm looking at this now ... – davidbak May 24 '21 at 23:15
  • Just updated my answer. Minor change -- `mkdir` should not have `/p` after it. – Stephen G Tuggy May 24 '21 at 23:42
  • I had some trouble with the batch file format. Couldn't get the environment variable settings to persist after the batch file(s) exited. I ended up converting to .ps1 PowerShell script format instead. Would you like to see that as well? – Stephen G Tuggy May 25 '21 at 23:09