Пользователь
0,0
рейтинг
21 октября 2011 в 19:41

Разработка → Делаем релизы с помощью Maven в Java из песочницы

JAVA*

О чем эта статья?


Эта статья о том:
  1. Что такое релиз?
  2. Как нумеруются релизы?
  3. Зачем при релизе нужен бранч?
  4. Почему релиз это больше, чем просто jar (war, ear, zip, etc)?
  5. Что такое maven-release-plugin?
  6. Делаем бранч c помощью release:branch.
  7. Подготовка к релизу с помощью release:prepare.
  8. Выпускаем релиз с помощью release:perform.

Для чтения и понимания этой статьи я рекомендую освежить свои знания по Maven. Многие термины могут быть непонятны без понимания этой технологии.

Первые несколько разделов посвящены общей концепции релизов и их нумерации. Если вы достаточно опытны, skip it!

Что такое релиз и с чем его едят


Для себя релиз я определяю, как закрепление определенного набора функциональности. Закрепляя функциональность, вы говорите: «Эта версия продукта содержит в себе возможность создавать новый документ, перелистывать страницы пальцем или запускать ракеты в космос.» Хорошей практикой является ведение списка закрепленной функциональности в баг-трекинговой системе, позволяя ребятам из QA быстро и просто определить, что умеет ваш релиз, а чего он не может, сказать, что где в вашу систему закралась ошибка и прочее-прочее-прочее.
Рассмотрим пример небольшого проекта. Представим себе, что городской зоопарк заказал нам разработать систему мониторинга состояния животных: в каких вольерах они живут, какой смотритель за ними ухаживает. Так же нужно информировать смотрителя с помощью sms о том, что животное нуждается в уходе.
Пусть проект будет сделан для Web и имеет следующую структуру:
zoo
|---zoo-web
|---zoo-sensor-server

Релизом для нас будет war, который мы можем задеплоить на веб-сервер, и zip c небольшим сервером, принимающим сообщения от датчиков в вольерах с животными.

Нумерация релизов


«Для отслеживания изменений программного обеспечения было создано большое количество схем присвоения номеров версиям программного обеспечения,» — говорит нам Википедия.
И это воистину так. Большинство проектов, компаний, продуктов и индивидуальных разработчиков используют свой способ исчисления версий.
Maven предлагает нам использовать более-менее общепринятую схемой нумерации x.y.z, где:
  • x — номер основной функциональности релиза
  • y — номер дополнительной функциональности релиза
  • z — номер фикса багов

Например, 1.3.23, 0.7.7 и 1.4.112. Будем использовать ее для нашего зоопарка.

Тем временем в зоопарке

Итак, мы договорились дирекцией зоопарка, что перед запуском полноценного продукта, мы дадим им три промежуточные версии. Первая — будет содержать реализацию сервера, собирающего информацию с сенсоров в вольерах. Веб часть будет очень простой, выводящей метрики в неподготовленном виде. Вторая — будет содержать полноценный экран с информацией по вольерам, возможностями сортировки и прочее. Третья — будет содержать функциональность оповещения служащих о необходимых действиях через смс. И лишь когда мы все соберем, и клиенту все понравится — мы выпустим финальный релиз.

0.y.0

Дадим каждой из промежуточных версий имена 0.1.0, 0.2.0, 0.3.0 соответственно.
Первая цифра 0, потому что наш главный релиз еще не вышел, это лишь промежуточный результат.
Вторая цифра равна номеру поставки. Третья — 0, потому что мы считаем, что в ней нет багов на момент сборки.
Когда третья цифра равна 0, ее часто не пишут: 0.1, 0.2, 0.3.
image

Alpha, Beta и прочие RC

Перед тем, как поставить релиз клиенту, даже промежуточный, он должен пройти QA процесс. Подходы к выпуску релизов для тестирования так же могут различаться, но в своих проектах я стараюсь использовать понятие Release Candidate.
Перед выпуском релиза x.y вы собираете x.y-RC1, x.y-RC2 и так далее, пока QA не скажет вам, что релиз стабилен и готов в UAT или Production.
Тогда последний RC становиться тем самым долгожданным релизом.
image

0.1.x

В любом коде существуют ошибки. И пользователь с радостью их найдет. В процессе саппорта заводятся новые баги, вы их правите и должны предоставить версию с исправлениями. Для этого собираются баг-фикс релизы.
Например, вы выпустили версию 0.1.0, и в ходе реальной работы выяснилось, что данные датчика температуры неправильно обрабатываются. Выпускается релиз 0.1.1, который исправляет досадное недоразумение.
image

