🏡

Homebrew管理下のCLIをNixに移してみる Home Manager篇

2024/07/29に公開

macOSにおけるパッケージマネージャといえばHomebrewが定番ですね。

https://brew.sh/

ところで、最近は純粋関数型パッケージマネージャのNixが流行りで激アツです。Nix関連ツールのHome Managerを使うと、Homebrewのようなパッケージ管理ができるということを知り、やってみました。

https://nixos.wiki/wiki/Home_Manager

前回の記事で作ったファイルを書き換える形で進めていくので、先に以下の記事をご確認ください。また、Apple Silicon Mac環境での作業を基に書いています。

https://zenn.dev/kawarimidoll/articles/0a4ec8bab8a8ba

導入

前回作ったflakeにhome-managerを導入します。

  • inputsに追加
  • outputsの引数に追加
  • outputsの本体にhomeConfigurationsを追加

また、 homeConfigurationsでinputsを使いたい(home-managerの設定にinputsを渡したい)ので、outputsの引数部分に@ inputsを追加します。

flake.nix
 {
   description = "Minimal package definition for aarch64-darwin";
   inputs = {
     # 略...
+    home-manager = {
+      url = "github:nix-community/home-manager";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
   };

   outputs = {
     # 略...
-  }: let
+    home-manager,
+  } @ inputs: let
     system = "aarch64-darwin";
     pkgs = nixpkgs.legacyPackages.${system}.extend (
       neovim-nightly-overlay.overlays.default
     );
   in {
     # 略...
+    homeConfigurations = {
+      myHomeConfig = home-manager.lib.homeManagerConfiguration {
+        pkgs = import nixpkgs {
+          system = system;
+        };
+        extraSpecialArgs = {
+          inherit inputs;
+        };
+        modules = [
+          ./.config/home-manager/home.nix
+        ];
+      };
+    };
   };
 }

前回の記事ではnixpkgsのシステム指定(およびoverlayの指定)にextendを使いましたが、今回の記事ではimportを使っています。できることは同じだと思いますが、Web上のサンプルだとimportを使っているものが多い印象です。

https://nixos.wiki/wiki/Overlays#In_a_Nix_flake

Home Managerの設定自体は別のファイルに書いていきます。筆者は上記のflakeをdotfilesリポジトリに置いているので、Home Managerの設定は./.config/home-manager/home.nixに配置することにしました。パスはお好みで変更してください。

最初のhome.nixはこんな感じです。

home.nix
{
  inputs,
  lib,
  config,
  pkgs,
  ...
}: let
  username = "kawarimidoll";
in {
  nixpkgs = {
    config = {
      allowUnfree = true;
    };
  };

  home = {
    username = username;
    homeDirectory = "/Users/${username}";

    # https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
    stateVersion = "24.05";
  };

  programs.home-manager.enable = true;
}

注意点としては、usernameおよびhomeDirectoryは現在のシステムの$USERおよび$HOMEとそれぞれ一致している必要があります。Webで見つけたサンプルは、homeDirectory/home/${username}としているものが多かったです。macOSではホームディレクトリが/Users/${username}なので、以下の通り怒られます。

Error: HOME is set to "/Users/kawarimidoll" but we expect "/home/kawarimidoll"

また、stateVersionは更新時には注意が必要なようですが、新規作成なので最新バージョンでいいでしょう。記事執筆時点では24.05が最新です。

https://nixos.org/download/#nixos

設定を保存したら、以下のコマンドでビルドします。myHomeConfigはflakeのoutputsのhomeConfigurationsに設定したキーの名前です。

nix run nixpkgs#home-manager -- switch --flake .#myHomeConfig

nixはgitを使ってファイルを認識します。home.nixをgit addしておくことを忘れずに。


実行例スクショ

エラーなく完了すれば、nix run nixpkgs#home-manager -- generationsでgenerationsが確認できます。第一世代の誕生です。

パッケージ定義の記述

前回の記事で導入したパッケージをHome Managerの管理下に移動させていきます。Home Managerのほうの設定を少しずつ増やし、それに伴って旧設定を少しずつ消していきます。

