MyTetra Share
Делитесь знаниями!
Туториал полезный по Maven
Время создания: 31.08.2017 20:57
Текстовые метки: Knowledge
Раздел: Maven
Запись: xintrea/mytetra_db_mcold/master/base/15042017794xtq9g375p/text.html на raw.githubusercontent.com

От велосипеда к Maven

Java

Из песочницы

Так уж сложилось, что до недавнего времени все проекты, написанные мною на Java я собирал, кхм, за меня собирал NetBeans. И меня такой расклад вещей вполне устраивал: после сборки всего проекта всё аккуратно складывалось в директорию dist со всеми подвязанными библиотеками, оставалось накидать туда пользовательской документации, необходимых native-библиотек (например от Firebird) и в путь, т.е. всё в архив. Когда то я делал это вручную, потом велосипедом, а потом уже Maven'ом. Под катом находится история о том, как же я пришел в стан maven и что из этого получилось.

Интересные вещи начали твориться, когда мне захотелось обернуть явовский jar-ник в exe-файл, как-то смотреть приятнее на него, чем на bat-ник (хотя бы потому что можно иконку прикрутить :)), а потом ещё и документация чтоб сама копировалась, а потом чтоб в архив всё собиралось, а потом и номер версии чтоб присутствовал в названии архива, а потом… а потом… в общем понеслось. Закончились эти хотелки написание велосипеда, добавлением к названию проекта префикс builder как имени моего велосипеда и написанием небольшого кода, выполняющего всю работу за меня (кроме, правда что сборки проекта NetBeans'ом). А номер версии, о ужас, вообще вытягивался из строковой константы где-то в исходниках. Понятное дело, на универсальность мой инструмент не претендовал, а вышеописанные хотелки обещали повторяться и дальше для других проектов.

И вот я познакомился с Maven. Случайно почитал о нем на одному русско-язычном ресурсе, понял, что мои задачи он решит, но до конца не понимал, зачем использовать именно его, чем велосипед не крут? Полистал форумы в интернете, благо там нашлись такие же непонимающие как и я, которым объясняли, объясняли, вроде убедили. Началось изучение, трудное, с учетом моего знания английского, а вся документация именно на этом языке. Очень помогли статьи с хабра: Apache Maven — основы и Maven — автоматизация сборки проекта. Последней каплей принять правильное решение оказалось воспоминание, как приходилось заново линковать библиотеки для проекта на Java, склонированного с Mercurial-репозитория. Хорошо хоть, немного их было и разработчик прежних проектов сидел напротив, спросить можно было :).

Итак, перед глазами витала цель: надо всё сделать красиво, чтоб всё собиралось за меня :) А лень, как говорится, двигатель прогресса. В итоге сформировались следующие задачи:
— сборка всего проекта как в NetBeans (со всеми библиотеками);
— автоматизация нумерации сборок без необходимости лезть в код;
— оборачивание jar в exe-файл;
— синхронизация с Mercurial-репозиторием (вообще хотелось создавать сборки на основе номера коммита);
Благо, поддержка Maven в NetBeans идет из коробки;
— добавление пользовательской документации и прочих нужных файлов (например, файлов .properties вне jar-архива).

Что входит в понятие «как в NetBeans»: скопировать все ресурсы (изображения, .properties-файлы), подтянуть все связанные библиотеки, прописать необходимую информацию в manifest. Стандартно maven собирает в jar-архив только .class-файлы и ничего лишнего, а манифест скуден до безобразия. 

В этой статье я не буду рассказывать о фазах жизненного цикла проекта. Посмотреть можно здесь Introduction to the Build Lifecycle и в статьях, приведенных выше.

Итак, все мы (ура, уже и я придачу :)) знаем, что вся информация по сборке проекта хранится в файле pom.xml. Там и зависимости (артефакты проекта и plugin'ов), и конфигурация этих самых plugin'ов, что и как нужно сделать, куда положить, какие ещё манипуляции провести. Каждый плагин конфигурировался в своем теге <plugin/> с указанием, конечно же, его версии, имени, фазы жизненного цикла. Все конфигурации помещались в общий тег <plugins/> внутри тэга <build/>, отвечающего за сборку всего проекта.

Разберемся по пунктам.

Сборка проекта как в NetBeans


Что сюда входит? Ага, уже определились: копирование всех ресурсов из директории src (там лежат исходники), копирование в итоговую директорию (target) всех зависимостей, добавление информации в manifest.

Копирование всех зависимостей осуществлялось плагином maven-dependency-plugin, ниже его конфигурация:

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-dependency-plugin</artifactId>

<configuration>

<outputDirectory>${project.build.directory}/lib/</outputDirectory>

<overWriteReleases>false</overWriteReleases>

<overWriteSnapshots>false</overWriteSnapshots>

<overWriteIfNewer>true</overWriteIfNewer>

</configuration>

<executions>

<execution>

<id>copy-dependencies</id>

<phase>package</phase>

<goals>

<goal>copy-dependencies</goal>

</goals>

</execution>

</executions>

</plugin>



Обращаем внимание, что все они складываются в директорию lib (как в NetBeans :)). При этом указываем, что перезаписываем библиотеки с наличием более новых версий, не перезаписываем текущие версии и не перезаписываем зависимости без окончательной версии (snapshot). Также указывается, на какой фазе происходит данная операция: package. В принципе, тут без разницы, когда гости пожалуют.

