如何配置一个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 开发环境会既乏味又耗时。

它涉及以下的安装和配置项:

  1. 一个 Common Lisp 实现 —— 通常是 sbcl

  2. emacs —— Common Lisp 开发中首选的非商业编辑器;

  3. quicklisp —— Common Lisp 包管理器的金标准;

  4. 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

rlwrapsbcl非常有用,因为“裸”的 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.手动方式

  1. 全局安装 emacs
$ sudo apt install emacs
     
# 后台运行 emacs
$ emacs &
  1. 通过 Roswell 安装 slime
$ ros install slime

看来这还不够,还需要通过在 emacs 中安装 slime: M-x install-package RET slime RET

  1. 在 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)
  1. 重新打开 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 并假设它依赖于包 alexandriacl-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 中的测试通常使用 FiveAMProve 来完成。

roveProve的接班人,和 Roswell 配合得很好。

安装:

$ ros install fukamachi/rove

在你的 REPL 中首次载入它:

(ql:quickload :rove)

进入它的命名空间:

(in-package :rove)

测试的语法很简单

肯定和否定测试

通常,您在执行测试时检查真或假。rove 为您提供了 okng(否定)函数进行检查。

;; 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.lispdefpackage 表达式所做的那样。

如果我们创建多个测试文件/套装,每个文件都必须在 .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 commitpush 它,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: 部分必须存在,因为 clispabclalisp 和/或 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。但是,许多信息可在包和工具的文档中找到。)

posted @ 2022-02-23 17:22  fmcdr  阅读(984)  评论(0编辑  收藏  举报