Акт 1. Экспозиция

Исторически сложилось, что одна из главных проблем C++ - это тулчейны, системы сборки, управление зависимостями и всё вокруг. Ничего из этого не является частью стандарта, поэтому кто во что горазд. Любой бигтех просто обязан написать свой инструмент, который наконец-то станет лучшим.
Другой же лагерь, наоборот стоит за использование максимально распространенных, общеизвестных и поддерживаемых решений, и готов мириться с их недостатками. В системах сборки явным лидером и де-факто стандартом стал CMake. В пакетных менеджерах нет такого единства, здесь у нас можно обозначить двух лидеров в лице vcpkg и Conan. Дабы разводить холивар в комментариях, а не в этом небольшом историческом очерке, мы будем говорить только про последнего из двух)
Статья посвящена больше тем, кто не любит писать велосипеды, а использует готовые инструменты. Да, есть некоторый каламбур, учитывая что речь вообще-то про userver.
Нужен ли вам пакетный менеджер - каждый решает самостоятельно. Приведу лишь несколько аргументов за
Отслеживание уязвимостей в сторонних пакетах;
Прозрачный процесс обновления зависимостей;
Упрощение различных процессов сертификации итоговых продуктов. Речь не только про ФСТЭК, больше про внутренние регламенты в организациях;
Ускорение развертывания рабочего окружения - быстрая адаптация новых сотрудников.
За несколько лет я прочитал много публикаций на русском и английском как же хороши пакетные менеджеры, как же с ними легко и просто решаются все ваши проблемы. Вот прямо серебренная пуля, то чего всегда так не хватало в плюсах! Если кратко - это так. Тем не менее у них хватает своих недостатков. Судя по этим статьям казалось, что использовать пакетный менеджер в разработке очень просто... Вот только про внедрения в крупные проекты почти никто не пишет, и скоро мы поймем почему.
Пришло время исправить это упущение, и на примере внедрения Conan в userver также рассмотреть реалии open-source разработки. Нам быстро придется покинуть мир розовых пони, погрузиться в самые дебри и понять, что иногда новые модные проекты держаться на людях, отрицающих все современные методы разработки.
Conan
Небольшое введение в Conan. Первый доступный релиз Conan вышел 1 декабря 2015 года. Совсем недавно был юбилей, по этому поводу можно открыть шампанское)
В 2017 @ZaMaZaN4iKнаписал инструкцию на habr’e, которая стала путеводной звездой в Рунете для любого желающего изучить новый пакетный менеджер. С тех пор многое изменилось, публикация давно утратила актуальность. Сам Conan из модного-молодежного инструмента вырос в зрелый продукт и теперь используется в кровавом энтерпрайзе. Сейчас пакетный менеджер уже предоставляет полноценные инструкции для самых разных сценариев, с ними лучше ознакомиться в оригинале.
Акт 2. Завязка
Поехали. За точку отсчета берем выход статьи, и соответственно публичный релиз самого userver'a. После недолгого открытого обсуждения c сопровождающими было решено добавить поддержку conan (выбор был между vcpkg и conan). И как говорится - кто предложил, тому и флаг в руки.

В голове созрел план как осуществить задуманное:
Большой проект. Слона надо есть по частям. Делаем минимальный рабочий вариант.
Conan v1/v2.
Conan-center-index не самоцель, туда рецепт отправляем позднее больше для ревью, нежели ожидая попадание в репозиторий.
Акт 3. Кульминация
Пункт 1. Как сделать минимальный вариант.
Тут надо понимать, что проект достаточно хорошо структурирован, но всё же не заточен на какие-то нюансы пакетников. Получился вот такой достаточно несложный рецепт
Что заработало из коробки:
self.requires('boost/1.79.0')
self.requires('libev/4.33')
self.requires('spdlog/1.9.0')
self.requires('fmt/8.1.1')
self.requires('c-ares/1.18.1')
self.requires('libcurl/7.68.0')
self.requires('cryptopp/8.6.0')
self.requires('yaml-cpp/0.7.0')
self.requires('cctz/2.3')
self.requires('http_parser/2.9.4')
self.requires('openssl/1.1.1q')
self.requires('rapidjson/cci.20220822')
self.requires('concurrentqueue/1.0.3')
if self.options.with_postgresql:
self.requires('libpq/14.2')
if self.options.with_mongodb:
self.requires('mongo-c-driver/1.22.0')
self.options['mongo-c-driver'].with_sasl = 'cyrus' # да, это не по канону
if self.options.with_redis:
self.requires('hiredis/1.0.2')
if self.options.with_rabbitmq:
self.requires('amqp-cpp/4.3.16')
if self.options.with_utest:
self.requires('gtest/1.12.1')
self.requires('benchmark/1.6.2')Из коробки не заработали зависимости для
grpc
jemalloc
clickhouse
gssapi (kerberos)
Случился успешный успех. Userver собирался и работал с совсем другими версиями библиотек.

