straight.el と use-package を使ったパッケージ管理
Emacsは、Emacs Lispで書かれたパッケージを導入することで柔軟に機能を拡張できる。大昔はEmacs Lispのファイルをひとつひとつ手で落としてきて設定していたのだが、しばらく前からはpackage.elを使い、パッケージ間の依存関係に従ってMELPAのようなパッケージ・アーカイヴから芋づる式に自動インストール、といった管理が主流になった。さらに最近では、Caskやel-get、Borg、Quelpaなど様々なパッケージマネージャが登場している。
なぜそういうことになるかというと要はどれも一長一短あるからだが、比較的最近開発されたstraight.elは先行する他のパッケージマネージャをよく研究していて、なかなか筋が良い設計になっている。私自身ここ数年 Cask
を使っていたのだが、つい最近straight.elに乗り換えた。というわけで、その紹介をしたい。
straight.el のインストール
Cask
は実装がEmacs Lispで完結しておらず、 Cask
自体のインストールにPythonが必要だった。最近では大概のシステムにPythonが入っているので、ある意味どうでもいいとは言えるのだが、Windowsなどではひと手間増えてしまうのは間違いない。一方 straight.el
はパッケージの管理にGitが必要だが、インストールそのものは ~/.emacs.d/init.el
に以下を書いてEmacsを起動するだけである(これは el-get
も同じだが)。
;; straight.el のインストール (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage))
ようはGithubのstraight.elのレポジトリからinstall.elを落としてきて、それを実行しているわけですね。 straight.el
関係の全ては基本的に ~/.emacs.d/straight
の中にあるので、何かおかしくなったらこのディレクトリを消してやり直せばよい。
straight.el
が好ましい点の一つは、use-packageと緊密に連携しているところである。 use-package
に関しては日本語でも他に詳しい解説がいくらもあるのでそちらを参照してもらいたいが、大ざっぱに言えば、 ~/.emacs.d/init.el
でのライブラリの読み込みや、起動時の遅延読み込みの設定が明快に書けるようになる便利なマクロだ。 (require 'foobar)
と書き続けて数十年のオールドタイマーだと目が慣れるまでは違和感があると思うが、いったん要領を掴めば書き換えはそんなに手間ではない。
straight.el
と use-package
の連携を使うには、
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
と書いておく。このように設定すると、
(use-package foobar)
と ~/.emacs.d/init.el
に書くだけで、MELPAなどのレポジトリから適当に foobar
パッケージを落としてきてインストールしてくれるのである。しかも、 straight.el
の場合落としてきたパッケージは ~/.emacs.d/straight/repos
以下にクローンされたGitレポジトリとして展開されているので、内容確認や手直しも簡単だ。なので straight.el
にはパッケージのアップデートという概念はなく、fetchとかmergeとかpushなのである。
また、自分で手でインストールしたとか、Emacsと一緒に配布されていてダウンロードする必要がないというライブラリであれば、
(use-package foobar :straight nil)
と個別に straight.el
対応を無効にしてやればよい。あるいは、そのパッケージがMELPAにあるのは知っているが、MELPAのバージョンだと不具合があるので、他の人がGithubかどこか別のところで公開しているバージョンが使いたい、ということもあるだろう。そうした場合は、
(straight-use-package '(foobar :type git :host github :repo "mhatta/foobar")) (use-package foobar)
とか、
(straight-use-package '(foobar :type git :host github :repo "upstream/foobar" :fork (:host github :repo "mhatta/foobar")))
などとフォークを明示的に指定してインストールすることもできてしまうのである。他にも straight.el
は、パッケージのバージョンを固定したりいろいろなことが出来るのだが、そのへんは例えば このあたりの記事を参照してもらいたい。
というわけで、straight.el
を上手く使うと、例えば他のコンピュータにinit.elだけ持っていけば、あとは Emacs を起動するだけでパッケージのインストールから設定まで終わってしまうのである。これはとても便利だ。
straight.el を使った Org modeのインストール
straight.el
について延々と書いているかと言えば、straight.el で Org modeをインストールするときに若干の小細工が必要になるからである。しばらく前からEmacs本体にOrg modeが同梱されているため、レポジトリから最新の Org をインストールしようというとき、システムには古いバージョンの Org が存在していてすでに読み込まれている、ということが避けられない。これがインストール時に思わぬ不具合をもたらすことがあるので、init.elに
;; straight.elによるorg modeのインストールに先立つ準備 (require 'subr-x) (straight-use-package 'git) (defun org-git-version () "The Git version of org-mode. Inserted by installing org-mode or when a release is made." (require 'git) (let ((git-repo (expand-file-name "straight/repos/org/" user-emacs-directory))) (string-trim (git-run "describe" "--match=release\*" "--abbrev=6" "HEAD")))) (defun org-release () "The release version of org-mode. Inserted by installing org-mode or when a release is made." (require 'git) (let ((git-repo (expand-file-name "straight/repos/org/" user-emacs-directory))) (string-trim (string-remove-prefix "release_" (git-run "describe" "--match=release\*" "--abbrev=0" "HEAD"))))) (provide 'org-version) ;; 以下Org modeの設定 (use-package org)
などと書いておくとよい。ちなみに前回までの話を踏まえた Org の設定を use-package
で書くと、
(use-package org ;; :defer t で起動時に Org を読み込まない(起動が速くなる) :defer t :config (setq org-directory "~/Dropbox/Org" org-default-notes-file "notes.org" org-agenda-files '("~/Dropbox/Org/gtd.org" "~/Dropbox/Org/notes.org") org-log-done 'time org-startup-truncated nil org-use-speed-commands t org-enforce-todo-dependencies t) (setq org-todo-keywords '((sequence "TODO(t)" "SOMEDAY(s)" "WAITING(w)" "|" "DONE(d)" "CANCELED(c@)"))) (setq org-refile-targets (quote ((nil :maxlevel . 3) (mhatta/org-buffer-files :maxlevel . 1) (org-agenda-files :maxlevel . 3)))) (setq org-capture-templates '(("t" "Todo" entry (file+headline "~/Dropbox/Org/gtd.org" "INBOX") "* TODO %?\n %i\n %a") ("j" "Journal" entry (function org-journal-find-location) "* %(format-time-string org-journal-time-format)%^{Title}\n%i%?") ("n" "Note" entry (file+headline "~/Dropbox/Org/notes.org" "Notes") "* %?\nEntered on %U\n %i\n %a") )) ;; org-tempo ;; Org 9.1.14 では明示的に読み込む必要があるかも (use-package org-tempo :straight nil) :bind (("\C-ca" . org-agenda) ("\C-cc" . org-capture) ("\C-ch" . org-store-link)) )
こんな感じになるのでしょうかねえ。