image С момента публикации предыдущей части прошло больше полутора лет, была реализована большая куча фичей, сделано несколько релизов, но не об этом пойдёт речь. Пару дней назад в жизни библиотеки произошло важное событие: она была добавлена в основной репозиторий conan'а (conan-center-index). Об том, как это случилось, что для этого пришлось сделать и что вообще нужно делать, чтобы добавить туда свою библиотеку, и пойдёт речь под катом.


JFrog do the best they can


image Для начала — пара слов о том, что есть такое conan (он же конан, он же conan.io) и conan-center-index. Конан — это (ещё один, но стремительно набирающий популярность) менеджер пакетов и зависимостей, ориентированный на C++. Он довольно прост в инсталляции, настройке и имеет несколько несомненных плюсов:


  1. Он не привязан к какой-то конкретной сборочной системе, компилятору или операционной системе;
  2. Может использовать распределённую систему репозиториев;
  3. С помощью artifactory позволяет разворачивать локальные(приватные) проектные/командные репозитории;
  4. Поддерживает поставку зависимостей как в бинарном (уже собранном) виде, так и сборку их локально из исходников под конкретный профиль;
  5. Относительно прост в настройке и использовании.

Собственно, с конаном вы просто перечисляете список зависимостей, которые требуются вашему проекту, дальше он всё делает сам. И, при условии, что все настройки (conan profile) для проекта указаны правильно, дальше вы просто собираетесь с выгруженными, собранными и настроенными библиотеками. Магия! Но для этого сборки библиотек и рецепты для их on-site-сборки должны где-то лежать. Где-то, где их можно найти. Лежат они, очевидно, в репозиториях. Одном официальном и бесконечном множестве персональных (или командных).


Долгое время официальный репозиторий конана поддерживался основной командой, поэтому пакеты с библиотеками там появлялись очень медленно и неохотно. Гораздо больше можно было найти в репозитории bincrafters — его мейнтейнеры бодро затягивали туда библиотеки и утилиты, так необходимые в повседневной жизни. Но у репозитория bincrafters (как и у любого другого персонального) был один неприятный недостаток: после установки конана его надо было отдельным образом регистрировать.


Прошлым летом было принято решение кардинальным образом изменить сложившуюся ситуацию, и команда conan.io создала проект conan-center-index. Это — гитхаб-репозиторий, совмещённый с мощной сборочной фермой. Любой желающий (ну, почти — об этом ниже) может сделать в этот репозиторий pull request с рецептом библиотеки или утилиты. После прохождения ревью и успешной сборки во всех базовых конфигурациях — библиотека (рецепт сборки, бинарные предсобранные пакеты и всё остальное) попадает в основной репозиторий конана и становится доступной просто по своему имени и версии. Например: jinja2cpp/1.1.0


Nothing to do all day but stay in bed


image Кто хотя бы раз слушал мастер-класс Павла Филонова о том, как использовать conan.io в повседневной работе, как добавлять туда пакеты и всё такое, мог получить представление о том, насколько это не сложно. Это действительно так. Освоение этой премудрости занимает ну, может, один рабочий день — как написать простенький рецепт, как создать собственный conan-репозиторий, как опубликовать в нём свой пакет и т. п. В сети можно нарыть немало готовых рецептов для примера, а в репозитории bincrafters есть готовые шаблоны: https://github.com/bincrafters/templates.


Но всё это хорошо и легко ровно до тех пор, пока этот пакет будет использоваться для сборки конкретным тулчейном или довольно ограниченным набором тулчейнов. Если же хочется сделать пакет публичным, разместить его на bintray (хотя бы даже в своём репозитории) — всё становится значительно интереснее и увлекательнее. Я назову одну лишь цифру: 130. При добавлении библиотеки в conan-index (текущий центральный репозиторий конана) делается минимум 130 вариантов сборок этой библиотеки в бинарные пакеты. По крайней мере, именно столько было сделано для Jinja2C++. Сборочная матрица включает в себя:


  • Операционную систему (Windows/Linux)
  • Архитектуру (x86/x64)
  • Компилятор и его версию
  • Версию стандарта
  • Тип рантайма (для MSVC — это статический или динамический, для gcc — это версия ABI для стандартной библиотеки)
  • Конфигурацию сборки (debug/release)
  • Тип библиотеки (shared/static)

