Read OSS

构建系统、测试基础设施与贡献者工作流

中级

前置知识

  • 第 1 篇:架构总览(了解各组件的构成)
  • CMake 基础知识(构建目标、依赖关系、变量)
  • 熟悉命令行构建工具和测试运行器

构建系统、测试基础设施与贡献者工作流

对架构的理解,只有在你能够构建项目、运行测试并提交变更时,才能真正发挥价值。WebKit 的构建系统虽然面对的是一个体量庞大的项目,但上手体验出乎意料地友好——不过它也有一些容易让新人踩坑的特殊之处。双构建系统、统一源文件优化,以及覆盖广泛的测试套件,都需要你真正理解之后才能高效使用。

本篇是第 1 至第 5 篇架构深度解析的实践配套指南。读完之后,你将掌握如何在自己的平台上构建 WebKit、针对具体改动运行合适的测试,以及使用 git-webkit 工作流提交 Pull Request。

双构建系统:Xcode 与 CMake

WebKit 并行维护两套构建系统,各自面向不同平台:

构建系统 平台 入口文件
Xcode macOS、iOS、tvOS、watchOS、visionOS WebKit.xcworkspace
CMake GTK、WPE、PlayStation、Windows CMakeLists.txt

在 Apple 平台上,Xcode workspace(WebKit.xcworkspace)是主要的构建入口。它包含了所有组件的构建目标,并负责处理 framework 的生成、代码签名以及平台相关的编译配置。Apple 工程师和大多数 macOS 贡献者都走这条路径。

对于所有非 Apple 平台,CMake 是构建系统。CMakePresets.json 文件定义了常用的构建配置:

flowchart TD
    subgraph APPLE["Apple Platforms"]
        XC["WebKit.xcworkspace"] --> XB["xcodebuild"]
        XB --> FW["Frameworks<br/>(WebKit.framework, etc.)"]
    end
    
    subgraph OTHER["Non-Apple Platforms"]
        CMAKE["CMakeLists.txt"] --> PRESET["CMakePresets.json"]
        PRESET --> GTK_P["GTK preset"]
        PRESET --> WPE_P["WPE preset"]
        PRESET --> PS_P["PlayStation preset"]
        GTK_P --> LIBS["Shared Libraries<br/>(libwebkit2gtk.so, etc.)"]
    end
    
    BW["build-webkit script"] --> XC
    BW --> CMAKE

预设配置包含 releasedebugdev 三种基础构建类型,以及 gtk-releasewpe-debug 等平台专属预设。dev 预设会启用 CMAKE_EXPORT_COMPILE_COMMANDS(用于 IDE 集成)和 DEVELOPER_MODE(构建测试运行器和性能工具)。

平台专属的构建配置通过各组件内的 PlatformX.cmake 文件来完成。正如我们在第 1 篇中所见,PlatformMac.cmake 负责添加 macOS 专属源文件和 framework 依赖,而 PlatformGTK.cmake 则添加 GTK 专属源文件和 pkg-config 依赖。

统一源文件:编译时间优化

仅 WebCore 就拥有超过 5,270 个源文件,如果逐一编译,整个过程将耗费数小时。WebKit 采用**统一源文件(unified sources)**机制,将多个 .cpp 文件打包成单个编译单元,从而大幅缩短编译时间。

flowchart TD
    SOURCES["Sources.txt<br/>5,270 .cpp file paths"] --> GEN["Build system<br/>(CMake or Xcode)"]
    GEN --> U1["UnifiedSource1.cpp<br/>#include 'File1.cpp'<br/>#include 'File2.cpp'<br/>...<br/>#include 'File8.cpp'"]
    GEN --> U2["UnifiedSource2.cpp<br/>#include 'File9.cpp'<br/>...<br/>#include 'File16.cpp'"]
    GEN --> UN["UnifiedSourceN.cpp<br/>..."]
    
    U1 --> COMP["Compiler"]
    U2 --> COMP
    UN --> COMP

Sources.txt 是一个简单的相对路径列表,每行一个 .cpp 文件。构建系统读取这份列表,将文件按约 8 个一组进行打包,生成以 #include 原始源文件为内容的 UnifiedSourceN.cpp 文件。

这种方式能将编译时间缩短一个数量级,原因如下:

  1. 头文件在每个打包单元中只解析一次,而非每个源文件各解析一次。
  2. 模板实例化可在同一打包单元内的文件之间共享。
  3. 编译器的整体遍历次数大幅减少。

这种优化也带来了一定的代价——同一打包单元内的文件共享同一个编译单元,这意味着:

  • 某个文件中的静态符号,对同一打包单元内的其他文件是可见的。
  • 头文件的引入顺序至关重要——在第 3 个文件中引入的头文件,在同一打包单元的第 4 至第 8 个文件中同样可用。
  • 同一打包单元内的文件之间可能发生符号冲突。

