Cmake

Подключение сторонних библиотек в Qt через FetchContent в CMake

Введение

Раньше подключение сторонней библиотеки в C++-проект означало либо ручную установку через системный пакетный менеджер, либо копирование исходников в репозиторий (vendoring), либо submodule Git с дополнительной настройкой сборки. Модуль CMake FetchContent решает эту задачу декларативно: указываете источник (Git-репозиторий, архив, локальный путь), и CMake сам скачивает, настраивает и подключает библиотеку как часть сборки текущего проекта. В статье разберём, как подключить стороннюю библиотеку (на примере nlohmann/json) в Qt-проект через FetchContent.

Концепция

FetchContent_Declare() описывает, откуда брать зависимость (обычно через GIT_REPOSITORY и GIT_TAG для фиксации конкретной версии), а FetchContent_MakeAvailable() фактически скачивает её (если ещё не скачана) и добавляет в сборку через add_subdirectory() — после этого таргеты библиотеки доступны для target_link_libraries() так же, как если бы они были найдены через find_package().

Преимущество FetchContent перед git submodule — версия зависимости фиксируется прямо в CMakeLists.txt, а не требует отдельной команды git submodule update, и весь процесс воспроизводим «из коробки» при первой настройке проекта. Минус — повторная загрузка при каждой чистой сборке (если не настроено кеширование), что замедляет CI без дополнительных мер.

Пример кода: Подключение сторонних библиотек

cmake_minimum_required(VERSION 3.21)
project(MyApp LANGUAGES CXX)

include(FetchContent)

FetchContent_Declare(
    nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG v3.11.3
)

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

FetchContent_MakeAvailable(nlohmann_json fmt)

find_package(Qt6 REQUIRED COMPONENTS Core)

add_executable(MyApp main.cpp)

target_link_libraries(MyApp PRIVATE
    Qt6::Core
    nlohmann_json::nlohmann_json
    fmt::fmt
)
// main.cpp
#include <QCoreApplication>
#include <nlohmann/json.hpp>
#include <fmt/core.h>

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    nlohmann::json config;
    config["name"] = "MyApp";
    config["version"] = 1;

    fmt::print("Конфигурация: {}\n", config.dump());

    return 0;
}

Пояснения к коду

Каждая зависимость объявляется отдельным вызовом FetchContent_Declare() с фиксированной версией через GIT_TAG — это важно для воспроизводимости сборки на разных машинах и в CI. Один вызов FetchContent_MakeAvailable() с несколькими именами одновременно скачивает и подключает обе библиотеки. После этого их таргеты (nlohmann_json::nlohmann_json, fmt::fmt) используются в target_link_libraries точно так же, как таргеты, найденные через find_package.

В коде main.cpp обе библиотеки используются совместно с Qt — FetchContent не накладывает ограничений на совместное использование сторонних библиотек с Qt-классами в одном проекте.

Подводные камни

  • Повторная загрузка зависимостей при каждой чистой сборке заметно увеличивает время CI — для ускорения стоит настроить кеширование папки _deps между запусками CI или использовать FETCHCONTENT_FULLY_DISCONNECTED/локальное зеркало для офлайн-сборок.
  • Отсутствие фиксации тега (GIT_TAG указывает на ветку, например main) делает сборку невоспроизводимой — содержимое зависимости в будущем может измениться без какого-либо изменения в самом проекте, что приводит к трудноуловимым регрессиям. Всегда фиксируйте конкретный тег или коммит.
  • Конфликт версий одной и той же зависимости, если несколько подключаемых библиотек сами объявляют FetchContent_Declare для общей сторонней зависимости с разными версиями — CMake использует первую объявленную версию, что может привести к несовместимости с ожиданиями второй библиотеки.
  • Долгая первичная настройка проекта из-за необходимости клонировать репозитории зависимостей по сети — для крупных зависимостей (например, с большой историей коммитов) стоит использовать GIT_SHALLOW TRUE для поверхностного клонирования без полной истории.