Относительно хорошая новость здесь в том, что, по идее, такая матрица должна быть уже настроена для CI вашей библиотеки. На appveyor, travis, Github Actions должны крутиться виртуалки, а в виртуалках — докер-контейнеры, которые каждый коммит в репозиторий библиотеки прогоняют через ну, похожу матрицу. Нет? Ещё не крутятся? Не настроены? Ну… Тогда у меня плохие новости.


Errors flying over your head


Аксиома №1: то, что ваша библиотека собирается в вашем локальном сборочном окружении, под конкретный компилятор (или пару его версий) и в паре конфигураций — ещё не значит, что она будет собираться в окружении вашего соседа.
Аксиома №2: если вы не позаботились о достаточном тестовом покрытии вашей библиотеки, то её собираемость ещё не означает, что она будет правильно работать при определённых комбинациях сборочных параметров.
Аксиома №3: вы даже предположить не можете, с каким флагами библиотеку будет собирать кто-то, кто вдруг захотел ею воспользоваться.



Решить основные проблемы со сборкой под кучу конфигураций желательно до того, как принимается решение о публикации библиотеки. Это долго, это нудно, отладка CI может занимать длительное время, требовать кучи коммитов с единственным комментарием: "Debug CI build", но будучи настроенным один раз CI может сэкономить кучу времени и сил.


Приключение это становятся ещё увлекательнее, когда у вашей библиотеки появляются внешние зависимости. Например, от буста или ещё чего-нибудь такого же, не менее популярного. Рассказ об этих приключениях — это тема отдельной статьи. Здесь достаточно лишь сказать, что конан позволяет облегчить жизнь. Если, конечно, речь идёт о пакетах из основных репозиториев (типа conan-index или bincrafters). В этом случае есть определённая гарантия, что если зависимости подходят друг другу по версиям, то, скорее всего, на сборке всё будет хорошо, а лог ошибок сборки будет девственно чистым. Разумеется, в экзотических случаях что-то наверняка пойдёт не так (ведь опция сборки библиотеки из исходников всегда остаётся), но на то они и экзотические случаи. И на этот случай всегда есть баг-трекер проекта, в который можно написать гневный issue.


Для статистики: сборочная матрица библиотеки Jinja2C++ на GitHub Actions состоит из 30-и вариантов сборки под Linux, 48-и сборок под Windows, плюс к этому 16 сборок на appveyor и 5 на трависе. Спойлер: на сборочном конвейере conan-center-index всё равно были проблемы.


If you want to survive, get out of bed!


Итак, решение идти в основной репозиторий конана принято. Что дальше? Тут надо соблюсти несколько предварительных условий:


  1. Вы совершенно уверены, что библиотека собирается в популярном наборе конфигураций. Под windows, под linux, всем популярным зоопарком компиляторов и прочее. Уверенность будут вселять настроенный и запущенный CI с соответствующей матрицей. Использование сборочной фермы конана для простого тестирования сборки может быть воспринято не очень позитивно.
  2. У вас на гитхабе (битбакете, гитлабе и т. п.) есть хотя бы один релиз. Из апстрима в conan index ничего не идёт. Только ссылки на архивы исходников с явно прописанными хешами.
  3. Вы подали заявку на участие в Early Access Programm проекта conan index, и эту заявку одобрили. Этот шаг необходимый, но формальный. Я в соответствующем issue не видел ни одной неодобренной заявки. Заявку подавать здесь: https://github.com/conan-io/conan-center-index/issues/4
  4. Вы установили себе последнюю версию конана и зарегистрировали хуки к ней. Это необходимое требование. Хуки проверяют ваш рецепт и бьют по рукам, если вы хотите сделать что-то не так. Например, прописали не тот набор настроек, метаинформации, или, скажем, инсталлятор библиотеки копирует лишние файлы (или наоборот — не копирует). Проверок там много.
  5. Вы форкнули себе репозиторий conan-center-index. Публикация собственной библиотеки делается через PR в этот репозиторий с необходимым и дотошным ревью со стороны мейнтейнеров из conan.io team.

