пятница, 2 июня 2017 г.

[prog.thoughts] Пара докладов про управление зависимостями в C++ и небольшое послесловие к ним

Надеюсь, что тем, кто интересуется проблемой управления зависимостями в С++ проектах, могут быть интересны эти два доклада. Первый хорош тем, что показывает спектр проблем, с которыми приходится иметь дело в больших и кроссплатформенных C++ проектах.

Второй доклад хорош тем, что он дает небольшой, но веселый и бодрый обзор некоторых из существующих пакетных менеджеров для C++:

Мне же, однако, подходы на базе централизованых хранилищ пакетов, будь то conan.io (в ближайшем будущем conan-central), hunter или cppan.org не очень нравятся. Точнее говоря, я не верю, что это окажется достаточно жизнеспособным из-за разных причин. Во-первых, в C++ все очень разобщено, поэтому слабо верится в то, что народ массово начнет концентироваться вокруг какого-то одного центрального репозитория C++ных проектов. Во-вторых, кто и с какой радости должен будет оплачивать сей банкет? Хостинг 100500 непонятных проектов и кучи версий для них -- это накладные расходы, которые кто-то должен оплачивать. Что-то в мире C++ мне не видится организации, которая взяла бы на себя бремя этих накладных расходов. Тем более, что почти что все уже живет на ресурсах типа GitHub, BitBucket или SourceForge.

Кроме того, те же Conan и Hunter работают по принципу организации центрального хранилища загруженных пакетов на машине разработчика. Т.е., если разработчик однажды скачал себе Boost-1.64 для проекта A, а затем захотел использовать Boost-1.64 для проекта B, то пакетный менеджер будет переиспользовать уже загруженный ранее Boost.

Как по мне, то этот подход хорош для быстрой разработки какой-нибудь мелкой программулины. Ну, например, прочитал я на Хабре какую-то статью и захотелось мне проверить один из ее тезисов. Или задали на LOR-е вопрос, для поиска ответа на который нужно написать программку на 50 строк. В этих случаях хочется приложить минимум усилий: где-то просто перечислить список нужных мне внешних зависимостей (например, таких, как Boost, ICU, zlib) и потратить минимум времени на подгрузку и сборку этих зависимостей. Поскольку, если я пишу программу на 50 строк "на выброс", то мне вряд ли захочется ждать, пока Boost еще раз скачается и скомпилируется. Поэтому переиспользование того, что было загружено ранее, в таких условиях -- это вполне себе разумный подход.

С другой стороны, когда я делаю какой-то внутренний проект (или же работаю с какой-то приватной веткой), то у меня могут возникать ситуации, когда мне нужно взять не просто библиотеку X, а конкретный ее слепок. Скажем, библиотека X разрабатывается моим коллегой и он добавил в X новый нужный мне функционал, но еще не зафиксировал ее стабильную версию. Поэтому я на свой страх и риск хочу взять X от ревизии (коммита) R1. С большой долей вероятности я могу наткнуться в X@R1 на какую-то проблему. Из-за чего мне потребуется перейти на X от ревизии R2 (может быть даже эту ревизию я создам сам для того, чтобы исправить проблему, а потом вернуть правки в основную ветку разработки X).

В этих случаях, как мне кажется, плохо работают обе составляющие централизованного подхода (положенного в основу canon/hunter/cppan):

  1. Центральное хранилище опубликованных пакетов. Поскольку для работы с X@R1 кто-то должен быть создать соответствующий пакет и опубликовать его в этом хранилище. Потом это же нужно будет проделать с X@R2 и т.д. При том, что вряд ли хорошо засирать центральное хранилище промежуточными пакетами. Как и делать собственное зекало этого центрального хранилища для того, чтобы засирать его локально.
  2. Централизованное хранилище загруженных на локальную машину пакетов. Поскольку если X@R1 и X@R2 мне нужны только для проекта Y, да и то в какой-то временной ветке Y, то не есть хорошо сохранять X@R1 и X@R2 там же, где хранятся нормальные стабильные версии чего-то вроде Boost-а или ICU.

Имхо, для таких случаев хорошо работает подход, который мы используем в своем MxxRu::externals:

  • во-первых, MxxRu::externals сам забирает исходники стороннего компонента оттуда, где эти исходники лежат. Например, лежит Boost в tar.bz2 на SourceForge -- оттуда его и заберем. А исходники spdlog от конкретного коммита есть в git-е на GitHub-е -- ну значит вытянем их через git с GitHub-а. Таким образом для того, чтобы сделать свой пакет доступным для пользователей, не нужно предпринимать каких-то серьезных усилий. Можно даже свой тарбол не делать, если кроме GitHub-а ни на что мозгов не хватает, то достаточно просто теги в своем репозитории создавать, GitHub сам для них тарбол доступным для загрузки сделает. Ну а если нужно забирать какие-то промежуточные версии собственных компонентов из закрытых репозиториев внутри своей организации, то тут вообще никаких проблем нет, ничего дополнительно поднимать не нужно (речь про локальный инстанс того же Conan-а);
  • во-вторых, загруженные зависимости кладутся только в локальный рабочий каталог. Поэтому, если моему проекту Y потребовался сперва X@R1, а затем X@R2, то исходники X@R1 и X@R2 никуда за пределы рабочего каталога Y не уйдут. И, соответственно, когда я удалю этот рабочий каталог, то автоматически исчезнет и весь "мусор" , который был с этим связан.

Однако, поскольку мир сходит с ума и куча народа с удовольствием жрут тот же самый CMake, то есть серьезные подозрения, что CMake в качестве системы сборки и пакетные менеджеры на его основе (вроде Conan-а и Hunter-а) таки станут де-факто стандартом в мире C++. Ну что тут поделать, количество разума -- величина постоянная, а население-то растет.

ЗЫ. Отдельно хочется помянуть незлым тихим словом красноглазых Linux-оидов, которые всерьез считают, что для управления C++зависимостями нужно пользоваться штатным пакетным менеджером вашего любимого дистрибутива Linux-а. Этих сторойными рядами прямо направляю в жопу. Ибо ваше мнение не интересно. Ну вот от слова совсем. По крайней мере пока не поймете, что "кроссплатформенность" -- это "переносимость между разными платформами", а не "переносимость между разными дистрибутивами Linux-а".

Комментариев нет: