Pull to refresh

Управление зависимостями на C++ с помощью vcpkg registry и сервера кеширования

Level of difficultyMedium
Reading time9 min
Views1.1K

Здравствуйте, дорогие читатели Хабра! Я давно хотел поделиться своими знаниями о работе с реестрами под ключ, так как нигде нет четкой и последовательной информации по этой теме. Сегодня мы разберем, как управлять зависимостями через реестры vcpkg и как кэшировать их на сервере. Статья рассчитана на пользователя, который имел опыт работы с vcpkg или conan

Ссылки на документацию

Содержание

  • Создание реестра

  • Создание своих пакетов Qt5/Boost

  • Сборка standalone окружения

  • Организация кэширования бинарей на сервере nuget

  • Сборка тестового проекта, с использованием vcpkg + nuget (кеширование)

Создание реестра

Создаем новый удаленный Git-репозиторий (подойдет любой, к которому у вас есть доступ). В статье я буду использовать свой репозиторий (ссылка)

Для первоначальной настройки потребуется файл baseline.json в папке versions.

versions/baseline.json
{
  "default": {
  }
}

Коммитим. Пушим. Пустой реестр создан. Для удобства работы можно взять скрипты для автоматизации из папки scripts.

Создание своих пакетов

Рекомендации по созданию port-файлов можно найти в официальной документации. В данной статье не предполагается подробное освещение этого вопроса.

В процессе работы мне потребуется использовать библиотеку Boost версии 1.87. В настоящее время в реестре доступна версия 1.88, однако её переопределение в vcpkg является для меня неудобным. Кроме того, я намерен использовать сборку qt5-base из исходников на зеркале Яндекса. Для достижения этой цели необходимо будет выполнить следующие действия:

  • Произвести форк файла port qt5-base.

  • Создать собственный файл port для библиотеки Boost.

Port qt5-base

За основе я взял актуальный port qt5-base. Копируем из vcpkg/ports в наш репозитори port/qt5-base

Структура репозитория

├───ports
│ ├───boost
│ └───qt5-base
│ ├───cmake
│ └───patches
├───scripts
├───share
└───versions

Редактируем источник скачивания (файл registry/ports/qt5-base/cmake/qt_download_submodule.cmake

    set(FULL_VERSION "${QT_MAJOR_MINOR_VER}.${QT_PATCH_VER}")
    set(ARCHIVE_NAME "${NAME}-everywhere-opensource-src-${FULL_VERSION}.tar.xz")
    set(URLS
        "https://mirror.yandex.ru/mirrors/qt.io/${QT_MAJOR_MINOR_VER}/${FULL_VERSION}/submodules/${ARCHIVE_NAME}"
    )

Порядок действий для обновления информации о пакете:

  • Коммитим файлы в папке ports

  • Форматируем порт-фалы (scripts/format-ports.py)

cd scripts
py format-ports.py
  • Коммитим изменения, если есть.

  • Обновляем актуальную информацию о версиях и снова коммитим изменения

cd scripts
py update-versions.py
пример вывода
Пример обновления версий
Пример обновления версий

После выполнения данных шагов у вас появятся автоматически сгенерированые папки и файлы в versions (не нужно их редактировать руками!)

Пример файла versions/q-/qt5-base.json
{
  "versions": [
    {
      "git-tree": "e04eef6f4c169b57fd43a68a1a3d2bd9ef6d6ec2",
      "version": "5.15.16",
      "port-version": 4
    }
  ]
}

Этот файл содержит все версии и привязки к конкретным коммитам вашего port-файла.

Port boost

Все основные шаги по добавлению и обновлению описаны при добавлении qt5-base и заострять большое внимание на них не будем.

Расскажу кратко. Мой portfile будет

  • Собирать все библиотеки в рамках 1 порт-файла

  • Нужной мне версии

  • Представлены 2 фичи - принудительно static, принудительно shared

мой portfile.cmake

string(REPLACE "." "-" ARCHIVE_VERSION ${VERSION})

set(BOOST_ARVHICE_FILENAME boost-${ARCHIVE_VERSION}.zip)

vcpkg_download_distfile(
    ARCHIVE_ZIP
    URLS https://github.com/boostorg/boost/releases/download/boost-${VERSION}/boost-${VERSION}-cmake.7z
    FILENAME ${BOOST_ARVHICE_FILENAME}
    SHA512 994356c84f4b96e263087eff40e53298791e7d72c6d24dd2cc3988c32edaa880180d39401504befe5c1b197ebead77c855452c608c6560a42f50f5a3016e0add
)

vcpkg_extract_source_archive(
    SOURCE_PATH
    ARCHIVE "${ARCHIVE_ZIP}"
)

string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" BUILD_SHARED)
string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" BUILD_STATIC_CRT)

if(BUILD_SHARED)
    set(OPTION_BUILD_SHARED_LIBS ON)
else()
    set(OPTION_BUILD_SHARED_LIBS OFF)
endif()

if(BUILD_STATIC_CRT)
    set(BOOST_RUNTIME_LINK static)
else()
    set(BOOST_RUNTIME_LINK shared)
endif()


if("force-static" IN_LIST FEATURES)
    set(OPTION_BUILD_SHARED_LIBS OFF) #rewrite options
    vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
endif()

if("force-shared" IN_LIST FEATURES)
    set(OPTION_BUILD_SHARED_LIBS ON) #rewrite options
    vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY)
