(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет!

Сегодня расскажу о нескольких неочевидных особенностях работы Maven.

1) за каждый этап жизненного цикла сборки Maven отвечает какой-то плагин. Один плагин может покрывать несколько этапов. Это не новость. Я надеюсь) Интересно то, что эти плагины не обязательно указывать в pom, вот минимальный рабочий pom, с которым ваш проект скомпилируется - https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#Minimal_POM
Работает принцип convention over configuration.
А вот описание алгоритма определения нужного плагина: https://maven.apache.org/guides/introduction/introduction-to-plugin-prefix-mapping.html
Перескажу его вкратце.

Если вы указали при сборке фазу жизненного цикла, например, maven install, то определяются все необходимые шаги сборки и требуемые плагины как описано вот тут https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
А далее Maven ищет специальные файлы maven-metadata во всех подключенных к проекту репо, сливает их в один и ищет плагин по атрибуту prefix, который равен указаному вот тут как plugin https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#default-lifecycle-bindings-packaging-ejb-ejb3-jar-par-rar-war
Вот пример базового maven-metadata из Maven Central https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml
Как можно заметить, у большинства плагинов, поисковый префикс является частью artefactId. Это соглашение о наименовании, хотя и не обязательное. Т.е. всегда можно сделать свой maven-metadata-local.xml и привязать нужный плагин к нужной фазе там. Но я бы не рекомендовал так делать, т.к. это завязка на конкретный сборщик, которая хранится отдельно от кода вашего проекта. Также при создании плагина можно назвать его как угодно и указать к какой фазе сборки он привязан, может быть полезно, когда одним словом сложно выразить задачу плагина.

Если вы явно указываете цель для сборки, например, compiler:compile, то выполняется только эта цель, Maven пропускает разбор жизненного цикла сборки, сразу переходит к поиску нужного плагина в maven-metadata, в данном случае по префиксу compiler.

Вопрос - а как Maven определяет версию плагина? Есть еще один maven-metadata файлик с версиями у каждого плагина https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/maven-metadata.xml

И как раз здесь лежит подводный камень: когда Maven-у понадобится конкретный плагин - он возьмет последнюю версию из тех, что найдет в подключенных к проекту репозиториях. А брать последнюю версию не всегда хорошо - там могут быть баги, или она просто может криво закачаться, если речь про внутренний прокси репозиторий.
Поэтому рекомендую начинать с минимальным POM, а перед выходом на ПРОМ фиксировать все версии плагинов.

Еще может возникнуть вопрос - а нельзя ли этот фокус провернуть с неофициальными плагинами? Ответ - можно, нужно лишь указать в settings.xml где еще искать maven-metadata.xml:
<pluginGroups>
<pluginGroup>org.codehaus.modello</pluginGroup>
</pluginGroups>

Еще момент - всегда можно добавить в pom конфигурацию плагина и там переопределить фазу сборки, на которой он запустится.
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>

В примере цель из плагина jacoco-maven-plugin привяза к фазе test.
Выше я описывал, как работает Maven в режиме convention over configuration.

Продолжение следует...

#maven #buildtool #conv_over_conf
Всем привет!

Продолжим про Maven.

2) Несмотря на то, что к каждой фазе жизненного цикла по умолчанию привязана одна цель (goal) из конкретного плагина, на каждую фазу можно повесить сколько угодно целей. Порядок выполнения - как указано в pom.xml. Это еще одна одна причина, почему полезно явно обьявлять плагины в pom.

3) Если у вас в settings.xml указано несколько репозиториев с артифактами, и поиск по всем не дал результата - будет ошибка, но с некорректным текстом. В ней будет сказано, что артифакта нет в первом по списку репозитории

4) Если какие-то из репозиториев в settings.xml требуют аутентификации, данные для аутентификации были указаны и протухли, то вы узнаете об этом только тогда, когда понадобится обновить библиотеку, которая есть только в этом репозитории. Ошибка с аутентификацией будет в логах каждой сборки, но сборку не ломает

5) Может возникнуть вопрос - а зачем Maven каждый раз ходит по удаленным репозиториям, если зависимости не менялись. Это поведение контролируется параметром репозитория updatePolicy в settings.xml:

<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2</url>
<releases>
<updatePolicy>always</updatePolicy>
</releases>
</repository>

Возможные значения: "always", "daily" (default), "interval:XXX" (in minutes) or "never".
Можно установить never, но есть два риска:
а) не будут подтягиваться новые snapshot,
б) не будут подтягиваться новые версии уже закачанных артифактов. А эту информацию индексирует IDEA и использует при AutoComplition

6) Если сохраненные локально зависимости были повреждены, то принудительно их перезагрузить можно через очистку локального репозитория, который находится в папке .m2. Командой mvn dependency:purge-local-repository, подробнее тут https://www.baeldung.com/maven-force-update или просто грохнув нужную папку в .m2

