Nix を知って 1 年ほど経ち、dotfiles はほぼ全て Nix に寄せましたが、唯一 .emacs.d は手動で clone して管理していました。
設定ファイルは takeokunn の構成を真似て作っていました。彼の設定は高速で起動することで有名だったのですが、最近 .emacs.d を Nix の設定に統合し、彼は Nix のもたらす再現性を手にした代わりに、高速起動する Emacs を失ってしまいました。(悪口みたいになってごめん)
俺は起動速度と再現性を両立したいんだよ、ということで小難しそうなことで有名な Twist.nix を Emacs の設定に導入することを検討します。
この際、ドキュメント・Example のリポジトリが 2 年ほど前から更新されていない上に、実際の設定は複数のファイルに横断しているため理解に時間がかかり、設定に苦労しました。
今後 Twist.nix を導入する人のために、これについての知見を共有します:
俺の設定は以下のリポジトリにあります。動かない時はこれをみてください
https://github.com/Kyure-A/.emacs.d
Twist.nix は Nix based な package-build です。
Twist.nix を利用することで、
- Emacs Lisp パッケージのバージョンを Commit 単位で固定できる
- Nix がパッケージをビルドしていることが保証されるため、パッケージがインストールされているかのチェックが不要になる
など Emacs の起動の高速化が見込めます。
設定ファイルにはもちろん Emacs Lisp を用いることもできますし、Org-babel の Nix 実装が Twist.nix のエコシステムには存在する為、Org-mode で記述したものを利用することもできます。
また、Twist.nix は use-package
をパースしてくれるため、use-package
を用いて init.el の設定をしている場合は容易に設定を移行することが可能です。
最小構成は以下のようになります:
.
└── .emacs.d/
├── recipes/
│ ├── package-recipe1
│ └── package-recipe2
├── lock/
│ ├── archive.lock
│ ├── flake.lock
│ └── flake.nix
├── nix/
│ └── some-lib.nix
├── flake.lock
├── flake.nix
└── init.org
また、ディレクトリについての説明です:
-
recipes/
Emacs Lisp パッケージの recipe が入ります。これはMELPA のレシピフォーマットで記述でき、quelpa や自身の ELPA を作成して利用している場合は recipe の流用が容易です。
-
lock/
Twist.nix が自動生成するロックファイルが配置される場所です。
-
nix/
flake.nix から参照される任意の .nix ファイルが配置される場所です。flake.nix が肥大化するのを恐れないのであれば、切り出す必要はないためこのディレクトリは不要です。
これらのディレクトリ構成は akirak/emacs-config と terlar/emacs-config を参考にしていて、Contributor (というか Author もいる) の設定であるため確度は高いと思われます。
flake.nix の構成
flake.nix
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
twist.url = "github:emacs-twist/twist.nix";
org-babel.url = "github:emacs-twist/org-babel";
elpa = {
url = "github:elpa-mirrors/elpa";
flake = false;
};
melpa = {
url = "github:melpa/melpa";
flake = false;
};
nongnu = {
url = "github:elpa-mirrors/nongnu";
flake = false;
};
epkgs = {
url = "github:emacsmirror/epkgs";
flake = false;
};
emacs-overlay.url = "github:nix-community/emacs-overlay";
};
outputs = {
self,
nixpkgs,
flake-utils,
emacs,
...
} @ inputs: flake-utils.lib.eachDefaultSystem
(system: let
inherit (nixpkgs) lib;
pkgs = import nixpkgs {
inherit system;
overlays = [
inputs.org-babel.overlays.default
emacs-overlay.overlay
];
};
profile = {
lockDir = ./lock;
initFiles = [ (pkgs.tangleOrgBabelFile "init.el" ./init.org {}) ]
emacsPackage = pkgs.emacs-git;
extraRecipeDir = ./recipes;
};
package = (inputs.twist.lib.makeEnv {
inherit pkgs;
inherit (profile) emacsPackage lockDir initFiles;
registries = (import ./nix/registries.nix inputs) ++ [
{
name = "custom";
type = "melpa";
path = profile.extraRecipeDir;
}
];
});
in {
packages.default = package;
homeModules.twist = {
imports = [
inputs.twist.homeModules.emacs-twist
];
};
apps = package.makeApps {
lockDirName = "lock";
};
});
}
inputs
には ELPA や MELPA などの非 flake のリポジトリがありますが、attribute に flake = false;
を指定することで管理できます。
outputs.profile
では Emacs の設定について記述されており、
-
lockDir
: ロックファイルを生成する位置
-
initFiles
: init.el, early-init.el などをはじめとする設定ファイル
-
emacsPackage
: Twist.nix で生成する環境で使う Emacs パッケージ (Stable にするか HEAD Build を使うかみたいな指定)
が指定されています。
これを outputs.package
で読み込み、Emacs の環境を生成します。
registries.nix の構成
これは nix/
ディレクトリに配置されます。
registries.nix はその名のとおりで、ELPA や MELPA などのパッケージレジストリについて記述しています:
registries.nix
inputs: [
{
name = "gnu";
type = "elpa";
path = inputs.elpa.outPath + "/elpa-packages";
auto-sync-only = true;
exclude = [
"lv"
];
}
{
name = "melpa";
type = "melpa";
path = inputs.melpa.outPath + "/recipes";
}
{
type = "elpa";
path = inputs.nongnu.outPath + "/elpa-packages";
}
{
name = "gnu-devel";
type = "archive";
url = "https://elpa.gnu.org/devel/";
}
{
name = "nongnu-devel";
type = "archive";
url = "https://elpa.nongnu.org/nongnu-devel/";
}
{
name = "emacsmirror";
type = "gitmodules";
path = inputs.epkgs.outPath + "/.gitmodules";
}
]
最小構成は意外とシンプルであり、既存の設定にあるファイル分けが難解さを演出していたことがわかります。
programs.emacs-twist
emacs-d
として .emacs.d の設定を inputs
で読み込み、emacs-config = emacs-d.package.${system}.default
とします
{ emacs-config } : {
programs.emacs-twist = {
enable = true;
emacsclient.enable = true;
createInitFile = true;
config = emacs-config;
};
}
また、emacs-d.homeModules.${system}.twist
を import しておきます。
createInitFile
については true
を指定していないと設定ファイルが生成されないので注意してください(一敗)
実際に設定してみて得た知見について以下に列挙していきます:
ロックファイルの生成方法
再現可能な設定をするために Nix を導入したにも関わらず、Twist.nix を用いた Emacs Lisp のバージョン固定用のロックファイル (archive.lock
, flake.lock
, flake.nix
) を生成する方法がわからず数日困りました。
上の flake.nix の構成で書いた通り、Twist.nix には lib.makeEnv
という関数があり、Emacs の設定環境を構築できます。この環境を flake.nix
の outputs における apps
属性にセットする際に以下のようにします。(package
を lib.makeEnv
で構築した環境とします)
apps = package.makeApps { lockDirName = "lock"; };
このとき、lockDirName
には path ではなく、string を指定することに注意してください。
その後 nix flake show
すると以下のようなレコードが表示されるでしょう:
├───apps
│ ├───aarch64-darwin
│ │ ├───lock: app: no description
│ │ └───update: app: no description
│ ├───aarch64-linux
│ │ ├───lock: app: no description
│ │ └───update: app: no description
│ ├───x86_64-darwin
│ │ ├───lock: app: no description
│ │ └───update: app: no description
│ └───x86_64-linux
│ ├───lock: app: no description
│ └───update: app: no description
apps
は flake の標準出力カテゴリの一つである為、コマンドラインにて
を実行するとロックファイルが lib.makeEnv
の引数に渡した lockDir
と同じ path に生成されます。
Example に現在非推奨なものが多い
inventories
という変数を examples では lib.makeEnv
の引数に渡していると思うんですが、これは非推奨となっていて、registries
という引数に渡すべきらしいです。これについてはあんまり理由がわかりません。
また、Overlay API も非推奨になっています。これについては inputOverrides
を使うことで設定の簡単化を目指したのだと思います。
use-package を使うなら :ensure t する
Twist.nix では、use-package
の :ensure
キーワードをパースしてパッケージをビルドしますが、このとき defcustom
で定義された use-package-ensure-function
を上書きしないと毎回起動時に ELPA に問い合わせてしまうため、起動速度が低下してしまいます。
せっかく Twist.nix がパッケージをビルドしてパッケージの存在を保証しているわけですから、以下のように
(setopt use-package-ensure-function '(lambda (&rest args) t))
としてあげることで、起動時のチェックを抑制して起動速度が向上します。劇的に向上させたいのであれば :defer
とか :hook
を活用してなるべく起動時にパッケージを require
しないようにしましょう。
この詳細については takeokunn の
https://zenn.dev/takeokunn/articles/56010618502ccc
が詳しいです。
ELPA を定期的に取得できなくなる
なんか上手く行ったり行かなかったりするので、Twist.nix の作者が用意したミラーを使うといいです。今回書いた flake.nix
ではこちらを採用しています。
twist.el
https://github.com/emacs-twist/twist.el
なんかホットリロードができるらしいです。
org-babel
https://github.com/emacs-twist/org-babel
100 % Nix で書かれた org-babel-tangle の実装です(すごい)
多すぎるので他は Organization のリポジトリをみてください
https://qiita.com/akirak
Twist.nix の作者の記事を読んでください。