首页
/ php-src 官方内部文档(docs)完全指南:Sphinx 文档站构建、rstfmt 格式化与 CI 发布流水线

php-src 官方内部文档(docs)完全指南:Sphinx 文档站构建、rstfmt 格式化与 CI 发布流水线

2026-09-05 09:13:20作者:邓越浪Henry

php-src 仓库中的 docs/ 目录是 PHP 官方团队正在建设的内部文档(internals documentation)主站点,用于系统性地讲解解释器的工作原理、数据结构与扩展开发。本文以 docs/README.md 为核心骨架,逐行拆解文档站的构建命令、依赖清单与 Makefile 目标设计,结合 docs/Makefiledocs/source/conf.py 和 CI 工作流 docs.yml 的源码证据,带你从零完成“本地构建 HTML 文档站、规范化 RST 格式、理解文档结构与自动发布机制”的完整闭环。

一、文档站的定位:取代分散资料的事实标准

docs/README.md 开篇明确了这套文档的定位:它是 php-src 内部文档的家,托管在官方 GitHub Pages 站点 php.github.io/php-src/。README 说明,该项目目前还处于“非常早期的阶段”(in very early stages),但其意图是逐步成为记录 php-src 相关新信息的主要场所,并在时间推移中取代以下分散的媒介:

  • PHP Internals Book(社区编写的内部机制书籍);
  • PHP Wiki 的 internals 板块;
  • 各贡献者散落在博客中的零散文章。

这一背景在 docs/source/index.rst 的正文中得到呼应:首页明确写道,php-src 是 PHP 解释器的规范实现,这份文档旨在帮助读者理解解释器如何工作、如何构建和测试改动、以及如何编写扩展;同时坦承文档“并不打算面面俱到”,而是聚焦于那些仅靠读代码难以掌握的核心概念,描述最佳实践,并会主动省略不推荐公开使用的 API。

此外,docs/ 目录下还有两篇与文档构建无关、但对项目协作重要的流程文档:docs/mailinglist-rules.md(邮件列表规则)与 docs/release-process.md(发布流程),它们随仓库一起分发,不属于 Sphinx 站点的内容树。

二、构建文档站:从 README 命令到 Makefile 目标逐行解析

2.1 README 给出的最小可运行步骤

README 的“如何构建”一节给出了完整且可复制的操作序列,前置要求是 Python 3 和 pip

cd docs
# 推荐:创建并激活一个 Python 虚拟环境
pip install --upgrade pip
pip install -r requirements.txt
make html

执行完毕后,打开 ./build/html/index.html 即可在浏览器中查看生成的文档站。

2.2 依赖清单:requirements.txt 中每个包的作用

docs/requirements.txt 只有四行,但每一项都对应构建链上的一个环节:

Sphinx
sphinx-design
sphinxawesome-theme
rstfmt
  • Sphinx:文档构建引擎本体,负责把 source/ 下的 RST 源文件编译为 HTML 站点;
  • sphinx-design:Sphinx 扩展,提供卡片、网格、提示框等排版组件,在 docs/source/conf.py 中被列入 extensions
  • sphinxawesome-theme:HTML 主题包,conf.py 中通过 html_theme = 'sphinxawesome_theme' 启用;
  • rstfmt:RST 格式化工具,既是构建链的一环(自动格式化),也是 CI 中格式校验的依据(见第四节)。

2.3 Makefile:不只是 sphinx-build 的封装

真正驱动 make html 的是 docs/Makefile,它的价值远超“调用一下 sphinx-build”。关键变量与目标如下:

SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
RSTFMT = rstfmt
RSTFMTFLAGS = -w 100

FILES = $(call rwildcard,$(SOURCEDIR),*.rst)

all : html

html : preflight
	$(SPHINXBUILD) -M $@ $(SOURCEDIR) $(BUILDDIR)
	@printf 'Browse the \e]8;;%s\e\\%s\e]8;;\e\\.\n' \
		"file://$(abspath $(BUILDDIR))/$@/index.$@" "php-src html docs locally"

preflight : $(SOURCEDIR)/.~

$(SOURCEDIR)/.~ : $(FILES)
	$(RSTFMT) $(RSTFMTFLAGS) $?
	touch $@

check-formatting :
	$(RSTFMT) $(RSTFMTFLAGS) --check $(SOURCEDIR)

clean :
	rm -rf -- $(wildcard $(SOURCEDIR)/.~ $(BUILDDIR))