7) согласно уже упоминаемому ранее принципу convention over configuration по умолчанию в список репозиториев неявно включается Maven Central. Подробнее про settings.xml и его опции можно почитать тут https://habr.com/ru/post/339902/

8) список репозиториев можно задать как в pom.xml, так и в settings.xml. Предпочтительнее второй вариант, особенно при разработке библиотек, особенно внутренних. Причина: адрес репозитория может поменяться, а settings.xml проще обновить централизованно. Не говоря уже про библиотеки, которые придется перевыпускать. Конечно, если вы ходили по ссылке из предыдущего пункта - у нас есть возможность переопределить любой репозиторий из pom через зеркала\mirror в settings.xml. Причем при настройке зеркала можно не только указывать явно заменяемый репозиторий, но и использовать операторы * и ! Но это все же костыль)

#maven #buildtool #conv_over_conf
Всем привет!

Минигайд по кодировкам в Maven.
Для начала - главное, что нужно знать про кодировки в целом:
1) UTF-8 рулит)
2) нужно явно указывать кодировку.

Как это сделать в Maven?

1)
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

Работает практически везде, за исключением пунктов 2) и 3) ниже.

2)
<properties>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

Работает для отчетных плагинов. Чтобы не выяснять для каких именно - проще установить и забыть)

3) Переменная среды JAVA_TOOL_OPTIONS равная -Dfile.encoding=UTF8
Работает тогда, когда Maven запускает отдельный Java процесс, в частности при запуске unit тестов из Maven. Причина падения тестов на Win, до этого успешно работающих на Mac\Lin. Падают из-за другой кодировки по умолчанию в Win.

4) Возможна точечная установка или переопределение кодировки для нужного плагина, например:
а) unit тесты
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration>
</plugin>
б) компиляция
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
Как видно из примеров - формат настроек у разных плагинов различается.

#maven
Всем привет!

Еще немного про Maven. Есть несколько способов настроить версию Java в Maven, все они описаны в этой статье - https://www.baeldung.com/maven-java-version
Оптимальным начиная с Java 9 выглядит вариант с указанием
<properties>
<maven.compiler.release>9</maven.compiler.release>
</properties>
т.к. данное свойство заменяет собой три: source, target и bootclasspath. Последнее означает, что начиная с Java 9 все JDK умеют притворяться более ранними версиями с точностью до classpath, т.е. до предоставляемого API. А это важно при переходе на 11-ю и 17-ю Java - в первой выпилили Java EE модули, типа SOAP, во второй - закрыли internal пакеты.

Но это не все, что нужно знать про настройки версии JDK.
Если в проекте есть Kotlin - надежнее явно указать ему версию JDK. Для справки - старые версии Kotlin поддерживали только JDK 6 как target bytecode, потом был период JDK 8, сейчас поддерживается все версии https://kotlinlang.org/docs/maven.html
Настраивается так:
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<configuration>
<jvmTarget>1.9</jvmTarget>
По хорошему плагин Kotlin должен переиспользовать maven.compiler.release, но я не нашел подтверждающей это информации.

Второй тонкий момент связан с IDEA. IDEA смотрит на maven.compiler.release, в результате правильно проставляет:
а) Source language level для корневого и дочерних модулей Maven - это можно увидеть в Project Structure -> Modules
б) Target bytecode для всех модулей - Double Shift, далее вбиваем "java compiler".
Но есть еще одна важная опция Project Structure -> Project -> SDK. Она определяет версию JDK, на которой будет собираться проект. Повторюсь, из-за сильных изменений в структуре JDK как минимум в 11 и 17 версиях сборка на более новой JDK может сломаться. Причем из логов причина ошибки будет не ясна(
Так вот, IDEA в данном кейсе игнорирует настройки Maven. Прямого решения у проблемы я не нашел, судя по всему, ребята из IntelliJ считают, что корневой модуль в проекте Maven и проект IDEA это не одно и тоже. И это фича, а не баг)
Но есть обходной путь. Если все или большинство ваших проектов живут на определенной версии JDK, то ее можно выставить в IDEA по умолчанию. Настраивается вот здесь: Double Shift, далее вбиваем "structure for new projects"

#maven #java #kotlin
Всем привет!

Три лайфхака по работе с Maven:

1) чтобы заработала отладка в IDEA при запуске через Maven к примеру unit тестов:
для версий surefire плагина >= 2.14: use -DforkCount=0
http://maven.apache.org/plugins/maven-surefire-plugin/examples/debugging.html

