Build for Windows (MSVC)

Native MSVC 2022 build, on a local libvirt VM or the dockerised build farm

Build for Windows (MSVC)

Build OctoMY for Windows using Visual Studio Build Tools 2022 (MSVC 14.4x) and Qt 6.10.1 (msvc2022_64). This is the production Windows target and the most thoroughly attempted of the three Windows paths.

There is no native-Windows-developer-workflow documented yet. There are two ways to drive a full MSVC build from Linux:

  • On your own machine: a local Windows 11 build VM on plain libvirt/QEMU, created and provisioned by integration/winvm/winvm. It gets the farm's exact toolchain and refuses anything else. This is the route for developers.
  • The CI farm: the dockurr/windows container. The rest of this page describes it. Its scripts live in integration/cicd/targets/windows-msvc-x64/; the compose file the farm actually runs is integration/qt-build-farm/docker-compose.yaml.

Status

Item State Notes
Build infrastructure ✅ Working Dockerised Win11 VM with VS 2022 + Qt 6.10.1 + qbs 3.3.1 (one install for both x64 and the arm64 cross; the former C:\qbs3 second install is retired)
Qbs toolchain detection ✅ Working qbs.toolchain.contains("msvc") branches in BuildOptions.qbs:191+
Random number generation ✅ Complete WinCryptoRNG (BCryptGenRandom) wired in librng
Serial port backend ✅ Complete WinAPI backend in libserial
Network gateway detection ✅ Complete WMI implementation in libutil
Memory info ✅ Complete GlobalMemoryStatusEx abstraction
App icons / .rc ✅ Complete Handled by integration/qbs/imports/App.qbs:31
CryptoStream ✅ Complete Now uses mbedtls_hkdf; no OpenSSL dependency
Process spawning ✅ Complete platform::process::spawnDetached via libplatform (QProcess::startDetached underneath)
File permissions / chmod ⚠️ POSIX only FsChmodTask.cpp uses QFile::setPermissions() on Windows (degraded behaviour but compiles + runs)
LLVM module ⚠️ Compiles + links, pending commit libllvm/lib.qbs is gated on project.useLlvm alone, with no platform test. Compiles and links under MSVC (measured 2026-08-19: 3,075 TUs, 0 errors, all 12 binaries) with LogicalView + PDB/DIA excluded. Four LLVM-source fixes are not yet committed, so the result is not yet reproducible from a clean checkout
Tests on Windows ✅ Run qbs run -p tests via winexec.sh "..." from the Linux host; cross-validated against MinGW-cross under Wine (240 tests, 239 PASS + 1 known-flaky testWatermark) — that cross-validation is a 2026 record; the MinGW route was retired in 2026-08 and is not repeatable
Portable .zip ✅ Working build_qt_project.py --create-portable produces octomy_<ver>_<arch>_portable.zip
setup.exe installer ✅ Working build_qt_project.py --create-installer via Inno Setup; lzma2/max compression, start-menu shortcuts
.msi installer ✅ Working Same script, automatic when WiX Toolset 3.11/3.14 is on the build host
Code signing 🔬 Investigated Free options identified (SignPath community, sigstore-like flows); not yet wired into the artifact pipeline
MSIX packaging 🔬 Investigated Newer Windows packaging format; not yet implemented

Bottom line: The MSVC pipeline produces release-quality binaries and three installer artifacts on every run. Code-signing and MSIX are the only outstanding shippability gaps; LLVM remains intentionally excluded on Windows. See Remaining work below.


Prerequisites

This path uses a Linux host driving a Windows VM. Bare-metal Windows developers can skip the build-farm parts and use the prerequisites table directly inside Windows.

Host (Linux)

Component Notes
KVM ls /dev/kvm must succeed; enable virtualisation in BIOS
QEMU Bundled in the dockurr/windows image; nothing to install on the host
User in kvm sudo usermod -aG kvm $USER (log out and back in)
Docker Compose Used to orchestrate the dockur/windows image
Disk / RAM 200 GB free; the compose files give the VM 48 GB RAM
CPU The compose files give the VM 48–62 cores, sized for y8; lower CPU_CORES on a smaller host, or use the local VM

Guest (Windows 11)

Provisioned by integration/cicd/targets/windows-msvc-x64/:

Component Version Installed by
Windows 11 latest dockur/windows unattended install
OpenSSH Server bundled windows/install.bat
Visual Studio Build Tools 2022 shared/provision-windows.ps1
Qt 6.10.1 msvc2022_64 via aqtinstall (-m all); floor is 6.10.0, see integration/toolchain-requirements.json
Qbs 3.3.1 (the floor in integration/toolchain-requirements.json; 3.1.1 is known bad) shared/provision-windows.ps1
CMake, Ninja, Python 3 latest shared/provision-windows.ps1

One-time setup

cd integration/cicd/targets/windows-msvc-x64

# Installs Windows 11, enables SSH, snapshots baseline,
# runs full provisioning (VS Build Tools, Qt, qbs).
./setup-windows.sh

First run: ~30–45 minutes (Windows install + provisioning). Subsequent runs restore from snapshot in seconds.

If provisioning fails

./snapshot-restore.sh baseline           # Win + SSH, pre-provisioning
# Fix shared/provision-windows.ps1, then:
./winexec.sh provision

Access modes

Mode Command
SSH ssh -p 2223 Docker@localhost (password: Docker)
VNC http://localhost:8006 in a browser
RDP xfreerdp /v:localhost /u:Docker /p:Docker

Build and deploy

From the Linux host

cd integration/cicd/targets/windows-msvc-x64
./build-windows.sh                # syncs source, builds via SSH
./build-windows.sh --installer    # also produces setup.exe / MSI / zip
./winexec.sh status               # check build environment state
./winexec.sh shell                # interactive PowerShell over SSH

Build outputs appear in integration/cicd/targets/windows-msvc-x64/shared/artifacts/windows/.

build-windows.sh uploads shared/build_octomy.py, which is not in this repository: the build driver lives in monolith. Point the script at a copy from there, or use the local VM with devsync and qbs directly.

Installer outputs

shared/build_qt_project.py orchestrates the packaging step on the Windows side. With --installer (or its individual flags --create-portable, --create-installer) and a fully provisioned build host, you get up to three artifacts side-by-side:

Artifact Tool Trigger Notes
octomy_<ver>_<arch>_portable.zip zipfile --create-portable Self-contained: every app + Qt DLLs (via windeployqt)
octomy_<ver>_<arch>_setup.exe Inno Setup 6 --create-installer + ISCC.exe present Compressed installer with start-menu shortcuts, uninstaller, x64-only enforcement
octomy_<ver>_<arch>.msi WiX Toolset 3 --create-installer + wix\v3.x\ present Per-app components with GUIDs; suitable for managed deployment / Group Policy

Tools probed on the Windows side:

  • Inno Setup 6: ISCC.exe on PATH or C:\Program Files (x86)\Inno Setup 6\.
  • WiX Toolset: C:\Program Files (x86)\WiX Toolset v3.14\bin\ (or v3.11; v4 not yet wired).

Missing tools are reported ("WiX Toolset not found - skipping MSI creation") and the script continues — never a hard failure.

Direct on Windows (inside the VM)

cd /d C:\octomy
C:\qbs\bin\qbs.exe build -d C:\build profile:qt6 config:release

C:\octomy is where integration/devsync puts the checkout. The farm provisioning creates profile qt6.

Run the test suite:

./winexec.sh "cd /d C:\\octomy && C:\\qbs\\bin\\qbs.exe run -d C:\\build -p tests"

Building with LLVM — the first build is two-step

The vendored LLVM tree is not in git; it is fetched by the build itself from src/libs/libllvm/llvm-pin.json. On a fresh checkout this makes the first build a two-step:

  1. The first qbs resolve finds no llvm-src, so libllvm is excluded — but the llvm-source product still runs during the build and fetches the tree (~252 MB, digest-verified).

  2. The probe result ("sources absent") is cached in the build graph, so a plain re-resolve still answers absent. Run:

    qbs resolve --force-probe-execution
    qbs build
    

    and LLVM is compiled in on this second pass.

Skipping step 2 is the single most likely way to waste an afternoon: the build goes green, produces working binaries, and silently contains no LLVM — build-features.json reports llvm.used:false. The resolve now warns loudly when this happens, and CI legs run with project.strictFeatures:true, which turns the warning into a resolve error.

Other things worth knowing on this path:

  • Build config:release. It is what the farm builds, and a debug MSVC build additionally pulls the non-redistributable debug CRT.
  • Profile naming. A box provisioned with the farm's provision-vm.ps1 gets profile qt6. (qt6101 is a local-Linux profile name and does not exist on Windows boxes.)
  • Qt supplies zlib and OpenSSL. Every official Windows Qt kit ships include/QtZlib/zlib.h with the symbols exported from Qt6Core, and Qt publishes x64 OpenSSL as the tools_opensslv3_x64 package — neither needs separate provisioning. Only an arm64 leg would build OpenSSL from source.
  • Never hand-edit llvm-src. It is .gitignored and re-materialised from the pin, so an edit there survives only until the next fetch and is invisible to review. Fixes to vendored files are declared as overrides in llvm-pin.json — four MSVC conformance overrides ship there today. See the internal spec content/doc/internal/specs/windows-llvm-msvc/.

Feature gating for faster turnaround

The MSVC build is slow. Drop a templated oc_use.json to disable heavy optional modules:

cp integration/oc_use_examples/oc_use_windows_fast.json oc_use.json

That switches off WebEngine, PDF, LLVM, and FEM.


Run the tests on Windows

kiln test reads every suite's own declarations and runs it the way the suite asks. Use it instead of starting suite binaries by hand. The qbs build makes kiln.exe in its kiln.<hash> folder.

kiln test --prebuilt C:\Users\you\brel --profile ci

The runner reads category, timeout, headless, requiresRealPlatform, guardTest and the two pass floors of each suite. It adds --headless or --display per suite, enforces the timeout, applies the floors, and prints one line per suite and a final count. It puts the Qt bin folder that the build used first on PATH for each suite, so the suites find the Qt DLLs.

Useful options:

Option Effect
test\testName Run one suite. Give more paths for more suites.
--category unit Run one category. Separate more categories with commas.
--guard-only Run only the suites marked guardTest: true.
-- --rhi-backend d3d12 Force a backend for every suite. Everything after -- goes to every suite.
-- --rhi-debug Turn on the Direct3D debug layer. Needs the "Graphics Tools" feature.
--log-dir DIR Write each suite's output to its own file.
--report DIR Write tests.json, tests.junit.xml and each suite's log.

A suite that needs a real window must reach the interactive desktop. An SSH session lands in session 0 and cannot show one, so start the runner through a scheduled task. integration\win\kiln-test-desktop.ps1 makes and starts that task:

powershell -ExecutionPolicy Bypass -File integration\win\kiln-test-desktop.ps1 ^
    -BuildDir C:\Users\you\brel -OutDir C:\Users\you\test-out ^
    -KilnArgs '--profile','ci' -Wait

The run writes kiln-test.log, which ends in a line EXIT=<code>, and the report in <OutDir>\report.

Keep the symbols before you rebuild

A crash dump is readable only with the program database that matches the binary that wrote it. A rebuild replaces that file, and every dump already on the machine becomes unreadable. Archive first:

powershell -File integration\cicd\targets\windows-msvc-x64\shared\archive-symbols.ps1 ^
    -BuildRoot C:\Users\you\brelmain\release -Name construct

The script writes the executable and its program database into a folder named for the time, and prints the folder. Point the debugger at that folder when you read an old dump.

Snapshot management

./snapshot-list.sh                  # list snapshots
./snapshot-save.sh post-qt-install  # save current state
./snapshot-restore.sh baseline      # revert to Windows + SSH

The baseline snapshot is created automatically by setup-windows.sh after Windows + SSH are up but before any provisioning, so iterative provisioning fixes are cheap.


Troubleshooting

SSH connection fails

docker compose ps                          # is the container up?
docker compose logs windows-builder        # last 100 lines
ssh -v -p 2223 Docker@localhost            # verbose handshake

