12 KiB
Phase 0 — Infrastructure
Goal of Phase 0: make the entire stack compile and run on Linux, macOS, and Windows. No editing, no rendering features. Success = the engine library builds clean on all three platforms and the smoke test passes. Phase 1 does not begin until Gate G0 and G0b are reached.
Phase 0 task board
| # | Task | Owner | This session |
|---|---|---|---|
| 1 | CMake root + vcpkg + CI/CD pipeline | Dev 1 | Done — scaffolded |
| 2 | PDFium build (depot_tools + GN + Ninja) | Dev 1 | Build scripts staged; not yet run |
| 3 | Skia build integration | Dev 2 | Not started |
| 4 | FreeType + HarfBuzz vcpkg integration | Dev 3 | In manifest; wrappers not started |
| 5 | FastAPI service scaffolding | Dev 1 | Placeholder dir only |
| 6 | React + TypeScript frontend scaffolding | Dev 2 | Placeholder dir only |
| 7 | WASM hello-world build (Emscripten) | Dev 1 | Toolchain hook + preset stubbed |
| 8 | Frozen interface contracts (Gate G0b) | All | Placeholder header; not designed |
This session delivered Task 1 in full plus the repository structure for everything else. Scope was deliberately limited to the build pipeline — see "What is intentionally not done" below.
What was built this session
Code/
├── CMakeLists.txt root build; refuses to configure without a pinned baseline
├── CMakePresets.json debug/release/asan per platform + a wasm stub
├── vcpkg.json dependency manifest (freetype, harfbuzz, spdlog, gtest)
├── .clang-format .clang-tidy style + naming rules from blueprint §16.1
├── .gitignore .gitattributes .editorconfig
├── cmake/
│ ├── pdfium.cmake turns the PDFium install tree into pdfium::pdfium
│ ├── CompilerWarnings.cmake high warning levels per compiler
│ ├── Sanitizers.cmake ASan/UBSan wiring
│ └── toolchains/wasm.cmake Emscripten hook (stub)
├── engine/
│ ├── CMakeLists.txt
│ ├── include/pdfengine/ public headers (version, umbrella, pdf_document placeholder)
│ ├── src/core/ engine_info.cpp — version/build introspection
│ ├── src/parser/ pdfium_loader — the ONLY FPDF_-allowed dir (Rule R2)
│ └── tests/ gtest smoke test backing Gate G0
├── third_party/pdfium/ from-source build scripts + pinned-ref file + args.gn
├── scripts/
│ ├── bootstrap.{sh,ps1} installs vcpkg, pins the dependency baseline
│ └── check_pdfium_boundary.{sh,ps1} Rule R2 enforcement
├── .github/workflows/ci.yml Linux/macOS/Windows build matrix + lint jobs
├── bindings/ gateway/ frontend/ wasm/ corpus/ placeholder dirs with READMEs
└── docs/phase0.md this file
How to build (developer onboarding)
Prerequisites
| Tool | Version | Notes |
|---|---|---|
| CMake | >= 3.25 | presets v6 |
| Ninja | any recent | the only generator used |
| C++ compiler | MSVC 19.36+ / GCC 13+ / Clang 16+ | needs C++23 |
| vcpkg | — | scripts/bootstrap installs it if VCPKG_ROOT is unset |
| Git | any recent | |
| clang-format | 22.1.5 | not natively packaged on Windows — pip install clang-format==22.1.5 (CI is pinned to this exact version) |
Windows: either Visual Studio 2022/2026 (with the "Desktop development with C++" workload) or Build Tools 2026 (no IDE — installer product
Microsoft.VisualStudio.Product.BuildTools) is supported.cl.exeis not onPATHby default — run builds from a Developer PowerShell or importVC\Auxiliary\Build\vcvars64.batfirst. Build Tools is not a defaultvswhereproduct, so detection scripts needvswhere -products *(the PDFium build script already does this). CI usesilammy/msvc-dev-cmd.
VCPKG_ROOTset viasetx(or the bootstrap script's persistent install) does not propagate into already-open shells — set$env:VCPKG_ROOTexplicitly in that shell, or open a new terminal. PowerShell 5.1 is fine;pwsh(7+) is not required by anything in this repo.
Steps
# 1. One-time setup — checks tools, installs vcpkg, pins the dependency baseline.
pwsh scripts/bootstrap.ps1 # Windows
./scripts/bootstrap.sh # Linux / macOS
# 2. Configure + build + test.
cmake --preset windows-debug # linux-debug | macos-debug
cmake --build --preset windows-debug
ctest --preset windows-debug
The first configure compiles the vcpkg dependencies (freetype, harfbuzz, spdlog, gtest) — slow once, cached after.
Building with PDFium
PDFium is built separately from source (Task 2):
# Pin the revision first — edit third_party/pdfium/pdfium.pinned (see its README).
pwsh third_party/pdfium/build_pdfium.ps1 # or .sh
Until then the engine builds with PDFium code paths #ifdef-ed out, which is
the correct Phase 0 default — it keeps the pipeline green while Task 2 runs.
On Linux/macOS, enabling PDFium is a single flag added to a debug build:
cmake --preset linux-debug -DPDFENGINE_WITH_PDFIUM=ON
On Windows there is more to it — see the next section.
Windows + PDFium
PDFium's static-lib GN build forces the static CRT (/MT, is_debug=false)
and offers no knob for "static lib + dynamic CRT". The engine, vcpkg deps,
and PDFium must therefore all use the same static CRT, or the link dies with
LNK2038: 'RuntimeLibrary' mismatch. The repo is wired for this ("Option A,
all static CRT, release-flavored"):
- The hidden
windows-basepreset in CMakePresets.json setsVCPKG_TARGET_TRIPLET=x64-windows-static(vcpkg deps as static lib + static CRT — first configure rebuilds them, ~7 min one-time) andCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$<CONFIG:Debug>:Debug>. - PDFium's own args.gn keeps
is_debug=false(i.e./MT). - The PDFium-linked engine build must be RelWithDebInfo, not Debug — a
Debug engine is
/MTdand still mismatches PDFium's/MT. The plainwindows-debug -DPDFENGINE_WITH_PDFIUM=ONrecipe will not link; use thewin-local-pdfiumuser preset below.
win-local-pdfium user preset
CMakeUserPresets.json is git-ignored (build dirs are per-developer and live
outside OneDrive). Drop this preset in at Code/CMakeUserPresets.json, with
your own username in binaryDir:
{
"version": 6,
"configurePresets": [
{
"name": "win-local-pdfium",
"inherits": "windows-release",
"binaryDir": "C:/Users/<you>/pdfeng-build/win-local-pdfium",
"cacheVariables": { "PDFENGINE_WITH_PDFIUM": "ON" }
}
],
"buildPresets": [
{ "name": "win-local-pdfium", "configurePreset": "win-local-pdfium" }
],
"testPresets": [
{ "name": "win-local-pdfium", "inherits": "common", "configurePreset": "win-local-pdfium" }
]
}
Then, from a shell with vcvars64.bat imported:
cmake --preset win-local-pdfium
cmake --build --preset win-local-pdfium
ctest --preset win-local-pdfium
Success looks like pdfengine_smoke.exe linking cleanly and logging
pdfium=on. The build dir is intentionally outside OneDrive and on a
space-free path — see the OneDrive section below for why.
Gotcha: depot_tools shadows ninja
The PDFium build adds depot_tools to PATH and the depot_tools ninja /
ninja.bat are not real Ninja — they fail with:
Running ninja --version failed with unknown error
... CMAKE_CXX_COMPILER not set, after EnableLanguage
If depot_tools ended up on your persistent PATH, CMake will pick its
broken ninja for the engine build. Two fixes:
- Short-term: point CMake at the real Ninja explicitly, e.g.
cmake --preset win-local-pdfium -D CMAKE_MAKE_PROGRAM=C:/path/to/real/ninja.exe. This caches, so only the first configure needs the flag. - Long-term (recommended): keep
depot_toolsoff the persistentPATH.third_party/pdfium/build_pdfium.ps1already prepends it per-run, so the PDFium build still works.
Dependency pinning
The blueprint rule is "pin all dependency versions on Day 1, never track rolling HEAD." Two mechanisms:
- vcpkg deps —
scripts/bootstraprunsvcpkg x-update-baseline --add-initial-baseline, which writes abuiltin-baselinecommit intovcpkg.json. That pins the entire dependency registry to one commit. The rootCMakeLists.txtrefuses to configure until this is present. → The first commit to the repo must include the bootstrappedvcpkg.json, otherwise CI fails at the configure step (by design). - PDFium —
third_party/pdfium/pdfium.pinnedholds an exact commit SHA. The build script refuses to run while it is the placeholder. Rebases are a deliberate, scheduled (quarterly) action.
Engineering conventions
- Naming (
.clang-tidy):CamelCasetypes,camelBackfunctions,snake_casefile names. - Errors:
std::expected<T, E>internally;int error_codeacross the C ABI. - Branches:
main,develop,feature/*,release/*. - Rule R2: only
engine/src/parser/may use rawFPDF_*APIs — enforced byscripts/check_pdfium_boundary.*locally and in CI.
What is intentionally NOT done this session
- PDFium is not actually built — scripts are staged; the revision needs to be pinned and the (long) build run as the second half of Task 2.
- No interface contracts —
engine/include/pdfengine/pdf_document.hppis a placeholder.PdfDocument/PdfPageare designed and frozen at Gate G0b in an all-devs session; nothing proceeds until it is signed off. - Skia / FreeType / HarfBuzz wrappers — FreeType + HarfBuzz are in the vcpkg manifest and link-tested, but the actual wrappers are Dev 3's Phase 0/1 work. Skia is Dev 2's task.
- FastAPI / React / WASM — placeholder directories only. WASM has a toolchain hook and preset stub so the integration point exists (Rule R5: WASM never blocks shipping).
Gates ahead
| Gate | Criterion | Unblocks |
|---|---|---|
| G0 | All platforms build clean; PDFium + Skia + FreeType + HarfBuzz compile | Phase 1 |
| G0b | PdfDocument / PdfPage contracts locked by all 3 devs |
Coding begins |
The CI build matrix is the automated half of G0. The smoke test
(engine/tests/smoke_test.cpp) is what it runs.
OneDrive warning
This checkout lives under OneDrive\Work\Maskan\PDF Editor\Code. The path is
both OneDrive-synced and contains a space (PDF Editor). Both bite C++
builds:
- Sync churn — build output is thousands of
.obj/.ofiles.out/is git-ignored, but OneDrive still tries to upload it. - File locks — OneDrive can hold a handle on a file mid-sync, causing intermittent "permission denied" errors during compile or link.
- Spaces in build paths break tooling — vcpkg/meson (harfbuzz) fail
with
LNK1181whenvcpkg_installedis under the spaced path, anddepot_tools/ GN / Ninja.batwrappers cannot handle a space in their own path at all.
On Windows, building inside the repo path is not viable — put the build
dir outside OneDrive on a space-free path. The win-local-pdfium preset
above already does this (C:/Users/<you>/pdfeng-build/...); do the same for
any non-PDFium preset by overriding binaryDir:
cmake --preset windows-release -B C:/Users/<you>/pdfeng-build/windows-release
The PDFium build is even stricter: third_party/pdfium/build_pdfium.ps1
takes a PDFIUM_BUILD_ROOT env var and hard-errors if it contains a space.
Use e.g. C:\Users\<you>\pdfium-build.
On Linux/macOS the OneDrive path is still a sync nuisance but the toolchain
itself is fine. Either point the build dir outside OneDrive, or exclude
out/ and vcpkg/ from sync, or pause sync while building.
Long term, the repository should live outside OneDrive on a real Git remote.