После выполнения этих несложных требований лучше пойти в вики проекта conan-center-index и почитать, как сейчас принято оформлять рецепты: https://github.com/conan-io/conan-center-index/wiki И убедиться, что приключение предстоит несколько сложнее, чем набросать небольшой питон-скрипт и зааплоадить его в персональную репу.


You've got your orders better shoot on sight


Рецепты для конана имеют строго определённый формат, ровно как и расположение файлов рецепта. Рецепт каждой библиотеки лежит в своей подпаке github-репозитория, структура которой зависит от того, что за библиотека и как её можно собирать. Я не буду здесь дублировать содержимое официальной вики, покажу на примере Jinja2C++. Это — собираемая библиотека с пока что одной выпущенной версией. Структура папки с её рецептом такова:


.
+-- recipes
|   +-- jinja2cpp
|       +-- config.yml
|       +-- 1.1.0
|           +-- CMakeLists.txt
|           +-- conanfile.py
|           +-- conandata.yml
|           +-- test_package
|               +-- CMakeLists.txt
|               +-- conanfile.py

В файлах лежит следующее:


config.yml


В этом файле — общая информация о версиях библиотеки, которые лежат в папке проекта и из каких подпапок их собирать:


---
versions:
  "1.1.0":
    folder: 1.1.0 

Дело в том, что есть варианты, когда рецепт пишется один на все версии библиотеки, в этом случае вместо номерных подпапок там будет all, а в config.yml — перечислены все поддерживаемые версии (подробнее об этом — в вики проекта).


1.1.0


Папка, в которой лежит рецепт для сборки этой конкретной версии библиотеки. Или группы версий. Или всех версий. Зависит от. В обязательном порядке содержит файл рецепта (conanfile.py), информацию об источниках исходников (conandata.yml) и подпапку test_package, в которой — рецепт сборки простого тестового проекта. Этот рецепт необходим для того, чтобы убедиться, что пакет с библиотекой в заданной конфигурации собирается корректно.


1.1.0/conanfile.py


Основной файл с рецептом сборки. Подробнее о нём — ниже, в отдельном разделе.


1.1.0/conandata.yml


Файл с информацией об источниках исходных файлов библиотек. Как я писал выше, конкретный рецепт собирается из конкретного набора исходников. Сделано это для воспроизводимости сборки. Это свой собственный персональный рецепт вы можете написать так, чтобы исходники всегда брались из актуального master'а вашего репозитория. Для библиотек в conan-center-index так нельзя. Поэтому пишется такой вот файл:


sources:
  "1.1.0":
    url: https://github.com/jinja2cpp/Jinja2Cpp/archive/1.1.0.tar.gz
    sha256: 3d321a144f3774702d3a6252e3a6370cdaff9c96d8761d850bb79cdb45b372c5

Для каждой версии указывается путь к архиву с исходниками и хеш-сумма этого архива.


1.1.0/CMakeFile.txt


Необязательный, но иногда крайне необходимый файл. В моём случае используется для адаптации некоторых опций сборки, устанавливаемых conan'ом, к CMake-файлу моей библиотеки:


cmake_minimum_required(VERSION 3.4.3)
project(cmake_wrapper)

include(conanbuildinfo.cmake)
conan_basic_setup()

unset (BUILD_SHARED_LIBS CACHE)
if (NOT CMAKE_BUILD_TYPE)
    set (CMAKE_BUILD_TYPE ${JINJA2CPP_CONAN_BUILD_TYPE})
endif ()

add_subdirectory(source_subfolder)

Зачем это нужно и что здесь происходит? cmake-генератор конана генерирует специальный файл (conanbuildinfo.cmake), в котором выставляет кучу флагов и настроек сборки, соответствующих текущему сборочному профилю. Ну, там, компилятор, версию стандарта, PIC и прочие подобные вещи. Если у вашего проекта (вашей библиотеки) CMake-файл универсальный, и на conan не заточен, то эти флаги могут легко потеряться. Вот чтобы этого не произошло — и делается скрипт-прослойка. Кроме того, он может делать что-то ещё. В моём случае — прокидывать тип сборки для случая, если сборка толкается на msbuild.exe.


