如何配置一个Common Lisp 集成开发环境 - 使用 Roswell 在 Ubuntu 中快速开始 Common Lisp 编程
如何配置一个Common Lisp IDE - 使用 Roswell 在 Ubuntu 中快速开始 Common Lisp 编程
[原文]https://towardsdatascience.com/how-to-set-up-common-lisp-ide-in-2021-5be70d88975b
如果走错了路,配置一个 Common Lisp 开发环境会既乏味又耗时。
它涉及以下的安装和配置项:
-
一个 Common Lisp 实现 —— 通常是
sbcl
; -
emacs
—— Common Lisp 开发中首选的非商业编辑器; -
quicklisp
—— Common Lisp 包管理器的金标准; -
slime
——emacs
和 CL 实现(sbcl)之间的桥梁。
这个过程充满了陷阱,可能会令人不知所措。尤其是对于初学者而言。幸运的是,现在有了 roswell
!
roswell
是一个现代的 Common Lisp 虚拟环境和包管理器,它不仅允许将 Common Lisp 的不同实现(sbcl, ecl, allegro 等)安装到单独的虚拟环境中,还可以将它们的不同版本安装到单独的虚拟环境中。Roswell 允许在不同的实现和版本之间切换,这对于 Common Lisp 包的开发人员来说必不可少。因此,Roswell 应该是任何严肃的 Common Lisp 程序员的好伙伴。
此外,Roswell 允许设置测试环境并集成 CI(参见此处)。
在 Ubuntu 20.04 LTS 中安装 Roswell
# Following instructions in github of roswell: # 安装到系统侧 sudo apt-get -y install git build-essential automake libcurl4-openssl-dev zlib1g-dev git clone -b release https://github.com/roswell/roswell.git cd roswell sh bootstrap ./configure make sudo make install ros setup # 安装到用户侧 git clone -b release https://github.com/roswell/roswell.git cd roswell sh bootstrap ./configure --prefix=$HOME/.local make make install echo 'PATH=$HOME/.local/bin:$PATH' >> ~/.profile PATH=$HOME/.local/bin:$PATH ros setup
或者,您也可以按照以下步骤操作:
# install dependencies for ubuntu $ sudo apt install libcurl4-openssl-dev automake # download Roswell installation script $ curl -L https://github.com/roswell/roswell/releases/download/v19.08.10.101/roswell_19.08.10.101-1_amd64.deb --output roswell.deb # run the installation $ sudo dpkg -i roswell.deb # add roswell to PATH to your ~/.bashrc # (important for scripts to run correctly!) export PATH="$HOME/.roswell/bin:$PATH" # don't forget to source after adding to ~/.bashrc if youare still # in the same session and # if you don't want to restart your computer: $ source ~/.bashrcalso
设置 Roswell 后,我们可以通过以下方式检查可用的 Common Lisp 实现:
$ ros install # this prints currently: Usage: To install a new Lisp implementaion: ros install impl [options] or a system from the GitHub: ros install fukamachi/prove/v2.0.0 [repository... ] or an asdf system from quicklisp: ros install quicklisp-system [system... ] or a local script: ros install ./some/path/to/script.ros [path... ] or a local system: ros install ./some/path/to/system.asd [path... ] For more details on impl specific options, type: ros help install implCandidates impls for installation are: abcl-bin allegro ccl-bin clasp-bin clasp clisp cmu-bin ecl mkcl sbcl-bin sbcl sbcl-source
安装不同的 Common Lisp 实现和不同的版本
# these are examples how one can install specific implementations and versions of them: $ ros install sbcl-bin # 默认的 sbcl $ ros install sbcl # 最新发布的 sbcl $ ros install ccl-bin # 预编译的 ccl $ ros install sbcl/1.2.0 # 安装指定的 sbcl 版本
我建议安装最新的 sbcl, sbcl-bin 似乎不支持多线程。
ros install sbcl
要列出已安装的实现和版本,请执行以下操作:
ros list installed
在不同的实现和版本之间切换
检查当前使用的实现/版本:
$ ros run -- --version SBCL 1.2.15
切换一另一个实现/版本:
$ ros use sbcl/2.1.7
启动 REPL
$ ros run # 或者使用 rlwrap 包裹运行 $ rlwrap ros run
rlwrap
对sbcl
非常有用,因为“裸”的 sbcl REPL不允许在行内跳转或其它有用的编辑命令,这是使用 Ubuntu shell 形成的习惯。
使用 Roswell 安装 Common Lisp 的包
任何可以使用 quicklisp
的 (ql:quickload ...)
安装的包,现在都可以用 roswell 从命令行安装进当前激活的实现中,方法是:
# 从 quicklisp 的仓库安装 $ ros install rove # 直接从 github 安装 $ ros install fukamachi/rove # 更新已安装的包 $ ros update rove
安装 emacs 和 slime 并将它们与 Roswell 连接
A.简单方式(目前好像不行了)
通常,可以通过 Roswell 中的一条命令安装和设置 emacs,其中 quicklisp 连接并运行 lem ,这是日本开发人员 cxxxr 开发的 CL 特殊模式。
$ ros install cxxxr/lem # 启动 lem $ lem
不幸的是,目前 lem 安装存在问题。
因此,必须手动安装 emacs 并手动将其与 Roswell 连接。
B.手动方式
- 全局安装 emacs
$ sudo apt install emacs # 后台运行 emacs $ emacs &
- 通过 Roswell 安装 slime
$ ros install slime
看来这还不够,还需要通过在 emacs 中安装 slime: M-x install-package RET
slime RET
- 在 emacs 的配置文件
~/.emacs.d/init.el
, 通过 slime 连接 Roswell。
在 emacs 中,通过按 C-x C-f 打开 ~/.emacs.d/init.el
Emacs 快捷键符号:
- C 代表 Ctrl
- M 代表 Alt
- S 代表 Shift
- SPC 代表空格键
- RET 代表回车键
- 组合键表示按住一个键的同时按下另外一个键,例如:先按下 Ctrl 键,不要放开,再按下 x, 这个组合键写成
C-x
C-x C-f
表示按下 Ctrl
的同时按下 x
, 放开,然后,按下 Ctrl
的同时再按下 f
,再放开。
在 emacs 中打开~/emacs.d/init.el
后,我们按照 Roswell wiki 中的说明写了下面的内容:
;; 初始化并启用 emacs 的包管理器 (require 'package) (setq package-enable-at-startup nil) (setq package-archives '()) ;; 使用清华的镜像源 (setq package-archives '(("gnu" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/") ("melpa" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/"))) ;; 初始化包列表 (package-initialize) (package-refresh-contents) ;; 确保 `use-package` 已经安装,如果就有的话就安装 (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; slime for common-lisp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; to connect emacs with roswell (load (expand-file-name "~/.roswell/helper.el")) ;; for connecting slime with current roswell Common Lisp implementation (setq inferior-lisp-program "ros -Q run") ;; for slime;; and for fancier look I personally add: (setq slime-contribs '(slime-fancy)) ;; ensure correct indentation e.g. of `loop` form (add-to-list 'slime-contribs 'slime-cl-indent) ;; don't use tabs (setq-default indent-tabs-mode nil) ;; set memory of sbcl to your machine's RAM size for sbcl and clisp ;; (but for others - I didn't used them yet) (defun linux-system-ram-size () (string-to-number (shell-command-to-string "free --mega | awk 'FNR == 2 {print $2}'"))) (setq slime-lisp-implementations `(("sbcl" ("sbcl" "--dynamic-space-size" ,(number-to-string (linux-system-ram-size)))) ("clisp" ("clisp" "-m" ,(number-to-string (linux-system-ram-size)) "MB")) ("ecl" ("ecl")) ("cmucl" ("cmucl"))))
保存并关闭
- C-x C-s (保存当前缓冲区/文件)
- C-x C-c (关闭 emacs)
-
重新打开 emacs
emacs &
要尝试 emacs 和 Common Lisp 实现(Roswell 中当前激活的实现),请创建一个 test.lisp
文件:
- 在 emacs 中打开/创建文件: C-x C-f 然后输入:
test.lisp
,敲回车
如果原先就存在test.lisp
文件,emacs 会打开它,如果不存在就新建一个。
- 在 emacs 中运行 slime: M-x slime RET
在当前缓冲区的下面会出现一个新的缓冲区,你应该可以在里面看到 slime REPL 的提示符:
; SLIME 2.26.1 CL-USER>
最好的事情是,现在,test.lisp
与 Roswell 中当前的 CL 实现连接(通过 slime
)。 您可以将光标移动到 test.lisp
文件中任意 lisp 表达式的末尾,然后按 C-x C-c :emacs 会将这个表达式转发到连接的 Common Lisp 实现(在我们的例子中是 sbcl)并执行该表达式。
要通过 vim 或 atom 而不是 emacs 进行连接,请查看 Roswell GitHub 站点的 wiki
在下文中,我们将会了解如何创建 Common Lisp 包(或项目),如何设置测试,尤其是 Travis CI 的自动化测试并监控测试的代码覆盖率。
使用 cl-project 开始一个 Common Lisp 包/项目
可以通过 cl-project
自动生成一个包的骨架。通过 Roswell 安装:
$ ros install fukamachi/cl-project
进入 Roswell 的 local-projects
文件夹,因为我们要先将包保存在本地机器上。
$ cd ~/.roswell/local-projects
现在创建您的项目主干——让我们将项目称为 my-project
并假设它依赖于包 alexandria
和 cl-xlsx
:
$ make-project my-project --depends-on alexandria cl-xlsx
用tree
命令查看目录结构:
$ tree my-project my-project ├── my-project.asd ├── README.markdown ├── README.org ├── src │ └── main.lisp └── tests └── main.lisp 2 directories, 5 files
由 Common Lisp 标准的包系统 ASDF(Another System Definition Facility)构建的元信息项目文件 my-project/my-project.asd
的内容为:
(defsystem "my-project" :version "0.1.0" :author "" :license "" :depends-on ("alexandria" "cl-xlsx") :components ((:module "src" :components ((:file "main")))) :description "" :in-order-to ((test-op (test-op "my-project/tests")))) (defsystem "my-project/tests" :author "" :license "" :depends-on ("my-project" "rove") :components ((:module "tests" :components ((:file "main")))) :description "Test system for my-project" :perform (test-op (op c) (symbol-call :rove :run c)))
Common Lisp 中的测试被组织为单独的包——因此,.asd
文件将项目的测试定义为单独的包,并添加了 :rove
作为其依赖项之一。
填写作者(:author
)、许可(:license
)和描述(:description
)部分。
在你的项目中,当引入新的包时,它们应该首先包含在两个包系统的 :depends-on
列表中。
:components
部分列出了属于该系统的所有文件和文件夹(列出的文件名没有扩展名 .lisp
!)。 因此,无论何时添加新文件或文件夹,都应更新此文件中的两个 defsystem
!
my-project/src/main.lisp
的内容是:
(defpackage my-project (:use :cl)) (in-package :my-project) ;; blah blah blah.
用你的代码替换掉 ;; blah blah blah
为了避免在引用其它包的符号时加上包前缀,可以将其它包添加到 use
列表中,在当前的情况下,(:use :cl :alexandria :cl-xlsx)
是合理的。
或者,如果您想从这些包中导入单独的函数,则仅通过以下方式显式导入这些函数:
(defpackage my-project (:use #:cl #:cl-xlsx) ;; this package's entire content is imported (:import-from #:alexandria ;; package name #:alist-hash-table) ;; a function from :alexandria (:export #:my-new-function))
在 :export
子句中,您可以声明哪些函数应该被导出并公开——从这个文件外部可见。
设置用 Roswell 和 rove 测试
Common Lisp 中的测试通常使用 FiveAM
或 Prove
来完成。
rove
是Prove
的接班人,和 Roswell 配合得很好。
安装:
$ ros install fukamachi/rove
在你的 REPL 中首次载入它:
(ql:quickload :rove)
进入它的命名空间:
(in-package :rove)
测试的语法很简单
肯定和否定测试
通常,您在执行测试时检查真或假。rove
为您提供了 ok
和 ng
(否定)函数进行检查。
;; expect true (ok (= (+ 1 2) 3)) ;; expect false (ng (eq 'a 'a)) ;; add error message (ok (= 1 1) "equality should be true") (ng (< 3 2) "3 and 2 should not be equal")
您可以检查错误、标准输出和宏展开:
;; tests for a specific, expected error (ok (signals (/ 1 0) 'division-by-zero)) ;; tests for error occurrence itself without exact specification (ok (signals (/ 1 0)))
输出到控制台:
(ok (outputs (format t "hello") "hello"))
下面是有些蠢的宏展开示例
(defmacro my-sum (&rest args) `(+ ,@args)) (ok (expands '(my-sum 1 2 3) '(+ 1 2 3))) ;; which is: (ok (expands '<macro-call> '<expected macroexpand-1 result>))
将几个测试分组,并命名和注释
一个人应该写下为什么/为什么要做测试。 有人说“测试是最精确的文档形式”。 ——所以它们也应该有可读性。
(deftest <test-name> (testing "what the test is for" (ok <expr>) (ok <expr>) (ng <expr>)) (testing "another subpoint" (ok <expr>) (ng <expr>) (ng <expr>)))
这是一个示例函数及其测试:
(defun my-absolute (x) (if (< x 0) (* -1 x) x))
这是可能的测试:
(deftest my-absolute (testing "negative input should give positive output" (ok (eq (my-absolute -3) 3)) (ng (eq (my-absolute -3) -3))) (testing "positive input should give positive output" (ok (eq (my-absolute 3.1) +3.1)) (ng (eq (my-absolute 2.3452) + 2.4352))) (testing "zero should always give zero" (ok (zerop (my-absolute 0)) (zerop (my-absolute 0.0000000))))
从控制台运行测试
(run-test 'my-absolute)
将一项或多项测试组合成测试包(测试套件)
想象一下,您创建了一个名为 my-package
的项目或包。
在包/项目文件夹中应该有一个tests
文件夹,其中包含该包的测试文件(测试套件)。
cl-project
已经自动创建了测试的骨架,在我们的例子中是 tests/main.lisp
。
它自动生成的内容是:
(defpackage my-project/tests/main (:use :cl :my-project :rove)) (in-package :my-project/tests/main) ;; NOTE: To run this test file, execute `(asdf:test-system :my-project)' in your Lisp. (deftest test-target-1 (testing "should (= 1 1) to be true" (ok (= 1 1))))
每当我们需要包作为新的依赖项或它们的单个函数时,我们应该修改这个 defpackage
定义,类似于我们在 my-project/src/main.lisp
的 defpackage
表达式所做的那样。
如果我们创建多个测试文件/套装,每个文件都必须在 .asd
文件的 defsystem
的 :components
部分中注册。 这样 (asdf:test-system :my-project)
就包含了所有的测试套件。
添加准备和收尾过程
setup
函数在测试包之前求值其列出的命令一次。 这很有用,例如 用于建立数据库或创建临时目录。
测试运行后,可以使用 teardown
命令清理临时目录或其他生成的文件。
;; after the defpackage and in-package declarations: (setup (ensure-directories-exist *tmp-directory*)) ;; all the tests of the package (teardown (uiop:delete-directory-tree *tmp-directory* validate t :if-does-not-exist :ignore))
对于应该在每次测试之前或之后运行的命令,请使用:
(defhook :before ...) (defhook :after ...)
为了让测试具有不同的风格,您可以指定全局变量:
(setf *rove-default-reporter* :dot) ;; other: :spec :none (setf *rove-debug-on-error* t)
运行测试套件
(asdf:test-system :my-project) (rove:run :my-project/tests) ;; you can modify their style when calling to run (rove:run :my-project/tests :style :spec) ;; other :dot :none
另一个测试套装 FiveAM
也非常类似,
使用 Travis-CI 自动化测试
借助 https://raw.githubusercontent.com/roswell/roswell/release/scripts/install-for-ci.sh 中提供的脚本,Travis CI 的设置大大简化,该脚本负责在 Roswell 中安装所有可用的实现。
首先,必须在 Travis CI 中注册一个帐户。
这里和这里说明了将 Travis CI 与 GitHub 或 Bitbucket 或 GitLab 连接。
总而言之:必须单击个人资料图片并单击设置,然后选择您希望 Travis CI 监控的存储库。 (在我们的例子中,这将是包含 my-project
的 GitHub 存储库)。
下一步要做的就是创建一个名为 .travis.yml
的文件并将其放在项目根文件夹中(由 git 运行)。 一旦你 git 添加新的 .travis.yml
文件并 git commit
并 push
它,Travis CI 将开始“照管”你的存储库。
对于仅使用最新的 SBCL 二进制文件进行测试,一个非常简短、简约的 .travis.yml
就足够了:
language: common-lisp sudo: required install: - curl -L https://raw.githubusercontent.com/roswell/roswell/release/scripts/install-for-ci.sh | sh script: - ros -s prove -e '(or (rove:run :my-project/tests) (uiop:quit -1))'
通过将 sudo: 字段设置为 false,可以激活 Travis CI 以使用新的基于容器的基础架构(测试的启动时间比旧系统更快)。
下面的 .travis.yml
脚本让 Roswell 在每次提交和 git push 代码时通过两个实现运行 rove
测试。
language: common-lisp sudo: false env: global: - PATH=~/.roswell/bin:$PATH - ROSWELL_INSTALL_DIR=$HOME/.roswell matrix: - LISP=sbcl-bin - LISP=ccl-bin install: - curl -L https://raw.githubusercontent.com/roswell/roswell/release/scripts/install-for-ci.sh | sh - ros install rove - ros install gwangjinkim/cl-xlsx script: - ros -s rove -e '(or (rove:run :my-project/tests :style :dots) (uiop:quit -1))'
env:
下的 matrix:
部分列出了要测试的 Common Lisp 的不同版本和实现。
这是一个完整的 .travis.yml
文件,用于测试更多的 Common Lisp 实现。
language: common-lisp sudo: false addons: apt: packages: - libc6-i386 - openjdk-7-jre env: global: - PATH=~/.roswell/bin:$PATH - ROSWELL_INSTALL_DIR=$HOME/.roswell matrix: - LISP=sbcl-bin - LISP=ccl-bin - LISP=abcl - LISP=clisp - LISP=ecl - LISP=cmucl - LISP=alisp matrix: allow_failures: - env: LISP=clisp - env: LISP=abcl - env: LISP=ecl - env: LISP=cmucl - env: LISP=alisp install: - curl -L https://raw.githubusercontent.com/roswell/roswell/release/scripts/install-for-ci.sh | sh - ros install fukamachi/rove - ros install gwangjinkim/cl-xlsx cache: directories: - $HOME/.roswell - $HOME/.config/common-lisp script: - ros -s rove -e '(or (rove:run :my-project/tests :style :dots) (uiop:quit -1))'
addons: apt:
部分必须存在,因为 clisp
、abcl
、alisp
和/或 cmucl
需要 openjdk-7-jre
,而 clisp
需要必须使用 apt
安装的 libc-i386
。
cache:
下列出的目录被缓存(为了更快地启动)。
install:
部分列出了当缺少其中一个组件时要为您的系统完成的安装——按照这个给定的顺序。
这些实现由 https://raw.githubusercontent.com/roswell/roswell/release/scripts/install-for-ci.sh 脚本准备,因此单个实现的安装不必在安装下列出: 明确地。 如果没有 Roswell,测试不同的实现需要做更多的工作。 Roswell 使用最新版本的实现进行安装和测试。
如果您的测试包本身有一些更简单的命令行命令可用,则 script:
命令看起来会更简单。
查看 Travis CI 文档以获取更多信息(在不同操作系统上进行测试等)
工作完成后,让 Travis CI 通过电子邮件或 telegram 通知您
将下面的内容添加到 .travis.yml
notifications: email: - my-email@gmail.com
每次运行作业时都会通知您。
[略]
使用 Coveralls 将代码覆盖率添加到 Travis
为此,我们遵循 Roswell 的文档或 Boretti 的博客, 使用 Roswell 独立脚本和 FiveAMAs 脚本作为测试系统。
和 Travis CI 类似,你需要先创建一个 Coveralls)帐号
然后,您在 .travis.yml
文件中标记应评估其代码覆盖率的实现:
env: matrix: - LISP=sbcl COVERALLS=true - LISP=ccl
并且还应指定安装 coveralls 的命令:
install: # Coveralls support - ros install fukamachi/cl-coveralls
最后,脚本的调用必须修改为:
- ros -s rove -s cl-coveralls -e '(or (coveralls:with-coveralls (:exclude (list "t")) (rove:run :quri-test)) (uiop:quit -1))'
结束 —— 这才是开始
现在,我们已经为 2021 年构建了一个完整的 Common Lisp IDE 系统。
请享受 Lisp Hacking!
(欢迎在评论区提出建议和反应!——关于设置 Roswell 和 cl-project
以及使用 rove
进行测试的部分,我从我的日本朋友那里了解到,他们用日语写了一本书:SURVIVAL COMMON LISP——作者是:Masatoshi Sano、Hiroki Noguchi、Eitaro Fukamachi、gos-k、Satoshi Imai、cxxxr、Takafumi Harashima。但是,许多信息可在包和工具的文档中找到。)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