Казалось бы, ну всё, работа почти закончена. Можно праздновать. Всё это было сделано за полгода после выхода в open-source. Возможно, многим знакомо правило 80/20. Так вот :) Как можно догадаться, это было 20% всей работы. Минимальный рабочий вариант есть, идем к следующему пункту.
Пункт 2. Какую же версию выбрать.
В этот момент уже появился Conan v2 beta, новый функционал добавлялся каждый месяц, а документация не обновлялась или вообще отсутствовала. Поэтому пошел самым простым путем. Писать рецепт под v1, поддерживая полную совместимость с v2. Это было сложно, но возможно (иногда). За полгода именно такой рецепт у нас и получилось, и оно работало.
Пункт 3. Conan-center-index. Боль
К этому пункту пришел только спустя год после п.1 и п.2.
Open-source дело неблагодарное, вам не скажут спасибо, ваш pr не посмотрят, а иногда и просто отменят без объяснений. Стоит готовиться к такому.

Растущая популярность сыграла злую шутку с сопровождающими cci. Сюда же попал процесс миграции всех рецептов на v2. Именно в это время я решил туда принести один из самых больших рецептов. Угадайте было ли у них свободное время посмотреть мой pr. Правильно, нет. А дальше самое интересное. Conan работает с деревом зависимостей и все они должны быть друг с другом согласованы (сейчас подход сделали более мягким). Что это значит и как работает.

Итоговый рецепт может содержать только зависимости одной версии. Вариант как на картинке не сработает. Т.е. наш рецепт должен иметь такие зависимости:
Рецепт X/3.0
Рецепт Y/0.1 <- Рецепт X/3.0
Рецепт Z/0.1 <- Рецепт X/3.0
Conan-center-index публичное пространство, новые версии библиотек выходят достаточно часто (минорная версия условной zlib/curl/openssl раз в месяц принесет вам адскую боль). А теперь попробуйте успеть оформить успешный pull request, да так, чтобы все версии совпали. Кстати, все опции сборки тоже должны совпадать.

Такое тоже не сработает.
Оформление PR в conan‑center‑index
Как же выглядел процесс оформления pull request в conan-center-index в 2022-2024 годах:
Оформляем pr с зелеными билдами;
Сопровождающий пишет замечание;
Исправляем замечание. Падает сборка из-за конфликтов версий библиотек, потому что в каком-то одном пакете обновили версию, а в другом нет;
Обновляем версии, снова зеленый билд;
Ждем от 2 недель до месяца;
Сопровождающий перезапускает сборку, и она опять падает т.к. в v2 что-то поменялось за это время;
Снова пишет замечание. Поздравляю, вы вернулись в П.2.

Небольшое лирическое отступление. Команда JFrog решала проблему. Что они сделали:
Автоматический апрув pr, которые только поднимали версии зависимостей
Разрешили ранжирование версий зависимостей, т.е. вот такое стало работать
self.requires("zlib/[>=1.2.11 <2]")
self.requires("openssl/[>=1.1 <4]")Меня хватило на год работы в таком режиме. Извините, но всё-таки нет, это невозможно. Рабочий рецепт был много раз, но принимать мы его не хотели) Дело кончилось здесь
Как вы поняли, этот пункт оказался самым сложным и, так и не был выполнен за годы. Но об этом позже
Акт 4. Спад
А что дальше? Внимательный читатель заметил, что рецепт то у нас минимальный, мы не поддержали много модулей.
Далее grpc и jemalloc были включены уже командой Яндекса в лице @Anton3
Для clickhouse потребовалось намного больше работы. Здесь используется clickhouse-cpp, а она содержала достаточно простой CMakelists, который не поддерживал install. В качестве дополнительного упражнения clickhouse-cpp содержала ровно один заголовок abseil. Это уже значило, что для большого проекта нам нужно линковать именно библиотеку, а не использовать этот заголовок, либо придумать что-то с пространством имен. Да, решили что лучше тащить целиком весь abseil в таком случае. Поэтому пришлось сначала сделать опции компиляции, а потом разделить shared и static сборки (тут спасибо @enmk). Теперь мы возвращаемся к conan-center-index. Поскольку мы опираемся в своем рецепте на библиотеки оттуда, то и здесь пришлось clickhouse-cpp добавить туда. Маленький и аккуратный рецепт, всё самое интересное реализовали в cmake, а здесь просто обертка снаружи. Но это заняло 3 месяца.
Вишенкой на торте стала поддержка kerberos или же krb5. Продвинутые читатели возможно догадались в чем дело. krb5 тоже не было в conan-center-index - значит наша работа его добавить… Но не тут то было,
Собирается оно Autotools;
make install работает не так как нам нужно;
Зависимости прибиты гвоздями, например будет использоваться системный openssl, а не тот что мы несем через conan, и в итоге получаем две версии openssl в конечном бинарнике;
Репозиторий на гитхабе это зеркало, а патчи то они принимают по почте.
Была, не была. Попробуем малой кровью, conan-center-index позволяет нести патчи с собой.
С одной стороны conan удобный инструмент, с другой стороны conan-center-index обычно требует от вас рецепт библиотеки, которая поддерживается максимальным количеством систем. Т.е. userver мы собираем только под linux и только статически с динамическим рантаймом. Теперь же нам предстоит написать рецепт, который поддерживает:
macOS, Linux, Windows
clang, gcc, msvc, mingw-gcc, clang-cl
динамическую и статическую библиотеки
динамический и статический рантайм
Таким нехитрым образом вместо отладки одной сборки, мы получили больше дюжины, а учитывая что под каждую систему несколько версий компиляторов, то там 20+ вариантов. Так сказать, упростили себе задачу.
Путем нехитрых изысканий находим
From ccd48457ae137a32c627afe56f3d6dcfaf6ec1bd Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <anonymous.maarten@gmail.com>
Date: Thu, 3 Sep 2020 20:55:07 +0200
Subject: [PATCH] Use PKG_CHECK_MODULES to system library com_err
To: krbdev@mit.edu
Да, нас сильно выручил товарищ Anonymous Maarten, который еще в 2020 написал заготовки рецепта и нужные патчи… которые за 2 года так и не были приняты в krb5.
Делаем первый подход. Спустя несколько итераций всё заработало, да одна беда… На Linux krb5 make install написан, что подразумевается только установка в систему, чего мы делать не собираемся… Ушел думать как это дело починить. Снова пришла помощь откуда не ждали. Параллельно появился другой pull request, который в общем-то поддерживал намного меньше вариантов, но исправлял ту самую проблему make install. Вооружившись это патчем делаем второй подход. За полгода удалось дотянуть до рабочего решения, пускай и жертвой пал Windows, а также статическая линковка openssl. Рецепт не кажется сложным с высоты пройденного пути, но на тот момент отсутствовала любая документация по AutotoolsToolchain и AutotoolsDeps, можно было только смотреть в чужие рецепты. Ситуация с NMakeToolchain, NMakeDeps аналогичная.
Спустя 2 года после старта работ krb5 заехал в conan-center-index, но к сожалению, из-за невозможности статической линковки openssl его нельзя было использовать. Спустя еще год, уже в 2025 в результате обновления версий, принятия части патчей в upstream krb5 рецепт наконец-то стал полностью рабочим. Можно попробовать включать поддержку в userver, но это будет уже совсем другая история)