1.1.0/test_package


Подпапка с тестовым проектом. Здесь так же лежит рецепт (conanfile.py) и сборочный скрипт (CMakeLists.txt) и исходник (main.cpp), которые позволяют собраться тестовому проекту вместе с библиотекой и вывести что-нибудь простенькое на консоль. Например, "Hello World!".


1.1.0/test_package/conanfile.py


Рецепт сборки. Так сказать, файл-пускач. Выглядит довольно просто:


from conans import ConanFile, CMake, tools
import os

class Jinja2CppTestPackage(ConanFile):
    settings = "os", "arch", "compiler", "build_type"
    generators = "cmake", "cmake_find_package"

    def build(self):
        cmake = CMake(self)
        compiler = self.settings.get_safe("compiler")
        if compiler == 'Visual Studio':
            runtime = self.settings.get_safe("compiler.runtime")
            cmake.definitions["MSVC_RUNTIME_TYPE"] = '/' + runtime

        cmake.configure()
        cmake.build()

    def test(self):
        if not tools.cross_building(self.settings):
            ext = ".exe" if self.settings.os == "Windows" else ""
            bin_path = os.path.join("bin", "jinja2cpp-test-package" + ext)
            self.run(bin_path, run_environment=True)

В отличие от рецепта сборки библиотеки, этот файл довольно прост. Его задача — толкнуть сборку (метод build) и толкнуть тест (метод test). При необходимости — прокинуть дополнительные флаги. Например, тип рантайма для компилятора MSVC. Зависимость от основной библиотеки указывать не нужно — конан это и так знает. Типы генераторов зависят от системы сборки, используемой для сборки тестового проекта. В случае CMake — это cmake и cmake_find_package. Что это и зачем — в следующем разделе.


1.1.0/test_package/CMakeLists.txt


Тут всё довольно просто:


cmake_minimum_required(VERSION 2.8.0)
project(Jinja2CppTestPackage)

set(CMAKE_CXX_STANDARD 14)

include (${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

find_package(jinja2cpp REQUIRED)

if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
    if (CMAKE_BUILD_TYPE MATCHES "Debug" AND MSVC_RUNTIME_TYPE)
        set (MSVC_RUNTIME_TYPE "${MSVC_RUNTIME_TYPE}d")
    endif ()

    set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MSVC_RUNTIME_TYPE}")
    set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MSVC_RUNTIME_TYPE}")
    set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${MSVC_RUNTIME_TYPE}")
    set (CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/PROFILE")

    message (STATUS "Build type: ${CMAKE_BUILD_TYPE}")
    message (STATUS "USE MSVC RUNTIME: ${MSVC_RUNTIME_TYPE}")

endif()

add_executable(jinja2cpp-test-package main.cpp)
target_link_libraries(jinja2cpp-test-package PRIVATE jinja2cpp::jinja2cpp)

Генератор cmake, про который я говорил выше, создаёт файл conanbuildinfo.cmake, а cmake_find_package — создаёт find'еры, позволяющие искать conan-библиотеки привычным образом. Важный нюанс: имена пакетов — case-sensitive, и иногда (нечасто, правда) в пакетах могут поменять регистр. В этом случае ваш проект внезапно перестанет собираться.


Кроме того, в файле обрабатываются параметры, переданные из рецепта ну и собирается исполняемый файл, который можно запустить.


1.1.0/test_package/main.cpp


Простой исходник, успешная сборка которого говорит о том, что с conan-пакетом всё в порядке:


#include <jinja2cpp/template.h>
#include <iostream>

int main()
{
    std::string source = R"(
{{ ("Hello", 'world') | join }}!!!
{{ ("Hello", 'world') | join(', ') }}!!!
{{ ("Hello", 'world') | join(d = '; ') }}!!!
{{ ("Hello", 'world') | join(d = '; ') | lower }}!!!)";

    jinja2::Template tpl;
    tpl.Load(source);

    std::string result = tpl.RenderAsString(jinja2::ValuesMap()).value();
    std::cout << result << "\n";
} 

