Как мы делали сборки

в 4:43, , рубрики: ant, gtd, Hudson, java, maven, nexus, интеграция, разработка, сборка, метки: , , , , ,

Введение

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

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

В этой статье приведен опыт внедрения сборок с помощью maven, поэтому базовые знания по maven необходимы. Для быстрого старта будет достаточно изучения сайта http://www.apache-maven.ru, для более детального освоения темы — стоит обратиться к первоисточнику http://maven.apache.org

Цель статьи не научить пользоваться майвеном, а показать опыт внедрения с положительными и отрицательными решениями. Обладая этими знаниями, гораздо проще и быстрее внедрить сборки maven у себя в команде.

Почему не ant?

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

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

Окружение

Сначала стоит рассказать о структуре наших проектов и подходах к разработке. Это очень важно, в силу того, что проект развивается уже много лет и имеет структуру, не соответствующую maven-webapp-artifact

И так, у нас имеется множество модулей, хранящихся в одном SVN репозитарии. Каждый модуль имеет основную ветку разработки (trunk), а также метки (tags) и ветки (branches) по необходимости. Большинство модулей содержат как java-классы, так и jsp-страницы, скрипты, графику и пр. Большинство модулей являются самостоятельными веб-приложениями и могут работать независимо. Примеры модулей: core, soap, workflow, addons, common… Их я насчитал порядка 100.

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

Необходимые модулю библиотеки хранятся в папке lib прямо в репозитарии, для веб-ресурсов имеется своя папка sx-war. А также стандартные папки src и test.

Как мы делали сборки

Естественно, почти все модули зависят от core, и друг от друга, по необходимости.

Еще есть база данных с мета-информацией. На основе нее взаимодействуют модули, строится вся работа с пользователем. Например, блок публикации — тоже является единицей мета-информации и хранится в БД. То есть помимо самих данных, в базе хранятся настройки работы с этими данными. Они настраиваются в консоли, которая тоже построена на блоках публикации. Есть возможность переносить мета-информацию с помощью пакетов обновлений.

В идеале, каждый модуль имеет свой общий пакет с мета-информацией. БД для core самая легкая БД и может расширяться любым модулем. Ядро — почти application-server, только без возможности динамической загрузкивыгрузки многофункциональных модулей. Не все модули имеют отделенную мету, но мы к этому стремимся :)

До нашей эры

Ходит у нас такая фраза. Означает, что «это» было очень давно, еще до начала нашей работы в компании. Расскажу, как проходила настройка сборки до меня. Мне тоже пришлось потратить немало времени на настройку в первый раз.

И вот вы устроились к нам на работу. Идет второй день. Первый ушел на просмотр скринкастов по подсистемам и тренировки работы с мета-информацией. Надо настроить сборку. Хорошо, если в базе знаний по проекту есть информация по необходимым модулям для проекта. Часто эта информация не актуальна (забывают обновлять или обновляют от случая к случаю). Информации о зависимостях самих модулей не найти, а та что есть, тоже устаревает.

Из SVN извлекаются trunk-версии всех необходимых модулей, создается проект в IDE. А дальше самое сложное: настроить импорт всех необходимых библиотек и связи модулей. Все еще осложняется, когда надо исключать одни версии библиотек из одних модулей и использовать другие. Настроить сборку всех модулей в одну папку. Далее настроить сервер веб-приложений (используется tomcat).

И вот проект настроен, затем надо настроить второй, третий, версию первого (использовать branches для работы над ошибками). Разработчику становится не по себе.

Поменялись зависимости, сломались сборки у всех. А администраторы используют ant для создания production-сборок — у них тоже все ломается. Часто возникают проблемы из-за разных сборок у администратора и разработчика.

Первая попытка

У нас не было специальных людей, отвечающих за сборку, как не было и хороших знаний maven. Поэтому первая попытка внедрения maven на проектах провалилась.

Родительский pom.xml был помещен в отдельную папку репозитария. После чего в отдельной проектной папке настраивались svn-externals и включались все необходимые проекту модули, плюс родительский pom.xml в отдельной папке:

Как мы делали сборки

Классический подход с использованием иерархии модулей в единой структуре SVN нам не подошел, потому что каждый модуль используется в десятках проектов. А не только в одном единственном. А любые манипуляции с parent pom на проектах нежелательны. К тому же фиксация версии любого модуля автоматически отразится на всех проектах.

Все необходимые библиотеки не искались на http://search.maven.org, потому что их очень много и все они обезличены в репозитарии (в папке lib хранятся библиотеки без версий. Например axis.jar, jtds.jar, velocity.jar). Поступили просто: все библиотеки с помощью скрипта загрузили в Nexus. GroupId и ArtifactId были заданы по маске sx.<имя библиотеки> (sx.axis).

