【Maven研究】Maven Pluginを自分で実装してみたらすごく理解が深まった!②

技術

最初のプラグインを作る

前回はMavenプラグインの役割を確認し、プラグイン開発の環境をDockerコンテナに作成するところまで出来ました。

【Maven研究】Maven Pluginを自分で実装してみたらすごく理解が深まった!①
分かるようでよく分かってないPOM たまたま業務の都合で近頃はJava開発が多いです。Spring Frameworkを使ったWebアプリケーション開発だったり、AWSのLambda開発だったり。 で、そのいずれでも現場の都合(というか好み...

そんなわけで、今回はいよいよMaven本家のチュートリアルに従って実際にプラグインを作ってみます。

Maven – Plugin Developers Centre

まずは前回作ったDockerコンテナにVSCodeのRemote Development拡張で接続しましょう。

コンテナにアクセスできたら「Ctrl + @」でTerminalを開き、下記のコマンドを実行して作業用ディレクトリを作成します。

mkdir /usr/src/work

「work」ディレクトリが作成できたら、VSCodeのメニューから「File → Open folder」で作成したworkディレクトリを開きます。

プラグイン開発プロジェクトのひな型はこのコマンドで作ります

mvn archetype:generate \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DarchetypeArtifactId=maven-archetype-plugin

groupId artifactIdの入力を求められるので、チュートリアルのサンプルに従って下記の通り入力します。

groupIdsample.plugin
artifactIdhello-maven-plugin
version1.0-SNAPSHOT※デフォルトのままでいいので
何も入力せずにEnter
packagesample.plugin※デフォルトのままでいいので
何も入力せずにEnter

プロジェクトひな型が作成成功するとこんなフォルダ構成ができると思います。

前回、「プラグインにはgoalが定義されていて、ビルドフェーズと紐づけることでビルド ライフサイクルに組み込むことができる」という流れを確認しました。

このフォルダ構成の中にあるMyMojo.javaというファイルがまさにgoalを定義したものになります。

Your First Mojo

テンプレートに最初から用意されているMyMojo.javaはいったん置いておいて、ごく簡単なgoalを実装してみましょう。

「hello-maven-plugin/src/main/java/sample/plugin」の下、MyMojo.javaの隣にGreetingMojo.javaを新規作成してください。

package sample.plugin;
 
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
 
/**
 * Says "Hi" to the user.
 *
 */
@Mojo(name = "sayhi")
public class GreetingMojo extends AbstractMojo
{
    public void execute() throws MojoExecutionException
    {
        getLog().info("Hello, world.");
    }
}

goalを実装したクラスはMojoと呼ばれるそうです。

MojoクラスはAbstractMojoの派生クラスとし、execute()メソッドに実際の処理を実装します。
GreetingMojoでは「Hello, world.」とログ出力するだけの実装になっています。

ちなみにGreetingMojoは「sayhi」という名前のgoalとして定義されています。
@Mojoというアノテーションでnameプロパティにgoal名を指定すればいいようです。

GreetingMojo.javaが実装できたら次はpom.xmlを確認してみます。

mvn archetype:generateコマンドでひな型を作成した場合、pom.xmlは下記3つが生成されています。

  1. hello-maven-plugin/pom.xml
  2. hello-maven-plugin/it/simple-it/pom.xml
  3. hello-maven-plugin/test/resources/project-to-test/pom.xml

今回必要なのは 1. だけです。2. および 3. はテスト用みたいなので、ひとまず置いておきます。

pom.xmlにはたくさんの定義がありますが、重要なポイントだけ確認しておきましょう。

<?xml version="1.0" encoding="UTF-8"?>


<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>sample.plugin</groupId>
  <artifactId>hello-maven-plugin</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>maven-plugin</packaging>

  <name>hello-maven-plugin Maven Plugin</name>

  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>
     ・
     ・
     ・

ポイントの1つ目は「packaging」にはmaven-pluginを指定することです。
こうすることで、hello-maven-pluginプロジェクトをMavenプラグインとしてビルドすることになります。

ポイントの2つ目は依存関係です。

     ・
     ・
     ・
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>${maven.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-core</artifactId>
      <version>${maven.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-artifact</artifactId>
      <version>${maven.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-compat</artifactId>
      <version>${maven.version}</version>
      <scope>test</scope>
    </dependency> 
    <dependency>
      <groupId>org.apache.maven.plugin-tools</groupId>
      <artifactId>maven-plugin-annotations</artifactId>
      <version>3.6.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
     ・
     ・
     ・

依存関係で特に重要なのはmaven-coremaven-plugin-annotationsです。
これらはAbstractMojoクラスや@Mojoアノテーションが定義されているライブラリで、依存関係に含まれていないとコンパイルに失敗してしまいます。

プラグインを実行してみる

まずは作成したhello-maven-pluginをビルドしましょう。

cd /usr/src/work/hello-maven-plugin
mvn install

このコマンドを実行して、BUILD SUCCESSと出力されたら、hello-maven-pluginはMavenのローカルリポジトリにインストールされます。
BUILD FAILUREと出力されているうちはビルドに失敗しているので、コードの何かが間違えている可能性があります。
エラーの内容をよく確認して修正してください。

次に作成したプラグインを利用するための適当なプロジェクトを作ります。

cd /usr/src/work
mvn archetype:generate \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DarchetypeVersion=1.4
groupIdsample.plugin
artifactIdhello-world
version1.0-SNAPSHOT※デフォルトのままでいいので
何も入力せずにEnter
packagesample.plugin※デフォルトのままでいいので
何も入力せずにEnter

コマンドが成功すると work ディレクトリの配下、hello-maven-pluginの隣にhello-worldディレクトリが作成されていて、配下にプロジェクトができていると思います。

今回はhello-maven-pluginを試してみるだけなので、hello-worldのソースコードは一切触らず置いておきます。

hello-worldディレクトリ下のpom.xmlが今回のターゲット。

<?xml version="1.0" encoding="UTF-8"?>

<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>sample.plugin</groupId>
  <artifactId>hello-world</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>hello-world</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        <plugin>
          <groupId>sample.plugin</groupId>
          <artifactId>hello-maven-plugin</artifactId>
          <version>1.0-SNAPSHOT</version>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>sample.plugin</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <executions>
          <execution>
            <phase>compile</phase>
            <goals>
              <goal>sayhi</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

まず、72~76行目で今回作成したhello-maven-pluginをビルド時のプラグインとして追加します。
次に79~96行目で「sayhi」というゴールと「compile」というビルドフェーズを紐づけています。

これで、hello-maven-pluginが実行できるはずです。
早速、hello-worldディレクトリでmvn installコマンドを実行してみましょう。

cd /usr/src/work/hello-world
mvn install

下記のように実行結果が出力されていて、ちょうど真ん中あたりでhello-maven-pluginの「sayhi」ゴールが実行されていることがわかると思います。

改造してみる

プラグインはpom.xml上でプロパティを定義して、実行時に参照することができます。
GreetingMojo.javaを改造して試してみましょう。

package sample.plugin;
 
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; 

/**
 * Says "Hi" to the user.
 *
 */
@Mojo(name = "sayhi", defaultPhase = LifecyclePhase.COMPILE)
public class GreetingMojo extends AbstractMojo
{
    /**
     * The greeting to display.
     */
    @Parameter(property = "sayhi.greeting", defaultValue = "Hello World!" )
    private String greeting;

    public void execute() throws MojoExecutionException
    {
        getLog().info("Hello, world. - " + greeting);
    }
}

この改造を加えた後hello-maven-pluginプロジェクトでmvn installしておきましょう。
ちゃんとBUILD SUCCESSと出力されていることが確認しておいてください。

改造ポイント1

改造ポイントの1つ目は@MojoアノテーションにdefaultPhase = LivecyclePhase.COMPILEを追加していることです。

前回少しだけ触れた「ゴールとビルド フェーズのデフォルトの紐づけを実装時に定義する方法」というのがこれです。
こうすることで、hello-maven-pluginを利用する時のpom.xmlで<phase>の定義を省略してもcompileフェーズとsayhiゴールがデフォルトで紐づけられることになります。

    <plugins>
      <plugin>
        <groupId>sample.plugin</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <executions>
          <execution>
            <!-- <phase>compile</phase> -->
            <goals>
              <goal>sayhi</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>

このように<phase>の定義をコメントアウトしてhello-worldプロジェクトをmvn packageしてみます。

この通り、ちゃんとcompileフェーズでsayhiゴールが実行されていることが確認できました。

改造ポイント2

改造ポイントの2つ目はgreetingプロパティを定義していることです。

greetingはprivateフィールドとして定義していますが、@Parameterアノテーションを使うことで後から値を設定できるようになっています。
また@ParameterアノテーションはpropertydefaultValueという2つのプロパティでプロパティ名と省略時の値を指定できます。

再びhello-maven-pluginを利用する時のpom.xmlに下記のように<configuration>を追加します。(13~15行目)

    <plugins>
      <plugin>
        <groupId>sample.plugin</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <executions>
          <execution>
            <!-- <phase>compile</phase> -->
            <goals>
              <goal>sayhi</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <greeting>compile</greeting>
        </configuration>
      </plugin>
    </plugins>

pom.xmlで指定した「compile」という文字が「Hello, world. – compile」という形で出力されていることが確認できました。

もう少し続きます

そんなわけで今回はMavenプラグインの初めての実装とその実装したプラグイン ゴールをビルドフェーズと紐づける方法について試してみました。

作成したGreetingMojoはそれ自体はなんてことのないプラグインです。
ですが、次回は今回作ったGreetingMojoを使って色々試してみようと思います。

気になったことを試してみて、Mavenプラグインの理解を深めていければと思います。

ちなみに前回の記事はこちら

【Maven研究】Maven Pluginを自分で実装してみたらすごく理解が深まった!①
分かるようでよく分かってないPOM たまたま業務の都合で近頃はJava開発が多いです。Spring Frameworkを使ったWebアプリケーション開発だったり、AWSのLambda開発だったり。 で、そのいずれでも現場の都合(というか好み...

コメント

タイトルとURLをコピーしました