Emacsがこの先生きのこるためのたった1つの方法

![スクリーンショット 2014-02-20 2.22.29.png](https://qiita-image-store.s3.amazonaws.com/0/1743/71240ca5-57d6-e3e9-92ca-76d45eadb87c.png)

仮想環境技術の進歩によりVagrant、Chef、Ansibleなどのツールを用いて、
サーバーの環境構築を今までの手作業的なものからソースコードで記述していこうという流れがある。
これらに纏わる技術はサーバーの本番運用に関してのトピックがメインだけども、
サーバーのセットアップが自動で行えるようになれば開発環境のセットアップも
そうなっていくのは自明で、VagrantでローカルにUbuntuを入れて開発に利用する
なんてことは既に多くの人が行っているはず。

そういうことをしていると、必然的に環境ごとにエディタの設定なども行わなければならない。
必要とされるのは、冪等性のあるワンストップかつ自動でインストールさせることが出来る手段だ。

emacsの場合、リモート先のファイルを編集するためのTrampという優れた機能が存在するため、
必ずしもサーバー側にエディタが存在している必要はないのだけど、自分は一番使いやすいという理由で
開発サーバ内にemacs環境を構築して利用している。（他にもリモート先のファイルを編集する方法は
いくつかある。詳しくはこちらを参照。 http://d.hatena.ne.jp/daimatz/20111225/1324756736 ）

しかし、emacsの場合、開発環境に対して瞬時にセットアップしようとする2点ほど問題が発生する。

# 問題

## 1. byte-compile問題

通常、emacs lispは、`.el`という拡張子で開発が行われ、実際に利用する際には`.elc`という
コンパイル済みのファイルとしてロードすることで高速化して利用する事が多い。
しかし、emacsのバージョンが異なると別のバージョンでコンパイル済みファイルを
利用できない可能性が高いため環境ごとにbyte-compileしなければならない。

## 2. el-get一発でインストール出来ない問題

使い方はここでは説明しないけど、el-getはemacs24から導入されたpacakge.elを内包する拡張のパッケージ管理ツールで、
recipeとして定義された拡張をどんどんインストール出来る。recipeの中には、パッケージをビルドする手段を記述できて、
例えば[magitのレシピ](https://github.com/dimitri/el-get/blob/master/recipes/magit.rcp)は、リポジトリをgit cloneしたあとに
makeを実行するというようなことが記載されていて、その時にmakeinfoコマンドが必要となる。
そのmakeinfoコマンドが存在しないとビルドは失敗し、他の拡張のインストールもこけてしまい、
完璧な状態のemacsが利用できなくなる。

このようにインストールが簡単には出来なかったりするパッケージが存在していると、
el-getのsync（`(el-get 'sync <package名>)`）だけでは、上手く同期できない時が存在する。

# ソリューション

## 1 gitによる管理

既に多くの人が行っていると思うが、まずは.emacs.dをgit管理にしてgithubにおいておく。
その際に.gitignoreにはコンパイル済みの`*.elc`ファイルを指定してリポジトリから除くようにしておく。

自分は、以下の様なディレクトリ構造で.emacs.dを構成し、rakeタスク(後述)で管理することとした。

```
> tree -L 1
.
├── README.md   # src/init/el-get-conf.el から利用中パッケージのリストを取得して表示。
├── Rakefile    # rakeタスクが定義されてる
├── misc        # README.mdのひな形が入ってる
├── old         # 既に.emacs.dが存在している時の退避先
├── src         # .emacs.dの実体。~/.emacs.dからシンボリックリンクを張る。
└── test        # .emacs.dをテストするためのスクリプトが入ってる

```
https://github.com/ainame/emacs-d/

リポジトリ自体は適当な箇所に置いておき、必要になった時に
~/.emacs.dにsrcディレクトリへのシンボリックリンクを作るようにしている。

こうしておくことで、.emacs.dとは関係ない例えばREADME.mdとかテストファイルを
同じリポジトリで扱うことが出来る。

## 2. rakeタスクの定義

rakeだろうがmakeだろうが何でもいいんだけど、
自分はrakeが一番書きやすかったのでrakeを使った。

rakeタスクには以下の様なものを定義していて、`rake install`を実行すると、
~/.emacs.dにシンボリックリンクを貼り、el-getで全拡張を取得してきて、
全ての`.el`にbyte-compileを実行し、最後にテストを実行する。

```sh
$ rake            # execute :clean and :compile task
$ rake copy       # copy ~/.emacs.d into `pwd`/old
$ rake new        # create symbolic link to `pwd`/src
$ rake old        # you can revert src settings
$ rake compile    # compile conf files
$ rake clean      # remove compiled conf files
$ rake gen_readme # generate README.md from pacakge list
$ rake test       # run tests for .emacs.d
$ rake install    # link to ~/.emacs.d, build and test
```
https://github.com/ainame/emacs-d/blob/master/Rakefile

テストは、以下の様に`el-get-package-installed-p`関数を利用して、
各種拡張がインストール済みかどうかを調べるだけのものしかまだない。

しかし全ての`.el`ファイルを起動時に読み込んだ上で、このテストを実行すれば
かなり高い確率で自分が期待したとおりに動く可能性が高い
（各種の設定が正しいかどうかは日常利用中に保証する前提）。

```lisp
(require 'cl)
(require 'ert)

(defun check-package (pkg)
  (ignore-errors
      (el-get-package-installed-p pkg)))

(defmacro deftest-load-package (pkg)
  `(ert-deftest ,(intern (concat "test-load-package-" (symbol-name pkg))) ()
       ,(concat "test for loading " (symbol-name pkg))
            (should (check-package ',pkg))))

(defun deftest-load-package-func (pkg)
  (eval `(deftest-load-package ,pkg)))

(mapcar 'deftest-load-package-func my-el-get-packages)
```
https://github.com/ainame/emacs-d/blob/master/test/test-load-all-packages.el

このコードはマクロを利用していて、emacs24から標準添付のテストライブラリertを
使ってmy-el-get-packagesというリストに入っているパッケージを、以下のように
一つ一つassertionを定義していくのと同じである。

```lisp
(ert-deftest test-load-package-anything ()
  "test for loading anything"
  (should (check-package 'anything)))

(ert-deftest test-load-package-auto-complete ()
  "test for loading auto-complete"
  (should (check-package 'auto-complete)))

(ert-deftest test-load-package-magit ()
  "test for loading magit"
  (should (check-package 'magit)))
...
```

## 3. Emacs meats Travis-CI

rakeから一発でインストール出来て、テストも実行できるようになった。
なので、次は.travis.yamlでTravis-CIを利用して継続的インテグレーションする。
Travis-CIの使い方の詳しい説明も避けるけど、以下のように書くとEmacsのテストも
Travis-CI上で行えるようになる。

```yaml
language: emacs-lisp
env:
  matrix:
    - EMACS=emacs24
before_install:
  - if [ "$EMACS" = "emacs24" ]; then
    sudo add-apt-repository -y ppa:cassou/emacs &&
    sudo apt-get update -qq &&
    sudo apt-get install -qq emacs24 emacs24-el texinfo;
    fi
script:
  rake install_for_bash                                                 
```

ここで注意すべきは、先程も書いたとおりmakeinfoコマンドがないとmagitが
インストール出来ないので、makeinfoが内蔵されている`texinfo`パッケージを書いている。

さらに、emacs24.3以降だと、emacs lispでcommon lisp関数を利用するためのcl-libの扱いが
以前とは異なっていてemacs標準添付となり、ハマることがあったのでadd-apt-repositoryで
ppa:cassou/emacsを指定して、最新のemacs24.3をインストールするようになっている。

また、scriptでrake install_for_bashと書いてあるのは、zshに依存した処理が
若干.emacs.d内に入ってるのでbash環境向けにinstallするときに使ってる。

こうして継続的インテグレーション出来るようになると、.emacs.dを少しでも変更して、
githubのリモートリポジトリにpushするたびにテストが走り、テストの結果によって
他の環境に反映させてもおっけーかどうかが大体わかるようになる。

https://travis-ci.org/ainame/emacs-d/

![c8161f48eca3066424ed20ea9bb3120c.png](https://qiita-image-store.s3.amazonaws.com/0/1743/ed29e5cd-e43d-2d8a-a3aa-239aae30792e.png)


現状、自分の設定は24.3限定で動くようにしているが、他のバージョンでも
動作確認したければ、matrixに別バージョンを追加すれば良い。

# まとめ
このように、.emacs.dをバージョン管理し一発でセットアップ出来るコマンドを用意して、
CIすることで、いつでもどこでもいろんな環境で自分自身の最強の環境を利用できるような
準備をしておくのが、これからemacs"と共に"生き残るための唯一の方法ではないだろうか。