CMake і vcpkg

Підключення зовнішніх бібліотек: find_package, add_subdirectory, FetchContent, pkg-config

П'ять способів підключити чужу бібліотеку до CMake-проєкту, режими Module і Config у find_package, автоматичне завантаження через FetchContent, ручне підключення і pkg-config, порівняльна таблиця.

Підключення зовнішніх бібліотек: find_package, add_subdirectory, FetchContent, pkg-config

🎯 Мета лекції

  • Назвати п'ять способів підключити зовнішню бібліотеку та обрати доречний для задачі.
  • Користуватися find_package з компонентами та розуміти, що відбувається, коли пакет не знайдено.
  • Завантажувати залежності під час конфігурації за допомогою FetchContent.
  • Підключати бібліотеки без CMake-підтримки вручну та через pkg-config.

🔑 Ключові терміни

  • Імпортована ціль (imported target): ціль на кшталт Boost::filesystem, яку створює find_package для вже встановленої бібліотеки.
  • Режим Module / Config: два способи, якими find_package шукає бібліотеку.
  • FetchContent: модуль для завантаження джерел залежності під час конфігурації.
  • pkg-config: утиліта, яка зберігає прапорці збірки бібліотек в окремих .pc файлах.

Власні бібліотеки вміємо збирати. Реальний проєкт використовує ще й чужі: JSON-парсер, фреймворк тестування, мережеву бібліотеку. Нижче розглянуто п'ять способів їх підключити. Вони розташовані від найпростішого до найбільш ручного.

Спосіб 1: find_package (бібліотека вже встановлена)

Команда find_package шукає на комп'ютері вже встановлену бібліотеку і створює для неї цілі, які можна передати в target_link_libraries.

cmake_minimum_required(VERSION 3.10)
project(MyProject)

set(CMAKE_CXX_STANDARD 17)

# Шукаємо бібліотеку
find_package(Threads REQUIRED)  # потоки POSIX

add_executable(MyApp main.cpp)

# Зв'язуємо
target_link_libraries(MyApp Threads::Threads)

Ключове слово REQUIRED означає: якщо пакет не знайдено, конфігурація зупиняється з помилкою. Без нього CMake піде далі, а наявність пакета треба перевіряти вручну змінною Threads_FOUND.

cmake -S . -B build
$ cmake -S . -B build
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE
-- Configuring done (0.7s)

Рядок Found Threads: TRUE підтверджує успіх. Тепер ціль Threads::Threads можна лінкувати.

Як find_package шукає пакет

Існує два режими пошуку:

РежимЩо шукає CMakeХто пише цей файл
ModuleФайл Find<Пакет>.cmakeРозробники CMake (для популярних бібліотек) або автор проєкту
ConfigФайл <Пакет>Config.cmake або <пакет>-config.cmakeАвтор самої бібліотеки, його кладе поруч з бібліотекою при інсталяції

За замовчуванням CMake спершу пробує Module, потім Config. Режим Config можна зафіксувати словом CONFIG: find_package(fmt CONFIG REQUIRED). Така форма використовується з менеджером пакетів vcpkg і надійніша, бо не залежить від застарілих модулів Find*.cmake.

Де саме CMake шукає Config-файли, визначають шляхи системи та змінна CMAKE_PREFIX_PATH. Якщо бібліотека лежить у нестандартній папці, передайте її при конфігурації:

cmake -S . -B build -DCMAKE_PREFIX_PATH=/opt/mylibs
# або точно вказати папку з Config-файлом конкретного пакета
cmake -S . -B build -Dfmt_DIR=/opt/mylibs/lib/cmake/fmt

Приклад: OpenCV

cmake_minimum_required(VERSION 3.10)
project(OpenCVExample)

set(CMAKE_CXX_STANDARD 17)

# Шукаємо OpenCV
find_package(OpenCV REQUIRED)

# Виводимо інформацію
message(STATUS "OpenCV version: ${OpenCV_VERSION}")
message(STATUS "OpenCV include dirs: ${OpenCV_INCLUDE_DIRS}")

add_executable(MyApp main.cpp)

# Підключаємо заголовки та бібліотеки
target_include_directories(MyApp PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(MyApp ${OpenCV_LIBS})

Цей приклад показує старий стиль: пакет повертає змінні (OpenCV_INCLUDE_DIRS, OpenCV_LIBS), які доводиться передавати в дві різні команди. Сучасні пакети створюють цілі, і достатньо одного target_link_libraries, як у наступному прикладі.

Приклад: Boost із компонентами

cmake_minimum_required(VERSION 3.10)
project(BoostExample)

set(CMAKE_CXX_STANDARD 17)

# Шукаємо конкретні компоненти Boost
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)

add_executable(MyApp main.cpp)

target_link_libraries(MyApp
    Boost::filesystem
    Boost::system
)

