ASDF 使用手册(ASDF Manual)
注意:因这是我从我的 为知笔记中 考出来的,可能不全,如需最新请跳转我的分享链接,如链接有问题请回复我
1. 引言(Introduction)
ASDF或另一个系统定义工具是一个构建系统:一种工具,用于指定 Common Lisp 软件的系统如何由组件(子系统和文件)组成,以及如何以正确的顺序操作这些组件可以进行编译,加载,测试等。如果您不熟悉 ASDF,请参阅快速入门指南。
ASDF 呈现三张面孔:一张给希望重用他人代码的 Common Lisp 软件用户,一张给想要指定如何构建系统的 Common Lisp 软件的编写者,一张给想要扩展 Java 扩展的 Common Lisp 扩展的实现者。构建系统。有关更多详细信息,请参阅 《使用ASDF》 以了解如何使用 ASDF 加载系统。请参阅使用 defsystem 定义系统,以了解如何定义自己的系统。有关 ASDF 内部结构以及如何扩展 ASDF 的说明,请参见 ASDF 的对象模型。
请注意,ASDF 不是库和系统安装的工具;它起着 make 或 ant 的作用,而不像包管理器那样。特别是,不应将 ASDF 与试图为您查找和下载 ASDF 系统的 Quicklisp 或 ASDF-Install 混淆。尽管顾名思义,ASDF-Install 从来都不是 ASDF 的一部分。它始终是一个单独的软件。 ASDF-Install 也已经很长时间没有维护和淘汰了。我们建议您改用 Quicklisp(http://www.quicklisp.org/),它是一个通用的 Lisp 程序包管理器,可以很好地工作并得到积极维护。如果要从版本控制而不是从 tarball 下载软件,则可以更轻松地对其进行修改,建议使用 clbuild(http://common-lisp.net/project/clbuild/)。至于文件系统上安装 Common Lisp 软件的位置,我们建议 〜/common-lisp/的子目录:从ASDF 3.1.2(2014)开始,此层次结构包含在默认的源注册表配置中。
最后,请注意本手册不完整。涵盖了所有基础,但是几乎没有提及许多高级主题,并且在示例方面也没有太多。源代码仍然是最终的信息来源,Quicklisp 中的免费软件系统仍然是示例的最佳来源,而邮件列表是寻求帮助的最佳位置。
2.快速入门总结(Quick start summary)
加载ASDF系统:
使用 (require "asdf") 将 ASDF 加载到 Lisp 映像中。使用 (asdf:asdf-version) 检查您是否具有最新版本。有关更多详细信息,或者以上任何操作失败,请参阅 加载ASDF。
确保软件安装在 ASDF 可以找到的地方。最简单的方法是将所有 Lisp 代码放在 ~/common-lisp/(从ASDF 3.1.2开始)或 ~/.local/share/common-lisp/source/(适用于ASDF 2及以后版本,或者如果您想将源代码保存在一个隐藏目录中) 的子目录中。。有关更多细节,请参阅 配置 ASDF 以找到您的系统。
使用 (asdf:load-system "my-system") 加载你的系统。请参阅使用ASDF。
制作自己的 ASDF 系统:
如上所述,加载并配置 ASDF。
再次在 ASDF 可以找到它的位置为您的系统创建一个新目录 my-system/。 在其他条件相同的情况下,最简单的位置可能是 〜/common-lisp/my-system/。 请参阅 配置 ASDF 以找到您的系统。
创建一个 ASDF 系统定义,列出系统的依赖项、组件及其相互依赖项,并将其放入 my-system.asd 中。这个文件必须与您的系统同名,全部小写。参见使用 defsystem 定义系统。
使用 (asdf:load-system "my-system") 确保一切正常。请参阅 使用ASDF。
3. 加载 ASDF(Loading ASDF)
3.1加载预装的ASDF(Loading a pre-installed ASDF)
推荐加载 ASDF 的方法是通过:
(require"asdf")
现在,所有积极维护的 Lisp 实现都包括 ASDF 3 的副本,您可以使用 Common Lisp 的 require 函数以这种方式加载该文件。
如果您使用的实现没有提供最新的 ASDF 3,我们建议您升级它。 如果出于某种原因您不想升级它,建议您更换实现的 ASDF。 请参阅替换实现的 ASDF。 如果所有其他方法均失败,请参阅下面的从源代码加载 ASDF。
如果你使用的是一个积极维护的实现,但它没有提供一个最新的、足够稳定的 ASDF 版本,你也可以向你的 Lisp 供应商发送一个bug 报告并抱怨它——或者你可以自己修复这个问题,如果它是自由软件的话。
在撰写本手册时,以下实现以这种方式提供了ASDF 3:ABCL,Allegro CL,CLASP,Clozure CL,CMUCL,ECL,GNU CLISP,LispWorks,MKCL,SBCL。 以下实现仅提供ASDF 2:MOCL,XCL。 以下实现不提供ASDF:Corman CL,GCL,Genera,MCL,SCL。 后者的实现未得到积极维护(可能是GCL除外); 如果其中一些再次发布,则它们可能包括 ASDF 3。
为了提供最大的便利,您可能希望在每次启动 Lisp 实现时都加载 ASDF,例如通过从启动脚本中加载它或转储自定义内核来进行加载-请查看 Lisp 实现的手册以获取详细信息。 SLIME 具有 slime-asdf 设计,这使 ASDF 的使用更加轻松。
3.2检查是否已加载ASDF(Checking whether ASDF is loaded)
要检查是否正确加载了 ASDF,您可以运行以下:
(asdf:asdf-version)
如果返回字符串,则为当前安装的 ASDF 版本。 如果该版本是最新版本(例如3.1.2或更高版本),则可以直接跳至下一章:请参阅 配置ASDF。
如果出现错误,则说明未加载 ASDF,或者您使用的 ASDF 版本非常旧,并且需要安装 ASDF 3。
要更精确地检测新旧版本,请参阅如何检测ASDF版本?
如果您遇到 ASDF 的问题,请在与我们联系并提出问题之前,尝试使用以下方法升级到最新发行的版本。
3.3升级 ASDF(Upgrading ASDF)
如果您的实现已经提供了 ASDF 3 或更高版本(它应该提供),但是您想要一个比您的实现提供的更最新的 ASDF 版本,那么您只需确保最近的 ASDF 安装在配置的路径中,就像任何其他系统一样。我们建议您下载一个正式的 tarball 或者从 git 检出一个版本到~/common-lisp/asdf/。(请参阅 配置 ASDF 以找到您的系统)。
一旦 ASDF 的源代码被安装,你不需要任何额外的步骤来加载它,只需要 (require "asdf"):ASDF 3 会自动查看其自身的更新版本是否可用。 它可以编译其他任何东西。
如果您的实现无法提供 ASDF 3 或更高版本,请参阅替换实现的 ASDF。
3.4替换实现的 ASDF(Replacing your implementation’s ASDF)
现在,所有维护的实现都在最新版本中提供 ASDF 3。 如果没有,我们建议您升级它。
现在,如果您坚持使用未提供 ASDF 或未提供旧版本的旧实现,我们建议将最新的 ASDF(如下所述)安装到您的实现的安装目录中。 因此,您修改后的实现现在将提供 ASDF3。这需要适当的写权限,并且可能需要以系统管理员身份执行。
ASDF 源存储库包含一个工具,可帮助您升级实现的 ASDF。 您可以从 shell 命令行将其作为 tools/asdf-tools install-asdf lispworks 调用(您可以在其中用相关实现的名称替换 lispworks),也可以从 Lisp REPL 中执行 (load "tools/install-asdf.lisp")。
该脚本适用于Allegro CL,Clozure CL,CMU CL,ECL,GCL,GNU CLISP,LispWorks,MKCL,SBCL,SCL,XCL。 它不适用于 ABCL,Corman CL,Genera,MCL和MOCL。 幸运的是,ABCL 通常是最新的,不需要该脚本。 GCL 需要一个非常新的版本,并且没有经过太多测试。 无论如何,Corman CL,Genera,MCL都是过时的。 MOCL不完整。
3.5从源代码加载 ASDF(Loading ASDF from source)
如果您编写的构建脚本必须保持对旧机器的可移植性,而旧实现不能确保已经升级或修改以提供最近的 ASDF,那么您可能必须在某处安装文件 asdf.lisp 并加载以下文件:
(load"/path/to/your/installed/asdf.lisp")
使用 ASDF 通常只需要一个文件 asdf.lisp。
您可以从 ASDF 网站上最新发行版的 tarball 中提取此文件。 如果您敢于并愿意报告错误,则可以从其 git 信息库中获取最新,最好的 ASDF 版本。 请参阅获取最新版本。
对于最初尝试仅通过 require 尝试使用 ASDF 的脚本,并且如果它们一开始没有成功尝试进行艰难的加载尝试,请参见随 ASDF 源存储库分发的 tools/load-asdf.lisp 或代码cl-launch。
4.配置 ASDF(Configuring ASDF)
对于标准用例,ASDF 应该是开箱即用的。我们建议您浏览关于配置 ASDF 的章节,以找到您的系统,并选择最适合您的安装 Lisp 软件的方法。然后直接跳过看 使用ASDF。这可能就足够了。您不太可能需要担心 ASDF 存储目标文件的方式,并且重置 ASDF 配置通常只在极端情况下需要。
4.1 配置 ASDF 以查找您的系统(Configuring ASDF to find your systems)
为了编译和加载系统,必须配置ASDF以查找包含系统定义的 .asd 文件。
从最简单到最复杂的设置,可以使用多种不同的技术来设置 ASDF:
将您的所有系统放在以下标准位置之一:
~/common-lisp/
~/.local/share/common-lisp/source/.
如果在这里安装软件,则不需要进一步配置。然后你就可以跳到下一部分了。参见加载系统。
如果你正在使用一些工具来安装软件(如Quicklisp),该工具的作者应该已经配置了 ASDF。
如果您对如何在磁盘上布置软件有更具体的要求,则配置ASDF在何处找到系统的首选方法是源注册工具,这在本手册的单独章节中进行了详细说明。 请参阅 控制ASDF在哪里搜索系统。
首先创建目录 ~/.config/common-lisp/source-registration.conf.d/;在这里创建一个文件,可以使用您选择的任何名称,但类型为 conf,例如 50-luser-lisp.conf;在这个文件中,添加以下代码行来告诉 ASDF 递归地扫描 /home/luser/lisp/ 下的所有子目录,以查找 .asd 文件(:tree "/home/luser/lisp/")
到这就够了。您可以将 /home/luser/lisp/ 替换为您想要安装源代码的任何位置。如果您使用默认的 ~/common-lisp/,并且您的实现提供了 ASDF 3.1.2或更高版本,那么实际上不需要指定任何内容。如果您的实现提供了一个早期的 ASDF 3 变体,那么您可能希望指定 (:tree (:home "common lisp/")) 用于引导目的,然后在 ~/common-lisp/ASDF/ 下安装一个最近的 ASDF 源树。
如果您喜欢使用 “link farm”,它比递归遍历使用起来速度更快,但管理成本更高,例如在 /home/luser/.asd-link-farm/中,则可以(或另外)创建一个文件 42-asd-link-farm.conf,包含以下行:(:directory "/home/luser/.asd-link-farm/")
首次尝试查找系统时,ASDF 会自动读取您的配置。 如有必要,可以使用以下命令重置源注册表配置:
(asdf:clear-source-registry)
在早期版本的 ASDF 中,系统源注册表使用全局变量 ASDF:*central registry* 配置。有关这方面的更多细节,请参阅下面的部分,配置 ASDF 以找到您的系统-旧样式。除非您需要理解这一点,否则直接跳到配置 ASDF 存储目标文件的位置。
4.2配置 ASDF 来找到你的系统-旧样式(Configuring ASDF to find your systems — old style)
初学者可以跳过此部分。初学者请不要使用 central-registry,也不要指导初学者使用 central-registry。
配置 ASDF 来查找系统的旧方法是将目录路径名推到变量 asdf:*central-registry*。
在加载 ASDF 3 或更高版本之后,但在首次尝试使用它之前,必须配置此变量。 这种 ASDF 的加载和配置必须作为某些初始化脚本的一部分进行: 通常是您维护的用于构建项目的脚本,或者是实现的初始化脚本(例如,SBCL的 ~/.sbclrc)。
另外,如果您使用的是旧版 ASDF 2 或更早版本来加载 ASDF 3 或更高版本,那么在加载旧版 ASDF 之后,您的脚本必须首先配置 central-registry,以告诉 ASDF 1 或 2 在哪里找到 ASDF 3, 然后向 ASDF 3 加载例如 (asdf:operate 'asdf:load-op "asdf"),然后再次配置 central-registry,因为升级时 ASDF 3 不会保留来自 ASDF 2 的中央注册表。 您可能应该改为使用将保留的 source-registry(除非您使用参数手动调用 asdf:initialize-source-registry,在这种情况下,您确实必须再次执行此操作)。 但是,如果您使用的是旧版 ASDF 2,我们强烈建议您改为升级实现,或者用较新的版本覆盖旧版 ASDF 安装: 请参阅 替换ASDF实现。
在 asdf 2 或 asdf 3 中,asdf:*central registry* 默认为空,但仍然支持与 asdf 1 的兼容性。使用时,它优先于上面的源注册表。
例如,假设您希望 ASDF 查找 .asd 文件 /home/me/src/foo/foo.asd。 在您的 Lisp 初始化文件中,您可以具有以下内容:
(require"asdf")
(push"/home/me/src/foo/"asdf:*central-registry*)
注意后面的斜杠:当搜索一个系统时,ASDF 将计算 central-registry 的每个条目,并将结果强制到一个路径名。必须使用末尾的目录名分隔符来告诉 Lisp 您正在使用的是目录而不是文件。如果您忽略了它,那么 ASDF 很可能会在 /home/me/src/ 中查找,而不是像您预期的那样在 /home/me/src/foo/ 中查找,并且无法找到您的系统定义。现代版本的 ASDF 会发出一个错误,并向您提供从 central-registry 中删除这些条目。
通常,.asd 文件很多,一个常见的习惯用法是将指向一个人所有 .asd 文件的符号链接放在一个公共目录中,然后将该目录推送到 asdf:*central-registry*, 而不是推送每个单独的系统目录。
ASDF知道遵循符号链接到系统的实际位置。
例如,如果 #p"/home/me/cl/systems/" 是 *central-registry* 的一个元素,您可以设置系统 foo 如下:
$cd/home/me/cl/systems/
$ln-s~/src/foo/foo.asd.
这种配置 ASDF 的旧样式不推荐给新用户,但是它支持给老用户,以及那些想要用简单的方法通过编程控制添加到ASDF搜索路径的目录的用户。
4.3配置 ASDF 存储目标文件的位置(Configuring where ASDF stores object files)
ASDF允许您配置存储目标文件的位置。这里提供了合理的默认值,你通常不必担心它。
这使得相同的源代码存储库可以在几个公共 Lisp 实现的几个版本之间共享,可以在几个使用不同编译选项的用户之间共享,也可以在共享源目录上缺少写权限的用户之间共享。这还可以防止源目录与 object/fasl 文件混杂在一起。
从 ASDF 2 开始,将 asdf-output-translations 功能添加到 ASDF 本身。 此功能控制目标文件将被存储的位置。 本手册的一章“控制ASDF在何处保存已编译的文件”中对此功能进行了详细说明。
请注意,在 ASDF 2 之前,其他 ASDF 附加组件提供了相同的功能,但每种功能都具有微妙的不同和不兼容的方式:ASDF-Binary-Locations, cl-launch, common-lisp-controlle。 现在不再需要 ASDF-Binary-Locations,因此不应使用。 cl-launch 3.000 和 common-lisp-controller 7.2 已更新,以将目标文件放置委托给 ASDF。
4.4重置ASDF配置(Resetting the ASDF configuration)
当您转储和恢复一个映像,或当您调整您的配置,您可能想要重置 ASDF 配置。为此,您可以使用以下功能:
Function: clear-configuration
撤消有关源注册表或输出转换的任何 ASDF 配置。
默认情况下,这个函数被推送到 uiop:*image-dump-hook* 上,这意味着如果你使用 uiop:dump-image 保存一个 image,或者通过 asdf:image-op 和 asdf:program-op 保存一个 image,它会被自动调用以清除你的配置。如果由于某种原因你喜欢调用实现的基本功能,请务必手动调用清除配置,或将其推送到实现等效的 uiop: *image-dump-hook*, 例如 SBCL 中的sb-ext:*save-hooks*,或 CMUCL 和 SCL 上的 ext:*before-save-initializations* 等。
5.使用 ASDF(Using ASDF)
5.1加载系统(Loading a system)
通过运行以下 Lisp 格式来加载(和编译,如果需要的话)foo 系统:
(asdf:load-system:foo)
在一些实现中(参见便利功能),ASDF 与 cl:require 工具挂钩,你可以使用:
(require:foo)
请注意,系统的规范名称是小写的字符串。系统名称也可以指定为符号(包括关键字符号)。如果一个符号作为参数给出,则将其包忽略,将其 symbol-name 接受并转换为小写。对于要在其中找到系统的任何文件系统 :name 必须使 make-pathname 的合适值。
使用小写字母作为规范是非常规的,但是经过考虑后才选择的结果。 我们支持的文件系统类型可以使用小写字母作为常规大小写(Unix,Mac,Windows),也可以将小写字母无声转换为大写字母(lpns)。
5.2便利功能(Convenience Functions)
ASDF 为最常见的系统操作提供了三个命令:加载系统(load-system),编译系统(compile-system)和测试系统(test-system)。
ASDF 还提供了 require-system,这是加载系统(load-system)的一种变体,它跳过了已加载的加载系统。 有时这很有用,例如,以避免重新加载预先装入 lisp 实现中的库。
ASDF 还提供了 make,一种允许系统开发者为他们的系统选择默认操作的方法。例如,创建了用于格式化特定文档的系统的开发人员可能会将文档格式化设置为 make 调用的默认操作,而不是加载操作。如果系统开发人员没有在系统定义中指定,默认操作将是加载。
因为 ASDF 是用于定义组件上的操作的可扩展系统,所以它还提供了通用的函数操作,所以您可以在默认操作之外任意地对系统进行操作。(在交互式 REPL 中,用户通常使用其较短的别名 oos,表示系统上操作,这是从 mk-defsystem 继承来的名称。)除了编译、加载和测试之外,您可以在任何时候使用 operate 。
Function: load-system system &rest keys &key force force-not verbose version &allow-other-keys
load-op 中 system 和任何提供的关键字参数一起使用。推荐使用 load-system 是将 system 加载到当前映像中的常规建议方法。
Function: compile-system system &rest keys &key force force-not verbose version &allow-other-keys
compile-op 操作中 system 和任何提供的关键字参数一起使用。 这将确保 system 中的所有文件都已编译,但不一定要在当前映像中加载其中的任何文件。 在大多数系统上,它将不会加载当前映像中的所有已编译文件。 存在此功能是为了与 load-system 对称,但不建议使用此功能,除非您正在编写构建脚本并知道自己在做什么。 但是,您可能对 program-op 而不是 compile-op 感兴趣。
Function: test-system system &rest keys &key force force-not verbose version &allow-other-keys
test-op 操作中 system 和任何提供的关键字参数一起使用。请参阅 test-op。
Function: make system &rest keys &key &allow-other-keys
使你的系统做“正确的事情”。从 ASDF 3.1 开始,这个 make 功能也可用。默认行为就像通过 load-system 加载系统一样;但是系统作者可以在他们的系统定义中覆盖这个默认值,他们可以指定一个替代操作作为他们系统的预期用途,使用 defsystem 形式中的 :build-operation 选项(参见 Build-operation),以及该操作的预期输出路径名 :build-pathname。这个功能是实验性的,基本上没有经过测试。使用风险自负。
Function: require-system system &rest keys &key &allow-other-keys
按照 cl:require 的精神,require-system 跳过对已经加载的系统的任何更新。它通过使用一个关键字选项来调用 load-system,该选项不包括已经加载的系统。对于积极维护的自由软件实现(即ABCL、Clozure CL、CMUCL、ECL、GNU CLISP、MKCL和SBCL的最新版本),一旦 ASDF 本身被加载,cl:require 也可以加载 ASDF 系统,方法是返回到 require-system 来处理实现不认可的模块名。(但是,需要注意的是,require-system 并不依赖于 cl:require ;这将引入一个“有趣的”可能被打破的无限循环)
cl:require 和 require-system 适用于加载在当前编程会话期间没有被修改的代码。cl:require 将特殊加载实现提供的扩展模块;而 require-system 不会,除非它们也被定义为系统,SBCL和MKCL就是这样做的。require-system 也可以用来加载任何数量的用户尚未开发或调试的 ASDF 系统,对于这些系统,之前安装的版本被认为是满意的; cl:require 上述实现中的将委托给 require-system,也可以加载它们。但对于您正在积极开发的代码,调试,或以其他方式修改,您应该使用 load-system, 因此 ASDF 将选择您的修改并以可移植的方式重新构建已修改的文件以及所有依赖于它们的文件(除非是明确要求的系统或其依赖项,否则 ASDF 本身不会构建任何东西)。
Function: already-loaded-systems
返回到目前为止已经成功加载的系统的名称列表。
5.3继续(Moving on)
这就是使用 ASDF 加载别人编写的系统所需要知道的全部内容。本手册的其余部分涉及为您自己编写的普通 Lisp 软件编写系统定义,包括如何扩展 ASDF 来定义新的操作和组件类型。
6.使用 defsystem 定义系统(Defining systems with defsystem)
本章介绍如何使用 ASDF 定义系统和开发软件。
6.1defsystem 形式(The defsystem form)
本节以系统定义的示例开始,然后给出 defsystem 的完整语法。
让我们看一个简单的系统。这是一个完整的文件,应保存为 hello-lisp.asd(以便 ASDF 在被命令在名为 “hello-lisp” 的系统上运行时可以找到它)。
;;这里允许使用常规的Lisp注释
(defsystem"hello-lisp"
:description"hello-lisp: a sample Lisp system."
:version"0.0.1"
:author"Joe User
:licence"Public Domain"
:depends-on("optima.ppcre""command-line-arguments")
:components((:file"packages")
(:file"macros":depends-on("packages"))
(:file"hello":depends-on("macros"))))
关于这个例子的一些注意事项:
defsystem 定义了一个名为 hello-lisp 的系统,该系统包含三个源文件:packages.lisp,macros.lisp 和 hello.lisp。
Lisp 源文件隐含 .lisp 后缀。源文件与具有系统定义的 .asd 文件位于同一目录中。
文件 macros 取决于 packages(大概是因为它所在的程序包是在 packages 中定义的),而文件 hello 则取决于 macros(因此可传递地取决于 packages)。这意味着 ASDF 将在开始编译文件 hello 之前先编译并加载 packages,然后再 macros。
该示例系统在其他两个系统上具有外部依赖关系:optima.ppcre(提供用于匹配正则表达式的友好接口)和 command-line-arguments(提供从 shell 命令行解析传递参数的方法)。要使用此系统,必须配置 ASDF 来查找这些系统的已安装副本。它将在尝试编译和加载 hello-lisp 之前加载它们。
该系统还定义了一堆元数据。虽然可以选择定义这些字段(以及其他字段,例如 :bug-tracker,:mailto,:long-name,:long-description,:source-control),但强烈建议定义以下字段 :description,:version ,:author 和 :licence,尤其是如果您打算最终将软件包含在 Quicklisp 中。
确保您知道如何解析 :version数字!目前仅接受以句点分隔的非负整数。请参阅 版本说明符。
该文件包含一个形式,即 defsystem 声明。没有 in-package 形式,没有 asdf: 封装前缀,什么也没有。只是一个赤裸的 defsystem 形式。这是我们的建议。任意 Lisp 代码都可以使用更复杂的系统定义文件,但是我们建议您尽可能简化它。这将使您的系统定义更健壮,并且更适合未来。
这就是定义简单系统所需要知道的全部内容。下一个示例要复杂得多,它让您大致了解如何完成更复杂的事情。但是,由于它最终是任意的 Lisp 代码,因此没有任何漏洞可言。
6.2 一个更复杂的例子(A more involved example)
让我们通过一个稍微复杂的示例来说明 defsystem 的更多使用情况:
(in-package:asdf-user)
(defsystem"foo"
:version(:read-file-form"variables":at(32))
:components
((:file"package")
(:file"variables":depends-on("package"))
(:module"mod"
:depends-on("package")
:serialt
:components((:file"utils")
(:file"reader")
(:file"cooker")
(:static-file"data.raw"))
:output-files(compile-op(oc) (list"data.cooked"))
:perform(compile-op:after(oc)
(cook-data
:in(component-pathname(find-componentc"data.raw"))
:out(first(output-filesoc)))))
(:file"foo":depends-on("mod"))))
(defmethodaction-description
((ocompile-op) (c(eql(find-component"foo""mod"))))
"cooking data")
以下是关于这个例子的一些注意事项:
该文件的主要作用是定义系统 foo。它还包含其他 Lisp 表单,我们将在下面进行检查。
除了 Lisp 源文件,该系统还包含一个名为 “mod” 的 :module 组件,该组件是三个 Lisp 源文件 utils.lisp,reader.lisp,cooker.lisp 和 data.raw 的集合。
请注意,与 Lisp 源文件不同,:static-file 没有隐式文件类型。
该文件将位于主代码目录的名为 mod/ 的子目录中(此位置可能已被覆盖为同一目录或其他子目录中;请参见 defsystem 语法 中有关 :pathname 选项的讨论)。
:serial t 表示 mod 的每个子组件都取决于先前的组件,因此 cooker.lisp 取决于 reader.lisp 且取决于 utils.lisp。另外 data.raw 依赖于所有这些文件,但这无关紧要,因为它是静态文件;另一方面,如果它首先出现,则在修改数据时将重新编译所有 Lisp 文件,这在这种情况下可能不是所希望的。
method 形式的标记为定义特定组件上的方法提供了一种捷径。这部分
:output-files(compile-op(oc) (list"data.cooked"))
:perform(compile-op:after(oc)
(cook-data
:in(component-pathname(find-componentc"data.raw"))
:out(first(output-filesoc))))
具有下面相似的效果:
(defmethodoutput-files((ocompile-op) (c(eql...)))
(list"data.cooked"))
(defmethodperform:after((ocompile-op) (c(eql...)))
(cook-data
:in(component-pathname(find-componentc"data.raw"))
:out(first(output-filesoc))))
在那里,有问题的部件。在这种情况下,会展开成这个样子:
(find-component"foo""mod")
有关此类格式的语法的更多详细信息,请参见defsystem语法。有关这些方法的更多详细信息,请参见ASDF的对象模型中的操作。
还有另外一个具有类似效果的 defmethod,因为 ASDF(自ASDF 3.1.5起)无法接受上述用于 action-description 的内联方法,而仅支持不推荐使用的 explain 接口。
在这种情况下,这些方法描述了此模块如何定义代码,然后该代码用于处理某些数据。
重要的是,ASDF 被告知了数据 cooker 使用的输入和输出文件,并且为了确保每个人都同意,cooking 功能明确使用 ASDF 来访问输入和输出数据的路径名。
该文件以 (in-package :asdf-user) 开头,但实际上是多余的,不是必需的,也不建议这样做。 但是,更复杂的情况(也不建议使用)可以使用 in-package 形式。
实际上,ASDF 不会简单地用 cl:load 加载 .asd 文件,您也不应该这样做。当您在系统上操作时,应该让 ASDF 查找并加载它们。如果您必须加载 .asd 文件,请使用 asdf 使用的相同函数 asdf:load-asd。除此之外,它已经将 *package* 绑定到 asdf-user。SLIME 的最新版本(2013-02及更高版本)在使用 slime-asdf contrib 时 C-c C-k 时知道这样做。
如果你想让事情变得简单,你就不应该使用 in-package 形式。当您打算在 .asd 文件中定义新类、函数、变量、宏等时,才使用 in-package(并且在此之前是 defpackage),并希望因此避免名称冲突。旧版 ASDF 手册建议在 .asd 文件中使用这样的习惯用法,但 ASDF 3,我们建议您不要这样做了,而不是定义任何 ASDF 扩展在自己的系统中,你可以声明一个依赖, 使用 :defsystem-depends-on。参见defsystem 语法。
更普遍的是,在 .asd 文件中你总是可以使用来自软件包 asdf, common lisp 和 uiop 的符号, 最重要的是包括 defsystem。因此,在 .asd 文件中使用带有包前缀的 asdf:defsystem 符号是多余的,而且很不得体。只需使用 (defsystem…), 仅当以某种方式从尚未使用 ASDF 软件包的软件包中动态生成系统定义时,才对其加上前缀。
asdf-user 实际上只在 ASDF 3 开始可用,你不应该关心与 ASDF 2 的兼容性。我们不再支持 ASDF 2,我们建议你也不要。
从 ASDF 3.1 开始,asdf-user 使用 uiop,而在 ASDF 3 的早期变体中,它只使用 uiop/package。我们建议您使用包前缀 UIOP: 作为 UIOP 函数的前缀,或者确保您的系统 :depends-on ((:version "asdf" "3.1.2")) ,或者有 #-asdf3.1 (error "MY-SYSTEM requires ASDF 3.1.2")。
最后,我们省略了大部分元数据,但是展示了如何让 ASDF 从源文件中自动提取系统版本。在本例中,第 4 个表单的第 3 个子表单(注意,Lisp 使用基于 0 的索引,而英语使用基于 1 的索引)。第四种形式大概是这样的 (defparameter *foo-version* “5.6.7”)。
6.3defsystem 语法(The defsystem grammar)
system-definition:=(defsystemsystem-designatorsystem-option*)
# 见 6.3.1 系统指示符(System designators)
system-designator:=simple-component-name
|complex-component-name
# NOTE: 不允许下划线.
# 见 6.3.2 简单组件名称(Simple component names (simple-component-name))
simple-component-name:=lower-casestring|symbol
# 见 6.3.3 复杂组件名称(Complex component names)
complex-component-name:=string|symbol
system-option:=:defsystem-depends-onsystem-list# 见 6.3.6 Defsystem 依赖(Defsystem depends on)
|:weakly-depends-onsystem-list# 见 6.3.8 弱依赖(Weakly depends on)
|:classclass-name# 见 6.3.5 系统类的名字(System class names)
|:build-pathnamepathname-specifier# 见 6.3.9 路径名说明符(Pathname specifiers)
|:build-operationoperation-name# 见 6.3.7 构建操作(Build-operation)
|system-option/asdf3
|module-option
|option
# 这些只在ASDF 3(实际上是它的alpha版本2.27)之后才可以使用。
system-option/asdf3:=:homepagestring
|:bug-trackerstring
|:mailtostring
|:long-namestring
|:source-controlsource-control
|:versionversion-specifier
|:entry-pointobject# 见 6.3.17 入口点(Entry point)
source-control:=(keywordstring)
module-option:=:componentscomponent-list
|:serial[t|nil]# 见 6.3.14 串行依赖性(Serial dependencies)
option:=:descriptionstring
|:long-descriptionstring
|:authorperson-or-persons
|:maintainerperson-or-persons
|:pathnamepathname-specifier# 见 6.3.15 源位置(Source location (:pathname))
|:default-component-classclass-name
|:performmethod-form
|:explainmethod-form
|:output-filesmethod-form
|:operation-done-pmethod-form
|:if-featurefeature-expression# 见 6.3.16 if-feature 选项(if-feature option)
|:depends-on(dependency-def*)
|:in-order-to(dependency+)
person-or-persons:=string|(string+)
system-list:=(simple-component-name*)
component-list:=(component-def*)
component-def:=(component-typesimple-component-nameoption*)
# 见 6.3.4 组件类型(Component types)
component-type:=:module|:file|:static-file|other-component-type
other-component-type:=symbol-by-name# see Component types
# 这是在 :depends-on 中使用的,而不是在 :in-order-to 中使用的 "dependency"
dependency-def:=simple-component-name
|( :featurefeature-expressiondependency-def)# 见 6.3.12 特征依赖(Feature dependencies)
|( :versionsimple-component-nameversion-specifier)# 见 6.3.10 版本说明符(Version specifiers)
|( :requiremodule-name)
# "dependency" 在 :in-order-to 中使用,而不是 "dependency-def"
dependency:=(dependent-oprequirement+)
requirement:=(required-oprequired-component+)
dependent-op:=operation-name
required-op:=operation-name
# 注意:路径名应该全小写,没有下划线,但允许使用连字符。
pathname-specifier:=pathname|string|symbol
version-specifier:=string
|( :read-file-formpathname-specifierform-specifier?)
|( :read-file-linepathname-specifierline-specifier?)
line-specifier:=:atinteger# base zero
form-specifier:=:at[integer|(integer+) ]
method-form:=(operation-namequallambda-list&restbody)
qual:=method-qualifier?
method-qualifier:=:before|:after|:around
feature-expression:=keyword
|( :andfeature-expression*)
|( :orfeature-expression*)
|( :notfeature-expression)
operation-name:=symbol
6.3.1系统指示符(System designators)
系统指示符(System designators)可以是简单的组件名称,也可以是复杂的组件名称。
6.3.2简单组件名称(Simple component names (simple-component-name))
简单的组件名称可以写成字符串或符号。
使用字符串时,只使用小写。
符号将被解释为对作为其符号名的字符串的方便速记,并转换为小写字母。换句话说,符号可以是简单组件名称的指示符,但简单组件名称本身是字符串。
永远不要在组件名称中使用下划线,无论是字符串还是符号。
不要在简单的组件名称中使用斜杠("/")。斜杠表示复杂的组件名称;见下文。不正确地使用斜杠将导致 ASDF 发出警告。
如果在组件名称中混合大小写或包含下划线,违反这些约束,可能会导致无法找到系统或组件,因为组件名称被解释为文件名。使用逻辑路径名配置 ASDF 的用户肯定会出现这些问题。
6.3.3复杂组件名称(Complex component names)
复杂组件名称是主名称,后跟斜杠,后跟辅助名称。这使程序员可以将多个系统定义放在单个 .asd 文件中,同时仍允许 ASDF 查找这些系统。
复杂系统的主名称必须与 .asd 文件的名称相同。
文件 foo.asd 将包含系统 "foo" 的定义。 但是,它也可能包含辅助系统的定义,例如 "foo/test","foo/docs"等。 ASDF 将“知道”,如果您要求它加载系统 "foo/test",则应在 foo.asd 中查找该系统的定义。
6.3.4组件类型(Component types)
组件类型名称(即使表示为关键字)也将在当前包和 asdf 包中(如果在当前包中找不到)按名称查找。 因此,在当前软件包 my-system-asd 中,组件类型 my-component-type 可以指定为 :my-component-type 或 my-component-type。
system 及其子类不允许作为此类子组件的组件类型。
6.3.5系统类的名字(System class names)
系统类名称的查找方式与 Component 类型(请参见上文)相同,只是只允许 system 及其子类。 通常,除非使用在某些 ASDF 扩展中定义的非标准系统类(通常通过 DEFSYSTEM-DEPENDS-ON 加载),否则无需指定系统类名称,请参见下文。 对于 ASDF 软件包中的此类名称,建议使用关键字符号指定 :class 选项,例如
:class:MY-NEW-SYSTEM-SUBCLASS
这种做法将确保避免软件包名称冲突。 否则,符号 MY-NEW-SYSTEM-SUBCLASS 在从 :defsystem-depends-on 加载的 ASDF 扩展名中导出之前,将被读入当前包中,从而导致当前包中的名称冲突。
6.3.6Defsystem 依赖(Defsystem depends on)
defsystem 的 :defsystem-dependent-on 选项允许程序员指定另一个 asdf 定义的系统或必须在处理系统定义之前加载的系统集。通常这用于加载系统定义中使用的 ASDF 扩展。
6.3.7构建操作(Build-operation)
defsystem 的 :build-operation 选项允许程序员指定将要应用的操作,以在系统上运行 make(请参见make)时代替 load-op。 选项值应该是操作的名称。 例如: :build-operation doc-op
此功能是实验性的,未经测试。 使用风险自负。
6.3.8弱依赖(Weakly depends on)
我们不建议您使用此功能。如果你想写一个系统 foo 弱依赖于一个系统栏,我们建议您改为以参数方式编写系统 foo,并提供一些特殊的变量和 / 或挂钩来专门化其行为;那么您应该编写一个系统 foo + bar 来将事物挂钩。
defsystem 的(不建议使用):weakly-depends-on 选项允许程序员指定 ASDF 应该尝试加载但不必加载才能成功的另一个 ASDF 定义的系统或系统集。通常,如果存在许多系统,这些系统可以提供附加功能,但对于基本功能来说,这些系统不是必需的,那么就使用这种方法。
当前,尽管已将其指定为仅是 defsystem 的选项,但此选项在任何组件中都可以接受,但可能仅在 defsystem 级别有意义。提醒程序员除 defsystem 级别外,不要使用此组件选项,因为这种异常行为可能会在没有警告的情况下被删除。
6.3.9路径名说明符(Pathname specifiers)
路径名称说明符(pathname-specifier)可以是路径名、字符串或符号。通常情况下,当没有为组件提供路径名说明符时,将使用组件名称本身。
如果给定了一个字符串(通常情况),该字符串将被解释为 unix 样式的路径名,其中 / 字符将被解释为目录分隔符。通常,使用 unix 风格的相对路径名(即不以 /开头,而不是绝对路径名);它们相对于父组件的路径。最后,根据 component-type,路径名可能被解释为文件或目录,如果它是文件,则可能添加与组件类型对应的文件类型,否则将从字符串本身提取它(如果适用)。
例如,component-type :module 需要一个目录路径名,因此字符串 "foo/bar" 将被解释为路径名 #p"foo/bar/"。 另一方面,component-type :file 需要一个 lisp 类型的文件,因此字符串 "foo/bar" 将被解释为路径名 #p"foo/bar.lisp",而字符串 "foo/bar.quux" 将解释为路径名 #p"foo/bar.quux.lisp"。 最后,component-type :static-file 需要一个没有指定类型的文件,因此字符串 "foo/bar" 将被解释为路径名 #p"foo/bar",而字符串 "foo/bar.quux" 将被解释为路径名 #p"foo/bar.quux"。
ASDF将字符串 ".." 解释为路径名目录组成词 :back,合并后该词返回目录层次结构中的上一层。
如果给定了一个符号,它将被转换为字符串,并在过程中转换为小写。符号的下划线是非常规的,但是经过深思熟虑后选择的。我们支持的文件系统要么以小写作为常规大小写(Unix、Mac、Windows),要么以无声方式将小写转换为大写(lpns),因此这比尝试使用 :case :common作为 make-pathname 的参数更有意义,据报告,该参数在某些实现中不起作用。
请避免在系统名称或组件(模块或文件)名称中使用下划线,因为下划线与逻辑路径名不兼容(参见使用逻辑路径名)。
可以给定路径名对象以覆盖组件的路径。 通常使用 reader 宏(例如 #p 或 #。(make-pathname ...))指定此类对象。 但是请注意,#p...是一个简写。#.(parse-namestring ...),并且 parse-namestring 的行为是完全不可移植的,除非您使用 Common Lisp logical-pathname,它们本身还涉及其他不可移植的行为(请参阅使用逻辑路径名)。 使用 #.(make-pathname ...) 创建的路径名通常可以通过上述字符串语法更轻松地完成。真正需要路径名对象的唯一情况是重写给定组件的组件类型默认文件类型。因此,仅应很少使用路径名对象。 不幸的是,ASDF 1 以前不能正确地支持将组件名称解析为指定目录路径的字符串,因此必须使用繁琐的 #.(make-pathname ...) 语法替代。 要使用 read-time 计算 (eval `(defsystem ... ,pathname ...)).
请注意,在指定 pathname 对象时,ASDF 不会对受组件类型影响的路径名进行任何特殊解释,这与指定字符串路径名的过程不同。一方面,您必须小心地提供一个路径名,以正确地满足该组件类型所要求的任何约束(例如,以适当的类型命名一个目录或文件);另一方面,如果指定字符串,则可以绕过文件类型。
6.3.10版本说明符(Version specifiers)
版本说明符是以句点分隔的整数列表的字符串。 即,在该示例中,将 "0.2.1" 粗略地解释为(0 2 1)。 特别是,版本 "0.2.1" 与 "0.0002.1" 的解释相同,尽管后者不是规范的,并可能导致发出警告。 另外,"1.3" 和 "1.4" 严格来说都是 uiop:version< to "1.30",这完全不同于将版本字符串解释为十进制小数的情况。
除了是文字字符串之外 :version 参数可以是一个表达式,它可以使用以下普通专用语言解析为这样的字符串,而不是表示版本的字符串,它还可以是形式为 (:read-file-form
鼓励系统定义者为主版本、次版本和补丁级别使用形式 x.y.z 的版本标识符,在这些版本中,显著的 API 不兼容通过主版本号的增加来表示。
参见常见的组件属性。
6.3.11需求(Require)
使用实现本身的 require 方法加载 module-name。
最好使用 (:feature :implementation-name (:require module-name)) 而不是 #+implementation-name (:require module-name) 仅依赖于指定模块的特定实现来实现。 请参阅特征依赖。
6.3.12特征依赖(Feature dependencies)
特性依赖关系的形式是 (:feature feature-expression dependency),如果在解析系统定义时运行的 lisp 满足 feature-expression,则该依赖关系将被添加到系统的依赖关系中。如果不满足 feature-expression,则忽略特性依赖形式。
注意,这意味着 :feature 不能用于强制相关系统的特性依赖。也就是说,它不能用来要求为了加载系统定义而保持某个特性。例如,不能使用 (:feature :sbcl)来要求一个系统只能在 sbcl 上使用。
不要将特性依赖与过时的特性需求(feature requirement)(参见特性需求)或 if-feature 混淆。
6.3.13使用逻辑路径名(Using logical pathnames)
通常,我们不建议使用逻辑路径名,特别是对于 Common Lisp 的新手而言,则不建议这样做。 但是,我们确实支持之前版本使用逻辑路径名,这是它们的首选项。
要使用逻辑路径名,必须使用 #p"LOGICAL-HOST:absolute;path;to;component.lisp" 这样的语法为使用它的组件提供一个 pathname 对象作为 :pathname 说明符。
您只需为您的系统或某些顶级组件指定这样的逻辑路径名。子组件的相对路径名(使用名称的字符串语法指定)将正确地与其父组件的路径名合并。 但是,ASDF 语法中不直接支持逻辑路径名主机的规范,而 ASDF 语法中不以字符串的形式来指定路径名。
asdf-output-translation 转换层将避免试图解析和转换逻辑路径名。这样做的好处是,您可以自己定义要使用逻辑路径名工具的转换。缺点是,如果您没有定义这样的转换,那么任何使用逻辑路径名的系统在 asdf-output-translation 下的行为都会与您使用的其他系统不同。
如果您希望使用逻辑路径名,您必须在使用它们之前自己配置它们的转换。ASDF 目前不提供定义逻辑路径名转换的特定支持。
请注意,我们不建议使用逻辑路径名的原因是:(1)在使用逻辑路径名之前,没有可移植的方法来设置逻辑路径名;(2)逻辑路径名仅限于仅可移植地使用单个字符,数字和连字符。尽管您可以自己解决第一个问题,但描述如何在ASDF 支持的十五种实现中的每一种上进行描述远远超出了我们的文档说明。对于第二个问题,请注意,该限制是在 SBCL 上强制执行的,因此您不能轻而易举地违反该限制,而必须定义自己的某种编码,并添加单独的映射以命名不符合该限制的物理路径名。当您的 Lis 文件是一个较大项目的一部分时,通常会出现问题,在该项目中,通常使用包含受支持协议的版本号的方式来命名文件或目录,或者与使用不同编程编写的软件共享文件约定包括在路径名中使用下划线,点或 CamelCase 的语言。
6.3.14串行依赖性(Serial dependencies)
如果为模块指定了 :serial t 选项,则 ASDF 将为每个子组件添加依赖关系,以文本方式在其前面的所有子组件上。 就像通过 :depends-on 来完成一样。
:serialt
:components((:file"a") (:file"b") (:file"c"))
相当于
:components((:file"a")
(:file"b":depends-on("a"))
(:file"c":depends-on("a""b")))
6.3.15源位置(Source location (:pathname))
对于通过 defsystem 定义的系统,:pathname 选项在所有情况下都是可选的,通常是不必要的。在简单的情况下,源文件将在与系统相同的目录中找到,或者在模块的情况下,在与模块同名的子目录中找到。
更具体地说,ASDF 遵循一套复杂的规则,其设计目的是:
find-system 将从磁盘加载系统,并且其路径名默认存放于正确的位置。
如果用户将 .asd 文件加载到编辑器中并以交互方式重新运行该格式,则该路径名信息将不会被 *default-pathname-defaults*(可能位于其他位置)覆盖。
如果系统是第一次加载,它的顶级路径名将被设置为:
如果 *load-truename* 被绑定, 则是 host/device/directory
否则 *default-pathname-defaults*
如果系统被重新定义,顶级路径名将为
已更改(如果显式提供或从 *load-truename* 获得)(以便更新的源位置反映在系统定义中)
如果之前已经从 *default-pathname-defaults* 中设置,则更改
与之前一样,如果它之前被设置为 *load-truename*,而 *load-truename* 目前是未绑定的(这样开发人员可以在编辑器中运行 defsystem 表单,而不需要破坏它的源位置)
6.3.16if-feature 选项(if-feature option)
此选项允许您指定要计算的特性表达式,就像通过 #+ 有条件地在构建中包含组件一样。如果表达式为 false,则删除该组件以及指向它的任何依赖项。与使用在读取时展开的 #+ 相比,这允许您在组件层次结构中拥有一个对象,该对象可用于构建项目旁边的操作,并可被希望推断系统结构的外部代码访问。
程序员应该谨慎考虑何时使用 :if-feature。回想一下,ASDF 首先计算一个构建计划,然后执行该计划。ASDF 将检查一个特性是否在计划时出现,而不是在构建期间。因此,不能使用 :if-feature 来检查在构建过程中设置的特性。它只能用于在执行任何构建操作之前检查特性的状态。
这个选项是在ASDF 3中添加的。有关更多信息,请参见必备特性。
6.3.17入口点(Entry point)
:entry-point 选项允许开发人员指定由 program-op 创建的可执行程序的入口点。
调用 program-op 时,传递给此选项的表单将由 uiop:ensure-function 转换为函数,并绑定到 uiop:*image-entry-point*。通常会指定一个字符串,例如 "foo:main",以便可执行文件以 foo:main 函数开始。 请注意,改用符号 foo:main 可能不起作用,因为当 ASDF 读取 defsystem 格式时,foo 包不一定存在。有关程序操作的更多信息,请参见ASDF 的预定义操作。
6.3.18特性需求(feature requirement)
已在 ASDF 3.1 中删除了此要求。 请不要使用它。 在大多数情况下,:if-feature(请参阅if-feature 选项)将提供适当的替代。
用于确保缺少关键功能时,组件依赖关系链失败的功能要求。 与 :if-component-dep-fails 结合使用时,这提供了一种表达条件编译的回旋方式。
6.4.asd 文件中的其他代码(Other code in .asd files)
包含 defsystem 格式的文件是常规的 Lisp 文件,由加载执行。 因此,原则上您可以将所需的任何 Lisp 代码放入这些文件中。 但是,建议尽量减少这种形式,而是定义与 :defsystem-depends-on 一起使用的 defsystem 扩展。
但是,如果您坚持在 .asd 文件中包含代码,例如,检查和调整编译时环境,可能会在 *features* 中添加适当的特性。如果是这样,这里有一些约定,我们建议你遵循,这样用户可以控制 .asd文件中 Lisp 执行的某些细节:
任何有用的输出(警告和错误除外,这是条件系统要处理的)都应发送到标准 CL 流 *standard-output*,以便用户可以轻松控制 ASDF 操作的输出处理。
6.5包系统扩展(The package-inferred-system extension)
从 3.1.2 版本开始,ASDF 支持每个文件一个包的编程样式,其中每个文件都是其自己的系统,并且依赖关系是从 defpackage 形式或其变体 uiop:define-package 推导出来的。
在这种系统定义风格中,软件包名称映射到具有相同名称(小写字母)的系统,如果使用 :class package-inferred-system 定义系统,则以该名称开头的系统名称(使用斜杠) / 分隔符)是指在定义系统的文件系统层次结构下的文件。 例如,如果系统的 my-lib 定义在/foo/bar/my-lib/my-lib.asd 中。然后系统 my-lib/src/utility 将在文件 /foo/bar/my-lib/src/utility.lisp 中找到。
faslpath 和 quick-build 使得每个文件样式一个软件包流行,并且以更严格的软件包规范为代价,可能会产生更多可维护的代码。 lisp-interface-library 和其他一些库在 ASDF 本身(从ASDF 3开始)中使用了这种样式。
要使用此样式,请选择一个顶级系统名称,例如 my-lib,然后创建文件 my-lib.asd。 使用 defsystem 中的 :class :package-inferred-system 选项定义 my-lib。 例如:
;;本示例基于LISP-INTERFACE-LIBRARY的lil.asd。
#-asdf3.1 (error "MY-LIB requires ASDF 3.1 or later.")
(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(oc) (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))
在上面的代码中,第一个表单为检查我们使用的是 ASDF 3.1或更高版本,它提供了包推导系统。 这可能已经没有必要了,因为主要的 lisp 实现都不提供旧版本的 ASDF。
当提供软件包的 system/file 与软件包名不同(转换为小写)时,必须调用函数 register-system-packages 来注册 system 使用或提供的 package。
my-lib 层次结构下的每个文件都将以包定义开头。 支持格式 uiop:define-package 以及 defpackage。 ASDF 将从此包定义的 :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 可以知道 my-lib/interface/definition 和 my-lib/interface/base 这些 file/system 取决于系统 closer-mop(已在上面注册)。
但是,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 /.../all 系统,因为 ASDF 检测到 interface/order.lisp 以及来自所有系统的 :use-reexport 子句的所有其他依赖项,从而有效地允许了符号导出 "inheritance" 。
ASDF 还从 :import-from 子句检测依赖关系。 因此,您可以从现有软件包中导入一组定义良好的符号,并且 ASDF 将知道要加载提供该软件包的系统。 在以下示例中,ASDF 将从第一个 :import-from 推断当前系统依赖于 foo/baz。 如果您更喜欢使用由软件包前缀完全限定的任何此类符号,则可以通过带有空白符号列表的 :import-from 子句声明对该软件包及其相应系统的依赖关系。 例如,如果我们更喜欢使用名称 "foo/quux:bletch",则第二个空的 :import-from 格式将导致 ASDF 加载 foo/quux。
(defpackage:foo/bar
(:use:cl)
(:import-from:foo/baz#:sym1 #:sym2)
(:import-from:foo/quux)
(:export...))
请注意,仅从 ASDF 3.1.5.6 开始,ASDF 将在 component-pathname(通过 :pathname选项指定)下查找源文件,而较早的版本将忽略此选项并使用 .asd 文件所在的 system-source-directory 。
7.ASDF 对象模型(The Object model of ASDF)
ASDF 是从头开始以面向对象的方式设计的。系统的结构和可以在系统上执行的操作均遵循可扩展的协议,从而允许程序员向 ASDF 添加新行为。例如,cffi 添加了对与 C 库进行交互的特殊 FFI 描述文件以及对在 Lisp 中嵌入 C 代码的包装文件的支持。 asdf-jar 支持在 ABCL 中创建 Java JAR 存档。 poiu 支持使用后台进程并行编译代码。
ASDF 中的关键类是 component 和 operation。 component 代表一个单独的源文件或一组源文件,以及由此产生的产品(例如 fasl 文件)。 operation 表示可以在组件上执行的转换,将其从源文件转换为中间结果再转换为最终输出。组件(Components)通过系统定义中指定的依赖关系进行关联。
如果命令 operate 要对某个组件(通常是系统)进行某种操作,则 ASDF 首先会使用 make-plan 函数遍历依赖图来计算一个计划。结果计划对象包含一个有序的操作列表。动作是一对 operation 和 component,代表要执行的特定构建步骤。计划的顺序确保在满足所有依赖项之前,不执行任何操作。
在本章中,我们将描述 ASDF 的面向对象协议,组成该协议的类以及这些类的通用功能。这些通用功能通常以操作和组件为参数 :ASDF 的许多功能和可配置性是通过使用 CLOS 的多调度来提供的。我们将描述内置的组件和操作类,并说明如何通过为 ASDF 的通用功能定义新的类和方法来扩展 ASDF 协议。我们还将描述许多可配置为自定义现有功能行为的钩子。
7.1操作(Operations)
只要用户想对类似的系统进行操作,就会实例化适当类型的操作对象:
编译所有文件
将文件加载到运行的 Lisp 环境中
将其源文件复制到其他地方
可以直接调用操作,也可以在不执行操作的情况下检查操作的效果。有一堆专门用于操作和组件类型的方法,它们实际上完成了繁琐的工作。操作通过 operate (参见下面的 operate)在系统上调用。
ASDF 包含许多预定义的操作(operation)类,这些类可用于您可能要使用的常见甚至是非常不常见的任务。 此外,ASDF 包含“抽象”操作类,程序员可以将其用作构建 ASDF 扩展的基础。 我们将在下面依次讨论这些内容。
操作(Operations)通过 operate 在系统上调用。
Generic function: operate operation component &rest initargs &key force force-not verbose &allow-other-keys
Generic function: oos operation component &rest initargs &key &allow-other-keys
operate 调用系统上的操作(operation)。oos 是 operate 的同义词(它代表在系统上操作(operate-on-system))。
operation 是操作指示符, 它可以是操作对象本身,或者通常是传递给 make-operation 的符号(它将调用 make-instance)以创建操作对象。
component 是一个组件指示符, 它可以是一个组件对象本身,或者通常是一个命名系统的字符串或符号(要小写),很少是指定系统子组件的字符串或符号的列表。
现已不建议使用将 initargs 传递给的 make-operation。 有关详细信息,请参见 make-operation。注意,依赖关系可能会导致操作调用系统或其组件上的其他操作:新操作可能使用与原始操作(目前)相同的 initargs 创建,也可能不使用。
如果 force 是 :all,那么所有系统都必须重新编译,即使自上次编译以来没有修改过。如果 force 为 t,则只有正在加载的系统被迫重新编译,即使自上次编译以来没有修改,但其他系统不受影响。 如果 force 是一个列表,那么它指定了一个系统列表,这些系统在上次编译之后即使没有修改也必须重新编译。
如果 force-not 为 :all,则强制所有系统都不重新编译,即使自上次编译以来已经修改过。如果 force-not 是 t,那么除了正在加载的系统之外,所有系统都被强制不重新编译,即使自上次编译以来已经修改过(注意:这在 ASDF 3.1.2 中已经更改)。如果 force-not 是一个列表,那么它指定了一个系统列表,这些系统即使在上次编译后修改过,也必须不重新编译。
force 和 force-not 都不适用于作为依赖项且已被编译的系统。 实际上,force-not 优先于 force,但自 ASDF 3.1.2 起,它就不应该优先于武力。 此外,已被 register-immutable-system 注册为不可变的系统(自ASDF 3.1.5起)始终被视为 force-not,并且甚至不会从文件系统刷新它们的 .asd。 请参阅其他功能。
看看 operate 会做什么,你可以使用:
(asdf:traverseoperation-classsystem-name)
Function: make-operation operation-class &rest initargs
创建操作对象时,initargs 将传递给 make-instance 调用。
注意:现已弃用用于操作的 initargs,并将在不久的将来从 ASDF 中将其删除。
注意:绝对不能直接使用 make-instance 创建操作实例, 只能通过 make-operation 创建。 尝试直接进行 operation 实例将导致运行时错误。
7.1.1ASDF的预定义操作(Predefined operations of ASDF)
本节中描述的所有操作都在 asdf 包中。它们通过泛型函数 operate 调用。
(asdf:operate'asdf:operation-name :system-name {operation-options ...})
Operation: compile-op
此操作将编译指定的组件。 cl-source-file 将被编译。系统或模块的所有子代和依赖项将由 compile-op 递归编译。
compile-op 依赖于 prepon-op,而 prepon-op 本身依赖于组件所有依赖项的 load-op,以及组件父依赖项的 load-op。当在 compile-op 上调用 operation 时,所有这些依赖项将被加载和编译。 但是,系统主体的某些部分仍处于卸载状态,因为没有任何依赖它们。 使用 load-op 加载系统。
Operation: load-op
此操作将加载指定组件的已编译代码。 cl-source-file 将加载其编译的 fasl,fasl 是 load-op 所依赖的 compile-op 的输出。
load-op 将递归加载系统或模块的所有子代。
load-op 操作还取决于 prepare-op ,而 prepare-op 本身取决于组件所有依赖项及其父项依赖项的 load-op。
Operation: prepare-op
此操作可确保加载组件的依赖关系及其递归父级(根据 load-op),这是在可对给定组件执行 compile-op 和 load-op 操作之前的先决条件。
Operation: load-source-op
Operation: prepare-source-op
load-source-op 将加载模块中文件的源,而不是编译后的 fasl 输出。它有一个与 prepare-op 类似的 prepsource-op,以确保依赖项本身通过 load-source-op 加载。
Operation: test-op
此操作将在模块上执行一些测试。默认方法将不执行任何操作。默认的依赖关系是要求首先在模块上执行 load-op。它的默认 operation-done-p 方法返回 nil,这意味着该操作永远不会完成–我们假设如果您调用 test-op,即使您已经进行过测试,也要测试系统。
ASDF 未定义此操作的结果。事实证明,很难定义测试操作应如何以与社区中使用的所有各种测试库和测试技术兼容的方式向用户传达其结果,并且鉴于 ASDF 操作不返回一个值指示成功或失败。对于愿意付出努力的人,我们建议定义条件以在测试操作失败时发出信号,并在这些条件中存储描述哪些测试失败的信息。
人们通常定义一个单独的测试系统来保存测试。这样做避免了不必要地将测试框架添加为对库的依赖。例如, 可能有一个
(defsystem"foo"
:in-order-to((test-op(test-op"foo/test")))
...)
(defsystem"foo/test"
:depends-on("foo""fiveam") ;fiveam是一个测试框架库
...)
然后在test-op上定义执行方法,如下所示:
(defsystem"foo/test"
:depends-on("foo""fiveam") ;fiveamisatestframeworklibrary
:perform(test-op(os)
(uiop:symbol-call:fiveam'#:run!
(uiop:find-symbol*'#:foo-test-suite
:foo-tests)))
...)
Operation: compile-bundle-op
Operation: monolithic-compile-bundle-op
Operation: load-bundle-op
Operation: monolithic-load-bundle-op
Operation: deliver-asd-op
Operation: monolithic-deliver-asd-op
Operation: lib-op
Operation: monolithic-lib-op
Operation: dll-op
Operation: monolithic-dll-op
Operation: image-op
Operation: program-op
这些是“捆绑”操作,可以为应用程序中每个系统或整个应用程序的所有内容创建单个文件“捆绑”。
compile-bundle-op 将为所需的每个系统创建一个 fasl 文件,将其所有 fasls 分组为一个,因此您可以将每个系统作为一个 fasl 交付。
monolithic-compile-bundle-op 将为目标系统及其所有依赖项创建一个 fasl 文件,因此您可以将整个应用程序作为一个 fasl 交付。
load-bundle-op 将加载 compile-bundle-op 的输出。 请注意,如果输出不是最新的,则 compile-bundle-op 可能会加载中间 fasls, 这是一个副作用。 捆绑在一起的框架在 ECL 上非常重要,在这种情况下,加载数十个单独的框架所涉及的动态链接可能比加载单个框架要昂贵得多。
注:为了向后兼容性,有一些旧的名称仍然存在,尽管它们不能很好地标记正在发生的事情。
一旦你用 compile-bundle-op 创建了一个 fasl,就可以使用预编译系统以与依赖于系统的客户端兼容的方式来交付它,无论是作为源分发还是作为单个二进制分发;与 fasl 一起传递的 .asd 文件如下所示:
(defsystem:mysystem:class:precompiled-system
:fasl(someexpressionthatwillevaluatetoapathname))
或者,您可以使用 delivery-asd-op 来让 ASDF 为您创建这样的系统,以及 compile-bundle-op 输出或 monolithic-deliver-asd-op。 这允许您可以将系统或应用程序的代码作为单个文件交付。 当然,如果要在当前映像中测试结果,则在尝试使用任何新创建的 .asd 文件之前,请不要忘记 (asdf:clear-configuration) 或至少 (asdf:clear-source-registry),因此它会从文件系统中重新填充源注册表。
program-op 操作将从指定的系统及其依赖项创建一个可执行程序。您可以将 UIOP 用于它的映像前转储钩子、映像后恢复钩子以及它对命令行参数的访问。 您可以通过在 defsystem 中指定 :entry-point "my-app:main" 来指定入口点 my-app:main。根据您的实现,运行 (asdf:operate 'asdf:program-op :my-app) 可能会在完成后退出当前的Lisp映像。请参阅 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。 还有 dll-op(分别等效于其单片等效的 monolithic-dll-op),用于构建可链接的 .so 文件(Windows:.dll,MacOS X:.dynlib),以为所有要添加的 FFI 代码创建单个动态库, 链接到您的每个系统(分别是整个应用程序)。
自 ASDF 3 开始,所有这些受支持的 Lisp 实现都可以使用所有这些“捆绑式”操作,但在未维护的旧版实现中可能不可用。 作为单独的系统 asdf-bundle 的一部分,此功能以前可用于某些选择的实现,而该系统本身是仅 ECL 的 asdf-ecl 的后代。
bundle 操作的输出路径名通常受制于输出转换,除非该操作等于 defsystem 的 :build-operation 参数。这种行为不是很令人满意,将来可能会改变。也许您对如何更好地配置它有建议?
Operation: concatenate-source-op
Operation: monolithic-concatenate-source-op
Operation: load-concatenated-source-op
Operation: compile-concatenated-source-op
Operation: load-compiled-concatenated-source-op
Operation: monolithic-load-concatenated-source-op
Operation: monolithic-compile-concatenated-source-op
Operation: monolithic-load-compiled-concatenated-source-op
这些操作,如其各自的名称所示,将按照依赖关系定义的顺序,将系统(或系统及其所有依赖关系,如果是整体的话)中的所有 cl-source-file 源文件串联起来,然后加载结果,或者 编译然后加载结果。
这些操作对于将系统或应用程序作为单个源文件交付,以及测试该文件是否正确加载或编译然后正确加载非常有用。
ASDF 本身以这种方式作为单个源文件交付,使用 monolithic-concatenate-source-op,在 asdf/defsystem 系统本身之前添加前奏和 uiop 库。
7.1.2创建新操作(Creating new operations)
PASS
7.2 组件(Components)
组件(component)代表一个单独的源文件或一组源文件,以及被转换成的东西。 system 是位于组件层次结构顶层的组件,可以通过 find-system 找到。 source-file 是代表单个源文件和将其转换成的后续输出文件的组件。 module 是一个中间组件,它本身将几个其他组件,自身的源文件或其他模块进行了分组。
系统指示符(system designator)是一个系统本身,或者是一个字符串或符号,其行为与任何其他组件名称一样(包括有关组件名称的大小写转换规则)。
相对于基本组件,组件指示符(component designator)可以是组件本身,也可以是字符串、符号或指示符列表。
Function: find-system system-designator &optional (error-p t)
给定一个系统指示符(system designator),find-system 查找并返回一个系统。 如果未找到系统,则会抛出类型为 missing-component 的错误,或者如果 error-p 为 false,则返回 nil。
要查找和更新系统,find-system 会对 *system-definition-search-functions* 列表中的每个元素进行调用,以期望返回路径名或系统对象,从中可以提取路径名并进行注册。 如果满足下列条件之一为真,则加载结果路径名(如果有):
内存中没有该名称的系统
路径名不同于先前加载的路径名
文件的最后修改时间超过了内存中系统的最后修改时间
从 .asd 文件加载系统定义时,它们会隐式加载到 ASDF-USER 程序包中,它使用 ASDF,UIOP 和 UIOP/COMMON-LISP12 使程序员在 .asd 文件中做重要的事情,例如定义新变量。 ,函数或类在此文件中应包含 defpackage 和 in-package 形式,因此它们不会覆盖彼此的扩展名。 这些格式也可以帮助文件在通过 cl:load 手动加载进行开发或调试时表现出相同的效果,尽管我们建议您改用 slime-asdf 知道的函数 asdf::load-asd。
*system-definition-search-functions* 的默认值是三个函数组成的列表。 第一个函数在 *central-registry* 成员求值的每个目录中查找名称为系统名称且类型为 asd 的文件。 返回第一个这样的文件,无论是否实际定义了适当的系统。 第二个函数对源注册表中指定的目录执行类似的操作,但仅搜索文件系统一次并缓存其结果。 第三个函数使软件包推断系统扩展工作,请参阅包系统扩展。
由于这些搜索函数的定义方式,您应该将系统 foo 的定义放在一个名为 foo 的文件中。在中央注册表中的目录中,或者可以使用源注册表配置找到该目录。
在同一个文件中定义多个系统通常很有用,但是 ASDF 只能根据系统名称来定位系统的定义文件。因此,ASDF 3 的系统搜索算法已得到扩展,以允许文件 foo.asd 除了包含名为 foo 的主系统之外,还包含名为 foo/bar,foo/baz,foo/quux 等的辅助系统。由斜杠字符 / 分隔的系统名称的第一部分称为系统的主要名称。可以通过函数 asdf::primary-system-name 提取基本名称;当 ASDF 3 被告知查找名称中带有斜杠的系统时,它将首先尝试加载相应的主系统,从而看到任何此类定义和 / 或程序包推断系统的任何定义。如果文件 foo.asd 还定义了系统, 不遵循该惯例,例如,名为 foo-test 的系统,ASDF 将无法自动找到这些系统的定义,并且只有在您明确找到或加载该系统时,才能看到它们的定义主系统使用例如(asdf:find-system "foo"),然后再尝试使用它们。尽管当前支持向后兼容,但我们强烈建议您反对这种做法。
Function: primary-system-name name
内部函数(未导出) asdf::primary-system-name。 从 name 返回主系统名称(在辅助系统名称中,斜杠前的部分)。
Function: locate-system name
通常不应直接调用此函数。 它仅作为希望提供自己的“系统定义搜索功能”的程序员作为 API 的一部分导出。
给定一个系统名称指示符,尝试定位从何处加载系统定义。返回5个值:foundp、found-system、pathname、previous 和 previous-time。当一个系统被发现时,无论是一个新的未注册的系统,还是一个先前注册的系统,foundp 都为 t。如果在源注册表中找到了系统定义,则 found-system 返回值将是一个系统对象。如果系统定义尚未加载,则不会加载。pathname(不为 null 时)是加载系统的路径,可以与已创建的系统相关联,也可以与以前的系统相关联。如果 previous 不是 null,它将是以前加载的同名系统对象(注意,系统定义以前加载过:系统本身可能是也可能不是)。previous-time(不为null时)是先前的系统定义文件的时间戳,即加载先前的系统定义时的时间戳。
例如,如果您当前的注册表在 /current/path/to/foo.asd 中具有 foo.asd,但是先前从 /previous/path/to/foo.asd 中加载了系统 foo,则 locate-system 将返回以下值:
foundp 是 t
found-system 为 nil
pathname 为 #p"/current/path/to/foo.asd”
previous 将是类型为 SYSTEM 的对象,其系统源文件插槽值为 #p"/previous/path/to/foo.asd"
previous-time 是 #p"/previous/path/to/foo.asd" 加载时的时间戳。
Function: find-component base path
给定一个基本组件(或这样的指示符)和一个路径,查找由从基本组件开始的路径指定的组件。
如果 path 是一个组件对象,它独立于基对象来指定自己。
如果 path 是一个字符串,或者是一个符号(通过强制名称表示一个字符串),则 base 被解析为一个组件对象,该组件对象必须是系统或模块,并且指定的组件是该 path 命名的子级。
如果 path 是一个 cons,则将 find-component 的 car 与 base 进行比较,并将生成的对象用作尾调用的基础,以对 path-car 的 find-component 进行尾部调用。
如果 base 是组件对象,则它指定自己。
如果 base 为 null,则将 path 用作 base,将 nil 用作 path。
如果 base 是字符串,或者是通过强制名称表示符号的符号,则它将根据 find-system 指定一个系统。
如果 base 是一个 cons,则将 find-component 找到的组件指定为其 car 作为 base,cdr作为 path。