Статьи

В моем Dojo есть Mojo (Как написать плагин Maven)

Я был до моих подмышек, связанных с использованием Maven на работе. Для большого числа разработчиков я услышу «Ну и что?» Разница в том, что я обычно работаю в среде, где у меня нет прямого доступа к Интернету. Поэтому, когда я говорю, что много использую Maven, это что-то значит.

Зависимость ада

Чтобы быть справедливым, я использовал Maven случайно в моих примерах. Я обнаружил, что удобнее загружать зависимости и избегать «ада зависимостей». Ситуация, когда я должен загрузить библиотеку для библиотеки, которую я использую. Например, необходимо загрузить Hamcrest, чтобы использовать JUnit. Дома вставьте зависимость для JUnit, и Maven загрузит Hamcrest для меня, потому что это зависимость от JUnit. Если бы была зависимость от Хэмкреста, Мэйвен тоже загрузил бы это. Когда я на работе, мне нужно исследовать, какие зависимости есть у JUnit, а затем исследовать, какие есть зависимости. Я избегал использования библиотек из-за этой самой ситуации.

Ситуации меняются

Изменение связано с тем, что я использую Spring Roo на работе. Roo использует Maven для управления зависимостями Spring, которые он должен включить. Из-за этого изменения я настроил сервер Nexus в сети разработки и начал процесс переноса зависимостей из Интернета в сеть разработки. Это заставило меня узнать о Maven.

Что я узнал о Maven

Прочитав две книги, « Знакомство с Maven и Maven Build Customization» , я получил довольно хорошее представление о Maven и о том, как создать тему этого поста. Я могу продолжать и рассказывать о том, что я узнал, но я сосредоточу внимание на том, что необходимо для изучения плагинов Maven. Я предполагаю, что кто-то видел pom-файл и теперь запускает несколько сборок Maven в посте. Если нет, купите книги, которые я прочитал, или сначала зайдите на http://maven.apache.org .

Maven — плагин, богатый

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

Maven ориентирован на пакет, жизненный цикл, фазу и цель

Maven известен тем, что он встраивает что-то в какой-то упакованный предмет, например, файл jar. Это очевидно, это одна из первых строк файла pom. Что может быть неизвестно, так это то, что существует ряд «фаз» или «жизненных циклов», которые выполняют сборку пакета (посмотрите, что я там делал). Фактически, один из этих этапов называется «упаковка». Список фаз по умолчанию в жизненном цикле выглядит следующим образом:

  1. Validate
  2. генерировать-источники
  3. Процесс-источники
  4. генерировать-ресурсы
  5. Процесс-ресурсы
  6. компиляции
  7. Процесс-классы
  8. генерировать-тест-источников
  9. Процесс-тест-источники
  10. генерировать-тест-ресурсы
  11. Процесс-тест-ресурсы
  12. тест-компиляция
  13. Процесс-тест-классы
  14. контрольная работа
  15. подготовить-пакет
  16. пакет
  17. Предварительная интеграция-тест
  18. Интеграция испытаний
  19. после интеграции тест
  20. проверить
  21. устанавливать
  22. развернуть

В сборке Maven много чего происходит! Все это выполняется каким-то плагином. Каждый плагин состоит из целей, которые можно настроить на определенную фазу жизненного цикла. Например, цель jar maven-jar-plugin настроена на запуск в фазе пакета.

Создание плагина

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

Плагины полны моджо

Что такое моджо? Mojo — сокращение от Maven, равнина Old Java Objects Это самая маленькая единица в плагине, который распознает Maven. Все плагины сделаны из моджо. Каждое моё связано с целью. Поэтому для того, чтобы плагин имел несколько целей, ему нужно несколько моджо. В примере, который я покажу, к сожалению, есть только одно моджо, но в этом примере также будут показаны лучшие методы тестирования плагина.

Лучшие практики — единственные допустимые практики

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

  1. Вы не разорили торговую марку Apache
  2. Другие знают, что один сделал плагин.

Что в имени?

Соглашение об именах для плагинов Apache — maven- <title> -plugin. Например, плагин jar называется maven-jar-plugin. Для всех остальных соглашение об именах — <title> -maven-plugin. Например, созданный мной пример называется Remder-Maven-Plugin. Другим примером, который использовался при создании этого поста, является плагин Spring Boot , и он называется spring-boot-maven-plugin. Исходный код Spring Boot находится здесь . Я раздвоил это, чтобы я мог просматривать и злоупотреблять кодом. Мою вилку можно найти здесь . Если кто-то хочет злоупотребить этим вместе, пожалуйста, раскошелите мою копию и отправьте мне запрос на извлечение, когда ваш конкретный фрагмент насилия будет завершен. В любом случае, если кто-то использует соглашение об именах Apache, это является нарушением торговой марки. Вы были предупреждены.