Финальный релиз и далее

После серии из промежуточных поставок вы собираете 1.0-RC, заканчиваете процесс QA и собираете 1.0. Если впоследствии заказчик захочет функциональность автоматического открытия клеток по ночам, чтобы животные могли порезвиться, и вы напишите и соберете 1.1. Найдет пару десяток сотню багов и вы выпустите 1.1.47.
Если он вдруг захочет написать все заново, красивее и круче, то вам скорее всего понадобиться 2.0, потом 2.1.45 и так далее, и так далее.
image

Бранч при выпуске релиза


Итак, релиз — это закрепление функциональности. То есть мы создали сервер по сбору данных с сенсоров в вольерах. Сделали простой веб-интерфейс. Протестировали и отдали все это в зоопарк, пусть ставят датчики. Мы пока начнем делать красивый и дружелюбный интерфейс. И конечно же все разберем при этом.
Возникает несколько вопросов:
  1. Где должен писаться новый код дружелюбного интерфейса?
  2. Где мы будем править ошибку в обработке информации с датчика, если ее вдруг найдут в зоопарке?

В транке мы, конечно, должны поправить ошибку. Но мы не можем собрать за несколько дней новую версию и отдать в зоопарк. Мы же все разобрали ради красивого интерфейса.
Поэтому, обычно, выпуск релиза с основной или дополнительной функциональностью сопровождается созданием ветвления в VCS (бранча). Это ветвление будет содержать в себе фикс багов, найденных в текущей production версии клиента. В транке вы можете делать, что угодно. И при этом у вас всегда есть стабильный код, который содержит только исправления ошибок.
Обычно такой бранч носит название <имя проекта>-0.1.x, то есть бранч от релиза 0.1, который содержит фикс багов данной версии.
image

Почему релиз это больше, чем сборка war


Конечный продукт — это не просто war у вас на руках. У продукта есть жизненный цикл. Он должен быть доступен для других продуктов. Мы можем собрать релиз заново, если все сборки были утеряны в результате пожара в серверной зоопарка.
Релиз должен выполнять две задачи:
  1. Закрепление и обеспечение доступности собранного продукта.
  2. Закрепление исходного кода релиза, на случай повторной сборки или начала новой ветки из этого релиза.

Таким образом вам нужно найти, где будет лежать собранный war и zip для зоопарка, а так же запомнить на уровне VCS, что именно это состояние кода соответствует релизу.
image

Запоминаем состояние кода

Для этого используется теги. Теги в системе контроля версий, это имя закрепленное за ревизией. Для пользовательского взгляда, тег это такая же папка с исходным кодом, но ее состояние закреплено на момент создания тега. И не надо использовать теги как бранчи.

Выкладываем собранный продукт

Для хранения и обеспечения доступа к собранному продукту, вы можете использовать даже шару на внешнем сервере. В промышленную разработку Maven приносит понятие репозитория. В репозиториях хранятся артефакты, из репозитория можно взять артефакт. Репозиторий структурирует артефакты по группам, именам и версиям.

Все вместе

Нам надо сделать:
  1. Если это релиз основной или дополнительной функциональности, сделать ветку для фикса багов.
  2. Сделать тег, чтобы запомнить состояние кода в момент релиза.
  3. Сделать сборку продукта и выложить его для общего доступа.


maven-release-plugin


maven-release-plugin — это инструмент взаимодействия между системой контроля версий и репозиторием, позволяющий выполнить релиз с помощью нескольких команд. Плагин выполняет за тебя весь процесс релиза от начала и до конца.

Настройка плагина

Для работы плагина ему необходима следующая информация в pom.xml главного модуля вашего проекта:

Информация о системе контроля версий

Во-первых, нам надо указать, где храниться исходный код проекта. Для этого используется стандартный для Maven тег:
<scm>
    <connection>scm:svn:http://svn.zoo.com/zoo/trunk</connection>
</scm>

scm:svn:http расшифровывается как: используй протокол scm для взаимодействия с репозиторием svn по пути http:. SCM это протокол Maven для работы с системами контроля версий.
Если мы будем писать зоопарк с использованием Mercurial, то надо написать следующее:
<scm>
   <connection>scm:hg:http://svn.zoo.com/zoo/trunk</connection>
</scm>


Конфигурация параметров сборки