Это не полновесный пакет тестов! Это примитивный smoke-тест, не более того. Его задача — собраться и сказать миру "привет!", и всё!


Your finger's on the keyboard


Собственно, рецепт. То, ради чего всё затевается, ради чего всё делается. Это — питон-скрипт, который должен соответствовать стандарту кодирования PEP8. Задача этого скрипта — описать библиотеку с помощью набора метоинформации, а так же дать исчерпывающие инструкции по тому, как, с какими вариантами опций и прочее библиотеку можно собирать. Общая его структура:


import os
from conans import ConanFile, CMake, tools
from conans.errors import ConanInvalidConfiguration

class Jinja2cppConan(ConanFile):
# global settings and metainfo

    def config_options(self):
#...

    def configure(self):
#...

    def source(self):
#...

    def build(self):
#...

    def package(self):
#...

    def package_info(self):
#...

Более подробно о каждой функции можно почитать в документации по conan. Здесь лишь краткая информация:


  • config_options — вызывается для корректировки набора опций профиля при сборке под конкретную платформу
  • configure — метод для конфигурирования сборки
  • source — метод для получения исходников
  • build — метод для собственно сборки
  • package — метод для формирования пакета из результатов сборки (аналог install, но не совсем)
  • package_info — метод, предоставляющий информацию о собранных артефактах для использования в других пакетах и рецептах

Реализация всех этих методов сильно зависит от типа библиотеки, её скриптов сборки и прочих нюансов. В случае с Jinja2C++, которая собирается с помощью CMake-скрипта, реализовано всё было так.


Блок глобальной конфигурации пакета


    name = "jinja2cpp"
    license = "MIT"
    homepage = "https://jinja2cpp.dev/"
    url = "https://github.com/conan-io/conan-center-index"
    description = "Jinja2 C++ (and for C++) almost full-conformance template engine implementation"
    topics = ("conan", "cpp14", "cpp17", "jinja2", "string templates", "templates engine")
    exports_sources = ["CMakeLists.txt"]
    generators = "cmake", "cmake_find_package"
    settings = "os", "compiler", "build_type", "arch"
    options = {
        "shared": [True, False], "fPIC": [True, False]
    }
    default_options = {'shared': False, "fPIC": True}
    requires = (
        "variant-lite/1.2.2",
        "expected-lite/0.3.0",
        "optional-lite/3.2.0",
        "string-view-lite/1.3.0",
        "boost/1.72.0",
        "fmt/6.1.2",
        "rapidjson/1.1.0"
    )

    _source_subfolder = "source_subfolder"
    _build_subfolder = "build_subfolder"
    _cpp_std = 14

Он общий для всех пакетов и включает себя собственно метаинформацию (name, license, homepage, url, description, topics), параметры настройки пакета (generators, settings, options, default_options), список зависимостей (requires) и глобальные настройки, используемые в методах (_source_subfloder, _build_subfolder и _cpp_std).


Какие тут особенности, связанные именно с пакетами в conan-center-index:


  • Считается, что пакет принадлежит всему community, поэтому авторство не указывается (нет параметра author);
  • url всегда указывает на conan-center-index;
  • Путь к оригинальному репозиторию — в homepage;
  • Библиотека должна поддерживать сборку как static, так и shared-пакетов. Аналогично с -fPIC. Это контролируется hook-скриптами;
  • Внешние зависимости — всегда на конкретную версию (диапазоны не допускаются), и только на то, что уже есть в conan-center-index.

config_options


Метод, который позволяет отключить те или иные параметры для определённых конфигураций:


    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC

В моём случае при сборке под Windows флаг fPIC — не нужен. Поэтому его убираем.


config


