Apache Ivyの紹介と基本的な使い方

Apache Ivyについては本ブログでも何回か用語自体は取り上げてきましたが、現状日本語での情報が限られるためか、AntそのものやMavenに比べるとユーザーが少ないように思われます。ここで基本的な使い方やMavenとの違いについて簡単に紹介させていただきたいと思います。

Apache Ivyとは

本家のホームページは以下の通りです。
Home | Apache Ivy ™
もともとはJayasoftという組織で開発されていたツールですが、バージョン2.0以降、Antの関連プロジェクトとしてApacheプロジェクトの元に加わっています。(Apacheというブランド名はツールを組織に導入する際に結構重要ですね。)
上記のホームページでは「アジャイルな依存性管理ツール」として紹介されていますが、Mavenの機能の中からビルド機能やプロジェクト管理機能を無くして、ライブラリーの依存関係の管理に特化したツールとなっています。「アジャイル」として紹介されている意味は、おそらく多くの機能を詰め込んでいるMavenと比較して、依存関係管理に特化していることにより、身軽であるということを強調しているのだと思います。しかし、実際に使いこんでみるとわかりますが、非常に高度なカスタマイズが可能な柔軟性も備えており、かなり奥の深いツールになっています。好みの分かれるところですが、確かに、多少押し付けがましい規約の多いMavenと比較して、本当に使いたい機能のみ利用して「The simplest thing that could possibly work = 要件を満たすもっとも単純なやり方」を実現するにはより適したツールであると言える思います。
基本的には以下のような場合に利用を検討するとよいと思います。

  • 長年Antを使っている組織で、ソースツリーのディレクトリー構造を変えられない場合
  • earファイルの生成など複雑なビルド対象を扱う場合
  • ビルドスクリプト専門の開発、保守担当をアサインできる場合

Antの専門家のいるプロジェクトで、かつ、Mavenに移行するのも大変という場合に重宝するツールです。有名なところだとSpring Framework(本体部分とWebFlowの部分)のビルドシステムがIvyを使って構築されています。また、Twitterscalaを使ったプロジェクトのいくつかでIvyを使っている例があったと思います。
なお、MavenとIvyとの選択については、以下もご覧ください。(Ant + Ivy vs Maven

Ivyのインストール手順

1.JDKのインストール

AntやIvyを利用するためには、当然JDKJREでなく)のインストールが必要です。
Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle
インストール後環境変数JAVA_HOMEをインストール先のディレクトリーに指定します。

2.Antのインストール

Ivyを使うためには、Antのインストールが必要です。
Apache Ant - Binary Distributions
からBinary版をダウンロードし、適切なディレクトリーに展開します。環境変数ANT_HOMEを展開先ディレクトリに設定します。

3.Ivyのインストール

Download | Apache Ivy ™
からBinary版をダウンロードし、適切なディレクトリーに展開します。次に、展開したディレクトリーの直下に入っているivy-バージョン番号.jarをANT_HOME配下のlibサブディレクトリーにコピーします。

4.HTTPプロキシーの設定(オプショナル)

インターネットに対して直接接続できない環境では、
FAQ | Apache Ivy ™
を参考にしてANT_OPTS環境変数を設定してください。

5.PATH環境変数の修正

PATH環境変数の先頭に%JAVA_HOME%\bin;%ANT_HOME%\binを追加します。(もちろんWindowsの場合。LinuxMacユーザーの方は常識ですよね。)

hello-ivyサンプルを実行してみる

まずはサンプルを実行する

Ivyのインストールができたら、インストールの確認も兼ねて、まずは付属しているhello-ivyサンプルを動作させてみます。
コマンドプロンプトを開き、

(Ivyインストール先)\src\example\hello-ivy

に移動します。そこでantと入力し、

Buildfile: D:\development\tools\Apache\apache-ivy-2.2.0\src\example\hello-ivy\build.xml