Для того, чтобы задать параметры самого релиза используется подключение плагина в секцию
<build><plugins>.

 <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-release-plugin</artifactId>
   <configuration>
      <tagBase>svn://svn.zoo.com/zoo/tags</tagBase>
      <branchBase>svn://svn.zoo.com/zoo/branches</branchBase>
      <preparationGoals>clean install</preparationGoals>
      <goals>deploy</goals>
      <autoVersionSubmodules>true</autoVersionSubmodules>
   </configuration>
</plugin>

В конфигурации:
  • tagBase — путь к папке в вашем репозитории, где будут храниться теги.
  • branchBase — путь к папке в вашем репозитории, где будут храниться ветки.
  • preparationGoals — задачи, который нужно выполнить при подготовке к релизу, перед созданием тега. Обычно это стандартный цикл сборки.
  • goals — задачи, которые нужно сделать при самом релизе. Обычно это интеграционные тесты и деплой в общий репозиторий.
  • autoVersionSubmodules — это полезная настройка, позволяющая указать новую версию сразу для всех модулей в проекте.

Так же плагин использует версию проекта:
<version>0.1-SNAPSHOT</version>


Общие требования

Для создания релиза так же небходимо иметь установленный в системе клиент для VCS. Он должен быть доступен из консоли и быть в PATH (svn, hg, git).
Так же плагин не позволит вам создать релиз, если у вас есть локальные модификации в коде. И правильно сделает!
Для создания релиза плагину нужен write-доступ в систему контроля версий. Убедитесь, что в авторизационном кеше на вашей машине есть необходимая информация или используйте -Duser=<vcs-user> -Dpassword=<vcs-password>.

Создаем релиз zoo-0.1


Соберем релиз первой поставки для нашего зоопарка. У нас на руках транк с версией 0.1-RC3-SNAPSHOT. QA команда сообщила нам, что RC2 был достаточно хорош, чтобы мы могли делать релиз.

Создание бранча для релиза — release:branch

Этот пункт нужно выполнять, только если вы собираете релиз с основной или дополнительной функциональностью. Если бы мы собирали багфиксинг релиз zoo-0.1.2, то этот пункт мы бы пропустили.
Если мы собираем Release Candidate, то этот шаг так же пропускаем.
Для создания бранча нужно выполнить:
mvn release:branch -DbranchName=zoo-0.1.x

В ходе выполнения, Maven спросит нас, какая следующая версия для транка, по умолчанию предложив 0.2-SNAPSHOT.
What is the new working copy version for "Zoo"? (com.zoo:zoo) 0.2-SNAPSHOT:


Подготовка к релизу — release:prepare

Фаза подготовки к релизу в терминах maven-release-plugin включает в себя проверку проекта (компиляция, тесты, сборка) и создание тега в VCS. Теги будут создавать в папке, заданной при конфигурации.
Релиз будем делать из созданного бранча, предварительно выбрав его из репозитория. Транк еще на предыдущем шаге начал жить новой жизнью и его больше не трогаем.
image
Если мы собираем баг-фикс релиз, то выбирать ничего не нужно. Мы и так в ветке для баг-фиксов.
Если вы собираем Release Candidate, то эту фазу мы выполняем из транка.
Выполним следующую команду:
mvn release:prepare

Maven последовательно спросит нас номер собираемой версии, название для тега и номер следующей версии:
На последний вопрос ответим ему 0.1.1-SNAPSHOT, ведь это и есть первый будущий багфикс релиз.

Выпуск релиза — release:perform

Фаза выполнения релиза включает в себя чекаут исходного кода из тега и сборку его (обычно до фазы deploy). Да, именно так, чекаут исходного кода из тега. Зачем это нужно, если у нас уже есть исходный код рабочей копии? Потому что Maven хочет быть уверен, что эта сборка будет идентична той, что потом можно сделать выбрав тег вручную.
Для завершения релиза надо выполнить:
mvn release:perform

На этот раз вас ничего не будут спрашивать, просто сделают все что надо. Результатом будет выложенный в общий репозиторий артефактов релиз.

Если что-то пошло не так.


Если в процессе использования плагина, что-то сломалось, то у вас есть три варианта:
  1. Если, это произошло по внешним факторам, например сервер с системой контроля версий был недоступен, то запустите задачу еще раз. Плагин помнит последнее успешное действие и постарается продолжить.
  2. Если плагин в процессе произошла ошибка самой сборки, например, интеграционный тест не прошел, используйте release:rollback, эта команда откатит все изменения версий и удалит неудачные теги из системы контроля версий
  3. Если вы уверены, что откатывать нечего, и просто хотите начать заново, используйте release:clean