可以拆解出几个有工程巧思的设计点:

  1. rwildcard 递归收集全部 RST 文件。宏定义 rwildcard = $(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) ...) 递归遍历 source/ 目录树,把所有 *.rst 文件聚合到 FILES。这让 Make 能够精确追踪哪些源文件发生了变化。

  2. preflight 是“构建前自动格式化”哨兵目标$(SOURCEDIR)/.~ 是一个隐藏的时间戳文件,它依赖于全部 RST 文件;只要有任意 RST 文件比它新(即被修改过),$(RSTFMT) $(RSTFMTFLAGS) $? 就会用 仅传入被修改的文件$? 展开为更新的先决目标)执行 rstfmt -w 100 原地格式化,然后 touch 刷新时间戳。由于 html : preflight每次执行 make html 都会先把改动的 RST 文件自动排好版再构建——这解释了 README 中“构建即可”的极简体验背后其实藏着一步隐式格式化。

  3. html 目标的 OSC 8 终端超链接。构建成功后,printf 通过终端转义序列 \e]8;;...\e\\ 在支持的终端里输出一条可点击的“php-src html docs locally”超链接,直接指向本地 build/html/index.htmlfile:// 绝对路径。

  4. check-formatting 目标只做校验不改动rstfmt -w 100 --check source 检查所有 RST 是否已符合 100 列宽排版,这是 CI 流水线(见第五节)中“Check formatting”步骤的实际执行命令。

  5. clean 同时清理时间戳哨兵和构建产物,保证 preflight 状态可重置。

本地环境参考:仓库开发环境已具备 Python 3 与 pip(例如 Python 3.12 + pip 26.x),可直接按上述步骤构建;pip install -r requirements.txt 建议放在虚拟环境中执行,这也是 README 注释中的推荐做法。

三、rstfmt 格式化规范:-w 100 从何而来

README 的“Formatting”一节说明:文档文件使用 rstfmt 工具格式化,手动执行的等价命令是:

rstfmt -w 100 source

这里 -w 100 表示按 100 列宽度重新折行排版 RST 段落。这个参数与 docs/Makefile 中的 RSTFMTFLAGS = -w 100 完全一致,即手动格式化、make html 的 preflight 自动格式化、make check-formatting 的校验,三者使用同一套规则,保证“本地所见即 CI 所查”。

README 同时给出了诚实的使用边界提示:rstfmt “并不完美”,遇到自定义指令(custom directives)时会中断处理(会打乱格式而非正确重排),团队因此保留未来切换到 fork 或其他工具的可能。这提醒贡献者:修改含自定义 Sphinx 指令的 RST 文件后,应人工检查格式化结果,必要时先 make check-formatting 验证。

四、站点配置 conf.py:主题、扩展与 PHP 高亮细节

docs/source/conf.py 是 Sphinx 站点的中枢配置,其中几个设置直接决定了文档站的观感与行为:

from sphinxawesome_theme import ThemeOptions
from sphinxawesome_theme.postprocess import Icons
from sphinx.highlighting import lexers
from pygments.lexers.web import PhpLexer

lexers['php'] = PhpLexer(startinline=True)
lexers['php-annotations'] = PhpLexer(startinline=True)

project = 'php-src docs'
author = 'The PHP Group'
extensions = [
    'sphinx_design',
    'sphinx.ext.autosectionlabel',
]
html_theme = 'sphinxawesome_theme'
html_static_path = ['_static']
html_css_files = ['css/code-no-font-ligatures.css']
  • PhpLexer(startinline=True):注册 phpphp-annotations 两个高亮别名并启用“内联模式”。含义是:当 RST 代码块直接以 <?php 之外的裸 PHP 语句开头时,Pygments 也能正确识别语法着色,避免文档中大量 PHP 片段因缺少 <?php 开标签而失去高亮。
  • sphinx.ext.autosectionlabel:为每个章节标题自动生成可交叉引用的标签,因此其他页面可以直接以“文档章节名”为链接目标(:doc: 指向章节),这是跨章节引用体验的核心。
  • 主题与图标sphinxawesome_theme 通过 ThemeOptions 开启 show_prev_next(页首上一页/下一页导航),并用 asdict(theme_options) 把 dataclass 展开为 html_theme_options 字典;页头额外链接区还自定义了一枚指向上游源码仓库的 SVG 图标。
  • 静态样式html_static_path = ['_static'] 引入的 code-no-font-ligatures.css 内容仅一条规则 code { font-variant-ligatures: none; }——在代码块中禁用字体连字。这一点对内部机制文档尤为重要:PHP/C 代码里的 ->=>=== 等符号在开启连字时可能被渲染成难以辨认的连字字形,禁用后保证代码逐字符可读。
  • 空目录 docs/source/_templates/(仅含 .gitkeep 占位)为后续模板覆盖预留了位置。