2) запустить цель (goal) на дочернем модуле и всех его детях: maven --projects artifactId --also-make-dependents
Полезно на больших многомодульных проектах. Подбробнее https://maven.apache.org/guides/mini/guide-multiple-modules-4.html
Запускать цель напрямую но дочернем проекте нельзя, т.к в таком случае не применятся настройки из корневого проекта - как Maven, так и к примеру Lombok.
Еще важно - перед тем, как отлаживать дочерний проект нужно проинсталлировать в локальный Maven репозиторий те модули, от которых он зависит. Я про maven install.

3) по умолчанию maven следует принципу fail fast и это правильно) Но если нужно понять, в каком состоянии проект - можно запустить полную сборку игнорирую промежуточные ошибки до окончания сборки: maven --fail-at-end

#maven #java
Всем привет!

Сегодня пост про очередной типичный вопрос на собеседовании.
Звучит он так: расскажите про жизненный цикл сборки Maven, он же build lifecycle.
Я вкратце уже писал про это https://t.me/javaKotlinDevOps/10. Но т.к. типичный ответ на собеседовании - перечисление циклов и фаз сборки, то хочется раскрыть тему. На самом деле вопрос про другое - в чем суть цикла сборки?

1) набор фаз сборки JVM проекта уже придуман и зафиксирован умными людьми) из команды Maven в виде Default Lifecycle https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference Т.е. считается, что список покрывает все потребности сборки. Как пример продуманности могу привести фазы pre-integration-test и post-integration-test, которые нужны для того, чтобы поднять и опустить контейнер сервлетов, менеджер очередей или БД до и после фазы интеграционных тестов (integration-test). Для обычных тестов (test) таких вспомогательных фаз нет.
2) последовательность фаз также зафиксирована, порядок менять нельзя да и нет смысла
3) большинство фаз являются опциональными, запускаются только если к фазе подключен хоть один плагин и либо в проекте присутствуют определенные файлы, либо есть настройки плагина, указывающие что ему делать - например, generate-sources или компиляция Kotlin
4) на одной фазе могут работать несколько целей (goal) из разных плагинов - например, компиляция Java и Kotlin на фазе compile или несколько тестовых фреймворков на фазе test
5) запуская определенный шаг сборки - mvn install - вы запускаете все предыдущие шаги цикла. И в большинстве случаев это правильно. Если такое поведение не требуется - нужно запустить конкретную цель у конкретного плагина, для моего примера это mvn install:install (плагин:цель). Т.е. тут совпадают название фазы сборки, плагина и цели. Так бывает не всегда, например, для фазы test-compile цель выглядит так compiler:testCompile
Итого: цель жизненного цикла сборки - стандартизировать процесс сборки.
Да и в принципе стандартизация - конек Maven. Тут и типовой набор папок https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html, и цикл сборки, и сложность написания своего кода для сборки, и достаточно богатый набор стандартных плагинов, для подключения которых ничего не надо делать (если быть точным - неплохо бы зафиксировать версию в pom), и архетипы, и конвенция по наименованию артефактов, и наконец центральный репозиторий - https://mvnrepository.com/repos/central

#maven #interview_question
Всем привет!

Продолжая тему Maven. Когда я изучал документацию по Maven, то наткнулся такую несколько странную возможность, как отключение компиляции через опцию -Dmaven.main.skip.
Почему, зачем? На первый взгляд - бессмысленно.
Но недавно на практике понял зачем она нужна.

Предположим в вашем pom есть некий плагин с достаточно сложными нестандартными настройками. Есть процесс, в котором нужно выполнить его goal. Если мы просто запустим goal командой mvn plugin:version:goal, то настройки из pom будут проигнорированы. Предположим, задать их из командной строки невозможно, т.к. плагин это не поддерживает. А даже если возможно - это будет дублирование, нарушение DRY, т.е. тоже плохой вариант)
Хуже всего то, что наш плагин находится в build lifecycle https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference после фазы compile. Пусть проект уже собран, но как известно инкрементальная компиляция в Maven существует, но с ней есть нюансы: https://stackoverflow.com/questions/16963012/maven-compiler-recompile-all-files-instead-modified/19653164#19653164
К слову - именно по скорости компиляции Maven сильно проигрывает Gradle.

Так вот - именно тут опция -Dmaven.main.skip и выходит на сцену, сильно уменьшая время выполнения.

P.S. Да, по моему опыту - плагин для компиляции Kotlin, увы, ее не поддерживает.

#maven #java #kotlin
Всем привет!

Есть интересная тема - инструменты сборки для JVM проектов. А в рамках нее другая горячая тема - управление конфликтами зависимостей. Когда в проект подтягивается, как правило транзитивно, две версии одной и той же зависимости. А должна остаться только одна)
Отличное сравнение 3 систем сборки по управлению конфликтами зависимостей еще 10+ лет назад проведено в этой статье: https://habr.com/ru/companies/jugru/articles/191246/
Вывод из статьи - в Maven все сделано, скажет так, странно)))
Приходится явно указывать нужную версию каждой конфликтной зависимости в проекте.

