pip 的 towncrier 变更日志模板解析:从 news fragment 到 NEWS.rst 的渲染机制
2026/9/24 17:01:49 网站建设 项目流程
  • 包管理器
  • 开发工具

【免费下载链接】pip

The Python package installer

项目地址:https://gitcode.com/gh_mirrors/pi/pip
点击查看免费下载

本篇技术指南围绕 pip 仓库中维护变更日志的核心模板文件 tools/news/template.rst 展开,系统讲解 pip 如何基于 towncrier 将散落在news/目录的 news fragment 自动聚合、渲染为结构化的 NEWS.rst 变更日志,并同步到 docs/html/news.rst 在线文档。读完本文,你将掌握 pip 的 news 条目命名规范、[tool.towncrier]配置含义、该自定义 Jinja 模板每一段逻辑的作用,以及发布流程中towncrier build的执行位置,从而能独立为 pip 贡献 news 条目、理解其变更日志生成链路。

一、背景:为什么 pip 需要定制 towncrier 模板

towncrier 是 Twisted 社区开发的经典变更日志聚合工具:开发者将每个改动写成一个小文件(news fragment),发布时由工具按版本号收集并合并为一份完整的NEWS.rst。pip 在此基础上做了深度定制——它不只是使用默认模板,而是维护了一份"重度定制"(heavily customised)的版本 tools/news/template.rst,原因有两个:

  1. 在线文档的无缝集成:pip 的在线文档通过sphinxcontrib.towncrier扩展渲染未发布的 news 条目(见 docs/html/news.rst 第 10 行的.. towncrier-draft-entries:: Not yet released指令)。
  2. 避免空渲染:当没有待渲染的 news 条目时,该 Sphinx 扩展依然会触发模板渲染,导致出现空白或错误的输出。模板开头的{% if sections[''] %}守卫正是为此设计——它让整个渲染过程在"没有条目可展示"时直接跳过,充当兼容性 hack(模板注释明确说明:This serves as a compatibility "hack")。

从仓库的架构文档 docs/html/development/architecture/anatomy.rst 中也可以印证:news/目录用于存放 news fragment,每次 pip 产生对用户可见的变更都会在其中添加一个文件(通常是一段指向 GitHub issue 的简短说明),并在发布时通过 towncrier 自动化生成 NEWS 文件并自动删除旧条目;而tools/news/template.rst正是"towncrier 使用的 changelog 模板,是一份 Jinja 文件"。

二、配置侧:[tool.towncrier]与 news fragment 规范

2.1 pyproject.toml 中的 towncrier 配置

模板本身不孤立工作,它与 pyproject.toml 第 132–154 行的[tool.towncrier]配置段紧密耦合:

[tool.towncrier] # For finding the __version__ package = "pip" package_dir = "src" # For writing into the correct file filename = "NEWS.rst" # For finding the news fragments directory = "news/" # For rendering properly for this project issue_format = "`#{issue} <https://github.com/pypa/pip/issues/{issue}>`_" template = "tools/news/template.rst" # Grouping of entries, within our changelog type = [ { name = "Deprecations and Removals", directory = "removal", showcontent = true }, { name = "Features", directory = "feature", showcontent = true }, { name = "Bug Fixes", directory = "bugfix", showcontent = true }, { name = "Vendored Libraries", directory = "vendor", showcontent = true }, { name = "Improved Documentation", directory = "doc", showcontent = true }, { name = "Process", directory = "process", showcontent = true }, { name = "Trivial Changes", directory = "trivial", showcontent = false }, ]

逐项解读:

配置键取值(以当前仓库为准)作用
package/package_dirpip/src定位包版本号(src/pip/__init__.py中的__version__),用于生成"版本 (日期)"标题
filenameNEWS.rsttowncrier build 后写入的变更日志文件路径(仓库根目录的 NEWS.rst)
directorynews/news fragment 的存放目录
issue_format`#{issue} <https://github.com/pypa/pip/issues/{issue}>`_条目末尾 issue/PR 引用的 RST 链接格式
templatetools/news/template.rst指定自定义渲染模板,即本文主角
type7 种类型定义条目分类:removalfeaturebugfixvendordocprocesstrivial

type列表中有两个关键属性:

  • name:渲染到 NEWS 中的小节标题(如 "Bug Fixes"、"Features"),模板通过definitions[type_]['name']读取;
  • showcontent:是否展示该类型的具体条目内容。trivial类型为false,这与贡献指南中"trivial 变更不值得出现在 news 文件中"的定位一致——只有显示showcontent = true的类型会渲染条目正文。

