CMake і vcpkg

vcpkg для досвідчених: версії, features, реєстри, власні порти, кеш і діагностика

Фіксація версій через builtin-baseline, version>= та overrides, необов'язкові features, vcpkg-configuration.json із реєстрами та overlay-портами, створення власного порту, бінарний кеш, використання у CI і розв'язання типових проблем.

vcpkg для досвідчених: версії, features, реєстри, власні порти, кеш і діагностика

🎯 Мета лекції

  • Зафіксувати версії залежностей так, щоб збірка відтворювалась через роки.
  • Керувати необов'язковими можливостями пакетів (features) та умовними залежностями.
  • Підключати власні порти й реєстри через vcpkg-configuration.json.
  • Налаштувати бінарний кеш і використати vcpkg у CI.
  • Діагностувати збої встановлення за логами.

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

  • Baseline: коміт каталогу vcpkg, який визначає версії всіх портів за замовчуванням.
  • Override: примусова версія конкретного пакета, яка перекриває baseline.
  • Feature: необов'язкова можливість пакета, яка підключається окремо.
  • Реєстр (registry): джерело портів: git-репозиторій або папка.
  • Overlay-порт: порт, що підміняє або доповнює каталог, не змінюючи сам vcpkg.

Попередні лекції стосувались щоденної роботи з vcpkg. Цього вистачає, доки збірки робляться тут і зараз. Але програма живе роками, і через пів року vcpkg.json без додаткових налаштувань може встановити іншу версію бібліотеки. Нижче розглянуто, як це контролювати.

Версії залежностей

Проблема: каталог постійно змінюється

Каталог vcpkg — це git-репозиторій. Кожного тижня в ньому з'являються нові версії бібліотек. Якщо vcpkg.json перелічує лише назви ("fmt"), то яку версію отримає користувач, залежить від того, який коміт каталогу в нього зараз. У вас сьогодні fmt 12.1.0, а через пів року колега отримає іншу.

Baseline: знімок каталогу

Поле builtin-baseline у vcpkg.json містить хеш коміту каталогу. vcpkg бере версії портів саме з цього коміту, незалежно від поточного стану репозиторію:

{
  "name": "awesome-app",
  "version": "1.0.0",
  "dependencies": ["fmt", "spdlog"],
  "builtin-baseline": "1d611efb49276c65d69fb6d010c2aaaf039322b0"
}

Поточний коміт можна підставити автоматично:

vcpkg x-update-baseline --add-initial-baseline

Якщо builtin-baseline уже є, ця ж команда оновлює його до останнього коміту каталогу (аналог оновлення залежностей у npm). Тому правило таке: baseline змінюється лише свідомо, окремим комітом із перевіркою збірки.

Без builtin-baseline vcpkg не вміє працювати з обмеженнями версій: поля version>= та overrides потребують baseline. Формат значення: 40-символьний хеш коміту, а не дата й не назва гілки.

Мінімальна версія: version>=

Об'єкт залежності з полем version>= задає нижню межу:

{
  "name": "awesome-app",
  "version": "1.0.0",
  "dependencies": [
    { "name": "fmt", "version>=": "10.0.0" },
    "spdlog"
  ],
  "builtin-baseline": "1d611efb49276c65d69fb6d010c2aaaf039322b0"
}

Запис означає «не нижче 10.0.0». Конкретну версію вибирає baseline: якщо в ньому fmt 12.1.0, буде 12.1.0. Якщо baseline старіший за вимогу, vcpkg повідомить про помилку. Ця межа зручна для бібліотек, які вимагають мінімальної версії залежності, але не фіксують її жорстко.

Точна версія: overrides

Блок overrides примусово вибирає версію пакета, ігноруючи baseline:

{
  "name": "version-demo",
  "version": "1.0.0",
  "dependencies": [
    { "name": "fmt", "version>=": "10.0.0" }
  ],
  "overrides": [
    { "name": "fmt", "version": "10.2.1" }
  ],
  "builtin-baseline": "1d611efb49276c65d69fb6d010c2aaaf039322b0"
}

