关于 nix-platform-specific-options
nix-platform-specific-options 是编写含平台相关选项的 Nix 模块参考技能,说明如何用 optionalAttrs 处理平台检查、用 mkIf 处理配置条件,以避免选项缺失错误和无限递归。
主要功能
- 01区分平台检查与配置检查
- 02隐藏特定平台的选项路径
- 03避免模块求值时无限递归
- 04使用静态选项默认值并在配置中解析
- 05排查常见递归模式
适用场景
- 01编写 NixOS 和 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 部分使用 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