Информация в манифест добавлялась следующим образом:

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-jar-plugin</artifactId>

<configuration>

<archive>

<manifest>

<addClasspath>true</addClasspath>

<classpathPrefix>lib/</classpathPrefix>

<classpathLayoutType>simple</classpathLayoutType>

<mainClass>com.khmb.block_v2.Block_v2App</mainClass>

</manifest>

<manifestEntries>

<Version>${buildNumber}</Version>

</manifestEntries>

</archive>

</configuration>

</plugin>



В тэге <classpathPrefix/> как раз и указывается, что библиотеки тянутся из директории lib. Тэг <classpathLayoutType/> со значением simple говорит сборщику, что jar-ники следует скидывать в одну кучу. Есть ещё значение repository, тогда библиотеки будут складываться как в репозитории maven, т.е. со всеми поддиректориями с именами пакетов, версий, названий библиотек. 
Стоит отметить переменную ${buildNumber}, о ней рассказано ниже.
Помимо зависимостей, в манифесте указывается класс, с которого будет запускаться программа.
Описание всех параметров используемого плагина лежит здесь: 
Maven JAR plugin, а там много интересного.

Далее требовалось перед сборкой в jar-архив скидать все ресурсы (картинки и .properties-файлы) в директорию со скомпилированными .class-файлами.

<plugin>

<artifactId>maven-resources-plugin</artifactId>

<version>2.5</version>

<executions>

<execution>

<id>copy-resources</id>

<phase>validate</phase>

<goals>

<goal>copy-resources</goal>

</goals>

<configuration>

<outputDirectory>${project.build.outputDirectory}/com/khmb/${project.name}</outputDirectory>

<resources>

<resource>

<directory>${project.build.sourceDirectory}/com/khmb/${project.name}</directory>

<filtering>true</filtering>

<includes>

<include>**/*.properties</include>

</includes>

</resource>

<resource>

<directory>${project.build.sourceDirectory}/com/khmb/${project.name}</directory>

<includes>

<include>**/*.png</include>

</includes>

</resource>

</resources>

</configuration>

</execution>

</executions>

</plugin>



Никаких хитрых копирований, просто прошу maven положить всё точно также, как и было. Но есть одна особенность: если png-файлы (а в проекте используются картинки только в этом формате) копируются просто так, то файлы .prioperties копируются фильтрующе, т.е. плагин посмотрит внутрь них и если что нужно заменить переменными maven'а, заменит. На это указывает параметр тэга <filtering/> — true. Поэтому-то ресурсы и разнесены по разным тэгам <resource/> — картинки фильтровать бессмысленно.

Компиляция проекта регулируется следующим плагином:

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>2.3.2</version>

<configuration>

<source>${jdkVersion}</source>

<target>${jdkVersion}</target>

</configuration>

</plugin>



О переменной ${jdkVersion} чуть попозже.https://www.youtube.com/

Автоматизация нумерации сборок без необходимости лезть в код


А что же там такого, внутри этих файлов .properties? Всё очень просто, в одном из них лежит номер версии приложения, который тянется в runtime'е и отображается в окошке с информацией об этом приложении (так называемый about).

Application.version = ${buildNumber}



И откуда взялся этот билд-намбер? Своим появлением на свет он обязан плагину buildnumber-maven-plugin. Роль плагина заключается в формировании номера версии, абсолютно любого. Я же решил включить туда номер версии моей программы (а точнее артефакта), плюс дату сборки:

<plugin>

<groupId>org.codehaus.mojo</groupId>

<artifactId>buildnumber-maven-plugin</artifactId>

<version>1.0</version>

<configuration>

<format>{0}-{1,date,yyyyMMdd}</format>

<items>

<item>${project.version}</item>

<item>timestamp</item>

</items>

<doCheck>true</doCheck>

<doUpdate>true</doUpdate>

</configuration>

<executions>

<execution>

<phase>validate</phase>

<goals>

<goal>create</goal>

</goals>

</execution>

</executions>

</plugin>



