nix-platform-specific-options について
nix-platform-specific-optionsは、プラットフォーム依存のオプションを含むNixモジュール作成の参考スキルです。プラットフォーム判定にoptionalAttrs、設定条件にmkIfを使い、オプション欠落や無限再帰を防ぐ方法を説明します。
主な機能
- 01プラットフォーム判定と設定判定の使い分け
- 02プラットフォーム固有のオプションパスの非表示
- 03モジュール評価時の無限再帰の回避
- 04静的なオプション既定値とconfigでの解決
- 05よくある再帰パターンのデバッグ
活用シーン
- 01NixOSとDarwin向けのモジュールオプション作成
- 02プラットフォーム間のオプション欠落エラーの調査
- 03再帰を引き起こすconfig参照の特定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ edmundmiller/ dotfiles --skill nix-platform-specific-optionsインストール後、Claude Code で/nix-platform-specific-optionsを入力して読み込まれたか確認します。
詳細
Nix のモジュールにおけるプラットフォーム固有のオプション
概要
プラットフォーム固有のオプション(NixOS と Darwin)を隠す必要がある Nix モジュールを記述する際、mkIf だけを使うと無限再帰が発生します。このスキルでは、正しいパターンについて説明します。
問題
mkIf は遅延評価されますが、モジュールの評価中にはオプションのパスが可視のままです。そのため、次のようなエラーが発生します。
error: The option `users.defaultUserShell' does not exist.
または、オプションのデフォルト値や optionalAttrs の条件で config を参照すると、無限再帰が発生します。
パターン
プラットフォームのチェックには optionalAttrs を、設定に依存するチェックには mkIf を使います。
| チェックの種類 | ツール | 評価時期 |
|---|---|---|
プラットフォーム(isDarwin、!isDarwin) | optionalAttrs | 解析時 |
設定値(cfg.enable、cfg.flavor) | mkIf | 遅延 |
例
❌ 誤り:プラットフォームのチェックに mkIf を使用
config = mkIf (!isDarwin) {
users.defaultUserShell = pkgs.zsh; # Darwin sees this path!
};
✅ 正しい:プラットフォームのチェックに optionalAttrs を使用
config = optionalAttrs (!isDarwin) {
users.defaultUserShell = pkgs.zsh; # Hidden from Darwin
};
❌ 誤り:optionalAttrs の条件で設定値を使用
# cfg.flavor evaluated at parse time → infinite recursion
(optionalAttrs (isDarwin && cfg.flavor == "personal") {
services.onepassword-secrets.enable = true;
})
✅ 正しい:optionalAttrs の中に mkIf をネスト
# Platform check at parse time, config check lazy
(optionalAttrs isDarwin (mkIf (cfg.flavor == "personal") {
services.onepassword-secrets.enable = true;
}))
❌ 誤り:オプションのデフォルト値で config を参照
options.modules.foo = {
user = mkOpt types.str config.user.name; # Infinite recursion!
};
✅ 正しい:デフォルト値は静的にし、設定セクションで config を使用
options.modules.foo = {
user = mkOpt types.str null;
};
config = mkIf cfg.enable (let
user = if cfg.user != null then cfg.user else config.user.name;
in {
# Use 'user' variable here
});
組み合わせパターン
プラットフォーム固有のオプションと設定に依存する動作の両方があるモジュールでは、次のようにします。
config = mkIf cfg.enable (mkMerge [
# Common config (all platforms)
{ /* ... */ }
# Darwin-only options
(optionalAttrs isDarwin {
programs.zsh.interactiveShellInit = "...";
})
# NixOS-only options
(optionalAttrs (!isDarwin) {
users.defaultUserShell = pkgs.zsh;
})
# Darwin + config-dependent (nested)
(optionalAttrs isDarwin (mkIf (cfg.flavor == "personal") {
services.onepassword-secrets.enable = true;
}))
]);
クイックリファレンス
| シナリオ | パターン |
|---|---|
| NixOS 専用のオプション | optionalAttrs (!isDarwin) { ... } |
| Darwin 専用のオプション | optionalAttrs isDarwin { ... } |
| プラットフォーム+有効化チェック | optionalAttrs isDarwin (mkIf cfg.enable { ... }) |
| プラットフォーム+設定値 | optionalAttrs isDarwin (mkIf (cfg.foo == "bar") { ... }) |
| 設定から取得するオプションのデフォルト値 | デフォルト値に null を使用し、config セクションで解決する |
デバッグ
_module.freeformType または anon-43 に言及する無限再帰エラーが発生した場合:
- オプションのデフォルト値に
config.の参照がないか検索します optionalAttrsの条件にcfg.の参照がないか検索します- プラットフォーム固有のオプションを囲む
mkIf (!isDarwin)またはmkIf isDarwinがないか検索します
# Find problematic patterns
grep -rn "mkOpt.*config\." modules/
grep -rn "optionalAttrs.*cfg\." modules/
grep -rn "mkIf.*isDarwin" modules/
コメント
Markdown 対応