月曜日, 4月 28, 2025
Google search engine
ホームニューステックニュースTwist.nix で再現性のある Emacs 設定を作る

Twist.nix で再現性のある Emacs 設定を作る



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-configterlar/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 属性にセットする際に以下のようにします。(packagelib.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 の作者の記事を読んでください。

フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -
Google search engine

Most Popular