Номер версии собирается из нескольких частей (тэг <format/>), каждая из которых заключается в фигурные скобки и формируется согласно описанию из 
MessageFormat языка Java. Каждой часть соответствует тэг <item/>, указывающий, какое значение должно быть подставлено. Кстати говоря, можно вставить в него текст «buildNumber», только каждый раз при сборке будет генерироваться номер этой самой сборки (значение хранится в файле buildNumber.properties) в корне директории проекта. Я отказался, ведь номер может быть очень большим ввиду постоянных проверок работоспособности своей программы, сколько раз приходится его запускать.

Оборачивание jar в exe-файл


Во времена моего велосипеда я наткнулся на хорошую программку 
launch4j, которая мало того, что оборачивала jar в exe, так она ещё и позволяла добавить иконку приложению (а без неё exe-шник очень похож был на какой-нибудь вирус или старую добрую dos-программу), информацию об авторе, версии и прочем, указать, какую версию jre следует использовать, откуда на неё ссылаться (можно ведь и portable-версию с собой таскать), да в общем много чего там ещё можно сделать. Все настройки хранятся в xml-файле, его описание лежит на сайте программы. Велосипедом я формировал этот xml-файл и передавал его путь в качестве параметра вызываемого launch4j.exe. На выходе получал exe-файл, который был привязан к зависимостям также, как и его jar-собрат (т.е. должен лежать там же, ссылаться на те же зависимости, если не указаны какие-либо особые параметры конечно же). Каково было моё счастье, что эта полезная программка существует и в виде плагина к maven. Конфигурация плагина практически полностью соответствует его старшему брату-программе, за исключением некоторых особенностей, которые можно подглядеть вот здесь. Кстати говоря, можно собирать бинарники не только ОС Windows, но и под Linux. Ниже приведен мой конфиг.

<plugin>

<groupId>com.akathist.maven.plugins.launch4j</groupId>

<artifactId>launch4j-maven-plugin</artifactId>

<executions>

<execution>

<id>l4j-clui</id>

<phase>package</phase>

<goals>

<goal>launch4j</goal>

</goals>

<configuration>

<headerType>gui</headerType>

<outfile>target/${exeFileName}.exe</outfile>

<jar>target/${project.artifactId}-${project.version}.jar</jar>

<errTitle>${product.title}</errTitle>

<icon>favicon.ico</icon>

<classPath>

<mainClass>com.khmb.block_v2.Block_v2App</mainClass>

<addDependencies>true</addDependencies>

<preCp>anything</preCp>

</classPath>

<jre>

<minVersion>${jdkVersion}.0</minVersion>

</jre>

<versionInfo>

<fileVersion>${project.version}.0</fileVersion>

<txtFileVersion>${project.version}</txtFileVersion>

<fileDescription>Программа для блокировки счетов в соответствии со списком</fileDescription>

<copyright>Copyright © 2011 ${product.company}</copyright>

<productVersion>${project.version}.0</productVersion>

<txtProductVersion>${project.version}</txtProductVersion>

<companyName>${product.company}</companyName>

<productName>${product.title}</productName>

<internalName>${exeFileName}</internalName>

<originalFilename>${exeFileName}.exe</originalFilename>

</versionInfo>

</configuration>

</execution>

</executions>

</plugin>



Я указал, какую версию jre следует использовать (соответствует переменной ${jdkVersion}), входной файл, выходной файл, какую иконку прикрутить, ну и информацию, которую можно глянуть при просмотре информации об exe-файле. Почему номер версии jre написан так: ${jdkVersion}.0? Всё очень просто, плагин launch4j требует номер версии в формате x.x.x[_x], а в другом месте файла pom.xml (в конфигурации плагина компиляции) требуется указать номер версии в формате x.x. Ну не следить же за идентичными параметрами по отдельности? Поэтому общая часть была вынесена в переменную (смотри в конце).

Кстати, чтобы плагин подтянулся из maven-репозитория в интернете (в центральном репозитории этого плагина нет) требуется указать ещё один:

<repositories>

<repository>

<id>akathist-repository</id>

<name>Akathist Repository</name>

<url>http://www.9stmaryrd.com/maven</url>

</repository>

</repositories>

<pluginRepositories>

<pluginRepository>

<id>akathist-repository</id>

<name>Akathist Repository</name>

<url>http://www.9stmaryrd.com/maven</url>

</pluginRepository>

</pluginRepositories>



Тэги <repositories/> и <pluginRepositories/> лежат внутри корневого тэга project, т.е. наравне с тэгом-описанием зависимостей и тэгом-описанием сборки.

Синхронизация с Mercurial-репозиторием


Изначально была задумка, чтобы плагин брал номер последнего коммита активной ветки и вставлял его в качестве номера сборки проекта. Но у меня так и не вышло, как это сделать. Буду очень благодарен тем, кто подскажет.
А вообще суть работы с репозиторием сводился к тому, чтобы перед сборкой всего проекта он получал последние изменения из своего репозитория. Тут мне помог плагин maven-scm-plugin. К тому же, была заявлена полная поддержка Mercurial, в отличие, скажем, от Git. Ну и здорово, потому что я привык именно к ртутному репозиторию :). А конфигурация следующая:

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-scm-plugin</artifactId>

