跳转到内容

Common Lisp/外部库/ASDF/Budden 的不常被问到的问题

来自 Wikibooks,开放世界中的开放书籍

关于 ASDF 的 Budden 不常被问到的问题

[编辑 | 编辑源代码]

此信息非官方,并非来自 asdf 维护人员,高度主观,可能重复手册的某些部分,可能不正确,或用英语表达不佳。

“向上”和“向下”是什么意思?

[编辑 | 编辑源代码]

我不知道。根据手册,“操作向下传播”意味着 asdf 首先对系统的组件执行操作,然后对系统本身执行操作。“操作向上传播”意味着操作先在系统上执行,然后在它的组件上执行。但这似乎与系统的依赖无关,而这正是我遇到的问题。

如何在给定目录中制作带有自己的 fasl 缓存的可移植 Lisp 程序?

[编辑 | 编辑源代码]

(来自 asdf-devel 邮件列表) https://mailman.common-lisp.net/pipermail/asdf-devel/2015-October/004962.html

;; this is a fragment of my sbclrc (user-level lisp initialization script)
;; note we rely on the fact that we have no :uiop loaded
;; if :uiop is present in the image at the beginning, this answer won't help you
(assert (null (find-package :uiop)))
(require :uiop)
(defconstant *clcon-uiop-user-cache-override* #P"c:/clcon/fasl-cache/asdf/")
(setf uiop:*user-cache* *clcon-uiop-user-cache-override*)
(require 'asdf)
(defun check-output-translations-ok ()
  "Call it now and once again at the end of loading to ensure that fasls are placed to a right place"
  (assert (equalp (asdf:apply-output-translations "c:/aaa.bbb")
    (merge-pathnames "c/aaa.bbb" *clcon-uiop-user-cache-override*))))
(check-output-translations-ok)

#-quicklisp
(let ((quicklisp-init "c:/clcon/quicklisp/setup.lisp"
                                       ))
  (when (probe-file quicklisp-init)
    (load quicklisp-init))) 

; swank is handled separatedly (maybe irrelevant to asdf, but relevant
; to task of creating "portable file releases"
(load (merge-pathnames "swank-loader.lisp" (ql:where-is-system :swank)))
(setq swank-loader::*fasl-directory* "c:/clcon/fasl-cache/swank/")
(swank-loader:init)

;;; Here I load all my systems

; after loading all our code check that no one smashed the variable.
; this is only a limited protection, there is no protection against
; binding
(check-output-translations-ok)

注意:这并非 asdf 维护人员推荐的做法,但对我来说,它确实起到了作用。

:second 系统依赖于 :first 系统,两者都加载到镜像中。我更改了属于 :first 系统的文件。 (asdf:load-system :first) 会重新加载 :second 吗?

[编辑 | 编辑源代码]

正如我的实验表明,不会(在 asdf 3.1.5 中)。手册中指出:“但对于正在积极开发、调试或修改的代码,应该使用 load-system,这样 ASDF 就会选择你的修改并递归地重建修改后的文件以及所有依赖它们的代码。”但这句话只适用于你传递给 load-system 的系统名称。

我想让我的文件被加载,而不是编译。该怎么做?

[编辑 | 编辑源代码]

1. 阅读asdf FAQ 中的说明

2. 如果你仍然不相信,请尝试以下操作

;; Contents of s1.asd:
(defsystem :s1
  :components 
  ((:static-file "file-to-load.lisp")
   (:file "loader" :depends-on ("file-to-load.lisp"))))
;; EOF s1.asd 
;; ------------------------------
;; Contents of file-to-load.lisp
(eval-when (:compile-toplevel :load-toplevel) (error "I must be loaded as a source"))
(eval-when (:execute) (print "file-to-load.lisp is loading as a source")) 
;; EOF file-to-load.lisp
;; ------------------------------
;; Contents of loader.lisp
(load (merge-pathnames "file-to-load.lisp" #.*compile-file-pathname*))
;; EOF loader.lisp

3. 另一种方法是我的asdf-load-source-cl-file(在 SBCL 和 asdf 3.1.5 上测试过)。以下是从源代码中获取的一个示例系统定义

(asdf:defsystem :asdf-load-source-cl-file-example-system
  :defsystem-depends-on (:asdf-load-source-cl-file)
  :serial t
  :components
  ((:load-source-cl-file "file-to-load")
   ))

我想删除所有 fasl 文件。如何找到它们?

[编辑 | 编辑源代码]

这可能会有所帮助

(asdf:apply-output-translations (asdf:system-source-directory (asdf:find-system :s1)))

当前镜像中加载了哪些系统?

[编辑 | 编辑源代码]

asdf:already-loaded-systems 是一个函数,它返回加载的系统的名称。手册中几乎有关于它的文档。你也可以读取 asdf::*defined-systems* 的值。注意,这是一个未公开的变量。

如何在 asdf 系统上执行“make clean”?

[编辑 | 编辑源代码]

无法仅仅执行 make clean。加载系统会对 Lisp 镜像产生副作用。例如:

  • 可以创建包
  • 可以在泛型函数上定义方法
  • 可以修改现有的函数
  • 可以修改变量(例如 *features*)

因此,请考虑“卸载”而不是“必须清理”。为了实现“卸载”,我们必须跟踪安装过程中所做的所有更改,但我不知道是否有人尝试过实现它。

如果你只想删除所有 fasl,请查看 arnesi 的开头。代码是 2005 年编写的,所以可能已过时。我没有测试过它。来自asdf.lisp 的引用如下

(defclass clean-op (asdf:operation)
  ((for-op :accessor for-op :initarg :for-op :initform 'asdf:compile-op))
  (:documentation "Removes any files generated by an asdf component."))

(defmethod asdf:perform ((op clean-op) (c asdf:component))
  "Delete all the output files generated by the component C."
  (dolist (f (asdf:output-files (make-instance (for-op op)) c))
    (when (probe-file f)
      (delete-file f))))

(defmethod asdf:operation-done-p ((op clean-op) (c asdf:component))
  "Returns T when the output-files of (for-op OP) C don't exist."
  (dolist (f (asdf:output-files (make-instance (for-op op)) c))
    (when (probe-file f) (return-from asdf:operation-done-p nil)))
  t)

关于 asdf:run-program + SBCL + Windows 呢?

[编辑 | 编辑源代码]

我不知道正确的答案。首先,我在 Windows 上使用 SBCL 运行程序从未成功过。当我在 EMACS 中调用 SBCL 时,尤其如此。我在 SBCL launchpad 上提交了一个错误报告,但至今未得到理会。也许是我没有正确阅读手册,但我相信实际上存在错误。因此我编写了一个小型 Delphi 程序 CallBatFromGuiDetached.exe,它具有以下功能

  • 它可以运行另一个程序(控制台或 GUI),并等待其完成或不等待。
  • 它可以在指定目录中运行程序。
  • 它可以将 stout 和 stderr 重定向到文件。
  • 可以提取退出代码。

你可能仍然会遇到字符编码问题。请随意修复它们。运行程序的代码如下

(defun call-bat (bat args &key (wait t) redirect-output directory)
  (let ((arglist nil))
    (check-type redirect-output (or null string))
    (when redirect-output
      (setf arglist (append arglist `("-s" ,redirect-output))))
    (setf arglist (append arglist (list bat) args))
    (apply #'sb-ext:run-program
           "c:/clcon/bin/util/CallBatFromGuiDetached.exe"
           arglist
           :wait wait
           (if directory (list :directory directory))
           )))

CallBatFromGuiDetached.exe 的源代码和二进制文件包含在clcon 分发版 中。一些消息和注释为俄语。示例

启动 notepad.exe 而不等待

(call-bat "notepad.exe" () :wait nil)

启动 notepad.exe 并等待

(call-bat "notepad.exe" ())

启动一个不存在的命令并提取错误代码

(sb-impl::process-exit-code (call-bat "nonexisting_command" ()))

在 c:\tmp 中启动 dir 并将输出重定向到文件

(call-bat "cmd" '("/c" "dir" "/b" "/s") :redirect-output "outputs" :directory "c:/tmp")

stdout 和 stderr 将保存到 c:\tmp\outputs.stdError.txt 和 c:\tmp\outputs.stdOutput.txt 中。

在哪里可以找到定义自定义组件类的良好示例?

[编辑 | 编辑源代码]

我不知道哪里有好的例子,但 asdf 手册首先推荐使用 CFFI-GROVEL。当您加载包含 :grovel-file 组件的系统时,asdf 会按照以下步骤处理该组件:

1. 在名为 X 的文件上调用特殊的 Lisp 函数 cffi-grovel:process-grovel-file,该文件名作为组件的名称给出。结果将写入到一个名为 Y 的 Lisp 文件中。

2. 文件 Y 将被编译并像任何其他正常的 Lisp 文件一样加载。

cffi-grovel 的 asdf.lisp 演示了以下内容:

  • 如何创建新的组件类
  • 如何处理 asdf 版本
  • 如何注册类,以便它可以作为 asdf 组件类型使用
  • 如何定义方法

为了更深入地了解,让我们看一下

(defmethod component-depends-on ((op compile-op) (c process-op-input))
  `((process-op ,c) ,@(call-next-method)))

代码指出,要进行 compile-op(编译源代码),我们应该先执行 process-op。compile-op 的 input-files 方法被重新定义,以便编译生成的 Lisp 文件而不是原始文件。

(defmethod input-files ((op compile-op) (c process-op-input))
  (list (first (output-files 'process-op c))))

process-op 的方法被定义为调用 groveller。

手册说明,只需定义组件的新类,它就可以在系统中使用。组件类的名称必须位于哪个包中?

在 grovel 的 asdf.lisp 中,我们看到了以下代码:

(in-package :cffi-grovel)
...
;; Allow for naked :grovel-file ... in asdf definitions.
(setf (find-class 'asdf::cffi-grovel-file) (find-class 'grovel-file))

在阅读 UIOP/UTILITY:COERCE-CLASS 的源代码后,我得出结论,注释不正确,应该改为:

;; Allow for naked :cffi-grovel-file ... in asdf definitions.

因此,我们可以将组件指定为

(:cffi-grovel-file "bububub")

或者

(cffi-grovel:grovel-file "bububub")
华夏公益教科书