五、文档内容树:toctree 结构与其指向的源码

index.rst 用三棵隐藏 toctree 组织了当前全部内容:

分类(caption) 页面 对应仓库路径
Introduction High-level overview docs/source/introduction/high-level-overview.rst
Introduction IDEs(含 Visual Studio Code) docs/source/introduction/ides/
Core Data structures(zval、zend_string、引用计数等) docs/source/core/data-structures/
Miscellaneous stubs、writing-tests、running-tests docs/source/miscellaneous/

其中 high-level-overview.rst 是内容最完整的入门章节,完整描述了 php-src 的四阶段流水线(Tokenization → Parsing → Compilation → Interpretation),并逐一标注了每个阶段在仓库中的真实落点——这也是把文档与源码对照阅读的最佳起点:

该章节还解释了 opcache 的角色:把 tokenization 到 compilation 的产物(opcode)缓存在内存中跨请求复用,并在缓存前执行优化(因为 opcode 会被复用很多次,优化阶段多花时间值得),JIT 部分则位于 ext/opcache/jit。首页同时提供了求助渠道(Discord 的 #php-internals 频道、Stack Overflow 的 R11 房间)与前置知识建议:C 语言基础(绝大多数捆绑扩展用 C 编写,ext-intl 是唯一用 C++ 的)以及 PHP 语言本身的语义理解。

六、CI 发布流水线:docs.yml 的完整触发与执行链

文档站的自动托管由 docs.yml 驱动,整个文件仅 30 行,却构成一条完整的“校验—发布”链:

name: Docs
on:
  push:
    branches: [master]
    paths: [docs/**]
  pull_request:
    paths: [docs/**]
jobs:
  pages:
    runs-on: ubuntu-latest
    permissions:
      pages: write
      id-token: write
    if: github.repository == 'php/php-src'
    steps:
      - name: git checkout
        uses: actions/checkout@v6
      - name: Install dependencies
        run: pip install -r docs/requirements.txt
      - name: Check formatting
        run: make -C docs check-formatting
      - name: Publish
        if: github.event_name == 'push'
        uses: sphinx-notes/pages@v3
        with:
          checkout: false
          documentation_path: docs/source

从该工作流可以确认以下机制:

  1. 路径过滤:只有 docs/** 下的变更才会触发,push 事件限定 master 分支,pull_request 事件不限分支——即 PR 阶段也会跑流程,但仅做格式校验。
  2. 格式校验前置make -C docs check-formatting 在构建之前执行,等价于本地 rstfmt -w 100 --check source。任何不符合 100 列排版规范的 RST 提交都会让该步骤失败,从而与 README 的格式化规范形成强制约束。
  3. 仅 push 到 master 才发布Publish 步骤带 if: github.event_name == 'push' 条件,通过 sphinx-notes/pages@v3 Action 以 docs/source 为源目录构建并部署到 GitHub Pages(即 README 所述的 php.github.io/php-src 站点);permissions 中的 pages: writeid-token: write 正是该 Action 所需的 OIDC 权限。
  4. 仓库限定if: github.repository == 'php/php-src' 确保该流水线不会在 fork 仓库的 PR 上执行发布逻辑。

七、实操要点与适用限制小结

  • 完整工作流:修改/新增 docs/source/ 下任意 RST 文件 → pip install -r requirements.txt(虚拟环境推荐)→ make html(preflight 自动对改动文件执行 rstfmt -w 100)→ 打开 build/html/index.html 自查 → 提交前用 make check-formatting 确保 CI 不失败;
  • 参数口径统一:本地手动格式化、Makefile preflight、CI 校验三处统一使用 rstfmt -w 100,不存在“本地通过、CI 失败”的宽度歧义;
  • 已知限制:rstfmt 对自定义指令的排版支持不完美(README 原文明确承认),涉及自定义 directive 的页面修改后建议人工复核格式;文档内容本身仍处于早期阶段,首页 warning 框提示当前阶段其他指南仍提供更完整的图景;
  • 构建产物位置docs/build/html/(由 Makefile 的 BUILDDIR = build 决定),make clean 可同时清除构建产物与 preflight 哨兵文件 source/.~

通过以上内容,读者既能按 docs/README.md 的原始步骤完成文档站的本地构建与格式化,也能借由 docs/Makefiledocs/source/conf.pydocs.yml 的源码证据,理解自动格式化哨兵、PHP 高亮内联模式、连字禁用样式与“校验后发布”的 CI 机制是如何协同工作的。

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