自分の環境でCaskが限界に達して僕の力ではデバッグも諦めたのでQuelpaに移住したメモです。
概要
Emacsの標準パッケージ管理ツールとしてpackage.el
があります。これはELPA(Emacs Lisp Package Archive)と呼ばれるパッケージリポジトリからLispファイルをダウンロードして管理するための仕組みです。
package.el
を補強するためのツールのひとつにCaskが、私の環境では最近ちょっとエラーがいろいろ出て、日常用途でのEmacsのパッケージ管理には断念せざるを得ない決断をするところまできました。なのでQuelpaに移住します。
注意
この記事はtadsanが一晩で自分のinit.el
を動かすためだけにQuelpaを触った範囲で書いたので、事実誤認があったらごめんね。
筆者はまだ二晩程度しかQuelpaに触れてない程度でのメモ書きなので、自分の中でもまだちゃんと運用に乗ってません。
最初にまとめ
私がCaskからQuelpaに移行するために行った変更はこちら
https://github.com/zonuexe/dotfiles/pull/1/files
- CaskでやってたことはQuelpaで概ねできます
- ただしQuelpaはめっちゃディスクスペースとります (数百MB〜1GB程度)
- 最初のインストール時はかなり時間かかります (数十分)
- アップデートするときもちょっと待ちます (僕の環境では2分)
- QuelpaはMELPA以外のリポジトリを対象としないので、別途アプローチが必要になります
-
use-package
を組み合せることで、Quelpaへの依存を減らせる可能性があります
この記事は飽くまでメモであり、Quelpa導入方法の決定版として書かれたものではありません。
こんな記事を書いておいてなんですが、QuelpaよりもEl-Getを使った方が簡単に運用できる可能性があります。
ELPAとMELPA
2015年Emacsパッケージ事情に概ね書いた通りですが、ELPAには公式のパッケージリポジトリであるGNU ELPA*(Emacsの初期設定)と、その他の有志による非公式のリポジトリ(ユーザーが各自で設定する必要がある)*があります。MELPAはその中でも、現時点で最大規模で、パッケージ更新が最も頻繁なリポジトリです。
自作LispパッケージのMELPAへの追加はGitHubのPull requestで、一度追加されたパッケージの更新はgit push
だけで完結するのも大きな特徴です。
MELPAはELPAと互換性があり、URLをリストに追加するだけでMELPAのパッケージも利用することができるのが特徴です。 (ただし、今回の記事の主題となるQuelpaを利用する場合は、その設定は不要です)
この記事では以下の用語は明確に区別して書いてるので気をつけてください。
- ELPA: Emacs Lispのパッケージリポジトリの総称
- GNU ELPA: GNU公式のELPA
- MELPA: 非公式のELPA
なぜpackage.el
だけで済まないのか
ELPAに登録されたものだけを使ってるぶんにはCaskや、今回紹介するQuelpa
のようなツールは基本的には不要です。しかし実際にはELPAに登録に登録されてないバージョンのパッケージや、開発中ブランチ、MELPAに登録してない自作のパッケージなどをインストールしたくなります。
ELPAに登録されたパッケージと同列にGitHubやBitBacketなどのVCSリポジトリ(以後、単にVCS (Version Control System)と呼ぶことがあります)からもEmacs Lispパッケージをインストールできるとべんりなことがあります。
別にそんな用事はねーよって方は、use-package
を利用するとパッケージを簡単に管理できるかもしれません。
Caskとは何か
Caskは基本的にpackages.el
のラッパーです。特定のディレクトリにEmacs Lispパッケージをダウンロードするためのツールですが、登録されたELPAリポジトリだけではなく、特定のVCSリポジトリ(Git, Mercurial, Subversion, etc...)からも取得することができます。
これはgem
コマンドに対するBundlerに相当し、Lispパッケージ作者にとっては開発時に必要なパッケージのみ記述する場合などに便利です。Caskの基本的な利用方法はCask
ファイルに独自DSLで依存パッケージ一覧を記述します。
Cask
ファイルに書き込む内容のDSLは構文はS式ですが、Emacs Lispとして実行できるように定義された関数ではありません。 (なので、このコードを実行することはできません)
(source gnu)
(source melpa)
(source org)
(depends-on "0xc")
(depends-on "2048-game")
(depends-on "aa-edit-mode")
(depends-on "ac-geiser")
(depends-on "dmacro" :git "git@github.com:zonuexe/dmacro.git")
(depends-on "phpunit" :git "git@github.com:nlamirault/phpunit.el.git" :branch "develop")
(depends-on "mode-test" :git "git@github.com:emacs-php/mode-test.git" :files ("mode-test.el"))
このように記述することで、ELPAに登録されてないパッケージでもGitHubなどのリポジトリから直接インストールできるようになります。また、Palletパッケージでpackage.elと高度に統合することもできます。
Quelpaとは何か
QuelpaもCaskと同じくpackage.el
のラッパーですが、Caskとはアプローチが異なります。
- MELPAのレシピを利用するが、任意のELPAを利用できるわけではない
- 可能な限り必ずソースコードをVCSリポジトリから取得して利用する
- ビルドしたファイルは通常のELPAと同じディレクトリ(通常は
~/.emacs.d/elpa/
)に保存される - Quelpaで追加したパッケージはELPAでインストールされたものと同様にロード可能 (=Emacs起動時にはquelpa不要)
上記のCask
ファイルは、以下のようにほぼ一対一に置換できます。ファイル名はなんでもいいのですが、~/.emacs.d/my-packages.el
とします。
;;; Code:
(require 'quelpa (locate-user-emacs-file "site-lisp/quelpa/quelpa"))
(package-initialize)
(quelpa 'exec-path-from-shell) ; 必ず先頭に置く
(exec-path-from-shell-initialize) ; PATHをインポートする
(quelpa '0xc)
(quelpa '2048-game)
(quelpa 'aa-edit-mode)
(quelpa 'ac-geiser)
(quelpa 'dmacro '(:fetcher github :repo "zonuexe/dmacro"))
(quelpa 'phpunit '(:fetcher github :repo "nlamirault/phpunit.el" :branch "develop"))
(quelpa 'mode-test '(:fetcher github :repo "emacs-php/mode-test" :files ("mode-test.el")))
;;; my-packages.el ends here
これはCask
ファイルと違って、普通のEmacs Lispのスクリプトです。特別なものではなく普通の関数なので、スクリプトの先頭から順に実行されます。
このフォーマットはCaskと微妙に違って、MELPAのRecipe Formatと互換性があります。
QuelpaのREADMEでは(quelpa '(dmacro :fetcher github :repo "zonuexe/dmacro"))
のような記法ですが、実行結果は同じになります。好きな書きかたで大丈夫です。
Quelpaの準備
どこでもいいのでQuelpaを持ってきます。
私は ~/.emacs.d/site-lisp/quelpa
に配置することにしました。あと私は.emacs.d
を含めてdotfilesリポジトリに集約してますので、これをsubmoduleで持つことにします。
mkdir -p ~/.emacs.d/site-lisp
cd ~/.emacs.d/site-lisp
git submodule add git@github.com:quelpa/quelpa.git
Cask依存を外す
init.el
にこのような行があったら消します。
(require 'cask)
(cask-initialize)
(pallet-mode t)
site-lisp
以下のファイルをオートロード可能にする
こんな感じのMakefile
を書きました。
EMACS ?= emacs
ELS = site-lisp/quelpa/quelpa.el
ELCS = $(ELS:.el=.elc)
all: site-autoload elc elpa
%.elc: %.el
$(EMACS) -Q -batch -L . -f batch-byte-compile $<
elc: $(ELCS)
elpa: my-packages.el
$(EMACS) -batch -l my-packages.el
site-autoload: $(ELS)
$(EMACS) -Q -batch -L . --eval \
"(progn \
(require 'package) \
(package-generate-autoloads \"site-lisp\" (locate-user-emacs-file \"site-lisp\")))"
clean:
@rm $(ELCS) site-lisp/site-lisp-autoloads.el
.PHONY: all clean
これでmake site-autoload
を実行すると/site-lisp/site-lisp-autoloads.el
が生成されます。init.el
でsite-lisp
のディレクトリにload-path
を追加した上で読み込みます。
(let ((default-directory (locate-user-emacs-file "./site-lisp")))
(add-to-list 'load-path default-directory)
(normal-top-level-add-subdirs-to-load-path))
(load (locate-user-emacs-file "./site-lisp/site-lisp-autoloads.el") t)
Cask
ファイルを移行する
先程挙げたmy-packages.el
です。
依存したいパッケージの中にMercurial(hg
)に依存するものがあったのですが、私の環境では/usr/local/bin/hg
にあったのでexec-path-from-shellでシェルと同じPATH
をインポートする必要があります。なので、これを上の方に書いてやります。
Quelpaの罠なのですが (quelpa 'hogehoge)
のように存在しないパッケージ名を選択しても何のエラーも起こりません。同様に、MELPAに存在しないパッケージやMELPAから削除されてしまったパッケージについても怒られません。これはつらい。
ヘルパーコマンドを用意する
M-x my/quelpa-setup
を起動すると、さくっと初期化できるようにします。
(defun my/quelpa-setup ()
"Setup Quelpa packages."
(interactive)
(load (locate-user-emacs-file "my-packages")))
VCSとMELPA以外のパッケージ
MELPAとGNU ELPAの両方に登録されたパッケージはいくつかあるのですが(ivyとかYASnippetとか)、GNU ELPAにしかないパッケージも存在します。私が深く依存するパッケージとしてはundo-treeがそうでした。
私はuse-packageユーザーなので:ensure t
と付けるだけで、package.el
を使っていい感じにインストールしてくれます。
(use-package undo-tree :ensure t
:diminish undo-tree-mode
:init
(global-undo-tree-mode)
(bind-key "C-_" #'undo-tree-undo)
(bind-key "C-?" #'undo-tree-redo))
おてがる!
運用方法
パッケージのアップデート
M-x quelpa-upgrade
を実行して、しばらく待ちます。
インストールしたパッケージの削除
ふつうのパッケージと同じようにM-x list-packages
で消したいパッケージを選んでdを押してチェックしてからxで実行してください。my-packages.el
からは手動で消します。
Quelpaの欠点
いっぱいあります。
- 挙動の癖を理解しないとわかりにくい
- 全パッケージのVCSリポジトリをぜんぶ取得してくるのでディスクを消費する
- 私の依存するパッケージで約1GB
- ビルドがめっちゃ時間かかる
- Cask使ってたときにちゃんと時間を計測したことはないですが、体感で倍かかります
- アップデートの差分がわかりにくい
-
cask update
したときの出力が好きでした
-
Quelpaの長所
- Caskではない
- 起動時に読み込むべきものがない (
(package-initialize)
以外) - つまり起動時のコストはかからない
- Palletとか追加しなくてもpackage.elと統合されてる
今回やらなかったこと
Caskからのインポート/エクスポート
今回はCask
ファイルをQuelpaの形式に手動で変換しましたが、構造が簡単なのでQuelpa用にCask
ファイルをインポートすることも、Cask
からQuelpa用にファイルをエクスポートすることも、割と簡単に作れます。誰かやってみてね。
QuelpaをVCSインストールのためだけに利用する
ひょっとして、ELPAに登録されたパッケージはuse-package
の:ensure t
に頼って、QuelpaではVCSリポジトリに依存するものだけを入れるようにした方が話が早かったのでは……?
まあ、困ってないので、そのうち検証するかも。
El-Getの検証
5年前はEl-Getを使ってたので、出戻ってもなあ… みたいな。
Borgの検証
BorgはMagit!のメイン開発者のJonas Bernoulliが作った独特なパッケージ管理ツールです。
Caskからサクッと移行できるような感じではないので、今回の対象にはしませんでした。少なくとも初心者向けではないです。Emacsair! Assimilate Emacs packages using Git submodulesとかBorg User Manual: Topをよく読んで使ってみてください。
あとがき
cask update
がエラーしか吐かなくなって試行錯誤してたら、うっかりと rm -rf .cask
してしまって依存パッケージの一切が消えてなくなってしまったときはどうなることかと思って半泣きだったのですが、意外にどうにかなりましたね ヾ(〃><)ノ゙
この記事に書きもらしたこともあるかもしれにあので、僕がCaskからQuelpaに移行した全差分を見たい型はプルリックを見てください。
https://github.com/zonuexe/dotfiles/pull/1/files