如何从 .asd 文件的目录编译和 运行 Common Lisp 程序?

How do I compile and run a Common Lisp program from the directory of the .asd file?

我的目录结构如下:

my-project/
├── my-project.asd
├── package.lisp  # defpackage.
├── utils.lisp    # Functions used by main.lisp.
└── main.lisp     # Main program.

my-project.asd:

(defsystem "my-project"
  :components ((:file "package")
               (:file "utils")
               (:file "main")))

package.lisp:

(defpackage :com.example
  (:use :cl))

utils.lisp:

(in-package :com.example)

(defun double (x)
  (* x 2))

main.lisp:

(in-package :com.example)

(format t "~a" (double 3))

问题是:如何使用 ASDF 编译和 运行 main.lisp

我能够通过以下方式编译和运行程序:

$ mv my-project ~/common-lisp/.
$ sbcl
* (require :asdf)
* (asdf:load-system :my-project)

然而,这是非常愚蠢的。我不想将我的项目移动到 ~/common-lisp/ 只是为了 运行 它。我想直接从项目目录本身编译和 运行 程序。 my-project/ 目录可以在任何地方,我希望它可以放在任何地方。换句话说,我想从当前目录加载系统。

想想 make,在这里我可以直接从 Makefile 本身的目录编译文件。我如何从 *.asd 文件本身的目录中类似地编译和 运行 Common Lisp 程序?

(我使用的是 SBCL 1.4.5 版和 ASDF 3.3.1 版)

您需要告诉 asdf 在哪里可以找到您的项目。

相关参考如下:

https://common-lisp.net/project/asdf/asdf/Configuring-ASDF-to-find-your-systems.html

引用上述资源:

First create the directory ~/.config/common-lisp/source-registry.conf.d/; there create a file with any name of your choice but with the type conf, for instance 50-luser-lisp.conf; in this file, add the following line to tell ASDF to recursively scan all the subdirectories under /home/luser/lisp/ for .asd files: (:tree "/home/luser/lisp/")

That’s enough. You may replace /home/luser/lisp/ by wherever you want to install your source code. You don’t actually need to specify anything if you use the default ~/common-lisp/ as above and your implementation provides ASDF 3.1.2 or later. If your implementation provides an earlier variant of ASDF 3, you might want to specify (:tree (:home "common-lisp/")) for bootstrap purposes, then install a recent source tree of ASDF under ~/common-lisp/asdf/.

link 中还有更多内容。

这就是我解决这个问题的方法。它可能不是您所需要的,但这里的想法可能会有所帮助。

为了发展

确保您尝试构建的系统只有一个副本。因此,特别要确保没有 ASDF 可能会找到的已安装副本:见下文。

那么这个就可以了。首先确保您的 ASDF 系统定义是可冷加载的,因此特别要确保它在顶部有正确的 (in-package :asdf-user)

那么构建你的系统的方法是:

$ cd .../my-project
$ sbcl
[...]
* (require :asdf) ;already loaded in my case by init files
nil
* (load "my-project.asd")
t
* (asdf:load-system "my-project")
; compiling file "/media/psf/share/tmp/my-project/package.lisp" (written 15 DEC 2020 09:06:54 AM):
; processing (defpackage :com.example ...)
[...]
*

现在你很好了。所以我做的三招是:

  • 完全避免考虑整个源注册表头发,因为如果你考虑太多带有触手的东西会撕掉你的脸(我知道这一点,它发生在我身上,我现在没有脸);
  • 确保只有一个系统副本所以ASDF不能使用源注册表发我已经避免想着找错了;
  • 显式加载系统定义文件——ASDF 查找的任何其他地方,它至少会在与该文件相同的目录中查找。

用于生产

答案是 Quicklisp。做任何需要让 Quicklisp 安装在你的 Lisp 中的事情。然后你需要知道它的安装目录在哪里:有一些默认值,但我从不使用它,因为我对文件系统应该是什么样子有自己的想法。

那么诀窍就是 Quicklisp 会在其 local-projects 目录下查找、构建和加载系统(据我所知,Quicklisp 完全由能力和魔法构成告诉)。所以如果你把一个系统放在那里,那么 Quicklisp 将简单而优雅地处理将它放入 运行ning 图像。

要执行此安装...我有生成文件。我知道,我应该使用 Lisp 工具,但我生活在 *nix 平台上,makeinstall 擅长整个复制文件位。

Makefile 的相关部分(实际上这就是全部)是:

# Where Quicklisp lives, and the name of this project using slashes
QUICKLISP = /local/tfb/packages/quicklisp
THIS      = org/tfeb/sample
FASLS     = *fasl *fsl

.PHONY: install uninstall clean

# To install the project make its directory, copy all the sources and
# the sysdcl into it, and then nuke Quicklisp's cache file so it searches
# next time
#
install:
    @mkdir -p "$(QUICKLISP)/local-projects/$(THIS)"
    @cd "$(QUICKLISP)/local-projects/$(THIS)" && rm -f $(FASLS)
    @install -C -v -m 444 *.lisp *.asd "$(QUICKLISP)/local-projects/$(THIS)"
    @rm -f "$(QUICKLISP)/local-projects/system-index.txt"

# To uninstall the project just nuke the directory and the cache
#
uninstall:
    @rm -rf "$(QUICKLISP)/local-projects/$(THIS)"
    @rm -f "$(QUICKLISP)/local-projects/system-index.txt"

clean:
    @rm -f $(FASLS) *~

这里有四件有趣的事情:

  • 我只是将 make 用作文件复制机 – 它不会编译任何东西或类似的东西,完全可以使用脚本;
  • 您需要清除 Quicklisp 的缓存文件,以便它在开始时再次搜索;
  • 我禁用了 ASDF 的输出翻译,这就是为什么我花时间吹掉编译后的文件——安装后项目总是应该从冷重建;
  • uninstall 目标是您在开发前需要 运行 的目标——它会核对已安装的版本,因此 ASDF 找不到它。

一旦您在项目目录中 运行 合适的 make install(ql:quickload :org.tfeb.sample) 就会为您编译并加载它。

请注意,另一种方法(Ehvince 在评论中建议)是在 Quicklisp 的 local-projects 目录下保留一个符号 link 返回到代码的规范版本。我不这样做,但它会工作得很好,并且在某些方面可能会更好。

我发现可以执行以下操作:

$ sbcl
* (require "asdf")
* (asdf:load-asd (merge-pathnames "my-project.asd" (uiop:getcwd)))
* (asdf:load-system :my-project)

注:

  • (require "asdf") 是加载 ASDF 的推荐方式,根据 ASDF manual.

    的“加载 ASDF”部分

    NB: all implementations except GNU CLISP also accept (require "ASDF"), (require 'asdf) and (require :asdf). For portability’s sake, you should use (require "asdf").

  • asdf:load-asd 必须是绝对路径,并且当给定的路径不正确(!)时可能不会失败并出现任何错误,因此请确保给定的绝对路径是正确的。

  • 使用 cl:load 而不是 asdf:load-asd 似乎也可行,但 ASDF 手册明确警告不要这样做:

    Indeed, ASDF does not load .asd files simply with cl:load, and neither should you. You should let ASDF find and load them when you operate on systems. If you somehow must load a .asd file, use the same function asdf:load-asd that ASDF uses. Among other things, it already binds the *package* to asdf-user. Recent versions of SLIME (2013-02 and later) know to do that when you C-c C-k when you use the slime-asdf contrib.

我做了与您发现的类似的事情。它的要点是 --load .asd.

先决条件

my-project.asd(require "asdf") 开头。

(require "asdf")
(asdf:defsystem "my-project"
  :version "0.0"
  …)

当我在 Slime 上时,我可以C-c C-k(编译并加载)这个文件。

我不太确定if/why“--load”是否需要它。

单行

我可以通过一次调用 Lisp 编译器来构建项目:

sbcl --load my-project.asd --eval '(asdf:load-system "my-project")'

这会加载它并给我一个 REPL。我可以添加 --eval (uiop:quit) 退出。

注意:我听到有人说最好使用 asdf:load-asd

使用 Quicklisp - 当你有依赖项时是必要的

我实际上使用的是 Quicklisp,而不是 asdf:load-system,因为它会加载我项目的依赖项。

sbcl --load my-project.asd --eval '(ql:quickload "my-project")'

(请注意,我没有将我的项目复制到 Quicklisp 的本地项目。如果我复制了,我就不需要在此处加载 .asd)

使用 Makefile

这一行可以变成一个简单的 Makefile。

LISP ?= sbcl
build:
    $(LISP) --load my-project.asd \
         --eval '(ql:quickload :my-project)' \
         # more rules here
         --eval '(quit)'

用 lisp 文件简化为 运行 全部

我们需要:

1-加载.asd

2- 快速加载依赖项

3- 运行 我们的脚本

我们也可以从 lisp 文件中执行此操作。

run.lisp:

(load "my-project.asd")  ;; relative path: we must be in the same directory
(ql:quickload "my-project")  ;; I installed Quicklisp and my ~/.sbclrc has the Quicklisp-initialization snippet

(my-project::main)  ;; whatever function acts as the entrypoint
(uiop:quit)         ;; portable way to quit.

构建二进制文件

我按照此处的说明使用 asdf:makehttps://lispcookbook.github.io/cl-cookbook/scripting.html#with-asdf