nixpkgsから取り込む

flakeでhome-manager.lib.homeManagerConfiguration.pkgsにnixpkgsを設定しているので、このファイル内ではpkgsからnixpkgsのパッケージを取り込むことができます。以下のようにhome.packagesにパッケージを指定します。

home.nix
 {
   # 略...
   home = {
     # 略...
     
+    packages = with pkgs; [
+      git
+      curl
+    ];
   };
   # 略...
 }

withは前回の記事で登場しました。つまりこう書いているのと同じです。

    packages = [
      pkgs.git
      pkgs.curl
    ];

旧設定のほうからgitとcurlの設定を消します。

flake.nix
 {
   # 略...

   outputs = {
     # ...
   }: 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: {
           # ...
         }))
         neovim
      ];
     };
     # ...
   };
 }

まだ旧設定が残っているので、nix run .#updateでgitとcurlの削除を反映したあと、Home Managerをビルドします。再びgitとcurlが使えるようになれば成功です。

nixpkgs以外のflakeから取り込む

前回と同様、nix-community/neovim-nightly-overlayを導入します。

nixpkgsの中にoverlaysを追加します。neovim-nightly-overlayはinputsを介して呼び出すことができます。
これでhome.packagesに追加したneovimがnightlyになります。

home.nix
 {
   inputs,
   # ...
 }: let
   username = "kawarimidoll";
 in {
   nixpkgs = {
+    overlays = [
+      inputs.neovim-nightly-overlay.overlays.default
+    ];
     config = {
       allowUnfree = true;
     };
   };
   home = {
     # ...
     packages = with pkgs; [
       git
       curl
+      neovim # nighly
     ];
   };
   # ...
 }

旧設定からneovim関連の記述を消しておきましょう。

flake.nix
 {
   # 略...

   outputs = {
     # ...
   }: let
     system = "aarch64-darwin";
-    pkgs = nixpkgs.legacyPackages.${system}.extend (
-      neovim-nightly-overlay.overlays.default
-    );
+    pkgs = nixpkgs.legacyPackages.${system};
   in {
     packages.${system}.my-packages = pkgs.buildEnv {
       name = "my-packages-list";
       paths = with pkgs; [
         (vim.overrideAttrs (oldAttrs: {
           # ...
         }))
-        neovim
      ];
     };
     # ...
   };
 }

GitHubから取り込む

最後にVimの最新版の取得・ビルドの設定を行います。overlaysに設定を追加します。prev.xxxがオーバーレイ前のパッケージですね。
これでhome.packagesに追加したvimがlatestになります。

home.nix
 {
   inputs,
   # ...
 }: let
   username = "kawarimidoll";
 in {
   nixpkgs = {
     overlays = [
+      (final: prev: {
+        vim = prev.vim.overrideAttrs (oldAttrs: {
+          version = "latest";
+          src = inputs.vim-src;
+          configureFlags =
+            oldAttrs.configureFlags
+            ++ [
+              "--enable-terminal"
+              "--with-compiledby=nix-home-manager"
+              "--enable-luainterp"
+              "--with-lua-prefix=${prev.lua}"
+              "--enable-fail-if-missing"
+            ];
+          buildInputs =
+            oldAttrs.buildInputs
+            ++ [prev.gettext prev.lua prev.libiconv];
+        });
+     })
       inputs.neovim-nightly-overlay.overlays.default
     ];
     config = {
       allowUnfree = true;
     };
   };
   home = {
     # ...
     packages = with pkgs; [
       git
       curl
+      vim # latest
       neovim # nighly
     ];
   };
   # ...
 }

↑このprev.withを使ってまとめたかったのですが、うまいやり方がわかりませんでした。

これで旧設定からパッケージ定義がなくなりました。

flake.nix
 {
   # 略...

   outputs = {
     # ...
   }: let
     system = "aarch64-darwin";
     pkgs = nixpkgs.legacyPackages.${system};
   in {
     packages.${system}.my-packages = pkgs.buildEnv {
       name = "my-packages-list";
       paths = with pkgs; [
-        (vim.overrideAttrs (oldAttrs: {
-          # ...
-        }))
      ];
     };
     # ...
   };
 }