平台专属文件放在独立的列表中:Apple 平台对应 SourcesCocoa.txt,GTK 对应 SourcesGTK.txt,依此类推。这些文件会被单独打包,且只在对应平台上参与编译。

提示: 如果你的改动导致出现莫名其妙的构建错误,比如某个符号"已被定义"或某个头文件"找不到",很可能是遇到了统一源文件的排序问题。检查一下你的文件落在哪个打包单元中,并确保它不会与 Sources.txt 中相邻的文件产生冲突。

条件编译:PLATFORM、ENABLE、USE

正如第 1 篇所讨论的,三组宏家族控制着编译内容的取舍。下面来更深入地了解它们之间的协作方式:

flowchart TD
    subgraph MACROS["Conditional Compilation Macros"]
        PLAT["PLATFORM(X)<br/>Target OS<br/>COCOA, GTK, WPE, WIN"]
        EN["ENABLE(X)<br/>Feature Flags<br/>WEBGL, CSS_SELECTOR_JIT, FTL_JIT"]
        USE_M["USE(X)<br/>Implementation Choice<br/>CG, CAIRO, SKIA, CF, GLIB"]
    end
    
    PLAT --> CONFIG_H["config.h<br/>(auto-generated)"]
    EN --> CONFIG_H
    USE_M --> CONFIG_H
    CONFIG_H --> EVERY_CPP["Every .cpp file<br/>#include 'config.h'"]

PLATFORM(COCOA) 在所有 Apple 平台(macOS、iOS 等)上为真;PLATFORM(GTK) 在 GTK 移植版上为真。两者互斥。

ENABLE(X) 系列的开关可以独立切换。ENABLE(WEBGL) 可以不受平台限制地开启或关闭;ENABLE(CSS_SELECTOR_JIT) 启用第 3 篇介绍的 CSS JIT 编译器;ENABLE(FTL_JIT) 启用第 5 篇介绍的 FTL 编译层级。

USE(X) 用于选择具体的实现方案。USE(CG) 表示使用 CoreGraphics 进行 2D 渲染;在非 Apple 平台上,USE(CAIRO)USE(SKIA) 是可选的替代方案;USE(CF) 表示使用 CoreFoundation;USE(GLIB) 表示使用 GLib。

构建 WebKit:build-webkit 及相关工具

构建 WebKit 最简便的方式是使用 Tools/Scripts/ 目录下的 build-webkit 脚本:

# Build for macOS (Debug)
Tools/Scripts/build-webkit --debug

# Build for macOS (Release)
Tools/Scripts/build-webkit --release

# Build for GTK
Tools/Scripts/build-webkit --gtk --release

# Build for WPE
Tools/Scripts/build-webkit --wpe --debug

构建完成后,可以用本地构建版本启动 Safari:

Tools/Scripts/run-safari --debug

这个脚本会将 DYLD_FRAMEWORK_PATH 指向你的构建输出目录,使 Safari 加载本地构建的 WebKit framework,而非系统自带的版本。

flowchart LR
    DEV["Developer"] --> BW["build-webkit<br/>--debug or --release"]
    BW -->|macOS| XCODE["xcodebuild<br/>WebKit.xcworkspace"]
    BW -->|GTK/WPE| CMAKE_B["cmake --build"]
    XCODE --> BUILD_DIR["WebKitBuild/Debug<br/>or Release"]
    CMAKE_B --> BUILD_DIR
    BUILD_DIR --> RS["run-safari<br/>DYLD_FRAMEWORK_PATH"]
    RS --> SAFARI["Safari with local WebKit"]

根目录下的 Makefile 提供了一些便捷目标,将常用配置的 build-webkit 调用封装好,开箱即用。

提示: 想要加快迭代速度,可以只构建你正在修改的组件。如果你改动的是 WebCore,每次都从头重建 JSC 是没有必要的。构建系统对增量构建的支持已经相当完善,此外你也可以用 build-webkit --no-experimental-features 跳过不需要的功能,进一步节省时间。

测试基础设施:Layout Tests、JSTests 与 API Tests

WebKit 拥有四套相互独立的测试套件,分别覆盖不同的层次:

套件 位置 运行器 测试内容
Layout Tests LayoutTests/ run-webkit-tests Web 平台合规性(HTML、CSS、DOM 渲染)
JSTests JSTests/ run-javascriptcore-tests JavaScript 引擎正确性
API Tests Tools/TestWebKitAPI/ GTest C++ API 正确性
WebDriver Tests WebDriverTests/ 基于 Selenium 浏览器自动化

Layout Tests 是最具特色的一套。每个测试都是一个 HTML 文件,会产生文本转储或像素渲染结果。测试运行器将实际输出与预期结果(存储为 -expected.txt-expected.png 文件)进行比对。这种方式能够测试从解析到渲染的完整流程。运行方式如下:

Tools/Scripts/run-webkit-tests

JSTests 通过数千个 JavaScript 程序、压力测试和微基准测试来验证 JavaScriptCore 的正确性。测试运行器会覆盖所有编译层级:

Tools/Scripts/run-javascriptcore-tests
flowchart TD
    subgraph LAYOUT["Layout Tests"]
        HTML["test.html"] --> RUNNER["run-webkit-tests<br/>(DumpRenderTree / WebKitTestRunner)"]
        RUNNER --> OUTPUT["Actual output"]
        EXPECTED["test-expected.txt"] --> DIFF["Compare"]
        OUTPUT --> DIFF
        DIFF --> RESULT["PASS / FAIL"]
    end
    
    subgraph JSC_TESTS["JSTests"]
        JS["test.js"] --> JSC_RUN["run-javascriptcore-tests"]
        JSC_RUN --> |"All tiers"| JSC_OUT["Test results"]
    end
    
    subgraph API["API Tests"]
        GTEST["TestWebKitAPI/*.cpp"] --> GTEST_RUN["GTest runner"]
        GTEST_RUN --> API_OUT["Test results"]
    end

API Tests 是基于 GTest 的 C++ 测试,直接对 WebKit 的公共 API 和内部 API 进行验证。相比 Layout Tests,它们运行速度更快,非常适合对特定 C++ 类进行隔离测试。

性能基准测试

WebKit 维护了三大主要基准测试,用于检测性能回退:

  • JetStream — 通过哈希表、加密、物理模拟、解析器等多种负载,测试 JavaScript 和 WebAssembly 的性能。位于 PerformanceTests/JetStream3/

  • MotionMark — 通过动态 2D 动画场景对绘制和合成流程施压,测试图形和渲染性能。

  • Speedometer — 通过模拟用户与基于 React、Angular、Vue 等主流框架构建的 TodoMVC 风格应用进行交互,测试 Web 应用的响应速度。

这些基准测试位于 PerformanceTests/ 目录,并在 WebKit 的 CI 基础设施上持续运行。一旦检测到性能回退,会被当作需要修复的 bug 对待。

贡献者工作流:git-webkit 与代码风格

WebKit 使用 GitHub Pull Request 进行代码审查。git-webkit 工具(通过 Tools/Scripts/ 安装)可以简化整个工作流:

# Install git-webkit
git webkit setup

# Create a branch and make changes
git checkout -b my-fix
# ... edit files ...
git commit -m "[Component] Fix the thing"

# Submit a pull request
git webkit pr
flowchart LR
    BRANCH["Create branch"] --> EDIT["Make changes"]
    EDIT --> COMMIT["git commit"]
    COMMIT --> PR["git webkit pr"]
    PR --> REVIEW["Code review on GitHub"]
    REVIEW --> |"approved"| LAND["git webkit land"]
    REVIEW --> |"changes requested"| EDIT

代码格式由 .clang-format 统一约束,其中定义了 WebKit 的编码风格。主要规则包括:

  • 大括号风格BreakBeforeBraces: WebKit — 控制结构的左括号与语句同行,函数则另起一行。
  • 缩进:4 个空格,禁止使用制表符。
  • 列宽限制:clang-format 不强制限制(WebKit 采用手动换行)。
  • 命名规范:类名使用 PascalCase,方法名使用 camelCase,成员变量使用 m_camelCase

提交前,请运行风格检查工具:

Tools/Scripts/check-webkit-style

提示: Introduction.md 包含面向新贡献者的详细说明,涵盖开发环境搭建、如何找到适合入手的 bug,以及如何完成代码审查流程。这是官方的新人入门文档,会持续保持更新。

系列总结

这六篇文章带领我们从架构基础出发,一路走到了实际的贡献工作流,完整地穿越了 WebKit 代码库:

  1. 架构总览 — 分层依赖链、多进程模型与仓库结构。
  2. WTF 与内存管理 — 智能指针体系、protectedThis、IsoHeap 安全加固。
  3. WebCore — DOM、渲染流水线、CSS JIT 与 Web IDL 绑定。
  4. 多进程 IPC.messages.in 代码生成系统与代理模式。
  5. JavaScriptCore — 四层编译流水线与 B3 后端。
  6. 构建与测试 — 双构建系统、统一源文件、测试套件与贡献者工作流。

WebKit 是一个活跃演进的项目,每年有数千次提交。我们深入探讨的这些架构模式——分层依赖、进程隔离、分层编译、侵入式引用计数——都是经受了十余年考验的稳定设计决策。真正理解它们,你就能在这个庞大的代码库中游刃有余,无论走到哪个角落都不会迷失方向。

加深理解最好的方式,就是找一个你感兴趣的具体领域,读代码,并参与贡献。WebKit 社区对新贡献者非常友好,完善的测试基础设施也让你能够放心地大胆尝试。祝你好运,编码愉快。