endif()


set(BOOST_EXCLUDE_COMPONENTS test log wave stacktrace python)
list(JOIN BOOST_EXCLUDE_COMPONENTS "\\;" BOOST_EXCLUDE_LIBRARIES)

message(STATUS "Replace BoostConfig with fixed search lib_DIR")
configure_file(${CMAKE_CURRENT_LIST_DIR}/BoostConfig.cmake.in ${SOURCE_PATH}/tools/cmake/config/BoostConfig.cmake USE_SOURCE_PERMISSIONS @ONLY)

# disable check LINUX_VERSION_CODE in ASTRA
if(UNIX)    
    file(COPY_FILE ${CMAKE_CURRENT_LIST_DIR}/config.hpp ${SOURCE_PATH}/libs/asio/include/boost/asio/detail/config.hpp)
endif()

message(STATUS "Building boost with BUILD_SHARED_LIBS=${OPTION_BUILD_SHARED_LIBS}, CRT=${BOOST_RUNTIME_LINK} EXCLUDE:${BOOST_EXCLUDE_COMPONENTS}")

vcpkg_cmake_configure(
    SOURCE_PATH ${SOURCE_PATH}
    WINDOWS_USE_MSBUILD
    OPTIONS
        -DBUILD_SHARED_LIBS=${OPTION_BUILD_SHARED_LIBS}
        -DBOOST_RUNTIME_LINK=${BOOST_RUNTIME_LINK}
        -DBOOST_EXCLUDE_LIBRARIES=${BOOST_EXCLUDE_LIBRARIES}
        -DBOOST_ENABLE_PYTHON=OFF
        -DBUILD_TESTING=OFF
)

vcpkg_cmake_install()

vcpkg_cmake_config_fixup(PACKAGE_NAME boost CONFIG_PATH lib/cmake/Boost-${VERSION} DO_NOT_DELETE_PARENT_CONFIG_PATH) #main config

vcpkg_copy_pdbs()

# Fixup configs for debug and release
function(_fixup_boost_config_for_dir_ target_dir)

    message(STATUS "Fixup boost configs for dir: ${target_dir}")
    file(GLOB CONFIG_LIST LIST_DIRECTORIES true ${target_dir}/boost_*)

    foreach(dir ${CONFIG_LIST})
        IF(IS_DIRECTORY ${dir})

            string(REPLACE "${target_dir}/" "" dir_name "${dir}")
            #string(REPLACE "-1.83.0" "" PKG_CONFIG_NAME ${dir_name})
            string(REPLACE "-${VERSION}" "" PKG_CONFIG_NAME ${dir_name})
            message(STATUS "try fixup: ${dir_name} - ${PKG_CONFIG_NAME}")

            vcpkg_cmake_config_fixup(PACKAGE_NAME ${PKG_CONFIG_NAME} CONFIG_PATH lib/cmake/${dir_name} DO_NOT_DELETE_PARENT_CONFIG_PATH)
        ELSE()
            CONTINUE()
        ENDIF()
    endforeach()

    unset(CONFIG_LIST)
endfunction()

_fixup_boost_config_for_dir_(${CURRENT_PACKAGES_DIR}/debug/lib/cmake)
_fixup_boost_config_for_dir_(${CURRENT_PACKAGES_DIR}/lib/cmake)

file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") 
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake") 

