ASDF 筆記 –– Common Lisp 系統定義設施

作者:Juanito Fatas Lisp Taiwan

ASDF Another System Definition Facility

A tool for specifying how systems of Common Lisp software are comprised of components (sub-systems and files), and how to operate on these components in the right order so that they can be compiled, loaded, tested, etc. —— ASDF manual


ASDF (Another System Definition Facility) is an extensible build facility for Common Lisp software. —— CLiki.net

保持高度開發狀態的 ASDF,目前由在 Google Inc., 工作的 François-René Rideau 維護(Google Common Lisp 代碼風格指南的作者)。

當前版本 (Current Releases)

  • 2010.05.31 ASDF2 released.
  • 2013.01.31 ASDF3 released.
  • 2013.02.01 ASDF3 (v2.28)
  • 2013.02.16 ASDF3 (v2.29)

幾乎所有主流 Common Lisp 實現都包含了 ASDF2。目前官方已經不再支援 ASDF1 與 ASDF2,所以升級到 ASDF3 吧!

這裡 ASDF2 與 ASDF3 並不是說 ASDF 的版本是 2.0 與 3.0。

ASDF3 實際上是版本 2.28 以上的 ASDF。

ASDF 與 ASDF-INSTALL

注意 ASDF 並不會幫你自動去下載你要用的函式庫。

asdf-install used to be a program for downloading and installing lisp packages.

ASDF-INSTALL 不是 ASDF 的一部分,並且是個 過時的 產物,沒有人在維護了,絕對不要再用了!

推薦使用目前處於積極開發狀態的 Quicklisp 來取代,更進階的用途推薦使用 CLBUILD

加載 ASDF (Loading ASDF)

Common Lisp 實現通常附有一份 ASDF2,很快的將會內建 ASDF3。

要在 Common Lisp 裡加載 ASDF,使用 require 函數:

1
(require "asdf")

