首页
/ Go 版本发布说明(Release Notes)工作流:从 doc/next 片段到发布文档的完整指南

Go 版本发布说明(Release Notes)工作流:从 doc/next 片段到发布文档的完整指南

2026-09-05 21:17:54作者:庞队千Virginia

本文基于 Go 官方仓库中的 doc/README.md 展开,讲解 Go 项目如何组织、撰写和生成每个版本的发布说明:doc/initialdoc/next 两级目录的分工、开发者如何为变更提交(CL)补充发布说明片段、api/next API 变更文件与说明片段的强制对应关系、发布说明的 Markdown 书写规范,以及发布团队如何用 relnote 工具完成合并与生成。读完本文,你能够独立完成一次标准的发布说明编写流程,并理解 Go 发布文档从“碎片化片段”到“单一发布文档”的自动化管线。

doc 目录的发布说明体系:initial 与 next 的分工

doc/README.md 开篇指出,doc 目录下的 initialnext 两个子目录专门用于存放发布说明(release notes):

  • doc/initial:保存的是模板骨架。例如 doc/initial/1-intro.md 的标题写作 “DRAFT RELEASE NOTES — Introduction to Go 1.N”,并带有占位符 “{Month} {Year}”,表示这是一个尚未填充版本信息的初始模板;
  • doc/next:保存的是当前开发周期正在累积的发布说明。当前仓库中 doc/next/1-intro.md 的标题为 “DRAFT RELEASE NOTES — Introduction to Go 1.28”,并声明 “Go 1.28 is not yet released … expected to be released in February 2027”,说明本仓库正处于 Go 1.28 的开发周期。

next 目录的顶层文件采用 数字-主题.md 的命名方式,当前包括:

文件 对应章节
1-intro.md 版本简介
2-language.md 语言变更
3-tools.md 工具变更
4-runtime.md 运行时变更
5-toolchain.md 工具链变更
6-stdlib/ 标准库变更(目录,见下文)
7-ports.md 平台移植

这种命名并非随意:doc/README.md 说明,开发周期结束时,这些文件会**按文件路径名的排序顺序拼接(concatenated in sorted order by pathname)**合并为最终文档。数字前缀正是为了保证拼接后章节顺序稳定、可预期——这是理解整个发布说明体系的第一个关键机制。

开发者规则:发布说明必须写入 next,而不是注释

doc/README.md 对开发者的第一条硬性规定是:

Do not add RELNOTE=yes comments in CLs. Instead, add a file to the CL (or ask the author to do so).

也就是说,Go 项目放弃了早期“在代码注释里标记 RELNOTE=yes、由机器人代为补写说明”的做法,改为由提交者在 CL 中直接附带(或要求作者附带)一个说明文件。这使发布说明与代码变更在同一个变更中原子地落地,避免了说明滞后或缺失。

