Вторая Жизнь С Maven

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

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

Но в какой-то момент отставание становится настолько значительным, что товар перестает быть конкурентоспособным и постепенно исчезает с рынка.

Именно такую ситуацию постепенного затухания мне довелось наблюдать.

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

когда проект построен.

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



Итак дано:

— Система, «ядро» которой состоит из набора dll и предоставляется разработчикам приложений без исходных кодов.

— Прикладной язык программирования, аналогичный Паскалю, но помимо стандартных возможностей Паскаля (из которых доступны не все) позволяет использовать объекты, реализованные в «ядерных» dll. Этот язык приложения компилируется в своего рода байт-код и выполняется отдельной dll «ядра».

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

office и изменению не подлежит. Весьма вероятно, что разработка и поддержание собственного языка программирования, подобного Паскалю и основанного на Delphi, могло быть плохой идеей, но 15-17 лет назад, когда все это началось, никто об этом не думал.

Преследовались и другие цели, которые в целом были успешно достигнуты.

Сама система представляет собой продукт, продаваемый заказчику, с сервером, клиентом и тонким клиентом.

Естественно, большинство клиентов не устраивает стандартный функционал системы, поэтому существует несколько отделов разработки приложений, которые «допиливают» систему под их нужды.

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

Взаимодействие и обмен опытом между группами катастрофически низки.

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

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

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

Чтобы исправить ситуацию, было решено организовать репозиторий (хранилище) и систему сбора проектов с использованием набора общих библиотек кода ( Пакеты ).

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



Но обо всем по порядку

Основная проблема, о которой я даже не подозревал в начале своих исследований, оказалась в том, что Packages должны содержать файлы не скомпилированные в байт-код, а исходные коды, ведь компиляция должна происходить именно под версию «ядра» для которым они будут использоваться.

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

Другими словами, используйте почти стандартный жизненный цикл Maven, когда .

classы сначала компилируются, а затем вместе с ресурсами упаковываются в jar. Только в моем случае Пакет вместо скомпилированных файлов был сформирован из исходных bpas, упакованных в zip.

Немного о том, как работает Maven

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

Более того, при попытке запустить какую-то фазу жизненного цикла, например «mvn compile», я фактически запускаю всю цепочку фаз от validate (проверки проекта) до компиляции, не пропуская ни одной.

Для некоторых фаз существуют так называемые плагины по умолчанию, которые будут вызываться несмотря на то, что в pom.xml (основном Maven-файле проекта) есть только заголовок и никаких инструкций по запуску плагинов.

Стоит отдельно отметить тот факт, что Maven — это полностью подключаемая система.

Другими словами, он практически ничего не умеет, кроме запуска плагинов, но на многое уже способен.

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

Этот абсолютно валидный пустой pom.xml, несмотря на свою пустоту, при получении команды mvn Deploy запустит Плагин для инициализации, компиляции, упаковки и развертывания исходников Java из папки src/main.

  
  
  
  
  
  
  
  
  
   

<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>com.mycompany.projectgroup</groupId> <artifactId>projectname</artifactId> <version>01.03.03-00</version> <build> </build> </project>

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

В моем случае мне пришлось отказаться от многих значений по умолчанию, поэтому pom.xml уже не выглядит таким скромным.



Построение нового жизненного цикла в pom.xml

Для реализации пакетов был выбран следующий жизненный цикл.

инициализировать (инициализация) — прочитайте настройки из файла конфигурации (свойство или ключ = значение) и добавьте их в тег свойств.

О теге свойств мы поговорим чуть позже.

генерировать-источники (генерация исходного кода) — Загрузите и разархивируйте все пакеты, являющиеся зависимостями данного пакета/проекта, в отдельный каталог для последующей компиляции вместе с исходными кодами текущего пакета/проекта.

компилировать (компиляция) — мы запускаем наш плагин компиляции для наших bpas, который определяет правильный порядок компиляции и запускает компилятор командной строки для нашего языка.

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

сборка (сборка) — пакет, состоящий из исходников bpas, упаковываем в zip, сохраняя структуру подкаталогов исходных файлов.

развертывать (в нашем случае загрузка в репозиторий) — Собранный на этапе сборки zip отправляется в локальный репозиторий Maven с добавленным в него pom.xml и другой информацией о пакете.

Эта процедура почти идентична обычному файлу jar развертывания, но со специальными параметрами.