Число 1.70 — мінімальна версія. Слово COMPONENTS перелічує бібліотеки Boost, які реально потрібні (Boost складається з десятків окремих частин, і більшість із них header-only).

Що бачимо, коли пакета немає

Якщо на комп'ютері Boost не встановлено, конфігурація зупиняється з помилкою:

cmake -S . -B build
$ cmake -S . -B build
CMake Error at .../FindPackageHandleStandardArgs.cmake:290 (message):
Could NOT find Boost (missing: Boost_INCLUDE_DIR filesystem system)
(Required is at least version "1.70")
-- Configuring incomplete, errors occurred!

У повідомленні перелічено, чого саме не вистачає. Виправити можна трьома способами: встановити бібліотеку (системним менеджером або vcpkg), вказати CMAKE_PREFIX_PATH або перейти на FetchContent.

Починаючи з CMake 3.30 вбудований модуль FindBoost вважається застарілим (політика CMP0167): сучасний Boost постачає власні Config-файли. З політикою за замовчуванням і cmake_minimum_required(VERSION 3.10) CMake виводить попередження про це. У новому коді найкраще встановлювати Boost через vcpkg і викликати find_package(Boost CONFIG REQUIRED COMPONENTS ...).

Спосіб 2: бібліотека в підпапці (add_subdirectory)

Якщо вихідний код бібліотеки лежить прямо у вашому репозиторії (скопійований або підключений як git submodule), її збирають разом з проєктом:

Команда add_subdirectory(шлях) виконує CMakeLists.txt з цієї папки, а цілі, створені там, стають доступними в усьому проєкті. Вся бібліотека збирається вашим компілятором і з вашими прапорцями, тому проблем із сумісністю версій компілятора немає. Платите місцем у репозиторії і тим, що бібліотека збирається щоразу разом із проєктом.

Спосіб 3: автоматичне завантаження (FetchContent)

Модуль FetchContent завантажує джерела залежності під час конфігурації, а далі діє як add_subdirectory. Ручне копіювання коду в репозиторій не потрібне.

cmake_minimum_required(VERSION 3.14)
project(MyProject)

set(CMAKE_CXX_STANDARD 17)

include(FetchContent)

# Описуємо, звідки брати Google Test
FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG release-1.12.1
)

# Завантажуємо та підключаємо
FetchContent_MakeAvailable(googletest)

add_executable(MyApp main.cpp)

# Ціль gtest_main тепер доступна
target_link_libraries(MyApp PRIVATE GTest::gtest_main)

Дві команди мають різну роль:

  • FetchContent_Declare лише описує джерело (git-репозиторій і мітку або URL архіву). Нічого не завантажується.
  • FetchContent_MakeAvailable завантажує джерело в build/_deps/, викликає add_subdirectory для нього і робить його цілі доступними.
Завжди фіксуйте GIT_TAG на конкретній версії (тег або хеш коміту), а не на main. Інакше збірка у вас і у колеги через місяць дасть різні результати, бо автор бібліотеки міг змінити API.

Повний приклад із JSON-бібліотекою nlohmann/json

Завантажимо архів релізу бібліотеки. Для великих репозиторіїв архів швидший за клонування історії git:

Перший запуск конфігурації завантажує бібліотеку, наступні беруть її з build/_deps/:

збірка і запуск
$ cmake -S . -B build
-- Using the multi-header code from .../build/_deps/json-src/include/
-- Configuring done (1.8s)
$ cmake --build build && ./build/MyApp
{
"age": 25,
"city": "Київ",
"name": "Іван",
"skills": [
"C++",
"Python",
"CMake"
]
}
Ім'я: "Іван"
Вік: 25
Ключі у виводі розташовані за алфавітом (age, city, name, skills), а не в порядку створення. Тип nlohmann::json за замовчуванням зберігає об'єкти в упорядкованому словнику. Якщо потрібен порядок вставки, використовують nlohmann::ordered_json.

Обмеження FetchContent

  • Повільна перша конфігурація. Бібліотека завантажується і збирається разом з проєктом. Для великих бібліотек (Boost, Qt) це недоцільно.
  • Потрібен інтернет під час першої конфігурації. Рішенням для CI-систем є кеш build/_deps/ або менеджер пакетів із бінарним кешем.
  • Усі налаштування залежності змішуються з вашими. Змінні та опції бібліотеки з'являються у вашому кеші CMake, а помилка в чужому CMakeLists.txt стає вашою.

З цих причин FetchContent найкраще підходить для невеликих бібліотек (JSON, форматування, тестові фреймворки). Для великих залежностей кращий менеджер пакетів, про який — окрема тема.

Спосіб 4: бібліотека без підтримки CMake (ручне підключення)

Деякі старі або внутрішні бібліотеки не мають ні Config-файлу, ні Find*.cmake. Тоді шляхи прописують руками:

cmake_minimum_required(VERSION 3.10)
project(MyProject)

