Antの独自タスクの作り方。
|
|
|
独自に作ったタスクをbuild.xmlで使うには、antにそのクラスの場所を教えてやる必要がある。
taskdefを使えば、build.xml内で指定できる。(typedefでも可)
build.xml:
<taskdef name="original" …build.xml内で使う名称 classname="jp.hishidama.ant.taskdefs.OriginalTask" …クラス名 classpath="../classes" …クラスのある場所 /> <target name="〜"> <original> </original> </target>
特殊なデータタイプ(自作のデータタイプ)はtypedefを使って指定する。
build.xml:
<typedef name="filesmatch.text" …build.xml内で使う名称 classname="jp.hishidama.ant.taskdefs.condition.TextFilesMatch" …クラス名 classpath="../classes" …クラスのある場所 /> <target name="〜"> <compsync> <filesmatch.text /> </compsync> </target>
→この例の場合、compsyncもfilesmatch.textも独自タスクなので、本当はさらにloaderrefの指定が必要。
独自クラスはantのクラス(ライブラリー)を使用するので、コンパイルにはant.jarが必要になる。[2007-08-26]
インストールしたAntのlibディレクトリー(ANT_HOME/lib)に入っている。
Eclipseを使って開発するなら「Eclipseプラグインのディレクトリ」に入っているので、それをビルドパスに加えておけばよい。
(例: C:\eclipse\plugins\org.apache.ant_1.6.5\lib\ant.jar を外部JARに追加する)
Taskクラス(あるいは他の基本クラス)を継承して独自タスクをコーディングする。
OriginalTask.java:
import org.apache.tools.ant.Task;
public class OriginalTask extends Task {
}
コンストラクターは書かなくてもいい。[2009-01-22]
(デフォルトコンストラクター、すなわちpublicで引数なしのコンストラクターが暗黙に定義されている)
または、Projectを引数に持つpublicコンストラクターを用意してもいい。
public OriginalTask(Proejct p) {
setProject(p);
}
※デフォルトコンストラクターの場合は、インスタンスの生成が行われた後にsetProject()が呼ばれているのだろう。
タスクが実行されるときには、execute()メソッドが呼ばれる(つまりTask#execute()をオーバーライドしておく)。
必須の属性がある場合には、execute()メソッドの先頭でそのチェックを行う。
OriginalTask.java:
public class OriginalTask extends Task { 〜 public void execute() throws BuildException { //override validateAttributes(); //ここにタスクの動作を記述する。 〜 } /** * 属性値の精査 * @throws BuildException 精査エラー時 */ protected void validateAttributes() throws BuildException { if (this.attr == null) { throw new BuildException("attr must be set."); //attrがセットされる必要がある } if (this.param == null) { throw new BuildException("Specify a param."); //paramを1つ指定する必要がある } } }
初期化の為にinit()メソッドがある。[2010-01-23]
これは、タスクのインスタンス化が行われた後、属性のセッターメソッドが呼ばれる前に呼ばれる。
(PropertyHelperの初期化なんかに都合がいいかも。)
init()のデフォルトは空なので、super.init()を呼ぶ必要は無い。
public void init() throws BuildException { //override
//ここにタスクの初期処理を記述する。
〜
}
セッターメソッドを定義しておくと、build.xmlに書いたときに属性として扱われる。
(属性名の先頭1文字を大文字にして他を全部小文字にして、setを付けたメソッド)
実行時にはantによって値がセットされる。セッターメソッドの引数の型(booleanやFile)に応じて値を自動的に変換してくれるので、とっても便利。
→Antが行う属性変換
(→EnumeratedAttribute・クラスパス・プロパティーの展開)
<target name="〜">
<originaltask attr="value" />
</target>
public class OriginalTask extends Task {
private String attr;
public void setAttr(String str) {
this.attr = str;
}
}
いくつかの値の候補から1つをセットする属性の場合、EnumeratedAttributeクラスが便利。[2008-12-25]
(Eclipseでbuild.xmlを編集する際に、Ctrl+Spaceで候補から選択することが出来るようになる)
package jp.hishidama.sample.ant; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.EnumeratedAttribute; public class SampleTask extends Task { // EnumeratedAttributeを継承したpublicクラス public static class SelectAttr extends EnumeratedAttribute { @Override public String[] getValues() { return new String[] { "foo", "bar", "zzz" }; //属性値の一覧を返す } } protected SelectAttr select; public void setSelect(SelectAttr s) { select = s; } @Override public void execute() throws BuildException { validate(); String s = select.getValue(); log("value=" + s); int n = select.getIndex(); //getValues()の配列内の順番。0が先頭。 log("index=" + n); } protected void validate() throws BuildException { if (select == null) { throw new BuildException("select attribute must be set!"); } } }
<target name="〜"> <taskdef name="sample" classname="jp.hishidama.sample.ant.SampleTask" classpath="../classes" /> <sample select="bar" /> </target>
※指定する属性値は大文字小文字が区別される(完全に一致していなければならない)
独自タスクのボディー部に他のタスク(子要素)を記述する場合、addメソッドを定義しておく。[/2007-08-26]
(タスク名の先頭1文字を大文字にして他を全部小文字にして、addを付けたメソッド。あるいは、addConfiguredを付けたメソッド。あるいはcreateを付けたメソッド。
プログラマーの必要性に応じて、子要素毎にどれか1つを実装しておく)
“build.xml上の子要素の名前”のaddメソッドがあれば、実行時にそのaddメソッドの引数のクラスをインスタンス化してaddメソッドが呼ばれる。
ただし、addメソッドが呼ばれた時点では、そのインスタンスの各属性の値はまだセットされていない。Task#execute()が呼ばれる時にはセットされている。
addConfiguredメソッドを用意した場合は、そのメソッドが呼ばれた時点で各属性の値がセットされている。
createメソッドを用意した場合は、インスタンス生成の為にそのメソッドが呼ばれる。
当然、属性値はセットされていない。Task#execute()が呼ばれる時にはセットされている。
createは、インスタンス生成の為のコンストラクターに引数が必要な場合に使用する。(addやaddConfiguredでは、引数なしのコンストラクターが必須)
build.xml:
<target name="〜">
<originaltask>
<param name="n" value="v" />
</originaltask>
</target>
OriginalTask.java(addの例):
public class OriginalTask extends Task { private Parameter param; public void addParam(Parameter param) { // 引数paramにはParameterインスタンスが渡されるが // addメソッドが呼ばれた時点ではparamの各属性の値はまだ入っていないので、 // インスタンスを保持しておくに留める。 this.param = param; System.out.println(param.getName()); …nullが表示される executeメソッドが呼ばれる時には、値がセットされている } }
OriginalTask.java(addConfiguredの例):
public class OriginalTask extends Task { private Parameter param; public void addConfiguredParam(Parameter param) { // 引数paramには各属性の値が入ったParameterインスタンスが渡される。 this.param = param; System.out.println(param.getName()); …値が表示される } }
OriginalTask.java(createの例):
public class OriginalTask extends Task { private Parameter param; public Parameter createParam() { // 自分でParameterインスタンスを生成し、それを返す。 this.param = new Parameter(); System.out.println(param.getName()); …当然、nullが表示される executeメソッドが呼ばれる時には、値がセットされている return param; } }
→Parameter、DataType、クラスパス、継承関係のある子要素
独自の子要素クラス(データタイプ)(上記のParameterに当たるもの)を作る場合、そのクラスはJavaBeanであればよい(publicなデフォルトコンストラクターがあって、setterメソッドがあればよい)が、DataTypeクラスを継承しておくことによって、ログ出力等の機能が使えるようになる。
OriginalData.java:
public class OriginalData /*extends DataType*/ {
private String value;
public void setValue(String val) {
this.value = val;
}
public void getValue() {
return value;
}
}
OriginalTask.java:
public class OriginalTask extends Task {
private OriginalData data;
public void addData(OriginalData data) {
this.data = data;
}
}
build.xml:
<target name="〜">
<originaltask>
<data value="データ" />
</originaltask>
</target>
typedef等でOriginalDataを定義する必要は無い。
(たぶんAnt1.6以降)子要素のクラスを継承したクラスを用意しておき、どのクラスでも受け付けられるようにすることが出来る。[2007-08-26]
通常の子要素ではaddやaddConfiguredにタスク名を付けるが、タスク名を付けないでコーディングする。
(createの方法は無い)
そしてbuild.xmlの方でtypedefを使ってそれぞれ固有のタスク名を定義し、実際のクラスを割り当てる。
OriginalData.javaとそれを継承したクラス:
public class OriginalData /*extends DataType*/ {
}
public class Data1 extends OriginalData {
}
public class Data2 extends OriginalData {
}
OriginalTask.java:
public class OriginalTask extends Task {
private OriginalData data;
public void addConfigured(OriginalData data) {
this.data = data;
}
}
build.xml:
<taskdef name="org" classname="jp.hishidama.sample.ant.OriginalTask" classpath="../classes" loaderref="hmant" /> <typedef name="data.1" classname="jp.hishidama.sample.ant.Data1" classpath="../classes" loaderref="hmant" /> <typedef name="data.2" classname="jp.hishidama.sample.ant.Data2" classpath="../classes" loaderref="hmant" /> <target name="〜"> <org> <data.1 value="データ1" /> <data.2 value="データ2" /> </org> </target>
重要なのが、loaderref!
一連のタスク間では これが同じでないと関連するタスクとして認識してくれない(別々のクラスローダーでロードされてお互いに無関係として扱われる)ので、子要素として使えない。
↓loaderrefを付けない場合のエラー
BUILD FAILED build.xml:27: The <org> type doesn't support the nested "data.1" element.
loaderrefに付ける名前は…適当で よさげ(爆)
typedefには定義をプロパティーファイルからまとめて読み込む機能もあり、そうして読み込んだものは1つのローダーとして認識されるようなので、その方法を使う場合はloaderrefを指定する必要は無い。
build.xml:
<typedef resource="jp/hishidama/sample/ant/typedef.properties" classpath="../classes" />
classes/jp/hishidama/sample/ant/typedef.properties:
org = jp.hishidama.sample.ant.OriginalTask data.1=jp.hishidama.sample.ant.Data1 data.2=jp.hishidama.sample.ant.Data2
なお、複数のtaskdef・typedefで同一のloaderrefを使用する場合、classpathの指定はひとつだけで良い。[2009-01-13]
(最初の一つだけclasspathを指定してあればよい)
<taskdef name="org" classname="jp.hishidama.sample.ant.OriginalTask" classpath="../classes" loaderref="hmant" /> <typedef name="data.1" classname="jp.hishidama.sample.ant.Data1" loaderref="hmant" /> ←classpath指定は不要 <typedef name="data.2" classname="jp.hishidama.sample.ant.Data2" loaderref="hmant" /> ←classpath指定は不要
自作タスクのボディー部のテキストを取得することも簡単に出来る。[2009-01-26]
public void addText(String)というメソッドを用意すればいいだけ。
ただし、この文字列はプロパティー(${〜})の解釈は行われない。解釈をしたい場合は、Project#replaceProperties()を呼び出せばよい。
→内部テキスト
package jp.hishidama.sample.ant; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; public class TextTask extends Task { protected String text; public void addText(String s) { text = s; } public void execute() throws BuildException { log(text); log(getProject().replaceProperties(text)); } }
<target name="text"> <taskdef name="text" classname="jp.hishidama.sample.ant.TextTask" classpath="classes" /> <text>Antパスは${ant.library.dir}です。</text> </target>
Buildfile: C:\workspace\sample\bin\build.xml text: [text] Antパスは${ant.library.dir}です。 [text] AntパスはC:\eclipse\plugins\org.apache.ant_1.7.0.v200803061910\libです。 BUILD SUCCESSFUL Total time: 203 milliseconds
ボディー部のテキストが空の時は、addText()は呼ばれない。[2009-01-27]
<text></text> →呼ばれない <text /> →呼ばれない
<text> </text> →スペース1個のデータが入ってくる <text> </text> →改行のみのデータが入ってくる
ボディー部にテキスト以外のネストした要素があった場合、その要素もセットされ、テキストもセットされる。[2009-01-27]
ただしテキストは結合され、1つの文字列として一度だけaddText()が呼ばれる。
<text>abc<other>zzz</other>def</text>
public class TextTask extends Task { protected String text; public void addText(String s) { text = s; } public void addOther(Other o) { 〜 } 〜 }
↑この例では、addText()には「abcdef」が渡ってくる。
TaskやDataTypeを継承していると、ログ出力機能が使える。(実際は、Projectクラスのログ出力メソッドが呼ばれる)
public class OriginalTask extends Task {
public void execute() throws BuildException {
log("ログ出力", Project.MSG_VERBOSE);
}
}
各ログには、メッセージレベル(デバッグレベル)が指定できる。省略時はMSG_INFO。
そのレベルが実際に表示されるかは、実行時のantコマンドのオプションで指定されたオプションによる。
メッセージレベル | 概要 | antのオプションによる 出力範囲 |
|||||
---|---|---|---|---|---|---|---|
echoタスク | Projectクラス | ||||||
0 | error | MSG_ERR | エラー | -quiet | 通常 | -verbose | -debug |
1 | warning | MSG_WARN | 警告 | ||||
2 | info | MSG_INFO | 情報 | ||||
3 | verbose | MSG_VERBOSE | 詳細 | ||||
4 | debug | MSG_DEBUG | デバッグ |
デフォルトでは例外発生時のスタックトレースは表示されない。-verboseで表示されるので、VERBOSEレベルで出力しているのだろう。
(自分でタスクを作る場合、落ちまくるので(爆)、-verboseや-debugを指定して実行しろということだ^^;)
メッセージレベル5以上はたぶん仕様の範囲外だが、試してみると、-quietでも出力される。-debugでも全部出る。