lemを動かすのにちょっと苦労したのでメモ.
TL;DR
ros install cxxxr/lem
vi ~/.roswell/lisp/quicklisp/dists/quicklisp/software/cl-charms-****/src/low-level/curses-bindings.lisp
"libncursesw.so.6"
を読み込んでいる箇所をコメントアウトし,libncursesw.so.5
が読み込まれるようにする
lem
lemとは
lemとは,Common Lispで書かれたEmacsライクなエディタである.
Common Lispで設定を記述することができ(拡張を書くことができ),内部でCommon Lisp処理系を起動したり,外部で起動したCommon Lisp処理系にSWANKプロトコルで接続することもできる,Common Lispとの相互運用を意識した高機能なエディタである. また,よく使う機能(pareditやシンタックスハイライト,auto-complete)は最初から移植されており,いきなり使えるようになっている.
利用開始時にコードや拡張がコンパイルされるため,かなり高速に起動するのが特徴である.
lemが動かない
自分もこれをインストールしようと思っていたが,ncursesまわりのトラブルで起動すらできなかった.以下のようなエラーだ.
% ros install lem % lem Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING{10005305B3}>: Trying to access undefined foreign variable "COLORS". 0: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<SIMPLE-ERROR "Trying to access undefined foreign variable ~S." {1002C84F93}> #<unused argument> :QUIT T) 1: (SB-DEBUG::RUN-HOOK SB-EXT:*INVOKE-DEBUGGER-HOOK* #<SIMPLE-ERROR "Trying to access undefined foreign variable ~S." {1002C84F93}>) 2: (INVOKE-DEBUGGER #<SIMPLE-ERROR "Trying to access undefined foreign variable ~S." {1002C84F93}>) 3: (ERROR "Trying to access undefined foreign variable ~S." "COLORS") 4: (CFFI::FS-POINTER-OR-LOSE "COLORS" CL-CHARMS/LOW-LEVEL::LIBCURSES) 5: (CL-CHARMS/LOW-LEVEL::%VAR-ACCESSOR-*COLORS*) 6: (LEM.TERM:TERM-INIT) 7: ((:METHOD LEM-INTERFACE:INVOKE (LEM-NCURSES::NCURSES T)) #<unused argument> #<CLOSURE (LAMBDA (&OPTIONAL LEM::INITIALIZE LEM::FINALIZE) :IN LEM:LEM) {1002C8446B}>) [fast-method] 8: (SB-INT:SIMPLE-EVAL-IN-LEXENV (APPLY (QUOTE MAIN) ROSWELL:*ARGV*) #<NULL-LEXENV>) 9: (SB-INT:SIMPLE-EVAL-IN-LEXENV (ROSWELL:QUIT (APPLY (QUOTE MAIN) ROSWELL:*ARGV*)) #<NULL-LEXENV>) 10: (SB-EXT:EVAL-TLF (ROSWELL:QUIT (APPLY (QUOTE MAIN) ROSWELL:*ARGV*)) NIL NIL) 11: ((LABELS SB-FASL::EVAL-FORM :IN SB-INT:LOAD-AS-SOURCE) (ROSWELL:QUIT (APPLY (QUOTE MAIN) ROSWELL:*ARGV*)) NIL) 12: (SB-INT:LOAD-AS-SOURCE #<CONCATENATED-STREAM :STREAMS NIL {1002BB34B3}> :VERBOSE NIL :PRINT NIL :CONTEXT "loading") 13: ((FLET SB-FASL::THUNK :IN LOAD)) 14: (SB-FASL::CALL-WITH-LOAD-BINDINGS #<CLOSURE (FLET SB-FASL::THUNK :IN LOAD) {7F757CD5753B}> #<CONCATENATED-STREAM :STREAMS NIL {1002BB34B3}>) 15: ((FLET SB-FASL::LOAD-STREAM :IN LOAD) #<CONCATENATED-STREAM :STREAMS NIL {1002BB34B3}> NIL) 16: (LOAD #<CONCATENATED-STREAM :STREAMS NIL {1002BB34B3}> :VERBOSE NIL :PRINT NIL :IF-DOES-NOT-EXIST T :EXTERNAL-FORMAT :DEFAULT) 17: ((FLET ROSWELL::BODY :IN ROSWELL:SCRIPT) #<SB-SYS:FD-STREAM for "file /home/windymelt/.roswell/bin/lem-ncurses" {1002BB1AE3}>) 18: (ROSWELL:SCRIPT "/home/windymelt/.roswell/bin/lem-ncurses") 19: (ROSWELL:RUN ((:EVAL "(ros:quicklisp)") (:SCRIPT "/home/windymelt/.roswell/bin/lem-ncurses") (:QUIT NIL))) 20: (SB-INT:SIMPLE-EVAL-IN-LEXENV (ROSWELL:RUN (QUOTE ((:EVAL "(ros:quicklisp)") (:SCRIPT "/home/windymelt/.roswell/bin/lem-ncurses") (:QUIT NIL)))) #<NULL-LEXENV>) 21: (EVAL (ROSWELL:RUN (QUOTE ((:EVAL "(ros:quicklisp)") (:SCRIPT "/home/windymelt/.roswell/bin/lem-ncurses") (:QUIT NIL))))) 22: (SB-IMPL::PROCESS-EVAL/LOAD-OPTIONS ((:EVAL . "(progn #-ros.init(cl:load \"/usr/local/etc/roswell/init.lisp\"))") (:EVAL . "(ros:run '((:eval\"(ros:quicklisp)\")(:script \"/home/windymelt/.roswell/bin/lem-ncurses\")(:quit ())))"))) 23: (SB-IMPL::TOPLEVEL-INIT) 24: ((FLET SB-UNIX::BODY :IN SB-EXT:SAVE-LISP-AND-DIE)) 25: ((FLET "WITHOUT-INTERRUPTS-BODY-27" :IN SB-EXT:SAVE-LISP-AND-DIE)) 26: ((LABELS SB-IMPL::RESTART-LISP :IN SB-EXT:SAVE-LISP-AND-DIE)) unhandled condition in --disable-debugger mode, quitting Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING {10005305B3}>: Trying to access undefined foreign variable "stdscr". Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {10005305B3}> 0: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<SIMPLE-ERROR "Trying to access undefined foreign variable ~S." {1002CB4D53}> #<unused argument> :QUIT T) 1: (SB-DEBUG::RUN-HOOK SB-EXT:*INVOKE-DEBUGGER-HOOK* #<SIMPLE-ERROR "Trying to access undefined foreign variable ~S." {1002CB4D53}>) 2: (INVOKE-DEBUGGER #<SIMPLE-ERROR "Trying to access undefined foreign variable ~S." {1002CB4D53}>) 3: (ERROR "Trying to access undefined foreign variable ~S." "stdscr") 4: (CFFI::FS-POINTER-OR-LOSE "stdscr" CL-CHARMS/LOW-LEVEL::LIBCURSES) 5: (CL-CHARMS/LOW-LEVEL::%VAR-ACCESSOR-*STDSCR*) 6: (LEM.TERM:TERM-FINALIZE) 7: ((FLET "CLEANUP-FUN-2" :IN LEM-INTERFACE:INVOKE)) [cleanup] 8: ((:METHOD LEM-INTERFACE:INVOKE (LEM-NCURSES::NCURSES T)) #<unused argument> #<CLOSURE (LAMBDA (&OPTIONAL LEM::INITIALIZE LEM::FINALIZE) :IN LEM:LEM) {1002C8446B}>) [fast-method] 9: (SB-INT:SIMPLE-EVAL-IN-LEXENV (APPLY (QUOTE MAIN) ROSWELL:*ARGV*) #<NULL-LEXENV>) 10: (SB-INT:SIMPLE-EVAL-IN-LEXENV (ROSWELL:QUIT (APPLY (QUOTE MAIN) ROSWELL:*ARGV*)) #<NULL-LEXENV>) 11: (SB-EXT:EVAL-TLF (ROSWELL:QUIT (APPLY (QUOTE MAIN) ROSWELL:*ARGV*)) NIL NIL) 12: ((LABELS SB-FASL::EVAL-FORM :IN SB-INT:LOAD-AS-SOURCE) (ROSWELL:QUIT (APPLY (QUOTE MAIN) ROSWELL:*ARGV*)) NIL) 13: (SB-INT:LOAD-AS-SOURCE #<CONCATENATED-STREAM :STREAMS NIL {1002BB34B3}> :VERBOSE NIL :PRINT NIL :CONTEXT "loading") 14: ((FLET SB-FASL::THUNK :IN LOAD)) 15: (SB-FASL::CALL-WITH-LOAD-BINDINGS #<CLOSURE (FLET SB-FASL::THUNK :IN LOAD) {7F757CD5753B}> #<CONCATENATED-STREAM :STREAMS NIL {1002BB34B3}>) 16: ((FLET SB-FASL::LOAD-STREAM :IN LOAD) #<CONCATENATED-STREAM :STREAMS NIL {1002BB34B3}> NIL) 17: (LOAD #<CONCATENATED-STREAM :STREAMS NIL {1002BB34B3}> :VERBOSE NIL :PRINT NIL :IF-DOES-NOT-EXIST T :EXTERNAL-FORMAT :DEFAULT) 18: ((FLET ROSWELL::BODY :IN ROSWELL:SCRIPT) #<SB-SYS:FD-STREAM for "file /home/windymelt/.roswell/bin/lem-ncurses" {1002BB1AE3}>) 19: (ROSWELL:SCRIPT "/home/windymelt/.roswell/bin/lem-ncurses") 20: (ROSWELL:RUN ((:EVAL "(ros:quicklisp)") (:SCRIPT "/home/windymelt/.roswell/bin/lem-ncurses") (:QUIT NIL))) 21: (SB-INT:SIMPLE-EVAL-IN-LEXENV (ROSWELL:RUN (QUOTE ((:EVAL "(ros:quicklisp)") (:SCRIPT "/home/windymelt/.roswell/bin/lem-ncurses") (:QUIT NIL)))) #<NULL-LEXENV>) 22: (EVAL (ROSWELL:RUN (QUOTE ((:EVAL "(ros:quicklisp)") (:SCRIPT "/home/windymelt/.roswell/bin/lem-ncurses") (:QUIT NIL))))) 23: (SB-IMPL::PROCESS-EVAL/LOAD-OPTIONS ((:EVAL . "(progn #-ros.init(cl:load \"/usr/local/etc/roswell/init.lisp\"))") (:EVAL . "(ros:run '((:eval\"(ros:quicklisp)\")(:script \"/home/windymelt/.roswell/bin/lem-ncurses\")(:quit ())))"))) 24: (SB-IMPL::TOPLEVEL-INIT) 25: ((FLET SB-UNIX::BODY :IN SB-EXT:SAVE-LISP-AND-DIE)) 26: ((FLET "WITHOUT-INTERRUPTS-BODY-27" :IN SB-EXT:SAVE-LISP-AND-DIE)) 27: ((LABELS SB-IMPL::RESTART-LISP :IN SB-EXT:SAVE-LISP-AND-DIE)) unhandled condition in --disable-debugger mode, quitting
まとめると,COLORS
とstdscr
という変数にアクセスできなかったようだ.
lemは,複数の画面描画手段を使うことができるが,そのうちの標準的な手段がncursesである.これを使って画面制御を行うのだが,ncursesはC言語ライブラリなので直接Common Lispから呼び出すことができない. Common LispからC言語ライブラリを呼び出すといった,言語感のプログラムの呼び出しにはFFI(Foreign Function Interface)という仕組みを使う. またCommon Lisp上で動作するFFI実装として,CFFIという著名なモジュールがあり,lemでもこのモジュールを経由してncurses等の外部ライブラリを呼び出している. 今回のエラーも,CFFIによってncurses上で定義されているはずの変数を呼び出そうとしたが,該当する変数が存在しなかった,というエラーである.
どこが問題だったのか
実は不明である.
これはncursesの互換性の問題らしい.らしいというのは,リリースノートを見たが具体的な変更がどこかよく分からなかったからだ.
lem側の問題なのか,どちらかといえばncurses側の問題なのかを切り分けるのに時間がかかってしまったが,後述するcl-charms
のexampleを走らせたところエラーが生じたことから,ncurses(のラッパ)で問題が生じていることがわかった.
解決策
ncurses5を利用する.
これは全く天下り的な方法で発見した.つまり適当に読み込むライブラリのバージョンを5にしたら動いたというだけの話で,どこが問題なのかはよくわからなかった. ともかく,ncurses5では動作したので,これを使えばいい.
ところでopenSuSE Tumbleweedはncurses6を標準的に採用しているので,ncurses5だけ使うわけにはいかない.今回は,lemが呼び出しているライブラリをncurses5に制限するという方法で問題を回避する.
cl-charms
lemは,FFIを介して直接ncursesを使うのではなく,cl-charms
というncursesのラッパーライブラリを使うことで画面操作を行う.
このcl-charms
はコード中にCFFI
でncurses
を読み込む箇所がある.これを以下に示す.
#+unicode (cffi:define-foreign-library libcurses (:darwin (:or "libncurses.dylib" "libcurses.dylib")) (:unix (:or "libncursesw.so.6" "libncursesw.so.5" "libncursesw.so.14.0" "libncursesw.so")) (:windows (:or "pdcurses" "libcurses")) (t (:default "libcurses"))) #-unicode (cffi:define-foreign-library libcurses (:darwin (:or "libncurses.dylib" "libcurses.dylib")) (:unix (:or "libncursesw.so.6" ; XXX: is this the right thing ; to load? Should we also add ; libncursesw.so as a ; fallback? "libncurses.so.6" "libncurses.so.5" "libncursesw.so.14.0" "libncurses.so" "libcurses")) (:windows (:or "pdcurses" "libcurses")) (t (:default "libcurses")))
これを見ればわかるとおり,cl-charms
はまずOSを判別し,その後でncurses
を読み込んでいく.UNIXの場合その優先順位は,
libncursesw.so.5
libncursesw.so.6
libncursesw.so.14.0
libncursesw.so
である.これらのうちどれかが発見されれば,それが読み込まれる.
ちなみに#+unicode
や #-unicode
としている箇所は,処理系のUnicode対応によって分岐している箇所である*1.Unicode対応であればlibncursesw
が使われるが,そうでない場合はlibncurses
が使われるようだ(なぜかlibncursesw
を読み込む箇所もあるが).
また,roswellでlemをインストールする場合は,cl-charms
は~/.roswell/lisp/quicklisp/dists/quicklisp/software/cl-charms-****/src/low-level/curses-bindings.lisp
に保存されているので,これを書き換えればよい.
このファイルが存在しない場合は,ros run
して(ql:quickload :cl-charms)
などとしてダウンロードさせるとよい.
libncurses6を読み込まないようにする
さて,ncurses6
を読み込まないようにする.手順は至って簡単で,libncursesw.so.6
やlibncurses.so.6
となっている箇所をすべてコメントアウトすればよい.実施後を以下に示す.
#+unicode (cffi:define-foreign-library libcurses (:darwin (:or "libncurses.dylib" "libcurses.dylib")) (:unix (:or ;"libncursesw.so.6" ; ここ "libncursesw.so.5" "libncursesw.so.14.0" "libncursesw.so")) (:windows (:or "pdcurses" "libcurses")) (t (:default "libcurses"))) #-unicode (cffi:define-foreign-library libcurses (:darwin (:or "libncurses.dylib" "libcurses.dylib")) (:unix (:or ;"libncursesw.so.6" ; ここ ; XXX: is this the right thing ; to load? Should we also add ; libncursesw.so as a ; fallback? ;"libncurses.so.6" ; ここ "libncurses.so.5" "libncursesw.so.14.0" "libncurses.so" "libcurses")) (:windows (:or "pdcurses" "libcurses")) (t (:default "libcurses")))
3点コメントアウトした.これでncurses6
はcl-charms
からは読み込まれなくなった.
後は,通常通りlem
と実行するだけでlemが起動する.
うまくいかないときは,ros install lem
とすることでダンプされたバイナリが削除され,再度lemがビルドできるようになる.
まとめ
ncurses6が足枷になっているとは思いもよらなかったが,あまりncurses6は人気がないのだろうか.
もとよりcl-charms
は原作者がメンテナンスを放棄したものを有志がメンテしている状況なので,あまり開発が活発でないという側面があることも付け加えておきたい.
便利な資料
*1:Feature expression, 処理系が対応している機能によって,式読み取り時に式を分岐させるための記述CLHS: Section 24.1.2.1