S-JIS[2007-08-16/2015-12-28] 変更履歴

Ant独自タスクの作り方

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クラスパスプロパティーの展開

build.xml:

	<target name="〜">
		<originaltask attr="value" />
	</target>

OriginalTask.java:

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で候補から選択することが出来るようになる)

SampleTask.java:

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!");
		}
	}
}

build.xml:

	<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;
	}
}

ParameterDataTypeクラスパス継承関係のある子要素


独自の子要素クラス(データタイプ)(上記の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()を呼び出せばよい。
内部テキスト

TextTask.java:

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));
	}
}

build.xml:

	<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]

build.xml:

		<text></text>	→呼ばれない
		<text />		→呼ばれない
		<text> </text>	→スペース1個のデータが入ってくる
		<text>
</text>				→改行のみのデータが入ってくる

ボディー部にテキスト以外のネストした要素があった場合、その要素もセットされ、テキストもセットされる。[2009-01-27]
ただしテキストは結合され、1つの文字列として一度だけaddText()が呼ばれる。

build.xml:

		<text>abc<other>zzz</other>def</text>

TextTask.java:

public class TextTask extends Task {

	protected String text;

	public void addText(String s) {
		text = s;
	}

	public void addOther(Other o) {
		〜
	}
〜
}

↑この例では、addText()には「abcdef」が渡ってくる。


ログ出力

TaskDataTypeを継承していると、ログ出力機能が使える。(実際は、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でも全部出る。


Ant目次へ戻る / 技術メモへ戻る / ひしだま自作タスクへ行く
メールの送信先:ひしだま