Статьи

Избавиться от пом XML … почти

Вступление

Файлы POM — это файлы в формате XML, которые декларативно описывают структуру сборки проекта Java, который будет построен с использованием Maven. Поддержка XML-файлов POM в больших Java-проектах во много раз обременительна. XML является многословным, а также структура POM требует обслуживания избыточной информации. Многократное именование артефактов является избыточным, повторяя некоторую часть имени в groupId и в artifactId . Версия проекта должна отображаться во многих файлах в случае многомодульного проекта. Некоторые из повторений могут быть уменьшены с помощью свойств, определенных в родительском pom, но вам все равно нужно определить версию родительского pom в каждом модуле pom, потому что вы ссылаетесь на POM по координатам артефакта, а не просто называете его «pom это есть в родительском каталоге ». Параметры зависимостей и плагинов могут быть настроены в родительском POM в pluginManagement и управлении dependency но вы все еще не можете избавиться от списка плагинов и зависимостей в каждом модуле POM, хотя они обычно одинаковы.

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

Существуют некоторые технологии для поддержки других форматов, но они не используются широко. Одним из таких подходов к избавлению от XML является Poyglot Maven . Однако, если вы посмотрите на эту страницу Github самый первый пример, который представляет собой POM в формате Ruby, вы все равно увидите много избыточной информации, повторений. Это связано с тем, что Polyglot Maven подключается к самому Maven и заменяет только формат XML на что-то другое, но не помогает в избыточности самой структуры POM.

В этой статье я опишу подход, который я нашел намного лучше, чем любое другое решение, где файлы POM остаются XML для процесса сборки, поэтому нет необходимости в каком-либо новом плагине или изменении процесса сборки, кроме этих pom.xml файлы создаются с использованием языка макросов Jamal из файла pom.xml.jam и некоторых дополнительных файлов макросов, которые используются модулями.

Джамал

Идея заключается в использовании текстового макроязыка для генерации XML-файлов из некоторого исходного файла, который содержит ту же информацию в сокращенном формате. Это какое-то программирование. Описание макроса — это программа, которая выводит подробный формат XML. Когда макроязык достаточно мощный, исходный код может быть достаточно описательным и не слишком подробным. Мой выбор был Джамал. Честно говоря, одной из причин выбора Jamal было то, что это макроязык, который я разработал почти 20 лет назад с использованием Perl, а полтора года назад я переопределил его на Java.

Сам язык очень прост. Текст и макросы смешиваются друг с другом, а вывод — это текст и результат макроса. Макросы начинаются с { символа или любой другой строки, которая настроена, и заканчиваются соответствующим символом } или строкой, которая была настроена как конечная строка. Макросы могут быть вложенными, и есть точный контроль, в каком порядке должны оцениваться вложенные макросы. Существуют пользовательские и встроенные макросы. Одним из встроенных макросов является define который используется для определения пользовательских макросов.

Пример говорит лучше. Давайте посмотрим на следующий файл test.txt.jam .