Метод конфигурирования сборки пакета. Здесь проверяются параметры профиля сборки, устанавливаются дополнительные флаги и т. п.


    def configure(self):
        if self.options.shared:
            del self.options.fPIC

        cppstd = self.settings.get_safe("compiler.cppstd")
        if cppstd:
            cppstd_pattern = re.compile(r'^(gnu)?(?P<cppstd>\d+)$')
            m = cppstd_pattern.match(cppstd)
            cppstd_profile = int(m.group("cppstd"))
            if cppstd_profile < 14:
                raise ConanInvalidConfiguration("Minimum C++ Standard required is 14 (provided '{}')".format(cppstd))
            else:
                self._cpp_std = cppstd_profile

В моём случае — при сборке shared-версии библиотеки удаляется флаг fPIC и проверяется, что выполняется требование минимального стандарта (14). Если в профиле указано что-то меньшее, то конфигурация считается невалидной. Важно: генерация исключения ConanInvalidConfiguration — единственный способ корректно инвалидировать конфигурацию с точки зрения сборочной фермы. Все варианты профилей, для которых бросается это исключение, не будут приводить к сбою сборки.


source


Стандартный для всех рецептов метод:


    def source(self):
        tools.get(**self.conan_data["sources"][self.version])
        extracted_dir = "Jinja2Cpp-" + self.version
        os.rename(extracted_dir, self._source_subfolder)

Согласно файлу конфигурации здесь выкачивается архив и переименовывается в некое "стандартное" имя.


build


В этом методе выполняется собственно сборка. Используемые средства зависят от того, чем собирается библиотека. Конан поддерживает большой набор сборочных скриптов. Для CMake этот метод выглядит так:


    def build(self):
        cmake = CMake(self)
        cmake.definitions["JINJA2CPP_BUILD_TESTS"] = False
        cmake.definitions["JINJA2CPP_STRICT_WARNINGS"] = False
        cmake.definitions["JINJA2CPP_BUILD_SHARED"] = self.options.shared
        cmake.definitions["JINJA2CPP_DEPS_MODE"] = "conan-build"
        cmake.definitions["JINJA2CPP_CXX_STANDARD"] = self._cpp_std
        # Conan cmake generator omits the build_type flag for MSVC multiconfiguration CMake,
        # but provide build-type-specific runtime type flag. For now, Jinja2C++ build scripts
        # need to know the build type is being built in order to setup internal flags correctly
        cmake.definitions["JINJA2CPP_CONAN_BUILD_TYPE"] = self.settings.build_type
        compiler = self.settings.get_safe("compiler")
        if compiler == 'Visual Studio':
            # Runtime type configuration for Jinja2C++ should be strictly '/MT' or '/MD'
            runtime = self.settings.get_safe("compiler.runtime")            
            if runtime == 'MTd':
                runtime = 'MT'
            if runtime == 'MDd':
                runtime = 'MD'
            cmake.definitions["JINJA2CPP_MSVC_RUNTIME_TYPE"] = '/' + runtime

        cmake.configure(build_folder=self._build_subfolder)
        cmake.build()

Создаётся нужный объект, через него добавляются параметры сборки, после чего запускается генерация cmake'а и собственно сборка. В моём случае требуется прокинуть CMake-скрипту ряд настроек, специфичных именно для библиотеки Jinja2C++, тип рантайма и тип сборки.


package


Метод, который вызывается для формирования пакета с артефактами сборки. Выглядеть может так, но это совсем не обязательно:


    def package(self):
        self.copy("LICENSE", dst="licenses", src=self._source_subfolder)
        self.copy("*.h", dst="include", src=os.path.join(self._source_subfolder, "include"))
        self.copy("*.hpp", dst="include", src=os.path.join(self._source_subfolder, "include"))
        self.copy("*.lib", dst="lib", keep_path=False)
        self.copy("*.dll", dst="bin", keep_path=False)
        self.copy("*.so", dst="lib", keep_path=False)
        self.copy("*.so.*", dst="lib", keep_path=False)
        self.copy("*.dylib", dst="lib", keep_path=False)
        self.copy("*.a", dst="lib", keep_path=False)

