CPM.cmake to make CMake's FetchContent easier
Tags: cpp, programming, todayilearned, cmake
Published: 2022-12-28 15:07:55

Scrolling at my list of starred projets of GitHub, I saw a tool I had forgotten, [CPM.cmake](https://github.com/cpm-cmake/CPM.cmake), and decided that it was time to try it!

`CPM.cmake` (or just `CPM`) describes itself as "a cross-platform CMake script that adds dependency management capabilities to CMake". It's in fact a wrapper around [CMake's FetchContent module](https://cmake.org/cmake/help/latest/module/FetchContent.html) that makes must easier to download packages.

Do demonstrate its usage, I will build a project that depends on 3 libraries from GitHub: the famous [Catch2](https://github.com/catchorg/Catch2) and [fmt](https://github.com/fmtlib/fmt), and the probably not-as-famous-as-it-deserves [scnlib](https://github.com/eliaskosunen/scnlib).

If you want to try the code below, here is a `main.cpp` that uses these libraries:

```cpp
#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>
#include <scn/scn.h>
#include <string>

TEST_CASE("My test case") {

    // Scan with scn
    std::string firstName;
    std::string lastName;
    unsigned int number;
    const auto result = scn::scan("Bill Clinton was the 42nd president of the United States",
                            "{} {} was the {}",
                            firstName, lastName, number);

    // Format with fmt
    const auto output = fmt::format("{} {} {}", firstName, lastName, number);

    // Test with Catch2
    CHECK(result);
    CHECK(output == "Bill Clinton 42");
}
```

## Fetching the libraries from GitHub wihtout CPM

Here is how you declare and download the libraries from GitHub without `CPM`, directly with `FetchContent`:

```cmake
include(FetchContent)

FetchContent_Declare(
        Catch2
        GIT_REPOSITORY https://github.com/catchorg/Catch2.git
        GIT_TAG v3.0.0-preview4
)

FetchContent_Declare(
        fmt
        GIT_REPOSITORY https://github.com/fmtlib/fmt.git
        GIT_TAG 9.1.0
)

FetchContent_Declare(
        scnlib
        GIT_REPOSITORY https://github.com/eliaskosunen/scnlib.git
        GIT_TAG v1.1.2
)

FetchContent_MakeAvailable(Catch2 fmt scnlib)
```

If you have never used `FetchContent` before:

1. You include the script, which is provided natively by CMake.
2. You declare your dependencies.
3. You download them.

The targets from these libraries are then available and can be used with our own targets:

```cmake
add_executable(${PROJECT_NAME}
        sources/main.cpp)

target_link_libraries(${PROJECT_NAME}
        PRIVATE
            Catch2::Catch2WithMain
            fmt
            scn)
```

## With CPM instead

CPM is just a single `.cmake` file but it is not provided out-of-the-box by CMake. You have to download it from GitHub.

The simplest solution is to browse the [release page](https://github.com/cpm-cmake/CPM.cmake/releases/), download `CPM.cmake` from the desired release, and copy it to your project's directory.

Another nice solution is to [ask CMake to download the file](https://github.com/cpm-cmake/CPM.cmake/wiki/Downloading-CPM.cmake-in-CMake) for you. To do so, you can simply add the following lines at the beginning of your `CMakeLists.txt` :

```cmake
set(CPM_DOWNLOAD_LOCATION ${CMAKE_BINARY_DIR}/CPM.cmake)

file(DOWNLOAD
        https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/cpm.cmake
        ${CPM_DOWNLOAD_LOCATION})

include(${CPM_DOWNLOAD_LOCATION})
```

And then, it's so really simple to get our libraries from GitHub! We just have to replace all calls to `FetchContent_xxx()` functions with:

```cmake
CPMAddPackage("gh:catchorg/Catch2#v3.0.0-preview4")
CPMAddPackage("gh:eliaskosunen/scnlib#v1.1.2")
CPMAddPackage("gh:fmtlib/fmt#9.1.0")
```

There is no equivalent call to `FetchContent_MakeAvailable()`.

The call to `target_link_libraries()` is unchanged.

## The SYSTEM property

In my previous article ["The SYSTEM property from CMake 3.25"](https://dev.to/pgradot/please-meet-the-system-property-from-cmake-325-4007), I talked about the SYSTEM option for the function `FetchContent_Declare()`. As of Decembre 28th 2022, there is unfortunately no matching option in CPM... [An issue has been created](https://github.com/cpm-cmake/CPM.cmake/issues/377) in July but it's still open.

## Conclusion

`CPM` can [do more](https://github.com/cpm-cmake/CPM.cmake#comparison-to-pure-fetchcontent--externalproject) than just simplifying calls to `FetchContent_Declare()`. For instance, it has [a cache](https://github.com/cpm-cmake/CPM.cmake#cpm_source_cache) so that dependencies used by several projects can be downloaded only once.

But honnestly, the simplicity of `CPMAddPackage()` compared to `FetchContent_Declare()` seems enough to use CPM 🥳