還可使用 (require 'asdf)(require :asdf)(require "ASDF" ) 來加載(除了 CLISP), 為了保持可移植性最大化,一致使用 (require "asdf")

提供 ASDF 2 的實現:

即將提供的 ASDF 的實現

過時的 Common Lisp 實現:

檢查 ASDF 的版本:

可以輸入代碼來檢查 ASDF 版本:

1
2
(asdf:asdf-version) ; => 2.20
; 實際版本根據你所使用的 CL 實現不同。

或是寫一個函數:

1
2
3
4
5
6
7
8
9
(defun my-asdf-version ()
  (when (find-package :asdf)
    (let ((ver (symbol-value (or (find-symbol (string :*asdf-version*) :asdf)
                                 (find-symbol (string :*asdf-revision*) :asdf)))))
      (etypecase ver
        (string ver)
        (cons (with-output-to-string (s)
                (loop for (n . m) on ver do (princ n s) (when m (princ "." s)))))
        (null "1.0")))))
1
2
3
(my-asdf-version) ; => "version-number"
; Clozure Common Lisp Version 1.8-r15286M  (DarwinX8664)!
; (my-asdf-version) => "2.20"

若返回的字串為 "1.0",則可能是 ASDF 1.77 之前的版本(充滿 bug 的 1.x 版本)。

獲得 ASDF

  1. 直接下載 ASDF.lisp
  2. 下載完整的 tarball
  3. 使用 Git:

注意:master 分支為開發版本(dev),stable 分支為穩定版本。

1
git clone git://common-lisp.net/projects/asdf/asdf.git

可以到 gitweb 瀏覽最近的開發狀態

下載歷史版本的 tarball: archives

升級 ASDF

將 ASDF.lisp 放到 /path/to/new/asdf/ 目錄下

升級:

1
2
3
(require "asdf")
(push #p"/path/to/new/asdf/" asdf:*central-registry*)
(asdf:oos 'asdf:load-op :asdf)

記得加上路徑後面的 / ,。

如果不知道何故,仍然升級不了系統的 ASDF,去官網下一個最新的 ASDF,把這行代碼加入到啟動腳本 (startup script)裡(如 sbcl.rcccl-init.lisp):

1
(load "/path/to/new/asdf/asdf.lisp")

升級 ASDF 的限制

  • 先前加載的 ASDF 擴展都得重新加載,如: CFFI-Grovel。事實上很難知道啥擴展要重加載,ASDF 2.0.15 後自動替你完成,不用擔心。

  • 總之有一大堆相容性的理由,就升級到 ASDF3 吧!(詳見 ASDF manual)

  • 避免 ASDF 產生相依性問題

系統定義 (defsystem) 裡不要有 :depends-on (:asdf) 或是 :depends- on ((:version :asdf "2.010"))

應該用下面這段代碼來檢查,確保 ASDF 的版本夠新:

1
2
3
(unless (or #+asdf2 (asdf:version-satisfies
                     (asdf:asdf-version) *required-asdf-version*))
  (error "FOO requires ASDF ~A or later." *required-asdf-version*))

配置 ASDF

ASDF 就是幫我們加載及編譯系統的工具, 我們需要撰寫一個 .asd 文件,跟 ASDF 說系統的定義是什麼、依賴什麼文件阿、包放在哪阿…等等。

安裝 Common Lisp 軟件的默認位置為:

~/.local/share/common-lisp/source/

安裝在這就無需配置了!

要是安裝在別地方,你得給 ASDF 打個招呼才行。

但用 Quicklisp 安裝的朋友,Xach 已經幫你打好招呼了…

要讓 ASDF 知道你的 .asd 文件在 ~/user/.asd-link-farm/ ,首先建個目錄:/.config/common-lisp/source-registry.conf.d/,並添加一個文件,以 .conf 結尾,如:

42-asd-link-farm.conf 並添加內容:

(:directory "/home/luser/.asd-link-farm/")

告訴 ASDF 來這裡搜尋 .asd 文件,若該目錄有子目錄存在,需要遞歸搜索,則使用:

(:tree "/home/luser/.asd-link-farm/")

這邊命名有個小技巧,添加 42 當作前綴,要是你保持這個習慣,將會按照號碼搜索。

.conf 使得編輯器不會給你添加一些有的沒有的備份文件,而用CLISP 同時要處理有&無擴展名的文件是很痛苦的,所以用 .conf 吧。

這樣當要找某個系統時,ASDF 就會去掃描這些 .conf ,看看有沒有你需要的系統存在。

可以使用這個命令來重置 source-registry 的配置文件:

(asdf:clear-source-registry)

實際上在 dump 一個 Lisp image 前,使用

(asdf:clear-configuration)

就會自動重置 source-registry 的配置文件了。

配置 ASDF 來找到系統(舊風格)

push 路徑到 asdf:*central-registry* 變量。

要在加載完 ASDF 後,使用某個系統前配置好這個變量。

(寫在你的啟動腳本裡,如 ~/.sbclrc

缺省 ASDF2 & ASDF3 的 asdf:*central-registry* 為空,還保留這個變量是考慮到兼容性 (ASDF1)。

例子:告訴 ASDF 找到 /home/me/src/foo/foo.asd

.sbclrc:

(push "/home/me/src/foo/" asdf:*central-registry*)

注意最後一個 /很重要 ,一定要加上。因為 ASDF 在尋找系統時,會將 central-registry 裡的每個 entry 都求值,再 coerce 成一個路徑名,沒有加 / 的話,會被當成文件,而不是目錄。

通常 .asd 文件都存在不同的目錄下,通常的作法是用一個集中的目錄:比如 asd-link-farm ,目錄內放個 .asd 文件的 symbolic links ,再把 asd-link-farm 推至 central-registry

MacOS 請不要使用 Alias,Alias 與 symbolics link 是不一樣的!

例子:

#p"/home/me/cl/systems/"asdf:*central-registry* 的成員,則 foo 系統可以這麼配置:

1
2
$ cd /home/me/cl/systems
$ ln -s ~/src/foo/foo.asd

配置 ASDF 存儲 object 文件的位置

ASDF 讓你自己決定 object 文件存在哪裡,並帶有合理的默認值。

這使得不同版本的 Common Lisp 實現得以共享代碼庫,以不同的選項來編譯,也讓 object 文件不會污染了代碼庫。

從 ASDF2 開始,ASDF 加入了 asdf-output-translations 工具,用來控制 object 文件存放的位置。這個工具請參閱第八章(控制 ASDF 存放編譯後的文件

object 文件?

每個 Common Lisp 實現的編譯器,用二進制表示源文件文件(每個實現的表示方式不同,所以互相不兼容)

.fasl for fast load

什麼是 translation?

路徑名的對應,比如將 /foo/bar/baz/quux/ 對應到 /where/i/want/my/fasls/

要實現上面的對應,建立一個目錄 ~/.config/common-lisp/asdf-output-translations.conf.d/ ,並創建一個擴展名為 .conf 的文件,如:42-bazquux.conf (名字可隨便取),並添加以下內容:

("/foo/bar/baz/quux/" "/where/i/want/my/fasls/")

要是某個特定目錄下的路徑名不想做對應的話,創建一個文件 40-disable-toto.conf):

("/toto/tata/")

要整個禁止目錄的 translation,可以創建一個文件 00-disable.conf :

(t t)

要是想重置 source-registry 的配置可以使用:

(asdf:clear-output-translations)

你應該在 dump Lisp image 之前先將配置還原到默認設置。再一次強調,你應該在 dump image 之前執行:

(asdf:clear-configuration)

最後要注意的是,ASDF2 以前,其他的 ASDF 擴充 (add-ons)同樣提供了這個功能,但每個實現的方式有著巧妙的差異,並且各自不兼容:ASDF-Binary-Locations, cl-launch, common-lisp-controller. ASDF-Binary-Locations 已經功成身退了,應該不要再使用它了。 cl-launch 3.000 以及 common-lisp-controller 7.2 已經被更新,好讓他們可以給 ASDF 提供這個功能。

重置配置

當 dump 並 restore image 時,會是你在實驗配置時,你有時會想重置配置。可以使用 clear-configuration 這個函數,這個函數將會 UNDO 所有的 ASDF 配置,包含了 source-registry 或是 output-translations。

如果你使用 SBCL, CMUCL 或是 SCL,可以用這段代碼再 dump 出 image 之前自動清空配置:

1
2
3
4
#+(or cmu sbcl scl)
(pushnew ’clear-configuration
         #+(or cmu scl) ext:*before-save-initializations*
         #+sbcl sb-ext:*save-hooks*)

4. 使用 ASDF

4.1 載入系統

通過對下面這個 Lisp 形式求值來載入 foo 系統:

1
(asdf:load-system :foo)

某些實現(ABCL, Allegro CL, Clozure CL, CMUCL, ECL, GNU CLISP, LispWorks, MKCL, SBCL 以及 XCL)提供了 ASDF hook 到 CL:REQUIRE 工具裡,你可以直接:

1
(require :foo)

舊版的 ASDF 你需要使用:

1
(asdf:oos 'asdf:load-op :foo)

注意系統的名字可以是字串或符號(通常是關鍵字, 小寫 )。

4.2 其他操作

ASDF 提供了三個基本操作:

  • load-system
  • compile-system
  • test-system

另外還有個 require-system ,另一種的 load-system ,會試著更新已經載入的系統。

ASDF 提供了一個通用函數(generic function)operate (通常縮寫成 oos),除了加載、編譯、測試之外的操作,使用 oos

4.3 總結

要使用 ASDF:

  1. 加載 ASDF 到 Lisp 鏡像:

     (require "asdf")
    

     (load "/path/to/asdf.lisp")
    
  2. 確定 ASDF 可通過正確的 source-registry 配置,找到系統定義。

  3. (asdf:load-system :my-system) 來加載系統

4.4 更進一步

就這樣了!要加載別人的系統,你需要知道的就這麼多了。文章接下來會告訴你如何替你寫的 Common Lisp 軟件撰寫系統定義,知識包含了如何擴展 ASDF 來定義新的操作及組件類型(components types)。