set(CMAKE_CXX_STANDARD 17)

# Вручну вказуємо шляхи
set(MYLIB_INCLUDE_DIR "C:/libs/mylib/include")
set(MYLIB_LIBRARY "C:/libs/mylib/lib/mylib.lib")

add_executable(MyApp main.cpp)

# Підключаємо
target_include_directories(MyApp PRIVATE ${MYLIB_INCLUDE_DIR})
target_link_libraries(MyApp ${MYLIB_LIBRARY})

Недолік очевидний: шлях C:/libs/... існує лише на вашому комп'ютері. Збірка на іншій машині або ОС зламається. Прийнятний варіант — зробити шляхи налаштовуваними:

# Значення за замовчуванням, яке користувач може перевизначити через -D
set(MYLIB_ROOT "" CACHE PATH "Папка, у якій встановлено mylib")

find_path(MYLIB_INCLUDE_DIR mylib.h HINTS "${MYLIB_ROOT}/include")
find_library(MYLIB_LIBRARY mylib HINTS "${MYLIB_ROOT}/lib")

if(NOT MYLIB_INCLUDE_DIR OR NOT MYLIB_LIBRARY)
    message(FATAL_ERROR "mylib не знайдено. Вкажіть -DMYLIB_ROOT=...")
endif()

add_library(mylib UNKNOWN IMPORTED)
set_target_properties(mylib PROPERTIES
    IMPORTED_LOCATION "${MYLIB_LIBRARY}"
    INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_INCLUDE_DIR}"
)

Команди find_path та find_library самі шукають файли в стандартних місцях і в підказках HINTS. Створена IMPORTED ціль поводиться як звичайна: після target_link_libraries(MyApp PRIVATE mylib) заголовки та бібліотека підключаються автоматично.

Спосіб 5: pkg-config (Linux і macOS)

pkg-config зберігає прапорці збірки бібліотек у .pc файлах. Багато бібліотек Linux (GTK, GLib, SDL2) мають такі файли.

cmake_minimum_required(VERSION 3.10)
project(MyProject)

set(CMAKE_CXX_STANDARD 17)

# Шукаємо pkg-config
find_package(PkgConfig REQUIRED)

# Просимо pkg-config знайти бібліотеку
pkg_check_modules(GTK REQUIRED gtk+-3.0)

add_executable(MyApp main.cpp)

# Підключаємо
target_include_directories(MyApp PRIVATE ${GTK_INCLUDE_DIRS})
target_link_libraries(MyApp ${GTK_LIBRARIES})
target_link_directories(MyApp PRIVATE ${GTK_LIBRARY_DIRS})

Сучасніша форма, яка створює імпортовану ціль і позбавляє від трьох окремих команд:

pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)

add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE PkgConfig::GTK)

Недолік способу: pkg-config поширений переважно на Linux, а на Windows його немає за замовчуванням. Для кросплатформного проєкту краще find_package або менеджер пакетів.

Порівняння способів

СпосібПеревагиНедолікиКоли використовувати
find_packageСтандартний підхід, швидкий, бібліотека збирається один раз для всіх проєктівБібліотека має бути встановлена заздалегідьПопулярні бібліотеки (OpenCV, Boost, Qt, Threads)
add_subdirectoryПовний контроль, версія зафіксована в репозиторіїЗаймає місце в репозиторії, збирається щоразуВласні модулі, git submodules
FetchContentАвтоматичне завантаження, версія зафіксована в CMakeLists.txtПовільна перша збірка, потрібен інтернетНевеликі бібліотеки, тестові фреймворки
Ручне підключенняПрацює з будь-чимШляхи не переносяться на інші машиниЛише для винятків, коли інші способи недоступні
pkg-configСтандарт для системних бібліотек LinuxНемає на WindowsСистемні бібліотеки Linux (GTK, SDL2)
У реальному проєкті найкраще поєднувати способи: системні бібліотеки через find_package, маленькі залежності через FetchContent, а все, що потребує довгої збірки, через менеджер пакетів. Наприкінці розділу показано, як менеджер пакетів vcpkg перекриває і find_package, і FetchContent.

Питання для самоперевірки

Резюме

🔍 find_package

Шукає вже встановлену бібліотеку і створює імпортовані цілі. Режим CONFIG надійніший за застарілі модулі Find*.cmake.

📂 add_subdirectory

Збирає бібліотеку з її джерел у вашому репозиторії. Повний контроль над версією.

⬇️ FetchContent

Завантажує залежність під час конфігурації. Версію фіксують тегом, найкраще підходить для невеликих бібліотек.

🛠️ Вручну і pkg-config

Для бібліотек без CMake-підтримки. Ручні шляхи нерозповсюджувані, pkg-config в основному Linux.
Далі розглянемо мову CMake глибше: умови if, опції option, цикли, функції та макроси, а також генерацію заголовка з версією програми через configure_file.
Copyright © 2026