その発想はなかった! 新しい自動バイトコンパイルでEmacsを高速化する
Emacs Lispをバイトコンパイルすると動作が高速化するのは常識である。しかし、バイトコンパイルには致命的な欠点があって、Lispファイルの方がバイトコンパイルファイルよりも新しい場合は、古いバイトコンパイルファイルが読み込まれてしまうのだ!!そのため、Lispファイルを更新したらバイトコンパイルしておかないといけない。
このどうしようもない仕様をなんとかするべく、自動バイトコンパイルで自衛をしている人はけっこういると思う。というか、自動バイトコンパイルがないと絶対に泥沼にはまってしまう。
自動バイトコンパイルとは、Lispファイルを保存したときに after-save-hook をつかって自動でバイトコンパイルをするというもの。しかし、これだとバイトコンパイル中は待たされてしまう。シングルスレッドの悲劇。
Emacsで同時に複数の処理を実行するには、タイマーを使うか、外部プロセスに任せるか、しかない。
バイトコンパイルはコマンドラインから行うことができる。 emacs -Q -batch -f batch-byte-compile hogehoge.el って感じでbatch modeを使ってバイトコンパイルする。これを使えば、待たされることなくバイトコンパイルすることができる!!
バイトコンパイルスクリプトを作成する
しかし、話はそう簡単ではない。ファイルをバイトコンパイルするときには、ファイルのトップレベルのrequireを読むのである。そのため、バイトコンパイル時に依存Emacs Lispが読み込めないといけない。load-pathを適宜追加しないといけないのだ。
では、そのload-pathをどう追加するか?
load-pathは通常 (add-to-list 'load-path "/path/to/elisp") というS式で追加する。*1ならば、.emacsの中からこの式を正規表現で切り取れば新たに追加されたload-pathを得ることができる。
そして、batch-byte-compileのコマンドラインに -l load-path1 -l load-path2 という感じで追加されたload-pathを加える。
で、完成したのが以下のRubyスクリプト。byte-compile という名前で保存して実行属性をつけよう。Ruby 1.9でないと動かないからね!
EMACSはEmacs実行ファイル名、GLOBSが初期化ファイル(.emacs)が読み込むファイルのワイルドカードの配列、DEFAULT_OPTSが最初につけるオプション。俺はclマクロ常用しているので予め読み込ませている。~/emacs/initfuncs.elは自作マクロなどが入っている。これもないとバイトコンパイルができない。
http://www.rubyist.net/~rubikitch/archive/byte-compile にも置いている。
#!/usr/local/bin/ruby191 # byte-compile from command line # Needs Ruby 1.9.x EMACS = "emacs-snapshot" GLOBS = ["~/.emacs.el", "~/emacs/init.d/*.el"] DEFAULT_OPTS = ["-l", "cl", "-L", ".", "-l", File.expand_path("~/emacs/initfuncs.el"),] def extra_load_path(files) # extract load-path from init files files.map{|file| File.read(file).scan(/add-to.+load-path.+"(.+?)"|push "(.+?)" load-path/) }.flatten.compact end load_path_opts = extra_load_path(GLOBS.map{|g| Dir[File.expand_path(g)]}.flatten).map{|f| ["-L", f]}.flatten exec EMACS, "-Q", "-batch", *load_path_opts, *DEFAULT_OPTS, "-f", "batch-byte-compile", *ARGV
自動非同期バイトコンパイルの設定
今度はEmacsサイド。auto-async-byte-compile-modeというマイナーモードを定義している。これをemacs-lisp-mode-hookに加えておく。auto-async-byte-compileコマンドは外部コマンドとしてバイトコンパイルを起動させる。
(define-minor-mode auto-async-byte-compile-mode "With no argument, toggles the mode. With a numeric argument, turn mode on iff ARG is positive." nil "" nil (if auto-async-byte-compile-mode (add-hook 'after-save-hook 'auto-async-byte-compile nil 'local) (remove-hook 'after-save-hook 'auto-async-byte-compile 'local))) (defun auto-async-byte-compile () (interactive) (and buffer-file-name (string-match "\\.el$" buffer-file-name) (executable-interpret (format "byte-compile %s" buffer-file-name)))) (add-hook 'emacs-lisp-mode-hook 'auto-async-byte-compile)
追記[2010/01/08]
コメントありがとうございます。pushにも対応させました。
*1:consとsetqで追加するのはダサいので書き換えようねw