模板第 27 行{% for type_ in definitions if (sections[section_name][type_] and definitions[type_]['showcontent']) %}正是读取definitions(即 pyproject.toml 的type列表)来过滤"有条目且允许展示内容"的类型。

2.2 news fragment 的命名与内容规范

贡献者在提交"非 trivial"改动时必须附带 news 条目,规范详见 docs/html/development/contributing.rst 的 "NEWS Entries" 章节:

  • 命名规则:以 issue/PR 编号 + 类型后缀命名,如修复 bug 且编号为 1234,则创建news/1234.bugfix.rst;一个 PR 可跨多个类别创建多个文件(如同时有news/NNNN.feature.rstnews/NNNN.removal.rst)。
  • 去重机制:若一个 PR 涉及多个 issue,可为每个编号创建内容完全相同的文件,towncrier 渲染时会自动去重(模板第 33 行对issue_reference做了|sortjoin(', '),多个引用会合并到一条目上)。
  • 内容风格:条目内容是 reStructuredText 文本,不需要在正文中自行引用 issue/PR 编号——towncrier 会自动附加引用。官方要求条目保持句子大小写(sentence case)、少于 80 字符、使用祈使语气(应能补全句子 "This change will ...");内容面向最终用户,只保留与用户相关的细节;文件末尾必须有换行符。
  • trivial 标记:不需要进入 news 的改动(纯重构、拼写修正、空白调整等)可在news/目录添加一个随机命名、内容为空的.trivial.rst文件。POSIX 下可用touch news/$(uuidgen).trivial.rst,Windows 下用New-Item "news/$([guid]::NewGuid()).trivial.rst"。注意trivialshowcontent = false,因此这些空文件不会出现在最终 NEWS 中。

仓库现有 fragment 恰好展示了这几种类型,例如 news/13084.bugfix.rst(zipapp 场景下自检版本报告不正确的 bug 修复)、news/14235.feature.rst、news/14160.trivial.rst(自我引用 extras 回归测试扩展)、以及*.vendor.rst系列(certifi、distlib、msgpack 等依赖升级)。

三、模板逐段解剖:渲染逻辑全解析

完整模板仅 44 行(tools/news/template.rst),自上而下可拆为五个逻辑块。下面结合 Jinja 语法与 towncrier 数据模型逐段说明。

3.1 空渲染守卫:{% if sections[''] %}(第 10 行)

{% if sections[''] %} ... {% endif -%}

这是模板最关键的自定义点。towncrier 渲染时提供sections变量——一个以 section 名为键的字典,其中空字符串''对应默认 section(pip 未使用 towncrier 的命名 sections 功能,因此所有条目都在''之下)。当没有任何条目时sections['']为空,整个渲染被跳过。这正是为sphinxcontrib.towncrier在无条目时也会触发渲染的问题而设的兼容性 hack(模板第 3–9 行注释说明了这一点)。

3.2 版本标题行(第 12–14 行)

{{ versiondata.version }} ({{ versiondata.date }}) {{ top_underline * ((versiondata.version + versiondata.date)|length + 3) }}

versiondata.versionversiondata.date分别来自towncrier build --version传入的版本号与构建日期;第二行用top_underline(默认字符=)重复版本号 + 日期总长度加 3 次,生成 RST 节标题下划线。对照 NEWS.rst 的实际输出:

26.2.1 (2026-08-04) ===================

版本号长度 7、日期长度 10,下划线长度恰好为(7 + 10) + 3 = 20,与模板算式一致。

3.3 section 遍历(第 25 行)

{% for section_name, entries_by_type in sections.items() -%}

