Previous: , Up: defsystemでシステムを定義する   [Contents][Index]


6.5 package-inferred-system拡張

バージョン3.1.2から、ASDFは1ファイル1パッケージのスタイルをサポートしています。このスタイルは、1つのソースファイルに1つのシステムが対応し、システム同士の依存関係は(訳注: おそらく各ファイルの冒頭に書かれるであろう)defpackageまたはuiop:define-packageから推測されるという仕組みです。

このスタイルでは、まずパッケージは同じ名前(ただし小文字化される)のシステムに対応します。そして、:class package-inferred-systemを指定されたシステムが存在する時、そのシステム名から始まるシステム(セパレータは/)は元のシステムの場所からシステム名に基づいて辿ったパスにあるファイルに対応します。後者は例を挙げたほうがわかりやすいでしょう。システムmy-lib:class package-inferred-systemオプションつきで/foo/bar/my-lib/my-lib.asdに定義されているとします。この時、システムmy-lib/src/utilityはファイル/foo/bar/my-lib/src/utility.lispに対応するというわけです。

1ファイル1パッケージのスタイルは、以前にfaslpathquick-buildといったツールで一般的になった方法であり、パッケージの規則を厳格化することと引き換えにメンテナンスしやすいコードを書くことができます。ASDF 3からは、このスタイルはASDF自身に採用されていますし、lisp-interface-libraryやいくつかの他のライブラリでも使われています。

ではこのスタイルによるシステム定義の例を見てみましょう。トップレベルのシステム名をmy-libとします。まずはmy-lib.asdファイルを作り、defsystemフォームに:class :package-inferred-systemオプションを指定します。例えば次のような定義になるでしょう:

;; この例はLISP-INTERFACE-LIBRARYのlil.asdに基づいています。

#-asdf3.1 (error "MY-LIBにはASDF 3.1以降が必要です。")
(defsystem "my-lib"
  :class :package-inferred-system
  :depends-on ("my-lib/interface/all"
               "my-lib/src/all"
               "my-lib/extras/all")
  :in-order-to ((test-op (load-op "my-lib/test/all")))
  :perform (test-op (o c) (symbol-call :my-lib/test/all :test-suite)))

(defsystem "my-lib/test" :depends-on ("my-lib/test/all"))

(register-system-packages "my-lib/interface/all" '(:my-lib-interface))
(register-system-packages "my-lib/src/all" '(:my-lib-implementation))
(register-system-packages "my-lib/test/all" '(:my-lib-test))

(register-system-packages
 "closer-mop"
 '(:c2mop
   :closer-common-lisp
   :c2cl
   :closer-common-lisp-user
   :c2cl-user))

package-inferred-systemを扱えるのはASDF 3.1以降なので、最初のフォームはそのチェックをしています。もっとも、主要なLisp処理系でこれ以前のバージョンを含んでいるものはありませんから、おそらくもはや必要のない記述でしょう。

このスタイルでは、システムやコンポーネントによってuseされる、あるいは提供されるパッケージは、パッケージ名(の小文字化)とシステム名が一致していない場合はregister-system-packagesで登録されなければなりません。

次に、my-libを構成する各ソースファイルは、defpackageuiop:define-packageによるパッケージ定義から始まるでしょう。それらの間の依存関係は:use:mixなどのオプションから推論されます。例えば、interface/order.lispというファイルが次のパッケージ定義で始まるとします:

(uiop:define-package :my-lib/interface/order
  (:use :closer-common-lisp
        :my-lib/interface/definition
        :my-lib/interface/base)
  (:mix :fare-utils :uiop :alexandria)
  (:export ...))

ASDFはこのファイルがシステムcloser-mopに依存していることを知り(useしているのはcloser-common-lispパッケージですが、このパッケージがシステムcloser-mopに対応していることは上で登録されています)、また、2つのシステムmy-lib/interface/definitionmy-lib/interface/baseにも依存していると判断します。

しかし、ASDFはどのようにして、トップレベルのシステムmy-libからinterface/order.lispにたどり着くのでしょうか? 上の例では、interface/all.lisp(と他のall.lisp)が、それ以下の階層にあるパッケージの(エクスポートされている)シンボルをまとめて再エクスポートしているでしょう。このようなファイルはuiop:define-packageで簡単に書けます:

(uiop:define-package :my-lib/interface/all
  (:nicknames :my-lib-interface)
  (:use :closer-common-lisp)
  (:mix :fare-utils :uiop :alexandria)
  (:use-reexport
   :my-lib/interface/definition
   :my-lib/interface/base
   :my-lib/interface/order
   :my-lib/interface/monad/continuation))

こうすれば、my-lib.asdの依存関係に各my-lib/.../allシステムを指定するだけで、ASDFがinterface/order.lispや他のすべての依存関係を:use-reexportから発見してくれます。このように、:use-reexportはエクスポートされたシンボルを「継承」するオプションであり、他にもuiop:define-packageにはこの用途で便利なオプションがたくさんあります。

また、ASDFは:import-fromオプションからも依存関係を発見できます、パッケージからシンボルをインポートすると、ASDFはそのパッケージに対応するシステムを依存関係に含めるでしょう。次の例を見てください。最初の:import-from節ではfoo/bazパッケージからsym1sym2がインポートされていますが、この記述でASDFは、カレントシステムがシステムfoo/bazに依存していることを推論します。いっぽう、特にシンボルをインポートせず、foo/quux:bletchのようにパッケージの接頭辞付きで使いたいケースもあるでしょう。この場合は、2番目の:import-from節のように、単に依存関係を宣言するための空インポートをします。

(defpackage :foo/bar
  (:use :cl)
  (:import-from :foo/baz #:sym1 #:sym2)
  (:import-from :foo/quux)
  (:export ...))

ASDF 3.1.5.6から、ASDFはソースファイルの場所を(:pathnameオプションで指定できる)component-pathnameを基点に探すようになりました。それ以前のバージョンではsystem-source-directoryを使っていたので、明示的に:pathnameを指定していても、.asdファイルのあるディレクトリを基点にしていました。21


Footnotes

(21)

[訳注] システムfooのディレクトリ構成として、システム定義ファイルをfoo/foo.asdに置き、ソースファイルをすべてfoo/src/以下に置く方式を選んだとしましょう。この時、foo/src/baz.lispに対応するシステムはデフォルトではfoo/src/bazですが、:pathname "src"でトップレベルのパスをfoo/src/に変えるとfoo/bazにできます。:pathnameオプションについてはdefsystemの文法を参照してください。