Модульное тестирование

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

Структура каталога при выполнении модульного теста плагина

unit_test_plugin

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

Интеграционное тестирование

Я обнаружил, что это тестирование позволит узнать больше о конкретном плагине и о том, как он работает. Цель состоит в том, чтобы протестировать определенную ситуацию, как если бы она была частью реальной сборки проекта. Когда я имею в виду фактическую сборку проекта, я имею в виду, что существует даже временное хранилище только для интеграционной сборки. После прочтения о том, как настроить тесты, я много позаимствовал из настроек интеграционного теста spring-boot-maven-plugin и файлов mini-pom. Хорошо, я скопировал некоторые файлы в мой пример кода. Просто сообщаю, что Spring Boot сделал все правильно. Просто будьте в безопасности клон только для чтения или разветвите их код, чтобы быть в безопасности. Структура каталогов показана ниже.

it_test_plugin

Интеграционные тесты расположены не под каталогом test, а непосредственно под каталогом src в каталоге it. Я мог бы сделать больше интеграционных тестов, но сейчас достаточно одного.

пример

Пример плагина был вдохновлен тем фактом, что я рассеян и мне нужно напоминать обо всем, что я делаю. Я подумал о создании плагина wash-the-dogs-напоминалки-мавена, но я выбрал простой плагин напоминания-мейвена, потому что тогда я мог использовать его, чтобы напомнить мне обо всем, что мне нужно было сделать.

