Next: .asdファイル中の他のコード, Previous: 複雑な例, Up: defsystemでシステムを定義する [Contents][Index]
system-definition := ( defsystem system-designator system-option* ) # → システム指示子 system-designator := simple-component-name | complex-component-name # アンダースコアは許容されていません。→ 単独のコンポーネント名 simple-component-name := lower-case string | symbol complex-component-name := string | symbol # → 複合的なコンポーネント名 system-option := :defsystem-depends-on system-list | :weakly-depends-on system-list | :class class-name # → システムクラス名 | :build-pathname pathname-specifier | :build-operation operation-name | system-option/asdf3 | module-option | option # このオプションはASDF 3以降(正確には、そのアルファリリースである2.27以降)でのみ使えます。 system-option/asdf3 := :homepage string | :bug-tracker string | :mailto string | :long-name string | :source-control source-control | :version version-specifier # → バージョン指定子 | :entry-point object # → エントリーポイント source-control := ( keyword string ) module-option := :components component-list | :serial [ t | nil ] option := :description string | :long-description string | :author person-or-persons | :maintainer person-or-persons | :pathname pathname-specifier # → パス名指定子 | :default-component-class class-name | :perform method-form | :explain method-form | :output-files method-form | :operation-done-p method-form | :if-feature feature-expression | :depends-on ( dependency-def* ) | :in-order-to ( dependency+ ) # → Controlling file compilation person-or-persons := string | ( string+ ) system-list := ( simple-component-name* ) component-list := ( component-def* ) component-def := ( component-type simple-component-name option* ) # component-typeが:moduleであればmodule-optionも使えます。 component-type := :module | :file | :static-file | other-component-type other-component-type := symbol-by-name # → コンポーネント型 # dependency-defは:depends-onの中で使われます。 dependency-def := simple-component-name | ( :feature feature-expression dependency-def ) # → フィーチャーに応じた依存関係 | ( :version simple-component-name version-specifier ) | ( :require module-name ) # dependencyは:in-order-toの中で使われます。 dependency := ( dependent-op requirement+ ) requirement := ( required-op required-component+ ) dependent-op := operation-name required-op := operation-name # [訳注] 現状では required-component := dependency-def と考えて良さそうです。 # パス名はすべて小文字であるべきです。アンダースコアを含むべきではありませんが、 # ハイフンは許容されます。 pathname-specifier := pathname | string | symbol version-specifier := string | ( :read-file-form pathname-specifier form-specifier? ) | ( :read-file-line pathname-specifier line-specifier? ) line-specifier := :at integer # 0-based form-specifier := :at [ integer | ( integer+ ) ] method-form := ( operation-name qual lambda-list &rest body ) qual := method-qualifier? method-qualifier := :before | :after | :around feature-expression := keyword | ( :and feature-expression* ) | ( :or feature-expression* ) | ( :not feature-expression ) operation-name := symbol
システム指示子は単独のコンポーネント名(例: "foo"
)か、またはスラッシュで区切られた複合的なコンポーネント名(例: "foo/bar/baz"
)です。
simple-component-name
)単独のコンポーネント名は文字列またはシンボルで指定することができます。
文字列を使う場合は、小文字のみを使ってください。
シンボルはsymbol-name
で文字列に変換され、さらに小文字に変換されます。言い換えれば、シンボルは指示子として有効ですが、コンポーネント名そのものは文字列だということです。
文字列であれシンボルであれ、コンポーネント名にアンダースコアを含めてはいけません。
また、単独のコンポーネント名にスラッシュ/
を使ってはいけません。スラッシュは複合的なコンポーネント名を表すのに使われます。この点については次のサブセクションを参照してください。スラッシュが不適切に使われている場合、ASDFは警告を発するでしょう。
これらの制限を犯した場合、つまり、大文字と小文字を混ぜたり、アンダースコアを使ったりした場合、ASDFがシステムやコンポーネントを見つけるのが不可能になる可能性があります。というのも、コンポーネント名はファイル名として解釈されるからです。特に、論理パス名を使うようにASDFを設定したユーザーは、確実にこのような問題に直面するでしょう。
複合的なコンポーネント名は、いくつかの名前を階層に従ってスラッシュ区切りで並べたものです。この仕組みは、1つの.asdファイルに複数のシステム定義を置いた場合に、ASDFがそれらを見つけられるようにするためにあります。
この際、最上位の名前(master name)は.asdファイルの名前と一致していなければなりません。例えば、foo.asdファイルにはシステムfoo
が定義されているでしょうが、それに加えてfoo/test
やfoo/docs
などのシステムも定義することが可能です。ASDFは、システムfoo/test
をロードするように要求されたとき、foo.asdファイルを探せばよいことを知っているというわけです。
コンポーネント型の名前は、(仮にキーワードで指定されたとしても)まずカレントパッケージのシンボルから検索されます。カレントパッケージに見つからなければasdf
パッケージから検索されます。つまり、カレントパッケージmy-system-asd
に属するコンポーネント型my-component-type
は、:my-component-type
またはmy-component-type
で指定することができます。
system
とそのサブクラスは、システムの子コンポーネントのコンポーネント型として使うことはできません。
システムクラス名はコンポーネント型と同じように検索されますが、ここではsystem
とそのサブクラスのみを使うことができます。典型的な用途としては、何らかのASDF拡張で定義された標準外のシステムクラスを使う場合に、下述の:defsystem-depends-on
でその拡張モジュールをロードしつつ、:class
にクラス名を指定します。ASDFパッケージの中でこのクラス名を指定するときは、シンボル名の衝突を防ぐために
:class :MY-NEW-SYSTEM-SUBCLASS
のようにキーワードを使うことを推奨します。キーワードではなくMY-NEW-SYSTEM-SUBCLASS
で指定した場合、この名前のシンボルがカレントパッケージに読み込まれたあと、(ASDF拡張が:defsystem-depends-on
によってロードされて、そのパッケージから)同名のシンボルがエクスポートされる、という順序で処理が行われるため、名前の衝突が起きてしまいます。15
:defsystem-depends-on
):defsystem-depends-on
オプションを使うと、システム定義が処理される前に、ASDFで定義された別のシステム(またはシステムのまとまり)をロードすることができます。このオプションは典型的には、システム定義の中でASDFの拡張をロードするために使われます。
:build-operation
):build-operation
オプションには、システムに対してmake
(→ make)を実行したときに、どのオペレーションを適用するか指定することができます。デフォルトではload-op
が使われます。このオプションの値はオペレーションの名前でなければなりません。(例えば:build-operation doc-op
など)
この機能は実験的でテストが不十分です。自己責任で使ってください。
:weakly-depends-on
)この機能を使うことは推奨しません。システムbar
に弱依存しているシステムfoo
を作りたい場合は、パラメトリックにfoo
を定義して、スペシャル変数やフックで動作を変えられるようにすると良いでしょう。そして、システムfoo+bar
を定義してまとめてフックできるようにしましょう。
(廃止予定の):weakly-depends-on
オプションで指定されたシステム(あるいはそのまとまり)については、ASDFはそれらのロードを試みますが、ロードの成功が必須であるとはみなしません。このオプションは典型的には、システムの基本的な機能には必要ないが付加的な機能をもたらす、という依存関係を指定するのに使われます。
上の文法定義では、このオプションはdefsystem
にのみ指定されるものとなっていますが、今のところはどのコンポーネントにも指定可能です。しかし、defsystem
直下以外に指定するのはおそらく無意味でしょうし、この(例外的な)挙動は将来、警告なしに変わる可能性があるので、プログラマの方にはdefsystem
直下以外に指定しないように念を押しておきます。
パス名指定子(pathname specifier)はパス名、文字列、シンボルのいずれかですが、多くの場合、コンポーネントは:pathname
オプション無しで記述されるでしょう。省略された場合にはコンポーネント名そのものが使われます。
普通は文字列を与えますが、文字列はUnixスタイルのパス名として解釈され、文字/
はディレクトリのセパレータとみなされます。たいていは相対パスが使われ、その場合は親コンポーネントのパスを基点にした相対パスとみなされます。文字列はコンポーネント型に応じてファイルともディレクトリとも解釈されますが、ファイルの場合、コンポーネント型によっては自動的に拡張子が付加されることがあります。
例えば、コンポーネント型の1つである:module
は、ディレクトリを対象とするので、文字列"foo/bar"
はパス名#p"foo/bar/"と解釈されます。
また、コンポーネント型の1つである:file
は、ファイル形式がlispのファイルを対象とするので、文字列"foo/bar"
はパス名#p"foo/bar.lisp"と解釈され、"foo/bar.quux"
は#p"foo/bar.quux.lisp"と解釈されます。16
最後に、コンポーネント型の1つである:static-file
は、特定のファイル形式を前提とせずにファイルを対象にとるので、文字列"foo/bar"
はパス名#p"foo/bar"と解釈され、"foo/bar.quux"
は#p"foo/bar.quux"と解釈されます。
".."
はパス名のディレクトリコンポーネント中の:back
と解釈されます。17
つまり、文字列中の".."
は1つ上の階層のディレクトリを意味します。
シンボルが与えられた場合、小文字の文字列に変換されます。小文字に変換するのは慣習と異なっていますが、検討の末このような仕様になりました。我々がサポートするファイルシステムには、小文字を使うのが通例のもの(Unix、Mac、Windows)と、小文字を暗黙に大文字に変換するもの(ANSI CLの論理パス名)があるので、単にmake-pathname
を:case :common
として使う方式(うまく動かない処理系もある)よりも適切と思われます。
システム名やコンポーネント(モジュール、ファイル)名にアンダースコアを使うのは避けてください。論理パス名がアンダースコアに対応していないためです。(→ 論理パス名を使う)
パス名オブジェクトを与える場合、一般的には#p
や#.(make-pathname ...)
のようなリーダーマクロが使われるでしょう。しかし、#p...
は#.(parse-namestring '...)
と同等であり、parse-namestring
は論理パス名を使わない限りまったくポータブルではないし、論理パス名もまた別のポータブルでない挙動を含んでいるという問題があります。(→ 論理パス名を使う) また、たいていは、パス名を#.(make-pathname ...)
で作るより、上述の文字列を使う方式のほうが簡単です。本当にパス名オブジェクトを使う必要があるのは、コンポーネントのデフォルトのファイル形式(拡張子)を上書きする必要がある場合のみです。従って、パス名オブジェクトはめったに使われないでしょう。残念ながらASDF 1には、コンポーネント名そのものをディレクトリを指す文字列としてパースして適切に扱う仕組みが無かったので、#.(make-pathname ...)
という面倒な書き方をしなければなりませんでした。リード時評価#.
の代わりに(eval `(defsystem ... ,pathname ...))
で代用することも可能です。
文字列が与えられた場合にコンポーネント型が考慮されてパス名が解釈されるのと異なり、パス名オブジェクトが与えられた場合はそのまま使われるということに注意してください。つまり、パス名オブジェクトを使うなら、コンポーネント型の制約を満たすように(例えばディレクトリや特定のファイル形式が指定されるように)あなた自身が気を配って書かなければなりません。他方では、ファイル形式の制約をあえて無視するために使うこともできます。
バージョン指定子(version specifier)は文字列であり、ピリオド#\.
で区切られた整数としてパースされます。例えば、"0.2.1"
は大ざっぱに言えば(0 2 1)
というバージョンと解釈されます。留意点として、"0.0002.1"
は"0.2.1"
と同じバージョンと判断されますが、前者は正規の書き方ではないので、警告が通知されるでしょう。また、バージョンは小数として扱われるわけではないので、"1.3"
や"1.4"
は"1.30"
と比較してバージョンが小さい((uiop:version< "1.3" "1.30")
が真を返す)ことにも注意してください。
:version
引数には、バージョンを文字列リテラルで直接指定する代わりに、他の場所からバージョン文字列を抽出するための表現を与えることも可能で、次のようなDSLが用意されています: (:read-file-form <パス名または文字列> [:at <access-at-specifier>])
あるいは(:read-file-line <パス名または文字列> [:at <access-at-specifier>])
。名前が示すように、前者は与えられたパス(相対パス名やunix-namestring
で与えられた場合はシステムのパスをベースに解釈される18)から1つのフォームを読み、後者は1つの行を読みます。:at
引数にはuiop:access-at
と同様の指定をすることができますが、デフォルトでは0
、つまり最初のフォーム/行を読み込むことになります。(訳注: 記述の例は複雑な例にあります。) また、:read-file-form
にはサブフォーム、つまりフォーム中のフォームを指定することもできます。例えば(1 2 2)
は「2番目のフォーム中の3番目のフォーム中の3番目のフォーム」を指定しています。この指定は0番目から数えることに気をつけましょう。
システムを定義する際には、x.y.zという形式でバージョンを指定し、それぞれメジャーバージョン、マイナーバージョン、パッチレベルに対応させることを推奨します。APIの重要な非互換が発生する場合は、メジャーバージョンを上げてそのことを示しましょう。
:require
依存モジュールをロードするのに(ASDFのload-op
ではなく)処理系のrequire
関数を使う場合には、(:require モジュール名)
という指定をします。
特定の処理系でのみ、その提供モジュールに依存しているという場合には、単に#+処理系名 (:require モジュール名)
と書くよりは、(:feature 処理系名 (:require モジュール名))
を使うほうが良いスタイルと言えます。(→ フィーチャーに応じた依存関係)
:feature
)フィーチャーに応じた依存関係は(:feature feature-expression dependency)
というフォームで指定します。システム定義がパースされる時にfeature-expressionが満たされれば(訳注: 例えば、*features*
にfeature-expressionが含まれれば)依存関係が使われ、満たされない場合は無視されます。
ただし、:feature
は今定義しているシステムが特定のフィーチャーに依存していることを示すために使うことはできないという点に注意しましょう。すなわち、システム定義をロードするのに必要なフィーチャーを指定することはできません。例えば、システムをSBCLでしか使えないようにしようとして(:feature :sbcl)
と書いても無意味です。
なお、フィーチャーに応じた依存関係を、if-feature
や廃れた機能であるfeature requirement(廃止)と混同しないようにしましょう。
我々は一般に、論理パス名を使うことを推奨しません。(最近Common Lispを使い始めた人にはなおのことです。) しかし、論理パス名を好む古参の方のためにサポートはしています。
論理パス名を使うなら、コンポーネントの定義中の:pathname
に対して#p"LOGICAL-HOST:absolute;path;to;component.lisp"
などといった文法でパス名オブジェクトを与える必要があるでしょう。
論理パス名はシステムや親コンポーネントに指定すれば十分です。それらに属する子コンポーネントには特にパスを指定しなくても、名前を相対パスとして親コンポーネントのパスと適切にマージされます。なお、:pathname
に文字列を与える場合は、論理パス名のホストの指定をすることはできません。
asdf-output-translations
レイヤー(→ ASDFがコンパイルされたファイルを保持する場所を設定する)は論理パス名の解決・解釈をすることを避けます。この仕様の良いところは、論理パス名をどのように解釈するかあなた自身で決められることですが、解釈を定義しなかった場合に、論理パス名を含むシステムが異なる環境のasdf-output-translations
で異なる振る舞いをするという欠点もあります。
したがって、論理パス名を使いたい時は、使われる前にその解釈(logical-pathname-translations
)を設定する必要があるでしょう。ASDFには今のところ、論理パス名の解釈を決めるための機能がありません。
我々が論理パス名の使用を推奨しない理由としては、(1) 論理パス名が使われる前にその解釈を設定するポータブルな方法がないことと、(2) 論理パス名には1つのレターケース(大文字か小文字のどちらか)、数字、ハイフンしか使えないことがあります。1つ目の問題はあなた自身で解決することもできるでしょうが、15の処理系についてそれぞれどのように対処するかは、我々がドキュメントとして書ける範囲を超えています。また、2つ目の制約はSBCLが要求しているので、ポータビリティのためには無視することができません。したがって、この制約を侵す物理パス名を参照したい時は、あなた自身が何らかのエンコーディングを定義して独立したマッピングを追加しなくてはなりません。とりわけ、大規模プロジェクトの一部としてLispファイルが含まれる場合、この仕様は悩みの種になるかもしれません。典型的な例としては、プロジェクトのファイル、ディレクトリ名にバージョン番号が含まれる場合や、あるいは他のプログラミング言語で書かれたソフトウェアが共存していて、ファイル名にアンダースコアやドット、キャメルケースが使われる場合などがあります。
:serial
)モジュールの定義で:serial t
オプションを指定すると、そのモジュールの子コンポーネントは常に前の子コンポーネントに依存しているとみなされます。これは:depends-on
でいちいち依存関係を指定するのと同じことになります。つまり、
:serial t :components ((:file "a") (:file "b") (:file "c"))
は次の定義と同等です。
:components ((:file "a") (:file "b" :depends-on ("a")) (:file "c" :depends-on ("a" "b")))
:pathname
)defsystem
でシステムを定義する際、:pathname
オプションを任意で指定できますが、通常は必要ありません。単純なプロジェクトでは、ソースファイルはシステムと同じディレクトリにあるでしょうし、モジュールを使う場合はモジュールと同じ名前のサブディレクトリにあるでしょうから。
より詳細に説明すると、ASDFは次の挙動を実現するために、複雑なルールに従っています。
find-system
はシステムをディスクからロードして、そのデフォルトのパス名19を正しく設定します。
*default-pathname-defaults*
(この場合、まったく別の場所を指しているかもしれません)によって書き換えられることはありません。
システムが初めて定義される場合、そのトップレベルのパス名は次のように決まるでしょう:
*load-truename*
にパス名が束縛されている場合、そのホスト、デバイス、ディレクトリが使われます。
*load-truename*
にパス名が束縛されていない場合は、*default-pathname-defaults*
が代わりに使われます。
システムが再定義される場合、トップレベルのパス名は次のように決まります:
*load-truename*
にパス名が束縛されている場合は、新しいソースの場所を反映するように更新されます。
*default-pathname-defaults*
によってセットされていた場合は、更新されます。
*load-truename*
によってセットされていて、現在は*load-truename*
がnil
である場合は、更新されません。この仕様によって、開発者はdefsystem
フォームを(ソースの場所に影響を与えずに)エディタ内で評価することができます。
:if-feature
)このオプションは、特定のコンポーネントをビルドに含めるかどうかを(#+
のように)フィーチャーに応じた条件で指定することができます。フィーチャーが満たされない場合、そのコンポーネントは無視され、また、他のコンポーネントがそのコンポーネントに依存するという記述も無視されます。リード時に評価される#+
と異なり、このオプションを使うと、コンポーネントの階層にオブジェクトを追加し、そのオブジェクトをプロジェクトをビルドする際に利用したり、システム構造について推論したい外部のコードからアクセスしたりといったことができます。
プログラマはいつ:if-feature
が評価されるか知っておくべきです。ASDFは最初にビルド全体の計画を建て、次にその計画を実行するという順で動作しますが、フィーチャーが満たされるかどうかは計画時にチェックされます。従って、ビルド中に*features*
を変更して:if-feature
に影響を与えるということはできません。:if-feature
はビルドオペレーションが実行される直前の*features*
の状態だけをチェックするのです。
このオプションはASDF 3で追加されました。詳細な情報については、必要なフィーチャーを参照してください。
:entry-point
):entry-point
オプションには、program-op
で実行可能ファイルを作る際のエントリーポイントを指定することが出来ます。
program-op
が呼ばれた時、このオプションに与えられたフォームはuiop:ensure-function
で関数に変換され、uiop:*image-entry-point*
に束縛されます。uiop:ensure-function
は任意のオブジェクトを引数に取れますが、このオプションでは一般に"foo:main"
のような文字列が使われ、その場合、実行可能ファイルはfoo:main
関数から開始することになります。なお、シンボルfoo:main
を使うとうまく動かない可能性があることに注意してください。なぜなら、ASDFがdefsystem
フォームをリードする時にfoo
パッケージが既に存在しているとは限らないからです。program-op
に関する詳細は組み込みのオペレーションを参照してください。
この機能はASDF 3.1で廃止されたので、使わないでください。ほとんどの場合は:if-feature
(→ if-featureオプション)で代替できるでしょう。
この機能は、特定のフィーチャーが欠けているときに、コンポーネントの依存関係の連鎖が断たれるようにするためのものでした。:if-component-dep-fails
と併せて使うと、条件つきのコンパイルを(遠回りではありますが)記述することができたのです。
[訳注] 現在のバージョンでは、キーワード指定するとasdf/interface
パッケージからクラスが探されます。別のパッケージのクラスを使いたい場合は"package:my-new-system-subclass"
のように文字列で指定しましょう。
[訳注] "foo/bar.lisp"
は#p"foo/bar.lisp.lisp"と解釈されるので、拡張子を明示して書くことはできません。
[訳注] ここはわかりにくいですが、文字列中では".."
がUnixのパスと同じように使えるということを知っていれば十分です。ANSI CLのパス名オブジェクトは6つのコンポーネントを含みますが、その内の1つであるディレクトリコンポーネント中に:back
が含まれているのと同じ意味になるということです。CLHS: 19.2.2.4.3 Restrictions on Examining a Pathname Directory Componentも参照。
[訳注] CLのパス名(リテラル#p...
あるいは#.(make-pathname ...)
による)を与えるか、文字列でUnixスタイルの相対パスを与えるかの2択であり、絶対パスを指定したい場合は(稀と思われますが)前者のパス名しか使えないということです。相対パスは.asdファイルのあるディレクトリを基点に解釈されるので、:pathname
オプションでシステムのトップレベルのディレクトリを変更していても影響しないという点にも注意しましょう。
なお、具体的には(uiop:subpathname (asdf:system-source-file <システム名>) <パス名または文字列>)
の返すパス名が使われています。
[訳注] component-pathname
でアクセスできるパス名です。
Next: .asdファイル中の他のコード, Previous: 複雑な例, Up: defsystemでシステムを定義する [Contents][Index]