вторник, 6 апреля 2010 г.

Использование bzr-externals или компонентный подход

При работе над крупными проектами часто возникает потребность выделить часть проекта в отдельный подпроект, как самостоятельную сущность. Такой подпроект может представлять собой некоторую библиотеку, которая используется в разных проектах. Для работы с составными проектами, которые включают в себя дополнительные библиотеки, в bzr задумана функция под названием nested trees (в git для этого есть submodules, а в hg — subrepos). К сожалению разработка функции nested trees пока не завершена, поэтому вместо нее можно использовать плагины bzr-externals или bzr-scmproj. Я попросил автора плагина bzr-externals Евгения Тарасенко рассказать про работу с плагином. В данной статье описывается работа с плагином версии 1.3.
Автор: Евгений Тарасенко

Если ваш проект использует общие библиотеки или компоненты, то самым правильным решением будет подключить их как вложенные подпроекты (или компоненты), а не тупо копировать внутрь проекта.

В Bazaar работа с общими компонентами пока еще не доведена до ума, поэтому пришлось написать небольшой плагин bzr-externals. Надо отметить, что существует еще один похожий плагин bzr-scmproj, но в отличие от первого, в нем для работы с компонентами используются отдельные команды, а это означает, что работа возможна только из консоли и такие инструменты как Bazaar Explorer, QBzr и TortoizeBzr остаются не у дел.

Основная же идея bzr-externals — использование только стандартных команд, чтобы пользователю не нужно было делать лишних телодвижений для работы с компонентами.

Итак, нам понадобится плагин bzr-externals, который можно скачать с http://launchpad.net/bzr-externals или взять прямо из репозитория lp:bzr-externals, о том как это сделать можно узнать из этой статьи.

Проектом может быть любая ветка. Для добавления нового компонента в проект как внешней ссылки нам все же понадобится консоль, но так как это делается только один раз, то я думаю ничего страшного. Формат команды добавления компонента следующий:
bzr externals-add URL  DIRECTORY [--revision REVISIONSPEC]
Здесь URL может быть относительным, так же как и в svn:externals. Например, следующие команды делают почти одно и тоже, но разными способами, не считая использования конкретной ревизии в первом варианте (eadd — встроенный псевдоним команды):
bzr eadd bzr://example.com/repos/common/foo  common/foo -r revno:100
Если доступ к хранилищу возможен по разным протоколам (например, http или bzr+http), то для компонента имя протокола можно опустить, будет использоваться тот же протокол, по которому вы получите главный проект
bzr eadd //example.com/repos/common/foo  common/foo
Если ваш проект хостится на разных серверах, но с одинаковой структурой каталогов, то имя сервера также можно опустить, будет использовать сервер главного проекта
bzr eadd /repos/common/foo  common/foo
И наконец возможен доступ относительно каталога главного проекта в репозитории
bzr eadd ../../common/foo  common/foo
Каталог, в котором должен размещаться компонент, DIRECTORY указывается относительно корня проекта. Если такого компонента еще нет в рабочей папке проекта, то будет выполнен branch/checkout, в зависимости от типа главного проекта, иначе pull/update.
REVISIONSPEC — необязательный параметр, и должен использоваться, только если вам нужна конкретная версия компонента.

В результате выполнения команды externals-add будет добавлен компонент в рабочую директорию, а также в корне проекта появятся конфигурационные файлы:
.bzrmeta/externals
.bzrmeta/externals-snapshot
Первый файл используется для хранения параметров всех компонентов и может быть отредактирован пользователем; например, чтобы привязать компонент к конкретной версии или просто удалить компонент из проекта. Второй файл создается автоматически при каждом commit в проекте и предназначен для создания "снимка" используемых ревизий компонентов, чтобы впоследствии, при откате на какую-либо ревизию, можно было точно восстановить все дерево проекта (чего, кстати, не умеет делать subversion).

Формат обоих конфигурационных файлов одинаков и незатейлив:
URL DIRECTORY REVISIONSPEC
Итак, компонент добавлен в ваш проект, и теперь, казалось бы, надо описать, как нам работать с ним, но тут вступает в силу главный принцип плагина – никаких дополнительных команд. Поэтому всё, что вы делаете с основным проектом, будет повторяться и для всех компонентов. Вот список поддерживаемых команд для версии 1.3:
  • branch [--revision]
  • checkout [--revision]
  • commit
  • pull
  • push
  • update.
