NixOSで最新のVimをビルドする

2024/09/27に公開

TL;DR🦌

https://zenn.dev/kawarimidoll/articles/0a4ec8bab8a8ba
全面的に上記の記事を参考にしておりNixOS用にまとめ直しているだけです。足りない情報は各自で補足して頂くか、あるいは私に文句を言えば生えてきます。
また、前提知識は断りなく省略しているため、以下の記事や公式のマニュアルを読むことをおすすめします。

https://zenn.dev/asa1984/books/nix-introduction
https://zenn.dev/asa1984/books/nix-hands-on

下記のような定義を configuration.nix に導入するとビルドできます。

let
  # pkgsがnixpkgsへの参照であることを仮定
  vim-latest = pkgs.vim.overrideAttrs (oldAttrs: {
    version = "latest";
    src = pkgs.fetchFromGitHub {
      owner = "vim";
      repo = "vim";
      # ビルドしたいタグあるいはコミットハッシュをrevに書く
      rev = "v9.x.xxxx";
      # 一度ビルドを走らせて、出力されたハッシュと置き換える
      # これはダミーハッシュを書いてるけど空文字列でもいい
      hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
    };
    # 以下は筆者がif_luaを使っているため定義しているので必要無ければ消して大丈夫
    configureFlags = oldAttrs.configureFlags ++ [
      "--enable-luainterp"
      "--with-lua-prefix=${pkgs.lua}"
      "--enable-fail-if-missing"
    ];
    buildInputs = oldAttrs.buildInputs ++ [ pkgs.lua ];
  });
in
{
  environment.systemPackages = [
    vim-latest
  ];
}

はじめに

Vimmer が新しい開発環境で一番重要視することは Vim のビルドができることです。[要出典]
Vim 自体のビルドは難しい物ではなく、一般的な環境では依存ライブラリさえ集めれば簡単にビルドを通せます。Linux 環境においてはツールチェーンの導入自体が容易なこともあり、基本的に困ることはありません。

しかし最近おすすめされて導入した NixOS では、構成が特殊なこともあり同じやり方では上手く行きません。筆者は、gcc や make が無い(nix で導入できる)、ncurses が見つからない(これも nix で導入できる)、Lua の組み込みが上手く行かない(nix で導入できるしライブラリは見つかるものの、起動時に SEGV する)などの現象に遭遇して心が折れ、同じ方法を取るのは諦めました。

そうなると、Nix のやり方に従ってパッケージを作成し、ビルドする必要があります。具体的に何をどうやればいいのかを紹介していきます。

パッケージとは

Nix の世界においては原理上バイナリパッケージを区別するための仕組みが必要ないため[1]、パッケージというのは全てビルド手順を記した Nix 式から生成される derivation を指します。
今回の最終目的は、Vim の最新版をビルドする derivation を作成することになります。

パッケージの上書き

1 から書いてもいいのですが、既存の物を再利用するのが楽です。なので Nixpkgs にある Vim のパッケージを利用します。定義はリポジトリから頑張って探すか、公式提供されている検索を使ってSource経由でアクセスできます。
執筆時点では、ここに定義があります。
今回はバージョンを切り替えればいいだけなので version 及び src 属性を上書きしたらいいです。
コピペしてもいいですが、折角なのでもっと筋のいい方法を取りましょう。
Nixpkgsのstdenvを使用して作成されたパッケージには、overrideoverrideAttrsなどの便利なメソッドが生えています。今回は属性の上書きなのでoverrideAttrsを使用します。

pkgs.vim.overrideAttrs (oldAttrs: {
  version = "latest";
  src = pkgs.fetchFromGitHub {
    owner = "vim";
    repo = "vim";
    rev = "v9.x.xxxx";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };
})

上記のように、元になるパッケージの AttrSet を受け取り、上書きする AttrSet を返す関数を渡すと、新しいパッケージが作成されます。

versionについては、ストアパスの生成にそのまま使われるため、古いバージョンのままだと混乱するため上書きするだけです。とりあえずlatestにしておいて大丈夫です。
rec指定子と組み合わせ、ビルドしたいバージョンをこちらに指定してsrcに渡すことも可能です。