Первый вопрос, который приходит на ум - зачем в Maven так сделали и когда собираются исправлять.
Ответ тут - https://stackoverflow.com/questions/34201120/maven-set-dependency-mediation-strategy-to-newest-rather-than-nearest
Спойлер - исправлять не собираются, считают, что так сборка будет более предсказуемой и повторяющейся. Т.е. описанный выше подход - запускай приложение, находи конфликты в runtime и указывай явно версию в своем модуле - считается правильным. Но есть лайфхак - см. ответ на stackoverflow.

Ну а чтобы найти версии проблемной зависимости - нужен mvn dependency:tree. О его "секретных" (на самом деле полезных) ключах этой таски Maven можно почитать тут https://www.digitalocean.com/community/tutorials/maven-dependency-tree-resolving-conflicts

Ну и если хочется копнуть глубже, например понять, как разрешается конфликт scope-ов зависимости или узнать про то, как разработчик библиотеки может уменьшить возможность появления конфликта (optional) - см. главный источник истины по Maven - его документацию https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

#maven #gradle #java #buildtool #dependency_management
Всем привет!

Вдогонку ко вчерашней теме про управление зависимостями.
В Maven есть две фичи, смягчающие боль по управлению зависимостями:
1) bom - Bill Of material - pom-ник, объявляющий версии протестированного и гарантированного рабочего набора обязательных зависимостей, например, библиотеки Spring Security
2) dependencyManagemenet - возможность в одном месте, как правило это корневой pom, объявить все версии используемых в проекте библиотек. Туда же можно подключать уже готовые bom-ы. После этого задача поднятия версии какой-то зависимости по всему многомодульному проекту упрощается

Есть ли что-то подобное в Gradle?
Да, есть.
Вот тут описывается как "сэмулировать" bom в Gradle - https://habr.com/ru/articles/784784/
Ключевые слова platform и constraints https://docs.gradle.org/current/userguide/dependency_constraints.html
Почему я говорю сэмулировать: если посмотреть на структуру bom - это типичный Maven xml файл. Gradle публикует bom в двух форматах - maven для совместимости и свой json, вот пример https://repo1.maven.org/maven2/io/github/mfvanek/pg-index-health-bom/0.10.2/

Но на самом деле Gradle умеет чуть больше - есть такая штука, как catalog - по сути позволяющая структурировать зависимости в древовидную структуру и дать имя-ссылку каждому уровню. См. https://docs.gradle.org/current/userguide/platforms.html Причем объявлять завимости можно как в build.gradle, так и в отдельном toml файле. Каталог можно использовать сам по себе - как набор версий, так и ограничивать с помощью него версии транзитивных зависимостей - через те же constraints и platform.

#gradle #maven #depenedency_management #buildtool
Всем привет!

Если посмотреть на формат pom файлов Maven https://maven.apache.org/pom.html#POM_Reference, то можно увидеть там несколько свойств с метаданными проекта - organization, developers, contributors, scm, issueManagement... Плюс есть возможность получить информацию о дереве зависимостей проекта через команду mvn dependency:tree. Т.е. некоторую информацию о проекте собрать можно. Но собирать ее воедино придется руками. И это информация о проекте, но не о конкретной сборке этого проекта.
А что, если у нас другой сборщик? Или не Java проект? Или АС, включающая несколько компонентов? А можно ли сохранить информацию о найденных в проекте уязвимостях?
Для всего этого есть https://cyclonedx.org/use-cases/
СycloneDX позволяет сохранить информацию о проекте в объектной модели, которую можно сохранить в JSON, XML или Protocol Buffers. И кажется, что его разработчики решили дать возможность собрать всю возможную информацию:
1) состав проекта - библиотеки, приложения, веб-сервисы, драйверы, файлы... Есть средства группировки компонентов проекта.
2) информацию о разработчиках проекта - людях и компании
3) хэши для контроля целостности
4) информация о лицензиях
5) release notes, в т.ч. информация о commit, из которого собрана сборка, и фичах, вошедших в релиз
6) информация об уязвимостях, найденных в компонентах проекта, а также ссылка на патч, исправляющий уязвимость. Последнее нужно для того случая, когда накатить исправление невозможно из-за dependency hell. Информация об уязвимостях может быть получена как в привязке к зависимостям проекта, так и просто в виде списка.
7) дерево зависимостей
8) ссылки на сайты, чаты, почту...
9) любые произвольные метаданные в формате ключ-значение
И это только то, с чем я успел разобраться)

Есть интеграция с множеством сборщиков https://cyclonedx.org/tool-center/

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

#metadata #release_notes #maven #standardization