如何在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 
      )