Maven 3.5のビルドライフサイクルを学ぶ

# 概要

Maven3のビルドライフサイクル、フェーズ、ゴールについて学んだ内容をまとめた記事です。
なお、依存関係の解決やアーティファクトのリポジトリへのインストール、プロファイルの仕組み等については扱いません。

**環境**

* Windows 10 Professional
* Java 1.8.0_144
* Maven 3.5.2

**参考**

* [Maven] (https://maven.apache.org/index.html)
* [GitHub- apache/maven-plugins] (https://github.com/apache/maven-plugins)
* [最新の Maven プラグインを習得するためのアドバイス] (https://www.ibm.com/developerworks/jp/java/library/j-5things16/index.html)

# インストール

[Maven] (https://maven.apache.org/index.html)のdownloadページよりバイナリのアーカイブファイルをダウンロードし適当な場所へ展開します。
展開したディレクトリにあるbinを環境変数pathに追加します。

**バージョン確認**

```bat
> mvn --version
Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T16:58:13+09:00)
Maven home: D:\dev\apache-maven-3.5.2\bin\..
Java version: 1.8.0_144, vendor: Oracle Corporation
Java home: C:\Program Files\Java\jdk1.8.0_144\jre
Default locale: ja_JP, platform encoding: MS932
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
```

**Usage**

```text
mvn [options] [<goal(s)>] [<phase(s)>]
```

# 主な用語

Mavenの仕組みを理解するうえでよく出てくる用語についてまとめます。

## [ビルドライフサイクル] (https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)

Maven3には下記の3つのbuilt-inビルドライフサイクルがあります。

* default : プロジェクトのアーティファクトの作成およびデプロイ
* clean : プロジェクトのクリーニング
* site : プロジェクトサイトの作成およびデプロイ

それぞれのビルドライフサイクルは決められた一連のフェーズから構成されています。
たとえばcleanビルドライフサイクルは下記の3つのフェーズから構成されています。

* pre-clean
* clean
* post-clean

ビルドライフサイクルがどのようなフェーズで構成されているかはcomponents.xmlで定義されていて、maven-3.5.2の場合ではmaven-core-3.5.2.jarのMETA-INF/plexus/components.xmlで確認することができます。
下記はcleanビルドライフサイクルの定義をcomponents.xmlから抜粋した内容です。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<component-set>
  <components>

    <component>
      <role>org.apache.maven.lifecycle.Lifecycle</role>
      <implementation>org.apache.maven.lifecycle.Lifecycle</implementation>
      <role-hint>clean</role-hint>
      <configuration>
        <id>clean</id>
        
        <phases>
          <phase>pre-clean</phase>
          <phase>clean</phase>
          <phase>post-clean</phase>
        </phases>
        <default-phases>
          <clean>
            org.apache.maven.plugins:maven-clean-plugin:2.5:clean
          </clean>
        </default-phases>
        
      </configuration>
    </component>

  </components>
</component-set>
```

ちなみにmvnコマンドの引数にビルドライフサイクルを指定して実行することはできません。
下記のmvnコマンドはcleanビルドライフサイクルを実行するという意味ではなく、cleanビルドライフサイクルのcleanフェーズを実行するように指定しています。なので実行されるのはpre-cleanとcleanフェーズでpost-cleanフェーズは実行されません。

```bat
> mvn clean
```

試しにdefaultビルドライフサイクル（の全体）を実行するつもりで下記のmvnコマンドを実行するとエラーになります。

```bat
> mvn default

// 省略

[ERROR] Unknown lifecycle phase "default". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]

// 省略
```

## フェーズ

フェーズとは、ビルドライフサイクルで実行する各工程のことです。あるフェーズを実行すると、そのフェーズのビルドライフサイクルの先頭フェーズから指定したフェーズまで順番に実行されます。

たとえばdefaultビルドライフサイクルのcompileフェーズを実行すると、defaultビルドライフサイクルの先頭フェーズであるvalidateからはじまりinitialize → generate-sources → process-sources → generate-resources → process-resources → compileの順で実行されます。

それぞれのフェーズは0個以上のゴールから構成されています。ゴールは後述しますが実態はMavenプラグインの特定の処理（を実装したJavaクラス）です。
フェーズに対してゴールが紐づけられている（以下、バインドと表記します）場合に、ゴールが実行されます。バインドの方法はいくつかありますがプラグインがデフォルトのフェーズを設定したり、pom.xmlで指定するなどの方法があります。

mvnコマンドでフェーズを指定するにはフェーズ名を指定します。
下記はcleanフェーズを実行します。

```bat
> mvn clean
```

フェーズはフェーズ名を半角スペースで区切って複数個指定することができます。
下記はcleanライフサイクルのcleanフェーズを実行し、続けてdefaultライフサイクルのpackageフェーズを実行します。

```bat
> mvn clean package
```

ゴールがバインドされていないフェーズもありますが、そのようなフェーズでは何の処理も実行されません。
たとえばdefaultビルドライフサイクルのvalidateフェーズにはデフォルトではなんのゴールもバインドされていないため、下記のmvnコマンドを実行しても何も実行されません。

```bat
> mvn validate

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.120 s
[INFO] Finished at: 2017-11-27T20:05:18+09:00
[INFO] Final Memory: 6M/276M
[INFO] ------------------------------------------------------------------------
```

このような、ゴールがバインドされていないフェーズはデフォルトでは意味を持っていませんが、サードパーティや自作のプラグインをバインドしてビルドライフサイクルを拡張するような使い方ができます。

### コマンドラインから通常は実行しないフェーズ

`pre-`、`post-`、`process-`などのprefixが付いたフェーズは、通常コマンドラインから実行しないようですが、指定してはいけないというものでもないようです。

以下は公式ページの関連個所からの引用です。

> [Introduction to the Build Lifecycle] (https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
>
> Some Phases Are Not Usually Called From the Command Line
> 
> The phases named with hyphenated-words (pre-\*, post-\*, or process-\*) are not usually directly called from the command line. These phases sequence the build, producing intermediate results that are not useful outside the build. In the case of invoking integration-test, the environment may be left in a hanging state.

## ゴール

ゴールとは、Mavenプラグインに実装されている特定の処理を実行するJavaクラスにつけられた名前です。Mavenはフェーズとこの名前を紐づけます。
1つのMavenプラグインには複数のゴールを実装することができ、1つのゴールはmaven-plugin-apiのAbstractMojoクラス（AbstractMojoはMojoインターフェースをimplementしている）を継承しています。

たとえばmaven-compiler-pluginの場合、compileとtestCompileというゴールを持っていますが、それぞれのゴールは下記のように実装されています。
MojoアノテーションのdefaultPhase属性で、そのゴールをバインドするデフォルトフェーズを指定しています。

**compileゴール**

```java:CompilerMojo.java
@Mojo( name = "compile", defaultPhase = LifecyclePhase.COMPILE, threadSafe = true, 
    requiresDependencyResolution = ResolutionScope.COMPILE )
public class CompilerMojo
    extends AbstractCompilerMojo
{

    //...省略

    public void execute()
        throws MojoExecutionException, CompilationFailureException
    {
        if ( skipMain )
        {
            getLog().info( "Not compiling main sources" );
            return;
        }

        super.execute();

        if ( outputDirectory.isDirectory() )
        {
            projectArtifact.setFile( outputDirectory );
        }
    }

    //...省略

}
```

**testCompileゴール**

```java:TestCompilerMojo.java
@Mojo( name = "testCompile", defaultPhase = LifecyclePhase.TEST_COMPILE, threadSafe = true,
                requiresDependencyResolution = ResolutionScope.TEST )
public class TestCompilerMojo
    extends AbstractCompilerMojo
{

    //...省略

    public void execute()
        throws MojoExecutionException, CompilationFailureException
    {
        if ( skip )
        {
            getLog().info( "Not compiling test sources" );
            return;
        }
        super.execute();
    }

    //...省略

}
```

### ゴールが実行されるタイミング

ゴールが実行される状況はいくつかありますが主だったものに下記があります。

1. フェーズにバインドされ、そのフェーズが実行された場合
2. mvnコマンドから明示的にゴールを指定された場合
    3. 明示的に指定したゴールが依存しているフェーズが実行された場合
3. 他のプラグインから呼び出された場合（例えば[TimMoore/mojo-executor] (https://github.com/TimMoore/mojo-executor)を使って）

たとえばソースコードをコンパイルするためにmaven-compiler-pluginのcompileゴールを実行したい場合は

**フェーズを指定してコンパイル**

compiler:compileゴールはデフォルトでcompileフェーズにバインドされているのでcompileフェーズを指定するとcompileゴールを実行します。

```bat
> mvn compile
```

**ゴールを指定してコンパイル**

このようにゴールを指定することでもcompileゴールを実行することができます。

```bat
> mvn compiler:compile
```

この2つの実行方法はソースコードをコンパイルするということでは同じですが、厳密に言うと実行結果が違う場合があります。

compileフェーズを実行した場合は、defaultビルドライフサイクルのvalidateフェーズからcompileフェーズまで順に実行されます。またpom.xmlでなんらかのプラグインのゴールをcompileフェーズにバインドしている場合は、そのゴールも実行されます。

compiler:compileゴールを実行した場合は、単にこのゴールの処理だけが実行されます。（後述しますが、ゴールの実装によっては、そのゴールが他のフェーズに依存している場合がありますので、そのときは依存するフェーズも実行されます）

### ゴールが依存するフェーズ

ゴールを指定して直接実行する場合、そのゴールが依存しているフェーズがあれば、まずそのフェーズが実行されます。

たとえばspring-boot-maven-pluginのrunゴールは、test-compileフェーズに依存しているので、下記のように実行した場合、まずtest-compileフェーズまで実行された後に、runゴールが実行されます。

```bat
> mvn spring-boot:run
```

以下は公式ドキュメントからの関連個所の引用です。

> [spring-boot:run] (https://docs.spring.io/spring-boot/docs/1.5.8.RELEASE/maven-plugin/run-mojo.html)
> 
> Attributes:
> Invokes the execution of the lifecycle phase test-compile prior to executing itself.

### ゴールのスキップ

ゴールの中にはプロパティによってゴールの実行をスキップさせることができるものがあります。

|ゴール                   |プロパティ           |
|:-----------------------|:--------------------|
|resources:resources     |maven.resources.skip |
|resources:testResources |maven.test.skip      |
|compiler:compile        |maven.main.skip      |
|compiler:testCompile    |maven.test.skip      |
|surefire:test           |maven.test.skip      |
|〃                      |skipTests            |
|failsafe:integration-test |maven.test.skip      |
|〃                      |skipITs              |
|failsafe:verify         |maven.test.skip      |
|〃                      |skipITs              |
|install:install         |maven.install.skip   |
|deploy:deploy           |maven.deploy.skip    |

testフェーズのスキップについて

以下のプロパティはtestフェーズに加え、integration-test、verifyフェーズをスキップします。

```bat
> mvn clean install -DskipTests
```

以下のプロパティはtestフェーズに加え、test-compile、integration-test、verifyフェーズをスキップします。

```bat
> mvn clean install -Dmaven.test.skip=true
```

## プラグイン

プラグインにはMaven Projectがサポートする公式のプラグインと、それ以外（例えばサードパーティが開発するプラグインやユーザーが自作するプラグイン）に分けることができます。
公式のプラグインは[Available Plugins] (https://maven.apache.org/plugins/index.html)で確認することができます。

プラグインの命名には規則があり、この命名規則で公式プラグインかどうかを判別することができます。
公式のプラグインの名前は下記のルールになります。（自作のプラグインにこのルールで名前を付けてはいけません。）

```text
maven-{plugin-name}-plugin
```

公式以外のプラグインの名前は下記のルールになります。

```text
{plugin-name}-maven-plugin
```

# 補足

## 各ビルドライフサイクルのデフォルトのゴール

### cleanビルドライフサイクル

pre-clean、post-cleanフェーズにはゴールがバインドされていないので何も実行されません。

|phase                  |plugin                |goal          |
|:----------------------|:---------------------|:-------------|
|pre-clean              | | |
|clean                  |maven-clean-plugin    |clean         |
|post-clean             | | |

### defaultビルドライフサイクル

defaultビルドライフサイクルでは、どのフェーズでどのゴールが実行されるかはパッケージの種類によって変わります。詳細は[Plugin Bindings for default Lifecycle Reference] (https://maven.apache.org/ref/3.5.2/maven-core/default-bindings.html)で確認できます。
下表はパッケージの種類がjarの場合です。
ちなみにパッケージの種類はpom.xmlのpackaging要素で定義します。明示的に定義しない場合はデフォルトとしてjarが選択されます。

|phase                  |plugin                |goal          |
|:----------------------|:---------------------|:-------------|
|validate               | | |
|initialize             | | |
|generate-sources       | | |
|process-sources        | | |
|generate-resources     | | |
|process-resources      |maven-resources-plugin|resources     |
|compile                |maven-compiler-plugin |compile       |
|process-classes        | | |
|generate-test-sources  | | |
|process-test-sources   | | |
|generate-test-resources| | |
|process-test-resources |maven-resources-plugin|testResources |
|test-compile           |maven-compiler-plugin |testCompile   |
|process-test-classes   | | |
|test                   |maven-surefire-plugin |test          |
|prepare-package        | | |
|package                |maven-jar-plugin      |jar           |
|pre-integration-test   | | |
|integration-test       | | |
|post-integration-test  | | |
|verify                 | | |
|install                |maven-install-plugin  |install       |
|deploy                 |maven-deploy-plugin   |deploy        |

### siteビルドライフサイクル

|phase                  |plugin                |goal          |
|:----------------------|:---------------------|:-------------|
|pre-site               | | |
|site                   |maven-site-plugin     |site          |
|post-site              | | |
|site-deploy            |maven-site-plugin     |deploy        |

## Toolsプラグイン

Maven Projectがサポートするプラグインには、Toolsプラグインのようにデフォルトではビルドライフサイクルにバインドされていないプラグインがあります。このようなプラグインは特定の処理を実行するタスクとして利用します。

Toolsプラグインには以下のようなものがあります。

* [Maven Assembly Plugin] (https://maven.apache.org/plugins/maven-assembly-plugin/)
* [Maven Archetype Plugin] (https://maven.apache.org/archetype/maven-archetype-plugin/)
* [Maven Dependency Plugin] (https://maven.apache.org/plugins/maven-dependency-plugin/)
* [Maven Help Plugin] (https://maven.apache.org/plugins/maven-help-plugin/)

### [Maven Archetype Plugin] (http://maven.apache.org/archetype/maven-archetype-plugin/)

**generateゴール**

アーキタイプと呼ぶプロジェクトのテンプレートからMavenプロジェクトのひな型を生成します。

```bat
> mvn archetype:generate -B \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DgroupId=com.example \
-DartifactId=my-project \
-Dversion=1.0-SNAPSHOT
```

Mavenプラグインのプロジェクトのひな型を生成するにはrchetypeArtifactIdに`maven-archetype-plugin`を指定します。

```bat
> mvn archetype:generate -B \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-plugin \
-DgroupId=com.example \
-DartifactId=my-maven-plugin \
-Dversion=1.0-SNAPSHOT
```

### [Maven Dependency Plugin] (https://maven.apache.org/plugins/maven-dependency-plugin/)

**treeゴール**

プロジェクトの依存関係をツリー表示します。

```bat
> mvn dependency:tree
```

結果をファイルに保存するにはoutputFileでファイル名を指定します。
このファイルをバージョン管理することで依存関係の変化を把握しやすくなります。

```bat
> mvn dependency:tree -DoutputFile=dependecy.txt
```

ビルド時に自動的に出力させるにはライフサイクルに組み込みます。

```xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>tree</id>
            <phase>compile</phase>
            <goals>
                <goal>tree</goal>
            </goals>
            <configuration>
                <outputFile>dependency.txt</outputFile>
            </configuration>
        </execution>
    </executions>
</plugin>
```


### [Maven Help Plugin] (https://maven.apache.org/plugins/maven-help-plugin/)

**effective-pomゴール**

デフォルトの設定を含めたpom.xmlの内容を表示します。

```bat
> mvn help:effective-pom
```

**systemゴール**

環境変数やシステムプロパティを表示します。

```bat
> mvn help:system
```

**describeゴール**

pluginプロパティで指定するプラグインのhelpメッセージを表示します。

```bat
> mvn help:describe -Dplugin=compiler
```

より詳細な情報を得るにはdetailプロパティを指定します。

```bat
> mvn help:describe -Dplugin=compiler -Ddetail
```

cmdプロパティにフェーズを指定するとそのフェーズを含むビルドライフサイクルの各フェーズにバインドされているプラグインを調べることができます。

```bat
> mvn help:describe -Dcmd=package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building my-project 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-help-plugin:2.2:describe (default-cli) @ my-project ---
[INFO] 'package' is a phase corresponding to this plugin:
org.apache.maven.plugins:maven-jar-plugin:2.4:jar

It is a part of the lifecycle for the POM packaging 'jar'. This lifecycle includes the following phases:
* validate: Not defined
* initialize: Not defined
* generate-sources: Not defined
* process-sources: Not defined
* generate-resources: Not defined
* process-resources: org.apache.maven.plugins:maven-resources-plugin:2.6:resources
* compile: org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
* process-classes: Not defined
* generate-test-sources: Not defined
* process-test-sources: Not defined
* generate-test-resources: Not defined
* process-test-resources: org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
* test-compile: org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
* process-test-classes: Not defined
* test: org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
* prepare-package: Not defined
* package: org.apache.maven.plugins:maven-jar-plugin:2.4:jar
* pre-integration-test: Not defined
* integration-test: Not defined
* post-integration-test: Not defined
* verify: Not defined
* install: org.apache.maven.plugins:maven-install-plugin:2.4:install
* deploy: org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.147 s
[INFO] Finished at: 2017-12-03T13:34:00+09:00
[INFO] Final Memory: 11M/276M
[INFO] ------------------------------------------------------------------------
```


それぞれのプラグインでhelpゴールを実行することで同様のhelpメッセージが取得できます。

```bat
> mvn compiler:help
```

## サードパーティのプラグイン

### [MojoHaus Maven Plugins Project] (http://www.mojohaus.org/)

たくさんのプラグインを公開しているプロジェクトです。Codehasが2015年で運営を停止したため、MojaHausはそれを引き継ぐ形で発足しました。（詳しくは[The Demise of Open Source Hosting Providers Codehaus and Google Code] (https://www.infoq.com/news/2015/03/codehaus-google-code)をご覧ください）

メジャーなプラグインでは[FindBugs Maven Plugin] (http://www.mojohaus.org/findbugs-maven-plugin/)があります。

### [Spring Boot Maven Plugin] (https://docs.spring.io/spring-boot/docs/1.5.8.RELEASE/maven-plugin/)

[spring-projects/spring-boot] (https://github.com/spring-projects/spring-boot/tree/master/spring-boot-project/spring-boot-tools)

Spring Bootアプリケーションで使用するプラグインです。

**Goals**

|Goal      |Binds by default to the lifecycle phase |
|:---------|:---------------------|
|run       |validate              |
|repackage |package               |
|start     |pre-integration-test  |
|stop      |post-integration-test |
|build-info|generate-resources    |

**runゴール**

spring bootアプリケーションを実行します。

```bat
> mvn spring-boot:run
```

プロファイルを指定するにはrun.profilesパラメータを使用します。

```bat
> mvn spring-boot:run -Drun.profiles=dev,test
```

なお、Spring Boot 2.0以降は下記のようになる予定です。(記2018/2/10)

```bat
> mvn spring-boot:run -Dspring-boot.run.profiles=dev,test
```


**build-infoゴール**

ビルド情報をtarget/classes/META-INF/build-info.propertiesに出力します。この情報はspring-boot-actuatorから参照されます。
プロパティファイルにどのような情報を出力するかはカスタマイズ可能です。
このゴールはデフォルトで、defaultビルドライフサイクルのgenerate-resourcesフェーズにバインドされています。

デフォルトでは以下の情報が出力されます。

```text
#Properties
#Fri Dec 01 14:01:25 JST 2017
build.time=2017-12-01T14\:01\:25+0900
build.artifact=singleds
build.group=com.example
build.name=singleds
build.version=0.0.1-SNAPSHOT
```

spring-boot-actuatorを有効にしていなくてもClass [BuildProperties] (https://docs.spring.io/spring-boot/docs/1.5.0.RELEASE/api/org/springframework/boot/info/BuildProperties.html)を介して取得できます。

```java
@Autowired
private BuildProperties prop;
```

### [Dependency-check-maven Plugin] (https://jeremylong.github.io/DependencyCheck/dependency-check-maven/index.html)

脆弱性を持つ依存関係があるかチェックしてくれるプラグインです。

**Goals**

|Goal      |Binds by default to the lifecycle phase |
|:---------|:---------------------|
|aggregate | |
|check     | |
|update-only | |
|purge     | |

**checkゴール**

プロジェクトが依存するライブラリに脆弱性があるか検査します。

```bat
> mvn dependency-check:check
```

## Tips集

参考になったtips。

* [Maven Tips and Tricks] (https://itnext.io/maven-tips-and-tricks-127ca293df50)
* [Maven Compiler Plugin Understanding] (https://mincong-h.github.io/2018/08/29/maven-compiler-plugin-understanding/)
* [MavenからJUnitを実行すると文字化けが発生する件の対応方法] (https://www.greptips.com/posts/925/)
* [The Apache Software Foundation Blogging in Action.] (https://blogs.apache.org/maven/)