php-src 官方内部文档(docs)完全指南:Sphinx 文档站构建、rstfmt 格式化与 CI 发布流水线
php-src 仓库中的 docs/ 目录是 PHP 官方团队正在建设的内部文档(internals documentation)主站点,用于系统性地讲解解释器的工作原理、数据结构与扩展开发。本文以 docs/README.md 为核心骨架,逐行拆解文档站的构建命令、依赖清单与 Makefile 目标设计,结合 docs/Makefile、docs/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))
可以拆解出几个有工程巧思的设计点:
-
rwildcard递归收集全部 RST 文件。宏定义rwildcard = $(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) ...)递归遍历source/目录树,把所有*.rst文件聚合到FILES。这让 Make 能够精确追踪哪些源文件发生了变化。 -
preflight是“构建前自动格式化”哨兵目标。$(SOURCEDIR)/.~是一个隐藏的时间戳文件,它依赖于全部 RST 文件;只要有任意 RST 文件比它新(即被修改过),$(RSTFMT) $(RSTFMTFLAGS) $?就会用 仅传入被修改的文件($?展开为更新的先决目标)执行rstfmt -w 100原地格式化,然后touch刷新时间戳。由于html : preflight,每次执行make html都会先把改动的 RST 文件自动排好版再构建——这解释了 README 中“构建即可”的极简体验背后其实藏着一步隐式格式化。 -
html目标的 OSC 8 终端超链接。构建成功后,printf通过终端转义序列\e]8;;...\e\\在支持的终端里输出一条可点击的“php-src html docs locally”超链接,直接指向本地build/html/index.html的file://绝对路径。 -
check-formatting目标只做校验不改动:rstfmt -w 100 --check source检查所有 RST 是否已符合 100 列宽排版,这是 CI 流水线(见第五节)中“Check formatting”步骤的实际执行命令。 -
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):注册php与php-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),并逐一标注了每个阶段在仓库中的真实落点——这也是把文档与源码对照阅读的最佳起点:
- 分词:
re2c生成的扫描器,定义在 Zend/zend_language_scanner.l; - 解析:Bison 文法,定义在 Zend/zend_language_parser.y,AST 结构见 Zend/zend_ast.h;
- 编译(AST → opcode):Zend/zend_compile.c;
- 解释执行:opcode 全集见 Zend/zend_vm_opcodes.h,各指令行为定义在 Zend/zend_vm_def.h;
- 缓存与优化:opcache 扩展位于 ext/opcache/,字节码优化器位于 Zend/Optimizer/。
该章节还解释了 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
从该工作流可以确认以下机制:
- 路径过滤:只有
docs/**下的变更才会触发,push 事件限定master分支,pull_request 事件不限分支——即 PR 阶段也会跑流程,但仅做格式校验。 - 格式校验前置:
make -C docs check-formatting在构建之前执行,等价于本地rstfmt -w 100 --check source。任何不符合 100 列排版规范的 RST 提交都会让该步骤失败,从而与 README 的格式化规范形成强制约束。 - 仅 push 到 master 才发布:
Publish步骤带if: github.event_name == 'push'条件,通过sphinx-notes/pages@v3Action 以docs/source为源目录构建并部署到 GitHub Pages(即 README 所述的 php.github.io/php-src 站点);permissions中的pages: write与id-token: write正是该 Action 所需的 OIDC 权限。 - 仓库限定:
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/Makefile、docs/source/conf.py 与 docs.yml 的源码证据,理解自动格式化哨兵、PHP 高亮内联模式、连字禁用样式与“校验后发布”的 CI 机制是如何协同工作的。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00