Каталог на цьому комміті містить fmt 12.1.0, але проєкт просить 10.2.1. Результат реального запуску:

збірка з overrides
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
-- Running vcpkg install
The following packages will be built and installed:
fmt:arm64-osx@10.2.1
* vcpkg-cmake:arm64-osx@2024-04-23
* vcpkg-cmake-config:arm64-osx@2024-05-23
$ cmake --build build && ./build/demo
fmt 10.2.1

Програма, яка друкує FMT_VERSION, підтвердила: зібрана саме 10.2.1. Звідки vcpkg взяв стару версію, якщо каталог її не містить? З бази версій (папка versions/ у репозиторії каталогу): вона зберігає записи про всі версії порту, які колись були опубліковані, разом із комітами, де лежав порт тієї версії.

МеханізмЩо робитьТипове застосування
builtin-baselineЗадає знімок каталогу для всіх портівЗастосунки: фіксація середовища
version>=Нижня межа версіїБібліотеки: вимога до залежності
overridesПримусова версія одного пакетаЗастосунки: відкат або заморожування конкретного пакета
Для застосунку достатньо baseline: версії зафіксовано, а оновлення відбувається свідомо через x-update-baseline. overrides потрібні, коли окрему бібліотеку слід тримати на старій версії (наприклад, через зламану сумісність в новій) на тлі свіжих інших пакетів.

Features: необов'язкові можливості пакетів

Багато бібліотек мають частини, які потрібні не всім: підтримка HTTPS, додаткові формати, бекенди. Для цього порт оголошує features. Їх було видно у виводі vcpkg search як рядки порт[feature]:

cpp-httplib              0.27.0    A single file C++11 header-only HTTP/HTTPS server and client library
cpp-httplib[brotli]                Enables brotli compression support using brotli
cpp-httplib[openssl]               Enables HTTPS support using OpenSSL
spdlog[wchar]                      Build with wchar_t (Windows only)

Features у manifest

{
  "name": "web-app",
  "version": "1.0.0",
  "dependencies": [
    "fmt",
    {
      "name": "cpp-httplib",
      "features": ["openssl"]
    },
    {
      "name": "spdlog",
      "default-features": false
    }
  ],
  "builtin-baseline": "1d611efb49276c65d69fb6d010c2aaaf039322b0"
}
  • "features": ["openssl"] додає до пакета cpp-httplib підтримку HTTPS: разом із ним буде встановлено й залежність OpenSSL.
  • "default-features": false вимикає ті можливості, які порт вмикає за замовчуванням. Так збирається мінімальна версія: менше залежностей і швидша збірка.

Features у класичному режимі

Через командний рядок features записуються в квадратних дужках. Оболонка (особливо zsh) намагається розкрити їх як шаблон, тому запис беруть у лапки:

vcpkg install "cpp-httplib[openssl]"
Не плутайте features з компонентами Boost. Boost у vcpkg розбитий на окремі портиboost-filesystem, boost-asio, boost-beast та інші, а не на features одного порту boost. Тому команда зі старих прикладів vcpkg install boost[filesystem,system,thread] не відповідає реальній структурі. Правильно: vcpkg install boost-filesystem boost-system boost-thread, а у manifest додати кожен boost-* пакет окремо.

Умовні залежності

Залежність можна обмежити платформою за допомогою поля platform:

{
  "dependencies": [
    "fmt",
    { "name": "pthreads", "platform": "windows" },
    { "name": "libuv", "platform": "!windows" }
  ]
}

Вираз у platform складається з імен платформ (windows, linux, osx, android, x64, arm64 та інших), логічних &, | і !. Пакет, для якого вираз хибний, пропускається. Поле "host": true позначає залежність, потрібну під час збірки на комп'ютері розробника (інструменти на кшталт protoc), а не в кінцевому застосунку.

Features самого проєкту