чистый (очистка) – Этот этап не входит в общий жизненный цикл этапов сборки, а стоит несколько особняком, но поскольку он тоже модернизирован, о нем тоже стоит упомянуть.

Помимо стандартного удаления каталога, в котором находятся скомпилированные файлы.

(targetDirectory), необходимо было удалить мусор, который генерируется в процессе скачивания и распаковки пакетов зависимостей.



Общая структура pom.xml

Я условно делю pom.xml на две части: заголовок и сборку.

Заголовок состоит из идентификатора пакета (groupId, ArtifactId, Version), свойств (которые действуют как внутренние константы), указания локального репозитория (distributionManagement), указания локального репозитория плагина (pluginRepositories), указания локального репозитория.

репозиторий пакета (репозитории) и указание зависимостей этого пакета (dependents).

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

Так, например, мы можем решить хранить плагины отдельно от остального кода, а для доступа к пакетам в репозитории использовать http-доступ, при этом «деплоить» будем там, как в файловом хранилище.

Сборка (тег сборки) — это вторая часть pom.xml, в которой настраиваются особенности обработки той или иной фазы жизненного цикла различными плагинами с нестандартными настройками.

Кроме того, там настраиваются каталоги и параметры, которые будут участвовать в сборке проекта: sourceDirectory – каталог, в котором находятся исходники для компиляции.

FinalName – окончательное имя файла после упаковки в архив.

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

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



Реализация жизненного цикла в теге сборки

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



инициализировать
И здесь давайте немного отвлечемся и отдельно упомянем тег свойств.

Поясним, зачем он нужен и как его использовать.

Если очень грубо, то этот тег аналогичен объявлению констант, которые в дальнейшем будут использоваться в нашей программе (pom.xml).

Но ценности могут попасть туда тремя разными способами.

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

Самый низкий приоритет — для прямой записи свойств в тег свойств, например так:

<properties> <helloText>Hello my friend</ helloText > </properties>

Прямая установка параметров при запуске Maven имеет более высокий приоритет, что-то вроде «mvn –DhelloText=Hi Initialize» При запуске Maven с этим параметром исходное значение тега helloText будет заменено на значение, переданное в строке запуска для текущего сеанса, т.е.

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

Значения всех несуществующих констант представляют собой пустую строку.

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

Они также не сохраняются в pom.xml. Именно этот механизм мы будем использовать для переноса отдельных настроек сборки в файл свойств, содержащий «имя=значение».

Для этого используется плагин Properties-maven-plugin.

<build> … <plugins> … <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>properties-maven-plugin</artifactId> <version>1.0-alpha-1</version> <executions> <execution> <id>1</id> <phase>initialize</phase> <goals> <goal>read-project-properties</goal> </goals> <configuration> <files> <file>.

\build.conf</file> </files> </configuration> </execution> <execution> <id>2</id> <phase>pre-clean</phase> <goals> <goal>read-project-properties</goal> </goals> <configuration> <files> <file>.

\build.conf</file> </files> </configuration> </execution> </executions> </plugin> … </plugins>

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

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

Тег должен включать уникальный идентификатор (если выполняется более одного выполнения), а также может содержать отдельный тег конфигурации, приоритет которого выше всех остальных.



генерировать-источники
На этапе генерации исходников мы рекурсивно скачиваем из репозитория и распаковываем все нужные нам Packages, которые указаны в зависимостях.

Опять же, вам не нужно ничего делать самостоятельно.

Плагин все сделает за нас после указания правильных настроек.



<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.2.1</version> <executions> <execution> <id>unpackSources</id> <phase>generate-sources</phase> <goals> <goal>unpack</goal> </goals> </execution> </executions> <configuration> <workDirectory>${packagesPath}</workDirectory> </configuration> </plugin>

Дизайн ${packagesPath} означает, что вам нужно взять значение из тега «/project/properties/packagesPath».

Отдельно хотелось бы отметить, что с помощью плагина maven-сборка-плагин для распаковки считается устаревшим и не рекомендуется для использования в Maven 3. Вместо этого используйте плагин-зависимости maven с настройками, аналогичными указанным выше.

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



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

Пошаговая инструкция по написанию собственного плагина для Maven — это тема отдельной статьи, поэтому мы не будем сейчас на ней останавливаться.

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

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

maven-компиляция-плагин , призвание которого — компилировать исходники на Java (Без учёта возможности редактирования superPom.xml).

Итак, мои настройки плагина компиляции выглядят так:

<plugin> <groupId>com.companyname.utils</groupId> <artifactId>BPASCompilerPlugin</artifactId> <version>0.1.0.2</version> <executions> <execution> <phase>compile</phase> <goals> <goal>bpascompile</goal> </goals> </execution> </executions> <configuration> <protectionServer>${protectionServerName}</protectionServer> <protectionAlias>${protectionServerAlias}</protectionAlias> <bpasccPath>${pathToBPASCC}</bpasccPath> <binaryVersion>${env.BINARY_VERSION}</binaryVersion> </configuration> </plugin>

используемые параметры: ЗащитаСервер — сервер защиты, без которого невозможно запустить компилятор командной строки.

защитаПсевдоним — раздел используемой лицензии сервера защиты.

бпаскпуть — полный или относительный путь к компилятору командной строки.

двоичная версия — Версия, которая будет «встроена» в скомпилированную библиотеку.

Это не все настройки моего плагина, но как я уже сказал это тема для отдельной большой статьи.

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

сборка
При прохождении этапа сборки Maven настроен на запуск плагина maven-assembly-plugin по умолчанию.

Явно указав его запуск на этапе сборки в теге сборки, мы можем переопределить его настройки и заставить его работать за нас.

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



<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.2.1</version> <executions> <execution> <id>unpackSources</id> <phase>generate-sources</phase> <goals> <goal>unpack</goal> </goals> </execution> <execution> <id>packSources</id> <phase>assembly</phase> <goals> <goal>assembly</goal> </goals> </execution> </executions> <configuration> <descriptors> <descriptor>.

\src.xml</descriptor> </descriptors> <workDirectory>${packagesPath}</workDirectory> </configuration> </plugin>

Здесь мы видим второй раздел выполнения с id=packSources и настройкой .

\src.xml, необходимой для этого этапа.

src.xml содержит настройки упаковки исходных текстов.

На самом деле все эти настройки можно разместить прямо в теге дескриптора, но в pom.xml это может сильно загромождать.

Для полноты приведем пример src.xml.

<Эxml version="1.0" encoding="UTF-8"?> <!-- standart pack settings for bpas package--> <assembly> <id>src</id> <formats> <format>zip</format> </formats> <fileSets> <fileSet> <includes> <include>pom.xml</include> </includes> </fileSet> <fileSet> <directory>SOURCE</directory> <excludes> <exclude>.

svn</exclude> <exclude>**/*${packagesDirName}*/**</exclude> </excludes> </fileSet> </fileSets> </assembly>

packagesDirName — константа из файла /project/properties pom.xml. Отдельно хотелось бы отметить, что размещение настроек упаковки в отдельном файле позволило мне создать один конфиг упаковки для всех Пакетов, что крайне удобно.



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



<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>2.5</version> <configuration> <file>${project.build.directory}\${project.artifactId}-src.zip</file> <url>${project.distributionManagement.repository.url}</url> <repositoryId>${project.distributionManagement.repository.id}</repositoryId> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> <version>${project.version}</version> <packaging>zip</packaging> <pomFile>pom.xml</pomFile> </configuration> </plugin>

С этими ручными настройками maven-deploy-плагин позволяет загрузить любой файл (или даже группу файлов) в репозиторий Maven как действительную библиотеку (Пакет).

Теперь рассмотрим настройки по порядку.

файл — файл, который будет отправлен в репозиторий Maven в виде пакета.

URL — путь к репозиторию Maven идентификатор репозитория — идентификатор репозитория, в который будет производиться развертывание.

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

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

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

Сохранение исходного pom.xml выгодно по многим причинам, поэтому не будем брезговать этим вариантом.



чистый
Чистая фаза, как я уже сказал, не входит в стандартный жизненный цикл.

Его первоначальная задача — обеспечить, чтобы после выполнения команды mvn clean в проекте не оставалось никаких следов жизнедеятельности.

В нашем случае помимо стандартной папки targetSource (указанной в теге сборки) для успешной компиляции пакета/проекта также необходимо удалить все Packages, которые были «слиты» как зависимости.

Итак, настройки:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> <version>2.4.1</version> <configuration> <filesets> <fileset> <directory>${packagesPath}</directory> </fileset> </filesets> </configuration> </plugin>

каталог — собственно указание каталога, который необходимо удалить.

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

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