<version>1.5</version>

<configuration>

<!--

<username>username</username>

<password>password</password>

//-->

<connectiontype>developerConnection</connectiontype>

</configuration>

<executions>

<execution>

<phase>validate</phase>

<goals>

<goal>update</goal>

</goals>

</execution>

</executions>

</plugin>



Параметры логина и пароля закомментированы по той причине, что у меня используется локальный репозиторий без необходимости в авторизации. Да, кстати, вне тэга <build/> указываются параметры, где находится наш репозиторий:

<scm>

<connection>scm:hg:file:///${project.basedir}</connection>

<developerConnection>scm:hg:file:///${project.basedir}</developerConnection>

<url>file:///${project.basedir}</url>

</scm>



Как, что и где подкручивать, описано опять-таки в официальной документации: 
SCM Implementation: Mercurial. Делается запрос на репозиторий, принимаются последние изменения, фиксируются и у нас последняя версия проекта (в рамках активной ветки конечно же).

Финиш


Осталось подвести финальную черту, включить в проект пользовательскую документацию, запаковать всё в архив и радоваться. За это отвечает плагин maven-assembly-plugin

<plugin>

<artifactId>maven-assembly-plugin</artifactId>

<executions>

<execution>

<id>assembly</id>

<phase>package</phase>

<goals>

<goal>attached</goal>

</goals>

<configuration>

<descriptors>

<descriptor>assembly.xml</descriptor>

</descriptors>

</configuration>

</execution>

</executions>

</plugin>



В документации к плагину настоятельно рекомендуется использование именно цели attached. Вся же конфигурация хранится в отдельном файле assembly.xml. Вот его содержимое:

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">

<id>bin</id>

<formats>

<format>zip</format>

</formats>

<fileSets>

<fileSet>

<directory>${project.basedir}</directory>

<outputDirectory>/</outputDirectory>

<includes>

<include>data.ini</include>

<include>ReleaseNotes.txt</include>

</includes>

</fileSet>

<fileSet>

<directory>${project.basedir}</directory>

<outputDirectory>/docs</outputDirectory>

<includes>

<include>User's guide.pdf</include>

</includes>

</fileSet>

<fileSet>

<directory>${project.build.directory}</directory>

<outputDirectory>/</outputDirectory>

<includes>

<include>${exeFileName}.exe</include>

<include>${project.artifactId}-${project.version}.jar</include>

<include>lib/**</include>

</includes>

</fileSet>

</fileSets>

</assembly>



Указываем, что нам нужно вложить из каких директорий (не забываем про exe-файл и подвязанные библиотеки), а в списке форматов указываем только zip, остальные в принципе в моем случае не нужны. Всё, на выходе получаем архив со всем необходимым внутри (главное ничего не забыть :)).

Переменные


Все конфигурации просто кишат этими переменными. Что ж, расскажем.
— ${project.basedir} — хранит в себе путь до корневой директории проекта на maven;
— ${project.build.directory} — обычно соответствует поддиректории target проекта на maven;
— ${project.build.outputDirectory} — соответствует директории внутри target, куда складываются скомпилированные .class-файлы. Обычно имеет имя classes, и именно из её содержимого собирается конечный jar-архив.
— ${project.name} — название нашего артефакта, берется из тэга <artifactId/>
— ${project.version} — версия артефакта, значение тэга <version/>

Остальные переменные определялись собственноручно, в тэге <properties/> внутри корневого тэга <project/> файла pom.xml. Вот его содержимое:

<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<exeFileName>block</exeFileName>

<product.title>Блокировка счетов</product.title>

<product.company>Название моей организации :)</product.company>

<product.year>2011</product.year>

<jdkVersion>1.6</jdkVersion>

</properties>



Замечу, что имя exe-файла указано без расширения, т.к. в конфигурации плагина launch4j иногда требуется указать полное имя, а иногда без расширения.

Вывод


Используqте maven! :) потратив раз на его изучение вы сэкономите время потом.
Так мой проект оказался в локальном maven-репозитории, но можно настроить его публикацию в удаленном репозитории (например, в сети организации). При клонировании же проекта из репозитория Mercurial все зависимости подтянутся автоматически — очень удобно. И разрабатывайте проект дальше, хоть в NetBeans, хоть в Eclipse, хоть в IntelliJ IDEA — кому как больше нравится.

PS: все зависимости подтягиваются из интернета, потом складываются в локальный maven-репозиторий и в дальнейшем берутся именно от туда. Каково было моё «счастье», что на работе конфигурация прокси maven'а была несовместима с NTLM-аутентификацией, поэтому многие зависимости приходилось скачивать вручную и класть в нужные поддиректории.

Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования