如何在Common Lisp 上交付基于编译配置文件的lib 项目?
How to deliver a lib project which compile based configure file on Comon Lisp?
感谢Common Lisp强大的宏系统,我可以编写大量的代码模板来生成函数,避免手动编写冗余代码。更重要的是,它可以根据配置文件生成不同的代码,所以我可以实现多种功能只需应用不同的配置文件。
但是,我不知道如何交付项目(它是一个库):
在我看来,也许每个配置文件都对应一个包?
例如,有一个常见的lisp文件common.lisp
,它在编译时根据不同的配置文件生成不同的函数。
它在编译时读取 a.conf
并为 PackageA
生成函数,并在编译时为 PackageB
读取配置 b.conf
。但是in-place
声明必须只指定一个包,common.lisp
不能同时在包A和B中。
顺便说一句,我仍然找不到合适的方法来获取项目的配置路径(所以我可以在编译时读取和使用它来生成函数) 我试过 *load-truename*
因为它指向包含 SBCLv2.0.1 上的 .fasl 文件的缓存路径。但是好像里面没有静态文件,所以不行。
对于宏扩展,您关心的是编译时间,而不是加载时间,因此您需要的变量是*compile-file-pathname*
&/或*compile-file-truename*
。 ASDF 喜欢将编译后的文件(以及正在加载的文件)存储在它已知的某个地方,你可以关闭(我这样做)但默认情况下它们最终会远离它们的来源。
这是一个示例宏,它应该(我还没有真正测试过)让您在每个文件的基础上启用调试输出。在现实生活中,缓存配置的读取会更好 file/s 但这有点麻烦。
(declaim (inline mutter))
(defun mutter (format &rest arguments)
(declare (ignore format arguments))
(values))
(defmacro maybe-debugging (&body forms)
(let ((config-file (and *compile-file-truename*
(make-pathname :name "debug"
:type "cf"
:defaults *compile-file-truename*))))
(multiple-value-bind (debugging cond)
(if (and config-file (probe-file config-file))
(ignore-errors
(with-standard-io-syntax
(let ((*read-eval* nil))
(with-open-file (in config-file)
(values (assoc (pathname-name *compile-file-truename*)
(read in)
:test #'string-equal)
nil)))))
(values nil nil))
(when cond
(warn "bogons reading ~A for ~A: ~A"
config-file *compile-file-truename* cond))
(if debugging
`(flet ((mutter (format &rest arguments)
(apply #'format *debug-io* format arguments)))
,@forms)
`(progn
,@forms)))))
对于单个源文件在多个对象文件中产生的结果,您可以这样做(注意这重复了上述代码的变体):
(eval-when (:load-toplevel :compile-toplevel :execute)
(defvar *package-compilation-configuration*
nil
"Compile-time configuration for a package")
(defun package-config-value (key &optional (default nil))
(getf *package-compilation-configuration* key default)))
(declaim (inline mutter))
(defun mutter (format &rest args)
(declare (ignore format args))
(values))
(defmacro with-muttering (&body forms)
(if (package-config-value ':mutter)
`(flet ((mutter (fmt &rest args)
(apply #'format *debug-io* fmt args)))
,@forms)
`(progn
,@forms)))
(defun compile-file-for-package (file package &rest kws
&key (output-file nil output-file-p)
&allow-other-keys)
(with-muttering
(let* ((sf-pathname (pathname file))
(package-file (make-pathname :name (string package)
:type "cf"
:defaults sf-pathname))
(the-output-file
(if output-file-p
output-file
(compile-file-pathname
(make-pathname :name (format nil "~A-~A"
(pathname-name sf-pathname)
package)
:defaults sf-pathname))))
(*package-compilation-configuration*
(if (probe-file package-file)
(with-standard-io-syntax
(mutter "~&Compile ~A -> ~A using ~A~%"
sf-pathname the-output-file package-file)
(let ((*read-eval* nil))
(with-open-file (in package-file)
(read in))))
(progn
(mutter "~&Compile ~A -> ~A (no package)~%"
sf-pathname the-output-file)
nil))))
(apply #'compile-file file
:output-file the-output-file
kws))))
然后 (compile-file-for-package "x.lisp" "y")
将编译 x.lisp
并读取包 "y"
的配置。
要在愤怒中使用这样的东西,你需要将它与 ASDF 集成,我不知道该怎么做。
另一种想法是对源文件使用符号链接,并让依赖于文件名的配置依赖于符号链接名称,而不是目标名称。
对于我的情况:
project-a.asd
:
(asdf:defsystem #:project-a
:components ((:static-file "my-config-file.conf")
(:static-file "common.lisp") ; shared common lisp file
(:file "project-a-package")
(:file "project-a-setup")
;; other components
)
)
project-a-setup.lisp
:
(in-package #:project-a)
(eval-when (:compile-toplevel)
(defvar *mypackage* (find-package 'project-a))
(defvar *source-home* (path:dirname *compile-file-truename*))
;; read configure file
(defparameter *myconf*
(with-open-file (stream (merge-pathnames *source-home* #P"my-config-file.conf"))
(read stream)))
)
(load (merge-pathnames *source-home* #P"common.lisp"))
common.lisp
:
(let ((*package* *mypackage*))
;; intern symbol
)
感谢Common Lisp强大的宏系统,我可以编写大量的代码模板来生成函数,避免手动编写冗余代码。更重要的是,它可以根据配置文件生成不同的代码,所以我可以实现多种功能只需应用不同的配置文件。
但是,我不知道如何交付项目(它是一个库):
在我看来,也许每个配置文件都对应一个包?
例如,有一个常见的lisp文件common.lisp
,它在编译时根据不同的配置文件生成不同的函数。
它在编译时读取 a.conf
并为 PackageA
生成函数,并在编译时为 PackageB
读取配置 b.conf
。但是in-place
声明必须只指定一个包,common.lisp
不能同时在包A和B中。
顺便说一句,我仍然找不到合适的方法来获取项目的配置路径(所以我可以在编译时读取和使用它来生成函数) 我试过 *load-truename*
因为它指向包含 SBCLv2.0.1 上的 .fasl 文件的缓存路径。但是好像里面没有静态文件,所以不行。
对于宏扩展,您关心的是编译时间,而不是加载时间,因此您需要的变量是*compile-file-pathname*
&/或*compile-file-truename*
。 ASDF 喜欢将编译后的文件(以及正在加载的文件)存储在它已知的某个地方,你可以关闭(我这样做)但默认情况下它们最终会远离它们的来源。
这是一个示例宏,它应该(我还没有真正测试过)让您在每个文件的基础上启用调试输出。在现实生活中,缓存配置的读取会更好 file/s 但这有点麻烦。
(declaim (inline mutter))
(defun mutter (format &rest arguments)
(declare (ignore format arguments))
(values))
(defmacro maybe-debugging (&body forms)
(let ((config-file (and *compile-file-truename*
(make-pathname :name "debug"
:type "cf"
:defaults *compile-file-truename*))))
(multiple-value-bind (debugging cond)
(if (and config-file (probe-file config-file))
(ignore-errors
(with-standard-io-syntax
(let ((*read-eval* nil))
(with-open-file (in config-file)
(values (assoc (pathname-name *compile-file-truename*)
(read in)
:test #'string-equal)
nil)))))
(values nil nil))
(when cond
(warn "bogons reading ~A for ~A: ~A"
config-file *compile-file-truename* cond))
(if debugging
`(flet ((mutter (format &rest arguments)
(apply #'format *debug-io* format arguments)))
,@forms)
`(progn
,@forms)))))
对于单个源文件在多个对象文件中产生的结果,您可以这样做(注意这重复了上述代码的变体):
(eval-when (:load-toplevel :compile-toplevel :execute)
(defvar *package-compilation-configuration*
nil
"Compile-time configuration for a package")
(defun package-config-value (key &optional (default nil))
(getf *package-compilation-configuration* key default)))
(declaim (inline mutter))
(defun mutter (format &rest args)
(declare (ignore format args))
(values))
(defmacro with-muttering (&body forms)
(if (package-config-value ':mutter)
`(flet ((mutter (fmt &rest args)
(apply #'format *debug-io* fmt args)))
,@forms)
`(progn
,@forms)))
(defun compile-file-for-package (file package &rest kws
&key (output-file nil output-file-p)
&allow-other-keys)
(with-muttering
(let* ((sf-pathname (pathname file))
(package-file (make-pathname :name (string package)
:type "cf"
:defaults sf-pathname))
(the-output-file
(if output-file-p
output-file
(compile-file-pathname
(make-pathname :name (format nil "~A-~A"
(pathname-name sf-pathname)
package)
:defaults sf-pathname))))
(*package-compilation-configuration*
(if (probe-file package-file)
(with-standard-io-syntax
(mutter "~&Compile ~A -> ~A using ~A~%"
sf-pathname the-output-file package-file)
(let ((*read-eval* nil))
(with-open-file (in package-file)
(read in))))
(progn
(mutter "~&Compile ~A -> ~A (no package)~%"
sf-pathname the-output-file)
nil))))
(apply #'compile-file file
:output-file the-output-file
kws))))
然后 (compile-file-for-package "x.lisp" "y")
将编译 x.lisp
并读取包 "y"
的配置。
要在愤怒中使用这样的东西,你需要将它与 ASDF 集成,我不知道该怎么做。
另一种想法是对源文件使用符号链接,并让依赖于文件名的配置依赖于符号链接名称,而不是目标名称。
对于我的情况:
project-a.asd
:
(asdf:defsystem #:project-a
:components ((:static-file "my-config-file.conf")
(:static-file "common.lisp") ; shared common lisp file
(:file "project-a-package")
(:file "project-a-setup")
;; other components
)
)
project-a-setup.lisp
:
(in-package #:project-a)
(eval-when (:compile-toplevel)
(defvar *mypackage* (find-package 'project-a))
(defvar *source-home* (path:dirname *compile-file-truename*))
;; read configure file
(defparameter *myconf*
(with-open-file (stream (merge-pathnames *source-home* #P"my-config-file.conf"))
(read stream)))
)
(load (merge-pathnames *source-home* #P"common.lisp"))
common.lisp
:
(let ((*package* *mypackage*))
;; intern symbol
)