読者です 読者をやめる 読者になる 読者になる

Mavenでビルドする際の10のTips

maven

1. 環境別の設定はプロファイルで

環境毎に切り替えたいっていう設定ファイルは大抵のプロジェクトにはあると思います。DB接続先設定だったり、ロギング設定、場合によってはweb.xmlの初期化パラメータとか。最近流行り?のAppEngineだとデプロイ先の設定、開発時のcronの設定とか。こういった環境毎の設定を都度都度書き換えてなんてことをやってたらバージョン管理上うまくない*1ですし、Hudson、その他自動化スクリプトからデプロイを行ったりする際に色々とうまくないです。なので、こういった設定はプロファイルを使ってサクっと切り替えられるようにしてます。

詳しいプロファイルの使い方*2についてはそのうち別エントリで書く*3!...と思います。基本的なことはTECHSCOREさんのここを参照すればかなり分かるはずです。自分はここで覚えました。ただMaven3からはprofiles.xmlの使用が出来ない*4ので、今からやるのであれば使わないほうが無難です。

2. ライブラリの定義はdependencyManagementで

マルチスタイルのプロジェクトで共有するようなライブラリ*5はdependencyManagement句でバージョンを定義しておくのがいいです。dependencyManagementというのはdependenciesとは違って依存関係の定義ではなく、依存関係の定義の為の定義ってところです。百聞は一見にしかず。

親POMなんかで以下の定義をしておきます。これだけだと依存関係に追加されたことにはならないので、クラスパスに展開してくれたりはしません。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>0.9.21</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.1</version>
    </dependency>
  </dependencies>
</dependencyManagement>

ここで定義したライブラリを実際に使うモジュール側で"使うこと"を明示して、適切なスコープでクラスパスに追加してあげます*6。この際もう既にdependencyManagementで依存関係の定義、つまりバージョンの指定をしてあるので使う側では書く必要はないです*7

<dependencies>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

この方式でやると別ライブラリが使ってる同じライブラリのバージョンが衝突する場合でもexclusion指定をdependencyManagementで書けるので都度定義する必要がなく、見通しも良く出来て一石二鳥!

3. pluginのバージョンは必ず書く

Mavenはpluginのバージョンを省いても空気を読んで解決してくれちゃったりしますが、これを使ってるとある日突然ビルドが通らなくなったりしてワケワカメなことになるんで、ちゃんと書いてあげるのがいいです。これを書いておけばそういったことは防げますし、Mavenのバージョンを上げてもまず問題はないでないです。Maven3だとこの辺りのチェックが厳しくなって、バージョンが省かれていると警告ログが出るようになってます。警告ログを見にくいって方はid:cynipe:20101023:1287842267をどうぞ。

4. pluginの定義もpluginManagementで

マルチスタイルプロジェクトの場合は、ライブラリの定義よろしくpluginの定義もpluginManagementでやっておくといいです。プロジェクト内で必ず使わないんだけど〜なplugin用に便利。例えばmaven-war-pluginとかmaven-jetty-pluginとか。

5. Eclipseの設定ファイルはバージョン管理しない

これは好みが分かれるところではあるんですが、バージョン管理しておくと生成日時のせいでコミットしないとならなくなったりといらないお世話が必要になるので、mvn eclipse:eclipseすればいいじゃない派です。それに1.をやっているとどのみちmvn process-resourcesとか叩く必要が出てくるので、まぁいいかなと。フォーマットの設定やら、FindBugsの設定とかが必要な場合は適当なフォルダを掘ってそこから参照出来るように書いておくのがいいです。maven-eclipse-pluginプロジェクト的にはsrc/optional/eclipse-configの下においてあるみたいですね。->こちら

6. 共通の設定を持つ親POM必ず作る

Maven主体で色々と管理しだすとプロジェクトのpomが比較的大きくなってきます。JDKのバージョン*8エンコーディング*9、参照するMavenリポジトリ*10Eclipseの設定、使うMavenPluginのバージョンなど色々と書くことになるので、これを毎回書くのはいただけないですし、POMが巨大になってしまい見通しが悪くなってしまいます。なので、シングルプロジェクトだったとしても分けておいた方がいいです。自分は複数のwarが出来上がるマルチスタイルプロジェクトの場合はwarモジュール用に別途大元のParentPOMを継承する形で基底のPOMを作ったりしてます。

7. ビルドを統合するAggregatorPOMを作る

全てのモジュールをまとめてビルドする為のPOMは基底の親POMとは別に作ったりしてます。Mavenは子モジュールのparentで指定しなくても勝手に親POM*11を定義できるのでこれを利用して、modulesの定義をするPOMを別に作ってます。基底の設定は親POMに、ビルドの統合はAggregatorにといった感じです。このAggregatorは基本的にmodulesの統合が主な役割で、それ以外はデプロイ先リポジトリの定義以外は基本的にしません。HudsonのjobはこのPOMに対してゴール、プロファイルの指定をして実行してます。

