はじめに

今回はantを読みます。カレントディレクトリにbuild.xmlがあって、

ant

とした場合にどのような処理が行われているのか見てみましょう。build.xmlはこちらを使います。

なお、今回対象としたバージョンは1.7.0です。

org.apache.tools.launch.Launcher

antコマンドを実行した場合に起動されるのはorg.apache.tools.launch.Launcherのmainメソッドです。org.apache.tools.ant.Mainにもmainメソッドがあるのでだまされないように注意してください私はだまされました:-<

mainメソッドではLauncherオブジェクトを生成してrunメソッドを呼び出しています。runメソッドではライブラリパスの設定を行っています。詳しく解説はしませんがjarがたくさんあるときにエントリポイントとなるjarだけスクリプトで指定してライブラリパスを設定してメイン処理に移るというのはJavaのイディオムなようです。jarが100個とかあると環境変数領域が足りなくなりますからね。

というわけでメイン処理のorg.apache.tools.ant.Main.startAntメソッドに移りましょう。

org.apache.tools.ant.Main

startAntメソッドではまずprocessArgsメソッドを呼んでいます。この中でbuild.xmlがビルドファイルとして設定されています。その後、runBuildメソッドに移っています。

runBuildメソッドではProjectオブジェクトを構築し、初期化(Project.initメソッド)、ビルドファイルの読み込み(ProjectHelper.configureProjectメソッド)、ターゲットの実行(Project.executeTargetsメソッド)を行っています。それぞれ見ていきましょう。

org.apache.tools.ant.Project.init()

まず、プロパティを設定しています。Javaのバージョン番号設定を見てみるとヒューリスティック過ぎでおもしろいです。

次に、ComponentHelper.initDefaultDefinitionsメソッドを呼び出してタスクと型を設定しています。これにより、普段使うjavacなどのタスクが登録されるようです。また、initTasksメソッドとinitTypesメソッドを眺めてみるとタスクも型も型として登録されているようです。

org.apache.tools.ant.ProjectHelper.configureProject()

え〜っと、まずはProjectHelperの実装としていろいろなところからロードを試みてますがデフォルトが使われるはずなのでparseメソッドに進みましょう。org.apche.tools.ant.helper.ProjectHelper2です。

parseメソッドではまずAntXMLContextを生成しています。どうやらこいつを使ってビルドファイルを解析するようです。他のビルドファイルをインポートはしてないので、elseの方が実行されます。

// top level file
context.setCurrentTargets(new HashMap());
parse(project, source, new RootHandler(context, mainHandler));
// Execute the top-level target
context.getImplicitTarget().execute();

implicitTargetというのはtargetタグの外で設定されているもの、プロパティの設定などを実行するためのターゲットのようです。ふ〜んってことで3引数parseメソッドに進みましょう。コメントに書いてあるようにSAXを使ってビルドファイルの解析を行っているようです。

ProjectHandler2.RootHandler

SAXのイベントハンドラとしてProjectHandler2クラスの内部クラスのRootHandlerが渡されています。startElementメソッドはこんな感じ。

public void startElement(String uri, String tag, String qname, Attributes attrs)
    throws SAXParseException {
    AntHandler next
        = currentHandler.onStartChild(uri, tag, qname, attrs, context);
    antHandlers.push(currentHandler);
    currentHandler = next;
    currentHandler.onStartElement(uri, tag, qname, attrs, context);
}

現在のハンドラに子要素が始まったことを教えて子要素をハンドリングするハンドラを取得しています。初めのcurrentHandlerはMainHandlerなのでMainHandlerのonStartChildメソッドを見てみましょう。

public AntHandler onStartChild(String uri, String name, String qname,
                               Attributes attrs,
                               AntXMLContext context)
    throws SAXParseException {
    if (name.equals("project")
        && (uri.equals("") || uri.equals(ANT_CORE_URI))) {
        return ProjectHelper2.projectHandler;
    } else {
        // 例外送信
    }
}

というわけでProjectHandlerがcurrentHandlerになっています。ProjectHandlerのonStartElementメソッドでは淡々と設定を行っています。メソッドの最後だけ眺めると以下のようになっておりtargetタグの外側で設定されているものがimplicitTargetに設定されることがわかります。

context.setCurrentTarget(context.getImplicitTarget());

