引言
nixpkgs 是全球最大的包仓库之一——十万多个包、每天上千次合并。它靠的不是少数人的英雄主义,而是一套高度标准化的贡献流程:pkgs/by-name 让新包免写样板、ofBorg 自动跑 CI、staging 分支做大规模重建、backport 把修复送回稳定版。
本文是一份可操作的贡献指南:从仓库结构讲起,说清 by-name 的约定、打包一个新包的完整流程、提交规范与 review 流程、ofBorg 与 staging 的运作,以及维护者日常要做的版本更新与 backport。
目录
- 1. nixpkgs 仓库结构与组织
- 2. pkgs/by-name 自动调用约定
- 3. 打包一个新包:从零到 PR
- 4. 提交规范与 commit message
- 5. review 流程与维护者职责
- 6. ofBorg CI 与测试
- 7. staging 分支与 backport
- 8. 更新包与版本维护
- 9. 常见拒绝原因与最佳实践
- 10. 速查表与一句话记忆
- 延伸阅读
1. nixpkgs 仓库结构与组织
1.1 顶层目录
| 路径 | 内容 |
|---|---|
pkgs/ | 所有包定义 |
pkgs/by-name/ | 新式自动调用包(第 2 节) |
pkgs/top-level/ | 包集装配(all-packages.nix 等) |
lib/ | Nix 语言库(attrsets/lists/strings) |
nixos/ | NixOS 模块与测试 |
maintainers/ | 维护者名单与脚本 |
doc/ | 文档源码 |
1.2 两个关键文件
# pkgs/top-level/all-packages.nix:属性名 → 包定义的映射表
# maintainers/maintainer-list.nix:维护者 handle → { name, github, email }
all-packages.nix 是传统方式的入口:一个包的属性名必须在这里显式映射。by-name 就是为消除这一步而生的。
1.3 分支模型
| 分支 | 用途 |
|---|---|
master | 开发主线,一切新特性进这里 |
staging | 会触发大规模重建的改动 |
staging-next | staging 的验证与合流 |
nixos-XX.YY | 稳定发布分支(只收修复) |
release-XX.YY | 发布准备 |
记忆:nixpkgs 结构的关键是
pkgs/by-name(新式自动调用)、pkgs/top-level/all-packages.nix(传统属性映射)、lib/(语言库)、nixos/(模块与测试);分支上 master 是主线、staging 做大重建、nixos-XX.YY 是稳定版只收修复。
2. pkgs/by-name 自动调用约定
2.1 目录约定
pkgs/by-name/
he/hello/
package.nix
ri/ripgrep/
package.nix
规则:pkgs/by-name/<前两个字符>/<包名>/package.nix。属性名自动推导为目录名,无需改 all-packages.nix。
2.2 为什么这样设计
| 传统方式 | by-name 方式 |
|---|---|
写 pkgs/xxx/default.nix | 写 pkgs/by-name/xx/xxx/package.nix |
还要在 all-packages.nix 加一行 | 自动暴露为 pkgs.xxx |
| 新包 = 改两个文件 | 新包 = 加一个文件 |
好处:消除合并冲突(all-packages.nix 是巨型文件,人人改必冲突)、降低新包门槛、属性名与目录强绑定。
2.3 package.nix 的写法
{ lib, stdenv, fetchFromGitHub, openssl }:
stdenv.mkDerivation (finalAttrs: {
pname = "mytool";
version = "1.2.3";
src = fetchFromGitHub {
owner = "example";
repo = "mytool";
rev = "v${finalAttrs.version}";
hash = "sha256-AAAA...";
};
buildInputs = [ openssl ];
meta = {
description = "A tool that does something";
homepage = "https://github.com/example/mytool";
license = lib.licenses.mit;
maintainers = [ lib.maintainers.yourhandle ];
mainProgram = "mytool";
};
})
注意用 finalAttrs 而非 rec:version 可以被 rev 引用而无需递归属性集。
记忆:by-name 的约定是
pkgs/by-name/<前两字符>/<包名>/package.nix,属性名自动推导、无需改 all-packages.nix——它消除了巨型文件的合并冲突、降低新包门槛;写 package.nix 时用finalAttrs而非rec。
3. 打包一个新包:从零到 PR
3.1 完整流程
# 1) fork nixpkgs 并 clone 到本地
# 2) 建分支:git checkout -b add-mytool
# 3) 创建 pkgs/by-name/my/mytool/package.nix
# 4) 用 nix-build 或 nix build 构建验证
# 5) 运行 nixpkgs-review 或本地构建
# 6) commit(符合提交规范)
# 7) push 并开 PR
3.2 构建与取哈希
# 先用假哈希触发报错,拿到真实哈希
nix-build -A mytool 2>&1 | grep 'got:'
# 或用 nix-prefetch-url / nix store prefetch-file
nix-prefetch-url --unpack https://github.com/example/mytool/archive/v1.2.3.tar.gz
3.3 本地验证
# 直接构建该属性并运行;nixpkgs-review 可检查是否破坏别的包(见第 5 节)
nix-build -A mytool
./result/bin/mytool --version
# nix-init 可交互式生成 package.nix 骨架
nix run nixpkgs#nix-init
记忆:新包流程是「fork → 建分支 → 写 by-name/package.nix → 取哈希 → 构建验证 → 提交 → 开 PR」;取哈希靠「假哈希触发报错」或
nix-prefetch-url;nix-init可交互式生成骨架。
4. 提交规范与 commit message
4.1 格式
<包名>: <动词> <描述>
<可选正文:为什么这么改>
<可选:Fixes #12345>
示例:
mytool: init at 1.2.3
mytool: 1.2.3 -> 1.2.4
mytool: fix build on aarch64
mytool: add missing openssl dependency
4.2 常用动词
| 动词 | 场景 |
|---|---|
init at X | 新增包 |
X -> Y | 版本更新 |
fix ... | 修构建/运行问题 |
add ... | 增加依赖或功能 |
remove ... | 移除不再需要的东西 |
refactor | 不改变行为的结构调整 |
4.3 规则
# ☐ 包名在前、冒号后空格、动词开头
# ☐ 一次 PR 只做一件事(init 就别顺手升级别的包)
# ☐ 版本更新写 "old -> new" 格式
# ☐ 正文说明「为什么」,代码里看不出时才需要
记忆:nixpkgs 提交规范是
<包名>: <动词> <描述>——init at X(新增)、X -> Y(升级)、fix/add/remove;一次 PR 只做一件事、包名与动词必须规范,因为工具与 changelog 依赖这个格式。
5. review 流程与维护者职责
5.1 PR 生命周期
开 PR → ofBorg 自动测试 → 人工 review → 打标签 → 合并
↑ 失败则修 ↑ 需要时请求特定维护者
5.2 谁来 review
| 角色 | 职责 |
|---|---|
| 包维护者(meta.maintainers) | 该包的把关人 |
| 领域 reviewer | 语言/生态专家 |
| 提交者本人 | 自测 + 响应反馈 |
请求 review 的常用方式:在 PR 里 @ 相关维护者,或用 nixpkgs-review 结果作为证据。
5.3 维护者的日常
# ☐ 关注自己维护包的更新(有人 @ 或 CI 报错)
# ☐ 审阅自己包的 PR,给 approve 或修改意见
# ☐ 跟进上游 breaking change
# ☐ 定期检查包的构建状态(hydra 上的 red 状态)
用 nix run nixpkgs#nixpkgs-review pr 123456 可自动构建「受 PR 影响的所有包」,确认没有破坏。
记忆:PR 生命周期是「开 PR → ofBorg 自动测 → 人工 review → 合并」;review 者优先是 meta.maintainers 里登记的包维护者;维护者日常是「盯自己包的更新与 PR、跟进上游 breaking change、看 hydra 构建状态」,
nixpkgs-review用来确认改动没破坏别的包。
6. ofBorg CI 与测试
6.1 ofBorg 是什么
ofBorg 是 nixpkgs 的 CI 机器人,它会自动:
# 1) 在 PR 上评论构建结果(成功/失败/被跳过)
# 2) 检测提交格式是否规范
# 3) 检测是否影响大量包(提示走 staging)
# 4) 提供「哪些属性被构建」的摘要
6.2 常见 ofBorg 反馈
| 反馈 | 含义 | 应对 |
|---|---|---|
ofborg-eval 失败 | 求值出错 | 修表达式语法/属性 |
| 构建失败 | 某属性构建不过 | 看日志修 |
Mass-rebuild 标签 | 影响包数超阈值 | 需走 staging |
by-name 检查 | 目录结构不合规 | 按约定放 |
6.3 测试类型
# 包内 passthru.tests:为包附上测试
{ passthru.tests.version = testers.testVersion { package = mytool; }; }
NixOS 模块类改动则用 nixos/tests/ 下的集成测试。
记忆:ofBorg 是 nixpkgs 的 CI 机器人——自动在 PR 上报告构建结果、检查提交格式、识别 mass-rebuild(提示走 staging);包内可用
passthru.tests附测试,模块改动用 nixos/tests 集成测试。
7. staging 分支与 backport
7.1 什么时候走 staging
# 触发「大量包重建」的改动必须走 staging,例如:
# - 升级 gcc/glibc/python 等被广泛依赖的包
# - 修改 stdenv 或核心库
# 阈值由 ofBorg 计算,超过就自动打 Mass-rebuild 标签
走 staging 的流程:向 staging 分支开 PR → 通过 staging-next 验证 → 合入 master。
7.2 backport:把修复送回稳定版
# 场景:nixos-24.11 用户需要某个修复
# 做法:把 master 上的 commit cherry-pick 到 release-24.11 分支
# 结果:下个稳定点版本带上该修复
7.3 backport 的两种方式
| 方式 | 操作 |
|---|---|
| 自动 | 在 PR 上评论 @NixOS/nixpkgs-backport-24.11(或对应机器人) |
| 手动 | 直接向 release-24.11 分支开 cherry-pick PR |
7.4 原则
# ☐ 只有「修复」才 backport,新功能与新包不进稳定版
# ☐ backport 的 commit 必须与 master 上一致(cherry-pick 不手改)
# ☐ 破坏性变更(需用户改配置)不 backport
记忆:触发大量重建的改动走 staging(ofBorg 自动判 Mass-rebuild);backport 是把 master 的修复 cherry-pick 回 release-XX.YY 分支——只有修复能 backport,新功能/新包/破坏性变更都不进稳定版。
8. 更新包与版本维护
8.1 一次标准的版本更新
# 1) 改 version,把 hash 换成假值
# 2) 构建,从报错里拿真实 hash
nix-build -A mytool 2>&1 | grep 'got:'
# 3) 填回 hash,再次构建
# 4) 运行 nixpkgs-review 确认无破坏
# 5) commit: "mytool: 1.2.3 -> 1.2.4"
8.2 自动化工具与常见维护任务
nix-update 可自动查上游最新版并更新 hash(nix run nixpkgs#nix-update -- --flake mytool),配合 r-ryantm 机器人可在上游发布后自动开 PR。
| 任务 | 工具 |
|---|---|
| 升级版本 | nix-update |
| 更新 hash | 假哈希触发或 nix-prefetch-url |
| 修 aarch64/darwin 构建 | 本地或远程 builder 验证 |
| 跟进上游 API 变化 | 手改 + 测试 |
记忆:版本更新的标准动作是「改 version → 假哈希触发取真值 → 填回 → nixpkgs-review → commit
pkg: old -> new」;nix-update与 r-ryantm 机器人可自动化这一流程。
9. 常见拒绝原因与最佳实践
9.1 高频拒绝原因
| 原因 | 说明 |
|---|---|
| 提交信息不规范 | 没用 <pkg>: <动词> 格式 |
| 一次 PR 做太多事 | init + 升级 + 重构混在一起 |
| 没跑 nixpkgs-review | 无法证明没破坏别的包 |
用 rec 而非 finalAttrs | 新包应遵循现代写法 |
| 没登记 maintainers | 无人接手维护 |
| 用了不该用的 fetch 方式 | 应优先 fetchFromGitHub 等 |
9.2 最佳实践清单
# ☐ 新包放 pkgs/by-name/xx/name/package.nix
# ☐ 用 finalAttrs 模式,hash 用 SRI 格式(sha256-...)
# ☐ meta 里写全 description/homepage/license/maintainers/mainProgram
# ☐ 一次 PR 一件事,commit message 严格规范
# ☐ 本地跑 nixpkgs-review,附上结果
# ☐ 大改动先问 maintainers 或走 staging
9.3 参与方式
进阶路径是:从「给现有包加 maintainers」「修小 bug」入门,再到「升级版本」「新增包」,最后是「staging 大规模改动」「模块与测试」。
记忆:nixpkgs 拒绝的常见原因是「提交信息不规范、一次做太多事、没跑 nixpkgs-review、没用 finalAttrs、没登记 maintainers」;最佳实践是 by-name + finalAttrs + SRI hash + 完整 meta + 一次一事 + 附 review 结果,从修小 bug 逐步进阶。
10. 速查表与一句话记忆
| 需求 | 命令 / 约定 | 一句话 |
|---|---|---|
| 新包位置 | pkgs/by-name/xx/name/package.nix | 免改 all-packages |
| 写法 | finalAttrs 模式 | 别用 rec |
| 取哈希 | 假哈希触发 / nix-prefetch-url | 拿到 got 值 |
| 提交 | pkg: old -> new | 格式严格 |
| 自测 | nixpkgs-review pr <n> | 证明没破坏 |
| 大改动 | 走 staging | ofBorg 判 Mass-rebuild |
| 修复回稳定版 | backport / cherry-pick | 只 backport 修复 |
| 自动升级 | nix-update | 配合 r-ryantm |
一句话记忆:nixpkgs 贡献的核心是标准化流程——新包放 pkgs/by-name/<前两字符>/<名>/package.nix(自动推导属性名,免改 all-packages.nix),用 finalAttrs 模式与 SRI 哈希写 package.nix;提交严格遵循 <包名>: <动词> <描述>(init at X / X -> Y / fix),一次 PR 只做一件事并附 nixpkgs-review 结果;ofBorg 自动跑 CI 并在触发大量重建时打 Mass-rebuild 标签(这类改动走 staging 分支);backport 是把 master 的修复 cherry-pick 回 release-XX.YY,只有修复能进稳定版;日常维护用 nix-update 与 r-ryantm 机器人升级版本;meta 里写全 description/homepage/license/maintainers/mainProgram 是「可维护」的门槛——从修小 bug 到 staging 大改,是一条清晰的进阶路径。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。