Учитывая, насколько сложно поначалу разобраться с отдельными ветками настроек, ниже я приведу полный текст 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>com.companyname.packages</groupId> <artifactId>String</artifactId> <version>0.1.0.1</version> <name>String</name> <url> http://maven.apache.org </url> <properties> <project.build.sourceEncoding>windows-1251</project.build.sourceEncoding> <packagesPath>${basedir}/SOURCE/${packagesDirName}</packagesPath> </properties> <distributionManagement> <repository> <id>spb-maven-repo</id> <name>spb-maven-repo</name> <url> file://\\SPB-FS\maven-repo </url> </repository> </distributionManagement> <pluginRepositories> <pluginRepository> <id>spb-maven-repo</id> <name>spb-maven-repo</name> <releases> <enabled>true</enabled> <updatePolicy>always</updatePolicy> <checksumPolicy>fail</checksumPolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> <checksumPolicy>fail</checksumPolicy> </snapshots> <url> http://spb-maven-repo </url> <layout>default</layout> </pluginRepository> </pluginRepositories> <repositories> <repository> <id>spb-maven-repo</id> <url> http://spb-maven-repo </url> </repository> </repositories> <dependencies> <dependency> <groupId>com.companyname.packages</groupId> <artifactId>Arithm</artifactId> <version>0.1.0.1</version> <type>zip</type> </dependency> <dependency> <groupId>com.companyname.packages</groupId> <artifactId>bUnit</artifactId> <version>0.9.0.0</version> <type>zip</type> </dependency> </dependencies> <build> <directory>USER</directory> <sourceDirectory>${basedir}/SOURCE</sourceDirectory> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>properties-maven-plugin</artifactId> <version>1.0-alpha-1</version> <executions> <execution> <id>1</id> <phase>initialize</phase> <goals> <goal>read-project-properties</goal> </goals> <configuration> <files> <file>.

\build.conf</file> </files> </configuration> </execution> <execution> <id>2</id> <phase>pre-clean</phase> <goals> <goal>read-project-properties</goal> </goals> <configuration> <files> <file>.

\build.conf</file> </files> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.2.1</version> <executions> <execution> <id>unpackSources</id> <phase>generate-sources</phase> <goals> <goal>unpack</goal> </goals> </execution> <execution> <id>packSources</id> <phase>assembly</phase> <goals> <goal>assembly</goal> </goals> </execution> </executions> <configuration> <descriptors> <descriptor>.

\src.xml</descriptor> </descriptors> <workDirectory>${packagesPath}</workDirectory> </configuration> </plugin> <plugin> <groupId>com.companyname.utils</groupId> <artifactId>CompilerPlugin</artifactId> <version>0.1.0.2</version> <executions> <execution> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> </executions> <configuration> <protectionServer>${protectionServerName}</protectionServer> <protectionAlias>${protectionServerAlias}</protectionAlias> <bpasccPath>${pathToBPASCC}</bpasccPath> <binaryVersion>${env.BINARY_VERSION</binaryVersion> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>2.5</version> <configuration> <file>${project.build.directory}\${project.artifactId}-src.zip</file> <url>${project.distributionManagement.repository.url}</url> <repositoryId>${project.distributionManagement.repository.id}</repositoryId> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> <version>${project.version}</version> <packaging>zip</packaging> <pomFile>pom.xml</pomFile> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> <version>2.4.1</version> <configuration> <filesets> <fileset> <directory>${packagesPath}</directory> </fileset> </filesets> </configuration> </plugin> </plugins> </build> </project>



Полученные результаты

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

  • фаза и цель, а также их связь друг с другом и плагинами.

  • создание архетипов — шаблона проекта для быстрого развертывания вашей конфигурации pom.xml и набора файлов по умолчанию.

  • Репозитории Maven и управление ими.

  • как написать и отладить собственный плагин для Maven.
  • как НЕ писать свой плагин на каждый «чих».

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

пакета/проекта.

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

Основными преимуществами такой сборки пакета являются:

  • простота добавления общего кода
  • простота добавления зависимостей и сборки проекта
  • прозрачность проекта (можно увидеть, что используется и каков реальный объем нестандартных доработок)
  • выявление циклических зависимостей между пакетами благодаря механизмам Maven.
  • автоматическое распространение исправлений общего кода.

  • простота использования среднестатистическим разработчиком/тестировщиком (достаточно знать 3-4 команды).



Полезные ссылки

Управление версиями используемых зависимостей Самый базовый набор плагинов для Maven и документации к ним.

Дополнительный набор плагинов, включенный в основной репозиторий Maven. Теги: #maven #package #plugins #pom.xml #сборка пакета #java

Вместе с данным постом часто просматривают: