自分の環境で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として実行できるように定義された関数ではありません。 (なので、このコードを実行することはできません)

Cask
(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とします。

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にこのような行があったら消します

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.elsite-lispのディレクトリにload-pathを追加した上で読み込みます。

init.el
(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 を起動すると、さくっと初期化できるようにします。

init.el
(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を使っていい感じにインストールしてくれます。

init.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の検証

BorgMagit!のメイン開発者の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

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.