如何配置一個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。但是,許多信息可在包和工具的文檔中找到。)