1
2
3
4
5
6
7
{@define GAV(_groupId,_artifactId,_version)=
    {#if |_groupId|<groupId>_groupId</groupId>}
    {#if |_artifactId|<artifactId>_artifactId</artifactId>}
    {#if |_version|<version>_version</version>}
}
 
{GAV :com.javax0.geci:javageci-parent:1.1.2-SNAPSHOT}

обработав его с Джамалем, мы получим

1
2
3
<groupId>com.javax0.geci</groupId>
    <artifactId>javageci-parent</artifactId>
    <version>1.1.2-SNAPSHOT</version>

Я удалил пустые строки вручную для набора текста, но вы получите общее представление. GAV определяется с помощью встроенного макроса define . У него есть три аргумента с именами _groupId , _artifactId и _version . При использовании макроса имена аргументов формата в теле макроса заменяются фактическими значениями и заменяют пользовательский макрос в тексте. Текст самого встроенного макроса определения является пустой строкой. Существует специальное значение, когда использовать @ и когда использовать # перед встроенными макросами, но в этой статье я не могу получить этот уровень детализации.

groupId if также позволяют опустить groupId , artifactId или version , таким образом

1
{GAV :com.javax0.geci:javageci-parent:}

также работает и будет генерировать

1
2
<groupId>com.javax0.geci</groupId>
    <artifactId>javageci-parent</artifactId>

Если вы чувствуете, что в определении макросов все еще много избыточности: вы правы. Это простой подход к определению GAV , но вы можете пойти до крайности:

1
2
3
4
5
{#define GAV(_groupId,_artifactId,_version)=
    {@for z in (groupId,artifactId,version)=
        {#if |_z|<z>_z</z>}
    }
}{GAV :com.javax0.geci:javageci-parent:}

Имейте в виду, что для этого нужен безумный уровень понимания порядка оценки макросов, но в качестве примера он демонстрирует силу. Больше информации о Джамале https://github.com/verhas/jamal

Давайте вернемся к исходной теме: как Jamal можно использовать для поддержки файлов POM.

Готовим пом к варенью

Может быть много способов, каждый из которых может быть просто хорошим. Здесь я опишу первый подход, который я использовал для проекта Java :: Geci. Я создаю файл pom.jim ( jim обозначает импортированные или включенные файлы Jamal). Он содержит определения макросов, таких как GAV , dependencies , dependency и многие другие. Вы можете скачать этот файл из репозитория исходного кода Java :: Geci: https://github.com/verhas/javageci Файл pom.jim может быть одинаковым для всех проектов, в нем нет ни одного конкретного проекта. Существует также файл version.jim , содержащий макрос, который в одном месте определяет версию проекта, версию Java, которую я использую в проекте, и идентификатор группы для проекта. Когда я -SNAPSHOT номер релиза с -SNAPSHOT до следующего релиза или с релиза на следующий -SNAPSHOT это единственное место, где мне нужно его изменить, и макрос можно использовать для ссылки на версию проекта в POM верхнего уровня. ? но также и в модуле POM, ссылающемся на родителя.

В каждом каталоге, где должен pom.xml файл pom.xml я создаю файл pom.xml.jam . Этот файл импортирует файл pom.jim , поэтому в нем можно использовать определенные макросы. Например, файл модуля pom.xml.jam модуля Java :: Geci:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
{@import ../pom.jim}
{project |jar|
    {GAV ::javageci-engine:{VERSION}}
    {parent :javageci-parent}
    {name|javageci engine}
    {description|Javageci macro library execution engine}
 
    {@include ../plugins.jim}
 
    {dependencies#
        {@for MODULE in (api,tools,core)=
            {dependency :com.javax0.geci:javageci-MODULE:}}
        {@for MODULE in (api,engine)=
            {dependency :org.junit.jupiter:junit-jupiter-MODULE:}}
    }
}

Я думаю, что это довольно читабельно, по крайней мере, для меня это более читабельно, чем оригинальный pom.xml :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>javageci-engine</artifactId>
    <version>1.1.1-SNAPSHOT</version>
    <parent>
        <groupId>com.javax0.geci</groupId>
        <artifactId>javageci-parent</artifactId>
        <version>1.1.1-SNAPSHOT</version>
    </parent>
    <name>javageci engine</name>
    <description>Javageci macro library execution engine</description>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>com.javax0.geci</groupId>
            <artifactId>javageci-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.javax0.geci</groupId>
            <artifactId>javageci-tools</artifactId>
        </dependency>
        <dependency>
            <groupId>com.javax0.geci</groupId>
            <artifactId>javageci-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
        </dependency>
    </dependencies>
</project>

Для запуска Jamal я могу использовать плагин Jamal Maven. Для этого проще всего иметь POM-файл genpom.xml в корневом каталоге с содержимым:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>pom.xml_files</artifactId>
    <version>out_of_pom.xml.jam_files</version>
    <build>
        <plugins>
            <plugin>
                <groupId>com.javax0.jamal</groupId>
                <artifactId>jamal-maven-plugin</artifactId>
                <version>1.0.2</version>
                <executions>
                    <execution>
                        <id>execution</id>
                        <phase>clean</phase>
                        <goals>
                            <goal>jamal</goal>
                        </goals>
                        <configuration>
                            <transformFrom>\.jam$</transformFrom>
                            <transformTo></transformTo>
                            <filePattern>.*pom\.xml\.jam$</filePattern>
                            <exclude>target|\.iml$|\.java$|\.xml$</exclude>
                            <sourceDirectory>.</sourceDirectory>
                            <targetDirectory>.</targetDirectory>
                            <macroOpen>{</macroOpen>
                            <macroClose>}</macroClose>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Имея это, я могу запустить Maven с командной строкой mvn -f genpom.xml clear . Это не только создает все файлы POM, но также очищает предыдущий результат компиляции проекта, что, вероятно, является хорошей идеей при изменении файла POM. Это также может быть выполнено, когда в pom.xml еще нет pom.xml или когда файл недействителен из-за какой-то ошибки, которую вы можете иметь в готовом файле POM. К сожалению, вся рекурсивность должна где-то заканчиваться, и это неосуществимо, хотя возможно сохранить genpom.xml в виде POM-файла с вареньем.

Резюме

Я описал один из подходов к использованию макроязыка в качестве источника вместо pom.xml редактирования файла pom.xml . Преимущество — более короткое и простое определение проекта. Недостатком является дополнительный этап генерации POM, который выполняется вручную и не является частью процесса сборки. Вы также теряете возможность использовать плагин релиза Maven напрямую, так как этот плагин изменяет файл POM. У меня самого всегда были проблемы с использованием этого плагина, но это, вероятно, моя ошибка, а не ошибка плагина. Кроме того, вы должны немного выучить Джамал, но это также может быть преимуществом, если вам это понравится. Короче говоря, вы можете попробовать, если хотите. Начать легко, поскольку инструмент (Jamal) публикуется в центральном репозитории, источник и документация находятся на Github, поэтому все, что вам нужно, — это genpom.xml файл genpom.xml , приготовить джем и запустить плагин.

POM-файлы — не единственные исходные файлы, которые можно использовать с jam. Я легко могу представить использование макросов Jamal в документации по продукту. Все, что вам нужно, — это создать файл documentationfile.md.jam в качестве исходного файла и изменить основное POM для запуска Jamal во время процесса сборки, преобразуя .md.jam в полученный в результате обработанный макросом документ уценки. Вы также можете настроить отдельный POM, как мы это делали в этой статье, на случай, если вы хотите, чтобы выполнение преобразования выполнялось строго вручную. Вы можете даже иметь файлы java.jam на тот случай, если вы хотите иметь препроцессор для ваших файлов Java, но я прошу вас этого не делать. Я не хочу гореть в вечном огне в аду за то, что дал тебе Джамала. Это не для этой цели.

Есть много других возможных применений Джамала. Это мощный язык макросов, который легко встраивать в приложения, а также легко расширять с помощью макросов, написанных на Java. Java :: Geci также имеет модуль версии 1.0, который поддерживает Jamal, чтобы упростить генерацию кода, но при этом отсутствуют встроенные макросы, которые планируется сделать доступными к структуре кода Java с помощью отражений. Я также думаю о разработке простых макросов для чтения исходных файлов Java и включения в документацию. Когда у меня будет результат в тех, о которых я напишу.

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

Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотрите оригинальную статью здесь: избавиться от XML XML … почти

Мнения, высказанные участниками Java Code Geeks, являются их собственными.