pkgs.vim.overrideAttrs (oldAttrs: rec {
  version = "v9.x.xxxx";
  src = pkgs.fetchFromGitHub {
    owner = "vim";
    repo = "vim";
    rev = version;
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };
})

srcは元の定義ではfetchFromGitHubというGitHubからソースツリーを引っ張ってくる関数を使用しているため、引き続きこちらを使用します。
こちらは nixpkgs で提供されるライブラリなのでpkgs.fetchFromGitHubとして参照できます。
以下のステップで書き換えます。

  1. revをビルドしたいバージョンのタグもしくはコミットハッシュに置き換え、hashに空文字列か意味の無いハッシュを指定する
  2. パッケージをどこかで参照した後に一旦ビルドを回す[2]
  3. エラーになって落ちるので、出力されたハッシュをhashにコピペする

上記の作業で最新の Vim のパッケージができたので、後は他のパッケージ同様に扱うだけです。今回の例ではenvironment.systemPackagesに渡しています。

Flake

ここまでの作業を行えば、新しい Vim が使えてとても幸せなのですが、上記のやり方には Vim の激しい更新に追い付くのが困難という欠点があります。
Vim は、歴史がありますが、未だなお開発の盛んなソフトウェアで、激しい時には毎日のように、数コミット積まれるといったことがざらにあります。その度に手でバージョンを切り替えるのは大変な作業です。
更新を間引くなりnvfetcherのような外部のソフトウェアを使うなりのやりようはありますが、今回は Flake という Nix に実験的に搭載されている仕組み[3]を使用します。

Flake は、さながらプログラミング言語のパッケージマネージャのように、入力を記述して操作するとバージョンをその時点での最新に固定してくれます。通常は別の Flake を入力に取りますが、フラグを指定すると任意の非 Flake ソースを取り込んでビルドのソースとして扱えます。

{
  inputs = {
    vim-src = {
      flake = false;
      url = "github:vim/vim";
    };
  };

  outputs =
    { vim-src }:
    {
      # この中ではfetchFromGitHubの結果と同様に扱える
    };
}

このように、面倒なプロセスを経ることなく新しいバージョンを指定できる他、nix flake update コマンドを叩けば一発でその時点の最新を指すように更新してくれます。 これで圧倒的に楽になりますね。
今回は、例が多いので、実際に私が使っている設定でどのように入力を引き回しているかを貼っておきます。

https://github.com/kuuote/nixconf/blob/612cd263b5f07ae9fccf0cf56d1879ab7a5d84f2/flake.nix#L10-L11
https://github.com/kuuote/nixconf/blob/612cd263b5f07ae9fccf0cf56d1879ab7a5d84f2/flake.nix#L29
https://github.com/kuuote/nixconf/blob/612cd263b5f07ae9fccf0cf56d1879ab7a5d84f2/latitude/default.nix#L26
https://github.com/kuuote/nixconf/blob/612cd263b5f07ae9fccf0cf56d1879ab7a5d84f2/nixos/feat/vim.nix#L25

私は Nix を始めて一番最初にこれをやろうとして、kawarimidoll さんに紹介してもらった上記の記事を見様見真似で NixOS 向けに書き直してみたり、Flake が何なのかが理解できずに通常版に書き直してみたり、パッケージ一覧にoverrideAttrsを直接書く時、括弧で括らないと怒られるのを知らなくて無駄に overlay じゃないと駄目と思って使ってみたりと色々と遠回りしましたが、結果的に Nix に習熟できてよい体験だったなと思います。
折角ここまで辿り着いたので、当時の私が知りたかった知識について記述しておきました。
ちなみに、kawarimidoll さんの記事が nix-darwin 向けに書かれていることから分かる通り、パッケージの仕組みは NixOS 以外でも同じため流用できます。

Nix は全てが Nix 言語で書かれたテキストを元に動作します。そのため何をするにしてもテキストエディタの出番があります。最新のエディタで気持ちよく編集していきましょう。
Happy Vimming!!

脚注
  1. 代わりに Substituter という仕組みがあります。詳しくはこちらを参照ください ↩︎

  2. Nix言語は非正格評価の言語なので、宣言しても参照されないとそもそも評価されません ↩︎

  3. 実験的と言いつつデファクトスタンダートみたいな物になっていますが ↩︎

GitHubで編集を提案

Discussion