Caskはもう古い、これからはEl-Get - いまどきのEmacsパッケージ管理

秘伝のタレとなったEmacsの設定をgitで管理するなどして, 複数の環境で同じ設定を使うようにするのはかなり一般的になってきました. ただ, 使っている非標準パッケージも含めてきちんと管理しようと思うとけっこう大変です. とくに, 以下のような点はぜひとも実現したいところですが, (これまでは)なかなか難しい部分もありました.

  • 使っているパッケージのインストールを自動化したい
  • いろいろな配布元(GitHub, Emacs Wiki, 個人Webサイト, etc.)からインストールしたい
  • きちんと動くことがわかっているパッケージバージョンに固定したい
  • 新しいパッケージを簡単に試したい
  • パッケージと設定の対応をわかりやすくしたい

この目的のために, 最近はCaskを使うのが流行っているようですが, 上に挙げたポイントをすべて解決しているわけではありません. 筆者のまわりでも, Caskを使ってみたが思ったことができなくて困った, という話を複数聞きます. 元々パッケージの開発時に依存パッケージの管理をするために作られたもののようなので, 仕方ない面もあるかもしれません.

El-Getは, 自分のEmacsにインストールされているパッケージを管理する目的で作られただけあってより条件を満たしているのですが, 配布元のレシピをいちいち書かないといけない(と誤解されている), 書き方があまり簡単には見えない, などの理由で敬遠されている印象です. 公式レシピに登録されている配布元も, 基本的にはバージョン管理された最新版(≒開発版)を指定するポリシーで, 安定版のみを使いたい場合にはオーバースペックと思われているのかもしれません.

最近になって, (筆者によって)El-Getでも簡単な記法がサポートされて, レシピをいちいち書くことから完全に解放され, 安定版を使う指定も非常に簡単にできるようになりました. さらに, (筆者による)関連ツールを使えば, バージョン固定やコマンドラインでの操作も可能なばかりか, Caskファイルをそのまま読み込むことまでできます. 本稿では, CaskからEl-Getに乗り換える場合の方法も含めて, El-Getによるパッケージ管理のいまどきのやり方を解説します.

El-GetとCaskの比較

El-GetやCaskでEmacsのパッケージを管理する場合に関して, サポートされる機能を整理しておきます. 比較のためにEmacsの標準パッケージ管理システム(package.el)についても載せておきます.

Emacs標準 El-Get Cask
VCSから ×
特定サイトから × ×
バージョン固定 ×
DSL ×
コマンドライン
M-x実行
Emacs以外に依存しない ×
設定管理 × ×
パッケージ開発者向け機能

El-GetはまずサポートしているVCSの種類が豊富ですが, 実際にはCaskがサポートしている程度で十分でしょう. El-Getのよいところとしては, GitHubGist, Emacsmirror, Emacs Wikiからのインストールの場合にそれ用の簡単な指定で済む点が挙げられます.

package.elではバージョンを固定したインストールができないため, El-GetやCaskでも配布元がELPA*1の場合はバージョンを固定できません. VCSからのインストールの場合は, El-Get, Caskともにパッケージごとにひとつひとつリビジョンを指定することでチェックアウトするバージョンを固定できますが, これは非常に面倒です. さらに悪いことにCaskでは標準で選択される配布元がELPA系で, VCSからのインストールにはパッケージのURLを指定する必要があります. El-Getは, 公式レシピの配布元もほとんどはVCSになっているため, この点はあまり問題になりません. さらに, el-get-lockというパッケージを使うことで, RubyGemfile.lockPerlcpanfile.snapshotのような感覚でバージョンを固定することができます. (あとで詳しく解説します.)

Caskでは, DSLコマンドラインでインストールするパッケージを指定できます. El-Getではそれぞれel-get-caskel-get-cliを使うと同様のことが可能です. (あとで詳しく解説します.)

El-Getでは, Emacs上でM-x commandでパッケージの追加/削除/更新ができます. Caskにこの機能はありません*2. El-GetではM-xでの対話的なインストールは非同期で実行するため, インストール中も作業の手を止める必要がありません.

El-Getはパッケージの配布元のVCSを扱うためのコマンド(gitコマンド等)以外に外部コマンドを必要とせず, Emacsだけあれば最低限動作します. ブートストラップがEmacs Lispで書けるため, Emacs設定ファイルを読み込むだけでEl-Getそのもののインストールまで済ませられます. Caskではなぜかパッケージ管理のコマンドの実行にpythonが必要になります. 実際の処理はEmacs Lispで書かれていますが, コマンドから起動する部分だけPythonで書かれているというおかしな設計です. さらに悪いことに, CaskそのもののインストールをブートストラップするスクリプトPythonで書かれているので, Emacs設定ファイルでCaskのインストールまで完結させるのは容易ではありません*3.

El-Getは設定ファイル内で必要なパッケージのインストールまで指示することを強く意識しているため, パッケージがインストールされていたら(インストールに成功したら)実行する設定を定義できます. これは別のファイルに分離することも, インストールの指示と同じ場所に書くこともできます. (あとで詳しく解説します.)

Caskの唯一の利点は, パッケージ開発者が開発中のパッケージの依存パッケージを管理したり, (テスト等のために)依存パッケージをロードできる状態の実行環境を整えたりする機能でしょう. Caskは本来その目的のために存在しています. とはいえ, Emacsに元から備わっているしくみを使った実行環境の隔離の方法(あとで詳しく解説します)などを使えばたいていのことは事足りるので, Caskによる支援はそれほど重要ではありません. むしろEmacs Lispによる開発にPythonが必要になる点はパッケージ開発者としては見過ごせません. EmacsでできることはEmacsだけでやるべきです.

El-Getでパッケージ管理

https://raw.github.com/dimitri/el-get/master/logo/el-get.png

El-Getはただそのまま使ってみることもできますが, ここではEmacsの設定ファイル(~/.emacs.d/init.elなど)に設定を書いて, 複数の環境で設定共有する場合についてとくに解説します.

初期設定

Emacsの設定ファイル(~/.emacs.d/init.elなど)の先頭に以下のように書きます.

(when load-file-name
  (setq user-emacs-directory (file-name-directory load-file-name)))