Однако, если вам все же не хватает этих команд, то есть два пути: первый — просто зайти в директорию с компонентом и делать с ним что угодно, так как по сути это обыкновенная ветка Bazaar. И второй путь — использовать вторую дополнительную команду externals-cmd или кратко ecmd, чтобы выполнить любую команду Bazaar для всех компонентов проекта и для самого проекта тоже. Например, посмотреть последнюю ревизию для всех:
bzr ecmd -- log -r -1
Работа с Bazaar может быть организована различными способами, в том числе с использованием feature branch, когда сначала делается локальная копия удаленного репозитория, а уже от нее делается ветка для конкретной фичи, плагин поддерживает и такой режим работы.

Рассмотрение плагина подошло к концу, дополнительную информацию можно получить с помощью:
bzr help externals

5 комментариев:

  1. > "снимка" используемых ревизий компонентов, чтобы впоследствии, при откате на какую-либо ревизию, можно было точно восстановить все дерево проекта (чего, кстати, не умеет делать subversion)

    Вы не могли бы пояснить? В свн можно указать ревизию для внешнего включения. Например, версии 5 моего продукта соответствует версия 1010 из внешнего хранилища. А версии 15 моего продукта - версия 1500 из внешнего хранилища.
    При извлечении версии 5 я получу версию 1010 извне.

    Это отличается от того, как это делает ваш плагин?

    ОтветитьУдалить
  2. Я не слишком хорошо знаком с svn, но суть "снимков" в том, что они формируются автоматически, без явного участия пользователя.

    Явно указать ревизию внешнего проекта, как в svn, можно и здесь, см. опциональный аргумент REVISIONSPEC в команде externals-add.

    ОтветитьУдалить
  3. Плагин полностью повторяет принцип работы, который используется в svn: если нам всегда нужна последняя версия компонента, то при добавлении компонента мы не указываем никаких ревизий, если же мы работаем с какой-то конкретной версией компонента, то указываем ее, т.е. точно так же как в svn.

    Отличие, вернее даже не отличие, а дополнительная фича, заключается в том, что если у нас в конфиге не указана конкретная версия, т.е. работаем с последней версией компонента, и хотим сделать релиз (пометить тэгом) или же просто нужно откатиться на более старую версию главного проекта, то при этом выберется версия компонента соответствующая тому моменту времени, а не последняя как в svn, в этом и есть суть снимка.

    Иначе, нам пришлось бы ручками фиксировать конкретные версии компонент для выпущенного релиза, т.е. прописывать явно в конфиг файле.

    ОтветитьУдалить
  4. > Если ваш проект использует общие библиотеки или компоненты, то самым правильным решением будет подключить их как вложенные подпроекты (или компоненты), а не тупо копировать внутрь проекта.

    совершенно верно!

    для этого в ветки организовывается таким способом:

    ветка/репозиторий {библиотеки} (компонента) ведётся в одном месте (например в bzr.example.org/my-lib/ )

    а ветка/репозитарий с {основным-проектом} (который использует эту библиотеку) -- в другом месте (например в bzr.example.org/my-main-project )


    далее -- внутри {основного-проекта} делается:

    $ cd "..../..../..../my-main-project"
    $ bzr branch "bzr.example.org/my-lib/" lib/my-lib
    $ bzr join lib/my-lib
    $ bzr commit -m "библиотека my-lib - прикреплена к основному проекту из upstream"

    и всё! это работает очень хорошо УЖЕ (в первых версиях bzr-2.X )

    далее обновление {файлов-библиотеки} my-lib (внутри {главного-проекта}) -- происходит обычным "merge" внутрь {главного-проекта}:

    $ cd "..../..../..../my-main-project"
    $ bzr merge "bzr.example.org/my-lib/"
    $ bzr commit -m "библиатека my-lib - обновлена из upstream"

    ОтветитьУдалить
  5. Интересный подход с merge, решает проблему отображения истории компонентов прямо в главном проекте. Но есть один нюанс, если в качестве компонента выступает какой-нибудь большой фреймворк, за которым мы следим, то все хорошо. А вот если компоненты это модули ваших сослуживцев, которые еще изменяются чаще раза в неделю, то при некотором количестве этих компонентов процедура с merge может стать утомительной, плюс нужно отслеживать выходы новых багфиксов для компонент руками.
    Но в целом подход мне нравится.

    ОтветитьУдалить