Watch the VNC console at http://localhost:8006 to see whether Windows has finished booting.

openssl/evp.h: No such file or directory

CryptoStream is being pulled in. Either:

  • Build with oc_use_windows_fast.json (which sets OC_DISABLE_CRYPTOSTREAM), or
  • See Remaining work below — the unconditional include needs a Windows path.

fork is not declared

You hit NodeFactory::forkNodeProcess. It is not yet portable; see Remaining work. Feature-gate the calling product or stub the spawn.

Provisioning is slow / fails partway

cat integration/cicd/targets/windows-msvc-x64/shared/provision.log
./snapshot-restore.sh baseline
./winexec.sh provision

Reset everything

docker compose down
rm -rf storage/        # destroys the Windows install
./setup-windows.sh --fresh

Remaining work

The major source-code and build-system blockers are resolved. What's left is shippability polish — signing, MSIX, and a few minor items.

Source-code clean-up

  • [x] CryptoStream OpenSSL dependency → migrated to mbedtls_hkdf.
  • [x] NodeFactory POSIX fork() → platform::process::spawnDetached (libplatform).
  • [x] FsChmodTask POSIX chmod() → already uses QFile::setPermissions().
  • [ ] getppid() audit. Find any remaining call sites; the Windows replacement walks CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).

Build-system polish

  • [ ] vcpkg integration. No qbs-level vcpkg discovery exists today. Optional: only matters if a future product needs a vcpkg-supplied library (OpenSSL, libsodium, etc.).

Code signing

build_qt_project.py produces unsigned artifacts. SmartScreen warns on download until enough users approve through the reputation system, which is friction we can avoid. Three options have been investigated:

  • SignPath.io Foundation — free for OSS projects, runs the signing step in their HSM-backed CI. Requires a public repo + a few-day application turnaround. Lowest operational cost.
  • Self-signed certificate — viable for internal-only distribution but doesn't bypass SmartScreen on user machines.
  • Standard commercial cert (Sectigo / Certum / DigiCert) — clean but $80–$400/year and HSM-bound under post-2023 baseline requirements.

Implementation when chosen: drop a signtool sign /tr ... /td sha256 /fd sha256 ... step between MSI/setup.exe creation and artifact collection in build_qt_project.py:746 (after light.exe produces the MSI).

MSIX packaging

MSIX is Microsoft's current-generation packaging format (replaces ClickOnce + AppX). Investigated; deferred for now. The tools we'd need:

  • makeappx.exe (ships with the Windows SDK already on the build host)
  • AppxManifest.xml template (one-time authoring)
  • Signing — same path as for MSI/setup.exe above

A pilot would wrap the existing stage_dir from build_qt_project.py, authoring the manifest once and re-using the same package layout the MSI build already exercises.

Validation

  • [x] Run the test suite on Windows → MSVC runs under winexec.sh "...qbs run -p tests". (Historically also cross-validated against MinGW-cross under Wine; that route was retired in 2026-08.)

Documentation

  • [ ] Native-Windows quickstart. This doc assumes you drive from Linux. Some contributors will have Windows hardware and want a direct setup story (just the prerequisites + qbs build).

Architecture notes

  • MSVC exclusively. MSVC is the only supported Windows toolchain; the MinGW routes were retired in 2026-08.
  • Static lib-per-component — each src/libs/lib* builds as its own static library; apps link them all. Avoids Windows command-line length limits.
  • No RTTI / no exceptions — disabled project-wide; per-product overrides exist for vendored code.

Builds run at low priority

Every process this build starts is launched through lowprio, a small tool this project builds because Windows ships no nice equivalent. It sets IDLE_PRIORITY_CLASS, which Windows documents as inherited by child processes, so wrapping cl.exe covers everything cl.exe starts.

A cold tree builds lowprio itself first, at normal priority — the wrapper cannot require its own output to exist.

It covers the processor only. Microsoft is explicit that this is not enough on its own for disk-heavy work, so a big link can still be felt.

To disable it for an unattended batch build, or to strengthen it, see Build priority.

See also

Topics
howto windows msvc build qemu docker libvirt