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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM