EmacsパッケージをNixでビルドするtwist.nixが目指すもの

Emacs vs. Vimのテキストエディタ戦争は、twitterをときどき賑わせる古典的な話題です。私自身は2011年にVimを習得し、2017年に​[Spacemacsを経由](https://jingsi.space/post/2017/03/18/switching-from-vim-to-spacemacs/)​してEmacsに乗り換えました。

Emacsの最大の特徴は、Emacs Lispでの拡張性です。現在では、人々が書いたEmacs LispのコードはEmacsパッケージという形式で共有されています。Emacsにはpackage.elというパッケージマネージャが標準で組み込まれており、use-packageなどのマクロを使うことで宣言的にパッケージの構成を管理することができます。

パッケージはパッケージリポジトリからダウンロードすることができ、現在では、デフォルトの[GNU ELPA](https://elpa.gnu.org/)に加えて、最も多くのパッケージが登録されている[MELPA](https://melpa.org/#/)、Emacs 28以降デフォルトで有効になった[NonGNU ELPA](https://elpa.nongnu.org/)などのリポジトリが有名です。

Emacsについて特に熱心な人なら、まだリポジトリに登録されていないEmacs LispのコードをGitHub等からダウンロードして試用したり、パッケージを修正してPRを送ったことがあるでしょう。リポジトリに登録されていないライブラリを使うための基本的な手順は、Emacs Lispのファイルをどこかに保存してディレクトリをload-pathに追加するだけという簡単なものですが、use-package等で宣言的に管理したいという需要のために、任意のソースに対応する以下のようなサードパーティ製のパッケージマネージャがこれまで利用されてきました。

-   el-get
-   straight.el
-   quelpa
-   elpaca (straight.elの現メンテナであるprogfolio氏によって書かれており、straight.elの後継を自称している)

もうすぐEmacs 29がリリースされます。Emacs 29にはpackage-vcが追加されるため、これらサードパーティ製のパッケージマネージャを利用しなくても、Gitリポジトリから直接パッケージをインストールすることができるようになります。以下の情報を参考にするとよいでしょう。

-   [Exploring package-vc-install as an alternative to Quelpa : emacs](https://www.reddit.com/r/emacs/comments/z9i4ce/exploring_packagevcinstall_as_an_alternative_to/)
-   [Exploring package-vc-install – Tony Zorman](https://tony-zorman.com/posts/2022-11-30-package-vc-install.html)
-   [Announcing: vc-use-package – Tony Zorman](https://tony-zorman.com/posts/2022-12-22-vc-use-package.html)

Emacs 29ではuse-packageも組み込みになるため、今後Emacsユーザはuse-package + package-vcというEmacs組み込みのライブラリだけでパッケージ管理の基本的な必要性を満たせることになります。今後も引き続きuse-packageの代わりにleaf.elを使ったり、package.elの代わりにel-getやstraight.el（もしくはelpaca）を使うことは可能であるものの、敢えてそうすべき理由はワークフローや性能など個人の嗜好の問題になるでしょう。既存ユーザはさておき、今後のEmacs入門者にはuse-package + package.elを使う方法をお薦めしたいです。




## Nix

私はNixのユーザです。NixはNixOSというLinuxディストリビューションで使われているパッケージマネージャであり、Nixを使う利点については[以前の記事](https://qiita.com/akirak/items/11dafdf89e32d34f3fc9#direnv%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)で紹介した通りです。

NixOSを使うことで、マシン全体を宣言的に構成管理することができます。Emacs界隈では、同様に宣言的な構成管理を可能にするGuixに興味を持っている人も少なくないでしょう。GuixはNixの低レベルな仕組みの上に独自のエコシステムを構築したものだと聞いています。

GuixのNixに対する利点としては、S式(Scheme)で設定を書くことができる、Emacsでパッケージ管理をするための優れたUIを提供している等が挙げられるでしょう。一方Nix(OS)は、[登録されているパッケージが多く](https://repology.org/repositories/statistics/total)non-freeなパッケージもインストールすることができる、LinuxとmacOSで利用することができるクロスプラットフォームなツールであるといった利点があり、Guixよりも実際的です。

海外では、Haskell等の関数型言語を積極的に利用しているTweagやSerokell等の企業が、Nixをプロダクションに採用しています。特に話題になったのは、オンラインプログラミング環境である[Replit](https://replit.com/)が50以上のプログラミング言語をサポートするのにNixを使っているということです。日本国内ではHERPがNixを採用しています。NixOSはDevOps/SREにおける様々な技術的課題の有望な解決策であり、多くの企業から支援([Open Collective](https://opencollective.com/nixos))を受けています。私の観測範囲では、Haskellを書くことができるEmacsユーザの多くが、Nix （NixOSまたはNix-Darwin）を使っています。




## 現状のEmacs Lispパッケージエコシステムの課題

NixOSは最大のパッケージエコシステムであり、多くのEmacsユーザに利用されています。NixOSでEmacsのパッケージを管理するためのエコシステムも以前からあり、NixpkgsからEmacsのバイナリとEmacs Lispパッケージを一緒にインストールすることができます。

さらに[emacs-overlay](https://github.com/nix-community/emacs-overlay/)を利用すれば、まだ正式にリリースされていないGit版のEmacsをインストールし、かつEmacs Lispパッケージを最新に保つことができます。現在ではEmacs Lispパッケージを管理するためにNixを書く必要もなく、Nixでuse-packageを使って書かれたinit.elを読み込むことができます。emacs-overlayはEmacsとNixの両方に通じた多くのユーザに支えられながら日々メンテナンスされており、一見これに乗っかるのが正解であるように見えます。




### インフラの信頼性

現実には、Nixpkgs + emacs-overlayのインフラも万全ではありません。emacs-overlayのissuesをwatchしているとわかりますが、パッケージが1週間以上にわたって更新されないといった事象が過去にありました。常に最新版のパッケージを利用したいというユーザにとっては、ときどき不便なことがあります。

Emacs Lispパッケージの開発者体験という点でも、Nixを使ったパッケージ管理は劣ります。独自のパッケージを追加したり、既存のパッケージを別のブランチからインストールしたりするためには、Nixを書かなければなりません。些細な手間ではありますが、straight.elなどのEmacs Lispネイティブなパッケージマネージャと比べると体験において劣るのは否めず、Nixからstraight.elに戻した人もいます。

インフラの信頼性が低いのはNixの問題なのかというと、Nixに固有の問題ではありません。package.elを中心とした標準のEmacs Lispパッケージエコシステムも、万全ではありません。[MELPA](https://github.com/melpa/melpa/)のissuesをwatchしていると、ときどきサーバが落ちていることがわかります。

これらEmacsのインフラは有志によって維持されています。企業が専属のスタッフを雇いながら運用しているわけではありませんので、ときどき不安定だったり、復旧に少々時間がかかるのはやむを得ないと思います。ほとんどの課題はいずれメンテナが解決します。




### パッケージセキュリティ

Emacs Lispパッケージには、インフラの信頼性だけでなく、コードのセキュリティの問題もあります。

たとえばMELPAはパッケージのコードをチェックすることなく、常に最新版をビルドして配布しています。Nixpkgsを含めた、MELPAを利用するdownstreamのプロジェクトも、このMELPAから配布されるアーカイブを利用しています。もしパッケージ作者の一人が悪意あるコードをコミットしてpushしたら、ユーザは気づかずにインストールしてしまうでしょう。

MELPAだけが危険なのではなく、自動的に最新版をインストールする仕組みにはこのリスクが伴います。2022年初頭に、npmのcolorsおよびfakerの事件が話題になりましたが、同様のインシデント(サプライチェーン攻撃)はこれまでに[多数発生](https://drewdevault.com/2022/05/12/Supply-chain-when-will-we-learn.html)しています。Emacs Lispだから発生しないとはいえません。

Emacsコミュニティにおいてもこの問題はたびたび議論されており、以下でその形跡を辿ることができます。

-   [Are emacs package repositories a security risk? : emacs](https://www.reddit.com/r/emacs/comments/63e8hu/are_emacs_package_repositories_a_security_risk/)
-   [Why do we trust package.el?](https://www.reddit.com/r/emacs/comments/mdjd29/why_do_we_trust_packageel/)
-   [Borg vs Straight.el? : emacs](https://www.reddit.com/r/emacs/comments/j68u6z/borg_vs_straightel/)

Emacs Lispパッケージのセキュリティに細心の注意を払うなら、以下のプラクティスが推奨されます。

-   インストールしようとしているパッケージのソースを全部チェックすること。
-   すべてのパッケージを一括で全部更新するのではなく、一つずつ内容を確認しながら更新すること。

具体的にはどんなツールを利用すればいいのかというと、以下のいずれかの方法が最も安全です。

-   パッケージをインストールするには、[borg](https://github.com/emacscollective)を使う。Borgは、Git submodulesですべてのパッケージのバージョンを追跡するパッケージマネージャ。MELPAとMagitのメンテナである[tarsius](https://github.com/tarsius)氏の方法。[例](https://github.com/emacscollective/emacs.g)
-   もしくは、パッケージのソースをGitリポジトリに直接コミットする。org-qlやorg-super-agenda等で有名な[alphapapa](https://github.com/alphapapa)氏が推奨している方法。[例](https://github.com/alphapapa/ap.el/)

重要なのは、暗黙的にインストールされる（明示的にインストールしようとしているパッケージが依存している）パッケージも含めて、すべての依存関係のバージョンを追跡すること。および更新時に一つ一つのパッケージのソースの差分をチェックすることです。

これを実践するのは非常に手間がかかります。[SourceHut](https://git.sr.ht/)を運営しているDrew DeVault氏によると、現実的で安全な解決策は、Debianなどのディストリビューションのパッケージマネージャを使ってパッケージをインストールすることです。

> The difference in trust between managed software repositories like Debian, Alpine Linux, Fedora, and so on; and unmanaged software repositories like PyPI, npm, Chrome extensions, the Google Play store, Flatpak, etc — is starkly obvious. Debian and its peers are full of quality software which integrates well into the host system and is free of malware. Unmanaged repositories, however, are constant sources for crapware and malware.

[Developers shouldnt distribute their own software](https://drewdevault.com/2019/12/09/Developers-shouldnt-distribute.html)

[Debian](https://blog.liw.fi/posts/2023/debian-reasons/)などのディストリビューションは、各パッケージにメンテナを割り当てていますので、そのメンテナがパッケージの更新時に動作を確認することでセキュリティ上の信用が保証されます。残念ながらNixOSのEmacs Lispパッケージに関しては最新版のソースからビルドされているので、個別のパッケージのセキュリティについて責任を負っている人はいません。




## ユーザ自身が責任を負う

以上のような背景から、私はEmacs Lispパッケージのバージョンを個別に管理しつつ、Nixでビルドするような解決策が理想的だと思いました。 上記の課題を解決するために私がスクラッチから書いたのが[twist.nix](https://github.com/emacs-twist/twist.nix)です。

ELPAとMELPAの両方からパッケージのメタデータを取得して、Emacs Lispのパッケージをソースからビルドすることができます。暗黙的な依存関係も含めて、すべてのパッケージのソースのバージョンを2つのロックファイルに記録します。

ほとんどがNixとbash（Nixのsandboxの中で実行される）で書かれており、Rustなどで書かれたCLIに依存したりはしていません。バイナリを追加したらそのバイナリの安全性も保証しなければなりません。NixOSは信用できるが、私は信用できないかもしれませんので、attack vectorを最小にしたいところです。そこで、あらゆるタスクをNixに投げ、極力すべてのコードを純粋に保つことにしました。私はユーザの環境に対して副作用をなすことができません。

インフラについては、現状ではNixの既存インフラを利用しているだけなので、固有のインフラが原因でサービスがダウンするということは起こり得ません。MELPAのサーバが落ちていてもtwistはMELPAパッケージをビルドすることができます。

私は2022年の初めにtwistベースのinit.elに移行しました。以前の[emacs.d](https://github.com/akirak/emacs.d)は起動に1.8秒程度かかっていましたが、新たに全部書き直してtwistに移行したことで同一のマシンで0.5秒未満に短縮し、その後マシンを替えたことで現在は0.25秒未満で起動しています。単純に従来の書き方がまずかったのもありますが、それまで利用していたstraight.elは起動時にパッケージの状態に関するチェックをしていたはずなので、Nixで事前にビルドしておき実行時のチェックを不要にすることで高速化にも寄与しているでしょう。




## 今後の展望

Twistに関する今後のロードマップとしては以下のようなことを考えています。

-   Nixのプロジェクトを管理するための、guix.elのようなフロントエンドを追加することで、Emacs Lispに限らずあらゆる言語のプロジェクトをEmacsから効率的に開発できるようにする。
-   TwistをEmacs Lispパッケージの開発に応用する。NixはQEMUやOCIコンテナのイメージもビルドすることができ、Linuxデスクトップを動かすためのパッケージも揃っているので、仮想環境でUIテストを実施することもできる。

Twist.nix本体も、今後の機能強化を通して、より多くのタスクをビルド時に実施することができるようになる可能性を秘めています。起動を高速化するために多数のEmacs Lispファイルを一つにまとめたり、パッケージ更新時にregression testingを実行したりするといったことも、Nixなら共通機能として提供することができます。Twistが目指すのはEmacsのShift Leftです。