Уже в прошлом году @antoshkkaрешил повторно попробовать затащить userver в conan-center-index. Далее случилось ожидаемое:
I see this recipe is huge and complex to maintain, so I would like to know if it would be possible to start with something really simple
Комментировать только портить.
Акт 5. Развязка
Стартовали работы над Conan в userver в середине 2022 года.
Минимальный рабочий рецепт был готов к декабрю этого же года.
Весь 2023 и 2024 ушли на доводку рецепта, обновление Conan’a и работу с оставшимися зависимостями
На текущий момент рецепт userver использует 54 зависимости:
abseil/20240116.2
abseil/20250127.0
amqp-cpp/4.3.27
autoconf/2.71
automake/1.16.5
b2/5.4.2
benchmark/1.9.4
bison/3.8.2
boost/1.86.0
bzip2/1.0.8
c-ares/1.34.6
cctz/2.4
cityhash/1.0.1
clickhouse-cpp/2.5.1
cmake/3.31.11
cmake/4.2.3
concurrentqueue/1.0.3
cryptopp/8.9.0
cyrus-sasl/2.1.28
flex/2.6.4
fmt/11.0.2
gnu-config/cci.20210814
googleapis/cci.20230501
grpc/1.72.0
gtest/1.17.0
hiredis/1.3.0
icu/76.1
jemalloc/5.3.0
libbacktrace/cci.20210118
libcurl/7.86.0
libev/4.33
libiconv/1.18
libnghttp2/1.61.0
libpq/17.7
librdkafka/2.13.0
libtool/2.4.7
lz4/1.9.4
m4/1.4.19
meson/1.10.0
mongo-c-driver/1.30.6
ninja/1.13.2
openssl/3.3.2
opentelemetry-proto/1.9.0
pkgconf/2.1.0
pkgconf/2.5.1
protobuf/5.29.3
pugixml/1.15
rapidjson/cci.20230929
re2/20230301
snappy/1.1.10
sqlite3/3.51.0
yaml-cpp/0.8.0
zlib/1.3.1
zstd/1.5.7Внимательный читатель, наверное, заметил, что по какой-то причине некоторые зависимости содержатся несколько раз с разными версиями. Это уже возможности Conan v2.
10 лет назад вряд ли было возможно представить себе инструмент, который позволит собрать вместе зоопарк из Autotools, make, b2, cmake, m4, meson и ninja, чтобы получить итоговую библиотеку и всё это на разных окружениях с автоматической настройкой оного. Безусловно, это огромный шаг вперед для сообщества C и C++.
Легко ли принести что-то большое в open-source? Нет. Помогут ли вам? Да, но часто это будут совсем не те люди, от которых ожидаешь помощи.

Спасибо всем причастным, их список намного больше, чем указанный в статье. Всё это невозможно было бы без каждого из них. Естественно, отдельно нужно отметить @ProCxx и @probuildsystems.
