模板注释明确说明:由于 towncrier 未公开的 "sections" 特性(参见 twisted/towncrier#61),该循环对section_name == ""恰好执行一次。pip 不使用命名 sections,因此模板不会渲染 section 标题(如 "Features" 这类标题是类型标题,不是 section 标题)。

3.4 类型小节标题与条目渲染(第 27–41 行)

{% for type_ in definitions if (sections[section_name][type_] and definitions[type_]['showcontent']) %} {{ definitions[type_]['name'] }} {{ underlines[0] * definitions[type_]['name']|length }} {% for message, issue_reference in sections[section_name][type_]|dictsort(by='value') %} - {{ message }} {%- if type_ not in ["vendor", "process"] %} ({{ issue_reference|sort|join(', ') }}){% endif %} {% endfor %} {% else %} No significant changes. {% endfor -%}
  • 类型过滤definitions即 pyproject.toml 的type列表。只有"当前 section 下存在条目"且"showcontent为 true"的类型才会渲染,且顺序遵循 pyproject.toml 中的声明顺序(removal → feature → bugfix → vendor → doc → process,trivial 因showcontent=false永远被跳过)。
  • 标题与下划线:类型名(如 "Bug Fixes")作小节标题,underlines[0](默认-)按标题长度生成下划线。
  • 条目内容sections[section_name][type_]消息 → issue 引用列表的映射,经dictsort(by='value')按值排序,保证输出稳定;message即 fragment 文件内容。
  • 引用附加规则:默认所有类型都会在条目后追加(issue#1, issue#2)形式的引用(来源为issue_format配置),但模板对vendorprocess两种类型跳过引用(第 36 行{%- if type_ not in ["vendor", "process"] %})。这与实际输出吻合:在 NEWS.rst 中,"Vendored Libraries" 小节(如 "Upgrade certifi to 2026.6.17")与 "Process" 小节(如 "Include a CycloneDX SBOM ...")的条目不带(#xxx)引用,而 Bug Fixes / Features 的条目几乎都带。
  • 空类型兜底:若某版本没有任何showcontent=true的条目,模板输出 "No significant changes."(第 41 行)。例如 NEWS.rst 中24.1 (2024-06-20)只包含 "Vendored Libraries" 小节,而像23.3.2这样只有 Bug Fixes 的版本则正常渲染对应小节。

3.5 Jinja 语法细节

模板使用{%-/-%}形式的空白控制符精确裁剪换行与缩进,保证渲染出的 RST 文件中条目以-列表项格式连续排列、不引入多余空行——这在 NEWS.rst 的输出中可以看到效果:每个条目都是紧凑的单行- xxx (#14227)格式。

四、实际应用:渲染链路与发布流程

4.1 本地验证与文档集成

towncrier 的官方用法是在发布时执行towncrier build --version X.Y.Z --yes。pip 将渲染结果写入 NEWS.rst,其文件头有一段明确的提示(见 NEWS.rst 第 1–10 行):

.. note You should *NOT* be adding new change log entries to this file, this file is managed by towncrier. You *may* edit previous change logs to fix problems like typo corrections or such.

即开发者不应手工修改 NEWS.rst 的主体内容,该文件由 towncrier 全权管理。与此同时,在线文档 docs/html/news.rst 通过.. towncrier-draft-entries:: Not yet released渲染未发布的草稿条目,再通过.. pip-news-include:: ../../NEWS.rst引入已发布的完整历史。

文档构建侧(docs/html/conf.py)的配置与之配套:

"towncrier_draft_autoversion_mode": "draft" "towncrier_draft_include_empty": True "towncrier_draft_working_directory": pathlib.Path(docs_dir).parent
  • autoversion_mode = "draft":文档渲染时自动把未发布条目归入 "Not yet released" 草稿版本;
  • include_empty = True:即使没有草稿条目也渲染(这正是模板空渲染守卫存在的原因——两者配合避免输出异常);
  • working_directory = docs_dir.parent:指向仓库根目录,使sphinxcontrib.towncrier能正确找到tools/news/template.rstnews/目录。

4.2 发布环节的调用位置

在 pip 的发布自动化脚本 tools/release/init.py 的generate_news函数中可以看到实际调用:

def generate_news(session: Session, version: str) -> None: session.install("towncrier") session.run("towncrier", "build", "--yes", "--version", version, silent=True)

发布时通过 nox 会话(nox -s release传入版本参数,版本合法性由 tools/release/check_version.py 校验)安装 towncrier 并执行towncrier build --yes --version <版本号>--yes表示自动删除已被合并进 NEWS 的 fragment 文件——这正是 docs/html/development/architecture/anatomy.rst 所述"每次发布维护者会删除 news/ 中的旧文件"的实现机制。

4.3 依赖版本约束

仓库在 pyproject.toml 第 96–103 行对 towncrier 相关依赖做了版本锁定:

# currently incompatible with sphinxcontrib-towncrier # https://github.com/sphinx-contrib/sphinxcontrib-towncrier/issues/92 "towncrier < 24", "sphinxcontrib-towncrier >= 0.2.0a0",

注释明确:由于sphinxcontrib-towncrier与新版不兼容,towncrier必须被限制在< 24。这解释了为何文档构建与发布自动化要配套固定版本——模板渲染行为会随 towncrier 主版本变化,版本锁定保证tools/news/template.rst所用数据模型(sectionsdefinitionsversiondatatop_underlineunderlines)的稳定性。

五、模板与 NEWS.rst 输出的对照验证

以下用当前仓库真实数据验证模板每个渲染点:

1. 标题行公式(对应 3.2 节):

26.2.1 (2026-08-04) ===================

2. 类型小节与引用规则(对应 3.4 节),取自 NEWS.rst 的 26.2 版本:

Bug Fixes --------- - Only emit the invalid-metadata warning once per location per run, instead of repeating it during the same command. (`#11436 <https://github.com/pypa/pip/issues/11436>`_) - Handle ``BrokenPipeError`` when pip output is piped to a command that closes early. (`#11608 <https://github.com/pypa/pip/issues/11608>`_)

"Bug Fixes" 小节标题以-下划线(underlines[0]),每条带(#11436)(#11608)引用(由issue_format+issue_reference|sort|join(', ')生成)。

3. vendor 类型无引用(对应 3.4 节跳过规则):

Vendored Libraries ------------------ - Upgrade certifi to 2026.6.17 - Upgrade distlib to 0.4.2 - Upgrade idna to 3.18

与 Bug Fixes 不同,这里没有(#xxx)后缀,因为type_vendor

4. Process 类型同样无引用

Process ------- - Include a CycloneDX SBOM (Software Bill of Materials) file alongside vendored libraries.

5. 空输出守卫(对应 3.1 节):当前news/目录中除*.vendor.rst*.trivial.rst外还有若干未发布条目;当某一版本只有trivial条目(showcontent=false)而没有其他类型时,模板会走{% else %}分支输出 "No significant changes.",且若sections['']为空则整个文件不渲染任何内容。

六、给贡献者与维护者的实用要点

  1. 新增条目三步:在 GitHub 创建 issue/PR 获取编号 → 在 news/ 目录创建news/<编号>.<类型>.rst→ 写入不超过 80 字符、祈使语气、句子大小写的 RST 文本(末尾留空行)。类型可选removalfeaturebugfixvendordocprocesstrivial,与 pyproject.toml 的type定义一一对应。
  2. 不要手改 NEWS.rst:文件头注释明确要求不得新增条目,只能修正历史记录的拼写错误等。
  3. 调试渲染:本地可用towncrier build --version X.Y.Z --draft类草稿模式查看渲染效果(发布脚本实际使用--yes直接消费并删除 fragment);文档预览则依赖sphinxcontrib.towncrier的草稿渲染,配合towncrier < 24的版本约束使用。
  4. 理解模板的两处"定制"核心:一是{% if sections[''] %}空守卫(为 Sphinx 集成服务),二是vendor/process不追加 issue 引用的规则——这两点是 pip 的 NEWS.rst 与 towncrier 默认输出最大的不同。

相关文件速查

  • tools/news/template.rst:本文核心——pip 自定义的 towncrier 渲染模板
  • pyproject.toml 第 132–154 行:[tool.towncrier]配置(类型定义、文件名、issue 引用格式、模板路径)
  • NEWS.rst:towncrier 生成的历史变更日志
  • docs/html/news.rst:在线文档接入点(草稿渲染 + 历史引入)
  • docs/html/conf.py 第 83–88 行:towncrier_draft_*文档构建选项
  • docs/html/development/contributing.rst 第 75–129 行:NEWS 条目编写规范与类型选择指南
  • tools/release/init.py 第 119–121 行:发布流程中的towncrier build调用
  • news/:news fragment 实际存放目录(含*.bugfix.rst*.feature.rst*.trivial.rst*.vendor.rst等示例)
  • 包管理器
  • 开发工具

【免费下载链接】pip

The Python package installer

项目地址:https://gitcode.com/gh_mirrors/pi/pip
点击查看免费下载

相关推荐

上一篇:抖音无水印下载没你想的那么难:开源工具 douyin-downloader 从零上手指南
下一篇:绕过TPM升级Windows 11:一个免费脚本,让老电脑顺利上车

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询