resolve:
[ivy:retrieve] :: Ivy 2.2.0 - 20100923230623 :: http://ant.apache.org/ivy/ ::
[ivy:retrieve] :: loading settings :: url = jar:file:/D:/development/tools/Apache/apache-ant-1.8.1/lib/ivy-2.2.0.jar!/org/apache/ivy/core/settings/ivysettings.xml
[ivy:retrieve] :: resolving dependencies :: org.apache#hello-ivy;working@Ruby
[ivy:retrieve] 	confs: [default]
[ivy:retrieve] 	found commons-lang#commons-lang;2.0 in public
[ivy:retrieve] 	found commons-cli#commons-cli;1.0 in public
[ivy:retrieve] 	found commons-logging#commons-logging;1.0 in public
[ivy:retrieve] :: resolution report :: resolve 190ms :: artifacts dl 10ms
[ivy:retrieve] 	:: evicted modules:
[ivy:retrieve] 	commons-lang#commons-lang;1.0 by [commons-lang#commons-lang;2.0] in [default]
	---------------------------------------------------------------------
	|                  |            modules            ||   artifacts   |
	|       conf       | number| search|dwnlded|evicted|| number|dwnlded|
	---------------------------------------------------------------------
	|      default     |   4   |   0   |   0   |   1   ||   7   |   0   |
	---------------------------------------------------------------------
[ivy:retrieve] :: retrieving :: org.apache#hello-ivy
[ivy:retrieve] 	confs: [default]
[ivy:retrieve] 	0 artifacts copied, 7 already retrieved (0kB/10ms)

run:
    [javac] D:\development\tools\Apache\apache-ivy-2.2.0\src\example\hello-ivy\build.xml:53: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
     [java] standard message : hello ivy !
     [java] capitalized by org.apache.commons.lang.WordUtils : Hello Ivy !

BUILD SUCCESSFUL
Total time: 1 second

のように最後に「BUILD SUCCESSFUL」と表示されたら成功です。

何が起こっているか

私も最初そうでしたが、ここまで無事動作したものの、実際に何が起こっているのか、サンプルの動作を理解する次のステップが結構大変なところですね。このサンプルのソースコードは、src\example\Hello.javaのただ1つのクラスだけですが、ソースを開いてみるとわかるように、このクラスはcommons-langとcommons-cliという2つのライブラリーに依存しています。したがって、このクラスを正しくコンパイルして実行するためには、これらのライブラリーのjarファイルが必要となります。
ここで依存性管理を使わずに、Antを使って普通にビルドを行う場合、通常はlibディレクトリーなどjarファイルの格納場所を用意しておき、Antのビルド時のクラスパスにそのjarファイルの場所を追加することになりますが、この考え方はIvyを使う場合もあまり変わりません。ただし、依存関係をビルドスクリプトに直接記述する代わりに、ivy.xmlに以下のように記述します。

<ivy-module version="2.0">
    <info organisation="org.apache" module="hello-ivy"/>
    <dependencies>
        <dependency org="commons-lang" name="commons-lang" rev="2.0"/>
        <dependency org="commons-cli" name="commons-cli" rev="1.0"/>
    </dependencies>
</ivy-module>

基本的には直感的に理解できる内容で、特に難しいことはありません。(Mavenのpomファイルに比べて簡潔でかなりスッキリとしています。)このファイルでは、まずタグでこのモジュールのorganisation*1がorg.apacheで、module名がhello-ivyであることが宣言されています。organisationはMavenで言うところのgroup、moduleはartifactにそれぞれ対応する概念です。次に、タグの中身でこのモジュールが依存するライブラリーが宣言されています。タグのorg(organisation)属性はMavenのpomファイルの要素、name属性は要素、rev属性は要素にそれぞれ対応していると考えてください。
このように、モジュールの依存関係を宣言しておけば、後はAntビルドファイルの任意の場所でタグを記述することで、任意のディレクトリー配下に自動的に依存する(推移的な関連も含めて)jarファイルをダウンロードすることができます。実際、このサンプルプログラムのbuild.xmlファイルでは、

    <target name="resolve" description="--> retreive dependencies with ivy">
        <ivy:retrieve/>
    </target>    
    
 ...

    <target name="run" depends="resolve" description="--> compile and run the project">
        <mkdir dir="${build.dir}" />
        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" />
    	<property name="msg" value="hello ivy !"/>
        <java classpathref="run.path.id" classname="example.Hello">
        	<arg value="-message"/>
        	<arg value="${msg}"/>
    	</java>
    </target>

のように記述されているため、runタスクが実行される前に、resolveタスクが実行され、この中でが実行されています。デフォルトではlibサブディレクトリーにライブラリーが取得されますが、

    <property name="lib.dir" value="lib" />
    <property name="build.dir" value="build" />
    <property name="src.dir" value="src" />

    <path id="lib.path.id">
        <fileset dir="${lib.dir}" />
	</path>
    <path id="run.path.id">
        <path refid="lib.path.id" />
        <path location="${build.dir}" />
    </path>

の部分でクラスパスが設定されているため、コンパイルや実行が正しく行われます。
結局、ポイントはivy.xmlでモジュールの定義と依存関係の定義を行い、ライブラリーをで取得するというだけです。Ivyが良いのは第一にこの気軽さであり、これなら既存のAntビルドにも自由に組み込むことが可能なのがお分かりいただけると思います。

Ivyの基本概念について

レポジトリーとキャッシュ

このように、通常はを用いてローカルのディレクトリーにjarファイルをダウンロードしてくることが普通ですが*2、最終的にはMavenと同様に、jarファイルをレビジョンごとに管理したレポジトリーから取得するようになっています。Ivyのレポジトリーを読み込むモジュールはレゾルバーと呼ばれていますが、これがちょうどStrategyパターンになっていて、さまざまな実装をプラグインできるようになっています。ファイルシステム上で管理する実装や、HTTPサーバー上で管理する実装などさまざまなものがあり、また、ディレクトリー構造も細かくカスタマイズができるようになっています。*3
resolvers | Apache Ivy™ Documentation
そして、実際にはデフォルト設定でMavenのセントラルレポジトリーを読み込むレゾルバーが登録されています。(Maven形式のレポジトリーを読み込めるStrategy実装が利用されている)さらに、ここが少し分かりにくいところですが、レポジトリーを何段にも自由に階層化することが可能になっています。よくあるパターンは

  • ローカルPC上のレポジトリー
  • 社内のレポジトリー
  • 社外の公開レポジトリー

のように段階的にレポジトリーを構成しておき、順番に解決させるといった設定が可能です。
なお、Ivyではリポジトリーとは別にキャッシュという概念が存在します。デフォルトではホームフォルダー配下の.ivy2ディレクトリー配下にキャッシュが作成されます。hello-ivyを実行した段階では、以下のようなキャッシュディレクトリーが生成されているはずです。

Mavenの場合は共有されるレポジトリーとローカルのレポジトリーが存在し、ローカルのレポジトリーがキャッシュの役割を兼ねていますが、Ivyの場合キャッシュ専門のディレクトリーが存在する点に注意してください。

ivysettings.xmlを使ったIvyの基本設定のカスタマイズ

デフォルトではIvyのjarファイルの中で定義されている設定ファイルが利用されますが、通常ivysettings.xmlと呼ばれる名前のファイルを作成することで、Ivyの動作をカスタマイズすることができます。たとえば、レポジトリーの階層構造やキャッシュの場所などを設定できます。ivysettings.xmlのリファレンスは以下にあります。
Settings Files | Apache Ivy™ Documentation
ivysettings.xmlを使った設定の簡単な例を試すには、exampleの中で、とりあえずchained-resolversを動かしてみるのが良いと思います。

src\example\chained-resolvers\chainedresolvers-project

サブディレクトリーにcdして、antコマンドを起動してください。hello-ivyの場合と違って、build.xmlファイルの中で以下のようにタグが記述されています。

    <ivy:settings file="${ivy.settings.dir}/ivysettings.xml" />

これによって..\settings\ivysettings.xmlファイルが初期化時に読み込まれます。
そして、以下はivysettings.xmlの中身です。

<ivysettings>
  <settings defaultResolver="chain-example"/>
  <resolvers>
    <chain name="chain-example">
      <filesystem name="libraries">
        <artifact pattern="${ivy.settings.dir}/repository/[artifact]-[revision].[ext]" />
      </filesystem>
      <ibiblio name="ibiblio" m2compatible="true" />
    </chain>
  </resolvers>
</ivysettings>

ここでは、タグの中身で

  • ローカルのファイルシステム上のレポジトリーを読み込むためのfilesystemレゾルバーをlibrariesという名前で登録
  • Mavenのセントラルレポジトリーを読み込むibiblioレゾルバーをibiblioという名前で登録
  • これらを順に解決するためのchainレゾルバーをchain-exampleという名前で登録