file(INSTALL "${SOURCE_PATH}/LICENSE_1_0.txt" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)

Все изменения можно посмотреть в репозитории

Повторяем все действия по обновлению версий / коммитов, как в qt5-base

Сборка standalone окружения

С 2024 года vcpkg научился делать standalone окружения. Окружение может быть экспортировано полностью самостоятельным, отвязанным от основного репозитория vcpkg.

Для создания окружения нам будет необходимо подготовить два файла:

  • vcpkg.json - файл манифеста зависимостей

  • vcpkg-configuration.json - файл описания реестров, откуда что брать.

Для упрощения работы был написан вспомогательный скрипт make-vcpkg-configuration.py. Данный скрипт проходит по текущему реестру и собирает все версии, формируя эталонный конфигурации с привязкой к комиту и репозиторию.

пример подготовки файлов
cd scripts
py make-vcpkg-configuration.py

Будут сформированы два файла

Пример выполнения команды
Пример выполнения команды
vcpkg.json
{
    "dependencies": [],
    "overrides": [
        {
            "name": "boost",
            "version": "1.87.0#2"
        },
        {
            "name": "qt5-base",
            "version": "5.15.16#4"
        }
    ]
}
vcpkg-configuration.json
{
    "default-registry": {
        "kind": "git",
        "repository": "https://github.com/microsoft/vcpkg.git",
        "baseline": "3425f537d2f01c761d2d2cff59a7577eb4f568c0"
    },
    "registries": [
        {
            "kind": "git",
            "repository": "https://github.com/SaulBerrenson/registry.git",
            "reference": "main",
            "packages": [
                "boost",
                "qt5-base"
            ],
            "baseline": "f3b8c64ae49a1dfd4cd845767e7d14f6f03bd09d"
        }
    ]
}

Эти файлы можно заполнить руками, но скриптом проще и это универсальное решение.

Заполняем реестр теми пакетами, которые хотим видеть в будущем окружении

пример заполнения (vcpkg.json)
{
    "dependencies": [
		{
            "name": "boost",
            "features": ["force-static"]
        }
	],
    "overrides": [
        {
            "name": "boost",
            "version": "1.87.0#2"
        },
        {
            "name": "qt5-base",
            "version": "5.15.16#4"
        }
    ]
}

Выбираем удобую папку для сборки окружения и переносим туда:

  • vcpkg.json

  • vcpkg-configuration.json

  • vcpkg_manager.py (вспомогательный скрипт для удобства сборки окружений)

py vcpkg_manager.py --cache local --triplet x64-windows --steps install export

