Какой хороший способ организовать проекты с общими зависимостями в Mercurial?
В настоящее время я перехожу от устаревшей системы контроля версий и перехожу в проект моей группы к Mercurial. В качестве одного из примеров того типа кода, который я перемещаю, у меня есть более 25 проектов Visual Studio, содержащих несколько отдельных областей приложения, которые зависят от общего кода. Глядя на переполнение стека, ближайший вопрос, который я нашел, был этот, но он просто упомянул управление версиями. Я ищу несколько дополнительных советов по конкретным методам реализации использования Mercurial для управления этими зависимостями.
Упрощенный вид зависимостей выглядит примерно так: (Это только для иллюстрации и примера; фактические зависимости значительно сложнее, но похожи по своему характеру.)
Common Lib 1
/ | \
---- | -----
/ | \ \
App 1 Common Lib 2 \ App 2
/ | \ \
------- | ------ |
/ | \|
App 3 App 4 App 5
Модули Common Lib будут общим кодом - это будет DLL, SO или какая-то другая библиотека, которая будет использоваться между всеми приложениями одновременно - как во время компиляции, так и во время выполнения. В противном случае приложения могли бы работать независимо друг от друга.
У меня есть пара целей с настройкой моих ртутных репозиториев:
- Предоставьте каждому значимому приложению или группе компонентов свой собственный репозиторий.
- Сделайте каждый репозиторий самодостаточным.
- Сделайте общую сумму проекта самодостаточным.
- Упростите создание всей базы кода одновременно. (в конечном итоге все эти программы и библиотеки заканчиваются одним установщиком.)
- Будь проще.
Еще один момент заключается в том, что у меня настроен сервер, на котором у меня есть отдельные репозитории для каждого из этих проектов.
Я вижу несколько способов выложить эти проекты.
1. Создайте репозиторий "Shell", содержащий все.
Это будет использовать основанные на URL подпункты (например, в.hgsub, я бы сделал что-то вроде App1 = https://my.server/repo/app1
Выложено, это будет выглядеть следующим образом:
+---------------------------+
| Main Repository |
| | +---------------------+ |
| +-| Build | |
| | +---------------------+ |
| | +---------------------+ |
| +-| Common Lib 1 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| Common Lib 2 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 1 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 2 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 3 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 4 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 5 | |
| +---------------------+ |
+---------------------------+
Каждая основная папка в репозитории оболочки будет содержать вложенный репозиторий, по одному для каждой области проекта. Зависимости будут относительными: например, поскольку для приложения 4 требуется Common Lib 2, оно будет просто использовать относительные пути для ссылки на эту общую библиотеку.
Плюсы этого подхода:
- Каждая библиотека рушится один раз и только один раз.
- Подразделения Mercurial обеспечат автоматическое использование одной и той же версии библиотеки во всем проекте, поскольку в проекте существует только одна версия этого подпункта.
- Легко найти каждый ресурс.
Минусы этого подхода:
- Я не могу работать над приложением самостоятельно. Например, если я работаю над приложением 2, и ему нужно изменить общие библиотеки, все другие приложения должны будут принять эти изменения прямо сейчас.
- Если я сам извлекаю репо из приложения, я должен вручную выяснить (или знать), какие другие зависимые репо ему требуются, если я хочу его построить.
- Зависимости не сильно разделены - было бы заманчиво вставить новую функцию в любом месте, так как было легко получить все функции.
2. Имейте зависимые подпункты, которые будут полностью содержаться.
При таком подходе каждое приложение будет иметь свой собственный репозиторий (как и раньше), но на этот раз также будет содержать суб-репозитории: один для своего собственного источника и один для каждого зависимого суб-репозитория. Тогда общий репозиторий будет содержать каждый из этих репозиториев проекта и знать, как создать полное решение. Это будет выглядеть следующим образом:
+-----------------------------------------------------------------------+
| Main Repository |
| +--------------------+ +--------------------+ +--------------------+ |
| | Build | | Common Lib 1 | | Common Lib 2 | |
| +--------------------+ | | +--------------+ | | | +--------------+ | |
| | +-| Lib 1 Source | | | +-| Common Lib 1 | | |
| | +--------------+ | | | +--------------+ | |
| | | | | +--------------+ | |
| | | | +-| Lib 2 Source | | |
| | | | +--------------+ | |
| +--------------------+ +--------------------+ |
| +--------------------+ +--------------------+ +---------------------+ |
| | App 1 | | App 2 | | App 3 | |
| | | +--------------+ | | | +--------------+ | | | +--------------+ | |
| | +-| Common Lib 1 | | | +-| Common Lib 1 | | | +-| Common Lib 2 | | |
| | | +--------------+ | | | +--------------+ | | | +--------------+ | |
| | | +--------------+ | | | +--------------+ | | | +--------------+ | |
| | +-| App 1 Source | | | +-| App 2 Source | | | +-| App 3 Source | | |
| | +--------------+ | | +--------------+ | | +--------------+ | |
| +--------------------+ +--------------------+ +---------------------+ |
| +--------------------+ +--------------------+ |
| | App 4 | | App 5 | |
| | | +--------------+ | | | +--------------+ | |
| | +-| Common Lib 2 | | | +-| Common Lib 1 | | |
| | | +--------------+ | | | +--------------+ | |
| | | +--------------+ | | | +--------------+ | |
| | +-| App 4 Source | | | +-| Common Lib 2 | | |
| | +--------------+ | | | +--------------+ | |
| +--------------------+ + | +--------------+ | |
| | +-| App 5 Source | | |
| | +--------------+ | |
| +--------------------+ |
+-----------------------------------------------------------------------+
Плюсы:
- Каждое приложение может быть построено отдельно, независимо друг от друга.
- Зависимые версии библиотек можно отслеживать по каждому приложению, а не глобально. Для добавления новой зависимости требуется явный акт вставки вложенного репо в проект.
Минусы:
- При выполнении окончательной сборки каждое приложение может использовать свою версию общей библиотеки. (возможно, потребуется написать инструменты для синхронизации общих подпунктов lib. Eww.)
- Если я хочу собрать весь исходный код, я заканчиваю тем, что собираю разделяемые библиотеки несколько раз. В случае с Common Lib 1 мне пришлось бы тянуть его восемь (!) Раз.
3. Ни в коем случае не включайте зависимости как подзадачи - внесите их как часть сборки.
Этот подход будет очень похож на подход 1, за исключением того, что общие библиотеки будут извлечены только как часть сборки. Каждое приложение будет знать, какие репозитории ему нужны, и помещать их в общую папку.
Плюсы:
- Каждое приложение может быть построено отдельно.
- Общие библиотеки нужно будет тянуть только один раз.
Минусы:
- Мы должны были бы отслеживать версии библиотек, которые в настоящее время используются каждым приложением. Это дублирует функции subrepo.
- Мы должны были бы создать инфраструктуру для поддержки этого, что означает, что в скрипты сборки будет добавлено больше вещей. Тьфу.
4. Что еще?
Есть ли другой способ справиться с этим? Лучше? Какие способы вы пытались и добились успеха, какие способы вы пытались, но ненавидели? В настоящее время я склоняюсь к 1, но отсутствие независимости приложения, когда это возможно, действительно беспокоит меня. Есть ли способ получить хорошее разделение метода 2 без огромного извлечения дубликата кода и кошмара обслуживания зависимостей, при этом не нужно писать сценарии для его обработки (как в варианте 3)?
3 ответа
На мой взгляд, управление зависимостями является важным аспектом организации проекта. Вы подробно изложили различные решения, основанные на подпунктах Mercurial, и я согласен со всеми плюсами / минусами, которые вы дали.
Я думаю, что СКМ не очень подходят для управления зависимостями. Я предпочитаю иметь специальный инструмент для этого (это будет вашим решением № 3).
Мой текущий проект на Java. Он был построен с использованием Apache Ant, и я сначала настроил Apache Ivy как инструмент управления зависимостями. В итоге установка состояла из нескольких файлов конфигурации Ivy в общем каталоге и одного XML-файла, в котором перечислены зависимости для каждого модуля проекта. Целевые объекты Ant могут вызывать плющ, поэтому в каждом модуле я добавил два новых действия: "разрешить зависимости" и "развернуть встроенный артефакт". Развертывание добавляет результат buid (называемый артефактом) в общий каталог. Разрешение зависимостей означает транзитивное разрешение зависимостей модуля и копирование разрешенных артефактов в папку "lib" исходных кодов модуля.
Это решение применимо к проекту C++, поскольку Ivy не является специфичным для управления зависимостями Java: артефакты могут быть любыми. В C++ артефакты, создаваемые модулем, будут:
- так / DLL во время выполнения
- файлы заголовков во время компиляции.
Это не идеальное решение: настроить Ivy нелегко, вам все равно нужно сообщить сценарию сборки, какие зависимости использовать, и у вас нет прямого доступа к источникам зависимостей для целей отладки. Но в итоге вы получаете независимые репозитории SCM.
В нашем проекте мы затем переключили форму Ant+Ivy на Apache Maven, который занимается как сборкой, так и управлением зависимостями. Артефакты развертываются в Apache Archiva вместо общей папки. Это огромное улучшение, но оно будет хорошо работать только для проектов Java.
То, что вы хотите сделать, это иметь каждый проект в своем собственном каталоге, как в (1). Затем вы помечаете рабочие версии ваших зависимостей и сохраняете тег в каком-то файле для сборки, например:
App1 /.dependencies: CommonLib1 tag-20100515 CommonLib2 tag-20100510 App2 /.dependencies: CommonLib1 tag-20100510 CommonLib2 tag-20100510
Затем вы используете ваши сценарии сборки для создания библиотек на основе определенного тега и включаете эти встроенные библиотеки в качестве производных объектов для ваших приложений. Если время сборки является проблемой, вы можете иметь помеченную версию, которая используется для тех библиотек, которые предварительно собраны и сохранены где-то.
Примечание (принципы проектирования одинаковы при разработке схемы базы данных, объектной модели или сборки продукта):
- Не ссылаться на код в других проектах (нарушает инкапсуляцию)
- Не иметь несколько копий библиотек в вашем хранилище (модульность)
Мы решили аналогичную проблему с помощью Subversion.
Каждое приложение и каждый Common Lib
имеют свои собственные репозитории.
Каждое приложение имеет каталог Libs
которые содержат зависимые DLL.
поэтому приложение получает только обновление Common Lib
если предоставляется новый набор DLL.
однако обновление папки lib не является тривиальным, потому что зависимые подчиненные библиотеки должны соответствовать правильной версии.