Next: 新しいオペレーションを定義する, Previous: オペレーション, Up: オペレーション [Contents][Index]
このセクションで解説するオペレーションはすべてasdfパッケージにあり、総称関数operateで呼び出すことができます。
(asdf:operate 'asdf:オペレーション名 :システム名 {オペレーションのオプション ...})
このオペレーションは、指定されたコンポーネントをコンパイルします。つまり、cl-source-fileはcompile-fileでコンパイルされ、依存する、あるいは子コンポーネントであるシステム、モジュールには再帰的にcompile-opが適用されます。
compile-opはprepare-opに依存していて、prepare-opは依存するすべてのコンポーネントに対するload-opに依存しています。したがって、operateでcompile-opを呼び出すと、それらのコンポーネント群は単にコンパイルされるだけでなくロードもされるでしょう、しかし、compile-opでは、指定されたシステムそのもの(の一部)は、それに依存するシステムがないのでロードされずに残ることになります。システムをロードしたい場合にはload-opを使ってください。
このオペレーションは、指定されたコンポーネントのコンパイル済みコードをロードします。つまり、cl-source-fileについてはそのfaslファイルがロードされ、依存する、あるいは子コンポーネントであるシステム、モジュールには再帰的にload-opが適用されます。なおload-opはcompile-opに依存していて、faslファイルはcompile-opの出力として得られます。
load-opはprepare-opにも依存していて、prepare-opは依存するすべてのコンポーネントに対するload-opに依存しています。
このオペレーションは、指定されたコンポーネントが依存する(再帰的な意味で)すべてのコンポーネントが(load-opで)ロードされることを保証します。compile-opやload-opが実行される前に、必要な前提を満たすためのオペレーションです。
load-source-opはload-opと似ていますが、モジュールに属するcl-source-fileをfaslからではなくソースからロードします。load-source-opは(prepare-opに代えて)prepare-source-opに依存しています。prepare-source-opはprepare-opに類似のオペレーションで、依存関係がload-source-opでロードされることを保証します。
このオペレーションはモジュールのテストを行うためにありますが、デフォルトのメソッドは何もしません。また、デフォルトではモジュールに対して先にload-opが実行されることを要求します。なお、デフォルトのoperation-done-pメソッドは(訳注: たとえ、test-opが正常に実行された後であっても)常にnilを返すので、このオペレーションは決して完了したことにはなりません。(→ 新しいオペレーションを定義する) つまり、あなたがtest-opを呼ぶと、既にそのシステムをテストしたことがあっても、もう一度テストしたいのだとみなされるわけです。
ASDFはこのオペレーションの結果について何も定義していません。というのも、テスト結果をユーザーに伝える方法を、Lispコミュニティで使われる様々なテストのテクニックやライブラリすべてに対して互換性を持つように、統一的に定義するのは困難だとわかっているからです。さらに、ASDFのオペレーションは成功・失敗を返り値とするわけではないという点も、この問題を難しくしています。テスト結果を返すようにしたいのであれば、何らかのコンディションを定義し、test-opが失敗した時にそのコンディションを通知することを勧めます。どのテストが失敗したか、という情報はコンディションに保持させると良いでしょう。
一般的に、テストコードはテスト用の別のシステムに記述されることが多いです。テスト用のシステムを分離すれば、メインのシステムの依存関係には不要なテストフレームワークを指定しなくてすみます。それでは、テストフレームワークfiveamを使った例を見てみましょう:
(defsystem "foo"
:in-order-to ((test-op (test-op "foo/test")))
...)
(defsystem "foo/test"
:depends-on ("foo" "fiveam")
...)
さらに、test-opのperformメソッドを次のように定義します:
(defsystem "foo/test"
:depends-on ("foo" "fiveam")
:perform (test-op (o s)
(uiop:symbol-call :fiveam '#:run!
(uiop:find-symbol* '#:foo-test-suite
:foo-tests)))
:components ((:file "test")) ; (訳者追加)
...)
[訳者補足]
上のように定義すると、(asdf:test-system :foo)を実行してシステムfooに対するtest-opを呼び出した際、:in-order-toオプションによって、まずはシステムfoo/testに対するtest-opが呼び出されます。(fooのtest-opは何もしないので、アクションを「委託」しているのと同じことになります。)呼び出されたときの動作はfoo/testの:performオプションで定義されており、この例では、端的に言えば(fiveam:run! 'foo-tests:foo-test-suite)が実行されてテストが走ることになります。test.lispには例えば次のようなテストコードが書かれているでしょう。
(defpackage :foo-tests (:use :cl :fiveam)) (in-package :foo-tests) ;; スイート(テストのまとまり)を定義する (def-suite foo-test-suite) (in-suite foo-test-suite) ;; テストを定義する (test basic-test (is (= 1 (foo:baz 10))) ; (foo:baz 10)の返り値が1であることをテストする (signals type-error (foo:baz "10"))) ; (foo:baz "10")がtype-errorになることをテストする
fiveamはfoo-test-suiteに属する一連のテスト(この場合はbasic-testのみ)を実行し、その結果を標準出力に表示するでしょう。ただし、システム定義(defsystem "foo/test" ...)のリード時にfiveamパッケージが存在するとは限りませんから、(fiveam:run! ...)とそのまま書くとエラーになる可能性があります。uiop:symbol-callは「fiveamパッケージに属するrun!関数を呼び出す」フォームを安全に書く――シンボルの解決を実行時に遅らせる――ための関数であり、この用途でよく使われます。uiop:find-symbol*も同様の目的で使われています。UIOPのマニュアルも参照すると良いでしょう。
[訳者補足終わり]
これらは「バンドル(bundle)」に関わるオペレーションです。バンドルは、アプリケーション全体(もしくはアプリケーション中の各システム)を単一のファイルに束ねたものです。
compile-bundle-opはターゲットとなるシステムとそれが依存する各システムについて、それぞれ単一のfaslファイルを作ります。monolithic-compile-bundle-opはターゲットとなるシステムとそれが依存するシステムをすべて束ねて、単一のfaslファイルを作ります。つまり、アプリケーション全体を1つのfaslファイルにすることができます。load-bundle-opはcompile-bundle-opの出力をロードします。ただし、compile-bundle-opは(出力が最新でない場合)コンパイルの副作用として、中間生成物である各faslファイルをロードすることもあるという点に注意してください。バンドルに関するオペレーションは特にECLにおいて重要です。というのもECLでは、ダイナミックリンクの際に、数十もの独立したfaslファイルをロードするのは1つのファイルをロードするのに比べてずっと重いからです。
注意: compile-bundle-op、monolithic-compile-bundle-op、load-bundle-op、monolithic-load-bundle-op、deliver-asd-op、monolithic-deliver-asd-opは、ASDF 3.1以前はそれぞれfasl-op、monolithic-fasl-op、load-fasl-op、monolithic-load-fasl-op、binary-op、monolithic-binary-opと呼ばれていました。後方互換性のために古い名称も残されていますが、それらは実際の機能を表していない不適切な名前ではあります。
compile-bundle-opで作った単一のfaslファイルを配布する時は、precompiled-systemクラスを使うことで、ユーザーはソースで配布されたものと互換性を持ったシステムとして使えるようになります。単一のfaslとともに配布される.asdファイルは次のようになるでしょう:
(defsystem :mysystem :class :precompiled-system :fasl (faslのパス名に評価されるフォーム))
あるいは、deliver-asd-opやmonolithic-deliver-asd-opを使えば、ASDFはcompile-bundle-opの出力に加えてシステム定義も作ります。これによって、あなたのシステムやアプリケーションのコードを単一のファイルとして提供することができます。当然ながら、これらの結果をカレントイメージでテストしたい時は、新しくできた.asdファイルを使おうとする前に(asdf:clear-configuration)(あるいは少なくとも(asdf:clear-source-registry))を実行することを忘れないでください。ファイルシステムからソースレジストリを再設定するためです。
program-opは指定されたシステムとその依存関係から、実行可能ファイルを作り出します。その際、コアイメージをダンプ・復元する時のフックや、コマンドライン引数の処理が必要になるかもしれませんが、UIOPにはこの用途で便利なユーティリティがあります。実行時のエントリーポイントはdefsystemの:entry-pointオプションで指定できます。例えばエントリーポイントをmy-app:main関数にする場合は、:entry-point "my-app:main"とします。処理系によっては、(asdf:operate 'asdf:program-op :my-app)を実行すると、オペレーションの完了時に現在のLispイメージが終了するかもしれません。program-opの使用例はtest/hello-world-example.asd、test/hello.lispを参照してください。ビルド、テストはtest/test-program.script、test/make-hello-world.lispに書かれています。
image-opはイメージをダンプしますが、スタンドアローンとは限らないし、システムのエントリーポイントが使われたりすることもありません。お使いのLisp処理系のコアイメージの扱いに従い、システムがプリロードされたイメージを出力することになるでしょう。これは中間的なビルド結果として使われたり、ラッパーとなるスクリプトとともに使われたりします。
lib-opはターゲットとなるシステムとそれが依存する各システムについて、リンク可能なオブジェクト(FFIファイル、あるいはECLであればLispファイルも)から.aファイル(Windowsでは.lib)をそれぞれビルドするためのオペレーションであり、monolithic-lib-opはアプリケーション全体に対して1つの.aファイルを作ります。dll-op、monolithic-dll-opも類似のオペレーションですが、ダイナミックリンクのための.soファイル(Windowsでは.dll、MacOS Xでは.dynlib)を作ります。
これらの「バンドル」オペレーションは、ASDF 3以降で、アクティブにメンテナンスされているLisp処理系であれば常に利用可能ですが、メンテナンスされていない古い処理系では使えない可能性があります。この機能は、以前は特定の処理系のためにasdf-bundleというシステムに分離されて実装されていて、さらに遡ればECL限定の機能であったasdf-eclが元になっています。
「バンドル」オペレーションの出力先のパスは、他のオブジェクトファイルと同様にアウトプットトランスレーションの設定に従いますが、defsystemの:build-operationオプションに設定されたオペレーションに限っては異なります。26 この仕様は満足のいくものとは言いがたいので、将来的には変わる可能性があります。より良い仕様についてあなたの提案はありますか?
concatenate-source-opは、ターゲットとなるシステムとそれが依存する各システムについて、すべてのcl-source-fileを依存関係に沿った順序でそれぞれ1つのソースファイルに連結します。monolithic-concatenate-source-opは、ターゲットとなるシステムとそれが依存するシステムをすべて束ねて、単一のソースファイルを出力します。上のリストには、さらに連結した出力ファイルをロードしたり、コンパイルしてからロードしたりといったオペレーションが含まれています。
これらのオペレーションは、システムやアプリケーションを単一のソースファイルとして配布するのに役立ちます。その際に、ロードやコンパイル後のロードが適切に行われるかテストするのにも使われるでしょう。
ASDF自身も、monolithic-concatenate-source-opを使って単一のソースファイルasdf.lispとして配布されており、asdf/defsystemシステムそのものの前に前処理とuiopライブラリが挿入されています。
[訳注] この場合、:build-pathnameの指定があれば出力先はそのパス名になります。指定がない場合はcomponent-pathnameの返すディレクトリが使われるようですが、公式の仕様かどうかは明示されていません。