コード整理

パッケージが導入できたので、flake.nixのoutputsから不要な記述を消します。my-packages関連の部分は完全に消すことができます。
定義削除後、nix profile remove my-packagesしてmy-packagesのプロファイルを消しておきましょう。
また、Home Managerのビルドをupdateスクリプトにいれると便利でしょう。nix run .#updateでビルドできるようになります。
pkgsのシステム指定もlet ... inの方にまとめます。

flake.nix
  outputs = {
    self,
    nixpkgs,
-   neovim-nightly-overlay,
-   vim-src,
    home-manager,
+   ...
  } @ inputs: let
    system = "aarch64-darwin";
-   pkgs = nixpkgs.legacyPackages.${system};
+   pkgs = import nixpkgs { inherit system; };
  in {
-   packages.${system}.my-packages = pkgs.buildEnv {
-     name = "my-packages-list";
-     paths = with pkgs; [
-     ];
-   };

    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 "Updating home-manager..."
+       nix run nixpkgs#home-manager -- switch --flake .#myHomeConfig
        echo "Update complete!"
      '');
    };

    homeConfigurations = {
      myHomeConfig = home-manager.lib.homeManagerConfiguration {
-       pkgs = import nixpkgs {
-         system = system;
-       };
+       pkgs = pkgs;
        extraSpecialArgs = {
          inherit inputs;
        };
        modules = [
          ./.config/home-manager/home.nix
        ];
      };
    };
  };
この記事で作った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;
    };
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = {
    self,
    nixpkgs,
    home-manager,
    ...
  } @ inputs: let
    system = "aarch64-darwin";
    pkgs = import nixpkgs { inherit system; };
  in {
    apps.${system}.update = {
      type = "app";
      program = toString (pkgs.writeShellScript "update-script" ''
        set -e
        echo "Updating flake..."
        nix flake update
        echo "Updating home-manager..."
        nix run nixpkgs#home-manager -- switch --flake .#myHomeConfig
        echo "Update complete!"
      '');
    };

    homeConfigurations = {
      myHomeConfig = home-manager.lib.homeManagerConfiguration {
        pkgs = pkgs;
        extraSpecialArgs = {
          inherit inputs;
        };
        modules = [
          ./.config/home-manager/home.nix
        ];
      };
    };
  };
}
{
  inputs,
  lib,
  config,
  pkgs,
  ...
}: let
  username = "kawarimidoll";
in {
  nixpkgs = {
    overlays = [
      (final: prev: {
        vim = prev.vim.overrideAttrs (oldAttrs: {
          version = "latest";
          src = inputs.vim-src;
          configureFlags =
            oldAttrs.configureFlags
            ++ [
              "--enable-terminal"
              "--with-compiledby=nix-home-manager"
              "--enable-luainterp"
              "--with-lua-prefix=${prev.lua}"
              "--enable-fail-if-missing"
            ];
          buildInputs =
            oldAttrs.buildInputs
            ++ [prev.gettext prev.lua prev.libiconv];
        });
      })
      inputs.neovim-nightly-overlay.overlays.default
    ];
    config = {
      allowUnfree = true;
    };
  };

  home = {
    username = username;
    homeDirectory = "/Users/${username}";

    # https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
    stateVersion = "24.05";

    packages = with pkgs; [
      git
      curl
      vim # latest
      neovim # nighly
    ];
  };

  programs.home-manager.enable = true;
}

参考資料

この記事の設定をするにあたり、以下がたいへん参考になりました。

https://github.com/Misterio77/nix-starter-configs

https://zenn.dev/asa1984/articles/nixos-is-the-best#home-manager

なお、Home ManagerはCLI導入専用のツールではありません。設定可能な項目は多岐にわたります。ドキュメントはこちら。

https://nix-community.github.io/home-manager/options.xhtml

続編

以下の記事に続きます。

https://zenn.dev/kawarimidoll/articles/271c339c5392ce

Discussion