Use external libraries using FetchContent.

Problem description

Hi everyone! I’m starting the new project and I would like to make it using CMake in a good way from the beginning.

I’ve found the proposition of project structure at the modern CMake book and It look’s that it has sense but unfortunately I can’t found a nice example of how to make it alive.

If there is a more common or modern structure that should be used please point it out.

Based on instructions from the mentioned book I’ve created a similar but simplified (for now) directories structure.

Proposed directories structure

I want to logically separate external libraries from the application, use separate CMakeLists.txt to build the external modules and I won’t use git submodules because as I’ve read somewhere the FetchContent_Declare should be used instead.

I want to create the following directories structure:

.
├── CMakeLists.txt
├── external
│   └── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    └── main.cpp
  • external directory will contain external libraries (like cli11, spdlog, etc.) which should be available in the src directory.
  • src directory will contain the main application.

And this is how the CMakeLists looks like:

  1. Root CMakeLists.txt:
    cmake_minimum_required(VERSION 3.16)

    project(
      KillingRobot
      VERSION 0.1
      DESCRIPTION "A self-aware robot who kills people who don't know CMake"
    )

    add_subdirectory(external)
    add_subdirectory(src)
  1. external CMakeLists.txt:
    (I’m not sure if the separate requirements should be in the one CMakeLists.txt file or rather split into com .in files included in CMakeLists.txt.)
    cmake_minimum_required(VERSION 3.16)

    # Download libraries used in the project.
    include(FetchContent)

    # ----------------------------------------------------------
    # A fast and simple to use logging library.
    FetchContent_Declare(
      spdlog
      GIT_REPOSITORY https://github.com/gabime/spdlog.git
      GIT_TAG        v1.8.2)

    FetchContent_GetProperties(spdlog)
    if(NOT spdlog_POPULATED)
      message("Cloning spdlod")
      FetchContent_Populate(spdlog)
      add_subdirectory(
        ${spdlog_SOURCE_DIR}
        ${spdlog_BINARY_DIR})
    endif()

    # ----------------------------------------------------------
    # A command line argument parser.
    FetchContent_Declare(
        cli11
        GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git
        GIT_TAG        v1.9.1)

    FetchContent_GetProperties(cli11)
    if(NOT cli11_POPULATED)
      message("Cloning CLI11")
      FetchContent_Populate(cli11)
      add_subdirectory(
        ${cli11_SOURCE_DIR}
        ${cli11_BINARY_DIR})
    endif()
  1. src CMakeLists.txt:
    cmake_minimum_required(VERSION 3.16)

    add_executable(${CMAKE_PROJECT_NAME} main.cpp)

And below how the main.cpp file looks like:

    #include "spdlog/spdlog.h"
    #include "CLI/App.hpp"
    #include <string>

    int main(int argc, char *argv[]) {
      CLI::App app{"App description"};
      std::string filename = "default";
      app.add_option("-f,--file", filename, "A help string");
      CLI11_PARSE(app, argc, argv);

      spdlog::info("Filename: {0}", filename);
    }

Choosen libraries

In the described example I’m using two libraries intentionally because the spdlog is a library where including a header file is enough for building but in the case of cli11, the built library should be linked by a linker.

Downloaded libraries

After cmake the repositories are downloaded and stored in build/_deps directory as:

.
├── build
│   ├── _deps
│   │   ├── cli11-build
│   │   ├── cli11-src
│   │   ├── cli11-subbuild
│   │   ├── spdlog-build
│   │   ├── spdlog-src
│   │   └── spdlog-subbuild
(...)

Of course, it doesn’t build because the compiler can’t find the headers and library - it isn’t defined anywhere.

What is needed?

I’m pretty sure that I should use:

  • target_link_libraries for linking libraries
  • target_include_directories for including the headers

Unfortunately, I can’t find a similar example (which may indicate that I’m doing something wrong). And I’m not sure if should I use them in: src/CMakeLists.txt or rather in the root CMakeLists.txt and the propagate it down to src/CMakeLists.txt.

Questions

At the end I have some questions:

  1. How to manage includes in this case?
  2. How to manage libraries in this case?
  3. Should I use FetchContent_Declare or maybe git submodules as many other projects on github?
  4. FetchContent_Declare is ok for downloading libraries used in includes or rather should be used to download standalone modules like googletest or other things independent from the application?

Best regards,
Maciej

Fist of all, with target_link_libraries() the compile definitions, the cpp defines, and the libraries are imported. You should not need to set target_include_directories()

And perhaps, this may also interesting for you: GitHub - TheLartians/ModernCppStarter: 🚀 Kick-start your C++! A template for modern C++ projects using CMake, CI, code coverage, clang-format, reproducible dependency management and much more.

And you may use CPM.cmake:

    # ---- Add dependencies via CPM ----
    # see https://github.com/cpm-cmake/CPM.cmake for more info

    include(cmake/CPM.cmake)

    # PackageProject.cmake will be used to make our target installable
    CPMAddPackage(
        NAME PackageProject.cmake
        GITHUB_REPOSITORY TheLartians/PackageProject.cmake
        VERSION 1.5.1
    )

    CPMAddPackage(
        NAME fmt
        GIT_TAG ${FMT_VERSION}
        GITHUB_REPOSITORY fmtlib/fmt # to get an installable target
        OPTIONS "FMT_INSTALL YES"
    )

    CPMAddPackage(
        NAME spdlog
        GIT_TAG v${SPDLOG_VERSION}
        GITHUB_REPOSITORY gabime/spdlog # to use our installed fmt lib
        OPTIONS "SPDLOG_FMT_EXTERNAL YES"
    )

CPM may faster because you can have a local cache, If you enable it, CPM use installed packages first. Default this feature is disable to have reproducible builds.

Thank you very much for the explanation and especially for the provided link! It’s super cool and thank you for your contribution! In this case - I’ll use it in my project.

1 Like