Файл Пом

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.darylmathison</groupId>
    <artifactId>reminder-maven-plugin</artifactId>
    <packaging>maven-plugin</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>reminder-maven-plugin Maven Mojo</name>
    <url>http://maven.apache.org</url>
 
    <properties>
        <mavenVersion>3.2.1</mavenVersion>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
 
    <dependencies>
        <!-- Maven dependencies -->
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>${mavenVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-core</artifactId>
            <version>${mavenVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-compat</artifactId>
            <version>3.2.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-testing</groupId>
            <artifactId>maven-plugin-testing-harness</artifactId>
            <version>3.1.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-plugin-plugin</artifactId>
                    <version>3.2</version>
                    <executions>
                        <execution>
                            <id>mojo-descriptor</id>
                            <goals>
                                <goal>descriptor</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
    <profiles>
        <profile>
            <id>run-its</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-invoker-plugin</artifactId>
                        <version>1.7</version>
                        <configuration>
                            <debug>true</debug>
                            <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
                            <cloneClean>true</cloneClean>
                            <pomIncludes>
                                <pomInclude>*/pom.xml</pomInclude>
                            </pomIncludes>
                            <addTestClassPath>true</addTestClassPath>
                            <postBuildHookScript>verify</postBuildHookScript>
                            <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
                            <settingsFile>src/it/settings.xml</settingsFile>
                            <goals>
                                <goal>clean</goal>
                                <goal>compile</goal>
                                <goal>package</goal>
                            </goals>
                        </configuration>
                        <executions>
                            <execution>
                                <id>integration-test</id>
                                <goals>
                                    <goal>install</goal>
                                    <goal>run</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

Как видите, для его создания требуется немало плагинов и зависимостей. Здесь есть одна заметная зависимость. Это версия Junit. Версия должна быть 3.8.1. Это связано с тем, что Maven расширил класс TestCase, чтобы упростить модульное тестирование. Это будет видно в ближайшее время. Следует отметить два плагина, один из которых — maven-plugin-plugin, а другой — maven-invoker-plugin. Maven-plugin-plugin автоматизирует процесс создания цели помощи для своего плагина. Maven-invoker-plugin используется в интеграционных тестах. Его функция — запускать проекты Maven, что удобно, если вы работаете в тестовой среде.

ReminderMojo.java

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.darylmathison;
 
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
 
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
 
@Mojo(name = "remind",
        defaultPhase = LifecyclePhase.PACKAGE,
        requiresOnline = false, requiresProject = true,
        threadSafe = false)
public class ReminderMojo extends AbstractMojo {
 
    @Parameter(property = "basedir", required = true)
    protected File basedir;
 
    @Parameter(property = "message", required = true)
    protected String message;
 
    @Parameter(property = "numOfWeeks", defaultValue = "6", required = true)
    protected int numOfWeeks;
 
    public void execute() throws MojoExecutionException {
 
        File timestampFile = new File(basedir, "timestamp.txt");
        getLog().debug("basedir is " + basedir.getName());
        if(!timestampFile.exists()) {
            basedir.mkdirs();
            getLog().info(message);
            timestamp(timestampFile);
        } else {
            LocalDateTime date = readTimestamp(timestampFile);
            date.plus(numOfWeeks, ChronoUnit.WEEKS);
            if(date.isBefore(LocalDateTime.now())) {
                getLog().info(message);
                timestamp(timestampFile);
            }
        }
    }
 
    private void timestamp(File file) throws MojoExecutionException {
        try(FileWriter w = new FileWriter(file)) {
            LocalDateTime localDateTime = LocalDateTime.now();
            w.write(localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        } catch (IOException e) {
            throw new MojoExecutionException("Error creating file " + file, e);
        }
    }
 
    private LocalDateTime readTimestamp(File file) throws MojoExecutionException {
        try(FileReader r = new FileReader(file)) {
            char[] buffer = new char[1024];
            int len = r.read(buffer);
            LocalDateTime date = LocalDateTime.parse(String.valueOf(buffer, 0, len));
            return date;
        } catch(IOException ioe) {
            throw new MojoExecutionException("Error reading file " + file, ioe);
        }
    }
}

Это единственный Mojo в плагине, и, как можно заметить, он очень прост, но показывает некоторые интересные функции, которые предоставляет Mojo API. Аннотация класса определяет, что именем цели является «напоминание» и что оно не является поточно-ориентированным. Он также определяет фазу по умолчанию — фаза пакета. Последнее, что я упомяну, это то, что любая переменная-член может стать параметром. Это становится параметром для плагина цели.

ReminderMojoTest

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
package com.darylmathison;
 
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
 
import java.io.File;
 
/**
 * Created by Daryl on 3/31/2015.
 */
public class ReminderMojoTest extends AbstractMojoTestCase {
 
    @Override
    protected void setUp() throws Exception {
        super.setUp();
    }
 
    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
    }
 
    public void testJustMessage() throws Exception {
        File pom = getTestFile("src/test/resources/unit/reminder-mojo/pom.xml");
        assertNotNull(pom);
        assertTrue(pom.exists());
        ReminderMojo myMojo = (ReminderMojo) lookupMojo("remind", pom);
        assertNotNull(myMojo);
        myMojo.execute();
    }
}

Вот базовый пример модульного теста моджо. Тестовый класс расширяет AbstractMojoTestCase для получения некоторых функций, таких как getTestFile и lookupMojo. Ниже приведен файл теста POM.

Файл Pom Unit Test

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
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.darylmathison.test</groupId>
    <artifactId>reminder-maven-plugin-test-reminder</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>reminder-maven-plugin Maven Mojo</name>
 
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.darylmathison</groupId>
                <artifactId>reminder-maven-plugin</artifactId>
                <version>1.0-SNAPSHOT</version>
                <configuration>
                    <basedir>target/test-classes/unit/reminder-mojo</basedir>
                    <message>Wash the doggies</message>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

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

Интеграционный тест

Это заслуживает отдельного раздела, потому что это действительно отдельный проект Maven в рамках проекта Maven. Основная цель этого упражнения — увидеть, что плагин будет делать, а не что-нибудь еще. Пример приложения прост и предназначен для создания проекта Maven. Следует также отметить, что файл pom использует некоторую фильтрацию для соответствия groupId, artifactId и версии основного модуля pom.

Файл Пом

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
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.darylmathison.it</groupId>
    <artifactId>new-timestamp</artifactId>
    <version>0.0.1.BUILD-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>@project.groupId@</groupId>
                <artifactId>@project.artifactId@</artifactId>
                <version>@project.version@</version>
                <executions>
                    <execution>
                        <id>blah</id>
                        <goals>
                            <goal>remind</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <message>Wash the doggies</message>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

SampleApp

01
02
03
04
05
06
07
08
09
10
package java.test;
 
/**
 * Created by Daryl on 4/5/2015.
 */
public class SampleApp {
    public static void Main(String[] args) {
        System.out.println("out");
    }
}

Verify.groovy

1
2
3
System.out.println(basedir);
def file = new File(basedir, "timestamp.txt");
return file.exists();

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

Вывод

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

использованная литература