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/windowscontainer. The rest of this page describes it. Its scripts live inintegration/cicd/targets/windows-msvc-x64/; the compose file the farm actually runs isintegration/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.exeon PATH orC:\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:
-
The first
qbs resolvefinds nollvm-src, solibllvmis excluded — but thellvm-sourceproduct still runs during the build and fetches the tree (~252 MB, digest-verified). -
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 buildand 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.ps1gets profileqt6. (qt6101is 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.hwith the symbols exported from Qt6Core, and Qt publishes x64 OpenSSL as thetools_opensslv3_x64package — 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 asoverridesinllvm-pin.json— four MSVC conformance overrides ship there today. See the internal speccontent/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 setsOC_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]
→ migrated toCryptoStreamOpenSSL dependencymbedtls_hkdf. - [x]
→NodeFactoryPOSIXfork()platform::process::spawnDetached(libplatform). - [x]
→ already usesFsChmodTaskPOSIXchmod()QFile::setPermissions(). - [ ]
getppid()audit. Find any remaining call sites; the Windows replacement walksCreateToolhelp32Snapshot(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.xmltemplate (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 underwinexec.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
- Build for Linux desktop — native Linux build
- Build for Android — Android cross-compile
- Qbs Build System — build system internals