使用ASDF创建项目

Table of Contents

Common Lisp中,术语System就是“库”的意思,一个System就是由多个源文件或者第三方组件构成,定义一个System,就是为了解决文件和组件之间的依赖关系。通常“项目(Project)”的最小单位就是System。

而在Common Lisp标准中,最小单位的术语叫”modules“,主要涉及全局变量*modules*和provide、require两个函数。require在多数Common Lisp实现中已经被替换成其他,比如ASDF。

目前 ASDF 已成为Common Lisp“公认标准”,多数Common Lisp实现已自带ASDF模块。

1 使用ASDF手动构建项目

1.1 ASDF结构

ASDF定义的System里通常有一个.asd的文件,里面定义了这个System的名字、文件依赖 以及三方库的依赖等信息。

以restas(一个WEB框架)为例,解压后的restas目录里有一个restas.asd文件, 内容如下:

(defsystem #:restas
    :depends-on (#:cffi #:hunchentoot #:bordeaux-threads
			#:routes #:alexandria #:data-sift)
    :pathname "src"
    :components ((:file "packages")
		 (:file "special" :depends-on ("packages"))
		 (:file "declarations" :depends-on ("packages"))
		 (:file "errors" :depends-on ("special"))
		 (:file "render" :depends-on ("special"))
		 (:file "context" :depends-on ("special"))
		 (:file "module" :depends-on ("context" "declarations"))
		 (:file "route" :depends-on ("module" "render"))
		 (:file "decorators" :depends-on ("route"))
		 (:file "vhost" :depends-on ("special"))
		 (:file "hunchentoot" :depends-on ("vhost" "module" "errors"))
		 (:file "policy" :depends-on ("packages"))))

这里有几个参数需要说明:

defsystem 函数的第一个参数就是System的名字。

depends-on 定义了这个System外部三方组建依赖,这里是一个列表,列举了所 有依赖的组件,这些组件首先被加载。

pathname 指定了源码存放路径,此处指定了在src目录里。

components 定义了各个文件的依赖,这里是一个嵌套列表,关键字:file定义 了要加载的文件名,注意不需要文件后缀名。然后ASDF会依照列表的顺序加载 文件。这里首先加载src/packages.lisp;然后再加载src/special.lisp,并且 指明了special.lisp依赖packages.lisp。

1.2 手动创建.asd

本节我们先手动创建一个.asd来管理依赖。先找个位置创建个文件夹来管理 System,本例中我创建了一个文件夹在/tmp/hello里。

然后新建三个文件:

$ touch hello.asd
$ touch hello.lisp
$ touch package.lisp

hello.asd用来定义System信息,主要代码写在hello.lisp里,而package.lisp 是用来定义包名字的,通常都会把包定义单独放置在一个文件里来管理,这样方 便其他文件中调用。

首先定义hello.asd:

(defsystem #:hello
    :author "lu4nx"  ; 定义作者名字
    :version "1.0"   ; 定义版本号
    :license "MIT"   ; 定义开源协议
    :components ((:file "package")
		 (:file "hello" :depends-on ("package"))))

按上面说过的,components参数决定了加载哪些文件、加载顺序以及依赖关系。

package.lisp的内容如下:

(defpackage #:hello-world
  (:use :cl)
  ;; 对外导出say-hello函数
  (:export :say-hello))

这里定义了包名叫hello-world,注意Package的名字和System的名字之间区别, System名字是用于加载,而加载之后的命名空间由Package决定,两者可以不是 同名的,我这里定义的System名字叫hello,而Package的名字是hello-world。

最后,hello.lisp代码如下:

(in-package #:hello-world)

(defun say-hello ()
  (print "hello world"))

1.3 加载System

现在定义好了System,要加载它得先让ASDF能找到它,关于ASDF的搜索过程请见文档: http://common-lisp.net/project/asdf/asdf.html#Controlling-where-ASDF-searches-for-systems

在SBCL中,require函数默认已经被ASDF的加载实现替换了,所以可以直接用:

(require 模块名)

有些CL实现可能需要显示用ASDF的加载函数:

(asdf:load-system 模块名)

system加载路径:

  • 从固定路径中加载: ~/common-lisp或者~/.local/share/common-lisp/source/
  • 配置/etc/common-lisp/source-registry.conf

如果system分散在磁盘中多个位置,就可以配置source-registry.conf.d。

比如有些system是实际使用的,还有些system处于开发阶段,放在一个开发目录中(如:/my/mycode/common-lisp),但同时希望ASDF能加载这两个位置中的system。

  • 早期风格:asdf:*central-registry*

早期配置全局变量*central-registry*来定义加载位置,新版本的ASDF中不推荐继续使用。

1.3.1 示例

最简单的方法是在~/quicklisp/local-projects下创建一个hello.asd的链接,链接到/tmp/hello/hello.asd:

$ cd ~/quicklisp/local-projects
$ ln -s /tmp/hello/hello.asd .

然后用Quickload加载:

CL-USER> (ql:quickload :hello)
To load "hello":
  Load 1 ASDF system:
    hello
; Loading "hello"
[package hello-world]
(:HELLO)

尝试下调用say-hello函数:

CL-USER> (hello-world:say-hello)

"hello world"
"hello world"

1.4 解决附加文件问题

比如你的项目中某段代码需要加载某一个文件是用Common Lisp自带的文件函数来加载,在用Quicklisp加载你的这个项目时,会出现找不到该附加文件的错误。

ASDF编译system后,加载的话,*load-truename*和*load-pathname*会被改变,但是ASDF提供了system-relative-pathname函数可用:

(defun get-tld-data-path ()
  (asdf:system-relative-pathname 'cl-etcdomain "tld.dat"))

这样就会在原始目录加载tld.dat文件了。

2 QuickProject

我们其实可以用QuickProject这个库来自动创建项目,它会自动帮助我们创建.asd文件。

首先用QuickLoad加载QuickProject,如果系统里没有安装QuickProject会自动 下载并加载:

(ql:quickload :quickproject)

然后新建一个hello1项目:

(quickproject:make-project #p"/tmp/hello1" :author "lu4nx")

QuickProject会自动在/tmp目录下创建一个叫hello1的文件夹,之前我们手动定 义的内容它全部帮我们完成了:

  ├── hello1.asd
  ├── hello1.lisp
  ├── package.lisp
  └── README.txt

剩下的过程就跟上节的一样了,写代码,然后加载。