Алексей Дидик @adidik
карма
15,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (24)

  • 0
    Каскадный релиз snapshot'ов не знаете как сделать? Я имею ввиду, что у меня есть проект в версии SNAPSHOT. Он использует , которая тоже SNAPSHOT. Release плагин не даст сделать релиз с такой депенденсей, придется руками ее релизить. Нет ли автоматического тула, который релизнул бы все сразу?
    • +1
      Я такого тулза не знаю.
      Но, насколько я понимаю структуру Мавена, если такая тулза и есть, то она написана руками энтузиастов, которым нужен такой же юзкейс как вам. Ничего официального.

      Сам я против SNAPSHOT зависимостей в проектах. Если интересно, могу понудеть «почему».
      • 0
        Если интересно, могу понудеть «почему».

        Интересно, хотя я и сам испытывал на себе радости изменений snapshot'Ов :)
        • +1
          Мне кажется, что команда Мавена уже сама не рада, что они придумали снепшоты и поддержку их на уровне репозиториев.
          Обычно главным минусом снепшотов в иностранных блогах называют «зависимость от нестабильной, потенциально сломанной версии».
          Но в реальности, основная проблема — это синхронизация снепшота между общим репозиторием команды и локальным разработчика.

          — Петя, в твоей либе бага, поправь!
          — Пофиксил, комрады! В репозитории!
          — Спасибо, Петя!

          через час.

          — Петя, в твоей либе бага, поправь!
          — Пофиксил, комрады! В репозитории!
          — Петя, ни чего ты не пофиксил! У нас все та же бага!
          — Пофиксил, комрады :( В репозитории :(

          Комрады начинают разбираться, почему фикс в библиотеке Пети не работает на их машинах, и выясняют, что Мавен просто не скачивает обновленный снепшот с общего репозитория.
          Потому что Мавен обновляет снепшоты раз в сутки.
          Есть workaround, силой заставить Мавен проверять обновление снепшотов при каждой сборке.

          <updatePolicy>never</updatePolicy>


          Но мой взгляд это уже деревянная подпорка, под стройное кирпичное здание вашего проекта. Если снепшотов будет много, то обновления заметно замедлят каждый билд. А медленный билд — это проблемы с continious integration.
          В третьей версии уже хорошо заметна тенденция Maven к использованию точных номеров версий для плагинов и отказу от снепшот зависимостей. Они понимают, что и зависимость от непонятно чего, и проверка обновления каждый билд — зло.

          Как тогда вести два параллельно развивающихся проекта? Для своих проектов я веду честный версионинг.
          Даже если мне придется собрать два релиза в день, в каждом по одному багу. Это помогает отслеживать изменения в релизе в багтрекере и в целом держать проекты в форме. Но это, конечно, тема отдельной статьи.

          • 0
            Continuous integration системы как раз предназначены для того, чтобы быстро выявить проблему либо в коде самого приложения, либо в коде snapshot-зависимости (при условии, что в нем были изменения). Для того, чтобы Maven всегда обновлял snapshot-зависимости, достаточно прописать в репозитории следующее:
            <code:xml>
            true
            always

          • 0
            Прошу прощения, никак не совладать с парсером. В общем, нужно создать child элемент snapshots и в нем 2 child элемента enabled (значение true) и updatePolicy (значение always).

            snapshots
            — enabled:true
            — updatePolicy:always
            /snapshots
            • 0
              Да, да.
              Я как раз и имел ввиду:

              <updatePolicy>always</updatePolicy>


              Хотите, можно идти таким путем. Я просто его не выбираю.

              На мой взгляд, выявлением проблемы в коде snapshot-зависимости, должна заниматься билд-конфигурация самой зависимости. А не проекта, где ее используют. Разделяй и властвуй.

              • +1
                Дело в том, что проблема не всегда в коде snapshot-зависимости :) Проблема может быть и в том, что ваш проект использует какой-нибудь класс или метод, который был удален или переименован. Конечно, по-хорошему, API библиотек не должен меняться, однако это не всегда так.

                И пример из практики. Предположим, есть ядро системы и есть различные модули. Ядро разрабатывает одна команда, модули — другие команды. Модули зависимы от ядра (используют его библиотеки). При этом нужно поддерживать консистентное состояние — trunk модулей должен собираться с trunk'ом ядра. Сборка модулей со снэпшотом trunk'а ядра как раз и позволяет следить за этим.

                Если вы используете библиотеку не вашего проекта, то, конечно, стоит десять раз подумать, прежде чем писать в слово SNAPSHOT.
                • +1
                  Пример жизненный и понятный. Это как раз то слепое пятно, где от снепшотов не обойтись.
                  В своей практике я стараюсь держать ядро и модули в одном maven-проекте для обеспечения постоянной связанности. До тех пор, пока ядро не будет достаточно стабильно, чтобы перейти на раздельные проекты и систему релиз-зависимостей. Но я понимаю, что не всегда это возможно.

                  По поводу библиотеки не своего проекта, полностью с вами согласен.
                  • 0
                    До некоторого момента так и было (ядро и модули были одним целым). Однако, разделение проекта на составные части дало гораздо больше преимуществ, чем недостатков. Правда, я думаю это в первую очередь связано с тем, что речь идет о крупном проекте (около 1 млн строк кода).
          • 0
            Вместо

            <updatePolicy>never</updatePolicy>

            читать

            <updatePolicy>always</updatePolicy>

            Прошу прощения за оплошность.
          • 0
            Да, ситуации бывают. Но с другой стороны есть отличия SNAPSHOTов от новой версии. Все-таки автоматическое подсасываение нового кода может быть и правильным путем при активной разработке, нежели постоянный клич — «пофиксал, инкрементим версию». Серьезный nicht при работе со snapshot'ами — это смена API артефакта (удаление классов туда тоже относится естественно), вот тут как раз лучше версию инкрементить, с другой стороны как-то очень просто это правило нарушить.

            Но в принципе конечно — «обезьян с гранатой» нужно опасаться.

            Кстати
            Есть workaround, силой заставить Мавен проверять обновление снепшотов при каждой сборке.

            Достаточно собирать c ключом -U
            • 0
              да, точно. забыл про этот ключ. Спасибо.
    • 0
      По хорошему, при релизе вашего проекта и создании тега в VCS, в нем не должно быть snapshot-зависимостей. Цель тега как раз в том, чтобы зафиксировать состояние проекта. А в случае snapshot-зависимости это невозможно.
      • 0
        Да, именно поэтому мавен запрещает существование снепшот-зависимости при релизе.
      • 0
        Нет, мне не нужно релизить со SNAPSHOTами. Я хочу просто как раз зафиксировать версию SNAPSHOTовых зависимостей, чтобы в своем артефакте ссылатся на ту версию зависимости, которая использовалась в разработке.
      • 0
        в целом я согласен, но и с SNAPSHOT зависимостями можно зафиксировать состояние проекта — см lock-snapshots в maven-version-plugin
    • +1
      это вопрос в духе «как мне выстрелить себе в ногу?»
    • +1
      Каскадный релиз snapshot'ов разных библиотек?! Либо это не разные библиотеки, а модули одного проекта (и тогда maven-release-plugin зарелизит все модули «каскадно»), либо Вы недопонимаете, как важно беречь независимость разных библиотек, даже если их реализацией занимается одна команда.

      В текущем проекте я организовал следующий подход:
      Допустим имеется проект A-1.0-RC1-snapshot, который использует B-alpha-2-snapshot и С-alpha-7-snapshot.

      Прежде чем заморозить проект A, я замораживаю проект B:
      B-alpha-2-snapshot => B-alpha-2 => B-alpha-3-snapshot

      Далее прошу owner-a проекта C (это другая команда) также выпустить промежуточный релиз:
      С-alpha-7-snapshot => С-alpha-7 = С-alpha-8-snapshot

      После того как все зависимости стабилизированы и заморожены можно замораживать проект A:
      A-1.0-RC1-snapshot => A-1.0-RC1 => A-1.0-RC2-snapshot

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

    • 0
      попытайтесь использовать опцию allowTimestampedSnapshots
  • 0
    Я такого тулза не знаю.
    Но, насколько я понимаю структуру Мавена, если такая тулза и есть, то она написана руками энтузиастов, которым нужен такой же юзкейс как вам. Ничего официального.

    Сам я против SNAPSHOT зависимостей в проектах. Если интересно, могу понудеть «почему».
  • 0
    Огромное спасибо! Попытаюсь навести порядок в своей системе.
    • 0
      Рад помочь!
  • 0
    все таки Maven это ад и зло… JVZлу надо поотрывать руки и засунуть… чего-то я замечтался… Ребят, я вам очень горячо рекомендую посмотреть на Gradle. Тут уже про него писали когда-то, но если есть конкретные вопросы буду рад ответить.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.