Власні групи залежностей проєкту (наприклад, для тестів) описуються в корені vcpkg.json:

{
  "name": "awesome-app",
  "version": "1.0.0",
  "dependencies": ["fmt"],
  "features": {
    "tests": {
      "description": "Залежності для тестів",
      "dependencies": ["gtest"]
    }
  },
  "builtin-baseline": "1d611efb49276c65d69fb6d010c2aaaf039322b0"
}

Увімкнути групу можна змінною CMake:

cmake -B build -DVCPKG_MANIFEST_FEATURES=tests \
      -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake

Тепер gtest потрібен лише тим, хто збирає з -DVCPKG_MANIFEST_FEATURES=tests, а звичайний користувач завантажувати тестовий фреймворк не буде.

vcpkg-configuration.json

Окрім vcpkg.json, у проєкті може бути другий файл, vcpkg-configuration.json. Він описує звідки vcpkg бере порти. Створити обидва файли можна командою vcpkg new:

vcpkg new --application
$ vcpkg new --application
$ cat vcpkg-configuration.json
{
"default-registry": {
"kind": "git",
"baseline": "1d611efb49276c65d69fb6d010c2aaaf039322b0",
"repository": "https://github.com/microsoft/vcpkg"
},
"registries": [ ... ]
}
$ cat vcpkg.json
{}

Файл vcpkg.json створюється порожнім: далі його заповнюють командою vcpkg add port <ім'я>. Поле default-registry вказує основний каталог і baseline: це альтернативний запис того самого builtin-baseline.

Реєстри

Реєстр — це джерело портів. Основний реєстр — публічний каталог Microsoft. Додатковими можуть бути ваші власні: наприклад, корпоративні бібліотеки, які не виставляються назовні.

{
  "default-registry": {
    "kind": "git",
    "repository": "https://github.com/microsoft/vcpkg",
    "baseline": "1d611efb49276c65d69fb6d010c2aaaf039322b0"
  },
  "registries": [
    {
      "kind": "git",
      "repository": "https://github.com/my-org/vcpkg-registry",
      "baseline": "7f4b0e2c5a1d6e9a8b3c4d5e6f708192a3b4c5d6",
      "packages": ["my-core", "my-network"]
    }
  ]
}

Поле packages перелічує, які порти беруться з цього реєстру. Усе інше надходить із основного. Реєстри бувають типів git (репозиторій) і filesystem (папка на диску або мережевий ресурс). Значення baseline у прикладі умовне: підставте хеш реального коміту вашого реєстру.

Overlay-порти та overlay-triplets

Простіший спосіб додати власну бібліотеку без реєстру — overlay: папка з портами, що підключається до каталогу:

{
  "overlay-ports": ["./overlay-ports"],
  "overlay-triplets": ["./triplets"]
}

Порти з overlay мають вищий пріоритет, ніж каталог. Тому overlay підходить і для нових бібліотек, і для підміни існуючого порту виправленою копією, поки зміни не прийняли в основний каталог. Шляхи рахуються відносно файлу vcpkg-configuration.json.

Власний порт

Порт складається з трьох основних файлів: vcpkg.json (метадані), portfile.cmake (інструкція збірки) та, за бажанням, usage (підказка користувачу). Нижче порт mylib створено повністю локально, як overlay: код бібліотеки лежить поруч із портом, тож завантажувати нічого не треба.

Розбір portfile.cmake

Усі команди vcpkg_* — функції, які vcpkg надає портам.

  1. vcpkg_cmake_configure(SOURCE_PATH ...) конфігурує CMake-проєкт бібліотеки для Debug і Release (vcpkg збирає обидва варіанти).
  2. vcpkg_cmake_install() збирає і встановлює бібліотеку у тимчасову папку пакета.
  3. vcpkg_cmake_config_fixup(CONFIG_PATH ...) виправляє шляхи у згенерованих Config-файлах, щоб вони були відносними й працювали з папкою installed.
  4. file(REMOVE_RECURSE ".../debug/include") — заголовки в debug/ є дублікатом, а vcpkg перевіряє пакет на такі зайві файли.
  5. vcpkg_install_copyright копіює ліцензію: пакет без файлу copyright vcpkg не прийме.

