cl-launch - 用于 Common Lisp 的 shell 包装器
cl-launch - 用于 Common Lisp 的 shell 包装器
概要
cl [options] '(lisp form to evaluate)'
求值指定的 form, 在换行后面打印结果
cl [options] script-file arguments...
运行指定的脚本,传递参数
cl [options] [--execute] [options] [-- arguments...]
cl [options] --output EXECUTABLE [options]
生成可执行的脚本文件或二进制文件
特殊模式
-h or -? --help 显示简短的帮助信息
-H --more-help 显示完整的帮助信息
-V --version 显示 cl-launch 的版本和配置
-u FILE --update FILE 将一个 cl-launch 脚本升级到当前版本
软件规范
-w CODE --wrap CODE shell wrapper CODE to run in cl-launch
-l LISP... --lisp LISP... 尝试使用指定的LISP实现
-m IMAGE --image IMAGE 从指定的映像构建
-f FILE --file FILE 构建进包含指定的文件
-L FILE --load FILE 构建是载入指定的文件
-S X --source-registry X 覆盖 asdf 的系统源注册目录
-s SYSTEM --system SYSTEM 构建时载入指定的 asdf 系统
--load-system SYSTEM 和上面相同,为了与 buildapp 兼容
-p PACKAGE --package PACKAGE 将当前包切换为指定的包
-sp SP --system-package SP 相当于同进使用 -s 和 -p
-e FORM --eval FORM 构建时求值指定的表达式
--require MODULE 构建时 require 指定的模块
-r FUN --restart FUN 通过调用 fun 从构建重新启动
-E fun --entry FUN 通过调用 (fun argv) 从构建重新启动
-DE N/F --dispatched-entry N/F 如果以 N 的名字执行,以 (F argv) 重新启动
-i FORM --init FORM 重启后求值 FORM
-ip FORM --print FORM evaluate and princ FORM after restart
-iw FORM --write FORM evaluate and write FORM after restart
-F FORM --final FORM evaluate FORM before dumping IMAGE
-I PATH --include PATH cl-launch 安装位置的运行时路径
+I --no-include disable cl-launch installation feature
-R --rc 尝试读取 /etc/cl-launchrc, ~/.cl-launchrc
+R --no-rc 跳过 /etc/cl-launchrc, ~/.cl-launchrc
-Q --quicklisp 使用 quicklisp
+Q --no-quicklisp 为使用 quicklisp
-b --clbuild 使用 clbuild (see --more-help)
+b --no-clbuild 不使用 clbuild
-v --verbose 啰嗦模式
-q --quiet 安静模式(默认)
输出选项
-x -o ! --execute 立即运行指定的软件(这是默认的行为)
-o FILE --output FILE 创建可执行文件
-d IMAGE --dump IMAGE 转储堆映像以加快启动
-X ... -- 使用 #!/.../cl-launch 作为脚本解释器
-- -- 当使用 -x 或 -X 时标志参数的结束
调用 cl-launch
cl-launch
将求值 Common Lisp 代码或创建求值 Common Lisp 代码的 shell 脚本或可执行二进制文件。 cl-launch
遵循 Unix 脚本解释器和 Common Lisp 实现的调用约定。
cl-launch
的建议缩写名称是 cl
(如果它没有包含在操作系统的 cl-launch
包中,您可以自己创建符号链接)。 我们希望尽可能统一地使用路径/usr/bin/cl
,以便脚本作者可以合理地期望脚本在以下情况下可以工作:
#!/usr/bin/cl
(请参阅下面的 简单的 cl-launch 脚本 以了解 #!
脚本的注意事项。)
最近的 Linux 内核对脚本解释器的支持本身就是一个脚本。 BSD 内核和 Linux 不同,BSD 需要将一个小的 C 程序 cl-shim
编译并安装到 /usr/bin/cl
才能以这种方式使用 cl-launch
。
要正常工作,cl-launch
4.1.3 依赖于 ASDF
3.1.2 或更高版本,以及它的可移植层 UIOP
,来管理编译和映像的生命周期。
软件被指定为在几个阶段对代码进行求值;这种区别对于创建可执行二进制文件最为重要,但理解求值模型也可以避免在其他情况下出现意外。
第一阶段,初始化 Lisp 映像:
-
可以选择从某个映像 IMAGE 开始 (
-I --image
选项)[注:--image
的短选项不是-m
?] -
加载一个小的代码头,提供常用的
cl-launch
功能 -
载入
asdf 3
cl-launch
头将尝试加载“ASDF 3.1.2”或更高版本。如果您的实现没有通过(require "asdf")
提供它的话,您可以配置实现的asdf
(如果有的话)来找到它。或者你可以把它放在$HOME/common-lisp/asdf
里cl-launch
会找到它。
或者可以把它安装在/usr/share/common-lsp/source/cl-asdf/
里,cl-launch
也可以找到它。
如果上述任何一项失败,cl-launch
将无法继续。 -
(可选)加载quicklisp (
-Q --quicklisp
选项)
第二阶段中,将根据以下选项,按照顺序构建软件:
-
在当前包环境中求值一个或多个表达式 (
-e --eval
选项)。
在*package*
已被设置为当前包的上下文中,该系列表达式的计算方式为LOAD
(加载)(请参见下面关于包的解释)。 -
-L --load FILE
编译FILE
并加载 fasl
加载文件时,将*package*
绑定到当前包(见下文)。 -
-f --file FILE
include 一个文件,输出脚本中将包含的FILE
的内容,然后被编译成 fasl 并加载,就像通过选项-L --load
一样。与立即执行代码或转储映像相比,在创建输出脚本时,差异最为重要。这种方式只能指定一个文件。如果指定的文件名是-
,则使用标准输入。因此,您可以连接多个文件,并通过管道将它们提供给cl-launch
。如果要 include 的文件刚好叫做-
,请传递参数./-
(与cat
和其他Unix命令的技巧相同)。 -
-X SCRIPT --
执行脚本SCRIPT
,或者#!
脚本
或者通过使用不以(
或-
开头的文件名来执行选项,相当于:--package cl-user --load SCRIPT --execute --
一样。 -
--require
require 由实现提供的模块 -
让 ASDF 3 编译并加载一个系统 (
-s --system --load-system
选项)
选项-sp --system-package
载入一个系统,然后像-p --package
一样切换当前包。 -
(可选)
-d --dump
转储一个映像
在求值一个或多个最终表达式之前 (-F --final
选项)
如果使用选项-o --output
创建一个 shell 脚本,但不使用选项-d --dump
,那么前两个阶段仅在调用脚本时发生。如果使用选项-d --dump
,那么这两个阶段会立即发生,并且在调用时不会再编译。请注意,已编译的文件是缓存的,因此只有在通过--load of --system
首次加载文件时,或者在源文件被修改时,才会进行编译。这可能会导致首次启动速度变慢。缓存由“ASDF”的output-translations
机制控制。有关此缓存的配置,请参阅“ASDF”手册,该手册通常位于~/.cache/common-lisp/
下面。
第三阶段
您的软件通过UIOP:RESTORE-IMAGE
运行。如果在脚本上使用选项-x --execute
或通过#!
将cl-launch
作为 Unix 解释器调用,第三阶段会立即发生;
如果您使用选项-o --output
输出一个脚本或转储的映像(结合-d --dump
选项),则第三阶段发生在对输出文件的调用时:
-
UIOP:*IMAGE-RESTORE-HOOK*
队列,按照 FIFO 顺序调用。 -
-i --init
、-ip --print
、-iw --write
指定的一系列“FORM”,以文本字符串的表达式存储,并按顺序进行读取和求值,每个表达式都在请求时当前的包上下文中求值。(这些表达式与分隔的空白连接在一起,构成了由RESTORE-IMAGE
处理的UIOP:*IMAGE-PRELUDE*
。
以左括号开头的参数被假定为隐式--print
后面的“FORMS”。
从流加载意味着你不必担心糟糕的读取时间问题;表达式将由完整构建的 Lisp 映像读取;然而,这也意味着,如果在调用转储映像时非常关心启动延迟的最后一滴,那么只需使用选项-r --restart
或-E --entry
,并避免使用-init
及其变体。
选项-ip --print
指定'FORMS',这样最后一个表达式的结果将像PRINC
一样打印,后面跟着一个换行符。选项-iw --write
类似于--print
,使用write
而不是PRINC
。 -
(可选)
-r --restart
或-E --entry
指定的函数被调用。
如果该函数通过选项-r --restart
指定(与早期版本的cl-launch
兼容),它将不带参数调用。
如果该函数通过选项-E --entry
指定(与buildapp
兼容),它将使用一个参数调用,即传递给程序的参数列表,不包括argv[0]
在内。在大多数实现中可以通过函数uiop:argv0
来实现(在ASDF
3.1.2 及更高版本中可用)。
参数必须是能够从当前包中读取的函数名或 lambda 表达式(请参阅下面的选项-p --package
和-sp --system-package
)。
只能指定一个restart
或entry
函数,如果指定了多个,则最后指定的那一个将覆盖之前的。 如果你想调用多个函数,你可以使用DEFUN
定义一个函数作为入口,再通过它来调用其它函数,或者你可以使用多个初始化表达式,如下所示。 另请参阅下面的选项-DE --dispatch-entry
、-sm --system-main
、-Ds --dispatch-system
,它们的行为就如同指定了-E --entry
一样。 -
如果未能提供
restart
或entry
函数,程序将以状态码0
退出。如果提供了函数,程序将在函数返回后退出,当且仅当返回值是广义的布尔值“真”时,退出状态为0
,如果返回值为NIL
,则退出码为1
。详细信息请参数"UIOP:RESTORE-IMAGE"文档。
当前包可以通过选项 -p --package
及其变体 -sp --system-package
来控制,它的行为也类似于 -s --system
。
传递给--eval
、--init
、--print
、--write
、--final
、--restart
、-entry
的所有表达式都会在当前包中读取。在当前包中读取用 -f--file--load
指定的文件。”当前“”指的是正在处理的选项之前的最新选项-p --package
或-sp --system-package
指定的包,如果没有指定,则为cl-user
。
请注意,多个 -i --init
或 -F --final
表达式可能会在包被更改后连续求值,并且如果这些表达式之一本身修改了包,或者其他一些语法控制机制,例如 reader,它可能会对同一类别的后续表达式产生不利影响,但不会影响其他类别的表达式(如果达到)。
以下衍生选项的工作原理类似于简单选项的组合:
-
选项
-sp --system-package
将--system
和--package
组合在一个选项中,因此给定参数SYSTEM
,系统就像通过--system SYSTEM
加载一样,后者创建一个包system
,然后该包成为当前包。 -
如果使用选项
-DE --dispatch-entry
,则下一个参数必须遵循格式NAME/ENTRY
,其中NAME
是可以调用程序的名称(basename uiop:argv0
),而ENTRY
是一个函数,在这种情况下,就像通过--entry
调用一样。 如果ENTRY
被省略,则使用当前包中的函数main
。
对选项-DE --dispatch-entry
的支持被委托给一个dispatch
库,与cl-launch
一起分发,但不是cl-launch
本身的一部分,由- 注册对
dispatch
库的依赖,就像通过--system cl-launch/dispatch
(如果还没有注册的话) - 如果
--restart
和--entry
都没有指定,就注册一个默认的入口函数。相当于
--entry cl-launch/dispatch:dispatch-entry
。 - 注册一个构建表达式,该表达式注册调度条目。相当于
--eval '(cl-launch/dispatch:register-name/entry "NAME/ENTRY" :PACKAGE)'
,其中PACKAGE
是当前包。
- 注册对
有关更多详细信息,请参阅dispatch
库的文档。
-
如果选项
-Ds --dispatch-system
与SYSTEM
作为其参数一起使用,相当于
< -s | --system> SYSTEM < -DE | --dispatch-entry> sys-name/main
。
其中,sys-name
是系统的 basename, 路径中最后一个反斜杠后面的部分。 -
如果选项
-sm --system-main
与SYSTEM
作为其参数一起使用,就好像选项-s --system
与相同的参数一起使用,然后是选项-E --entry
与SYSTEM
包中的main
函数,但不改变当前包。
cl-launch
调用的一般说明:
选项从左到右处理; 通常,重复的选项会累积其效果,较早的实例在后面的实例之前生效。 在冲突或冗余选项的情况下,后者覆盖前者。
cl-launch
定义了一个导出以下符号的包 cl-launch
:
compile-and-load-file
以前由cl-launch
提供的运行时功能现在由“UIOP”提供,该可移植层由“ASDF3”提供。请参阅下面的部分 cl-launch 运行时 API。
当第一个无法识别的选项是文件名时,cl-launch
会尝试将此文件名作为脚本加载,就像通过 --load
一样,然后立即执行它,就像通过 --execute --
一样, 命令行的其余部分作为参数传递。 文件名不能以字符 -
或 (
开头 --- 要使用其中一个(或未知的东西)作为文件文件名的第一个字符,请在文件名前加上 ./
。注意它 让攻击者控制传递给 cl-launch
或其他命令的文件名是一种安全风险。
当指定选项 --execute
时,执行指定的软件。 命令行参数可以通过将它们放在特殊标记 --
之后来提供给正在执行的软件,这会结束 cl-launch
选项处理。
当使用选项 --output FILE
时,代码将生成到指定的 FILE
中。 输出文件本身将从完整生成的内容自动创建,因此可能与输入文件具有相同的路径名。不会求值restart
函数和init
表达式,但会在执行输出文件时保留。
如果指定了-
(引用后),则使用标准输出。
如果指定了!
(引用后),则假定选项--execute
。
当没有指定 --output
文件时,隐式假定选项 --execute
。 最后一个 --output
或 --execute
选项优先于之前的选项。
如果只存在一个参数并且它不以 -
开头,则该参数被视为提供给选项 -ip
,将立即进行求值和打印。
可以使用选项 --source-registry SOURCE_REGISTRY
覆盖ASDF3
源注册配置。 提供的配置将优先于环境或配置文件提供的任何内容,尽管它可以像往常一样从它们继承。 请参阅 ASDF3
手册。
选项 -l --lisp
和 -w --wrap
可用于控制在软件运行时找到 Common Lisp 实现的方式。 选项 -l --lisp
指定尝试使用的实现列表; 该列表以空格分隔,并包含 cl-launch
能够识别的昵称。
选项 -w --wrap
提供任意代码以供 shell 包装器求值,在它读取其配置并定义其内部函数之后,但在它尝试查找和运行 Lisp 实现之前。 此类包装器代码通常用于修改控制生成脚本的运行时行为的变量,如下所述。 可以使用 cl-launch
的其他内部结构,但不支持,这意味着您有责任保留与您的代码一起使用的特定版本的 cl-launch 的副本,并在您以后制作时更新您的代码 升级到不兼容的cl-launch
。 例如,--lisp "foo bar"
等价于 --wrap 'LIPS="foo bar"'
。请参阅下面有关 Lisp 实现调用 的文档部分。
选项 --no-include
指定 cl-launch 应该生成一个独立脚本,其中包括配置、shell 包装器、Lisp 标头和用户提供的 Lisp 代码(来自 --file
)。 除了 ASDF 和 Lisp 实现,该脚本实际上是自包含的,可以在文件系统中随意移动后仍然可以继续使用。 然而,输出的大小将是用户 Lisp 代码的大小加上大约 36KiB。
选项 --include PATH
指定 cl-launch
应该生成一个非常小的脚本(通常在 1KiB 以下),运行时将从指定的安装目录 PATH
读取 cl-launch
shell 包装器和 Lisp 标头。 此外,如果使用了选项 --include
,并且 Lisp 代码是用 --file
和以 /
开头的绝对路径名而不是相对路径名或标准输入指定的,那么 Lisp 代码也将是 在运行时从指定位置加载,而不是在生成时嵌入到脚本中。 此选项生成更精简的脚本,但可能不适用于同一脚本用于缺乏通用一致文件系统管理的各种情况。
--include
或 --no-include
中的哪一个是默认值可能取决于您的 cl-launch 安装。 作者分发的 cl-launch
版本默认使用 --no-include
,但是您的操作系统分发中可用的 cl-launch
版本可能依赖于管理良好的包含路径(这是 以 debian 为例)。 您可以使用选项 --version
查询 cl-launch
实例的配置。
例如,人们可能期望使用 cl-launch 的 debian 版本:
/usr/share/common-lisp/source/cl-launch/
作为系统管理的包含路径。 人们还可能期望系统管理的 Lisp 实现会附带在 Lisp 图像中预编译的 cl-launch
。 由于 cl-launch
提供了 :cl-launch
功能,并且由于 cl-launch
Lisp 标头被条件化为不能使用此功能读取,这将使 cl-launch
启动更快,同时仍然允许非 -system-managed Lisp 实现运行良好。
您可以使用以下命令创建 cl-launch 安装:
cl-launch --include /usr/share/common-lisp/source/cl-launch \
--lisp 'sbcl ccl clisp' \
--rc \
--output /usr/bin/cl-launch -B install
如果您只想配置 cl-launch(例如,使用不同的默认值 --lisp
但没有 --include
),则可以使用命令 -B install_bin
,如果您想配置,则可以使用命令 -B install_path
只想创建支持文件。 请注意,--backdoor
选项 -B
必须在您的调用中排在最后。
选项 +R --no-rc
指定 cl-launch
不应尝试读取资源文件 /etc/cl-launchrc
和 ~/.cl-launchrc
。
选项 -R --rc
指定 cl-launch 应该尝试读取资源文件 /etc/cl-launchrc
和 ~/.cl-launchrc
。 这些文件对于根据 $SOFTWARE_SYSTEM
定义覆盖 $LISP
的值特别有用。 提供了一个 shell 函数 system_preferred_lisps
,以便您的 cl-launchrc
可能包含如下行:
system_preferred_lisps stumpwm cmucl sbcl clisp
system_preferred_lisps exscribe clisp cmucl sbcl
请注意,为了解析选项 --no-rc
,资源文件在选项被处理后运行,并且任何内部变量的覆盖将因此抢占用户指定的选项。 发生此类覆盖时,将在标准错误输出上打印警告。 请注意,此类覆盖仅在脚本创建时发生。 由 cl-launch
创建的脚本不会尝试读取 cl-launch
资源文件。
选项 +Q --no-quicklisp
指定 cl-launch
不应使用 quicklisp
。 选项 -Q --quicklisp
指定 cl-launch
应该使用 quicklisp
。 哪个是默认值取决于您的安装。 默认默认是+Q
。 Quicklisp 从 ~/quicklisp/setup.lisp
加载(如果可用),否则从 ~/.quicklisp/setup.lisp
加载。
选项 -b --clbuild
指定 cl-launch
应该依赖 clbuild
来查找和调用 Common Lisp 实现。 选项 +b --no-clbuild
指定 cl-launch
不应依赖 clbuild
来查找和调用 Common Lisp 实现。 哪个是默认值取决于您的安装。 默认默认值是+b
。
cl-launch
生成的文件由几个易于识别的部分组成。 因此,这些部分可以被视为不同的软件,每个都可以在其自己的知识产权制度下使用(如果有的话)。 万一发生事故,您仍然可以通过剥离包装器来检索选项--file
提供的确切原始代码,由明确标识的标记分隔。 搜索标记字符串"BEGINS HERE:"
。 之后的一切都不是 cl-launch
。 这可以通过后门选项 -B extract_lisp_content
自动完成。 cl-launch
在嵌入使用选项 --file
指定的文件时隐式使用此功能,以便您可以处理先前由 cl-launch
生成的脚本并更改它包装嵌入式 Lisp 代码的选项 进入可运行的软件。
作为替代方案,您还可以升级先前生成的脚本以使用当前版本的 cl-launch
,同时使用选项 --update
保留其原始包装选项。 在这种情况下,软件规范选项将被忽略。 输出选项仍然适用。 指定-
(引用后)作为要更新的文件意味着从标准输入中读取要读取的内容。 此功能可能不适用于由非常早期版本的 cl-launch
实用程序生成的脚本。 它应该适用于 1.47 之后的版本。
支持的 Lisp 实现
当前 cl-launch 支持的 Lisp 实现包括:
abcl allegro ccl clisp cmucl ecl gcl lispworks sbcl scl xcl
还有几个别名:
clozurecl gclcvs lisp openmcl
分别代表了ccl
,gcl
,cmucl
,ccl
完全支持,包括生成独立可执行文件的实现:
sbcl: 1.2.2
clisp: 2.49
ecl: 13.5.1
cmucl: 20D
ccl: 1.10
lispworks: Professional 6.1.0
完全支持,但是不支持生成独立可执行程序的实现:
gcl: GCL 2.7.0 ansi mode (需要 checkout 最新的 git repo)
allegro: Allegro 9.0 (也曾与 5 一起使用)
scl: Scieneer CL 1.3.9
不完全支持:
abcl: ABCL 1.3.1 (不支持 dump image, 但是可以使用 abcl-jar)
xcl: XCL 0.0.0.291 (无法 dump image)
GCL
仅在 ANSI 模式下受支持。 cl-launch
确实导出了 GCL_ANSI=t,希望 gcl
包装脚本能像在 Debian 中那样做正确的事情。 此外,ASDF 3
需要最新的GCL 2.7
。 请注意,GCL
似乎不再被积极维护。
CLISP
上的独立可执行文件存在一些问题。 请参阅下面有关 独立可执行文件 的部分。
LispWorks
需要专业版; 不支持个人版,因为它不允许您控制命令行或转储映像。 转储的映像将打印横幅,除非您转储独立的可执行文件。 要转储映像,请确保您的目标目录中有许可证文件和/或 .../lispworks/lib/6-1-0-0/config/lwlicense (或使用反弹 shell 脚本 exec /path/to/lispworks "$@"
),创建一个构建脚本:
echo '(hcl:save-image "lispworks-console" :environment nil)' > si.lisp
lispworks-6-1-0-x86-linux -siteinit - -init - -build si.lisp
LispWorks 还要求您拥有 ASDF 3.1.2
或更高版本; 确保已在源注册中安装和配置它。 LispWorks 的纯控制台变体没有标准名称; 旧版本的 cl-launch 假定默认的 lispworks
; 从 4.1.2.1 开始,改为使用 lispworks-console
,以避免冲突。 您可以使用 shell 变量 $LISPWORKS
来控制使用的名称,或者您可以将 lispworks-console
留在路径中,并使用符号链接、副本、shell 别名或简单的包装脚本来启用您最喜欢的较短名称 lispworks
、lw
、lwcon
、lw-console
等。
同样,可以按如下方式创建 allegro 的 mlisp 映像:
alisp -e '(progn
(build-lisp-image "sys:mlisp.dxl"
:case-mode :case-sensitive-lower
:include-ide nil :restart-app-function nil)
(when (probe-file "sys:mlisp") (delete-file "sys:mlisp"))
(sys:copy-file "sys:alisp" "sys:mlisp"))'
此外,cl-launch
支持使用 clbuild
作为包装器来调用 Lisp 实现,并带有 --clbuild
选项。
支持的 shell
cl-launch
已经使用 posh 0.47, bash 2.05, bash 3.1, zsh 4.3.2, dash 0.5.3 和 busybox 1.01 ash 测试通过。
调用 Lisp 实现
当调用 cl-launch
生成的脚本时,cl-launch
shell 包装器将尝试使用它在给定列表中找到的第一个 Common Lisp 实现来执行 Lisp 代码,可以通过选项 --lisp 指定
。 cl-launch
shell 包装器的运行时行为可以通过一系列环境变量进行配置。 这些变量可以由用户通过将它们导出到他的环境中来控制,或者可以在脚本生成时使用 cl-launch 选项 --wrap
来限制它们。
如果定义了变量“LISP”,shell 包装器将首先尝试由变量“LISP”命名的实现。 如果失败,它将尝试在脚本生成时提供的实现列表。 如果指定,生成的实现列表将作为选项 --lisp
的参数。 否则,cl-launch
将提供其默认值。 cl-launch
的当前实例的默认值是:
sbcl ccl clisp abcl allegro lispworks scl cmucl ecl mkcl gcl xcl
此“LISP”选择仅在系统准备时发生。 如果您转储图像,则脚本将始终使用转储图像的 Lisp 实现。 如果你不这样做,那么用户可能会覆盖实现。
请注意,这些是 cl-launch
shell 包装器中内置的昵称,不一定是实际二进制文件的名称。 您可以控制实现昵称到实际二进制路径名的映射,以使用环境变量调用。 对于给定的实现昵称,环境变量将是给定昵称的大写。
因此,变量“$SBCL”控制在哪里寻找“sbcl”实现,而变量“$CMUCL”控制在哪里寻找“cmucl”实现。 如果找到具有匹配路径名的二进制文件(根据需要使用标准的 unix $PATH
),则将使用所述实现,使用适当的命令行选项,可以使用类似于之前的环境变量但使用 _OPTIONS
附加到其名称。
因此,$CMUCL_OPTIONS
用于 cmucl
,$CLISP_OPTIONS
用于 clisp
等。为每个实现提供了合理的默认值,以便在非交互模式下执行软件,禁用调试器,无需读取用户- 具体的配置文件等
如果您想坚持使用带有给定选项的给定实现,您可以使用选项 --lisp
和 --wrap
,如下所示:
--lisp 'sbcl clisp' --wrap '
LISP= # do not allow the user to specify his implementation
SBCL=/usr/bin/sbcl # not any experimental thing by the user
SBCL_OPTIONS="--noinform --sysinit /dev/null --userinit /dev/null \
--disable-debugger" # predictable Lisp state
CLISP=/usr/bin/clisp # fall back on machines that lack SBCL
CLISP_OPTIONS=" -norc --quiet --quiet"
# configure ASDF:
CL_SOURCE_REGISTRY=/usr/local/share/common-lisp/source//:
# assuming precompiled fasls there:
ASDF_OUTPUT_TRANSLATIONS=/my/cl/src:/my/fasl/cache:
如果您转储图像,则无需取消设置“LISP”变量,但您可能仍希望覆盖用户指定的任何用户指定的“SBCL”和“SBCL_OPTIONS”(或您选择的实现的相应变量)。
请注意,您可以使用选项 --wrap "$(cat your_script)"
将文件中的完整脚本嵌入到您的程序中。 您的脚本可能会在运行 shell 包装器之前进行任意计算。 它可能会在运行 Lisp 之前进行一些一致性检查并中止。 或者它可以分析调用参数并对 Lisp 实现选项进行相应的调整。 这对于设置无法从 Lisp 代码设置的选项很有用,例如运行时映像的路径、交互式或非交互式执行、堆大小、源文件编码的区域设置等。
阅读 cl-launch
的源代码可能会非常疯狂。 您可能会非常有趣地了解事物为何如此,并在不破坏任何东西的情况下添加功能! 但是,添加对新 CL 实现的支持应该很简单:只需搜索 clisp
或 sbcl
的源代码并模仿我为它们所做的事情。 一定要把你最喜欢的本月 Lisp 风格发给我。
有限的 clbuild 支持
cl-launch
2.12 和更高版本支持使用 clbuild
作为包装器来配置您的 Lisp 实现,带有选项 --clbuild
(如果它在默认情况下启用,则可以使用选项 --no-clbuild
禁用它 您的 cl-launch
安装)。
请注意,当您使用 clbuild
时,您不能再使用 SBCL_OPTIONS
来覆盖实现选项,因为 clbuild 会为您处理选项。 除非您指示 clbuild 这样做,否则不会删除任何实现横幅。 此外,您不能将 clbuild 与不同于 clbuild
的非可执行图像一起使用,这会排除使用 cmucl
或 allegro
进行图像转储(allegro
可能会更新,但我没有最近的许可证 测试和开发)。
clbuild
支持目前尚未完全测试。 请报告任何错误。
简单的 cl-launch 脚本
在简单的情况下,您可以使用 cl-launch
创建一个 Common Lisp shell 脚本,而无需脚本生成步骤,因为您将花费大量时间编辑和分发它,而等待脚本启动时间的时间却很少 . 如果您没有在给定计算机上生成许多相同版本的脚本实例,那么这尤其是一个好主意。 如果这是您想要的,您可以使用 cl-launch
作为脚本解释以下方式(去除前导空格):
#!/path/to/cl-launch ...options...
例如,您可以编写以下脚本(去除前导空格):
#!/usr/bin/cl --entry main
(defun main (argv)
(format t "Hello, World!~%~S~%" argv))
在最近的 Linux 内核上,选项可能包括空格、括号等,前提是它们在 shell 脚本中被引用。 此外,使用 -X
作为你的第一个选项和 --
作为你的最后一个选项将确保脚本工作,即使它的名称以 (
或 -
开头,除了使用旧版本的 cl-launch
。
但是请注意,Darwin (MacOS X) 和其他 BSD 内核或旧的 Linux 内核不喜欢 #!
解释器本身被解释。 在这些操作系统内核上,系统管理员必须编译和安装一个用 C 编写的小 shim,cl-shim.c
,它将处理正确的脚本调用。
大多数内核对如何处理 #!
脚本的参数都有限制,这会阻止例如 使用 /usr/bin/env
作为蹦床; 但是,您可以使用以下完全可移植的解决方案,其中 ":" ;
确保脚本应保持有效的双语 shell 和 Lisp 代码:
#!/bin/sh
":" ; exec cl-launch -X -sp my-package -E main -- "$0" ${1+"$@"} || exit
(实际上用 "$@"
而不是 ${1+"$@"}
应该可以正常工作,除非您有古董 shell。)
请注意,如果您不需要从脚本中加载 Lisp 代码,并且一切都发生在构建规范中,那么您可以改用简单的 #!/bin/sh
shell 脚本,您可以从中:
exec /path/to/cl-launch -x ... -- "$@".
此外,如果您不能依赖 cl-launch
位于固定路径,或者如果您的 shell 和/或内核组合不支持使用 cl-launch
作为脚本解释器,那么您可以改为启动 您的脚本包含以下几行:
#!/bin/sh
":" ; exec cl-launch -X -- "$0" "$@" || exit
(format t "It works!~%")
请注意,自 2.6.27.9 起,主线 Linux 内核仅支持 #!/usr/bin/cl-launch
中隐含的递归 #!
。
转储映像
您可以使用选项 --dump IMAGE
转储图像(用于静态编译和快速启动),其中 IMAGE
指定转储图像的路径。
如果您使用选项 --include PATH
,则图像将从该指定目录而不是您转储它的目录加载回来。 如果您正在准备将脚本安装在另一个地方(可能是在另一台计算机上),这很有用。
cl-launch
提供的所有 CL 实现当前都支持此选项。
作为限制,LispWorks
将在标准输出上打印横幅,除非您使用下面的独立可执行选项。
作为另一个限制,当从先前转储的图像(使用 --image
)运行时,ECL
将无法转储图像。 这是因为 ECL 的链接模型,您需要能够定位在链接原始图像时使用了哪些目标文件,跟踪这些文件,并将它们的列表添加到链接到的目标文件中 垃圾场。 这在概念上并非不可能,并且欢迎使用补丁。 但是,我们希望有一天能够通过一个真正的构建系统来支持这一点,例如 XCVB。
独立可执行程序
您可以使用选项 --dump '!'
创建独立的可执行文件(或通过提供与 --output
参数相同的 --dump
参数)。
目前只有 SBCL
、ECL
、CLISP
、CMUCL
、CCL
和 LispWorks
Professional 支持此选项。 此外,CLISP
有以下问题。
如果使用诸如 --clisp-help
或 --clisp-x '(sys::main-loop)'
之类的选项调用,CLISP
独立可执行文件会做出神奇的反应。 误打误撞是一件牵强附会的事情,“CLISP”维护者认为它是一个特性(我不这么认为)。 不要使用诸如 setuid
之类的可执行文件,也不要让不受信任的用户控制赋予这些以额外权限运行的可执行文件的参数。
cl-launch 运行时 API
cl-launch
提供了以下 Lisp 函数:
函数 cl-launch:compile-and-load-file
将源路径名指示符和关键字参数 force-recompile
(默认为 NIL
)和 verbose
(默认为 NIL
)作为参数。 如果明确要求,或者文件不存在,或者 fasl 不是最新的,它将安排编译指定的源文件。 它将以指定的详细程度进行编译和加载。 它将使用 uiop:compile-file-pathname*
来确定 fasl 路径名。
cl-launch
之前提供的以下变量和函数已替换为 ASDF
和 UIOP
:
变量 cl-launch:*arguments*
被替换为 uiop:*command-line-arguments*
。
函数 cl-launch:getenv
被替换为 uiop:getenv
。
函数 cl-launch:load-system
被替换为 asdf:load-system
。
函数 cl-launch:quit
被 uiop:quit
替换(注意:lambda-list 略有不同)。
此外,环境变量 CL_LAUNCH_PID
和 CL_LAUNCH_FILE
将分别设置为进程 ID 和脚本调用文件名。
详细输出模式
如果shell变量CL_LAUNCH_VERBOSE
被导出并且非nil
,那么cl-launch
和它生成的脚本会产生大量的输出,显示诸如Lisp调用命令,编译和加载文件用: 详细的 t
和 :print t
等。这仅对调试 cl-launch
和/或您的构建过程有用。 选项 --verbose
设置此变量,而选项 --quiet
重置它。
Makefile 的例子
### Automatically download of the current version of cl-launch if not present
cl-launch.sh:
wget -O cl-launch.sh http://fare.tunes.org/files/cl-launch/cl-launch.sh
chmod a+x cl-launch.sh
### Making a shell script executable from a simple Lisp file named foo.lisp
foo.sh: cl-launch.sh foo.lisp
./cl-launch.sh --output foo.sh --file foo.lisp
### A more complex example using all options.
run-foo.sh: cl-launch.sh preamble.lisp
./cl-launch.sh --output run-foo.sh \
--file preamble.lisp --system foo \
--init "(foo:main uiop:*command-line-arguments*)" \
--source-registry ${PREFIX}/cl-foo/systems: \
--lisp "ccl sbcl" --wrap 'SBCL=/usr/local/bin/sbcl-no-unicode' \
--no-include
### An example with horrible nested makefile, shell and Lisp quoting
hello:
opera=wORlD ; ./cl-launch.sh --execute --init \
"(format t \"~25R~A~A~%\" 6873049 #\\space '$$opera)"
给 Lispor 的警告
cl-launch
开始在 cl-user
包或您指定的任何包中求值您的 Lisp 软件。 在求值初始化表达式时,包可能已经更改,也可能没有更改,具体取决于“加载”的细粒度语义。 如果这些事情很重要,请务必使用in-package
。 如果您更改 readtable,甚至可能会发生更奇怪的事情。
在编写 shell 命令时,由于引用不当,有很多方法会出错。 cl-launch
做了正确的事,但你仍然必须小心 make
、shell 和 Lisp 的嵌套引用机制。
下面是一个使用 cl-launch 的简单示例,用于快速比较各种系统上相同计算的结果:
for l in sbcl cmucl clisp gcl ccl ; do
./cl-launch.sh --lisp $l --execute --init \
'(format t "'$l' ~A~%" most-positive-fixnum)' ; done
在内部,cl-launch
包含许多自检功能。 例如,您可以尝试(从可能创建垃圾的目录中):
./cl-launch.sh -l 'sbcl cmucl clisp gclcvs' -B tests
分享和享受!
请参阅我们的网页:http://www.cliki.net/cl-launch
注意:如果此帮助对您来说太长,您可以向后滚动,或使用:
cl --more-help | less