PS C:\projects\article\build\env\v142_x64> Get-ChildItem -Recurse -Depth 1 | Select-Object @{Name="Level";Expression={($_.FullName.Split('\').Count - (Get-Location).Path.Split('\').Count)}}, Name, FullName | Format-Table

Level Name         FullName
----- ----         --------
    1 installed    C:\projects\article\build\env\v142_x64\installed
    1 scripts      C:\projects\article\build\env\v142_x64\scripts
    1 .vcpkg-root  C:\projects\article\build\env\v142_x64\.vcpkg-root
    1 vcpkg.exe    C:\projects\article\build\env\v142_x64\vcpkg.exe
    2 vcpkg        C:\projects\article\build\env\v142_x64\installed\vcpkg
    2 x64-windows  C:\projects\article\build\env\v142_x64\installed\x64-windows
    2 buildsystems C:\projects\article\build\env\v142_x64\scripts\buildsystems
    2 cmake        C:\projects\article\build\env\v142_x64\scripts\cmake

После выполнения мы получаем полностью отвязанное окружение от vcpkg. Для его использования необходимо передавать toolchainFile не из репозитория vcpkg, а из сформированной папки - тогда он не будет учитывать только это окружение.

Организация кэширования бинарей на сервере nuget

Сборка изолированных окружение заманчива, но иногда, особенно, в целях автоматизации CI\CD и т.д. - есть желание прокешировать пакеты аналогично conan.

Подготовка

Выбрать можно любой nuget-сервер, я выбираю бесплатный и простой open-source проект Baget. Разворачивать все будем в докере. Я заранее подготовил уже готовую конфигурацию на базе docker (ссылка). Авторизации никакой не будет - организация доступа не цель статьи.

docker-compose.yml

version: '3.8'

services:
baget:
image: loicsharma/baget
container_name: baget
ports:
- "5555:80"
volumes:
- ./baget-data:/var/baget
environment:
- ApiKey__IsRequired=false
- Storage__Type=FileSystem
- Storage__Path=/var/baget/packages
- Database__Type=PostgreSql
- Database__ConnectionString=Host=postgres;Port=5432;Database=baget;Username=baget;Password=baget_password
- Search__Type=Database
networks:
- baget-network
depends_on:
- postgres

postgres:
image: postgres:15-alpine
container_name: postgres
environment:
- POSTGRES_USER=baget
- POSTGRES_PASSWORD=baget_password
- POSTGRES_DB=baget
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- baget-network

volumes:
postgres-data:

networks:
baget-network:
driver: bridge

docker-compose up -d

Важно: baget в стоке не умеет работать в postgresql с длинными наименованиями, который генерирует vcpkg. Фиксим это просто (сменой типов в ряде колонок)

docker exec -it postgres psql -U baget -d baget -c "ALTER TABLE \"Packages\" ALTER COLUMN \"Version\" TYPE TEXT; ALTER TABLE \"Packages\" ALTER COLUMN \"Id\" TYPE TEXT; ALTER TABLE \"Packages\" ALTER COLUMN \"OriginalVersion\" TYPE TEXT; ALTER TABLE \"PackageTypes\" ALTER COLUMN \"Version\" TYPE TEXT; ALTER TABLE \"Packages\" ALTER COLUMN \"Description\" TYPE TEXT;"

Сервер поднят - осталось наполнить пакетами.

Пустой сервер
Пустой сервер

Для организации кэширования можно воспользоваться vcpkg_manager.py

py vcpkg_manager.py --cache remote --triplet x64-windows --steps install export --nuget-url http://home-pc:5555/v3/index.json

# Указываем сервер куда заливать --nuget-url http://home-pc:5555/v3/index.json
Пример сборки окружения с учетом сервера кэширования
Пример сборки окружения с учетом сервера кэширования
Вид пакетов в nuge
Вид пакетов в nuge

Т.к. я уже выполнял, у меня они прокэшировались на сервере и были восстановлены от туда. Кэширование завершено.

Сборка тестового проекта, с использованием vcpkg + nuget (кеширование)

Итак, на сервере у нас уже имеются нужные нам пакеты. Для конфигурирования проекта cmake на понадобится использовать режим манифеста и наши файлы vcpkg.json и vcpkg-configuration.json, которые мы использовали для сборки зависимостей.

Примерная структура проекта

Примерная структура проекта cmake
Level Name
----- ----
    1 CMakeLists.txt
    1 CMakePresets.json
    1 main.cpp
    1 vcpkg-configuration.json
    1 vcpkg.json

Для успешного конфигурирования нам понадобится:

  • toolchainFile - путь до vcpkg.cmake в основном репозитории (где у вас установлен vcpkg)

  • vcpkg.json

  • vcpkg-configuration.json

  • Переменная окружения VCPKG_BINARY_SOURCES с описанием источника пакетов:

"VCPKG_BINARY_SOURCES": "clear;nuget,http://home-pc:5555/v3/index.json,readwrite;nugettimeout,600"

Для удобства я использовал cmake presets

   "configurePresets": [
     {
       "name": "windows-base",
       "hidden": true,
       "generator": "Ninja",
       "binaryDir": "${sourceDir}/out/build/${presetName}",
       "installDir": "${sourceDir}/out/install/${presetName}",
       "toolchainFile": "c:\\buildtools\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake",
       "environment": {
         "VCPKG_BINARY_SOURCES": "clear;nuget,http://home-pc:5555/v3/index.json,readwrite;nugettimeout,600"
       },
       "cacheVariables": {
         "CMAKE_C_COMPILER": "cl.exe",
         "CMAKE_CXX_COMPILER": "cl.exe",
         "VCPKG_TARGET_TRIPLET": "x64-windows"
       },
       "condition": {
         "type": "equals",
         "lhs": "${hostSystemName}",
         "rhs": "Windows"
       }
     }
Пример вывода конфигурирования проекта
Вывод конфигурирование проекта
Вывод конфигурирование проекта

Итак, мы сегодня научились:

  • делать простейший реестр пакетов

  • собирать окружение standalone

  • собирать и кэшировать окружение на удаленном сервере, подобно conan

Спасибо за внимание!

Tags:
Hubs:
-1
Comments3

Articles