Запуск показує, що vcpkg знаходить overlay-порт і збирає його так само, як порти каталогу:

cmake -B build -DCMAKE_TOOLCHAIN_FILE=...
-- Running vcpkg install
The following packages will be built and installed:
mylib:arm64-osx@1.0.0 -- .../overlay-ports/mylib
* vcpkg-cmake:arm64-osx@2024-04-23
* vcpkg-cmake-config:arm64-osx@2024-05-23
Installing 3/3 mylib:arm64-osx@1.0.0...
Building mylib:arm64-osx@1.0.0...
.../overlay-ports/mylib: info: installing overlay port from here
-- Configuring arm64-osx
-- Building arm64-osx-dbg
-- Building arm64-osx-rel
mylib provides CMake targets:
find_package(mylib CONFIG REQUIRED)
target_link_libraries(main PRIVATE mylib::mylib)
$ cmake --build build && ./build/demo
Привіт, студенте, це mylib з власного порту!

Рядок info: installing overlay port from here підтверджує, що порт узято з overlay, а не з каталогу.

Під час першої спроби порту без файлу usage в portfile.cmake vcpkg виводить попередження: «this port contains a usage file but doesn't install it», і підказує готовий рядок file(INSTALL ...). Рядок, який використано в portfile.cmake вище, узято саме з цієї підказки. Читайте повідомлення vcpkg: вони точні.

Порт для зовнішньої бібліотеки

Коли код бібліотеки лежить у GitHub, порт завантажує його замість локальної папки:

vcpkg_from_github(
    OUT_SOURCE_PATH SOURCE_PATH
    REPO user/mylib
    REF v1.0.0
    SHA512 <хеш-архіву>
    HEAD_REF main
)

vcpkg_cmake_configure(SOURCE_PATH "${SOURCE_PATH}")
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(PACKAGE_NAME mylib)

file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

Поле SHA512 — контрольна сума архіву, яка гарантує, що завантажений код не змінився. Значення не вигадують: завантажте архів і обчисліть хеш командою vcpkg hash <файл>. Другий спосіб: тимчасово вказати SHA512 0, і vcpkg під час збірки виведе очікуване значення в повідомленні про помилку.

Скелет порту для архіву з інтернету створює команда vcpkg create mylib https://github.com/user/mylib/archive/v1.0.0.tar.gz mylib-1.0.0.tar.gz. Вона розміщує файли в папці ports/mylib самого каталогу vcpkg. Для проєктних портів зручніше скопіювати їх в overlay-ports/.

Бінарний кеш

Як він працює

Для кожного пакета vcpkg обчислює ключ ABI (хеш) з усіх вхідних даних: версії порту, його файлів, компілятора, triplet, версій залежностей. Якщо в кеші є архів із таким ключем, його розпаковують замість збірки. Хеш змінюється при будь-якій зміні вхідних даних, тому застарілий пакет використаний не буде.

Кеш за замовчуванням лежить у домашній папці користувача: ~/.cache/vcpkg/archives на Linux та macOS і %LOCALAPPDATA%\vcpkg\archives на Windows. Усередині архіви розкладені по підпапках за двосимвольним префіксом хешу: наприклад, пакет із ключем 2a9f... лежить у підпапці 2a. Тому в реальному кеші видно набір папок 2a, 7d, 9f і так далі, а в кожній по кілька файлів .zip.

У попередніх лекціях цей кеш уже працював: повторне встановлення fmt займало 12 мс, бо vcpkg писав Restored 3 package(s) from .../archives.

Налаштування

# Інша папка для кешу
export VCPKG_DEFAULT_BINARY_CACHE=$HOME/.vcpkg-cache
mkdir -p $VCPKG_DEFAULT_BINARY_CACHE

