はじめに †今回は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がどうやってビルドファイルを読んでターゲットを実行しているのかを読みました。読んだ感想としては、
といったところです。それではみなさんもよいコードリーディングを。 |