Вот как выглядел типичный pom.xml модуля:

Базовый pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<!-- Родительский проект -->
	<parent>
		<groupId>sx.config</groupId>
		<artifactId>project-settings</artifactId>
		<version>2.0.1</version>
		<relativePath>project-settings/pom.xml</relativePath>
	</parent>
	<!-- Координаты -->
	<groupId>sx.builds</groupId>
	<artifactId>wfBuilder</artifactId>
	<version>2.0.1</version>
	<packaging>pom</packaging>	
	<!-- Репозиторий плагинов -->
	<pluginRepositories>
		<pluginRepository>
			<id>sx-repository</id>
			<url>http://v5-neptun/nexus/content/repositories/sx-repository</url>
		</pluginRepository>
	</pluginRepositories>
	<!-- Репозиторий Зависимостей -->
	<repositories>
		<repository>
			<id>sx-repository</id>
			<url>http://v5-neptun/nexus/content/repositories/sx-repository</url>
		</repository>
	</repositories>
	<!--URL SVN-->
	<scm>
		<connection>scm:svn:svn://svn-server/buildsMaven/trunk/workflowBuild</connection>		
	</scm>	
	<!--Модули-->		
	<modules>		
		<module>core</module>
		<module>workflow</module>
	</modules>		
</project>

Видно, что необходимо менять в нем список модулей для каждого проекта.

Во время сборки, каждый модуль собирался в отдельный war, а потом распаковывался в общий деплой с помощью maven-overlay.

Причины провала

Полностью перечеркнул преимущества maven подход использования svn-externals. Это свойство не позволяет однозначно зафиксировать код всех модулей без правки свойств svn папки вручную. Это становится практически нереально при наличии 10-15 модулей и выпуске патча для некой версии. А так хотелось использовать maven release plugin.

Плюсы

  • Все собирается «здесь и сейчас». Разработчик не зависит от периода обновления SNAPSHOT-зависимостей (сборки из основной ветки разработки);
  • Разработчик может вносить изменения сразу в несколько модулей (сомнительный пункт, потому что нарушается инкапсуляция модулей);
  • Виден весь набор необходимых модулей;
Минусы

  • Полная сборка всех модулей — длительная процедура;
  • Нельзя нормально зафиксировать версию кода;
  • Все библиотеки не берутся из публичного репозитория, добавление же новой — это отдельная процедура;
  • Возможен jar-hell, когда кто то решит использовать библиотеку из публичного репозитория и она уже имеется в корпоративном (в деплой попадут обе версии, а то и три!). Это происходит, потому что изменяется GroupId:ArtifactId:Version;
  • Код вроде бы хранится в одной папке SVN, а вычекивать надо совсем другую (с svn-externals). Не прозрачно — для эстетов;

Вторая попытка

Вторая попытка внедрения сборок развивалась в несколько итераций.

Итерация 1

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

Также был модифицирован родительский pom.xml в соответствии с задачами. Все модули используют его в качестве базового:

