Nix 开发环境
使用 Nix flakes、nixpkgs unstable、devenv 和 direnv 创建和维护项目开发环境。保持 flake 公共接口小巧,将 shell 特定细节放在单独的 Nix 文件中。
交互契约
将 Nix 配置视为可执行的项目策略。不要隐式修改或激活它。
- 首先检查仓库并解释拟议的更改。
- 在创建、编辑、重命名或删除文件之前,请求一次明确批准。将相关编辑合并到一个清晰的请求中。
- 在编辑获得批准并完成后,提出验证命令,并在运行任何命令之前请求单独的批准。
- 未经明确点名并获批准,不得运行
direnv allow。该命令授权自动评估项目配置。
如果用户拒绝任何一项批准,则仅提供计划或命令,并在该边界处停止。
默认值与范围
- 新工作需要一个 flake。除非项目已锁定其他修订版且用户要求更改,否则使用
nixpkgs/nixos-unstable。 - 支持所有语言。仅在项目需要时添加语言包、格式化工具、linter、服务和环境变量。
- 优先使用
nix develop和devShells.<system>.default作为可移植接口。 - 当需要声明性模块、服务、脚本、测试和 git hooks 等特性时,使用
devenv。不要在原始mkShell和 devenv 中创建重复定义,除非说明哪个是权威来源。 - 仅当用户要求或项目已使用
direnv时,才通过检查式.envrc集成direnv。保留用户的本地环境设置。 - 不要仅仅因为偏好更新布局就替换或删除现有的
shell.nix、flake.nix、.envrc、devenv.nix、锁文件或 CI 配置。
在提议更改之前进行检查
至少查找:
flake.nix、flake.lock、shell.nix、default.nix以及nix/目录devenv.nix、devenv.yaml和.envrc- 语言清单和锁文件、格式化工具/linter 配置、任务运行器和 CI 工作流
- 提及设置命令或支持平台的项目文档
在提议布局之前,阅读现有的 Nix 表达式。确定项目是否已有 flake、支持哪些系统、依赖项如何锁定、devenv 是否已安装或配置,以及 direnv 是否被有意排除。
当某个命令有助于发现时,优先使用只读命令,例如 nix flake metadata 或 nix flake show。如果用户尚未批准命令执行,则在执行前请求许可。
文件布局
对于新建或配置简单的仓库,除非仓库已有更好的惯例,否则提议以下布局:
.
├── flake.nix
├── flake.lock
├── devenv.nix # devenv 特定模块和服务(当使用时)
├── .envrc # direnv 入口点(当使用时)
└── nix/
└── devshell.nix # 包、变量和 shell hooks
flake.nix 应包含 inputs、支持系统的基础设施和导出的 outputs。将 mkShell 参数、包列表、环境变量和 shell hooks 放在 nix/devshell.nix 中。当 shell 显著增长时,在 nix/ 下使用更多聚焦的文件,例如 nix/packages.nix 或 nix/hooks.nix。
新设置不要使用 shell.nix。如果已存在,则保留它,除非用户明确批准迁移计划。在迁移过程中,解释对仍使用 nix-shell 的用户的兼容性影响。
设计工作流
1. 陈述当前状况
总结相关现有文件、检测到的项目工具以及任何不确定性。如果需求不完整,提出有针对性的问题,例如:
- 必须支持哪些平台(例如,仅 Linux 还是 Linux 和 macOS)?
- 哪些项目命令应在 shell 内工作?
- 是否需要数据库或其他本地服务?
devenv应该拥有环境,还是应该在devShell周围添加功能?
2. 提议实现方案
准确描述将添加或更改哪些文件及其原因。包括 flake.lock 是否会更改、.envrc 是否会被创建或编辑,以及每个依赖项的来源。
对于新的通用 flake,使用显式的支持系统策略并暴露 devShells.<system>.default。导入单独的 shell 表达式,而不是将整个 mkShell 主体放在 flake.nix 中。保持初始包集合最小:不要仅仅为了模板看起来完整而添加任意语言工具链。
如果用户请求 devenv,选择与已安装/版本锁定的 devenv 设置兼容的集成。将 devenv 声明保存在 devenv.nix(或其导入的聚焦文件)中。明确用户是通过 nix develop、devenv shell 还是两者进入,并确保文档化的入口点不会无声地分歧。
对于 direnv,使用项目现有的惯例。基于 flake 的 .envrc 通常依赖于 nix-direnv 支持;基于 devenv 的 .envrc 依赖于相应的 devenv 集成。不要编写其所需 direnv 扩展尚未确定的入口点。
将这些视为独立的事实:nix-direnv 是否已安装、是否已与此用户的 direnv 集成,以及此项目的 .envrc 是否评估成功。不要使用 direnv stdlib(或其输出中缺少 use flake)作为安装检查:它反映了该调用可用的标准库,对于声明式 NixOS 或 Home Manager 配置不具有权威性。特别是,空的 direnv stdlib | grep -F 'use flake' 结果不是 nix-direnv 缺失的证据。
当发现操作被批准后,检查相关证据,不要夸大其证明作用:
- 在 NixOS 或可用的 Home Manager 配置上,查找
programs.direnv.enable = true和programs.direnv.nix-direnv.enable = true(或等效的模块配置)。将其报告为声明式配置,而不是活动 shell 已加载它的证据。 - 检查
nix-direnv和direnv在当前环境中是否可用,并在可访问时检查适用的direnvrc或生成的 direnv 配置。将不可用的可执行文件或缺失的集成文件报告为当前环境观察,而不是关于用户声明式配置的声明。 - 读取项目的
.envrc以识别其所需的扩展。使用direnv status检查其当前的授权/加载状态。在.envrc在该项目中被评估之前,不要推断use flake入口点有效。
只有在获得明确批准后,才授权和测试项目的 .envrc。明确指出 direnv allow 会更改信任状态;随后运行 direnv reload 或 direnv exec . <harmless-command> 来执行配置。如果无法执行评估,则说明集成未经验证,而不是声明 nix-direnv 未安装。
3. 获取编辑批准
使用类似的简洁确认请求:
我将添加
nix/devshell.nix、更新flake.nix以导出默认开发 shell、添加/更新devenv.nix和.envrc,并在需要时生成/更新flake.lock(如果需要依赖项解析)。我可以进行这些编辑吗?
等待肯定答复。如果拟议工作与已批准计划不同,请在添加额外编辑前再次询问。
4. 仔细编辑
保留格式和本地命名约定。将注释集中在非显而易见的约束上,而不是重申 Nix 语法。通过 flake.lock 锁定 flake inputs;不要使用浮动 URL 替代锁文件。
添加 shell 包时,优先使用项目锁定的 pkgs 提供的包。除非用户明确请求并批准,否则避免使用 unfree 包、不安全包允许、二进制缓存或 substituter 配置。
5. 提议验证并获取单独批准
编辑后,仅列出相关命令,解释其目的,并请求批准。从以下内容中选择:
nix flake check
nix flake show
nix develop --command <project-check-or-tool-version-command>
devenv test
devenv shell -- <project-check-or-tool-version-command>
direnv allow
尽可能使用无害的命令进行 shell 入口验证,例如所需工具的 --version 或项目格式化工具的检查模式。在可能导致网络访问、构建、下载或锁文件更新的命令之前,解释预期的网络访问、构建、下载或锁文件更新。除非用户明确要求,否则切勿运行 nix flake update、广泛的垃圾回收、存储删除或更改配置文件的命令。
6. 报告结果
说明已更改的文件、入口命令、验证结果以及任何剩余的手动步骤。例如,确定用户在审查 .envrc 后是否应运行 nix develop、devenv shell 和/或 direnv allow。
故障排除原则
- 在更改锁定或添加强制覆盖之前,重现并检查实际错误。
- 区分评估失败、缺失属性、平台支持问题、构建失败和运行时依赖问题。
- 在假设不稳定包存在或具有预期名称之前,检查锁定的 input 和包属性。
- 优先使用小的、有文档记录的更正,而不是全局 Nix 配置更改。
- 当更改可能影响可重现性或所有贡献者时,提出替代方案并在编辑前请求批准。
完成标准
仅当配置是模块化的、声明的入口点在最终响应中有文档记录、请求的检查要么已通过要么已清晰报告其结果,并且每次编辑和验证命令都事先获得了所需的批准时,才认为任务完成。