stdlib 变更的特殊目录:*stdlib/*minor

并非所有说明文件都放在顶层。doc/README.md 规定:匹配 *stdlib/*minor 这个 glob 的目录中的文件会被特殊处理——

  1. 文件必须放在与标准库包路径对应的子目录中;
  2. 这些包路径的标题(headings)会自动生成,撰写者无需手写。

当前 doc/next/6-stdlib/99-minor/ 目录是这一规则的活样本,其结构完整镜像了标准库的包树:

doc/next/6-stdlib/99-minor/
├── 0-heading.md                  # 自动生成的标题锚点
├── README                        # 目录用途说明
├── embed/80822.md
├── encoding/base32/20235.md
├── encoding/base64/20235.md
├── flag/65675.md
├── go/parser/79802.md
├── net/29678.md
├── net/http/79040.md
├── net/http/79656.md
├── net/http/80058.md
├── net/http/url/79946.md
├── syscall/68595.md
└── testing/fstest/80822.md
    testing/synctest/77320.md

其中 doc/next/6-stdlib/99-minor/README 一句话点明该目录的定位:“API changes and other small changes to the standard library go here.”(标准库的 API 变更和其他小型变更写在这里)。

标题自动生成的实现细节体现在两个 0-heading.md 文件中:doc/next/6-stdlib/0-heading.md 提供章节级标题 ## Standard library {#library}doc/next/6-stdlib/99-minor/0-heading.md 提供小节标题 ### Minor changes to the library {#minor_library_changes},末尾的 {#anchor} 是供自动生成的包路径标题做锚点链接用的。

文件名 = 提案/问题编号

观察上面的文件名(80822202356567579040……)可以发现,文件名就是 API 提案或 issue 的编号,这直接服务于下一条规则。

api/next 与 doc/next 的强制对应关系

doc/README.md 给出了本项目发布说明体系中最严格的一条规则:

Files in this repo's api/next directory must have corresponding files in doc/next/*stdlib/*minor. The files should be in the subdirectory for the package with the new API, and should be named after the issue number of the API proposal.

即:api/next 中的每个 API 变更文件,都必须在 doc/next 的 stdlib minor 目录下有对应的说明文件;对应文件放在新增 API 所在包的子目录下,并以提案 issue 编号命名。README 中给的例子是:若存在 6-stdlib/99-minor 目录,则 api/next 中的

pkg net/http, function F #12345

必须对应一个 doc/next/6-stdlib/99-minor/net/http/12345.md。该文件至少要包含一个完整句子或一个 TODO,理想情况下注明负责补全说明的人。

当前仓库里就有完全符合这一规则的成对实例。api/next/65675.txt 的内容为:

pkg flag, func All() iter.Seq[*Flag] #65675
pkg flag, method (*FlagSet) All() iter.Seq[*Flag] #65675
pkg flag, type Flag struct, IsSet bool #65675

它对应 doc/next/6-stdlib/99-minor/flag/65675.md

The new [FlagSet.All] method returns an iterator over all the flags in the FlagSet;
the function [All] does the same for the global [CommandLine] flag set.
The new [Flag.IsSet] field indicates whether the flag has been set.

可以看到三行 pkg flag … API 描述与一个 flag/65675.md 说明片段一一对应,而说明片段使用的正是下面一节介绍的符号链接写法([FlagSet.All][All][Flag.IsSet])。

这条规则有自动化测试兜底

对应关系并非仅靠约定,仓库内有专门的检查代码。src/cmd/relnote/relnote_test.go 中的 TestCheckAPIFragmentssrc/cmd/relnote/relnote_test.go#L21-L39)会遍历 api/next/*.txt 中所有文件,调用 relnote.CheckAPIFile(rootFS, apiFile, docFS, "doc/next") 逐一验证每个 API 文件在 doc/next 下存在对应说明片段:

// Check that each file in api/next has corresponding release note files in doc/next.
func TestCheckAPIFragments(t *testing.T) {
	...
	files, err := fs.Glob(rootFS, "api/next/*.txt")
	...
	for _, apiFile := range files {
		if err := relnote.CheckAPIFile(rootFS, apiFile, docFS, "doc/next"); err != nil {
			t.Errorf("%s: %v", apiFile, err)
		}
	}
}

从源码结构看,该检查以 -check 标志启用(见 src/cmd/relnote/relnote_test.go#L18),依赖外部包 golang.org/x/build/relnote 提供的 CheckAPIFile 函数完成实际校验。这意味着开发者如果提交了 api/next 文件却忘记附 doc/next 片段,检查会直接报错——README 中的对应关系要求是机器可执行的。

关联提案:/issue/NUMBER 写法与自动 TODO 标记

doc/README.md 还规定:如果你的 CL 实现的是一个已被接受的提案(accepted proposal),必须在发布说明中以 /issue/NUMBER 的形式提及提案的 issue 编号,渲染后会在文本中生成指向该 issue 的链接。如果不想在正文中出现该编号,可以改为 HTML 注释形式:

<!-- go.dev/issue/12345 -->

同时,doc/README.md 提醒:如果某个已被接受的提案被 CL 提及却没有出现在发布说明中,自动化工具会将其标记为 TODO——即使该提案只是新增 API 也不例外。这与上一节的 relnote 工具链(见“发布团队流程”)相呼应:未完成的说明工作会在周期收尾前被系统性揪出来。

发布说明的 Markdown 书写规范

doc/README.md 给出了一套在发布说明 Markdown 中可用的链接/引用形式:

[http.Request]                     # symbol documentation; auto-linked as in Go doc strings
[Request]                          # short form, for symbols in the package being documented
[net/http]                         # package link
#12345             # GitHub issues
CL 6789                # Gerrit changelists

逐条解读:

形式 含义
[http.Request] 符号文档链接,自动生成,行为与 Go doc 注释中的自动链接一致
[Request] 短形式,用于指代“正在被说明的那个包”内部的符号
[net/http] 包链接
#12345 issue 链接
CL 6789 Gerrit 变更列表(changelist)链接

实际片段中这套写法随处可见。例如 doc/next/6-stdlib/99-minor/net/http/79040.md 写道:

The long-deprecated [Transport.CancelRequest] now does nothing. Use [NewRequestWithContext] (available since Go 1.13) to create a cancellable request.

[Transport.CancelRequest] 与 [NewRequestWithContext] 都会按 Go doc 字符串的规则自动链接到对应的符号文档;而 doc/next/6-stdlib/99-minor/testing/synctest/77320.md 则用 [testing.(*T).Run] 这种限定形式引用了其他包中的方法。注意:这些链接在 next 阶段只是纯文本标记,由合并/生成阶段解析为真实文档链接。

本地预览:在本地站点查看合并后的 next 内容

doc/README.md 提供了在本地预览“合并后”效果的命令。在仓库根目录下运行:

go run golang.org/x/website/cmd/golangorg@latest -goroot=..

然后访问 http://localhost:6060/doc/next,编辑文件后刷新页面即可看到最新效果。这里 -goroot=.. 指向 Go 源码仓库根目录,本地站点工具会直接读取 doc/next 下全部片段,按前文所述的排序规则拼接并渲染出完整的草稿文档——相当于在不触发正式发布流程的情况下,提前看到 relnote generate 的产物。

发布团队流程:relnote 工具的三步工作流

doc/README.md 的“For the release team”一节描述了周期收尾阶段的操作,核心工具是 relnote(位于外部仓库 golang.org/x/build/cmd/relnote),它直接操作 doc/next 中的文件:

  1. 收尾前检查:运行 relnote todo,列出所有未完成的发布说明工作(即上文提到的被自动标记的 TODO,包括缺失的说明片段、未提及的已接受提案等);
  2. 生成发布文档:运行 relnote generate,将 next 中所有 .md 文件合并为单一文件;随后(尽量)原子地完成两件事——把生成的文件加入 website 仓库的 _content/doc 目录,同时删除本仓库的 doc/next 目录;
  3. 开启下一个周期:用 initial 的内容填充新的 next。在仓库根目录下:
> cd doc
> cp -R initial/ next

然后编辑 next/1-intro.md,把其中的 “Go 1.N / {Month} {Year}” 占位符改为下一版本号与发布日期。

这正好解释了 initial 目录的存在意义:它是每个周期 next初始快照。以当前周期为例,initial/1-intro.md 中的 “Go 1.N … {Month} {Year}” 占位符,在 Go 1.28 周期中被替换成了具体的 “Go 1.28 … February 2027”(见 doc/next/1-intro.md),印证了第 3 步的实际执行结果。

小结:一次完整的发布说明生命周期

doc/README.md 的规则与仓库现状结合,一次发布说明的完整生命周期是:

  1. 周期开始cp -R initial/ next,更新 next/1-intro.md 的版本号与日期;
  2. 日常开发:每个 CL 按归属写入 next 对应位置——语言/工具/运行时变更进顶层编号文件,标准库小型变更与 API 变更进 6-stdlib/99-minor/<包路径>/<issue号>.md;若 CL 触碰了 api/next(如 api/next/77320.txtpkg testing/synctest, func Subtest(*testing.T, string, func(*testing.T)) #77320),必须有同名编号的说明片段(如 doc/next/6-stdlib/99-minor/testing/synctest/77320.md),且 TestCheckAPIFragments 会持续保证这一约束;
  3. 书写规范:使用符号自动链接、/issue/NUMBER、issue 与 CL 链接等固定形式,让说明在生成阶段能正确链接到文档与提案;
  4. 周期收尾relnote todo 清理欠账 → relnote generate 合并 → 产物进入 website 仓库,doc/next 归档移除;
  5. 本地验证:随时可用本地网站实例在 localhost:6060/doc/next 预览合并效果。

整个体系的设计要点在于:以文件路径为唯一事实来源——排序决定章节顺序,目录镜像决定包标题,文件名决定 API 关联,配合自动化检查(CheckAPIFile)与生成工具(relnote),把“发布说明”从人为维护的文档变成了可校验、可合并、可重复执行的工程化流程。

登录后查看全文
热门项目推荐
相关项目推荐