Skip to content

build-engine-windows #4

build-engine-windows

build-engine-windows #4

name: build-engine-windows
# Builds OUR OWN WinCairo WebKit engine FROM SOURCE on a Windows runner, relocates
# it into the Bunmaska engine-store layout, and proves it loads from the store —
# the Windows peer of `build-engine.yml` (which relocates apt's WebKitGTK on Linux).
# Windows has no system WebKit and we never ship Playwright's build, so the engine
# is our own from-source binary.
#
# This recipe mirrors the one proven locally end-to-end (a real BrowserWindow loaded
# the from-source engine from the store: STORE_ENGINE_OK). Notes baked in from that:
# * WinCairo dropped MSVC — it builds with clang-cl, so we install LLVM 20.
# * WebKit's code-gen needs gperf on PATH.
# * A short WEBKIT_OUTPUTDIR avoids Windows' 250-char object-path limit in vcpkg.
# * patch-webkit-wincairo.py routes ~300 serializer inputs through a response file
# (otherwise the inline command line overflows cmd.exe's ~8191-char limit).
# * `ninja -k 0` finishes past a broken dev-tooling target (compile_commands.json)
# that no product DLL depends on; success = WebKit2.dll exists.
#
# Heavy (~hours, GBs): runs on demand / on the engine branch only.
on:
workflow_dispatch:
inputs:
webkit_tag:
description: 'WebKit git tag to build (STABLE train only - even minor, e.g. wpewebkit-2.52.5)'
required: true
default: 'wpewebkit-2.52.5'
llvm:
description: 'LLVM version for clang-cl (older WebKit trains may need an older clang)'
required: false
default: '20.1.8'
push:
branches: [feat/windows-engine]
jobs:
build:
name: build WinCairo from source (windows)
runs-on: windows-latest
timeout-minutes: 350
env:
# Hosted engines track the STABLE WPE train only (even minor; WSAs land
# there). Development tags (odd minor) are for local experiments, never
# the feed.
WEBKIT_TAG: ${{ github.event.inputs.webkit_tag || 'wpewebkit-2.52.5' }}
# Short build-output root (WebKit honours WEBKIT_OUTPUTDIR): vcpkg's ICU build
# generates pathologically deep try-compile object paths that blow past
# Windows' 250-char limit when nested under the workspace.
WEBKIT_OUTPUTDIR: C:\wkb
LLVM_VER: ${{ github.event.inputs.llvm || '20.1.8' }}
LLVM_DIR: C:\llvm\clang+llvm-${{ github.event.inputs.llvm || '20.1.8' }}-x86_64-pc-windows-msvc
# Publish steps are gated on these so forks / unconfigured runs still build
# + prove the engine, skipping pack/sign/upload when the secrets are absent.
HAVE_KEY: ${{ secrets.ENGINE_SIGNING_KEY != '' }}
# R2_PUBLISH_TOKEN is R2-only, separate from the Pages-scoped
# CLOUDFLARE_API_TOKEN deploy-web.yml uses (which has no R2 perms).
HAVE_R2: ${{ secrets.R2_PUBLISH_TOKEN != '' && secrets.CLOUDFLARE_ACCOUNT_ID != '' }}
steps:
- name: Checkout bunmaska
uses: actions/checkout@v4
with:
path: bunmaska
# Clone WebKit to a SHORT path (C:\WebKit) at the pinned tag; shallow (history
# is enormous, the build only needs the tree at that revision).
- name: Checkout WebKit at the pinned tag
run: git clone --depth 1 --branch "$env:WEBKIT_TAG" https://github.com/WebKit/WebKit.git C:\WebKit
shell: pwsh
- name: Apply Bunmaska's WinCairo build patch (serializers response file)
run: python bunmaska/tools/engine/patch-webkit-wincairo.py C:\WebKit
shell: pwsh
# cmake + VS are preinstalled; add ninja, perl, ruby, gperf.
- name: Install build tools (ninja, perl, ruby, gperf)
run: |
choco install -y ninja strawberryperl ruby
curl.exe -L -A "Mozilla/5.0" -o "$env:TEMP\gperf.zip" `
"https://master.dl.sourceforge.net/project/ezwinports/gperf-3.1-w32-bin.zip?viasf=1"
Expand-Archive "$env:TEMP\gperf.zip" -DestinationPath C:\gperf -Force
shell: pwsh
# WinCairo builds with clang-cl (the find_library for clang_rt.builtins
# resolves relative to the clang-cl compiler). Version is an input so an
# older WebKit train can pin the clang it was contemporary with.
- name: Install LLVM (clang-cl)
run: |
curl.exe -L -o "$env:TEMP\llvm.tar.xz" `
"https://github.com/llvm/llvm-project/releases/download/llvmorg-$env:LLVM_VER/clang%2Bllvm-$env:LLVM_VER-x86_64-pc-windows-msvc.tar.xz"
New-Item -ItemType Directory -Force -Path C:\llvm | Out-Null
tar.exe -xf "$env:TEMP\llvm.tar.xz" -C C:\llvm
shell: pwsh
- name: Set up MSVC (x64)
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
# build-webkit configures (auto-building the vcpkg deps) and compiles; it stops
# on the broken dev-tooling target, so a follow-up `ninja -k 0` finishes the
# product DLLs. clang-cl is the compiler (CC/CXX); gperf + clang on PATH.
- name: Build WebKit (WinCairo, Release, clang-cl)
working-directory: C:\WebKit
env:
CC: ${{ env.LLVM_DIR }}\bin\clang-cl.exe
CXX: ${{ env.LLVM_DIR }}\bin\clang-cl.exe
run: |
$env:Path = "$env:LLVM_DIR\bin;C:\gperf\bin;$env:Path"
perl Tools/Scripts/build-webkit --wincairo --release
if (-not (Test-Path "$env:WEBKIT_OUTPUTDIR\Release\bin\WebKit2.dll")) {
Write-Host "finishing past the dev-tooling target with ninja -k 0 ..."
ninja -C "$env:WEBKIT_OUTPUTDIR\Release" -k 0
}
if (-not (Test-Path "$env:WEBKIT_OUTPUTDIR\Release\bin\WebKit2.dll")) {
Write-Error "WebKit2.dll was not produced"; exit 1
}
shell: pwsh
# A failed CMake probe (e.g. the atomics check) only explains itself in
# CMakeError.log, which dies with the runner - surface it in the job log.
- name: Dump CMake configure logs on failure
if: failure()
run: |
foreach ($f in @("CMakeError.log", "CMakeOutput.log")) {
$p = "$env:WEBKIT_OUTPUTDIR\Release\CMakeFiles\$f"
if (Test-Path $p) {
Write-Host "===== tail of $f ====="
Get-Content $p -Tail 150
}
}
shell: pwsh
- name: Compute the engine id from the tag
id: id
run: |
$ver = "$env:WEBKIT_TAG" -replace '^(wpe|webkit)webkit-?|^webkitgtk-', ''
"engine_id=webkit-2-$ver-bunmaska1-windows-x64" >> $env:GITHUB_OUTPUT
shell: pwsh
# Relocate the from-source closure (Release/bin) into the store, mark installed.
- name: Relocate into the engine store
run: |
$store = "$env:RUNNER_TEMP\store"
& bunmaska/tools/engine/build-wincairo-windows.ps1 `
-Source "$env:WEBKIT_OUTPUTDIR\Release\bin" -OutDir $store `
-EngineId "${{ steps.id.outputs.engine_id }}"
New-Item -ItemType File -Force `
-Path "$store\${{ steps.id.outputs.engine_id }}\INSTALLATION_COMPLETE" | Out-Null
shell: pwsh
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: bunmaska/.bun-version
- name: Install bun deps
working-directory: bunmaska
run: bun install --frozen-lockfile
# Prove the relocated engine works resolved purely from the store (no
# BUNMASKA_WEBKIT_PATH): a real BrowserWindow + executeJavaScript.
- name: Prove it loads from the store
working-directory: bunmaska
env:
BUNMASKA_ENGINES_PATH: ${{ runner.temp }}\store
BUNMASKA_WEBKIT_ID: ${{ steps.id.outputs.engine_id }}
run: |
$out = bun run tools/engine/windows-engine-load-probe.ts 2>&1 | Out-String
Write-Host $out
if ($out -notmatch 'STORE_ENGINE_OK') { Write-Error "engine did not load from the store"; exit 1 }
shell: pwsh
- name: Upload the engine artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.id.outputs.engine_id }}
path: ${{ runner.temp }}\store\${{ steps.id.outputs.engine_id }}
retention-days: 7
# Pack + sign the engine into the 3-file feed layout, then publish to R2.
# Gated on the signing key being present, so forks / unconfigured runs still
# build + prove the engine without needing the release secret.
- name: Pack + sign the engine for the feed
if: ${{ env.HAVE_KEY == 'true' }}
working-directory: bunmaska
env:
ENGINE_SIGNING_KEY: ${{ secrets.ENGINE_SIGNING_KEY }}
run: |
$key = "$env:RUNNER_TEMP\engine-signing-key.pem"
Set-Content -Path $key -Value $env:ENGINE_SIGNING_KEY -NoNewline
bun tools/engine/pack-engine.ts `
"$env:RUNNER_TEMP\store\${{ steps.id.outputs.engine_id }}" `
"$env:RUNNER_TEMP\feed" $key
Remove-Item $key -Force
shell: pwsh
- name: Publish the signed engine to R2
if: ${{ env.HAVE_KEY == 'true' && env.HAVE_R2 == 'true' }}
working-directory: bunmaska
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.R2_PUBLISH_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
bun tools/engine/publish-engine-r2.ts `
"$env:RUNNER_TEMP\feed" "${{ steps.id.outputs.engine_id }}" --bucket bunmaska-engines
shell: pwsh