なぜ6.と7.を分けているのかというと、基底の親POMの設定を別のプロジェクトで使いたかったりするので、modulesの定義も書いてしまうと使いまわせなくなってしまってもひとつうまくないからです。この問題?はMaven3で期待されている*12pom-mixin機能がでてくれば、各種設定を細かく分けておいてそれぞれ統合というのがAggregatorで簡単に出来るようになるはずなのでそれ待ちな感じです。pom-mixinはやくでないかなー。

8. TomcatなどへのデプロイはMavenでやらない

TomcatなどのAPサーバへのデプロイはMavenで書かないのがいいと思ってます。何故かというと、この設定は基本的にはソースレベル、アーティファクトレベルでの影響をプロジェクトに与えないというのと、ここを細々と書いていくとPOMの見通しが悪くなっていってしまうからです。なのでこういったタイプの設定はHudsonで気楽に行うことができるのでPOMでは定義せずどこかにデプロイすればいいよってスタイルでやってます。

9. warプロジェクトはjettyで起動出来るようにしておくといい

8.で言ってることと矛盾するやんけ!と思われるかもしれませんが、SVNからチェックアウトしてmvn jetty:runでその場で確認出来るというのは管理する人とか的には嬉しいことだと思います。わざわざローカルにTomcatを入れて、Eclipseの設定をしてなんてしなくても、簡単なバグの修正確認、その場でデモなんてのも手軽にできるので。

maven-jetty-pluginの設定は特殊なことでもしない限り↓位ですんでしまうので入れておくと少し幸せになれるかと思います。開発する人はSysdeoTomcatプラグインとか使っててもいいと思います。重要なのはチェックアウトしてすぐ確認が出来るようにしておくことです。

<plugin>
  <groupId>org.mortbay.jetty</groupId>
  <artifactId>maven-jetty-plugin</artifactId>
  <version>6.1.10</version>
  <configuration>
    <contextPath>/${project.artifactId}</contextPath>
    <scanIntervalSeconds>10</scanIntervalSeconds>
    <connectors>
      <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
        <port>8080</port>
        <maxIdleTime>60000</maxIdleTime>
      </connector>
    </connectors>
  </configuration>
</plugin>

10. なんでもMavenでやりすぎない

そしてここが一番重要なんですが、なんでもかんでもMavenでやりすぎないことです。Mavenの便利さに目覚めるとついついMavenでやるにはどうすれば?とかMavenでなんでもかんでもやろうとして複雑怪奇であうあうあーっていうのは比較的良く起こりがちに思います。かくいう自分にもそんな時代がありました。

さっきも上げたようにAPサーバへのデプロイはHudsonで気軽にできたりしますし、コードカバレッジなんかもHudsonで簡単にできる*13のでこういったものを使ってゴニョゴニョすればトータルで楽できますし、不幸せにならずに済むかと思います。

また、Mavenは複雑なことが出来ない!とよく言われますが、そこはそれなりに仰る通りかと思います。とは言われつつも自分はある程度型にはめられるMavenが好きですし、antでゴリゴリ書くのに比べて楽できる部分はほんとに楽に出来ます*14。要は適材適所なのでAntの方が簡単にできるのであればそこだけAntを使う。連携したければmaven-antrun-pluginで。Mavenでやり過ぎてしまうとほんとに作った人しか分からなくなってしまったりするので。

おまけ. defaultGoalを実行するbuild.xmlを作る

これは若干やりすぎなのかもですが、mavenはdefaultGoalに複数のゴールを指定することが現状出来ません。なので初回チェックアウト時の処理だったり、プロファイルを使ったりしてゴニョゴニョしたりしてる場合なんかは若干めんどくさかったりします。こういった際のプリセットコマンド的なものをMaven Ant Tasksを使ってxxx-build.xmlとかしておいておくとちょっぴり幸せになれます。なぜbatでなくAntなのかというとWin、Macユーザがいたりした場合にAntで書いておけば両方書く必要がないので楽チンできるという最近ちょっとお気に入りな手法。

ただこれも若干黒魔術というか、どのコマンドを叩いているのか意識せずにデフォルトセットが使えてしまうようになるので、やりすぎてしまうと綺麗にブラックボックス化されたビルド環境が出来上がり、知らない人は全くメンテ出来ないという事態になりかねないので程々に。

*1:ローカルで変更しててうっかりコミットとか、それがさらにConflictしてあうあうあーとか

*2:resourceのfiltering、propertiesとか

*3:かなり深いので・・・

*4:非推奨なだけかも

*5:ユーティリティ系だったり、兄弟モジュールだったりとか

*6:dependencyManagementでもスコープの定義は出来ますが、使う側で定義するのが筋だと思います。

*7:むしろ書いちゃうと意味ない

*8:maven-compiler-plugin

*9:project.build.sourceEncodingとか

*10:社内リポジトリとか

*11:自分はこれをAggregatorって呼んでます

*12:はず!

*13:自分は[http://wiki.hudson-ci.org/display/HUDSON/Emma+Plugin:title=Emma Plugin]を使ってますが、pomになにも書かずにhudsonでMavenゴールを設定するときにemma:emmaをおまじない的に追加しておけばあとはよしなに計らってくれます。

*14:appassembler-maven-pluginとか凄く楽チンですよ。