416
431

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Mavenの基本勉強メモ

Posted at

ずっと Gradle 使ってたけど、お仕事で Maven をゴリゴリに使わないといけなくなったのでお勉強。

Maven とは

Java のビルドツール。OSS。
Apache Ant に代わるものとして作られたらしい。

読み方は「メイヴン」または「メイヴェン」(自分はメイヴン派)。

結構昔からあるけど1、現在も開発が続けられているし、 Maven を採用しているプロジェクトも多い印象。
2020年現在、Java のビルドツールといえば Maven か Gradle が選ばれることが多い(と思う)。

2020年現在のメジャーバージョンは3。
Maven 1 と 2 は互換性が無いが、2 と 3 は互換性が保たれている。

環境

>mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: ...\bin\..
Java version: 11.0.6, vendor: AdoptOpenJDK, runtime: ...
Default locale: ja_JP, platform encoding: MS932
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

インストール

※JDK のインストール方法は割愛。

  • 公式サイトからアーカイブ(apache-maven-X.X.X-bin.zip)をダウンロードしてくる
  • 適当なところで解凍する
解凍後の様子
`-apache-maven-X.X.X/
  |-bin/
  |-boot/
  |-conf/
  |-lib/
  |-LICENSE
  |-NOTICE
  `-README.txt

※解凍先のフォルダ(上の例でいうと apache-maven-X.X.X)を、以後は %MAVEN_HOME% と記述する。

  • %MAVEN_HOME%\bin にパスを通す
  • 以下のコマンドを実行してバージョンの情報が出たらインストール完了
インストールの確認
>mvn --version
Apache Maven X.X.X (...)
...

設定ファイル

プロジェクトをまたがって共通な設定は、以下のいずれかのファイルに記述することができる。

  • %MAVEN_HOME%\conf\settings.xml
  • %USERPROFILE%\.m2\settings.xml 2

前者のファイルに設定した内容は、ユーザをまたがってすべてのプロジェクトで共通となる。
後者のファイルに設定した内容は、そのユーザ内でプロジェクトをまたがって共通となる。

前者のファイルはダウンロードした zip の中に入っているが、全ての設定は空になっていてコメントで説明が書かれている。
後者のファイルは最初は存在しないので、前者のファイルをコピーして作ると良い。

プロキシの設定

settings.xml
<settings ...>
  ...
  <proxies>
    <proxy>
      <id>optional</id>
      <active>true</active>
      <protocol>http</protocol>
      <username>proxyuser</username>
      <password>proxypass</password>
      <host>proxy.host.net</host>
      <port>80</port>
      <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
    </proxy>
  </proxies>
  ...
</settings>

必要な設定を書き換えて、前述のいずれかの settings.xml に記述する。

Hello World

プロジェクトを生成する

  • 適当なフォルダでコマンドラインを開き、以下のコマンドを実行する。
> mvn archetype:generate
  • 初回は色々ダウンロードなどの処理が走った後に、次のような一覧が表示される。
...
Choose archetype:
1: internal -> org.appfuse.archetypes:appfuse-basic-jsf (AppFuse archetype for creating a web application with Hibernate, Spring and JSF)
2: internal -> org.appfuse.archetypes:appfuse-basic-spring (AppFuse archetype for creating a web application with Hibernate, Spring and Spring MVC)
3: internal -> org.appfuse.archetypes:appfuse-basic-struts (AppFuse archetype for creating a web application with Hibernate, Spring and Struts 2)
...
16: internal -> org.apache.maven.archetypes:maven-archetype-quickstart ()
...
57: internal -> org.fusesource.scalate.tooling:scalate-archetype-empty (Generates a Scalate empty web application)
58: internal -> org.fusesource.scalate.tooling:scalate-archetype-guice (Generates a Scalate Jog web application)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 16:
  • Choose archetype と表示され、使用するアーキタイプの番号を入力するように促される
  • とりあえずデフォルトとなっている 16 (org.apache.maven.archetypes:maven-archetype-quickstart) を選択してみる(そのままEnter を入力すれば、デフォルトの番号が選択される)
Define value for property 'groupId': : 【com.example】
Define value for property 'artifactId': : 【hello-world】
Define value for property 'version':  1.0-SNAPSHOT: :【】
Define value for property 'package':  com.example: :【】
Confirm properties configuration:
groupId: com.example
artifactId: hello-world
version: 1.0-SNAPSHOT
package: com.example
 Y: : 【y】
  • 作成するプロジェクトの基本情報の入力が促されるので、適当に入力していく
    • 上記例は、【】で囲った部分がキーボードで入力した内容となる(空の 【】 は何も入力せずに Enter した箇所)
    • それぞれの意味はおいおい説明
  • 最後に入力した情報で作成してよいか聞かれるので、問題なければ y を入力して Enter
  • BUILD SUCCESS と表示されれば成功

生成されたプロジェクトの確認

  • カレントフォルダに artifactId で指定した名前と同じ名前のフォルダが作られている
    • 上の例で作った場合は hello-world というフォルダが作られている
  • hello-world フォルダの中身は次のようになっている
hello-worldフォルダの中身
hello-world/
|-pom.xml
`-src/
  |-main/java/
  | `-com/example/
  |    `-App.java
  `-test/java/
    `-com/example/
      `-AppTest.java
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>com.example</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>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</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>
        ...
      </plugins>
    </pluginManagement>
  </build>
</project>
App.java
package com.example;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }
}
  • 簡単な Hello World プログラムのプロジェクトが生成されている

コンパイルして実行する

  • 以下のコマンドを実行してソースコードをコンパイルする
コンパイル
> mvn compile
  • これまた、初回は色々ダウンロード処理が走る(2回目以降はダウンロードがなくなるので速くなるはず)
  • BUILD SUCCESS と表示されたら成功
  • hello-world/target/classes/ の下に、 App.java のコンパイル結果が出力されている
  • 以下のコマンドで実行する
hello-worldを実行する
> java -cp target\classes com.example.App
Hello World!

説明

アーキタイプ

  • 最初の mvn archetype:generate は、アーキタイプと呼ばれるプロジェクトをひな形から自動生成する仕組みを実行している
    • ここでは、最もシンプルな maven-archetype-quickstart を選択してプロジェクトを自動生成している

POM

Maven – Introduction to the POM

Maven の設定は pom.xml という XML ファイルで記述する。
POM は、 Project Object Model の頭文字を取った略となっている。

Maven はビルド対象をプロジェクトという単位で管理する。
POM は、そのプロジェクトについての様々な設定を記述するためのファイルとなる。

Super POM

POM には親子関係があり、全ての POM の最上位の親としてSuper POMというものが存在する。
例えば、最新の Super POM は次のページで確認できる。

Maven Model Builder – Super POM

プロジェクトの POM に設定がない場合は、基本的に親 POM の設定が継承されるようになっている。
つまり、 Super POM はすべてのプロジェクトで適用されるデフォルトの設定が記述された POM ということになる。

最小構成の POM

最小構成の POM を作るとすると、 pom.xml の内容は次のようになる。

最小構成の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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
</project>
  • 一番上に <project> タグを記述する
  • その中には、少なくとも次の4つのタグを記述しなければならない
    • <modelVersion>
    • <groupId>
    • <artifactId>
    • <version>
  • <modelVersion> は、 POM のバージョンを指定している
    • これは基本的に変わることが無いので、とりあえずおまじない的にこの値を指定しておけばいいと思う
  • <groupId>, <artifactId>, <version> の3つは、プロジェクトを一意に識別するための情報を定義している
    • プロジェクトの完全修飾名は、 <groupId>:<artifactId>:<version> となる
    • つまり、上記設定の場合は example:hello:1.0.0 がこのプロジェクトの完全修飾名となる
    • <groupId>
      • groupId には、そのプロジェクトが属する組織や上位プロジェクトを識別できる名前を指定する
      • 例えば、 Maven が提供する多くのプラグインは org.apache.maven.plugins という groupId が指定されている
      • . は付いてもついていなくてもいい(junit などは付いていない)
      • groupId の値と、そのプロジェクトの Java パッケージ構成が一致している必要はないが、一致させておいたほうが混乱が少なくて無難
    • <artifactId>
      • そのプロジェクトを識別する名前を指定する
      • 例えば、 Maven が提供しているコンパイラプラグインは maven-compiler-plugin という artifactId となっている
    • <version>
      • version は、そのプロジェクトのバージョンを指定する
      • groupIdartifactId によってプロジェクトが一意に特定され、さらに version によってバージョンが特定されることになる
  • この pom.xml に記述されていない設定は、基本的に Super POM から継承される3
    • つまり、記述されていない <repository> の設定は、 <url>https://repo.maven.apache.org/maven2</url> が採用されていることになる
    • ただ、実は Super POM に記述されてないけどデフォルトで採用されている設定も存在している(<packaging> とか)
      • そのへんの話はおいおい

開発バージョンを表すプレフィックス

  • アーティファクトのバージョン番号について、決まった採番のルールはない(たぶん)
  • ただし、末尾に -SNAPSHOT とついたバージョンについては、特別な意味がある
  • -SNAPSHOT プレフィックスが付いたバージョンは、それが開発中のバージョンであることを表している
  • 開発中のバージョンは、アーティファクト(jar)の中身が更新される可能性があることを表している
  • 一方で、 SNAPSHOT が付いていないバージョンは基本的にリリースバージョンを表しており、中身が更新されない(というのがお約束)
  • あくまでお約束なので、仕組みで縛られているわけではない
    • 後述するリポジトリ(Nexus)の機能で、リリースバージョンは更新不可、SNAPSHOTは更新可にするような制御は可能

変数

pom.xml の中では、同じ値を複数の箇所で重複して記述してしまう問題を回避するため、変数を参照できるようになっている。

プロジェクトモデル変数

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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>project.groupId = ${project.groupId}</echo>
            <echo>project.artifactId = ${project.artifactId}</echo>
            <echo>project.version = ${project.version}</echo>
            <echo>project.build.directory = ${project.build.directory}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 何やらいきなりいっぱい現れたが、ここで重要なのは次の箇所になる
重要な部分だけ抽出
            <echo>project.groupId = ${project.groupId}</echo>
            <echo>project.artifactId = ${project.artifactId}</echo>
            <echo>project.version = ${project.version}</echo>
            <echo>project.build.directory = ${project.build.directory}</echo>
  • いくつかの変数の値を出力(echo)している
    • この周りにいっぱい書いてあるやつは、 echo ができるようにするためにプラグインを追加している
  • これを実行すると、次のようになる
実行結果
> mvn antrun:run
...
     [echo] project.groupId = example
     [echo] project.artifactId = hello
     [echo] project.version = 1.0.0
     [echo] project.build.directory = F:\tmp\maven\hello\target
...
  • ${変数の参照式} で記述した部分が、式の評価結果に置き換えられて出力されているのが分かる
  • この例で参照している変数は、全て**プロジェクトモデル変数(Project Model Variable)**と呼ばれるものになる
  • 要するに、 pom.xml 内の <project> 配下のタグの値を参照している
    • project.version は、 <project><version> の値を参照している
    • project.build.directory は、この pom.xml の中にはないが Super POM の中で宣言されている

式の構文

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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>project.class = ${project.class}</echo>
            <echo>project.getClass() = ${project.getClass()}</echo>
            <echo>plugins[0].artifactId = ${project.build.plugins[0].artifactId}</echo>
            <echo>plugins[1].artifactId = ${project.build.plugins[1].artifactId}</echo>
            <echo>plugins[2].artifactId = ${project.build.plugins[2].artifactId}</echo>
          </target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jdeps-plugin</artifactId>
        <version>3.1.2</version>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn antrun:run
...
     [echo] project.class = class org.apache.maven.model.Model
     [echo] project.getClass() = ${project.getClass()}
     [echo] plugins[0].artifactId = maven-antrun-plugin
     [echo] plugins[1].artifactId = maven-jdeps-plugin
     [echo] plugins[2].artifactId = ${project.build.plugins[2].artifactId}
  • ${...} の中に記述する式は、基本的にドット (.) 区切りでオブジェクトのプロパティを参照していけるようになっている
    • foo という名前のプロパティを参照すると、裏では getFoo() (または isFoo())というメソッドが実行されている
    • つまり、 Getter さえあれば対応するプロパティ名で参照できる(class も参照できる)
  • ただし、参照できるのはプロパティのみで、メソッドを直接実行したりはできない
  • プロパティが配列または List の場合は、角括弧 ([]) を使ってインデックス参照ができる
  • 式が正常に評価できない場合は、ただの文字列として処理される
  • project 変数の実体は、 org.apache.maven.model.Model というクラスのインスタンスとなっている
    • この Model インスタンスが ReflectionValueExtractor に渡されることによって式が評価されている

Mapの参照

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>${project.build.pluginsAsMap(org.apache.maven.plugins:maven-antrun-plugin).id}</echo>
            <echo>${project.build.pluginsAsMap(org.apache.maven.plugins:maven-jdeps-plugin).id}</echo>
          </target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jdeps-plugin</artifactId>
        <version>3.1.2</version>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn antrun:run
...
     [echo] org.apache.maven.plugins:maven-antrun-plugin:1.8
     [echo] org.apache.maven.plugins:maven-jdeps-plugin:3.1.2
  • プロパティが Map の場合、 fooMap(<key>) という形でキー指定の参照ができる
    • <key> はダブルクォーテーション (") などで括る必要はなく、そのまま String のキーとして使用される
  • pluginsAsMap というプロパティは、 PluginContainer というクラスに定義されたメソッドを参照している
    • この Map は、キーに Plugin#getKey() の結果、値に該当する Plugin インスタンスが設定されている
      • Plugin#getKey() は、そのプラグインの groupIdartifactId をコロン (:) でつなげた値を返す
    • project.build で参照できる Build クラスは、この PluginContainer を継承している
  • このように、一部のクラスには ListMap 形式に変換したプロパティを提供しているものがある

各クラスのプロパティ

Model から参照できる各クラスやプロパティの全体像をクラス図でまとめた。

maven.jpg

  • 赤線が継承
  • 青線が単一の参照
  • 緑線が List での参照

特殊変数

  • プロジェクトモデルには含まれていないが、特別に定義されていて参照が可能な変数がいくつか存在する
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  
  <build>
    ...
          <target>
            <echo>project.basedir = ${project.basedir}</echo>
            <echo>project.baseUri = ${project.baseUri}</echo>
            <echo>maven.build.timestamp = ${maven.build.timestamp}</echo>
          </target>
    ...
  </build>
</project>
実行結果
     [echo] project.basedir = F:\tmp\maven\hello
     [echo] project.baseUri = file:///F:/tmp/maven/hello/
     [echo] maven.build.timestamp = 2020-03-04T13:45:10Z
  • 次の3つは、**特殊変数(Special Variables)**として暗黙的に定義されている変数となる
    • project.basedir
      • プロジェクト自体のフォルダ
    • project.baseUri
      • project.basedir を URI で表したもの
    • maven.build.timestamp
      • 実行時のタイムスタンプ(UTC)

タイムスタンプのフォーマットを指定する

  • maven.build.timestamp.format というプロパティを宣言することで、 maven.build.timestamp のフォーマットを任意に指定することができる
  • なお、フォーマットの書式は SimpleDateFormat に従う
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <properties>
    <maven.build.timestamp.format>yyyy/MM/dd HH:mm:ss</maven.build.timestamp.format>
  </properties>
  
  <build>
    ...
            <echo>maven.build.timestamp = ${maven.build.timestamp}</echo>
    ...
  </build>
</project>
実行結果
     [echo] maven.build.timestamp = 2020/03/04 13:49:49+00:00

プロパティ

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

  <properties>
    <foo>FOO!!</foo>
    <fizz.buzz>FIZZ.BUZZ!?</fizz.buzz>
    <hoge-fuga>HOGE-FUGA??</hoge-fuga>
  </properties>
  
  <build>
    ...
            <echo>foo = ${foo}</echo>
            <echo>fizz.buzz = ${fizz.buzz}</echo>
            <echo>hoge-fuga = ${hoge-fuga}</echo>
            <echo>FOO = ${FOO}</echo>
    ...
  </build>
</project>
実行結果
     [echo] foo = FOO!!
     [echo] fizz.buzz = FIZZ.BUZZ!?
     [echo] hoge-fuga = HOGE-FUGA??
     [echo] FOO = ${FOO}
  • <properties> タグの中で独自の変数(プロパティ)を宣言できる
  • -. を名前に含めることが可能
  • 大文字・小文字は区別される

環境変数

xml.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  
  <build>
    ...
            <echo>hoge = ${env.hoge}</echo>
            <echo>Hoge = ${env.Hoge}</echo>
            <echo>HOGE = ${env.HOGE}</echo>
    ...
  </build>
</project>
実行結果(Windows上で動かした場合)
> set Hoge=Hello

> mvn antrun:run
...
     [echo] hoge = ${env.hoge}
     [echo] Hoge = ${env.Hoge}
     [echo] HOGE = Hello
...
実行結果(Linux上で動かした場合)
$ export Hoge=Hello

$ mvn antrun:run
...
     [echo] hoge = ${env.hoge}
     [echo] Hoge = Hello
     [echo] HOGE = ${env.HOGE}
  • ${env.環境変数名} で、 OS の環境変数の値を参照できる
  • Windows 上で動く場合、 env の変数名はすべて大文字で正規化されている
    • つまり、 Windows 上で宣言されている環境変数の名前がたとえ Path であっても、 pom.xml 上で参照するときは ${env.PATH} と大文字で記述しなければならない
  • あくまで Windows 上での話で、 Linux 上で動かす場合は大文字・小文字を区別したそのままの名前で指定する
  • まぁ、環境変数は全部大文字で宣言するのが一般的だと思うので、 pom.xml も全部大文字で書いておけば事故ることはないと思う

システムプロパティ

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  
  <properties>
    <foo.bar>foo-bar</foo.bar>
    <fizz.buzz>fizz-buzz</fizz.buzz>
  </properties>

  <build>
    ...
            <echo>java.version = ${java.version}</echo>
            <echo>java.vm.vendor = ${java.vm.vendor}</echo>
            <echo>foo.bar = ${foo.bar}</echo>
            <echo>fizz.buzz = ${fizz.buzz}</echo>
    ...
  </build>
</project>
実行結果
> mvn antrun:run -Dfoo.bar=FOO-BAR
...
     [echo] java.version = 11.0.6
     [echo] java.vm.vendor = AdoptOpenJDK
     [echo] foo.bar = FOO-BAR
     [echo] fizz.buzz = fizz-buzz
  • Java のシステムプロパティ(System.getProperties() で取得できる値)を、そのまま ${システムプロパティ名} で参照できる
  • <properties> で同じ名前のプロパティが宣言されている場合は、システムプロパティで指定した値が優先される

リポジトリ

  • Maven の大事な構成要素の1つに**リポジトリ(Repository)**がある
  • Maven では、作成したプロジェクトの成果物(アーティファクト)をリポジトリに保存して管理する
    • アーティファクトの実体は、普通はそのプロジェクトをビルドしてできた jar ファイル

セントラルリポジトリ

  • リポジトリには2種類ある
    • ローカルリポジトリ
    • リモートリポジトリ
  • デフォルトで使用されるリモートリポジトリとして、セントラルリポジトリというものが存在する
    • ↑のリンクをブラウザで開けば、様々なサブディレクトリが存在することが分かる
    • ディレクトリを適当に辿っていくと、最終的に jar ファイルが配置されたディレクトリにたどり着く
    • 例えばこれは、Spring Framework の core の ver 5.2.4.RELEASE が配置されたディレクトリになる
  • このように、セントラルリポジトリには世界中の様々な OSS のアーティファクトが収められている
  • セントラルリポジトリの管理は Sonatype という会社が行っている
    • 申請を行えば、誰でもセントラルリポジトリで自分の OSS を公開できる(無料)
    • ただし、申請は英語
    • 申請の単位は groupId ごと
    • 「Maven セントラルリポジトリ 公開 手順」とかで検索すれば、いろいろ解説記事が出てくる
  • セントラルリポジトリを直接ブラウザで開いたものは検索がしづらいので、普通は Maven Repository: Search/Browse/Explore のような検索サイトを使う
    • もしくは、使用したいライブラリの公式ページに書いてある情報を参照する

依存関係の解決

  • Maven の強力な機能の1つとして、依存関係の解決がある
  • pom.xml では、そのプロジェクトが使用するアーティファクトを依存関係として次のように定義できる
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
    </dependency>
  </dependencies>
</project>
  • <dependencies> の下の <dependency> が、1つのアーティファクトを指している
  • このように宣言すると、 Maven はプロジェクトをビルドするときに自動的に <dependencies> で宣言したアーティファクトをリモートリポジトリからダウンロードしてきてくれて、クラスパスに追加してくれる
    • つまり、ライブラリを手で落としてくる手間が省ける
  • さらに、リポジトリに格納されたアーティファクトには、それ自身の pom.xml も格納されている
    • もしプロジェクトで指定したアーティファクトの pom.xml に、さらに <dependencies> があった場合、 Maven はその依存アーティファクトも自動的にダウンロードしてくれる
    • つまり、芋づる式にすべての依存関係を解決してくれる仕組みになっている
  • Maven は、この仕組みのおかげで依存ライブラリの管理が非常に楽になっている
    • Maven の前身である Apache Ant は依存関係を手動で管理しなければならかったので、非常に辛かった
    • この仕組みは、 Maven の後発である Gradle でも利用されている

ローカルリポジトリ

  • リモートリポジトリからダウンロードしてきたアーティファクトやメタ情報(pom.xml とか)は、 Maven を実行したマシンのローカルにキャッシュされる
  • このキャッシュ先のことを、ローカルリポジトリと呼ぶ
  • 毎回ビルドのたびにリモートリポジトリにアクセスしていると、時間がかかるしネットワークが使用できない環境ではビルドもできなくなってしまう
  • したがって、 Maven はまず先にローカルリポジトリを検索するようになっている
    • ローカルリポジトリに目的のアーティファクトが存在しない場合に、リモートリポジトリに検索に行くようになっている
  • リモートリポジトリでアーティファクトが見つかった場合は、それをダウンロードし、ローカルリポジトリに保存する
    • これにより、2回目以降はローカルリポジトリを参照すれば良くなるので、ネットワークアクセスなしでもプロジェクトをビルドできる
  • ローカルリポジトリの場所は、デフォルトでは %USERPROFILE%\.m2\repository になる
    • Linux 系の OS なら $HOME/.m2/repository
  • ローカルリポジトリにキャッシュされたアーティファクトは、何もしなければ残り続ける
    • ディスク容量が足りなくなったなどの理由がない限りは、特に消す必要もない

プライベートリポジトリ

  • Nexus Repository OSS という OSS のアプリケーションを使用すると、独自のリモートリポジトリを構築できる
    • Nexus はセントラルリポジトリを管理している Sonatype 社によって開発されている
    • 有償版と無償の OSS 版がある
  • 例えば、社内だけで共有したいアーティファクトがある場合に、イントラネット内で Nexus サーバーを構築すれば簡単にプライベートリポジトリとして利用できる
  • 使用するリモートリポジトリは、 pom.xml で次のように指定できる
    • ホスト名やポート番号は実際の環境に合わせて要調整
リモートリポジトリを指定した場合のpom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <repositories>
    <repository>
      <id>my-private-repository</id>
      <name>My Private Repository</name>
      <url>http://private.repository.host/repository</url>
      <layout>default</layout>
    </repository>
  </repositories>
</project>
  • さらに、 Nexus は他のリモートリポジトリのプロキシとしての機能も持っている
    • 例えば、次のような構成をとっていたとする
      • Nexus リポジトリを、セントラルリポジトリのプロキシとして構築する
      • Nexus リポジトリをリモートリポジトリとして pom.xml で設定する
    • プロジェクトに依存アーティファクトがある場合、 Maven はまずローカルリポジトリを検索する
    • ローカルリポジトリにない場合は、次にリモートリポジトリとして指定した Nexus リポジトリを検索する
    • Nexus リポジトリにもない場合は、 Nexus がセントラルリポジトリを検索する
    • そして、セントラルリポジトリからダウンロードしてきたアーティファクトを Maven に返してくれる
    • このとき、 Nexus リポジトリはセントラルリポジトリからダウンロードしたアーティファクトを内部にキャッシュする
    • もし同じアーティファクトに対する検索リクエストが来た場合は、キャッシュしたアーティファクトを返すようになる
    • これにより、ネットワークトラフィックを抑えたり、何らかの障害でインターネットが繋がらなくなっても、イントラネット内の Nexus リポジトリにさえつながれば依存関係の解決ができるようになる

プロキシのイメージ

maven.jpg

プラグイン(基本)

Maven は、すべての処理が**プラグイン(Plugin)**によって行われる。

例えば、 Java のソースコードをコンパイルするための処理は maven-compiler-plugin によって提供されている。

Maven プロジェクト自身が提供する基本的なプラグインは、Maven – Available Plugins で一覧を確認できる。

プラグインのゴール(Goal)

  • プラグインに定義された個々のタスク(処理)のことを**ゴール(Goal)**と呼ぶ
  • 使用したいプラグインがどのようなゴールを持つかは、それぞれのプラグインのドキュメントを参照する

ゴールの実行方法

プラグインのゴールを実行する方法は、大きく次の2つがある。

  1. コマンドラインで直接指定して実行する
  2. フェーズに紐付けて実行する

2つ目のフェーズに紐付ける方法は置いておいて、先に1の直接指定する方法について確認する。

完全修飾名指定

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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
</project>
jdepsプラグインを実行する
> mvn org.apache.maven.plugins:maven-jdeps-plugin:3.1.2:jdkinternals
...
classes -> java.base
   example          -> java.io            java.base
   example          -> java.lang          java.base
...
  • 何やら長ったらしい引数を mvn コマンドに渡している
  • これは、先程の maven-jdeps-plugin の完全修飾名とゴール名を指定している
  • 書式にすると <完全修飾名>:<ゴール名> といった感じ
    • <完全修飾名> は、ここでは org.apache.maven.plugins:maven-jdeps-plugin:3.1.2 で、
    • <ゴール名> は、 jdkinternals になる
  • 完全修飾名指定の場合は、その情報を元にリポジトリからプラグインの jar ファイルが取得され、ゴールが実行される

prefix 指定(プラグインの設定が無い場合)

  • 毎回完全修飾名を入力するのは辛いので、以下のような省略した記述もできるようになっている
省略した場合
> mvn jdeps:jdkinternals
...
classes -> java.base
   example          -> java.io            java.base
   example          -> java.lang          java.base
...
  • こちらは、 <prefix>:<ゴール名> という書式の指定方法になる
    • 完全修飾名指定なのか prefix 指定なのかは、コロン (:) の数で区別されている4
      • : が3つなら、 groupId:artifactId:version:goal という完全修飾名での指定
      • : が2つなら、 groupId:artifactId:goal というバージョン番号抜きでの指定
      • : が1つなら、 prefix:goal というプレフィックスでの指定
  • prefix 指定の場合、次のような感じで完全修飾名が解決される
  • まず、 groupdId を以下のいずれかとする
    • org.apache.maven.plugin
    • org.codehaus.mojo
  • 次に、 groupId ごとにメタデータ (maven-metadata.xml) を調べる
  • maven-metadata.xml の中身を見ると、 artifactId ごとに prefix の対応が列挙されているのがわかる
org.apache.maven.pluginのmaven-metadata-xml
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
  <plugins>
    <plugin>
      <name>Apache Maven ACR Plugin</name>
      <prefix>acr</prefix>
      <artifactId>maven-acr-plugin</artifactId>
    </plugin>
    ...
    <plugin>
      <name>Apache Maven JDeps Plugin</name>
      <prefix>jdeps</prefix>
      <artifactId>maven-jdeps-plugin</artifactId>
    </plugin>
    ...
  </plugins>
</metadata>
  • この maven-metadata.xml の中からコマンドラインで指定された prefix と一致するものを見つけ、その <artifactId>artifactId とする
    • コマンドラインで指定した prefix は jdeps なので、 maven-jdeps-pluginartifactId になる
  • 次は、 artifactId ごとのメタデータ(maven-metadata.xml)を調べる
maven-jdeps-pluginのmaven-metadata.xml
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jdeps-plugin</artifactId>
  <versioning>
    <latest>3.1.2</latest>
    <release>3.1.2</release>
    <versions>
      <version>3.0.0</version>
      <version>3.1.0</version>
      <version>3.1.1</version>
      <version>3.1.2</version>
    </versions>
    <lastUpdated>20190619052916</lastUpdated>
  </versioning>
</metadata>
  • リリースバージョン(<release>)に設定されている値を version とする
    • リリースバージョンがなければ、最新バージョン(<latest>5version とする
  • 以上で決定した groupId, artifactId, version を、完全修飾名とする
    • groupId : org.apache.maven.plugin
    • artifactId : maven-jdeps-plugin
    • version : 3.1.2

プラグインを明示的に設定する

  • ↑の方法はメタデータを元にプラグインのバージョンが決まっている
    • おそらく新しいバージョンが出たら最新版を使うことになるが、動きを固定できないとビルドが不安定になる恐れがある
    • したがって、通常はプロジェクトごとに使用するプラグインのバージョンを固定する
  • また、↑の場合 prefix 指定ができるのは groupIdorg.apache.maven.pluginorg.codehaus.mojo のいずれかの場合に限られる6
    • これら以外の groupId のプラグインを prefix 指定で利用したい場合は、プラグインを明示的に設定する必要がある
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      <plugin>
        <groupId>org.asciidoctor</groupId>
        <artifactId>asciidoctor-maven-plugin</artifactId>
        <version>1.5.8</version>
      </plugin>
    </plugins>
  </build>
</project>
  • 個々のプラグインは、 <plugin> タグで設定する
    • <plugin> は、 <build><plugins> の下に列挙する
    • <groupId>, <artifactId>, <version> で、プラグインの完全修飾名を指定する
  • ここでは Asciidoctor の Maven プラグイン を設定している
    • この設定により、 asciidoctor-mavne-plugin のバージョンは 1.5.8 に固定されることになる
asciidoctor-maven-pluginを実行している様子
> mvn asciidoctor:process-asciidoc
...
[INFO] BUILD SUCCESS
...

prefix 指定の解決(プラグインの指定がある場合)

  • ↑のように <plugin> でプラグインが明示されている場合、 prefix 指定から完全修飾名を解決する方法が少し変わる
  • まず Maven は、 pom.xml で明示的に設定されている各プラグインの plugin.xml を確認していく
    • 普通、 plugin.xml はプラグインの jar 内の META-INF/maven の下に格納されている
  • 例えば、 asciidoctor-mavne-pluginplugin.xml は次のようになっている
asciidoctor-maven-pluginのplugin.xml
<?xml version="1.0" encoding="UTF-8"?>

<!-- Generated by maven-plugin-tools 3.5 on 2019-03-30 -->

<plugin>
  <name>Asciidoctor Maven Plugin and Doxia Parser</name>
  <description>Asciidoctor Maven Plugin and Doxia Parser (for Maven Site integration)</description>
  <groupId>org.asciidoctor</groupId>
  <artifactId>asciidoctor-maven-plugin</artifactId>
  <version>1.5.8</version>
  <goalPrefix>asciidoctor</goalPrefix>
  <isolatedRealm>false</isolatedRealm>
  <inheritedByDefault>true</inheritedByDefault>
  ...
  • ここで重要なのは、 <goalPrefix> に設定されている値になる
    • この値がコマンドラインで指定された prefix と等しいプラグインの groupIdartifactId が採用される
  • 最後に、 version は次のように決まる
    • pom.xml で指定されていれば、そのバージョンが採用される
    • 指定されていない場合は、 artifactId ごとのメタデータ(maven-metadata.xml)から決定される(プラグイン指定なしの場合と同じ方法)

artifactId と prefix の関係

  • ここまでの説明で、 artifactId と prefix には、本質的には関連がないことがわかる
    • maven-metadata.xml<prefix> か、 plugin.xml<goalPrefix> で prefix が決まっている
  • しかし、実際に存在するプラグインたちには、 artifactId と prefix に次のような関係がある
    • Maven プロジェクトが提供している公式プラグインの場合
      • artifactIdmaven-XXX-plugin の場合、 XXX が prefix となる
    • その他のプラグインの場合
      • artifactIdXXX-maven-plugin の場合、 XXX が prefix となる
  • これらは、そういう命名ルールが推奨されている結果であって、この名前じゃないとプラグインが作れないわけではない
    • 作ろうと思えば、この命名ルールとは全く異なる artifactId のプラグインを作ることもできる
    • ただし、 maven-XXX-plugin という命名は、それが Maven の公式プラグインであること表すためのものなので、公式以外がこの命名を使うと商標侵害になるので要注意
    • 一般的でない命名をしたところで利用者が混乱するだけでメリットがないので、プラグインを自作するなら特別な理由がない限りは XXX-maven-plugin という artifactId にしておくのが無難
  • ということで、 artifactId と prefix は本質的には関連しないが、実用上は関連しているものと思って問題ない(と思う)
    • なので、 artifactIdfoo-maven-plugin なら、 prefix は foo と思っていい(と思う)
    • 逆に、 prefix が foo なら、 artifactIdfoo-maven-plugin と思っていい(と思う)(Maven プロジェクトが提供しているプラグインでない場合)

プラグインの設定(パラメータ)

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>Hello World!!</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn antrun:run
...
main:
     [echo] Hello World!!
...
  • プラグインの設定(パラメータ)は、 <plugin> の下の <configuration> タグで記述できる
  • <configuration> の下に指定できるタグは、プラグインのゴールごとに異なる
    • ここでは maven-antrun-plugin というプラグインを使用している
    • このプラグインには run というゴールがある
    • run ゴールの説明ページに、 <configuration> で指定可能なパラメータが載っているのがわかる
  • 複数のゴールを持つプラグインの場合、 <configuration> に指定したパラメータはすべてのゴールに対して適用されることになる

プラグインの説明を確認する

  • プラグインにどういうゴールがあり、どういうパラメータが指定できるのかは、個々のプラグインの説明ページを見れば確認できる
  • しかし、いちいちページを開くのが面倒なときは maven-help-plugin で確認することもできる
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-help-plugin</artifactId>
        <version>3.2.0</version>
        <configuration>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-antrun-plugin</artifactId>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn help:describe
...
Name: Apache Maven AntRun Plugin
Description: Runs Ant scripts embedded in the POM
Group Id: org.apache.maven.plugins
Artifact Id: maven-antrun-plugin
Version: 1.8
Goal Prefix: antrun

This plugin has 2 goals:

antrun:help
  Description: Display help information on maven-antrun-plugin.
    Call mvn antrun:help -Ddetail=true -Dgoal=<goal-name> to display parameter
    details.

antrun:run
  Description: Maven AntRun Mojo.
    This plugin provides the capability of calling Ant tasks from a POM by
    running the nested Ant tasks inside the <tasks/> parameter. It is
    encouraged to move the actual tasks to a separate build.xml file and call
    that file with an <ant/> task.

For more information, run 'mvn help:describe [...] -Ddetail'
...
  • describe というゴールを実行することで、 <configuration> で指定したプラグインの説明を確認できる
    • <groupId><artifactId> でプラグインを maven-antrun-plugin に特定できるようにしている
  • describe ゴールの説明ページを見るとわかるが、 <goal> でゴールを絞ったり <detail> で詳細(ゴールごとに指定できるパラメータ)の出力もできる
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-help-plugin</artifactId>
        <version>3.2.0</version>
        <configuration>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-antrun-plugin</artifactId>
          <goal>run</goal>
          <detail>true</detail>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn help:describe
...
antrun:run
  Description: Maven AntRun Mojo.
    ...
  Implementation: org.apache.maven.plugin.antrun.AntRunMojo
  Language: java

  Available parameters:

    ...

    target
      The XML for the Ant target. You can add anything you can add between
      <target> and </target> in a build.xml.

    ...
...
  • ↑の例は省略しているが、全てのパラメータが説明付きで出力される

システムプロパティで指定する

  • help:describe<configuration> で指定していたパラメータは、システムプロパティから指定することもできる
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-help-plugin</artifactId>
        <version>3.2.0</version>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn help:describe -Dplugin=antrun
...
Name: Apache Maven AntRun Plugin
Description: Runs Ant scripts embedded in the POM
Group Id: org.apache.maven.plugins
Artifact Id: maven-antrun-plugin
Version: 1.8
Goal Prefix: antrun

This plugin has 2 goals:

antrun:help
  Description: Display help information on maven-antrun-plugin.
    ...

antrun:run
  Description: Maven AntRun Mojo.
    ...

For more information, run 'mvn help:describe [...] -Ddetail'
...
  • Maven を実行するときのシステムプロパティとして -Dplugin=antrun を指定している
  • このように、ゴールのパラメータの中にはシステムプロパティで値を渡すことができるものがある
    • すべてのパラメータがシステムプロパティで指定できるわけではない
  • あるパラメータがシステムプロパティで指定できるかどうかは、そのパラメータのドキュメントに User property が書かれているかどうかで確認できる
    • 例えば、 plugin パラメータのドキュメント では、 User Property: plugin と書かれているのが確認できる
    • これは、 plugin という名前のプロパティで値を指定できることを表している
    • パラメータ名とプロパティ名は常に一致しているわけではない
      • plugin はたまたま一致しているだけで、 antrun の skip のように一致していないものもある
  • ここでプロパティと言っているのは、システムプロパティに限らず pom.xml 上の <properties> でも指定できることを表している
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <properties>
    <plugin>jdeps</plugin>
  </properties>

  <build>
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-help-plugin</artifactId>
        <version>3.2.0</version>
      </plugin>
    </plugins>
  </build>
</project>
  • <properties> で、 pluginjdeps を設定している
実行結果(システムプロパティでpluginを指定しない場合)
> mvn help:describe
...
Name: Apache Maven JDeps Plugin
Description: The JDeps Plugin uses the jdeps tool to analyze classes for
  internal API calls.
Group Id: org.apache.maven.plugins
Artifact Id: maven-jdeps-plugin
Version: 3.1.2
Goal Prefix: jdeps
...
実行結果(システムプロパティでpluginを指定した場合)
> mvn help:describe -Dplugin=antrun
...
Name: Apache Maven AntRun Plugin
Description: Runs Ant scripts embedded in the POM
Group Id: org.apache.maven.plugins
Artifact Id: maven-antrun-plugin
Version: 1.8
Goal Prefix: antrun
...
  • システムプロパティを指定していない場合は、 <properties> で指定した値(jdeps)が採用された
  • システムプロパティを指定した場合は、そちらの値(antrun)が採用された

ライフサイクルとフェーズ

  • 「ビルド」と一口に言っても、その内容は様々な処理を含んでいる
  • 例えば、簡単な Java プログラムをビルドするには、次のような処理が考えられる
    1. ソースコードをコンパイルする
    2. リソースファイルを収集する
    3. テストコードをコンパイルする
    4. テスト用のリソースファイルを収集する
    5. テストコードを実行する
    6. コンパイル結果とリソースファイルをまとめて、 jar などのアーカイブにパッケージングする
    7. アーカイブを所定の場所に格納する
  • Maven では、これら1つ1つの処理を**フェーズ(Phase)**と呼ぶ
  • そして、一連のフェーズのセットを**ライフサイクル(Lifecycle)**と呼ぶ

組み込みのライフサイクル

  • Maven には、標準で次の3つのライフサイクルが用意されている
    • default
    • clean
    • site

default ライフサイクル

  • default ライフサイクルは、プロジェクトをビルドしてデプロイするまでのライフサイクルを定義している
  • default ライフサイクルは、次のフェーズで構成されている
    1. validate
    2. initialize
    3. generate-sources
    4. process-sources
    5. generate-resources
    6. process-resources
    7. compile
    8. process-classes
    9. generate-test-sources
    10. process-test-sources
    11. generate-test-resources
    12. process-test-resources
    13. test-compile
    14. process-test-classes
    15. test
    16. prepare-package
    17. package
    18. pre-integration-test
    19. integration-test
    20. post-integration-test
    21. verify
    22. install
    23. deploy

clean ライフサイクル

  • clean ライフサイクルは、プロジェクトの成果物を削除するライフサイクルを定義している
  • clean ライフサイクルは、次のフェーズで構成されている
    1. pre-clean
    2. clean
    3. post-clean

site ライフサイクル

  • site ライフサイクルは、プロジェクトの Web サイト生成のライフサイクルを定義している
  • site ライフサイクルは、次のフェーズで構成されている
    1. pre-site
    2. site
    3. post-site
    4. site-deploy

フェーズとプラグインのゴールの関係

  • 各ライフサイクルのフェーズには、そのフェーズで実行するプラグインのゴールが紐付けられている

clean, site ライフサイクルの場合

  • 例えば、 clean ライフサイクルと site ライフサイクルでは、次のようにゴールが紐付けられている

clean プラグイン

フェーズ プラグイン ゴール
pre-clean - -
clean maven-clean-plugin clean
post-clean - -

site プラグイン

フェーズ プラグイン ゴール
pre-site - -
site maven-site-plugin site
post-site - -
site-deploy maven-site-plugin deploy
  • つまり、 clean フェーズを実行すると、 maven-clean-pluginclean ゴールが実行されることになる

default ライフサイクルの場合

  • default ライフサイクルの場合、フェーズとゴールの紐付けは固定ではない
  • default ライフサイクルで実行されるゴールは、そのプロジェクトの packaging によって変化する

packaging

  • packaging とは、そのプロジェクトをどのようにパッケージ化するかを決める設定値で、次のいずれかの値が指定できる
    • pom
    • jar
    • ejb
    • maven-plugin
    • war
    • ear
    • rar
  • この packaging は、 pom.xml 上で次のように指定する
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  <packaging>jar</packaging>
  ...
</project>
  • ここでは、 packaging として jar を指定している
  • なお、 <packaging> タグは pom.xml で省略することができる
    • 省略した場合、デフォルトは jar になる

packaging ごとのゴール

  • default ライフサイクルのフェーズに紐付けられているゴールは、 packaging によって次のように異なる
  • 一例として、 pomjar に紐付けられたゴールは次のようになっている

pom

フェーズ プラグイン ゴール
install maven-install-plugin install
deploy maven-deploy-plugin deploy

jar

フェーズ プラグイン ゴール
process-resources maven-resources-plugin resources
compile maven-compiler-plugin compile
process-test-resources maven-resources-plugin testResources
test-compile maven-compiler-plugin testCompile
test maven-surefire-plugin test
package maven-jar-plugin jar
install maven-install-plugin install
deploy maven-deploy-plugin deploy

フェーズを実行する

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <modelVersion>4.0.0</modelVersion>

  <groupId>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
</project>
testフェーズを実行する
> mvn test
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
...
  • フェーズは、 mvn コマンドの引数に指定することで実行できる
  • ここでは、 packaging が jar のプロジェクトで test フェーズを実行してみている
    • <packaing> の記述が省略されているので、 packaing はデフォルトの jar になる
  • フェーズを指定して実行した場合、そのフェーズより前のフェーズもすべて順番に実行される
    • 例えば、 test フェーズの前には validateprocess-resources, test-compile といったフェーズが定義されている
    • 上の例では maven-resources-plugin:resources, maven-compiler-plugin:compile, ... と実行されていっていることがわかる
  • あるフェーズが実行されると、そのフェーズに紐付いているゴールがすべて実行される
    • maven-resources-plugin:resources ゴールは resources フェーズに、
      maven-compiler-plugin:compile ゴールは compile フェーズに紐付けらている

フェーズやゴールを複数指定して実行する

> mvn clean compiler:compile
  • フェーズやゴールは、 mvn コマンドに複数指定して実行できる
  • この場合の実行順序は、 cleancompiler:compile の順序になる
  • つまり、引数で指定した順番で実行されるようになっている

フェーズにゴールを紐付ける

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <target>
            <echo>Hello Antrun!!</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn compile
...
[INFO] --- maven-antrun-plugin:1.8:run (default) @ hello ---
[INFO] Executing tasks

main:
     [echo] Hello Antrun!!
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
  • 任意のフェーズに対して、任意のゴールを紐付けることができる
  • フェーズとゴールの紐付けは、プラグインの設定の中で <execution> で行う
  • <phase> でフェーズを指定し、 <goals><goal> で紐付けるゴールを指定する
  • ここでは、 validate フェーズに対して maven-antrun-pluginrun ゴールを紐付けている
  • <goals><goal> という構造からも分かる通り、1つのフェーズに対して同じプラグインの複数のゴールを紐付けることもできる
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jdeps-plugin</artifactId>
        <version>3.2.6</version>
        <executions>
          <execution>
            <phase>verify</phase>
            <goals>
              <goal>jdkinternals</goal>
              <goal>test-jdkinternals</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn verify
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
...
[INFO] --- maven-jdeps-plugin:3.1.2:jdkinternals (default) @ hello ---
...
[INFO] --- maven-jdeps-plugin:3.1.2:test-jdkinternals (default) @ hello ---
  • maven-jdeps-pluginjdkinternalstest-jdkinternalsverify フェーズに紐づけている
  • 1つのフェーズに複数のゴールが紐付けられている場合、それらのゴールは pom.xml 上で宣言されている順番で実行される7
    • つまり、↑の設定の場合は jdkinternalstest-jdkinternals の順番で実行される
    • <goal> タグの順序を変えれば、実行順序を変更できる

複数のフェーズに紐付ける

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <executions>
          <execution>
            <id>foo</id>
            <phase>validate</phase>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
          <execution>
            <id>bar</id>
            <phase>compile</phase>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <target>
            <echo>Hello Antrun!!</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn compile
...
[INFO] --- maven-antrun-plugin:1.8:run (foo) @ hello ---
...
     [echo] Hello Antrun!!
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-antrun-plugin:1.8:run (bar) @ hello ---
...
     [echo] Hello Antrun!!
...
  • <execution> タグを複数書けば、複数のフェーズに対してゴールの紐付けができるようになる
  • <execution> を複数指定する場合は、 <id> タグも指定しなければならない
    • <id> には、その <execution> を識別できる一意な値を設定する
    • この値は、実行時のコンソールに出力されている(maven-antrun-plugin:1.8:run (foo)(foo) の部分)
    • どの <execution> が実行されたのかを識別するための情報になるので、ビルドがうまく行かないときなどのデバッグに役立つ
    • したがって、識別しやすいわかりやすい名前を付けておくのがいい

execution ごとに設定する

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <executions>
          <execution>
            <id>foo</id>
            <phase>validate</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <target>
                <echo>VALIDATE!!</echo>
              </target>
            </configuration>
          </execution>
          <execution>
            <id>bar</id>
            <phase>compile</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <target>
                <echo>COMPILE!!</echo>
              </target>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn compile
...
[INFO] --- maven-antrun-plugin:1.8:run (foo) @ hello ---
...
     [echo] VALIDATE!!
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-antrun-plugin:1.8:run (bar) @ hello ---
...
     [echo] COMPILE!!
...
  • ゴールの設定を記述する <configuration> は、 <execution> ごとにも指定できる
  • これにより、特定のフェーズごとに設定を分けるといったことが可能になる

ゴールを直接実行したときに採用される特別なid

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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <executions>
          <execution>
            <id>default-cli</id>
            <configuration>
              <target>
                <echo>Hello @ default-cli</echo>
              </target>
            </configuration>
          </execution>
          <execution>
            <id>validate-phase</id>
            <phase>validate</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <target>
                <echo>Hello @ validate-phase</echo>
              </target>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn antrun:run
...
     [echo] Hello @ default-cli
...

> mvn validate
...
     [echo] Hello @ validate-phase
...
  • ゴールを直接実行した場合、 id はデフォルトで default-cli になる
  • したがって、この値で <execution><id> を設定しておけば、ゴールを直接実行したときにだけ適用される設定を記述できる

id を指定して実行する

  • Maven の 3.3.1 からは、 <execution><id> を指定してゴールを実行することができるようになっている
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <executions>
          <execution>
            <id>default-cli</id>
            <configuration>
              <target>
                <echo>Hello default-cli!!</echo>
              </target>
            </configuration>
          </execution>
          <execution>
            <id>foo</id>
            <configuration>
              <target>
                <echo>Hello Foo!!</echo>
              </target>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
  • default-clifoo の、2つの <id><execution> を定義している
実行結果
> mvn antrun:run
...
     [echo] Hello default-cli!!

> mvn antrun:run@foo
...
     [echo] Hello Foo!!
  • antrun:run だけで実行した場合は、 default-cli のほうが実行される
  • antrun:run@foo のように、ゴール指定の後ろに @<id> と続けることで、指定された <id><execution> が実行される

デフォルトのフェーズ

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <version>3.1.1</version>
        <executions>
          <execution>
            <goals>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
実行結果
> mvn verify
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
...
[INFO] --- maven-checkstyle-plugin:3.1.1:check (default) @ hello ---
  • maven-checkstyle-plugin を追加して verify フェーズを実行している
  • pom.xml では、 <execution><goal>check</goal> しか指定しておらず、 <phase> の指定はしていない
  • しかし、実行結果を見ると verify フェーズで check ゴールが実行されていることがわかる
  • ゴールは、デフォルトで紐づくフェーズを定義できるようになっている
  • もし <execution> でフェーズが明示されていない場合は、このデフォルトで紐付いているフェーズで実行されるようになっている
  • あるゴールにフェーズが1つも紐付いていない場合、そのゴールはコマンドラインで直接指定しなければ起動できない

プラグインのバージョン指定

  • プラグインは、 <plugin> で明示的に設定しなくてもある程度使用できる
    • packaging の設定によって、いくつかの基本的なプラグイン(maven-compiler-plugin など)は自動的に適用される
    • プレフィックス指定された場合は、 groupId, artifactId, version が自動解決される
  • 設定がなくても動くのであれば、わざわざ <plugin> を書かなくても良いような気がする
  • しかし、 <plugin> の設定無しで実行する方法は、プラグインのバージョンが実行のたびに変わる可能性がある
  • 例えば、 packaging によって自動設定されるプラグインは、 Maven のバージョンによって変化する可能性がある
    • たとえば、 3.6.3 で設定されるプラグインのバージョンはここで確認できる
    • 開発者の使っている Maven のバージョンが揃っていないと、開発者ごとにビルド結果が変わるかもしれない
    • (そもそも、デフォルトで設定されるプラグインのバージョンは古い)
  • また、プレフィックス指定で解決されるプラグインのバージョンは、基本的にリポジトリの最新になる
    • これも、実行時の最新が変われば結果も変化する恐れがある
  • したがって、使用するプラグインのバージョンは、面倒だと思っても基本的に明示するのが良い
  • ただし、ここに記載している pom.xml の例は、以下の理由によりプラグインの設定を省略していることがある
    • 記述量が多くなると、それだけで読む気が失せる
    • 必要な部分(今説明しようとしている部分)に注目できない

プロジェクトの親子関係

  • プロジェクトには親子関係をもたせることができる
フォルダ構成
|-pom.xml
`-child/
  `-pom.xml
  • トップフォルダに親プロジェクトの pom.xml を置き、 child フォルダの下に子プロジェクトの pom.xml を置いている
/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <modelVersion>4.0.0</modelVersion>

  <groupId>example</groupId>
  <artifactId>parent</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>artifactId = ${project.artifactId}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 親プロジェクトの pom.xml では、 <packaging>pom を指定しなければならない
/child/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>

  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
  </parent>

  <artifactId>child</artifactId>
</project>
  • 子プロジェクトの pomx.ml では、親を指定するために <parent> を使用する
  • <parent> では、親POM を特定するために groupId, artifactId, version を指定する
  • 親子関係のあるプロジェクトでは、子の POM は親の POM を継承する
  • このため、子の pom.xml に記述されていない設定は、親の pom.xml の設定が引き継がれることになる
    • <groupId><version> なども親の設定が引き継がれるため、子の pom.xml では記述が不要になる
    • ただし、 <artifactId> はさすがに指定しなければならない
実行結果(トップフォルダ)
> mvn antrun:run
...
     [echo] artifactId = parent
実行結果(childフォルダ)
> mvn antrun:run
...
     [echo] artifactId = child
  • <plugins> の設定も引き継がれているため、親プロジェクトで設定された antrun プラグインを子プロジェクトでも使用できている

親プロジェクトの解決方法

  • <parent> で設定された親の pom.xml は、次の順序で検索される
    1. <relativePath> が指定されている場合は、その場所の pom.xml を参照
    2. <relativePath> が指定されていない場合は、1つ上の階層の pom.xml を参照
    3. それも無ければ、リポジトリ(ローカル or リモート)を検索して参照
  • ↑の例は親プロジェクトが子プロジェクトの1つ上だったため、特に場所の指定をしなくても親 POM が見つかっていた

親プロジェクトの場所を明示する

  • しかし、↓のようなフォルダ構成の場合は、 <relativePath> によるパスの指定が必要になる
フォルダ構成
|-parent/
| `-pom.xml
`-child/
  `-pom.xml
  • parentchild が横並びになっており、子プロジェクトの1つ上に親プロジェクトの pom.xml が存在しない状態になっている
  • この状態で子プロジェクトで Maven を実行すると、次のようなエラーになる
実行結果(子プロジェクト)
> mvn antrun:run
...
[FATAL] Non-resolvable parent POM for example:child:1.0.0: Failure to find example:parent:pom:1.0.0 in https://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced and 'parent.relativePath' points at wrong local POM @ line 6, column 11
 @
...
  • これを解決するには、リポジトリに親プロジェクトの jar を登録するか、以下のように <relativePath> で親プロジェクトの場所を明示してあげる必要がある
/child/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
    <relativePath>../parent/pom.xml</relativePath>
  </parent>

  <artifactId>child</artifactId>
</project>
実行結果(子プロジェクト)
> mvn antrun:run
...
     [echo] artifactId = child
  • 無事親プロジェクトが見つかって動作した

POMのマージ

  • POMに親子関係がある場合、子のPOMには親のPOMがマージされる
  • マージがどのように行われるのか確認してみる
親の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>example</groupId>
  <artifactId>parent</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <properties>
    <hoge>PARENT</hoge>
    <fuga>PARENT</fuga>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>parent/dir</directory>
      </resource>
    </resources>

    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>hoge = ${hoge}</echo>
            <echo>fuga = ${fuga}</echo>
            <echo>piyo = ${piyo}</echo>
            <echo>dependencies[0].artifactId = ${project.dependencies[0].artifactId}</echo>
            <echo>dependencies[1].artifactId = ${project.dependencies[1].artifactId}</echo>
            <echo>resources[0].directory = ${project.build.resources[0].directory}</echo>
            <echo>resources[1].directory = ${project.build.resources[1].directory}</echo>
            <echo>plugins[0] = ${project.build.plugins[0].id}</echo>
            <echo>plugins[1] = ${project.build.plugins[1].id}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 検証のために、いくつかの設定を記述し、 antrun プラグインで設定内容を表示できるようにしている
子の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>

  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
  </parent>

  <artifactId>child</artifactId>

  <properties>
    <fuga>CHILD</fuga>
    <piyo>CHILD</piyo>
  </properties>

  <dependencies>
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.14</version>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>child/dir</directory>
      </resource>
    </resources>

    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jdeps-plugin</artifactId>
        <version>3.1.2</version>
      </plugin>
    </plugins>
  </build>
</project>
  • 親POM を継承し、いくつかの設定が重なるように記述している
実行結果(親プロジェクト)
> mvn antrun:run
...
     [echo] hoge = PARENT
     [echo] fuga = PARENT
     [echo] piyo = ${piyo}
     [echo] dependencies[0].artifactId = commons-lang3
     [echo] dependencies[1].artifactId = ${project.dependencies[1].artifactId}
     [echo] resources[0].directory = parent/dir
     [echo] resources[1].directory = ${project.build.resources[1].directory}
     [echo] plugins[0] = org.apache.maven.plugins:maven-antrun-plugin:1.8
     [echo] plugins[1] = ${project.build.plugins[1].id}
  • まずは、親プロジェクトで各設定値を出力してみる
  • 当然親POM で設定した値だけが出力され、設定されていない値は式が評価できずにそのまま出力されている
実行結果(子プロジェクト)
> mvn antrun:run
...
     [echo] hoge = PARENT
     [echo] fuga = CHILD
     [echo] piyo = CHILD
     [echo] dependencies[0].artifactId = commons-codec
     [echo] dependencies[1].artifactId = commons-lang3
     [echo] resources[0].directory = child/dir
     [echo] resources[1].directory = ${project.build.resources[1].directory}
     [echo] plugins[0] = org.apache.maven.plugins:maven-antrun-plugin:1.8
     [echo] plugins[1] = org.apache.maven.plugins:maven-jdeps-plugin:3.1.2
  • 子プロジェクトで出力した結果、単一値の設定で重なる部分(<properties><fuga> など)は子POM の設定が採用されている
    • 一方、複数値の設定で重なる部分(<dependencies><plugins> など)は、親POM をベースにして子POM の要素が追加されている
  • 基本的に、単一値の項目は上書きされ、複数値の項目 (<plugins>など) は要素が追加される
    • ただし一部例外があり、例えば <build><resources> は複数値項目だが上書きされている
    • 公式ドキュメントでは、 <resources> もマージの対象と書かれているけど実際は上書きされる
    • 実装的には ModelMerger というクラスで POM のマージが行われているっぽい
      • このクラスの mergeBuildBase_Resources()<resources> のマージが行われる
      • この実装では、 <resource> は追加する形でマージされることになっている
    • しかし、実際にはこの ModelMerger のサブクラスである MavenModelMerger でマージが行われているっぽい
      • MavenModelMergermergeBuildBase_Resources() をオーバーライドしていて、 target (子POM)の <resources> が空の場合のみ親POMの内容をマージするようにしている
      • つまり、子POMに <resources> が存在すればマージは行われず、子POMの内容だけが採用される(結果として上書きの動作になる)
    • <resources> 以外は、この MavenModelMerger の実装を見てみるしかない?

マージ後の POM を確認する

  • POM には親子関係があり、複数の POM を親に持つような場合は末端の POM を見ただけでは最終的な状態がわからない
  • 特にビルドがうまく動作しないときは、 POM が期待通りにマージされているか確認したくなることがよくある
  • そういうときは、 maven-help-plugineffective-pom ゴールが利用できる
effective-pomゴールを実行する
> mvn help:effective-pom
...
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
  </parent>
  <groupId>example</groupId>
  <artifactId>child</artifactId>
  <version>1.0.0</version>
(長いので省略)
...
  • effective-pom ゴールを実行すると、Super POM を含めてすべての親POM の情報がマージされた最終的な POM が出力される
  • これを見ることで、そのプロジェクトの設定が実際どうなっているのかを確認できる

configurations のマージ

  • プラグインの設定を記述する <configurations> 以下は、プラグインごとに設定が異なるため、固定のスキーマ定義を持っていない
  • したがって、 <configurations> のマージは一定のルールに沿って画一的に行われる
    • 例えば、 <execution> なら <id> で識別ができるように定義が固まっているので、同じ <id> をマージするといったことができる
    • しかし、 <configurations> の下のタグはそういう識別ができないので、だいたい同じ位置のタグをそれっぽくマージする感じになる
      • (正確な仕様や実装を確認したわけではないので、そういう雰囲気というくらいの気持ちで)
  • どういうふうにマージされるのか、実際の例で見てみる
親POM
      ...
        <configuration>
          <persons>
            <person>
              <name>Taro</name>
              <age>18</age>
            </person>
            <person>
              <name>Hanako</name>
              <sex>female</sex>
            </person>
          </persons>
        </configuration>
      ...
子POM
      ...
        <configuration>
          <persons>
            <person>
              <sex>male</sex>
            </person>
            <cat>nya-</cat>
            <person>
              <name>Ayako</name>
              <age>15</age>
            </person>
            <person>
              <name>Rin</name>
              <age>12</age>
              <sex>female</sex>
            </person>
          </persons>
        </configuration>
      ...

この状態で help:effective-pom でマージ結果を確認する。

effective-pom
      ...
        <configuration>
          <persons>
            <person>
              <sex>male</sex>
              <name>Taro</name>
              <age>18</age>
            </person>
            <cat>nya-</cat>
            <person>
              <name>Ayako</name>
              <age>15</age>
              <sex>female</sex>
            </person>
            <person>
              <name>Rin</name>
              <age>12</age>
              <sex>female</sex>
            </person>
          </persons>
        </configuration>
      ...
  • 言葉で説明するのは難しいが、なんかいい感じにマージされているのが分かると思う
    • おそらく、同じ位置に同名のタグがあれば、その中身を再帰的にマージしていっている感じだと思う

マージの仕方を制御する

  • デフォルトの動作でも、そこそこよしなにマージしてくれる気がする
  • しかし、それだと色々問題があるという場合は、デフォルトのマージの挙動を変更することもできる
親POM
      ...
        <configuration>
          <persons>
            <person>
              <name>Taro</name>
              <age>18</age>
            </person>
            <person>
              <name>Hanako</name>
              <sex>female</sex>
            </person>
          </persons>
        </configuration>
      ...
  • 親POMは変更なし
子POM
      ...
        <configuration>
          <persons>
            <person combine.self="override">
              <sex>male</sex>
            </person>
            <cat>nya-</cat>
            <person combine.children="append">
              <name>Ayako</name>
              <age>15</age>
            </person>
            <person>
              <name>Rin</name>
              <age>12</age>
              <sex>female</sex>
            </person>
          </persons>
        </configuration>
      ...
  • combile.self="override"combine.children="append" という属性を追加している
effective-pom
      ...
        <configuration>
          <persons>
            <person combine.self="override">
              <sex>male</sex>
            </person>
            <cat>nya-</cat>
            <person combine.children="append">
              <name>Hanako</name>
              <sex>female</sex>
              <name>Ayako</name>
              <age>15</age>
            </person>
            <person>
              <name>Rin</name>
              <age>12</age>
              <sex>female</sex>
            </person>
          </persons>
        </configuration>
      ...
  • combine.* の属性を追加した要素だけ、マージのされかたが変わっているのが分かる
  • combine.self="override" を指定した場合、親POMの要素は完全に捨てられて子POMの要素だけが使用される
  • combine.children="append" を指定した場合、親POMの要素の末尾に子POMの要素を単純に追加する形になる
  • これら2つの属性を子POMで使用することで、マージの仕方をある程度調整できるようになる
  • ただし、これらの属性は、この属性を記述した要素にだけ効果がある
    • 入れ子の要素にまで伝播することはない
    • 入れ子の要素で動きを変更したい場合は、別途入れ子の要素でも同じように combine.* 属性を指定する必要がある

親プロジェクトで定義だけをまとめる

  • 親プロジェクトの設定は、常に子プロジェクトに継承される
  • すべての子プロジェクトで共通な設定の場合は便利だが、一部の子プロジェクトでだけ必要な設定の場合は、余計な継承が発生してしまう
  • そこで、定義だけを親プロジェクトにまとめて、実際の適用はその定義を使用したい子プロジェクトで明示的に行う方法が用意されている

プラグインの定義をまとめる

親の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>example</groupId>
  <artifactId>parent</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-antrun-plugin</artifactId>
          <version>1.8</version>
          <executions>
            <execution>
              <phase>validate</phase>
              <goals><goal>run</goal></goals>
              <configuration>
                <target>
                  <echo>Hello ${project.artifactId}</echo>
                </target>
              </configuration>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
  • 親プロジェクトでプラグインの定義をまとめるには、 <pluginManagement> を使用する
  • この下は、通常のプラグイン定義と同じように <plugins> を記述できる
  • ただし、ここで記述したものはあくまで設定を定義しただけで、プロジェクトには適用されていない
子1のpom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
  </parent>

  <artifactId>child1</artifactId>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>
  • 実際にプロジェクトに適用するには、 <groupId>, <artifactId> を適用したいプロジェクトのプラグイン定義に記述する
    • バージョンを省略した場合は、 <pluginManagement> に記述したものが使用される
  • 子プロジェクトで個別の設定を追加することも可能
子2のpom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
  </parent>

  <artifactId>child2</artifactId>
</project>
  • こちらは、特に何もプラグインを適用していない

実行結果(子1)
> mvn validate
...
[INFO] ---------------------------< example:child1 >---------------------------
[INFO] Building child1 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-antrun-plugin:1.8:run (default) @ child1 ---
[INFO] Executing tasks

main:
     [echo] Hello child1
[INFO] Executed tasks
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
  • 親POMで定義された設定で、プラグインが適用されている
実行結果(子2)
> mvn validate
...
[INFO] ---------------------------< example:child2 >---------------------------
[INFO] Building child2 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
  • プラグインは何も適用されていない

依存関係の定義をまとめる

親の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>example</groupId>
  <artifactId>parent</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
    </dependency>
  </dependencies>
</project>
  • 依存関係の定義を親にまとめるには、 <dependencyManagement> を使用する
  • この下は、通常の <dependencies> 同様の記述が可能
  • <pluginManagement> 同様、ここでの定義は宣言のみで、プロジェクトへの適用は個別に行う
子1のpom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
  </parent>

  <artifactId>child1</artifactId>

  <dependencies>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
    </dependency>
  </dependencies>
</project>
  • 子プロジェクトでの適用は、依存関係に <groupId><artifactId> を記述して行う
  • <version> を省略した場合は、 <dependencyManagement> で指定したものが使用される
    • 子プロジェクトで独自に <version> を上書き指定することも可能
子2のpom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
  </parent>

  <artifactId>child2</artifactId>
</project>
  • こちらは、特に依存関係を何も定義していない

実行結果(子1のeffective-pom)
> mvn help:effective-pom
...
  <dependencies>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
...
  • 親POMで設定したバージョンで commons-io が適用されている
実行結果(子2のeffective-pom)
> mvn help:effective-pom
...
  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
...
  • commons-io の依存関係は適用されていない

プロジェクトの集約(Aggregation)

  • 親子関係は子が親を参照する形だった
  • 一方で、プロジェクトの集約では逆に親が子を参照する形になる
  • プロジェクトを集約すると、親プロジェクトでまとめてゴールやフェーズを指定して実行できるようになる
フォルダ構成
|-pom.xml
`-sub/
  `-pom.xml
  • トップフォルダが集約元となるプロジェクト
  • child フォルダの下に、集約対象となるプロジェクトが存在する
集約元の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>example</groupId>
  <artifactId>root</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <modules>
    <module>sub</module>
  </modules>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>Hello ${project.artifactId}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • プロジェクトを集約するには、集約元となるプロジェクトで <modules> を使用する
  • <modules> の下に、集約対象となるプロジェクトを <module> で列挙していく
  • 集約元となるプロジェクトの <packaging> は、 pom にしておく必要がある
集約対象のpom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <modelVersion>4.0.0</modelVersion>

  <groupId>example</groupId>
  <artifactId>sub</artifactId>
  <version>1.0.0</version>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>Hello ${project.artifactId}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 集約対象の pom.xml は普通に書く
  • この POM は集約元のプロジェクトを継承しているわけではないので、 <groupId> の記述などは個別にしている
実行結果(集約元プロジェクトで実行)
> mvn antrun:run
...
main:
     [echo] Hello sub
...
main:
     [echo] Hello root
...
  • 集約元プロジェクトでプラグインのゴールを実行すると、集約対象のプロジェクトでも同じゴールが実行されている
  • このように、プロジェクトを集約すると、集約元プロジェクトで実行したコマンドが集約先のプロジェクトでも実行されるようになる
  • 全プロジェクトをまとめてビルドしたいときなどは、この仕組があると便利

集約と親子関係を組み合わせる

  • 集約と親子関係の仕組みは、組み合わせることができる(というか、普通は組み合わせて使用する)
フォルダ構成
|-pom.xml
`-child/
  `-pom.xml
親の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>example</groupId>
  <artifactId>parent</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <modules>
    <module>child</module>
  </modules>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>Hello ${project.artifactId}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 親POMの構成は、とくに変化無し
子の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>

  <parent>
    <groupId>example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
  </parent>

  <artifactId>child</artifactId>
</project>
  • <parent> で親プロジェクトを宣言
  • これにより、プラグインの設定は親の設定を継承できるようになり、記述が不要になった
実行結果(親プロジェクトで実行)
> mvn antrun:run
...
main:
     [echo] Hello parent
...
main:
     [echo] Hello child
...
  • 親子関係と集約を同時に適用できている

Java プロジェクトのビルド

  • packaging が jar のプロジェクトがどのようにビルドされるのかを読み解いていく。

default ライフサイクルで実行されるゴール

  • 普通に Java をビルドするプロジェクトを作る場合、 packaging はデフォルトの jar になる
  • したがって、 default ライフサイクルで実行されるゴールは以下になる
    1. resources:resources
    2. compiler:compile
    3. resources:testResources
    4. compiler:testCompile
    5. surefire:test
    6. jar:jar
    7. install:install
    8. deploy:deploy
  • 各ゴールが何をしているのか、1つずつ見ていく

リソースを収集する

  • 最初に実行されるのは、 maven-resources-pluginresources ゴール となっている
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
</project>
  • 最小構成の pom.xml で検証する
フォルダ構成
|-pom.xml
`-src/main/
  |-java/
  | `example/
  |  `-App.java
  `-resources/
    |-hoge.txt
    `-foo/
      `-bar.txt
  • src/main/resources 以下に適当にファイルやフォルダを配置している
コマンド実行
> mvn resources:resources
...
[INFO] --- maven-resources-plugin:2.6:resources (default-cli) @ hello ---
...
[INFO] BUILD SUCCESS
  • resources:resources ゴールを直接実行
実行後のフォルダ構成
|-pom.xml
|-src/main/
| |-java/
| | `example/
| |  `-App.java
| `-resources/
|   |-hoge.txt
|   `-foo/
|     `-bar.txt
`-target/classes/
  |-hoge.txt
  `-foo/
    `-bar.txt
  • target/classes フォルダの下に、 src/main/resources 以下の内容がコピーされている
  • maven-resources-plugin は、プロジェクトのリソースフォルダを出力先フォルダにコピーする処理を提供する
  • リソースフォルダと出力先フォルダは、デフォルトでは次のようになっている
    • リソースフォルダ:src/main/resources
    • 出力先フォルダ:target/classes
  • リソースフォルダは、 ${project.build.resources} に設定されているフォルダが対象となる
    • この値は、 Super POM で ${project.basedir}/src/main/resources が設定されている
  • 出力先のフォルダは、 resources ゴールの outputDirectory パラメータで指定された場所となっている
    • このパラメータには、デフォルトで ${project.build.outputDirectory} が設定されている
    • そして、この値は Super POM によってデフォルトで ${project.build.directory}/classes が設定されている
    • さらに、 ${project.build.directory} には ${project.basedir}/target が設定されている
  • ということで、例えば次のように pom.xml を書くことで動作をカスタマイズできる
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

  <build>
    <resources>
      <resource>
        <directory>src/main/hoge</directory>
      </resource>
    </resources>
  </build>
</project>
  • リソースフォルダを src/main/hoge に変更している
フォルダ構成
|-pom.xml
`-src/main/
  |-hoge/
  | |-fizz.txt
  | `-hoge/
  |   `-fuga.txt
  |
  `-resources/
    |-buzz.txt
    `-foo/
      `-bar.txt
  • src/main/resourcessrc/main/hoge の2つを用意している
resourcesゴールを実行
> mvn resources:resources
...
実行結果
|-pom.xml
|-src/main/
| |-hoge/
| | |-fizz.txt
| | `-hoge/
| |   `-fuga.txt
| |
| `-resources/
|   |-buzz.txt
|   `-foo/
|     `-bar.txt
|
`-target/classes/
  |-fizz.txt
  `-hoge/
    `-fuga.txt
  • src/main/hoge 以下だけがコピーされるように変わっている

  • 整理すると resources:resources ゴールは、デフォルトで次のように動作する
    • ${project.basedir}/src/main/resources 以下のファイルやフォルダを、 ${project.basedir}/target/classes の下にコピーする
    • リソースフォルダは、 <project><build><resources> で設定できる
    • 出力先フォルダは、 <project><build><outputDirectory> で設定できる

Java ソースをコンパイルする

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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
</project>
  • 最小構成の POM で検証する
フォルダ構成
|-pom.xml
`-src/main/java
  `-example/
    `-Hello.java
  • Hello.java だけを配置したシンプルな構成
compileゴールを実行
> mvn compiler:compile
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-cli) @ hello ---
...
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] ソース・オプション5は現在サポートされていません。6以降を使用してください。
[ERROR] ターゲット・オプション1.5は現在サポートされていません。1.6以降を使用してください。
  • エラーで落ちた
  • コンパイル時のオプションである -source および -target に指定されているバージョンが 5, 1.5 で古すぎるというエラー内容になっている
  • JDK 11 では、 -source, -target に 5 以下のバージョンは指定できなくなっている
  • packaging が jar の場合、デフォルトで適用される maven-compiler-plugin のバージョンは 3.1 となっている
  • maven-compiler-plugin の 3.1 では、 -source および -target のバージョン指定は、デフォルトで 1.5 となっている
    • ちなみに、確認時最新の maven-compiler-plugin の 3.8.1 では、 1.6 がデフォルトとなっている
  • ということで、このエラーを解消するには、 maven-compiler-plugin のバージョンを上げるか、 source, target の指定を上げる必要がある
  • 動作を確定させるためにも、両方とも指定しておくのが無難
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
    </plugins>
  </build>
</project>
  • maven-compiler-pluginsource を指定するには、 source パラメータ を設定する
    • <configuration> で指定することもできるが、 maven.compiler.source プロパティでも設定できるようになっている
    • ここでは、プロパティを使った設定を使用している(こっちの方が一般的?)
  • target も同様で、 maven.compiler.target プロパティで設定できるようになっている
実行結果
> mvn compiler:compile
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-cli) @ hello ---
...
[WARNING] File encoding has not been set, using platform encoding MS932, i.e. build is platform dependent!
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
...
  • コンパイルはできたが、なにやら警告が出ている
  • 警告内容は、ソースファイルのエンコーディングが環境デフォルトに依存している、というもの
  • ソースファイルのエンコーディングの指定は、 encoding パラメータ で行う
    • 説明に書いているように、デフォルトでは ${project.build.sourceEncoding} を使用するようになっている
    • しかし、この値は Super POM でも設定されていないので、結果的に環境デフォルト(Windows なら MS932 など)となっている
  • ということで、エンコーディングも設定してみる
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
    </plugins>
  </build>
</project>
  • ${project.build.sourceEncoding} という記述から、 <project><build> の下に <sourceEncoding> という要素を記述するのかなというイメージが湧くが、実際はプロパティを宣言する(誰が分かるねん
    • ちなみに、 encoding というプロパティでも設定可能となっているが、ネットで調べるとだいたい project.build.sourceEncoding の方法が引っかかる
    • encoding だと、 Java ソース以外のエンコーディングにも適用される可能性があるとか、そういうのがあるのかな?(適当)
実行結果
> mvn compiler:compile
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-cli) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
  • 無事、警告もなくコンパイルが成功した
出力結果
|-pom.xml
|-src/main/java/
| :
`-target/
  |-classes/
  : `-example/
      `-Hello.class
  • コンパイル結果は、 target/classes の下に出力されている
    • 出力先の設定は、 compile ゴール のドキュメント上には記載されていない
    • これは、 compile ゴールの実装クラスである outputDirectory フィールドで宣言されている
    • デフォルト値は ${project.build.outputDirectory} となっている
    • readonly=true が設定されているため、このフィールドを外部から直接設定することはできないようになっている
  • ソースフォルダは、 src/main/java がデフォルトで使用される
    • こちらも、ドキュメント上は記載がない
    • 実装上は、 compileSourceRoots というフィールドで宣言されている
    • defaultValue を見ると、 ${project.compileSourceRoots} が設定されていることがわかる
    • しかし、この project.compileSourceRoots というプロパティを pom.xml 上で参照しようとしても、値を確認することはできない
    • compileSourceRootsMavenProject クラスのフィールドとして宣言されている
    • このフィールドは、 DefaultProjectBuilder によって値が設定されている
    • この実装から、 project.build.sourceDirectorycompileSourceRoots に設定されていることが分かる
    • そして、 project.build.sourceDirectory には、 Super POM によって ${project.basedir}/src/main/java が設定されている
    • この仕組みを見ると、ソースフォルダを複数設定できないことが予想できる
      • 複数のソースフォルダを設定したい場合は、 build-helper-maven-plugin というプラグインを導入する必要がある

  • 整理すると、 compiler:compile では次のことが行われる
    • ${project.basedir}/src/main/java の下にある Java ソースコードをコンパイルして、 ${project.basedir}/target/classes に出力する
    • javac の -source, -target オプションは、 maven.compiler.source, maven.compiler.target プロパティで指定できる
    • エンコーディングの指定は、 project.build.sourceEncoding プロパティで指定できる
    • ソースフォルダは <project><build><sourceDirectory> で指定できる
      • 複数のフォルダを指定したい場合は、 build-helper-maven-plugin が必要
    • 出力先フォルダは、 <project><build><outputDirectory>

テスト用のリソース収集とコンパイル

  • 次に実行されるのは、テスト用のリソースを収集する resources:testResources と、
    テスト用のソースコードをコンパイルする compiler:testCompile になる
  • それぞれの基本的な動作は resources:resources, compiler:compile と同じなので、ざっくりとだけ
フォルダ構成
|-pom.xml
`-src/test/
  |-java/
  | `-example/
  |   `-HelloTest.java
  |
  `-resources/
    |-foo/
    | `-bar.txt
    `-fizz.txt
  • src/test/java の下にテスト用のソースコードを、
    src/test/resources の下にテスト用のリソースファイルを配置する
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
    </plugins>
  </build>
</project>
  • pom.xml は、 compiler:compile のときと同じ
実行
> mvn resources:testResources compiler:testCompile
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-cli) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-cli) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
実行結果
|-pom.xml
|-src/test/
| :
`-target/
  `-test-classes/
    |-fizz.txt
    |-foo/
    | `-bar.txt
    `-example/
      `-HelloTest.class
  • target/test-classes の下に結果が出力されている

  • 整理すると、次のような動作になっている
  • testResources ゴールは、 ${project.basedir}/src/test/resources 以下のファイルやフォルダを、 ${project.basedir}/target/test-classes にコピーする
    • リソースフォルダは <project><build><testResources> で指定できる
  • testCompile ゴールは、 ${project.basedir}/src/test/java 以下の Java ソースファイルをコンパイルして、 ${project.basedir}/target/test-classes に出力する
    • ソースフォルダは、 <project><build><testSourceDirectory> で指定できる

テストを実行する

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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

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

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.6.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.22.2</version>
      </plugin>
    </plugins>
  </build>
</project>
  • JUnit5 を動かすために以下の修正を入れている
    • junit-jupiter を依存関係に追加
    • maven-surefire-plugin のバージョンに 2.22.2 を指定
      • JUnit5 を使うには 2.22.0 以上を指定する必要がある
フォルダ構成
|-pom.xml
`-src/
  |-main/java/
  | `-example/
  |   `-Hello.java
  `-test/java/
    `-example/
      `-HelloTest.java
  • テスト対象のクラス(Hello.java)と、テストクラス(HelloTest.java)を配置
実行
> mvn test
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ hello ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running example.HelloTest
...
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.027 s - in example.HelloTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...

> dir /b target\surefire-reports
example.HelloTest.txt
TEST-example.HelloTest.xml
  • surefire:test ゴールは test フェーズに紐付いているので、 test フェーズを指定して実行している
    • surefire:test を単独で実行することも可能だが、その場合はソースのコンパイルは別途済ませておく必要がある
  • 実行対象となるテストクラスは includes パラメータ で指定されており、デフォルトで次のクラスが対象になる
    • **/Test*.java
    • **/*Test.java
    • **/*Tests.java
    • **/*TestCase.java
  • テストの結果は target/surefire-reports の下にテキストと xml 形式で出力される
    • これは、 surefire:test ゴールの reportsDirectory パラメータ で指定されている(デフォルトは ${project.build.directory}/surefire-reports

テストをスキップする

  • test フェーズの次はプロジェクトのコンパイル結果を jar に固める package フェーズが実行される
  • つまり、 jar を生成するためには test フェーズが正常終了する必要がある
    • test フェーズでテストが失敗した場合は、 package フェーズは実行されない
  • テストが通っていないコードを jar に固めても使い物にならない、というのが本来あるべき姿だが、実際はテストコードがメンテされておらずテストが通らないというプロジェクトも残念ながら存在する
  • そういうときは、 test フェーズだけ実行をスキップして package フェーズを実行するということがよく行われる(本来はすべきではない)
実行結果
> mvn -DskipTests package
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
[INFO] Tests are skipped.
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
...
  • surefire:test ゴールには skipTests というパラメータが用意されている
  • これが設定されていると、テストの実行がスキップされるようになる

  • 整理すると、 test フェーズでは次のことが行われる
    • maven-surefire-plugin によってテストコードが実行される
    • JUnit 5 を使用するためには、 maven-surefire-plugin のバージョンを 2.22.0 以上にしておく必要がある
    • 実行対象となるテストクラスは includes パラメータ で指定されている
    • テストの結果は target/surefire-reports の下に出力される(reportsDirectory パラメータ
    • テストの実行は skipTests パラメータ を設定することでスキップできる
      • ただし、これの使用は必要最小限に留めるべき
        • テストがメンテナンスされておらず、今すぐ jar が必要(本来はテストが通るように修正するのが正しい対応)
        • テストは通るが、実行に時間がかかるため毎回実行してられない
        • etc

jar に固める

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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
    </plugins>
  </build>
</project>
  • コンパイルで必要になる最小限の設定にしている
フォルダ構成
|-pom.xml
`-src/
  |-test/
  | :
  `-main/
    |-java/
    | `-example/
    |   `-Hello.java
    `-resources/
      `-hoge.txt
packageフェーズを実行
> mvn package
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ hello ---
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
  • jar:jar ゴールは package フェーズに紐付いているので、 package フェーズを実行している
実行結果
|-pom.xml
|-src/
| :
`-target/
  |-hello-1.0.0.jar
  :
  • target フォルダの直下に、 hello-1.0.0.jar が出力されている
    • jar ファイルの出力先は、 jar ゴールの outputDirectory パラメータによって設定されている
      • このパラメータのデフォルトは、 ${project.build.directory} となっている(${project.basedir}/target
    • また、 jar ファイルの名前は jar ゴールの実装では finalName というフィールドで宣言されている
      • このフィールドは readonly = true なので、外から直接変更することはできない
      • このフィールドの値には、 ${project.build.finalName} という値が設定されている
      • この値は Super POM によって ${project.artifactId}-${project.version} がデフォルトで設定されている
      • したがって、 jar の名前を変更したい場合は、 <project><build><finalName> を設定すればいい
  • この jar を解凍すると、中身は次のようになっている
hello-1.0.0.jarの中身
|-hoge.txt
|-example/
| `-Hello.class
`-META-INF/
  |-MANIFEST.MF
  `-maven/example/hello/
    |-pom.properties
    `-pom.xml
  • hoge.txtHello.class は、それぞれ resources:resources, compiler:compile によって target/classes の下に出力されたものが梱包されている
    • どのフォルダの内容が jar に梱包されるかは、 jar ゴールの classesDirectory パラメータによって設定されている
      • このパラメータのデフォルトは、 ${project.build.outputDirectory} となっている(${project.basedir}/target/classes
  • META-INF の下に pom.xml が出力されているが、その内容はこのプロジェクトの pom.xml と同じものが入っている
  • MANIFEST.MFpom.properties は、それぞれ次のような内容になっている
MANIFEST.MF
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven 3.6.3
Built-By: xxxxx
Build-Jdk: 11.0.6
pom.properties
#Generated by Maven
#Sun Mar 29 21:59:48 JST 2020
groupId=example
artifactId=hello
version=1.0.0
  • これらは、 jar ゴールの archive パラメータで設定する
  • archive の設定の詳細は Apache Maven Archiver – Reference で確認できる
  • デフォルトで有効になっている設定で、これらのファイルが生成されている

  • 整理すると、 jar ゴールは次のように動作する
  • ファイル名
    • ${project.artifactId}-${project.version}
    • 変更する場合は <project><build><finalName> で指定
  • 出力場所
    • ${project.basedir}/target
    • 変更する場合は <project><build><directory> で指定
  • 梱包対象
    • ${project.basedir}/target/classes
    • 変更する場合は、 <project><build><outputDirectory>jar ゴールの classesDirectory パラメータで指定
    • さらに archive パラメータで MANIFEST.MFMETA-INF 以下に追加される情報を設定できる

ローカルリポジトリにインストールする

  • install フェーズに紐付けられている maven-install-plugininstall ゴール は、ローカルリポジトリにプロジェクトの成果物をインストールする機能を提供する
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
  ...
</project>
installを実行
> mvn install
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
  • install:install ゴールは install フェーズに紐付いているので、 install フェーズを実行している(ややこしい)
  • 実行が完了したら、ローカルリポジトリの中を確認する
    • ローカルリポジトリの場所は、 Windows ならデフォルトは %USERPROFILE%\.m2\repository (Linux系のOSなら $HOME/.m2/repository)
    • settings.xml で別の場所を指定している場合は、そちら
ローカルリポジトリ
ローカルリポジトリ/
 |-example/
 : `-hello/
     |-1.0.0/
     : |-hello-1.0.0.jar
       :
  • ローカルリポジトリの中に jar ファイルが保存されている
  • 登録される jar ファイルは、多分 package フェーズで生成される jar ファイル
    • install:install ゴールのドキュメントでは project's main artifact と書かれていたが、具体的に何が project's main artifact になるのか、明確に書かれているところを見つけることはできなかった
    • 実装的には MavenProject の getArtifact() で取得できる Artifact の getFile() で取得できるファイルがインストールされているっぽいところまでは読み解けたが、この File オブジェクトが設定されているところを見つけきれなかった
  • ローカルリポジトリにインストールされたアーティファクトは、ローカルの別のプロジェクトで参照できるようになる
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>example</groupId>
  <artifactId>foo</artifactId>
  <version>1.0.0</version>

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

  <dependencies>
    <dependency>
      <groupId>example</groupId>
      <artifactId>hello</artifactId>
      <version>1.0.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
    </plugins>
  </build>
</project>
  • hello-1.0.0.jar への依存関係を <dependencies> で宣言している
Foo.java
package example;

public class Foo {
    public static void main(String... args) {
        new Hello().hello();
    }
}
  • Hello クラスを使った実装を書いている
プロジェクトをコンパイル
> mvn compile
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ foo ---
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ foo ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
  • コンパイルが通った(Hello クラスが解決できている)

  • 整理すると、 install ゴールは次のように動作する
  • おそらく package フェーズで生成された成果物(jar ファイルなど)を、ローカルリポジトリにインストールする
  • インストールされたアーティファクトは、ローカルの他のプロジェクトから依存対象として参照できるようになる

デプロイする

  • 最後に実行されるのが deploy フェーズとなる
  • deploy フェーズでは、 maven-deploy-plugindeploy ゴール が実行される
  • deploy ゴールは、プロジェクトの成果物をリモートのリポジトリにデプロイする
  • 環境構築とかが面倒な割に実際に使う機会は少ない気がするので検証は省略

Web プロジェクトのビルド

  • packaging を war にした場合に、プロジェクトがどのようにビルドされるかを見る
  • ただ、 default フェーズで実行されるプラグインは、 jar の場合とほとんど違いはない
  • jar と異なるのは、 package フェーズで実行されるゴールが war プラグインの war ゴール ということだけ
フォルダ構成
|-pom.xml
`-src/main/
  |-java/
  | `-example/webapp/
  |   `-HelloServlet.java
  `-webapp/
    `-WEB-INF/
      `-hello.jsp
  • src/main/webapp というフォルダを作成し、その下に war に含める WEB-INF などのフォルダを作成している
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>example</groupId>
  <artifactId>webapp</artifactId>
  <version>1.0.0</version>
  <packaging>war</packaging>

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

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.10</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.2.3</version>
      </plugin>
    </plugins>
  </build>
</project>
  • <packaging>war を指定している
  • 依存関係として次の2つを指定している
    • Servlet API
    • Apache Commons Lang3
ビルド
> mvn package
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ webapp ---
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ webapp ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ webapp ---
...
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ webapp ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ webapp ---
...
[INFO] --- maven-war-plugin:3.2.3:war (default-war) @ webapp ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
  • jar の場合と同様に、各フェーズでプラグインのゴールが実行されていき、最後に war:war が実行されている
出力結果
|-pom.xml
|-src/
| :
`-target/
  `-webapp-1.0.0.war
  • war ファイルは、 target の直下に生成されている
    • 出力位置は war ゴールの outputDirectory パラメータ で指定されており、デフォルトで ${project.build.directory} となっている
    • また、 war ファイルの名前は warName という読み取り専用のパラメータで指定されており、デフォルトが ${project.build.finalName} となっている
  • webapp-1.0.0.war の中身は次のようになっている
webapp-1.0.0.warの中身
webapp-1.0.0.war/
|-WEB-INF/
| |-hello.jsp
| |-classes/
| | `-example/webapp/
| |   `-HelloServlet.class
| `-lib/
|   `-commons-lang3-3.10.jar
`-META-INF/
  |-MANIFEST.MF
  `-maven/example/webapp/
    |-pom.properties
    `-pom.xml
  • src/main/java のコンパイル結果と src/main/webapp の下の内容が war ファイルの中に入っているのが分かる
    • class ファイルやライブラリの jar 以外で war の中に入れるファイルは、 src/main/webapp フォルダの下に配置する
    • これは war ゴールの warSourceDirectory パラメータ で指定されており、デフォルトが ${basedir}/src/main/webapp となっている
  • また、依存関係に指定していた Apache Commons Lang3 の jar も WEB-INF/lib の下に格納されている
    • Servlet API の jar は格納されていない
    • これは、 Servlet API への依存のスコープを provided に指定していたため
    • スコープの詳細な説明は後述

  • 整理すると、 packaging が war の場合、プロジェクトは次のようにビルドされる
    • 基本は jar と同じようにビルドされる
    • ただし、 package フェーズだけ war:war ゴールが実行される点が jar の場合と異なる
    • war:war ゴールでは、プロジェクトのコンパイル結果が war ファイルに固められて出力される
    • war の中には class ファイル・リソース・依存ライブラリに加えて、 src/main/webapp の下に配置したファイルもそのまま格納される

Java アプリケーションを実行する

Hello.java
package example;

import java.util.Arrays;

public class Hello {
    public static void main(String... args) {
        System.out.println("Hello World! args=" + Arrays.toString(args));
    }
}
  • 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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
          <execution>
            <id>default-cli</id>
            <configuration>
              <mainClass>example.Hello</mainClass>
              <arguments>
                <argument>one</argument>
                <argument>two</argument>
                <argument>three</argument>
              </arguments>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
実行
> mvn compile exec:java
...
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ hello ---
Hello World! args=[one, two, three]
...
  • Hello.java が実行された
  • exec-maven-pluginjava ゴール を使用すると、プロジェクトのビルド結果をクラスパスに入れて Java プログラムを実行できる
    • exec-maven-plugin には任意のコマンドを実行できる exec ゴール も用意されているが、ここでは割愛
  • mainClass パラメータで Main クラスを指定することで、 Java プログラムを実行できる
  • arguments パラメータで、コマンドライン引数を渡すことができる
    • commandlineArgs というパラメータでも引数を渡すことができるが、こちらは後述するコマンドラインから実行する場合に利用するためにある(たぶん)

コマンドラインから実行する

  • 最初の例は、あらかじめ pom.xml に実行の構成をすべて記述しておく必要がある
  • 場合によっては、コマンドラインで引数を色々変更しながら実行したくなることもあるかもしれない
  • その場合は、システムプロパティを介してゴールのパラメータを指定する方法をとるとやりやすい
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
          <execution>
            <id>default-cli</id>
            <configuration>
              <mainClass>example.Hello</mainClass>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
  • Main クラスをコマンドラインから指定するのは面倒なので、これだけはあらかじめ pom.xml に記述しておいていいと思う
実行結果
> mvn compile exec:java -Dexec.args="ONE TWO THREE"
...
Hello World! args=[ONE, TWO, THREE]
  • commandlineArgs パラメータを使えば、引数をスペース区切りの文字列で指定できる
  • システムプロパティで指定する場合は、 exec.args で指定する

依存関係

依存関係のスコープ

  • 依存関係には、**スコープ(Scope)**というものを設定できる
  • スコープとは、その依存関係を使用する範囲を表す
  • 例えば、 compile スコープに指定されている依存関係は、ソースコードをコンパイルするときからアプリケーションを実行するときまで、常に使用することを表している
  • また、 test スコープが指定されている依存関係は、テストコード(src/test/java)のコンパイルとテストの実行時にだけ使用することを表している
  • スコープは、次の6種類存在する
    • compile
    • provided
    • runtime
    • test
    • system
    • import

compile スコープ

pom.xml
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.10</version>
</dependency>
  • スコープを指定していない場合、デフォルトで compile スコープが採用される(明示することも可能)
  • compile スコープは、コンパイルから実行まで、常にその依存関係が必要であることを表している

provided スコープ

pom.xml
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>4.0.1</version>
  <scope>provided</scope>
</dependency>
  • provided スコープは、その依存関係が実行環境から提供される(provided)ことを表している
  • 次の条件が満たされる場合に使用する
    • コンパイルやテストでは必要になる
    • しかし、実行時は実行環境が jar を提供してくれるので、アプリケーションが個別に保持しておく必要はない
  • 最もよくある例として、 Servlet API が挙げられる
    • Servlet API は、 war をデプロイするアプリケーション・サーバーが提供するものを使用するため、実行時にアプリケーションが個別に依存関係を持っておく必要はない
    • 同様の理由で、 Java EE (Jakarta EE) が提供する他の API (EJB, JAX-RS, etc...)も provided で指定する
  • maven-war-plugin で war を生成した場合、 provided に指定された依存関係は WEB-INF/lib の下には配置されないようになっている

runtime スコープ

pom.xml
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.12</version>
    <scope>runtime</scope>
</dependency>
  • runtime スコープは、 provided の逆でコンパイル時には必要ないが実行時に必要な依存関係で使用する
  • 典型的な例として、 JDBC ドライバが挙げられる
    • JDBC ドライバを使ってプログラムを書く場合、実装では標準 API が提供している java.sql.Connection などのクラスにだけ依存すればいい
    • 各データベース製品が提供している具象クラスに直接依存することは基本的にはない
    • しかし、当然実行時は実体が必要になる
    • ということで、コンパイル時は必要ないが実行時は必要、というケースに当てはまることになる

test スコープ

pom.xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.6.1</version>
    <scope>test</scope>
</dependency>
  • test スコープは、テストソースのコンパイルと実行時にだけ使用する依存関係で使用する
  • main のソースコードのコンパイルや、実行時には使用できない
  • JUnit のような、テストのときだけ必要になる依存関係で使用する

system スコープ

pom.xml
<dependency>
  <groupId>javax.sql</groupId>
  <artifactId>jdbc-stdext</artifactId>
  <version>2.0</version>
  <scope>system</scope>
  <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
  • system スコープは、その依存関係がシステム(実行環境の JRE や JDK)から提供されるものであることを表す
  • このスコープは、リポジトリに存在せず JRE や JDK が内部に保持している拡張ライブラリなどを使用することを目的に用意されているものらしい
  • system スコープを指定した場合は、 <systemPath> で対象の jar ファイルのパスを指定する必要がある
  • リポジトリに存在しないライブラリをプロジェクトの中に持たせておいて、それを参照するのに使ったりもできる
    • 本来は、プライベートリポジトリを用意して、そこで管理するのが正攻法
  • 一応、このスコープの利用は非推奨となっている

import スコープ

フォルダ構成
|-foo/
| `-pom.xml
`-bar/
  `-pom.xml
  • foo, bar の2つのプロジェクトが存在する
pom.xml(foo)
<?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>example</groupId>
  <artifactId>foo</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <dependencyManagement>
    <dependencies>
      <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
          <version>3.10</version>
      </dependency>
      <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-text</artifactId>
          <version>1.8</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>
  • foo プロジェクトは packaging を pom にして、 <dependencyManagement> だけを宣言している
  • 依存対象として、次の2つを宣言している
    • org.apache.commons:commons-lang3:3.10
    • org.apache.commons:commons-text:1.8
pom.xml(bar)
<?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>example</groupId>
  <artifactId>bar</artifactId>
  <version>1.0.0</version>

  <dependencyManagement>
    <dependencies>
      <dependency>
          <groupId>example</groupId>
          <artifactId>foo</artifactId>
          <version>1.0.0</version>
          <type>pom</type>
          <scope>import</scope>
      </dependency>
      <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
          <version>3.9</version>
      </dependency>
      <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-csv</artifactId>
          <version>1.8</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>
  • bar プロジェクトでは、 <dependencyManagement> の中で、先程の foo プロジェクトを import スコープで指定している
    • import スコープは <dependencyManagement> の中でしか指定できない
    • import スコープを指定した場合は、合わせて <type>pom</type> も指定する必要がある
  • また、それ以外の依存対象として、次の2つを宣言している
    • org.apache.commons:commons-lang3:3.9
    • org.apache.commons:commons-csv:1.8
  • この状態で、 bar プロジェクトの effective-pom を確認する
effective-pomの確認(bar)
> cd bar

> mvn help:effective-pom
...
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.9</version>
      </dependency>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-csv</artifactId>
        <version>1.8</version>
      </dependency>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>1.8</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
artifactId foo bar effective-pom
commons-lang3 3.10 3.9 3.9
commons-csv - 1.8 1.8
commons-text 1.8 - 1.8
  • foo プロジェクトへの依存の宣言が消えて、代わりに foo プロジェクトの <dependencyManagement> で宣言していた依存関係が追加されている
  • ただし、アーティファクトが重複している場合は bar プロジェクトのバージョンが採用されている(commons-lang3
  • このように、 import は他の pom プロジェクトの <dependencyManagement> を取り込む(importする)特殊なスコープとなっている

BOM

  • マルチプロジェクトでの依存関係のバージョン管理手法として、 import スコープを利用した BOM (bill of materials) という方法が存在する
  • BOM では、まず pom.xml を持つ BOM プロジェクトを用意する
  • BOM プロジェクトの pom.xml には <dependencyManagement> が宣言されており、各プロジェクトで使用する依存関係が定義されている
  • 各プロジェクトでは、この BOM プロジェクトを import スコープで読み込む
  • 依存関係のバージョンは BOM プロジェクトで宣言されているので、各プロジェクトは groupId, artifactId だけを <dependency> に宣言すればいい
  • これにより、全プロジェクトで使用する依存関係のバージョンを BOM プロジェクトで一元管理できるようになる
  • ちなみに、Spring Boot の BOM が こちら
    • この BOM を読み込めば、使用するライブラリのバージョンを Spring Boot がサポートしているものに合わせることができる

  • 各スコープの範囲を俯瞰できるようにするため、スコープとゴールの関連を表にしてみた(import は特殊なので除外)

maven.jpg

  • が付いているのは、ゴールが該当するスコープの依存関係を参照・使用することを表している
  • war:war については、生成される war ファイル内に梱包されるかどうか、ということを意味している
  • それ以外は、クラスパスに設定されるかどうか、ということを意味している

依存関係をツリーで確認する

  • プロジェクトの持つ依存関係をツリー構造のグラフで表示して確認できる
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>
</project>
  • Spring Framework の web と jdbc を依存関係に宣言している
  • しかし、プロジェクトの依存しているライブラリはこれだけではなく、実際は spring-web, spring-jdbc が依存するライブラリにも依存している
  • 最終的にプロジェクトが依存しているライブラリがいくつあるのかは、 pom.xml を見ただけでは判断できない
  • maven-dependency-plugintree ゴール を実行すると、この推移的な依存関係を含めたすべての依存関係をツリー構造のグラフで確認できる
実行結果
> mvn dependency:tree
...
[INFO] +- org.springframework:spring-web:jar:5.2.5.RELEASE:compile
[INFO] |  +- org.springframework:spring-beans:jar:5.2.5.RELEASE:compile
[INFO] |  \- org.springframework:spring-core:jar:5.2.5.RELEASE:compile
[INFO] |     \- org.springframework:spring-jcl:jar:5.2.5.RELEASE:compile
[INFO] \- org.springframework:spring-jdbc:jar:5.2.5.RELEASE:compile
[INFO]    \- org.springframework:spring-tx:jar:5.2.5.RELEASE:compile
...
  • spring-web が、さらに spring-beans, spring-core に依存し、さらに spring-corespring-jcl に依存していることが分かる
  • そして、 spring-jdbcspring-tx に依存していることがわかる
    • ちなみに、 spring-jdbcspring-beans, spring-core にも依存しているが、 spring-web 側と重複するため表示は省略されている
  • デフォルトでは全てのスコープの依存関係が出力されるが、 scope パラメータ で絞ることもできる

依存関係のjarを集める

  • プロジェクトが依存する jar ファイルの実物を手元にすべてかき集めたい、となることが稀によくある
  • これは、 maven-dependency-plugincopy-dependencies ゴール で実現できる
  • ↑の pom.xml と同じ設定で実行してみる
実行結果
> mvn dependency:copy-dependencies
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...

> dir /b target\dependency
spring-beans-5.2.5.RELEASE.jar
spring-core-5.2.5.RELEASE.jar
spring-jcl-5.2.5.RELEASE.jar
spring-jdbc-5.2.5.RELEASE.jar
spring-tx-5.2.5.RELEASE.jar
spring-web-5.2.5.RELEASE.jar
  • target/dependency フォルダの下に、依存する jar がすべて出力されている
  • デフォルトでは全てのスコープの jar が対象になるが、 includeScope パラメータ などで絞り込むことができる

プロファイル

  • 基本的にプロジェクトのビルドは、どの環境でも同じ設定で同じ結果が出力されるようにしておくのがいい
    • ある環境ではビルドは成功するけど、別の環境ではエラーになる、なんてことになると色々面倒
  • しかし、実際に開発をしていると、環境に依存したビルド設定が必要になることが稀によくあるらしい(しらんけど)
  • ビルド環境によって pom.xml の内容を切り替えたい(特定のプラグインを有効にしたり、設定値を変更したり)場合に、**プロファイル(Profile)**という仕組みが利用できる
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

  <profiles>
    <profile>
      <id>foo</id>

      <properties>
        <message>foo profile!!</message>
      </properties>

      <dependencies>
        <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
          <version>3.10</version>
        </dependency>
      </dependencies>

      <build>
        <directory>${project.basedir}/build</directory>
      </build>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>message = ${message}</echo>
            <echo>project.build.directory = ${project.build.directory}</echo>
            <echo>dependency[0] = ${project.dependencies[0].artifactId}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • <profiles><profile> で、 foo というプロファイルを宣言している
  • foo プロファイルでは、プロパティや依存関係、 project.build.directory の設定などを行っている
  • maven-antrun-plugin を使って、それらの値を表示するようにしている
実行結果
> mvn antrun:run
...
     [echo] message = ${message}
     [echo] project.build.directory = F:\tmp\maven\hello\target
     [echo] dependency[0] = ${project.dependencies[0].artifactId}
...

> mvn -P foo antrun:run
...
     [echo] message = foo profile!!
     [echo] project.build.directory = F:\tmp\maven\hello\build
     [echo] dependency[0] = commons-lang3
  • 普通に実行した場合は、 foo プロファイルで設定した内容は反映されていない
  • 一方で、 -P foo を指定して実行した場合は、 foo プロファイルで設定した内容が反映されているのが分かる
  • このように、プロファイルを使用すると、そのプロファイルを使用したときだけ適用される設定を定義できる
  • プロファイルの定義は、 <profile> で記述する
    • <id> で、そのプロファイルを一意に識別するための名前を設定する
    • それ以外は、基本的に pom.xml に記述できる要素(<dependencies>, <build> など)をそのまま記述できる
  • プロファイルの指定は、コマンドラインで -P に続けてプロファイルの <id> を指定する

複数のプロファイルを指定して実行する

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

  <profiles>
    <profile>
      <id>foo</id>
      <properties>
        <foo>FOO</foo>
        <message>foo profile!!</message>
      </properties>
    </profile>
    <profile>
      <id>bar</id>
      <properties>
        <bar>BAR</bar>
        <message>bar profile!!</message>
      </properties>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <target>
            <echo>foo = ${foo}</echo>
            <echo>bar = ${bar}</echo>
            <echo>message = ${message}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • foobar という2つのプロファイルを宣言している
  • message は両方のプロファイルで宣言して重複するようにしている
実行結果
> mvn -P bar,foo antrun:run
...
     [echo] foo = FOO
     [echo] bar = BAR
     [echo] message = bar profile!!
  • プロファイルを複数指定する場合は、カンマ区切りで id を並べる
  • 重複する要素は、 pom.xml 上で後に来る方が採用されるっぽい(プロファイルの指定順ではなさそう)

プロファイルをデフォルトで有効にする

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

  <profiles>
    <profile>
      <id>foo</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <message>foo profile!!</message>
      </properties>
    </profile>
    <profile>
      <id>bar</id>
      <properties>
        <message>bar profile!!</message>
      </properties>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        ...
        <configuration>
          <target>
            <echo>message = ${message}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • foo プロファイルの方に、 <activeByDefault>true</activeByDefault> を設定している
実行結果
> mvn antrun:run
...
     [echo] message = foo profile!!
...

> mvn -P bar antrun:run
...
     [echo] message = bar profile!!
  • foo プロパティがデフォルトで有効になっている
  • <activation><activeByDefault>true を設定すると、そのプロファイルはデフォルトで有効になる
  • -P で別のプロファイルが明示された場合は、 <activeByDefault>true のプロファイルは無効になる

条件が満たされたときだけプロファイルを有効にする

システムプロパティが宣言されていたら有効にする

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

  <profiles>
    <profile>
      <id>foo</id>
      <activation>
        <property>
          <name>foo</name>
        </property>
      </activation>
      <properties>
        <message>foo profile!!</message>
      </properties>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        ...
        <configuration>
          <target>
            <echo>message = ${message}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • <activation> の中で、 <property> を宣言している
実行結果
> mvn antrun:run
...
     [echo] message = ${message}
...

> mvn -Dfoo antrun:run
...
     [echo] message = foo profile!!
  • -P によるプロファイルの指定はしていないが、 -Dfoo でシステムプロパティを宣言することで foo プロファイルが有効になっている
  • <activation> の中では、そのプロファイルを有効にする条件を定義できる
  • <property> は、システムプロパティが宣言されているか、特定の値が設定されているかを条件に指定できる
  • ここでは <name>foo</name> だけを設定しているので、システムプロパティ foo が設定されていれば、値に関わらずプロファイルが有効になる
  • 値も条件にする場合は、次のように <value> も宣言する
システムプロパティの値も条件に入れる場合
        <property>
          <name>foo</name>
          <value>enable</value>
        </property>
実行結果
> mvn -Dfoo antrun:run
...
     [echo] message = ${message}
...

> mvn mvn -Dfoo=enable antrun:run
...
     [echo] message = foo profile!!
  • システムプロパティ foo の値が enable のときにだけ、 foo プロファイルが有効になっている

JDK のバージョンを条件にする

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

  <profiles>
    <profile>
      <id>jdk11</id>
      <activation>
        <jdk>11</jdk>
      </activation>
      <properties>
        <message>jdk11 profile!!</message>
      </properties>
    </profile>
    <profile>
      <id>jdk14</id>
      <activation>
        <jdk>14</jdk>
      </activation>
      <properties>
        <message>jdk14 profile!!</message>
      </properties>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        ...
        <configuration>
          <target>
            <echo>message = ${message}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • <activation><jdk> 要素を使用している
実行結果
> java --version
openjdk 11.0.6 2020-01-14
...

> mvn antrun:run
...
     [echo] message = jdk11 profile!!
...

> java --version
openjdk 14 2020-03-17
...

> mvn antrun:run
...
     [echo] message = jdk14 profile!!
  • 実行時の JDK のバージョンに合わせてプロファイルが切り替わっている
  • <jdk> を使用すると、実行時の Java のバージョンをプロファイル適用の条件にできる
  • <jdk> に記述した条件は前方一致で Java のバージョンと比較される
  • なお、比較対象となる Java のバージョンは、システムプロパティ java.version で取得された値が使用されている
  • <jdk>!11</jdk> のように、先頭に ! をつけると条件を否定できる

OS を条件にする

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

  <profiles>
    <profile>
      <id>Windows</id>
      <activation>
        <os>
          <name>Windows 10</name>
        </os>
      </activation>
      <properties>
        <message>Windows profile!!</message>
      </properties>
    </profile>
    <profile>
      <id>Linux</id>
      <activation>
        <os>
          <name>Linux</name>
        </os>
      </activation>
      <properties>
        <message>Linux profile!!</message>
      </properties>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        ...
        <configuration>
          <target>
            <echo>message = ${message}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • <activation><os> を設定している
実行結果(Windows)
> mvn antrun:run
...
     [echo] message = Windows profile!!
実行結果(Linux)
> mvn antrun:run
...
     [echo] message = Linux profile!!
  • 実行時の OS によって、適用されるプロファイルが切り替わっている
  • <os> を使用すると、実行時に OS をプロファイル適用の条件に使用できる
  • <name> は、 OS の名前を条件にしている
    • OS の名前はシステムプロパティの os.name で取得できる値が使用されている
    • 大文字・小文字の区別はないっぽい
  • 名前以外にも、次の条件を指定できる
  • これらの条件を複数指定した場合は、全て AND 条件で結合される
  • 今の環境がどういう値になるのかは、 maven-enforcer-plugindisplay-info ゴール を実行することで確認できる
実行結果
> mvn enforcer:display-info
...
[INFO] Maven Version: 3.6.3
[INFO] JDK Version: 11.0.6 normalized as: 11.0.6
[INFO] OS Info: Arch: amd64 Family: windows Name: windows 10 Version: 10.0

ファイルの有無を条件にする

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

  <profiles>
    <profile>
      <id>Exists</id>
      <activation>
        <file>
          <exists>${basedir}/pom.xml</exists>
        </file>
      </activation>
      <properties>
        <message>Exists profile!!</message>
      </properties>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        ...
        <configuration>
          <target>
            <echo>message = ${message}</echo>
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • <activation><file> を指定している
実行結果
> mvn antrun:run
...
     [echo] message = Exists profile!!
  • <file> を使用すると、ファイルの有無をプロファイル適用の条件に使用できる
  • <exists> は、そのファイルが存在することを条件に設定する
  • ファイルが存在しないことを条件にする場合は、 <missing> を使用する
  • ファイルパスの指定では、埋め込みパラメータとして ${basedir} またはシステムプロパティ・リクエストプロパティ?しか使用できないという制限がある
    • ${project.basedir}/pom.xml とか書いていると、うまく判定されない

使用できるプロファイルを確認する

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

  <profiles>
    <profile>
      <id>foo</id>
      <properties>
        <message>foo profile!!</message>
      </properties>
    </profile>
    <profile>
      <id>bar</id>
      <properties>
        <message>bar profile!!</message>
      </properties>
    </profile>
  </profiles>

  ...
</project>
  • foo, bar、2つのプロファイルを定義している
実行結果
> mvn help:all-profiles
...
[INFO] Listing Profiles for Project: example:hello:jar:1.0.0
  Profile Id: bar (Active: false , Source: pom)
  Profile Id: foo (Active: false , Source: pom)

アクティブなプロファイルを確認する

実行結果
> mvn -P foo help:active-profiles antrun:run
...
Active Profiles for Project 'example:hello:jar:1.0.0':

The following profiles are active:

 - foo (source: example:hello:1.0.0)
...
     [echo] message = foo profile!!
  • active-profiles を使用すると、実行時にアクティブ(有効)になっているプロファイルを確認できる
  • デバッグのときとかに便利かも

いつプロファイルを使うべきか

  • プロファイルを使うと、プロファイルごとに異なるビルド結果を作り出すことができるようになる
  • このため、プロファイルを多様しすぎると、開発者によって異なるビルド結果が得られてしまうことになりかねない
    • 「開発用途では develop プロファイルを有効にしないといけない」みたいなルールがあって、その指定を忘れていたなど
  • ビルド結果が変わるのは、混乱の原因になりかねない
  • <os> などの条件設定が可能なことから想像できるように、プロファイルの本来の目的は、ビルドを行う環境の差異を吸収することにあると思う
    • したがって、それ以外の用途で用いるのは極力避けたほうが良い気がする(私見)
  • 例えば、開発用・検証環境用・本番環境用に使用する設定ファイルを切り替えるような方法では、プロファイルは使うべきではないのかもしれない

プラグインを自作する

  • プラグインは自作できる
  • org.apache.maven.pluginscom.codehaus.mojo にあるプラグインで目的が達成できない場合は、自分でプラグインを作ることになる

Hello World

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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>example</groupId>
  <artifactId>hello-maven-plugin</artifactId>
  <version>1.0.0</version>
  <packaging>maven-plugin</packaging>

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

  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>3.6.3</version>
    </dependency>

    <dependency>
      <groupId>org.apache.maven.plugin-tools</groupId>
      <artifactId>maven-plugin-annotations</artifactId>
      <version>3.6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>3.6.0</version>
      </plugin>
    </plugins>
  </build>
</project>
  • packaging を maven-plugin にする
  • 依存関係として、次の2つを設定する
    • org.apache.maven:maven-plugin-api
    • org.apache.maven.plugin-tools:maven-plugin-annotations
      • こちらは provided スコープで指定している
  • maven-plugin-plugin を設定している
    • Java 11 でコンパイルしようとすると、このプラグインのバージョンを新しくしておかないとエラーになるっぽい?
HelloMojo.java
package example;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;

@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {

    public void execute() throws MojoExecutionException {
        getLog().info("Hello Custom Plugin!");
    }
}
  • AbstractMojo クラスを継承してクラスを作成する
    • この HelloPlugin クラスが、1つのゴールに対応する
  • execute() メソッドの中で、そのプラグインの処理を実装する
  • @Mojo アノテーションで、ゴールのメタ情報を設定している
    • name は、ゴールの名前になる
ビルド
> mvn install
...
  • install フェーズを実行して、作成したプラグインをローカルリポジトリにインストールする
  • インストールしたプラグインを、他のプロジェクトで使用してみる
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>

  <build>
    <plugins>
      <plugin>
        <groupId>example</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <version>1.0.0</version>
      </plugin>
    </plugins>
  </build>
</project>
  • <plugin>hello-maven-plugin を設定している
実行結果
> mvn hello:hello
...
[INFO] Hello Custom Plugin!
  • 自作のプラグインが実行できた

プラグインの名前(artifactId)

pom.xml
...
  <groupId>example</groupId>
  <artifactId>hello-maven-plugin</artifactId>
  <version>1.0.0</version>
...
  • 自作プラグインの artifactId は、 XXX-maven-plugin とするのが慣例となっている
  • このパターンの artifactId にしておくと、 XXX の部分が自動的にプラグインのプレフィックスとしてメタ定義が作成される
  • 試しに、作成された jar ファイルの中を確認する
jarの中身
hello-1.0.0.jar/
 |-example/
 `-META-INF/
   |-MANIFEST.MF
   `-maven/
     |-example/
     `-plugin.xml
  • この plugin.xml の中身を見ると、次のようになっている
plugin.xml
...
<plugin>
  <name>hello-maven-plugin</name>
  <description></description>
  <groupId>example</groupId>
  <artifactId>hello-maven-plugin</artifactId>
  <version>1.0.0</version>
  <goalPrefix>hello</goalPrefix>
  <isolatedRealm>false</isolatedRealm>
  <inheritedByDefault>true</inheritedByDefault>
...
  • <goalPrefix>hello になっている
  • これによって、このプラグインは hello:<ゴール> というプレフィックス指定ができるようになっている
  • もし foo-maven-plugin という artifactId にしていれば、プレフィックスは foo になっている

Mojo

  • Maven のプラグインのゴールを実装するクラスのことを、**Mojo(Maven Old Java Object)**と呼ぶ
  • Maven のドキュメントを読んでいると、ところどころでこの Mojo という用語が現れる
  • その場合は、ゴールを実装したクラスのこと、と思えばいいと思う

パラメータを定義する

HelloMojo.java
package example;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
    @Parameter
    private String message;

    public void execute() throws MojoExecutionException {
        getLog().info("message = " + message);
    }
}
  • message フィールドを Mojo に追加して、 @Parameter アノテーションを付けている
  • Getter, Setter は定義していない
pom.xml(別プロジェクト)
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      <plugin>
        <groupId>example</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <version>1.0.0</version>
        <configuration>
          <message>Hello World!!</message>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • <configuration> で、 Mojo のフィールド名と同じ要素を宣言している
実行結果
> mvn hello:hello
...
[INFO] message = Hello World!!
  • Mojo で宣言した message フィールドを、パラメータとして指定できるようになっている
  • ちなみに、この状態だとシステムプロパティで値を指定することはできない

システムプロパティでパラメータを指定できるようにする

HelloMojo.java
package example;
...

@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
    @Parameter(property="hello.message")
    private String message;

    public void execute() throws MojoExecutionException {
        getLog().info("message = " + message);
    }
}
  • @Parameterproperty に、システムプロパティで指定するときの名前を設定する
pom.xml(別プロジェクト)
...
      <plugin>
        <groupId>example</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <version>1.0.0</version>
      </plugin>
...
  • <configuration> の指定はしないようにする(設定していると、こちらが優先される)
実行結果
> mvn hello:hello -Dhello.message="HELLO WORLD!!"
...
[INFO] message = HELLO WORLD!!
  • システムプロパティ経由で値を設定できるようになった

パラメータのデフォルト値を設定する

HelloMojo.java
package example;
...

@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
    @Parameter(property="hello.message", defaultValue="Hello Custom Plugin!!")
    private String message;

    public void execute() throws MojoExecutionException {
        getLog().info("message = " + message);
    }
}
  • @Parameter アノテーションの defaultValue で、そのパラメータのデフォルト値を宣言できる
pom.xml(別プロジェクト)
...
      <plugin>
        <groupId>example</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <version>1.0.0</version>
      </plugin>
...
  • <configuration> は未設定にしている
実行結果
> mvn hello:hello
...
[INFO] message = Hello Custom Plugin!!
...

> mvn hello:hello -Dhello.message=OVERRIDE!!
...
[INFO] message = OVERRIDE!!
  • 何も設定されていない場合は、 defaultValue で設定した値が採用されているのがわかる

デフォルト値に式を使用する

HelloMojo.java
package example;
...

@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
    @Parameter(defaultValue="${hello.mojo.message}")
    private String message;

    public void execute() throws MojoExecutionException {
        getLog().info("message = " + message);
    }
}
  • defaultValue の値には、 ${...} のようにして式を記述できる
  • 式の中で参照できる値については PluginParameterExpressionEvaluator の Javadoc を参照
  • システムプロパティやプロジェクトのプロパティ(<properties>)も参照できる
pom.xml(別プロジェクト)
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <properties>
    <hello.mojo.message>Project Property</hello.mojo.message>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>example</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <version>1.0.0</version>
      </plugin>
    </plugins>  
  </build>
</project>
  • <properties>defaultValue の式で宣言されていたものと同じキーで値を宣言している
実行結果
> mvn hello:hello
...
[INFO] message = Project Property
...

> mvn hello:hello -Dhello.mojo.message="System Property"
...
[INFO] message = System Property
  • <properties> で宣言された値が messge パラメータに設定されている
  • システムプロパティで上書きすることもできている

パラメータに使用できる型

  • パラメータの型は、 String だけでなく様々な型で宣言できる
HelloMojo.java
package example;

import java.io.File;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
    @Parameter
    private int intValue;
    @Parameter
    private long longValue;
    @Parameter
    private boolean booleanValue;
    @Parameter
    private double doubleValue;
    @Parameter
    private Date dateValue;
    @Parameter
    private File fileValue;
    @Parameter
    private URL urlValue;
    @Parameter
    private HelloEnum enumValue;
    @Parameter
    private List<String> listValues;
    @Parameter
    private Map<String, String> mapValue;

    public void execute() throws MojoExecutionException {
        Log log = getLog();
        log.info("intValue=" + intValue);
        log.info("longValue=" + longValue);
        log.info("booleanValue=" + booleanValue);
        log.info("doubleValue=" + doubleValue);
        log.info("dateValue=" + dateValue);
        log.info("fileValue=" + fileValue);
        log.info("urlValue=" + urlValue);
        log.info("enumValue=" + enumValue);
        log.info("listValues=" + listValues);
        log.info("mapValue=" + mapValue);
    }

    public enum HelloEnum {
        HELLO,
        WORLD;
    }
}
  • 様々な型でパラメータを定義している
pom.xml(別プロジェクト)
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      <plugin>
        <groupId>example</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <version>1.0.0</version>
        <configuration>
          <intValue>123</intValue>
          <longValue>1234567890</longValue>
          <booleanValue>true</booleanValue>
          <doubleValue>1234.5678</doubleValue>
          <dateValue>2019-10-20 12:13:14</dateValue>
          <fileValue>foo/bar</fileValue>
          <urlValue>https://www.google.co.jp/</urlValue>
          <enumValue>HELLO</enumValue>
          <listValues>
            <aaa>fizz</aaa>
            <bbb>buzz</bbb>
          </listValues>
          <mapValue>
            <foo>FOO</foo>
            <bar>BAR</bar>
          </mapValue>
        </configuration>
      </plugin>
    </plugins>  
  </build>
</project>
実行結果
> mvn hello:hello
...
[INFO] intValue=123
[INFO] longValue=1234567890
[INFO] booleanValue=true
[INFO] doubleValue=1234.5678
[INFO] dateValue=Sun Oct 20 12:13:14 JST 2019
[INFO] fileValue=F:\tmp\maven\hello\foo\bar
[INFO] urlValue=https://www.google.co.jp/
[INFO] enumValue=HELLO
[INFO] listValues=[fizz, buzz]
[INFO] mapValue={bar=BAR, foo=FOO}
...
  • intlong, float, double, boolean などのプリミティブ型は、普通に使用可能
    • ラッパークラスも可能
  • java.util.Date は、次のいずれかの書式で指定する
    • yyyy-MM-dd HH:mm:ss.S a (例:2005-10-06 2:22:55.1 PM
    • yyyy-MM-dd HH:mm:ssa (例:2005-10-06 2:22:55PM
    • ただし、完全に一致していなくても、そこそこ柔軟に解析してくれる(AM, PM は省略できるけど、時刻は省略できないっぽい)
  • java.io.File は、値をパスとして扱う
  • java.net.URL は、値を URL として扱う
  • enum 型の場合は、列挙された定数と同じ値を指定することで設定可能
  • List の場合は、パラメータ名と同じ要素を書いた中に任意の名前の要素を列挙すれば、それが List の要素として処理されるっぽい
    • 普通は <listValues><listValue>... のように記述するのがわかりやすいと思う
  • Map の場合は、入れ子要素の名前がキーに、要素の値がバリューになる
    • 検証は省略したけど、 java.util.Properties も同じように使用できる

デフォルトのフェーズを紐付ける

HelloMojo.java
package example;

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;

@Mojo(name="hello", defaultPhase=LifecyclePhase.VALIDATE)
public class HelloMojo extends AbstractMojo {

    public void execute() throws MojoExecutionException {
        getLog().info("Hello Mojo!!");
    }
}
  • @MojodefaultPhase で、デフォルトのフェーズを指定できる
  • フェーズの指定には、 LifecyclePhase 列挙型を使用する
  • ここでは validate フェーズに紐づけている
pom.xml(別プロジェクト)
...
      <plugin>
        <groupId>example</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <version>1.0.0</version>
        <executions>
          <execution>
            <goals>
              <goal>hello</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
...
  • hello ゴールだけ宣言して、フェーズの紐付けは行っていない
実行結果(別プロジェクト)
> mvn validate
...
[INFO] Hello Mojo!!
  • validate フェーズの実行で hello ゴールが実行されている

生成物を削除する

フォルダ構成
|-pom.xml
`-target/
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>example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0.0</version>
</project>
実行結果
> mvn clean
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
フォルダ構成(実行後)
`-pom.xml
  • maven-clean-pluginclean ゴール を実行すると、プロジェクトの生成物をすべて削除できる
    • ここで指定しているのは clean フェーズ
    • clean ゴールは、デフォルトで clean フェーズに紐付いている(ややこしい)
  • デフォルトでは、次のフォルダが削除対象となっている
    • ${project.build.directory}
    • ${project.build.outputDirectory}
    • ${project.build.testOutputDirectory}
    • ${project.reporting.outputDirectory}
  • 特に上記フォルダのパスを変更していないのであれば、 ${project.basedir}/target が削除される

削除対象のフォルダを追加する

フォルダ構成
|-pom.xml
|-target/
`-foo/
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-clean-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
          <filesets>
            <fileset>
              <directory>${project.basedir}/foo</directory>
            </fileset>
          </filesets>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • <filesets> を追加して、 foo フォルダも削除対象に追加している
実行結果
> mvn clean
...
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ hello ---
[INFO] Deleting ...\foo (includes = [], excludes = [])
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
フォルダ構成(実行後)
`-pom.xml
  • target だけでなく、 foo も削除されている
  • filesets パラメータ で、削除対象のフォルダやファイルを追加できる
  • <filesets> は、 Fileset のコレクションを指定する
    • <directory> だけでなく、 <includes><excludes> でファイルを絞ることもできるっぽい(試してない)

参考兼公式ドキュメント目次

どこになんの情報があるのか分かりにくすぎるので整理してみる

  1. Maven 1.0 が 2004 年、Maven 2.0 が 2005 年、Maven 3.0 が 2010 年(Maven – Maven Releases History

  2. %USERPROFILE% はユーザのホームを指す Windows の環境変数(Linux とかなら $HOME のこと)

  3. <modelVersion> も Super POM にあるやん」って思うけど、これは残念ながら継承されないもよう(消すとエラーになる)

  4. 厳密には compiler::::compile のような指定の場合もプレフィックス指定と判定されるけど、ここはわかりやすさを優先してコロンの数としている(詳しくは MojoDescriptorCreator の実装を参照

  5. <release> はリリースバージョン内での最新なのに対して、 <latest> はスナップショットも含めて最新のバージョンを指している

  6. 厳密には settings.xml<pluginGroups> で検索対象の groupId を追加できる(参考:Introduction to Plugin Prefix Resolution

  7. In Maven 2.0.5 and above, multiple goals bound to a phase are executed in the same order as they are declared in the POM,

416
431
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
416
431

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?