Контроль сторонних библиотек с помощью Subversion.
Когда я начинал пользоваться Subversion, по старой CVS-ой привычке не мог привыкнуть к тому, что в Subversion “всё, буквально всё,” делается копированием. “Всё” – это и копирование само по себе и создание веток (branches) и меток (tags). Кстати, отсутствие меток в чистом виде я не понимаю до сих пор. Возможно с архитектурной точки зрения это правильно, но с точки зрения пользователя это не удобно – поставив метку пользователь должен озаботится защитой вновь созданной ветки от изменений. Это можно сделать на уровне контроля доступа к репозиторию, но всё равно неудобно.
Вернёмся к нашим баранам. Со временем я оценил универсальность копирования. Сегодня я бы хотел поделиться удобным способом управления сторонними библиотеками, используемыми в моих проектах. Итак классический набор проблем, связанных с использованием сторонних библиотек:
- Сторонние библиотеки требуют хотя бы минимальной настройки окружения. Это может быть переменная окружения или путь в списке include директорий;
- Часто код зависит от конкретной версии библиотеки, что опять же добавляет работы по настройке окружения;
- Помимо первых двух пунктов, любая сторонняя библиотека требует хотя бы минимальной установки (например нужно распаковать архив);
- В случае, когда код библиотеки открыт для модификации, в него зачастую вносятся локальные исправления (особенно если разработчик библиотеки не радует частыми релизами). Подобные изменения ещё долее усложняют инсталляцию и настройку окружения;
- Переход на новую версию библиотеки влечёт за собой ещё больше проблем с обновлением библиотеки на всех компьютерах.
В качестве решения этих проблем удобно помещать исходный код или готовые бинарные файлы в репозиторий вместе с исходниками проекта. Конечно, в идеальном случае, в репозитории не должно быть бинарников, однако жизнь такая сложная штука… Как правило, я использую вот такую структуру репозитория:
/
external
library-1
trunk
version-1
version-2
library-2
trunk
version-1
version-2
project-1
trunk
library-1
library-2
version-1
version-2
project-2
Все стороние библиотеки помещаются в “/external”. Для каждой создается отдельный каталог. Текущая версия библиотеки хранится в “/external/*/trunk”. Все последовательные версии – в соответствующих метках “/external/*/version-x”. Проекты организованы аналогичным образом. Исходники (или бинарные файлы) библиотек копируются в каталог проекта, причём всегда копируется “trunk” нужной версии, а не одна из помеченных версий. Поскольку речь идет о копировании в понимании как это сделано в Subversion, такая схема позволяет относительно безболезненно мигрировать на новую версию библиотеки. Полный список преимуществ:
- Отпадает необходимость настройки окружения. Все нужные файлы берутся из репозитория;
- Каждый проект может пользоваться конкретной нужной ему версией библиотеки все зависимости от других проектов;
- Миграция на новую версию библиотеки безболезненна насколько это возможно;
- Локальные изменения в коде библиотеки будут учтены при смене версий.
Переход на новую версию библиотеки происходит в три этапа:
- Новая версия файлов чекинится в “/external/*/trunk”. При этом важно не забыть про добавленные и удаленные файлы. Это самый трудоёмкий этап;
- После того, как все файлы были зачекинены, создается новыя метка “/external/*/version-X”;
- В каталоге проекта делается merge, чтобы получить свежие изменения из “/external/*/trunk”.
В заключение добавлю, что помимо перечисленных достоинств, репозиторий не распухает от многочисленных копирований каталогов в силу особенностей реализации копирования в Subversion. Так что такую схему можно смело использовать для хранение больших библиотек.
я не сильно много жил с subversion, потому вопрос - для данного метода, насколько сложно будет держать в дистрибутиве актуальные версии действительно больших библиотек (например boost или wxWidgets) если я обновляю их с репозиториев соответствующих проектов (некоторые вещи из того же boost-а хочется пользовать до того, как выйдет релиз в котором они появятся)?
Я ещё не пробовал перенести boost в свой репозиторий. А все остальные библиотеки достаточно малы, чтобы делать миграцию руками. Однако этот процесс несложно автоматизировать с помошью tailor.py: http://www.darcs.net/DarcsWiki/Tailor
Пример синхронизации SVN и darcs можно найти здесь: http://www.darcs.net/pipermail/darcs-users/2005-January/005070.html. Синхронизация SVN и CVS должна работать аналогично.
Со временем я собираюсь перенести boost в свой репозиторий. Тогда я смогу поделиться детальным HOWTO.
Мы у себя решили попробовать использовать SVN вместо CVS и сейчас размышляем над тем как удобнее спланировать структуру хранилища.
Если не трудно уточни некоторые моменты по статье.
…
Исходники (или бинарные файлы) библиотек копируются в каталог проекта, причём всегда копируется trunk нужной версии, а не одна из помеченных версий.
…
Под каталогом проекта подразумевается каталог в хранилище?
Создается ли при такой схеме новая метка версии для проекта при создании новой версии библиотеки?
> Под каталогом проекта подразумевается каталог в хранилище?
Да.
> Создается ли при такой схеме новая метка версии для проекта при создании новой версии библиотеки?
Обычно да, если проект переводится на новую версию библиотеки. Т.е. если делается “svn merge -r N:M project/trunk/library-x”. Нет, если новая версия библиотеки просто добавляется в репозиторий и ничего больше не происходит.
> Однако этот процесс несложно автоматизировать с помошью tailor.py: http://www.darcs.net/DarcsWiki/Tailor
Похоже, что этот скрипт не очень подходит для этой цели. Он переносит все изменения один-в-один, включая все промежуточные версии. В нашем же случае нужны только финальные версии…