# Повний контроль через VCPKG_BINARY_SOURCES (джерела розділені крапкою з комою)
export VCPKG_BINARY_SOURCES="clear;files,$HOME/.vcpkg-cache,readwrite"

Значення VCPKG_BINARY_SOURCES складається зі списку джерел, розділених ;. Слово clear скидає джерела за замовчуванням, а files,<папка>,readwrite додає папку для читання й запису. Інші джерела: nuget (канал NuGet, поширений для CI), http (власний сервер), x-azblob (Azure Blob Storage), x-aws (Amazon S3).

У прикладах зустрічається запис vcpkg install fmt --binarysource=clear;files,C:\vcpkg-cache,readwriteбез лапок. В оболонці bash символ ; розділяє команди, тож усе після першої крапки з комою виконується як окрема команда. Користуйтеся змінною середовища або беріть значення параметра в лапки: --binarysource="clear;files,/шлях,readwrite".

vcpkg у CI

Автоматичні збірки на чистій машині за замовчуванням починають з порожнього кешу, тому кожен запуск збирає все з нуля. Рішення: зберігати папку кешу між запусками. Приклад для GitHub Actions:

name: build
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache
    steps:
      - uses: actions/checkout@v4

      - name: Кеш бінарних пакетів vcpkg
        uses: actions/cache@v4
        with:
          path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
          key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }}

      - name: Підготовка папки кешу
        run: mkdir -p "$VCPKG_DEFAULT_BINARY_CACHE"

      - name: Конфігурація
        run: >
          cmake -B build -S .
          -DCMAKE_BUILD_TYPE=Release
          -DCMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake

      - name: Збірка
        run: cmake --build build --parallel

      - name: Тести
        run: ctest --test-dir build --output-on-failure

Що тут важливо:

  • VCPKG_INSTALLATION_ROOT — змінна, яку GitHub встановлює на своїх образах із vcpkg. Встановлювати vcpkg вручну не потрібно.
  • Ключ кешу залежить від хешу vcpkg.json. Поки залежності не змінились, кеш використовується. Додали бібліотеку: хеш інший, ключ інший, і кеш збудується заново.
  • mkdir -p потрібен, бо vcpkg очікує, що папка існує.

Діагностика проблем

Де шукати причину

Якщо порт не зібрався, vcpkg виводить шлях до логів. Вони лежать у buildtrees/<порт>/:

ФайлЗміст
config-<triplet>-out.logЗвичайний вивід конфігурації CMake
config-<triplet>-err.logПовідомлення про помилки конфігурації
config-<triplet>-dbg-CMakeCache.txt.logКеш CMake для Debug-збірки
<triplet>.vcpkg_abi_info.txtДані, з яких обчислено ключ ABI
install-<triplet>-*.log, build-*.logВивід компіляції та установки

Першим дивитесь файл *-err.log: найчастіше причина записана там у кількох останніх рядках.

Типові проблеми

Практичне завдання

Резюме

📌 Версії

builtin-baseline фіксує знімок каталогу (40-символьний хеш), version>= задає нижню межу, overrides примусово вибирає версію.

🎛️ Features

Необов'язкові можливості пакетів (features, default-features), умовні залежності (platform, host) та власні групи проєкту через VCPKG_MANIFEST_FEATURES.

🗃️ Реєстри і overlay

vcpkg-configuration.json підключає власні реєстри (git, filesystem), overlay-ports та overlay-triplets.

🏗️ Власний порт

vcpkg.json плюс portfile.cmake з функціями vcpkg_cmake_*. Хеш завантажуваного архіву дає vcpkg hash.

⚡ Кеш і CI

Ключ ABI гарантує коректність кешу. У CI зберігайте папку архівів між запусками з ключем за хешем vcpkg.json.
Інструмент готовий. У завершальних лекціях розділу розглянемо самі бібліотеки: що вони роблять, як підключаються й у яких задачах застосовуються. Почнемо з форматування, логування та JSON.
Copyright © 2026