というようにレポジトリーに対するレゾルバーが設定されています。そして、タグでchain-exampleをデフォルトレゾルバーとして登録しています。以下のように、ビルド時のログを見ると、test.jarがローカルファイルシステム上のlibrariesから、commons-langがセントラルレポジトリーから読み込まれたことが分かります。(ただし、2回目以降のビルドではキャッシュが利くため、セントラルレポジトリーからのダウンロードは行われません。)

[ivy:retrieve]  found commons-lang#commons-lang;2.0 in ibiblio
[ivy:retrieve]  found org.apache#test;1.0 in libraries
Ivyの依存管理の中心的機能であるコンフィグレーション

前節のsettingsという概念と非常に混同しがち*4なので注意が必要なのですが、Ivyにはコンフィグレーション(configuration)と呼ばれる別の概念があり、Ivyの柔軟な依存管理を行う上での中心的な機能となっています。これは、Mavenで言うところのスコープ(scope)の概念に近いものですが、遥かに柔軟です。Mavenの場合、最初からtest、runtime、compile、providedなどの依存性のスコープが決められています。たとえば、testスコープは単体試験の時に利用されるライブラリーで、compileは本体のソースコードのコンパイルに利用するライブラリーといった具合です。また、これらのスコープにより推移的依存関係の際の動作も決まります。
一方、Ivyのコンフィグレーションは各モジュールのivy.xmlファイルでタグを記述することにより、任意の項目を追加できるようになっています。通常のruntimeやtestといったスコープに加えてweblogic、jbossなど特定のAPサーバー固有の依存関係を定義したり、integration-testのように結合試験の時のみ必要な依存関係を定義したりできます。この概念は最初はちょっと分かりにくいですが、まずはサンプルを動かしてみてください。
example\configurations\jdbc-example\ivy.xmlは以下のように記述されています。

<ivy-module version="1.0">
    <info organisation="org.apache" module="configurations" >
    	<description>
    		This is an example project that aims to demonstrate the usage of the configuration in ivy.
    		This project provide 4 configurations. Each configurations describe the requirement to build or run the project
    	</description>
    </info>
    <configurations>
    	<conf name="compile" description="This is this configuration that describes modules need to build our project"/>
    	<conf name="test" extends="compile" description="This is this configuration that describes modules need to run test on our project"/>
    	<conf name="rundev" extends="compile" description="This is this configuration that describes modules need to execute our project in a dev environement"/>
    	<conf name="runprod"  extends="compile" description="This is this configuration that describes modules need to execute our project in a production environement"/>    	
    </configurations>
    
    <dependencies>
	<!-- this dependency is needed for all configuration -->
        <dependency org="commons-cli" name="commons-cli" rev="1.0" />
        <!-- when launching our app in dev mode we use mckoi db and mckoi jdbc client conf="run.dev->embedded, client"-->
        <dependency org="mckoi" name="mckoi" rev="1.0.2"  conf="rundev->default"/> 
        <!-- when launching our app in production environement we needs other jdbc driver -->
        <dependency org="mm-mysql" name="mm-mysql" rev="2.0.7" conf="runprod->default"/> 
        <!-- junit is only need in the test configuration-->        
        <dependency org="junit" name="junit" rev="3.8" conf="test->default"/> 
    </dependencies>
</ivy-module>

今までのサンプルと違い、が記述されており、この中で複数のタグによって複数のコンフィグレーションが定義されています。実際ここではcompile、testという標準的なコンフィグレーションに加えてrundevとrunprodというコンフィグレーションが定義されている点に注目してください。
さらに、タグの中ではconf属性が記述されていることに注意してください。たとえば、

conf="runprod->default"

という記述はそのライブラリーのrunprodコンフィグレーションにおいて、依存相手のdefaultコンフィグレーションに依存することを示します。慣れないと非常に難しく思われるかも知れませんが、要するに依存関係といっても、目的によってさまざまなものがあるわけですから、その依存関係の種類ごとにコンフィグレーションとして定義しておき、おのおの必要な依存関係を自由に定義できるということに過ぎません。
このようにivy.xmlでコンフィグレーションを定義したら、あとはビルドスクリプト中でタグを記述する際に、どのコンフィグレーションを使うかをconf属性で指定するだけで、目的に応じたjarファイルのみを取得してくることができるわけです。(conf属性を指定しない場合conf="*"と見なされ、全コンフィグレーションの依存関係が取得されます。)
このコンフィグレーション別のretrieve機能を使えば、対象APサーバーごとに別々のコンフィグレーションを定義しておき、warファイルに可能するjarファイルを区別するなどといったことが簡単に行えるようになります。
なお、ivy:retrieveのpattern属性で以下のような書き方をすることで、jarファイルの取得先をコンフィグレーション名ごとのサブディレクトリーに分けることも可能です。