По сути — выполняет функцию install-скрипта. В принципе, если ваша библиотека предоставляет собственный сценарий инсталляции — можно использовать и его. Но есть, опять же, нюансы:


  • Обязательно требуется файл с лицензией. Это проверяют хуки.
  • Все .cmake-артефакты (если ваш инсталлятор их создаёт) должны быть вычищены. Это тоже проверяют хуки.

Поскольку в моём случае инсталляция довольно примитивна — я решил, что проще будет описать её в рецепте, чем обрабатывать напильником результат стандартного install'а.


package_info


Метод, предоставляющий информацию о пакете: его библиотеках, дефайнах, с которыми его надо использовать, и т. п. Чтобы те проекты, к которому этот пакет цепляется, могли корректно собираться. В моём случае реализация примитивна:


    def package_info(self):
        self.cpp_info.libs = ["jinja2cpp"]

Только библиотека. Раньше ещё define'ы прокидывались, но я смог от этого избавиться.


The sergeant calls (stand up and fight!)


Написать рецепт для пакета с библиотекой — это четверть дела. Надо протащить его через ревью и сборку на ферме. И для этого требуется масса терпения и нервов. Первое, и важное (это даже требуют при заведении PR): вы должны хотя бы один раз запустить сборку пакета локально. То есть хотя бы один раз, на одной из своих сборочных конфигураций выполнить скрипт:


> conan create . jinja2cpp/1.1.0@

(вместо jinja2cpp/1.1.0 — имя вашей библиотеки)


Выполнять эту команду надо в папке, где расположен файл с рецептом. Этот скрипт создаст в локальном conan-репозитории пакет с вашей библиотекой. Всегда с нуля. Если сможет. С первого раза, правда, вряд ли получится. Но… Всякие чудеса бывают.


На самом деле, для серьёзной отладки одного запуска (в одной конфигурации) будет мало. Потребуется тестирование рецепта как на Windows-, так и на Linux-сборках. На Windows — в разных комбинациях флагов shared и типов рантайма. На Linux… На Linux — проще. В докер-хабе есть докер-образы, которые команда conan использует на своей сборочной ферме. Найти их можно здесь: https://hub.docker.com/u/conanio Лично я использую эти образы на своей сборочной матрице в GitHub Actions.


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


[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=Visual Studio
compiler.runtime=MD
compiler.version=15
os=Windows
os_build=Windows
[options]
jinja2cpp:shared=False
[env]

[build_requires]

Чтобы не прописывать всякий раз параметры сборки ручками. Файл профиля конану скормить просто:


> conan create . jinja2cpp/1.1.0@ -p profile_md.txt

profile_md.txt — это файл с нужным профилем.


И тут может быть ещё одна засада: в процессе отладки сборки под конкретные конфигурации (под какие — вам скажет сборочная ферма, предоставив лог сборки и профиль, который завалился) может потребоваться повторный выпуск релиза. Потому что ошибка может оказаться не в рецепте, а в сборочных скриптах библиотеки, или в исходниках библиотеки. В общем, с момента создания pull request'а в conan-center-index начнётся основная жара и веселье. Которое, так или иначе, закончится, ибо проблемы в конце концов решатся. Иногда — не без помощи команды поддержки conan'а. Например, мне приходилось дожидаться, пока они проапдейтят версии Visual Studio на своих сборочных образах. Ну, всякое бывает, да.


You'll be the hero of the neighbourhood


Да, процесс публикации в conan-center-index непростой, сильно отличается от того, как это рассказывается на презентациях, но, на мой взгляд, оно того стоит. Пакетных менеджеров, достойных внимания, для C++ не так много. conan — один из них, и здесь только от сообщества зависит, насколько он будет в итоге полезным. Например, вот здесь есть длинный список того, что достойно размещения в конане, но ещё не размещено. Сейчас процесс добавления уже неплохо обкатан, можно брать за основу готовые рецепты — и создавать новые.


Ссылки вместо заключения:


Проект conan-center-index
Issue для подачи заявки на участие в EAP
Библиотека Jinja2C++
Профиль библиотеки в conan-center


В тексте статьи в качестве подзаголовков использовались строчки из песни "You in the army now" группы Status Quo