hnwの日記

Emacs起動時に自分好みのelispを自動的にインストールする


(2015/04/27追記)Emacs24.4付属のpackage.elでは「野良elispをpackage-installする」が動かなくなっていました。中の人が想定してない使い方で頑張るより、必要なものはMELPAにぶっこんでいくスタイルの方が正しい気がしますね…。


自宅と会社など、複数環境でEmacsを使っていると全環境を同じ状態に保つのは意外と手間がかかります。GitHubなどを利用してinit.elを共有すればそれなりに同じ状態にできますが、完全に同じ状態にしようと思うとelispのインストール状態も共有する必要があり、これが案外面倒だったりします*1


そこで、init.el内に自分が必要なパッケージ名を列挙しておいて、起動時にインストールされていないパッケージがあったらpackage.elでインストールする仕組みを導入しました。さらに、URLで指定されたelispをpackage.el管理する仕組みを組み合わせました。これが予想以上に快適だったので紹介します。これは、次の2つの記事を混ぜ合わせたような内容になっています。


僕と同じことをするには、init.elに下記の内容を貼り付ければ準備完了です。GNU ELPA・Marmalade・MELPAで提供されているパッケージの場合はmy/favorite-packagesのパッケージ名を、それ以外の場合はmy/favorite-package-urlsのURLを、それぞれお好みに合わせて書き換えてください。

;; package.el
;; http://qiita.com/catatsuy/items/5f1cd86e2522fd3384a0
;; http://www.robario.com/2013/08/07

(defvar my/favorite-packages
  '(
    php-mode
    haskell-mode
    csharp-mode
    yaml-mode
    open-junk-file
    gtags
    anything
    )
  "起動時に自動的にインストールされるパッケージのリスト")

(defvar my/favorite-package-urls
  '(
    ;; 1ファイルのelispしか管理できません
    ;; パッケージ名はファイル名の.elより前の部分になります
    "http://namazu.org/~tsuchiya/elisp/dabbrev-ja.el"
    "http://homepage3.nifty.com/oatu/emacs/archives/auto-save-buffers.el"
    )
  "起動時に自動的にインストールされるelispのURLのリスト")

;; ネットワーク経由で取得したelispをpackage.el管理する
(defun package-install-from-url (url)
  "URLを指定してパッケージをインストールする"
  (interactive "sURL: ")
  (let ((file (and (string-match "\\([a-z0-9-]+\\)\\.el" url) (match-string-no-properties 1 url))))
    (with-current-buffer (url-retrieve-synchronously url)
      (goto-char (point-min))
      (delete-region (point-min) (search-forward "\n\n"))
      (goto-char (point-min))
      (setq info (cond ((condition-case nil (package-buffer-info) (error nil)))
                       ((re-search-forward "[$]Id: .+,v \\([0-9.]+\\) .*[$]" nil t)
                        (vector file nil (concat "[my:package-install-from-url]") (match-string-no-properties 1) ""))
                       (t (vector file nil (concat file "[my:package-install-from-url]") (format-time-string "%Y%m%d") ""))))
      (package-install-from-buffer info 'single)
      (kill-buffer)
      )))

(defun package-url-installed-p (url)
  "指定されたURLに対応するパッケージがインストールされているか調べる"
  (interactive "sURL: ")
  (let ((pkg-name (and (string-match "\\([a-z0-9-]+\\)\\.el" url) (match-string-no-properties 1 url))))
    (package-installed-p (intern pkg-name))))

(eval-when-compile
  (require 'cl))

(when (require 'package nil t)
  (add-to-list 'package-archives
               '("melpa" . "http://melpa.milkbox.net/packages/") t)
  (add-to-list 'package-archives
               '("marmalade" . "http://marmalade-repo.org/packages/") t)
  (package-initialize)
  (let ((pkgs (loop for pkg in my/favorite-packages
                    unless (package-installed-p pkg)
                    collect pkg)))
    (when pkgs
      ;; check for new packages (package versions)
      (message "%s" "Get latest versions of all packages...")
      (package-refresh-contents)
      (message "%s" " done.")
      (dolist (pkg pkgs)
        (package-install pkg))))
  (let ((urls (loop for url in my/favorite-package-urls
                    unless (package-url-installed-p url)
                    collect url)))
    (dolist (url urls)
      (package-install-from-url url))))


ちなみに、僕は気になるパッケージがあったらpackage.el経由で普通にインストールして、気に入ったらinit.elにパッケージ名を書き写すような使い方をしています。


Emacs24からpackage.elが標準装備になったので、こういうことがやりやすくなりました。Emacs23以前を使っている場合、package.elだけは別の方法でインストールする必要があります。すこし悲しいですね。

*1:他人の書いたelispGitHub管理しちゃうよ派を除く