<ivy:retrieve pattern="${ivy.lib.dir}/[conf]/[artifact]-[revision].[ext]"/>
ビルド成果物のパブリッシュ

通常、ビルドした成果物(jarファイルなど)は最終的にパブリッシュという操作を行って、レポジトリーに対してメタ情報(ivy.xml)と共に公開します。パブリッシュを行う対象のモジュールではivy.xmlにてタグを記述します。(Ivyのプロジェクトでは一つのモジュールで複数のartifactをパブリッシュすることもできます。)

    <publications>
      <artifact name="sizewhere" type="jar" conf="core" />
    </publications>

そして、build.xmlにてタグを記述することで、実際にレポジトリーに対してパブリッシュ操作を実行します。詳しくはマニュアルを参照してください。

マルチプロジェクトのビルド

Ivyのような依存性管理が本当に重要になってくるのは複数のプロジェクトから構成されるような大規模なプロジェクトにおいてです。このようなマルチプロジェクトの一括ビルドを行う際にもIvyは便利です。サンプルとしては、

src\example\multi-project

が参考になると思います。ここではポイントだけ説明すると、このサンプルのルートディレクトリーに格納されているbuild.xmlにて

  <target name="buildlist" depends="load-ivy"> 
    <ivy:buildlist reference="build-path">
      <fileset dir="projects" includes="**/build.xml"/>
    </ivy:buildlist>
  </target>
  
  <target name="publish-all" depends="buildlist" 
  			description="compile, jar and publish all projects in the right order">
    <subant target="publish" buildpathref="build-path" />
  </target>

のように記述されている部分で、タグを使って配下のすべてのbuild.xmlからビルドリストを作成します。ここでIvyが自動的に依存関係から適切な順番でリストを作成してくれるところがポイントですね。(Ivyを使わない場合は手動でリストを記述する必要がある)あとは通常のAntでマルチプロジェクトのビルドを行う場合と同様、 タスクを記述してリストの順番にサブプロジェクトのタスクを自動的に呼び出させます。

まとめ

Ivyの基本的な使い方の要点をまとめると以下の通りとなります。

  • モジュール(プロジェクト)ごとにivy.xmlを記述して、依存関係、コンフィグレーション、パブリッシュ対象成果物を定義する。
  • Ivyのグローバルな設定はivysettings.xmlに定義し、タグで読み込む。
  • コンフィグレーションとは独自の依存関係のスコープを定義したものである。
  • レポジトリーとはjarファイルなどのライブラリーをバージョンごとに管理したものである。
  • レポジトリーから読み込むためのレゾルバーの階層を自由に定義することができる。
  • レポジトリーから読み込んだデータは最終的にローカルのファイルシステム上にキャッシュされる。
  • ビルドスクリプトの任意の場所でタグを記述することでjarファイルなどをレポジトリーから取得することができる。
  • ビルドした成果物(アーティファクト)はを実行することでレポジトリーにメタ情報と共にパブリッシュ(公開)される。

ここで紹介した内容は本当に基礎の部分のみです。詳しくは以下のリファレンスを参照してください。
Reference | Apache Ivy™ Documentation
なお、IvyDEというEclipseのプラグインもあります。
Home | Apache IvyDE™
以前のバージョンは結構不具合が多かったのですが、最近は比較的安定して使えるツールになってきているようです。

*1:新しいバージョンではorganizationでも可。Ivyの開発者はフランス人らしいので、もともとはイギリス英語になっている。

*2:タグを利用するとダウンロードなしでキャッシュ上のjarファイルをクラスパスに追加することもできる。

*3:基本的にIvyの思想はこのStrategyパターンによるさまざまな実装方式のプラグインにあるため、この点を理解することが非常に大切です。Strategy大好きのSpringがIvyを利用しているのもちょっと納得できることです。

*4:共に翻訳すると構成とか設定という意味になる。