Apache Ivyを触ってみました。
入門編としては以下のBlogが大変参考になりました:
Apache Ivyは依存関係のjarを取得する点に専念しており、その分Mavenのような複雑さはありません。Antが使えるのであればすんなりと入っていけると思います。Antならではの細かいカスタマイズに応えてくれます。その点で、Mavenを適用できないレガシーなプロジェクトであったり、依存関係の処理にMavenでは対応しきれない特殊な部分が出てくるようなプロジェクトには最適です。
今回試したのは、「MavenのローカルリポジトリをIvyから参照する」です。
実は当初勘違いしていて、Ivyって依存関係を".ivy"とかにDLして、そこに全部jarを保存しておいてくれるものかと思ってました。Mavenの".m2/repository/"みたいな感じで、classpathは適当にAntのbuild.xml中で自動解決してくれる~みたいな。
しかしこれは完全な勘違いで、Ivyのメインはあくまでも依存関係を「ダウンロード」することにあります。classpathの調整はやってくれないんですね。じゃあどう使うのかというと、Antプロジェクトの中に"lib"ディレクトリを作っておいて、"<ivy:retrieve/>"を実行することでその中にjarをダウンロードしてくれます。
ですが、複数のいろいろなプロジェクトで"<ivy:retrieve/>"するたびに毎回リポジトリからDLするのは通信の無駄が発生します。
というわけで、Ivyは一旦"$HOME/.ivy2/cache/"ディレクトリ以下にjarなどのDLファイルをキャッシュします。
"<ivy:retrieve/>"でプロジェクトローカルの"lib/"には、まずこのキャッシュを見に行って、無ければMavenなどのリポジトリに取りに行くようです。
逆に言えば、一旦、ひと通りのプロジェクトで"<ivy:retrieve/>"してしまえばキャッシュは不要です。ということで、ディスクスペースの節約のためにキャッシュをクリアしましょう・・・ということで"<ivy:cleancache/>"というタスクが提供されています。これを使うと "$HOME/.ivy2/cache/" 以下が空っぽになります。
ここも勘違いしてたところがありまして、当初 "$HOME/.ivy2/cache/" って永続的なものかと思ってました。なので、「う~ん、Mavenも使ってたら "$HOME/.m2/repository/" と "$HOME/.ivy2/cache/" で重複してるので無駄だよな~、".ivy2/cache/" を ".m2/repository/" で使い回しできないかな?」と思ったんです。
ですが上記の通り ".ivy2/cache/" はあくまでもキャッシュであるため、いつかは "<ivy:cleancache/>" でクリアされてしまうんです。
一応技術的には ".m2/repository/" を ".ivy2/cache/" に使い回しも出来るようなんですが・・・しかしそんなことをしてしまうと、うっかり "<ivy:cleancache/>" するとそれまでせっかく蓄積されたMavenのローカルリポジトリがゼロクリアされてしまうんですよね。いやまぁ、また自動でDLされるんだからいいじゃん、と言えばそうなんですが。
じゃぁどこでIvyとMavenのローカルリポジトリは連携するのか?ですが、単純にIvyが依存関係を探すリポジトリとして ".m2/repository/" を追加する、という連携方法になります。
あり得るパターンとしてはMavenで構築した自作ライブラリを"mvn install"で ".m2/repository/" 以下にインストールして、それをIvyで取りに行く、みたいな感じでしょうか。
ぶっちゃけ "<ivy:cleancache/>" で ".ivy2/cache/" 以下をクリアしたとしても、プロジェクトローカルには ".jar" ファイルが置かれてしまうので、Mavenも使ってたらどっちにしてもjarファイルは重複して存在してしまいます。
しかしそもそもIvyを使う目的は「Mavenでは歯が立たない様な依存関係の解決」にあり、どうしてもそうなるとプロジェクトローカルに".jar"を置く、という流れになってしまうので、これはもう逃れられない訳です。それが嫌ならMavenにせーよ、という。
ちなみにプロジェクトローカルにjarをDLするというIvyの流れ上、依存対象のjarがアップデートされても自動的には同期してくれません。バージョン番号変えるとか、一旦 "<ivy:cleancache/>" するとか、他にも更新チェック用の細かい設定があったりするようです。そのへんは今回は試してません。
参考:
というわけで試してみました。
環境:
Win7-SP1 64bit JDK 1.7.0_07, 64bit Oracle JVM Apache Maven 3.0.4 (r1232337; 2012-01-17 17:44:56+0900) Apache Ant(TM) version 1.8.2 compiled on December 20 2010 Apache Ivy 2.2.0
"mvnjartest"という名前でMavenプロジェクトを作ってみました。
commons-langに依存させます。
mvnjartest/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>mvntest</groupId>
<artifactId>mvnjartest</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>mvnjartest</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
ライブラリの動作としては、文字列をcapitalizeしたものを返します。
src/main/java/mvnjartest/App.java:
package mvnjartest; import org.apache.commons.lang.WordUtils; public class App { String message; public App(String m) { this.message = m; } public String capitalize() { return WordUtils.capitalizeFully(message); } }
(testは省略)
ビルドしてローカルリポジトリにインストールします。
> mvn install
以下のようなディレクトリ構成でインストールされました。
"$HOME/.m2/repository/
mvntest/
mvnjartest/
maven-metadata-local.xml
1.0-SNAPSHOT/
_maven.repositories
maven-metadata-local.xml
mvnjartest-1.0-SNAPSHOT.jar
mvnjartest-1.0-SNAPSHOT.pom
"mvnjartest-1.0-SNAPSHOT.jar"をIvyでDLしてくるサンプルを "resolv-test" というプロジェクト名で作ってみました。
まず依存性を定義する ivy.xml を用意します。
resolv-test/ivy.xml:
<ivy-module version="1.0">
<info organisation="ivytest" module="resolv-test"/>
<dependencies>
<dependency org="commons-lang" name="commons-lang" rev="2.0" conf="default"/>
<dependency org="mvntest" name="mvnjartest" rev="1.0-SNAPSHOT" conf="default"/>
</dependencies>
</ivy-module>
続いて、"$HOME/.m2/repository/" をリポジトリて参照する設定を ivysettigs.xml として用意します。
resolv-test/ivysettings.xml:
<ivysettings>
<property name="local-m2-dir" value="${user.home}/.m2/repository/" />
<settings defaultResolver="chain-to-m2local"/>
<resolvers>
<chain name="chain-to-m2local">
<filesystem name="local-m2-repo" m2compatible="true" local="true">
<artifact pattern="${local-m2-dir}/[organisation]/[module]/[revision]/[module]-[revision].[ext]" />
<ivy pattern="${local-m2-dir}/[organisation]/[module]/[revision]/[module]-[revision].pom" />
</filesystem>
<ibiblio name="ibiblio" m2compatible="true" />
</chain>
</resolvers>
</ivysettings>
Ivyでは依存性の解決方法でStrategyパターンを採用しており、ファイルシステム上から取得する方法が提供されています。解決方法(resolver)の連鎖(chain)に、"$HOME/.m2/repository/" 以下を参照するfilesystem resolverを組み込んでいます。filesystem上になければibiblioから取得するresolverを追加しています。
"ibiblio"というのは、インターネット上にあるリソースをコレクションしたミラーサイトの一種のようです。ibiblioが提供しているミラーの中に、Mavenリポジトリが含まれています。
ibiblioについて:
肝心のbuild.xmlです。build.xml中にjavaソースファイルも作成するように手抜きしてます。
resolv-test/build.xml:
<project name="resolv-test" default="run" xmlns:ivy="antlib:org.apache.ivy.ant">
<property name="src.dir" value="${basedir}/src" />
<ivy:settings file="${basedir}/ivysettings.xml" />
<property name="lib.dir" value="${basedir}/lib" />
<property name="build.dir" value="${basedir}/build" />
<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>
<target name="generate-src" description="generate sample java source">
<mkdir dir="${src.dir}/example" />
<echo file="${src.dir}/example/Hello.java">
package example;
import mvnjartest.App;
public class Hello {
public static void main(String[] args) {
App a = new App("hello world");
System.out.println(a.capitalize());
}
}
</echo>
</target>
<target name="resolve" description="resolve and retrieve dependencies with ivy">
<ivy:retrieve/>
</target>
<target name="run" depends="generate-src,resolve" description="compile and run sample">
<mkdir dir="${build.dir}" />
<javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" />
<java classpathref="run.path.id" classname="example.Hello"/>
</target>
<target name="clean" description="clean src and build directories">
<delete includeemptydirs="true" quiet="true">
<fileset dir="${src.dir}" />
<fileset dir="${build.dir}" />
</delete>
</target>
<target name="clean-cache" description="clean ivy cache">
<ivy:cleancache />
</target>
</project>
antでデフォルトの"run"ターゲットを実行すると・・・
"ant resolve" でIvyの依存性解決だけを単体で動かしてみた時の出力:
> ant resolve
Buildfile: ...\resolv-test\build.xml
resolve:
[ivy:retrieve] :: Ivy 2.2.0 - 20100923230623 :: http://ant.apache.org/ivy/ ::
[ivy:retrieve] :: loading settings :: file = ...\resolv-test\ivysettings.xml
[ivy:retrieve] :: resolving dependencies :: ivytest#resolv-test;working@HOGEHOGE
[ivy:retrieve] confs: [default]
[ivy:retrieve] found commons-lang#commons-lang;2.0 in ibiblio
### ibiblioでcommons-langが見つかりました。
[ivy:retrieve] found mvntest#mvnjartest;1.0-SNAPSHOT in local-m2-repo
[ivy:retrieve] found commons-lang#commons-lang;2.1 in local-m2-repo
### "$HOME/.m2/repository/" 上からmvnjartestとcommons-langが見つかりました。
### commons-langについてはmvnjartestのビルド時点でDLしています。
[ivy:retrieve] downloading C:\Users\xxxx\.m2\repository\mvntest\mvnjartest\1.0-SNAPSHOT\mvnjartest-1.0-SNAPSHOT.jar...
[ivy:retrieve] .. (1kB)
[ivy:retrieve] [SUCCESSFUL ] mvntest#mvnjartest;1.0-SNAPSHOT!mvnjartest.jar (18ms)
[ivy:retrieve] downloading C:\Users\xxxx\.m2\repository\commons-lang\commons-lang\2.1\commons-lang-2.1.jar ...
[ivy:retrieve] ..... (202kB)
[ivy:retrieve] .. (0kB)
[ivy:retrieve] [SUCCESSFUL ] commons-lang#commons-lang;2.1!commons-lang.jar (103ms)
### 両方のjarファイルとも、ローカルからプロジェクトの"lib/"以下にコピーされました。
[ivy:retrieve] :: resolution report :: resolve 853ms :: artifacts dl 127ms
[ivy:retrieve] :: evicted modules:
[ivy:retrieve] commons-lang#commons-lang;2.0 by [commons-lang#commons-lang;2.1] in [default]
---------------------------------------------------------------------
| | modules || artifacts |
| conf | number| search|dwnlded|evicted|| number|dwnlded|
---------------------------------------------------------------------
| default | 3 | 3 | 3 | 1 || 2 | 2 |
---------------------------------------------------------------------
[ivy:retrieve] :: retrieving :: ivytest#resolv-test
[ivy:retrieve] confs: [default]
[ivy:retrieve] 2 artifacts copied, 0 already retrieved (204kB/21ms)
BUILD SUCCESSFUL
Total time: 1 second
この時点で、mvnjartestとcommons-langのjarファイルは3箇所に重複して存在することになります。
"$HOME/.ivy2/cache/"以下は、 "<ivy:cleancache/>"をトリガーする以下のターゲットで空っぽにします。
> ant clean-cache
これでjarファイルの重複は2箇所、Ivyの使用目的としては必要最低限になります。
もしもMavenを全く使用せず、開発マシン上にはAntとIvyのみがセットアップされているのであれば、"$HOME/.m2/repository/" も存在しないためjarファイルの重複は存在しなくなります。
・・・というところまで理解してれば、GroovyのGrapeで"$HOME/.m2/repository/"との連携方法も整理できるかな・・・。
ivysettings.xml:
<ivysettings>
<settings defaultResolver="chain-to-m2local"/>
<resolvers>
<chain name="chain-to-m2local">
<ibiblio name="local" root="file:${user.home}/.m2/repository/" m2compatible="true"/>
<ibiblio name="ibiblio" m2compatible="true" />
</chain>
</resolvers>
</ivysettings>
もともと"m2compatible"が有効なので、rootで指定したリソース以下をそのままMaven2リポジトリとして参照できるようです。なので、あとはrootで"file:"以下をローカルファイルシステムを指定するだけで ibiblio resolverでも解決できてしまうようです。
参考:
何気に次のエントリのネタにしようとしてた内容・・・。