次にProjectHandlerのonStartChildメソッドです。

if (name.equals("target")
    && (uri.equals("") || uri.equals(ANT_CORE_URI))) {
    return ProjectHelper2.targetHandler;
} else {
    return ProjectHelper2.elementHandler;
}

今度はタグによってハンドラが変わるようです。TargetHandlerを見ると想像通りにTargetオブジェクトを作成して設定しています。

ProjectHandler2.ElementHandler

さてElementHandlerです。Main, Project, Targetと来てその下は全てElementとしてひっくるめられているようです。

target直下かどうかによって少し処理が変わるようですが全てのタグはコメントに書いてあるようにUnknownElementとして扱われ遅延評価されるようです。その後、RuntimeConfigurableオブジェクトでくるまれています。クラス名的にもwrapperという変数名的にも遅延評価が終わったら実際のオブジェクトに切り替わりそうですね。その後、wrapperのスタックに積まれて次に進むなようです。

以上でビルドファイルの読み込みは終わりです。

org.apache.tools.ant.Project.executeTargets()

ではターゲットの実行部です。一度Executorオブジェクト(実装はDefaultExecutor)を経由してexecuteTargetメソッドに処理が移っています。executeTargetメソッドでは実行するターゲットの依存関係を列挙して順に実行しています。依存関係の列挙に使用しているトポロジカルソートは・・・、まあ素直なアルゴリズムなのでわかるでしょう。

TargetクラスのperformTasksメソッドはexecuteメソッドを呼んでるだけ(イベント送ったりはしてますが)なのでexecuteメソッドに移ります。ifおよびunlessのチェックをしてますが使ってないので無視します。登録されているのはTaskクラスを継承しているUnknownElementオブジェクトなのでTaskクラスのperformメソッドに移ります。

Taskクラスのperformメソッドのコア部分です。

maybeConfigure();
DispatchUtils.execute(this);

UnknownElement

maybeConfigureメソッドですが、UnknownElementクラスでオーバーライドされているのでそちらを見る必要があります。

public void maybeConfigure() throws BuildException {
    if (realThing != null) {
        return;
    }
    configure(makeObject(this, getWrapper()));
}

makeObjectメソッドのコア部分は以下です。

ComponentHelper helper = ComponentHelper.getComponentHelper(
    getProject());
String name = ue.getComponentName();
Object o = helper.createComponent(ue, ue.getNamespace(), name);

3引数createComponentは1引数createComponentを呼び出した後、Taskオブジェクトならいろいろ設定してinitメソッドを呼び出しています。

1引数createComponentではgetDefinitionメソッドを呼び出してAntTypeDefinitionオブジェクトを取得しています。getDefinitionメソッドの中で呼ばれているcheckNamespaceメソッドは指定されたパスからantlib.xmlを読み込んでタスクや型を定義するようですがantlib.xmlはないので無視しましょう。その後、AntTypeDefinitionオブジェクトのcreateメソッドが呼ばれることで遅延されていたオブジェクトの生成が行われます。

次にconfigureメソッドで生成したオブジェクトを設定します。今度はTaskクラスのmaybeConfigureメソッドが呼ばれます。追いかけていくと実際の処理はRuntimeConfigurableクラスのmaybeConfigureメソッドで処理が行われています。RuntimeConfigurableクラスのmaybeConfigureメソッドではIntrospectionHelperクラスを用いてオブジェクトに値を設定しています。その後、子要素についても再帰的に処理を行うことにより完全なTaskオブジェクトを構築しています。

DispatchUtils

Taskオブジェクトの構築が終了したらDispatchUtils.executeメソッドを呼び出してタスクを実行しています。Dispatchableかどうかによって処理が変わるようですがexecuteメソッドが呼ばれると思って問題はないでしょう。

おわりに

今回はantがどうやってビルドファイルを読んでターゲットを実行しているのかを読みました。読んだ感想としては、

  • ビルトインタスクもtaskdefで定義できるタスクと同様に扱われている。これは予想通りでした
  • タスクオブジェクトの生成は実際に実行されるまで遅延される。これは予想外でした
  • メソッド呼び出しがかなり深い

といったところです。それではみなさんもよいコードリーディングを。


トップ   編集 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2007-10-03 (水) 03:01:02 (6043d)