Read OSS

Build Tool, Testing Infrastructure, and Contributor Workflow

Intermediate

Prerequisites

  • Article 1: Architecture Overview (understanding of what components exist)
  • Basic CMake knowledge (targets, dependencies, variables)
  • Familiarity with command-line build tools and test runners

Build tool, Testing Infrastructure, and Contributor Workflow

Architectural knowledge only becomes useful when you can build the project, run its tests, and submit changes. WebKit's build tool is surprisingly approachable despite the project's size — but it has quirks that can trip up newcomers. The dual build tools, the unified sources optimization, and the comprehensive test suites each require understanding to work with effectively.

This article is the practical companion to the architectural deep-dives in Parts 1–5. By the end, you'll know how to build WebKit on your platform, run the right tests for your changes, and submit a pull request using the git-webkit workflow.

Dual build tools: Xcode and CMake

WebKit maintains two parallel build tools, each targeting different platforms:

Build tool Platforms Entry Point
Xcode macOS, iOS, tvOS, watchOS, visionOS WebKit.xcworkspace
CMake GTK, WPE, PlayStation, Windows CMakeLists.txt

On Apple platforms, the Xcode workspace (WebKit.xcworkspace) is the primary build entry point. It contains targets for every component and handles framework creation, code signing, and platform-specific compilation. Apple engineers and most macOS contributors use this path.

For all non-Apple platforms, CMake is the build tool. The CMakePresets.json file defines common build configurations:

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

The presets include release, debug, and dev base configurations plus platform-specific presets like gtk-release and wpe-debug. The dev preset enables CMAKE_EXPORT_COMPILE_COMMANDS (for IDE integration) and DEVELOPER_MODE (which builds test runners and performance tools).

Platform-specific build configuration happens through PlatformX.cmake files within each component. As we saw in Part 1, PlatformMac.cmake adds macOS-specific sources and framework dependencies, while PlatformGTK.cmake adds GTK-specific sources and pkg-config dependencies.

Unified Sources: Compile Time Optimization

With over 5,270 source files in WebCore alone, compiling each file individually would take hours. WebKit uses unified sources to dramatically reduce compile times by bundling multiple .cpp files into single translation units.

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

The Sources.txt file is a simple list of relative paths — one .cpp file per line. The build system reads this list and groups files into bundles of approximately 8, generating UnifiedSourceN.cpp files that #include the original source files.

This reduces compile times by an order of magnitude because:

  1. Header files are parsed once per bundle instead of once per source file.
  2. Template instantiations are shared across bundled files.
  3. The compiler makes fewer passes overall.

The tradeoff is that files within the same bundle share a single translation unit, which means:

  • Static symbols in one file are visible in other files in the same bundle.
  • Include order matters — a header included in file 3 is available in files 4–8 of the same bundle.
  • Symbol conflicts between files in the same bundle are possible.

Platform-specific files go in separate lists: SourcesCocoa.txt for Apple, SourcesGTK.txt for GTK, and so on. These are bundled separately and only compiled on the relevant platform.

Tip: If your change causes a mysterious build failure where a symbol is "already defined" or a header is "not found," you may be experiencing a unified source ordering issue. Check which bundle your file falls into, and ensure it doesn't conflict with adjacent files in Sources.txt.

Conditional Compilation: PLATFORM, ENABLE, USE

As discussed in Part 1, three macro families control what gets compiled. Here's a more detailed look at how they interact:

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) is true for all Apple platforms (macOS, iOS, etc.). PLATFORM(GTK) is true for the GTK port. These are mutually exclusive.

ENABLE(X) flags can be independently toggled. ENABLE(WEBGL) can be on or off regardless of platform. ENABLE(CSS_SELECTOR_JIT) enables the CSS JIT compiler from Part 3. ENABLE(FTL_JIT) enables the FTL compilation tier from Part 5.

USE(X) selects implementation choices. USE(CG) means CoreGraphics for 2D rendering. USE(CAIRO) or USE(SKIA) are alternatives on non-Apple platforms. USE(CF) means CoreFoundation. USE(GLIB) means GLib.

Building WebKit: build-webkit and Friends

The simplest way to build WebKit is the build-webkit script in Tools/Scripts/:

# 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

After building, you can launch Safari with your local build:

Tools/Scripts/run-safari --debug

This script sets DYLD_FRAMEWORK_PATH to point to your build output, so Safari loads your locally-built WebKit frameworks instead of the system ones.

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"]

The root Makefile provides convenience targets that wrap build-webkit for common configurations.

Tip: For faster iteration, build only the component you're working on. If you're modifying WebCore, you don't need to rebuild JSC from scratch on every change. The build system handles incremental builds well, but you can also use build-webkit --no-experimental-features to skip features you don't need.

Test Infrastructure: Layout Tests, JSTests, and API Tests

WebKit has four distinct test suites, each testing a different layer:

Suite Location Runner What It Tests
Layout Tests LayoutTests/ run-webkit-tests Web platform conformance (HTML, CSS, DOM rendering)
JSTests JSTests/ run-javascriptcore-tests JavaScript engine correctness
API Tests Tools/TestWebKitAPI/ GTest C++ API correctness
WebDriver Tests WebDriverTests/ Selenium-based Browser automation

Layout Tests are the most distinctive. Each test is an HTML file that produces either a text dump or a pixel rendering. The test runner compares the output against expected results (stored as -expected.txt or -expected.png files). This approach tests the full pipeline from parsing through rendering. Run them with:

Tools/Scripts/run-webkit-tests

JSTests test JavaScriptCore's correctness with thousands of JavaScript programs, stress tests, and microbenchmarks. The runner exercises all compilation tiers:

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 are GTest-based C++ tests that exercise WebKit's public and internal APIs directly. They're faster than layout tests and useful for testing specific C++ classes in isolation.

Performance Benchmarks

WebKit maintains three major benchmarks that are used to detect performance regressions:

  • JetStream — Tests JavaScript and WebAssembly performance across a wide variety of workloads (hash tables, crypto, physics simulations, parsers). Located in PerformanceTests/JetStream3/.

  • MotionMark — Tests graphics and rendering performance with animated 2D scenes that stress the painting and compositing pipeline.

  • Speedometer — Tests web application responsiveness by simulating user interactions with TodoMVC-style apps built with various frameworks (React, Angular, Vue, etc.).

These benchmarks live in PerformanceTests/ and are regularly run on WebKit's CI infrastructure. Performance regressions detected by these benchmarks are treated as bugs to be fixed.

Contributor Workflow: git-webkit and Code Style

WebKit uses GitHub pull requests for code review. The git-webkit tool (installed from Tools/Scripts/) streamlines the workflow:

# 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

Code formatting is enforced by .clang-format, which defines the WebKit coding style. Key rules include:

  • Brace style: BreakBeforeBraces: WebKit — opening braces on the same line for control structures, new line for functions.
  • Indentation: 4 spaces, no tabs.
  • Column limit: Not enforced by clang-format (WebKit wraps manually).
  • Naming: Classes are PascalCase, methods are camelCase, member variables are m_camelCase.

Before submitting, run the style checker:

Tools/Scripts/check-webkit-style

Tip: The Introduction.md file contains detailed instructions for new contributors, including how to set up your development environment, how to find good first bugs, and how to navigate the review process. It's the official onboarding document and is kept up to date.

Series Conclusion

Across these six articles, we've traversed the WebKit codebase from its architectural foundations to practical contribution workflows:

  1. Architecture Overview — The layered dependency chain, multi-process model, and repository structure.
  2. WTF and Memory Management — The smart pointer system, protectedThis, IsoHeap security hardening.
  3. WebCore — The DOM, rendering pipeline, CSS JIT, and Web IDL bindings.
  4. Multi-Process IPC — The .messages.in code generation system and proxy pattern.
  5. JavaScriptCore — The four-tier compilation pipeline and B3 backend.
  6. Build and Testing — Dual build tools, unified sources, test suites, and contributor workflow.

WebKit is a living project with thousands of commits per year. The architectural patterns we've explored — layered dependencies, process isolation, tiered compilation, intrusive reference counting — are stable design decisions that have guided the project for over a decade. Understanding them gives you the foundation to navigate any corner of this massive codebase.

The best way to deepen your understanding is to pick a specific area that interests you, read the code, and contribute. WebKit's community is welcoming to new contributors, and the test infrastructure makes it safe to experiment. Good luck, and happy hacking.