(add-to-list 'load-path (locate-user-emacs-file "el-get/el-get"))
(unless (require 'el-get nil 'noerror)
  (with-current-buffer
      (url-retrieve-synchronously
       "https://raw.githubusercontent.com/dimitri/el-get/master/el-get-install.el")
    (goto-char (point-max))
    (eval-print-last-sexp)))

最初の2行はこの設定ファイルをどこか隔離されたディレクトリに置いて試したい場合にも対応するための設定で, El-Getと直接は関係しません. あとで詳しく触れますが, この設定はEl-Getを使うかどうかによらずやっておくべきです. ただし, この方法ではEmacs設定ファイルをEmacsディレクトリ下に置く必要があるので~/.emacs~/.emacs.elは設定ファイルとして使えません. (~/.emacsだとなにかよいことがあるわけでもないので~/.emacs.d/init.elを使うことをおすすめします.)

残りは, El-Getがインストールされていればそれを有効化し, そうでなければGitHubからダウンロードしてインストールするためのものです. El-GetそのものをEmacsの標準機能のみを用いてインストールするので, これを書いてEmacsを起動するだけでEl-Getがインストールされ使える状態になります.

インストールするパッケージを指定

パッケージをインストールするには, Emacs設定ファイル内(のEl-Getの初期設定よりも後)に(el-get-bundle パッケージ名)と書きます. たとえば, 以下のように書くと, Auto-CompleteEvilの2つのパッケージがインストールされます.

(el-get-bundle auto-complete)
(el-get-bundle evil)

既に前回の実行でパッケージがインストール済みの場合は改めてインストールされたりはしません.

インストール可能なパッケージ(レシピが定義されているパッケージ)を調べたいときは, Emacs上でM-x el-get-list-packagesとするとリストが表示されます*4. これでリストされる以外にもELPAMELPA, Emacs Wiki等で提供されているパッケージは, それらの配布元タイプを指定することでインストール可能です.

配布元を指定してインストール

配布元をELPA系にしたい場合は, パッケージ名の前にelpa:をつけます. 使用するリポジトリは, 主要なものは自動的に設定されているのでとくに指定する必要はありません.

(el-get-bundle elpa:undo-tree)

ELPA系で配布されておらず公式レシピにもないものは, 次の節で触れる詳細オプションを通して細かく配布元を指定する必要がありますが, 特定の配布元の場合はごく簡単な指定でインストール可能です. たとえば, Emacs WikiEmacsmirrorで配布されているパッケージは, それぞれパッケージ名の前にemacswiki:, emacsmirror:をつけるだけでインストールできます. 他に, GitHubやGistからの場合もかんたんな記法でインストールできます.

GitHubからインストールしたい場合は所有者/リポジトリをパッケージ名の代わりに指定します. たとえば, tarao/el-get-lockをインストールするには以下のようにします.

(el-get-bundle tarao/el-get-lock)

内部的なパッケージ名(リストしたときや削除/更新の際に指定する名前)はリポジトリ名部分になります. もしリポジトリ名とは別のパッケージ名にしたい場合は:nameオプションをつけます. たとえば, tarao/tab-group-eltab-groupという名前でインストールするには以下のようにします.

(el-get-bundle tarao/tab-group-el
  :name tab-group)

Gistからインストールしたい場合はgist:Gist ID:パッケージ名を指定します. たとえば, gist:5019545evil-tab-pageをインストールするには以下のようにします.

(el-get-bundle gist:5019545:evil-tab-page
  :depends (evil elscreen))

この場合はevilおよびelscreenに依存しているので:dependsオプションに指定します.

詳細オプションつきインストール

配布元や依存パッケージなどの細かい制御が必要な場合は詳細オプションを使います. オプションは:で始まるオプション名とオプション値をパッケージ名に続けて並べることで指定します. たとえば, :typeオプションにgithub, :pkgnameオプションにtarao/elisp, :featuresオプションにyaicompleteを指定して, tarao-elispパッケージをインストールする場合は以下のように書きます.

(el-get-bundle tarao-elisp
  :type github :pkgname "tarao/elisp"
  :features yaicomplete)

使用可能なオプションの完全なリストはel-get.infoの"Authoring Recipes"の章を参照してください(EmacsからはC-u M-x info RETとしたあと~/.emacs.d/el-get/el-get/el-get.infoを指定すると読めます). ここではよく使うものだけ紹介します.

:type
配布元の種類を指定します. el-get/methodsel-get-種類名.elのファイルがあるものが利用可能です. :typeを省略した場合, 基本的には定義済みのレシピ(自分で用意していない限りは公式レシピ)に定義された配布元が使用され, 他に指定したオプションは定義済みレシピによる指定を上書きします. 逆に:typeを指定した場合は定義済みレシピがあろうと無視され, :typeと同時に指定したオプションのみが使用されます. :typeを省略した場合で定義済みレシピがない場合でもパッケージ名部分や:urlオプションなどから類推できる場合は:typeが自動設定されます.
:url
配布元のURLを指定します. :typeに応じてHTTPやGitプロトコルなどのURLを文字列で(""で囲って)指定します.
:pkgname
Githubなどの配布元の場合に, その配布元での位置("所有者/リポジトリ"など)を文字列で(""で囲って)指定します.
:depends
依存しているパッケージの名前を指定します. 複数ある場合は()で囲んで並べます.
:features
パッケージインストール後にrequireしたいものを指定します. 複数ある場合は()で囲んで並べます.
:branch
配布元がVCSの場合に, 特定のブランチを選択します. 文字列で(""で囲って)指定します.
:checkout
配布元がVCSの場合に, 特定のコミット等を選択します. 文字列で(""で囲って)指定します.
パッケージの設定

El-Getをパッケージのインストーラとしてのみ使いパッケージの設定は従来通りに書く, という場合はこの節を読む必要はありません. 設定をパッケージごとにわかりやすく管理したい場合に参考にしてください.

パッケージごとの設定をそれぞれ独立したファイルに書いておきたい場合は, el-get-user-package-directory変数に設定ファイルの置き場所を指定します. たとえば, ~/.emacs.d/initに置くのであればEmacs設定ファイルに以下のように書きます.

(setq el-get-user-package-directory (locate-user-emacs-file "init"))

あとは~/.emacs.d/init/init-パッケージ名.elというファイルを置けば, その名前のパッケージがインストールされているときは自動的にファイルが読み込まれます. このファイルは自動的にバイトコンパイルされます.

el-get-bundleによるパッケージのインストール指示といっしょに, そのパッケージのための設定を書いておくこともできます. オプション(空でもよい)に続けて任意のLispの式を書くと, そのパッケージがインストールされている時に実行する設定を定義できます. たとえば以下の例では, anythingパッケージをインストール後に(そのパッケージで定義されている)anything-for-filesというコマンドにキーを割り当てます.

(el-get-bundle anything
  (global-set-key (kbd "C-x b") 'anything-for-files))

設定部分は(初回評価時に)自動的にバイトコンパイルされ, 未定義の変数への参照などは警告されます. これは変数名などの書き間違いを検出できて非常に有用です.

パッケージが自動ロードされるようになっている場合*5は, インストールは完了したもののまだロードされていないタイミングでパッケージで定義された変数にアクセスすると警告されてしまいます. このような場合はwith-eval-after-loadマクロを使って, そのパッケージがロードされてから設定を実行するようにしましょう.

(el-get-bundle anything
  (global-set-key (kbd "C-x b") 'anything-for-files))
(with-eval-after-load 'anything
  ;; `anything-map' への参照時には "anything.el" がロード済みであることが必要
  (define-key anything-map (kbd "M-n") 'anything-next-source)
  (define-key anything-map (kbd "M-p") 'anything-previous-source))

ただしこうした場合, with-eval-after-load内はel-get-bundleの外なので, 自動的にバイトコンパイルされず警告を最大限に活かせません. かといって, with-eval-after-loadel-get-bundleの中に入れてもこの場合はうまくいきません. なぜうまくいかないのか, うまくいくようにするにはどうしたらいいのか知りたければ後の節を参照してください.

その他の構文糖衣

パッケージをインストールしつつ, そのパッケージをすぐさまロードしたい(requireしたい)という場合はel-get-bundle!が使えます.

(el-get-bundle! evil)

上の指定は以下と同じ意味です.

(el-get-bundle evil
  :features evil)

もし, パッケージ名とロードする機能の名前が異なる場合は, (el-get-bundle! 機能名 in パッケージ名)記法を使うこともできます.

(el-get-bundle! yaicomplete in tarao/elisp)

上の指定は以下と同じ意味です.

(el-get-bundle elisp
  :type github :pkgname "tarao/elisp"
  :features yaicomplete)

requireしたいものが複数ある場合は:featuresオプションを直接使ってください.

パッケージ管理のワークフロー

パッケージを新しく試す

気になるパッケージを試してみるいちばん簡単な方法は, Emacs上でM-x el-get-installすることです. レシピが定義済みのものに限られますが, これだけでパッケージをインストールできます.

レシピが定義済みでない場合はlisp-interaction-modeになっているバッファ(たとえば*scratch*バッファ)で, el-get-bundleによるインストール指定を書いて, 指定の末尾でC-x C-eするとインストールできます. その他, どういう設定をしたらいいか模索するときも同じように設定を書いてC-x C-eしてみるとよいでしょう(エラーが出たらqキーでエラーバッファを閉じることができます).

ひととおり試して常用したいとおもったら, Emacs設定ファイルにel-get-bundleや設定の行を追加しましょう.

パッケージをアンインストール

試してみたところやっぱり必要ないとおもったら, M-x el-get-removeでパッケージを削除できます. アンインストールしてもいちど読み込んだ機能が元に戻るわけではないので, M-x unload-featureするか, Emacsを再起動するかしましょう.

自動的にインストールされた依存ファイルもきちんと消したい場合は, インストールする前に未インストールの依存パッケージを調べておいて, 試した後にそれらもいっしょに消すことになりますが, 通常そこまでする必要はありません. もし, el-get-bundleされたもの以外を綺麗に消したい場合は, いったん~/.emacs.d/el-getをまるごと削除して, Emacsを再起動して全パッケージをインストールしなおすのが確実です. 後の節で触れる環境隔離の方法を使って, 常用しているのとは別の汚してもかまわない環境で試すようにするのもよいでしょう.

既存のパッケージを更新

パッケージの更新はM-x el-get-updateでできます. 全パッケージを更新する場合はM-x el-get-upadate-allします.

パッケージリストを見ながら操作

インストール/更新/削除の操作はM-x el-get-list-packagesして表示されるリストからもできます. 操作キーを調べるにはリストのバッファ上でhキーを押しましょう.

バージョンの固定

配布元がVCSの場合は:checkoutオプションで任意のバージョンに固定することができますが, 各パッケージのインストール指定にいちいちバージョンを書いていくのはあまりにたいへんです.

そもそも, パッケージのバージョンを固定したい理由は, きちんと動くことがわかっているバージョンをインストールしたい, 勝手に最新版になることでパッケージおよび設定間の互換性が崩れて動かなくなるのを避けたい, ということだとおもいます. つまり, 本当に必要なのは指定したバージョンに固定することではなく, きちんと動くことを確かめたときのスナップショットを取ることのはずです.

el-get-lockを使うと, インストールされているパッケージのバージョンを~/.emacs.d/el-get.lockに書き出し, その情報を元にインストールされるバージョンを制限できます. このファイルのバックアップをとることでスナップショットとしての役割を果たすわけです.

設定

バージョン固定を有効にするにはEmacs設定ファイルに以下のように書きます.

(el-get-bundle tarao/el-get-lock)
(el-get-lock)

特定のパッケージだけ固定の対象にするには, el-get-lockに複数の引数を与えて, 固定するパッケージを指定します(呼出しそのものを複数回に分けても構いません).

(el-get-lock 'evil 'anything)
(el-get-lock 'magit)

逆に, 特定のパッケージのみ固定の対象から外すには, いったん無引数のel-get-lockで全パッケージを対象にしたあと, 外すパッケージをel-get-lock-unlockで指定します.

(el-get-lock)
(el-get-lock-unlock 'undo-tree)
ロックファイルの生成

バージョン固定の対象となったパッケージが新規にインストールされると, ~/.emacs.d/el-get.lockにインストールされたバージョンが書き出され, 以降の再インストール時にはこのバージョンに固定されます. もし既にel-get-bundle(あるいは他のEl-Getによるインストール方法)を使用していて, インストール済みのパッケージがある場合は, バージョンを書き出すためにいったんインストールしなおす必要があります. M-x el-get-reinstallM-x el-get-update(1つのパッケージのみを対象とする場合), あるいはM-x el-get-update-all(全パッケージを対象とする場合)するとよいでしょう.

書き出されたel-get.lockは, ~/.emacs.d/init.elなどとともにVCSで管理しておくことをおすすめします. たとえばGitで管理する場合であれば, 以下のようにしておきます.

cd ~/.emacs.d/
git add el-get.lock
git commit -m 'Add el-get lock file.'
固定されたバージョンを別環境でインストール

~/.emacs.dそのものをVCSで管理するなどして, 別環境に持ち出した場合は, 新たにEl-Getで環境構築する際に自動的にバージョン固定されます. つまりEmacsを起動するだけでうまくいきます.

既にEl-Getを使っている別環境にel-get-lockの設定とel-get.lockを新たに持ち込んだ場合は, M-x el-get-lock-checkout(パッケージを指定せずにそのまま確定)で全パッケージのバージョンを固定してインストールしなおすことができます.

最新版に更新

バージョンが固定されたパッケージを最新バージョンに更新する場合は, 単にM-x el-get-updateするだけです. 更新されるとel-get.lockの該当パッケージの箇所が変更されているので, 更新したパッケージがきちんと動作していそうなことを確認したのち, VCSに反映しておきましょう. 以下はGitの例です.

cd ~/.emacs.d/
git add el-get.lcok
git commit -m 'Update some packages.'
更新を元に戻す

パッケージを更新してみたら既存の設定を壊してしまうことがわかったとしましょう. 元に戻すには次のようにします.

  1. el-get.lockに記載されているバージョンを動いていたときのものに戻す
  2. M-x el-get-lock-checkoutで該当するパッケージをel-get.lockのバージョンに戻す

たとえば, el-get.lockをGitで管理していて, 最新コミットが動いていたときのバージョンになっている場合は, 以下のようにすることでel-get.lockを元に戻せます.

cd ~/.emacs.d/
git checkout el-get.lock

ただし, このやり方はel-get.lockのすべての変更を元に戻してしまうので, 複数のパッケージをいちどに更新した場合は差分を見ながら手動でel-get.lockを編集する必要があるかもしれません.

el-get.lockさえ元に戻れば, あとはM-x el-get-lock-checkoutでパッケージのバージョンを戻せます.

サポートされる配布元

el-get-lockはすべての配布元のバージョン固定をサポートしているわけではありません. バージョンの概念のない配布元や, 前のバージョンをインストールする方法が提供されていない場合は固定することができません.

完全サポート git, github, emacsmirror, hg
これらの配布元のパッケージのバージョンは固定, 更新でき, 元に戻せます.
部分サポート http, ftp, emacswiki
これらの配布元のパッケージのバージョンは固定, 更新できますが, 元に戻すことはできません. もしロックファイルを利用した新規インストール時により新しいバージョンしかなかった場合はエラーになります. この場合はM-x el-get-reinstallでそのパッケージを強制的に最新バージョンに固定しなおすしかありません.

ELPA系は, package.elのしくみ上バージョンを戻せないので, VCSで配布されているパッケージはVCSでインストールすることを強くおすすめします. 安定版を使いたいという場合でも, 配布元が安定版のブランチ(あるいはタグ)を用意していればそれを:branchオプションで指定するのがよいでしょう.

その他のTips

隔離された環境にパッケージをインストール

El-Getの導入の節で触れたように, Emacs設定ファイルを適切に書いておくと, インストールされるパッケージを含むEmacsの全設定を特定のディレクトリ下に隔離できます.

(when load-file-name
  (setq user-emacs-directory (file-name-directory load-file-name)))

先頭にこのように記述したEmacs設定ファイルを, たとえば~/path/to/somewhere/init.elに保存したとすると, 以下のようにしてすべて~/path/to/somewhere内で完結した状態のEmacsを起動できます*6.

emacs -q -l ~/path/to/somewhere/init.el

こうすることで, 既存の設定に干渉することなく, 新しいパッケージやその設定をクリーンな環境で試すことができます. このように記述されたEmacs設定ファイルが広まれば, 他の人の設定を試しに使ってみるのも非常にかんたんになります.

パッケージのインストール先をEmacsバージョンによって変える

インストールしたパッケージは通常バイトコンパイルされますが, 異なるバージョンのEmacsコンパイルされたバイトコードは基本的に互換性がありません*7. もし複数のバージョンのEmacsで設定ファイルを共有したい場合には, パッケージをインストールするディレクトリをEmacsのバージョンごとに別々にするとよいでしょう.

El-Getでインストールするパッケージは, ELPA系を除いてデフォルトでは~/.emacs.d/el-get以下に, ELPA系はpackage.elによってインストールされ, デフォルトでは~/.emacs.d/elpaに保存されます. それぞれel-get-dirpackage-user-dirで変更可能です. Emacsのバージョン文字列はemacs-version変数に入っているので, この文字列でディレクトリを作るとよいでしょう.

たとえば, Emacsのバージョンが24.4.1の場合, 以下のようにするとそれぞれ~/.emacs.d/24.4.1/el-get, ~/.emacs.d/24.4.1/elpaにインストールされるようになります.

(let ((versioned-dir (locate-user-emacs-file emacs-version)))
  (setq el-get-dir (expand-file-name "el-get" versioned-dir)
        package-user-dir (expand-file-name "elpa" versioned-dir)))

この設定は, user-emacs-directoryの設定よりも後, El-Getそのもののインストールよりも前の位置に書く必要があります.

設定のバイトコンパイル

次の例で, anything-mapanything.elで定義されており, anything.elは自動ロードされるようになっているとしましょう.

(el-get-bundle anything
  (global-set-key (kbd "C-x b") 'anything-for-files)
  (with-eval-after-load 'anything
    (define-key anything-map (kbd "M-n") 'anything-next-source)
    (define-key anything-map (kbd "M-p") 'anything-previous-source)))

el-get-bundleの中の設定は, anything.elがインストールされた上で実行されることは保証されますが, この設定が実行されるときにはまだanything.elはロードされていません. したがって, anything-mapは未定義の変数ということになり, 設定部分のコンパイル時に警告が出ます.

...init-1_anything.el:5:15:Warning: reference to free variable `anything-map'

では, anything-mapを参照する前にrequireしたらどうでしょうか?

(el-get-bundle anything
  (global-set-key (kbd "C-x b") 'anything-for-files)
  (require 'anything) ; 常にanythingをロード
  (with-eval-after-load 'anything
    (define-key anything-map (kbd "M-n") 'anything-next-source)
    (define-key anything-map (kbd "M-p") 'anything-previous-source)))

たしかにこれで警告は出なくなりますが, anythingが常にロードされてしまいます. せっかく自動ロードが設定されているのだから, 必要になるまでロードを遅らせてEmacsの起動を速くしたいところです.

コンパイル時には変数の参照が正しいかどうか調べるためにanythingがロードされている必要があるので, 最初の一回のrequireはどうしても必要です. しかし一度コンパイルしたあとはwith-eval-after-loadによってanythingの読み込み後に実行されることが保証されて, コンパイル処理も走らないので変数の参照はチェックされません(チェック済みという扱いです). つまり既にコンパイル済みだったらrequireしないようにできればよさそうです.

実はまさにこれをやってくれるパッケージが存在します*8. tarao/with-eval-after-load-feature-elです.

(el-get-bundle tarao/with-eval-after-load-feature-el)
(el-get-bundle anything
  (global-set-key (kbd "C-x b") 'anything-for-files)
  (with-eval-after-load-feature 'anything
    (define-key anything-map (kbd "M-n") 'anything-next-source)
    (define-key anything-map (kbd "M-p") 'anything-previous-source)))

with-eval-after-load-featureは, コンパイル時には指定されたパッケージをrequireしてから中身をコンパイル, 実行時はwith-eval-after-loadと同じように振る舞うということをしてくれます.

Caskエミュレーション

すでに見たように, Caskの機能のうち自分のEmacsで使うパッケージを管理するためのしくみは, ほぼEl-Getによって網羅されていると言ってさしつかえありません*9. この節ではもう一歩踏み込んで, Caskを使っているユーザがスムーズにEl-Getに移行するための方法を紹介します.

CaskファイルをEl-Get形式に書き換え

ここまでの説明を読んでいればCaskファイルをEl-Get用に書き換えるのはかんたんでしょう. とくにオプションのないdepends-onは, パッケージ名の前にelpa:をつけたel-get-bundleに, :gitのような指定は:type:urlつきのものに置き換えるだけです.

たとえば, id:naoyaさんのCask - naoyaのはてなダイアリーの記事に載っているCaskファイルの場合は, 以下のように書き換えることで全く同じパッケージをインストールできます.

(el-get-bundle elpa:ag)
(el-get-bundle elpa:anything)
(el-get-bundle elpa:auto-complete)
(el-get-bundle elpa:browse-kill-ring)
(el-get-bundle elpa:color-theme)
(el-get-bundle elscreen :type git :url "git@github.com:knu/elscreen.git")
(el-get-bundle elpa:flycheck)
(el-get-bundle elpa:git-gutter)
(el-get-bundle elpa:pbcopy)
(el-get-bundle elpa:popup)
(el-get-bundle elpa:popwin)
(el-get-bundle elpa:powerline)
(el-get-bundle elpa:quickrun)
(el-get-bundle elpa:recentf-ext)
(el-get-bundle elpa:zlc)

;; prog modes
(el-get-bundle elpa:coffee-mode)
(el-get-bundle elpa:go-mode)
(el-get-bundle elpa:js2-mode)
(el-get-bundle elpa:json-mode)
(el-get-bundle elpa:less-css-mode)
(el-get-bundle elpa:motion-mode)
(el-get-bundle elpa:puppet-mode)
(el-get-bundle elpa:rhtml-mode)
(el-get-bundle elpa:ruby-mode)
(el-get-bundle elpa:sass-mode)
(el-get-bundle elpa:slim-mode)

(el-get-bundle elpa:rubocop)
(el-get-bundle elpa:ruby-block)
(el-get-bundle elpa:ruby-electric)
(el-get-bundle elpa:ruby-end)

(el-get-bundle elpa:go-autocomplete)

これをたとえばCask2ElGetというファイル名で保存して, El-Getの初期設定の後に以下のように書けばでき上がりです.

(load (locate-emacs-file "Cask2ElGet"))

非常にかんたんそうですが, 一つ落とし穴があります. El-GetはELPA系ではなくVCSからのインストールを基本としていて, 依存ファイルはレシピできちんと指定するのが原則のため, このままではインストールされたパッケージの依存パッケージ(のうち上に列挙されていないもの)が正常に読み込めません(インストール自体はされます).

もし, ELPA系の安定版にこだわらないなら, 上記からelpa:を除いたものを用いると最新版がきちんとインストールされ, 依存パッケージも問題なく読み込めます. 公式レシピのみで足りるので自分でレシピを定義する必要もありません.

(el-get-bundle ag)
(el-get-bundle anything)
(el-get-bundle auto-complete)
(el-get-bundle browse-kill-ring)
(el-get-bundle color-theme)
(el-get-bundle elscreen :type git :url "git@github.com:knu/elscreen.git")
(el-get-bundle flycheck)
(el-get-bundle git-gutter)
(el-get-bundle pbcopy)
(el-get-bundle popup)
(el-get-bundle popwin)
(el-get-bundle powerline)
(el-get-bundle quickrun)
(el-get-bundle recentf-ext)
(el-get-bundle zlc)

;; prog modes
(el-get-bundle coffee-mode)
(el-get-bundle go-mode)
(el-get-bundle js2-mode)
(el-get-bundle json-mode)
(el-get-bundle less-css-mode)
(el-get-bundle motion-mode)
(el-get-bundle puppet-mode)
(el-get-bundle rhtml-mode)
(el-get-bundle ruby-mode)
(el-get-bundle sass-mode)
(el-get-bundle slim-mode)

(el-get-bundle rubocop)
(el-get-bundle ruby-block)
(el-get-bundle ruby-electric)
(el-get-bundle ruby-end)

(el-get-bundle go-autocomplete)

安定版に対するこだわりという点に関しては, 個人的にはあまりうまい考えだとは思いません. ELPA系で提供されるものであってもリポジトリによっては安定版を提供しているとも限らず, また, パッケージ提供元の考える「安定版」よりも, 自分のEmacs(とその設定)できちんと動くことがわかっているバージョンを使うことの方が重要だと思うからです.

とはいえ, これをCaskからEl-Getへのスムーズな移行と言うには無理があるかもしれません. そこで, CaskファイルをそのままEl-Getで読めるようにする方法を用意しました.

Caskファイルをそのまま読む

tarao/el-get-caskを使うと, CaskファイルをそのままEl-Getで使えて, デフォルトではELPA系のパッケージがインストールされ依存パッケージの読み込みも解決されます. (もちろんCaskのインストールは必要ありません.)

使い方もいたってかんたんで, El-Getの初期設定の後に以下のように書くだけです.

(el-get-bundle tarao/el-get-cask)
(el-get-cask-load)

さらに, Caskファイル中に以下のように書いておくと, ELPA系ではなくEl-Getのレシピを優先するようになります.

(source el-get)

こうすれば知らないうちにEl-Get流のやり方に置き換わっているというわけです.

実はel-get-caskではdepends-onel-get-bundleの別名のように解釈されるので, el-get-bundleでの配布元の簡易記法や, El-Getのすべての詳細オプションもサポートされます.

コマンドライン

Caskはコマンドラインでパッケージのインストールができる点が嬉しかった, という人にはtarao/el-get-cliをおすすめします. 設定ファイル(~/.emacs.d/init.el)をロードしたEmacsをバッチモードで起動してEl-Getのコマンドを実行するという単純な作りで実現しているので, 基本的にM-xでEl-Getのコマンドを実行するのと同様に使えます.

利用例

El-Getを実際に使っている設定ファイルの例として, 筆者のEmacs設定ファイルを挙げておきます.

この設定では, el-get-bundleの別名として定義したbundleというマクロ*10を使って, パッケージのインストールとその設定を同じ箇所に書くようにしています. 設定ファイルは内容ごとに適度にファイルに分割したものシンボリックリンクで順序づけて, init-loaderで読み込むようになっています. 公式レシピ以外に自分で定義したレシピ使っています.

おわりに

El-Getは, 自分のEmacsで使うパッケージを管理する機能としてはpackage.elやCaskの機能を上回るものだということを紹介しました. 実はEl-Getの方がCaskより歴史が古いようなのでこれまでの蓄積によるものとも言えますが, El-Getの方がEmacs本来のやり方に沿って実装されていて他の開発者が加わって拡張していきやすいというのもあるかもしれません.

謝辞

Caskに対する不満を共有してくれた同僚のid:shiba_yu36さんとid:mechairoiさんに感謝します. 彼らの話がこの記事を書く直接のきっかけとなりました.

*1:ELPA(Emacs公式)の他にMELPA, marmalade, SCなどがあります.

*2:Palletというパッケージを使うとできますが最低限のコマンドしかなく, たとえばパッケージの削除はできません.

*3:実際にはgit cloneするだけでよさそうなので全く不可能なわけではありませんが, 不親切なことは間違いありません.

*4:package.elにおけるpackage-list-packagesのようなものです.

*5:なにかのコマンドを実際に使うときまでそのパッケージを使う必要がない(最初から有効にして常に何かを表示/実行しておくような類のもの以外の)場合は常にそうなっているべきです.

*6:稀にこの設定を無視して~/.emacs.dに決め打ちして動作する行儀の悪いパッケージが存在しますが, そのような挙動はバグと言ってよいのでパッケージ開発者に文句を言いましょう.

*7:"In general, any version of Emacs can run byte-compiled code produced by recent earlier versions of Emacs, but the reverse is not true. "

*8:この目的のために筆者が作りました.

*9:パッケージ開発者向けの機能はこの限りではありません.

*10:本来はこれがオリジナルで, el-get-bundleはこのマクロが本家El-Getに取り込まれたものです.