Новый базовый pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <!-- Координаты -->
  <groupId>sx.config</groupId>
  <artifactId>main-settings</artifactId>
  <version>1.1-SNAPSHOT</version>
  <packaging>pom</packaging>

  <!--URL SVN-->
  <scm>
    <connection>scm:svn:svn://svn-server/maven/main-settings/trunk</connection>
  </scm>

  <!-- Репозиторий плагинов -->
  <pluginRepositories>
    <pluginRepository>
      <id>sx-repository</id>
      <url>http://v5-neptun/nexus/content/repositories/sx-repository</url>
    </pluginRepository>
  </pluginRepositories>

  <!-- Репозиторий Зависимостей -->
  <repositories>
    <repository>
      <id>sx-repository</id>
      <url>http://v5-neptun/nexus/content/repositories/sx-repository</url>
    </repository>
    <repository>
      <id>releases-repository</id>
      <url>http://v5-neptun/nexus/content/repositories/releases</url>
    </repository>
    <repository>
      <id>snapshots-repository</id>
      <url>http://v5-neptun/nexus/content/repositories/snapshots</url>
    </repository>
    <repository>
      <id>central-repository</id>
      <url>http://v5-neptun/nexus/content/repositories/central</url>
    </repository>
  </repositories>
  <profiles>
    <profile>
      <id>production</id>
      <!-- На ФТП публикуются только конечные сборки продуктов. Модули публиковать не нужно -->
      <distributionManagement>
        <repository>
          <id>ftp-repository</id>
          <url>ftp://ftp.sample.com/builds</url>
        </repository>
      </distributionManagement>
    </profile>

    <profile>
      <id>development</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <!-- Этот репозиторий указывается только для дочерних модулей -->
      <distributionManagement>
        <snapshotRepository>
          <id>snapshots-repository</id>
          <url>http://v5-neptun/nexus/content/repositories/snapshots</url>
        </snapshotRepository>
        <repository>
          <id>releases-repository</id>
          <url>http://v5-neptun/nexus/content/repositories/releases</url>
        </repository>
      </distributionManagement>
    </profile>
  </profiles>

  <!-- Настройки сборки -->
  <build>
    <defaultGoal>package</defaultGoal>
    <!--Исходный код -->
    <sourceDirectory>src</sourceDirectory>
    <testSourceDirectory>test</testSourceDirectory>
    <!--Ресурсы -->
    <resources>
      <resource>
        <directory>src</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.sql</include>
          <include>**/*.xml</include>
        </includes>
      </resource>
    </resources>

    <extensions>
      <!-- Enabling the use of FTP -->
      <extension>
        <groupId>org.apache.maven.wagon</groupId>
        <artifactId>wagon-ftp</artifactId>
        <version>1.0-beta-6</version>
      </extension>
    </extensions>

    <plugins>
      <!--Сборка -->
      <!--Синтаксический сахар для идеи -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <!-- У нас не стандартные директории -->
          <warSourceDirectory>sx-war</warSourceDirectory>
          <attachClasses>true</attachClasses>
        </configuration>
      </plugin>

      <!--Задаем опции выделения памяти, на больших модулях может выпадать out of memory-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
        <configuration>
          <minmemory>256m</minmemory>
          <maxmemory>512m</maxmemory>
        </configuration>
      </plugin>

      <!-- Настройки Ресурсов -->
      <plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.4.3</version>
        <configuration>
          <encoding>cp1251</encoding>
        </configuration>
      </plugin>
      <!-- Версия плагина для вычекивания -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-scm-plugin</artifactId>
        <version>1.4</version>
        <configuration>
          <username>${SvnUsername}</username>
          <password>${SvnPassword}</password>
        </configuration>
      </plugin>
      <!-- Плагин выпуска релизов модуля -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-release-plugin</artifactId>
        <configuration>
          <tagBase>svn://gelios/cms/maven/main-settings/tags</tagBase>
          <branchBase>svn://gelios/cms/maven/main-settings/branches</branchBase>
          <preparationGoals>clean install</preparationGoals>
          <goals>deploy</goals>
          <autoVersionSubmodules>true</autoVersionSubmodules>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Итерация 2

Во все модули проекта внедрили плагин выпуска версий. Для этого в каждый модуль были добавлены блоки:

Подключение плагина выпуска релизов

 <!-- Путь до модуля в SVN. Меняется при выпуске версий автоматически -->
 <scm>
   <connection>scm:svn:svn://svnserver/module/trunk</connection>
 </scm>

 <build>
   <plugins>
     <!-- Плагин выпуска релизов модуля -->
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-release-plugin</artifactId>
       <configuration>
         <!-- Пути в SVN для хранения версий модуля -->
         <tagBase>svn://svnserver/module/tags</tagBase>
         <branchBase>svn://svnserver/module/branches</branchBase>
         <preparationGoals>clean install</preparationGoals>
         <goals>deploy</goals>
         <autoVersionSubmodules>true</autoVersionSubmodules>
       </configuration>
     </plugin>
     ...
   </plugins>

Были мысли вынести эту конфигурацию в базовый pom.xml, и использовать свойство ArtifactId-Version, но не во всех проектах это значение совпадает с названием папки в репозитарии. Поэтому мысль была отложена до лучших времен. Первым версию получил как раз базовый для всех модулей pom.xml.

В базовый pom.xml добавили новый профиль: production. Он закачивает сборку на корпоративный FTP и может дальше может распространяться на проекты.

Итерация 3

После неприятного столкновения с jar-hell, решено было привести библиотеки в порядок во всех сборках. А это было не просто, потому что только в ядре оказалось больше 100 библиотек и почти все без версий. Каждую надо было найти в публичном репозитарии, “вычислить” версию. Вскрылись библиотеки “сюрпризы” с внутренними сборками и патчами, измененными версиями. Их мы бережно поместили в maven-репозиторий с сохранением координат артифакта, но с припиской _custom в Version.

В качестве репозитория у нас используется Nexus. На нем был настроен шлюз к публичным репозиториям с кешированием. Появилась единая точка входа: корпоративный maven-репозиторий. До этого их было больше трех: библиотеки, релизы, ежедневные сборки и остальные публичные репозитории. Все требовали авторизацию. Три похожих репозитория в каждом pom.xml, да еще параметры доступа в settings.xml вызывали постоянную путаницу.

Делали попытку оставить ссылку на репозиторий только в базовом pom.xml. Это вполне реально, но при первой сборке любого модуля майвен должен загрузить базовый модуль из репозитория, а его координаты неизвестны. Можно это сделать вручную, после этого все отлично работает. Отказались из-за непрозрачности этого действия.

Итерация 4

Настроили еженощные сборки всех базовых и проектных модулей в системе непрерывной интеграции Hudson. С ними не возникло проблем, потому что модули теперь полностью независимы. Нет никакой разницы, публичная это библиотека или проектный модуль. Сборки на основе maven запускаются после каждого изменения кода в SVN и успешные артифакты попадают в репозитарий Nexus.

Со временем, настроили каскадную сборку модулей в задачах, а также деплой на разработческие сборки и сборки для тестирования. Деплой выполняется с помощью достаточно простых ant-скриптов. Они извлекают из Nexus последнюю сборку модуля (dependency:get), распаковывают ее на сервере и перезапускают сервер приложений (сами скрипты можно найти в конце статьи).

Плюсы

  • Можно работать с каждым модулем в отдельности;
  • Фиксируя версии всех модулей-зависимостей можно зафиксировать весь код проекта;
  • Непрерывная интеграция избавляет от рутинной работы и позволяет быстрее переносить функционал в продуктивную среду и к заказчику;
  • Выпуск версии модуля полностью контролируем и достаточно прост;
  • Появилась возможность вести разработку только с одним конкретным модулем. Ведь общий деплой нескольких модулей может влиять на конечное поведение кода;
  • Благодаря Hudson, всегда видно, кто сломал сборку;
  • Вообще не надо настраивать проекты, IDE все сама делает. Надо только запустить сборку (да и mvn package достаточно);
  • Всегда ясно, от какого кода зависит модуль. А раньше всегда собирался trunk со своими ошибками, битыми коммитами и пр.;
Минусы

  • В IDE нет общего проекта со всеми модулями, поэтому внесение изменений сразу в несколько модулей требует отдельной работы с каждым модулем. Это конечно не особо удобно, но концептуально правильно, потому что любой модуль должен быть самодостаточен;
  • Нельзя выпустить все версии компонентов каскадно, при выпуске основного продукта

Развиваем решение

В процессе работы, в сборки вносятся правки. Для тех, кто все еще собирает «по-старинке» заведена страница в базе знаний, которая содержит зависимости модулей. Эта информация обновляется на основе pom.xml модуля каждую ночь отдельной задачей в Hudson (не забываем о DRY).

Для ускорения сборки модулей была исключена лишняя работа во время сборки. Например, мы собираем модуль soap, который зависит от core. Код core попадает в конечную сборку soap. Далее собирается project, который зависит от core и soap. soap в отдельности не используется, поэтому из его сборки исключается core, а в project он включается. Тем самым мы ускоряем сборку. Для возможности сборки soap с включением зависимого core был добавлен профиль сontext, который запускается при необходимости.

Можно заметить, что все модули встречаются в pom.xml дважды: одна запись используется при компиляции, вторая означает, что модуль попадает в деплой. Чтобы не подменять версии модулей по всему pom.xml, все версии корпоративных модулей были вынесены в отдельные свойства и располагаются в самом начале файла. Это снижает вероятность ошибки при переходе на другую версию:

Использование свойств для хранения версий

<!-- Версии компонент и другие свойства.
     В самом начале, чтобы проще искать -->
 <properties>
   <sx.core>4.5-SNAPSHOT</sx.core>
 </properties>
 ...
 <profiles>
   <profile>
     <!--Профиль сборки контекста-->
     <id>context</id>
     <dependencies>
       <dependency>
         <groupId>sx.core</groupId>
         <artifactId>core</artifactId>
         <version>${sx.core}</version>
         <type>war</type>
         <!-- Модуль попадет в деплой через maven-overlay -->
         <scope>runtime</scope>
       </dependency>
     </dependencies>
   </profile>
 </profiles>

 <!-- Настройка зависимостей -->
 <dependencies>
   <dependency>
     <groupId>sx.core</groupId>
     <artifactId>core</artifactId>
     <version>${sx.core.core}</version>
     <classifier>classes</classifier>
     <!-- Модуль используется только для сборкикомпиляции -->
     <scope>provided</scope>
   </dependency>
   ...
 </dependencies>

ant-cкрипты для работы с maven-артифактами

Заключение

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

Новые разработчики не тратят много времени на настройку сборок. А это только радует.

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

Новые наработки попадают на контексты разработки в течение суток, а значит и к заказчику они попадают быстрее. Сложно уловимые ошибки интеграции тоже стали проявляться, а значит, и исправляться, быстрее.

Автор: VaiMR

Поделиться

* - обязательные к заполнению поля