Homebrew管理下のCLIをNixに移してみる
macOSにおけるパッケージマネージャといえばHomebrewが定番ですね。
ところで、最近は純粋関数型パッケージマネージャのNixが流行りで激アツです。HomebrewのようにグローバルなCLIツールの管理もできるということを知り、やってみました。
本記事はApple Silicon Mac環境での作業を基に書いているので、他環境の場合は適宜読み替えてください。
導入
とりあえずNixの基礎的な部分はこちらで勉強できます。
上記のzero-to-nixを管理しているDeterminateSystemsが公式のインストーラをラップしたnix-installer
というものを提供しています。これでインストールするのが便利です。
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
インストール完了したらターミナルを再起動し、$PATH
に~/.nix-profile/bin
が入っているか確認してください。
なお、筆者はシェル設定の最後にhomebrewのパスを定義する設定を入れていたため、nixで入れたものよりhomebrewで入れたものが優先される形になってしまっていました。パスの順序も注意してください。
flake.nixの作成
設定ファイルを置くディレクトリ(Git管理下)でnix flake init
しましょう。flake.nix
が作られます。
デフォルトではシステム指定がlinuxになっているので、aarch64-darwin
に書き換えます。
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs }: {
packages.aarch64-darwin.hello = nixpkgs.legacyPackages.aarch64-darwin.hello;
packages.aarch64-darwin.default = self.packages.aarch64-darwin.hello;
};
}
システム指定を修正したら、以下を実行して動作を確かめましょう。outputs
のdefault
に指定されているもの(ここではnixpkgsのhello)がビルドされます。
nix build
ビルド後にresult
というディレクトリができていたら成功です。result/bin/hello
にビルド結果のプログラムが入っています。
./result/bin/hello --help
確認できたらresult
ディレクトリは削除して構いません。
パッケージ定義の記述
flake.nix
を修正して、パッケージの定義を書いていきます。
nixpkgsから取り込む
nixpkgsの提供しているパッケージは以下のページで検索できます。
flakeのoutputs
を修正します。今回は例としてgitとcurlを指定します。
{
description = "Minimal package definition for aarch64-darwin";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs }: {
packages.aarch64-darwin.my-packages = nixpkgs.legacyPackages.aarch64-darwin.buildEnv {
name = "my-packages-list";
paths = [
nixpkgs.legacyPackages.aarch64-darwin.git
nixpkgs.legacyPackages.aarch64-darwin.curl
# ここにパッケージを追記していく
];
};
};
}
以下のコマンドでprofileを反映します。
nix profile install .#my-packages
nix profile list
でinstallされた設定を見られます。筆者の例はこんな感じです。
❯ nix profile list
Name: my-packages
Flake attribute: packages.aarch64-darwin.my-packages
Original flake URL: git+file:///Users/kawarimidoll/dotfiles
Locked flake URL: git+file:///Users/kawarimidoll/dotfiles
Store paths: /nix/store/b5jphy5nm1ab1pz0xp08lmw7wf11pb7h-my-packages-list
ここで示されている/nix/store/{hash}-{package-name}/bin
にgitとcurlの実行ファイルがあるはずです。
こんな感じで実行ファイルが~/.nix-profile/bin/
以下のファイルになっていればインストール成功です。できていない場合は前述の通り$PATH
が上書きされている可能性があるので確認してください。
❯ which git
/Users/kawarimidoll/.nix-profile/bin/git
このprofileはnameを指定してアンインストールできます。
nix profile remove my-packages
設定を変更した場合は、nameを指定して更新します。nix flake update
もやっておくと良いみたいです。
nix flake update
nix profile upgrade my-packages
本記事ではgitとcurlだけ書いていますが、もちろん他のパッケージも追加できます。例えば、.nix
ファイルのフォーマッターであるalejandraなどが便利です。
nixpkgs以外から取り込む
nixpkgsで公開されているパッケージはバージョンが最新でないことがあります。たとえばNeovimは、記事執筆時点ではバージョンは0.9.5です。
nixpkgsのNeovimパッケージ
Neovimは機能追加・不具合修正が活発にされているため、(stableではなく)開発中の最新版を使いたいところです。幸い、nix-communityがneovim-nightly-overlayというパッケージを公開しています。これを導入します。
inputsでneovim-nightly-overlayを読み込み、outputsの引数に指定し、pathsの配列に追加します。
{
description = "Minimal package definition for aarch64-darwin";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
+ neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
};
outputs = {
self,
nixpkgs,
+ neovim-nightly-overlay,
}: {
packages.aarch64-darwin.my-packages = nixpkgs.legacyPackages.aarch64-darwin.buildEnv {
name = "my-packages-list";
paths = [
nixpkgs.legacyPackages.aarch64-darwin.git
nixpkgs.legacyPackages.aarch64-darwin.curl
+ neovim-nightly-overlay.packages.aarch64-darwin.neovim
];
};
};
}
GitHubから取り込む
Neovimは上記の通りneovim-nightly-overlayが公開されていますが、使いたいものが常にnix用に整備されているとは限りません。たとえばVimはこのような便利パッケージはありません。
したがって、GitHubから直接取り込み、自前でビルドする手段を取りました。
inputのvim-src.url
でURLを指定するのは前述のnixpkgsやneovim-nightly-overlayと同じですが、このリポジトリはflake.nixがないのでflake = false
を指定します。
そして、overrideAttrsでnixpkgsのvimの設定を上書きします。
- versionにはバージョン名
- ここでは、
/nix/store/{hash}-vim-latest/bin/vim
に実行ファイルがインストールされる
- ここでは、
- srcには読み込み元
- configureFlagsにはビルド時のフラグ
- buildInputsには依存関係
{
description = "Minimal package definition for aarch64-darwin";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
+ vim-src = {
+ url = "github:vim/vim";
+ flake = false;
+ };
};
outputs = {
self,
nixpkgs,
neovim-nightly-overlay,
+ vim-src,
}: {
packages.aarch64-darwin.my-packages = nixpkgs.legacyPackages.aarch64-darwin.buildEnv {
name = "my-packages-list";
paths = [
nixpkgs.legacyPackages.aarch64-darwin.git
nixpkgs.legacyPackages.aarch64-darwin.curl
+ (nixpkgs.legacyPackages.aarch64-darwin.vim.overrideAttrs (oldAttrs: {
+ version = "latest";
+ src = vim-src;
+ configureFlags =
+ oldAttrs.configureFlags
+ ++ [
+ "--enable-terminal"
+ "--with-compiledby=kawarimidoll-nix"
+ "--enable-luainterp"
+ "--with-lua-prefix=${nixpkgs.legacyPackages.aarch64-darwin.lua}"
+ "--enable-fail-if-missing"
+ ];
+ buildInputs =
+ oldAttrs.buildInputs
+ ++ [
+ nixpkgs.legacyPackages.aarch64-darwin.gettext
+ nixpkgs.legacyPackages.aarch64-darwin.lua
+ nixpkgs.legacyPackages.aarch64-darwin.libiconv
+ ];
+ }))
neovim-nightly-overlay.packages.aarch64-darwin.neovim
];
};
};
}
luaのパスの指定(--with-lua-prefix
オプション)が曲者だったのですが、変数展開することでnix内のluaのパスを使うことができます。ビルド後にvim --version
でLinkingの項目を見たところ、/nix/store/{hash}-lua-{version}/lib
のパスが指定されているのを確認できました。
なお、これらのフラグはhomebrewの設定を参考にしました。
リファクタリング
ここまでで基本的なパッケージの導入はできました。
以降はファイル内の冗長な部分を修正していきます。
まず、パッケージ指定でnixpkgs.legacyPackages.aarch64-darwin.
が繰り返されているのが気になります。これはwith
を使ってまとめることができます。
(diff表示だとかえって見づらくなったので)まとめた結果を示します。neovim-nightly-overlayはnixpkgs配下ではないので、配列を分割し、++
で連結しています。
{
# description/inputsは省略
outputs = {
# 引数略...
}: {
packages.aarch64-darwin.my-packages = nixpkgs.legacyPackages.aarch64-darwin.buildEnv {
name = "my-packages-list";
paths = with nixpkgs.legacyPackages.aarch64-darwin;
[
git
curl
(vim.overrideAttrs (oldAttrs: {
version = "latest";
src = vim-src;
configureFlags =
oldAttrs.configureFlags
++ [
"--enable-terminal"
"--with-compiledby=kawarimidoll-nix"
"--enable-luainterp"
"--with-lua-prefix=${lua}"
"--enable-fail-if-missing"
];
buildInputs =
oldAttrs.buildInputs
++ [ gettext lua libiconv ];
}))
]
++ [neovim-nightly-overlay.packages.aarch64-darwin.neovim];
};
};
}
overlayはその名の通り上書き用の設定です。extendを使って、nixpkgsのneovimをnightlyに上書きしましょう。
let ... in
で、nixpkgsにneovim-nightly-overlayを適用し、pkgs
という変数にします。以下のように書けます。
{
# description/inputsは省略
outputs = {
# 引数略...
- }: {
+ }:
+ let
+ pkgs = nixpkgs.legacyPackages.aarch64-darwin.extend (
+ neovim-nightly-overlay.overlays.default
+ );
+ in
+ {
- packages.aarch64-darwin.my-packages = nixpkgs.legacyPackages.aarch64-darwin.buildEnv {
+ packages.aarch64-darwin.my-packages = pkgs.buildEnv {
name = "my-packages-list";
- paths = with nixpkgs.legacyPackages.aarch64-darwin;
+ paths = with pkgs;
[
# neovim以外のパッケージは省略
- ]
- ++ [neovim-nightly-overlay.packages.aarch64-darwin.neovim];
+ neovim
+ ];
};
};
}
システム指定も変数にします。
# ここまで略...
let
- pkgs = nixpkgs.legacyPackages.aarch64-darwin.extend (
+ system = "aarch64-darwin";
+ pkgs = nixpkgs.legacyPackages.${system}.extend (
neovim-nightly-overlay.overlays.default
);
in
{
- packages.aarch64-darwin.my-packages = pkgs.buildEnv {
+ packages.${system}.my-packages = pkgs.buildEnv {
name = "my-packages-list";
# 以下省略
これで冗長な記述を圧縮することができました。
更新タスクの追加
設定更新時には以下のコマンドを実行すると書きましたが:
nix flake update
nix profile upgrade my-packages
これを一括で実行できるようにします。apps
という項目を追加します。
{
packages.${system}.my-packages = pkgs.buildEnv {
# 略...
};
+ apps.${system}.update = {
+ type = "app";
+ program = toString (pkgs.writeShellScript "update-script" ''
+ set -e
+ echo "Updating flake..."
+ nix flake update
+ echo "Updating profile..."
+ nix profile upgrade my-packages
+ echo "Update complete!"
+ '');
+ };
}
apps
のprogram
には既存の実行ファイルのパスを指定するのが原則らしいのですが↓
pkgs.writeShellScript {script-name} {content}
を使うことで、シェルスクリプトを直接記述して実行することができます。これで、以下の1コマンドで更新をかけられます。
nix run .#update
豆知識
flakeを更新→反映を繰り返しているとnixの環境を都度作り直すことになります。古いバージョンが溜まっている可能性があるので、設定が整ったら掃除しておきましょう。
nix store gc
また、flakeを問い合わせるためにGitHub APIを叩くので、何度も調整しているとRate Limitに引っかかる場合があります(筆者は引っかかりました)。設定ファイルにトークンを追加すると回数を増やすことができます。
access-tokens = github.com=abcd1234
この記事で作ったflake.nixの完成版
{
description = "Minimal package definition for aarch64-darwin";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
vim-src = {
url = "github:vim/vim";
flake = false;
};
};
outputs = {
self,
nixpkgs,
neovim-nightly-overlay,
vim-src,
}: let
system = "aarch64-darwin";
pkgs = nixpkgs.legacyPackages.${system}.extend (
neovim-nightly-overlay.overlays.default
);
in {
packages.${system}.my-packages = pkgs.buildEnv {
name = "my-packages-list";
paths = with pkgs; [
git
curl
(vim.overrideAttrs (oldAttrs: {
version = "latest";
src = vim-src;
configureFlags =
oldAttrs.configureFlags
++ [
"--enable-terminal"
"--with-compiledby=kawarimidoll-nix"
"--enable-luainterp"
"--with-lua-prefix=${lua}"
"--enable-fail-if-missing"
];
buildInputs = oldAttrs.buildInputs ++ [gettext lua libiconv];
}))
neovim
];
};
apps.${system}.update = {
type = "app";
program = toString (pkgs.writeShellScript "update-script" ''
set -e
echo "Updating flake..."
nix flake update
echo "Updating profile..."
nix profile upgrade my-packages
echo "Update complete!"
'');
};
};
}
続編
以下の記事に続きます。
Discussion