Skip to content
The CNA BibleCNA 0.1.0-alpha.1 Edition

Chapter 62 Windows I: Cross-Compiling from Linux

CNA’s evidenced Windows engineering loop starts on Linux. CMake selects a MinGW target, builds target-triple-specific SDL dependencies, links a PE executable, stages its runtime DLLs, and teaches CTest to invoke that executable through Wine. Each step has an independent failure mode; a successful compiler probe is not yet a runnable Windows program.

62.1 Two MinGW toolchains

cmake/toolchains/mingw-w64.cmake targets Windows x86_64 when the matching compiler exists and otherwise falls back to the i686 triple. It sets C, C++, and resource compilers and constrains CMake package, library, and header searches to the cross root while allowing build-host programs to remain native.

1 cmake -B build-windows \
2 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
3 -DCNA_GRAPHICS_RENDERER=SDL_RENDERER
4 cmake --build build-windows

The fallback is not a fully self-consistent 32-bit identity: the file leaves CMAKE_SYSTEM_PROCESSOR at x86_64 even after selecting the i686 compiler and search root. It can rescue compiler discovery, but it must not be treated as equivalent to the dedicated i686 lane when processor-keyed caches or ABI gates matter.

The separate mingw-w64-i686.cmake is not merely a convenience alias. GLIDE’s native ABI represents a window handle as a 32-bit FxU32, and the available Glide DLL is x86. That toolchain fixes the processor and all compiler names to i686. As the final section shows, it can validate the renderer boundary but cannot link the whole framework.

There is no CNA-owned Android, Emscripten, or Apple toolchain file. Those paths use the NDK or emsdk toolchain externally; Apple requires its native environment. The two checked-in files are specifically the Windows-from-Linux contract.

62.2 Vendored SDL is built for the target, not reused from the host

Before renderer selection, CNA unconditionally calls cna_configure_vendored_sdl(). Its helper performs a nested configure, build, and install of SDL3, SDL3_image, and SDL3_mixer, forwarding the parent’s toolchain. A MinGW build therefore receives Windows libraries and DLLs rather than accidentally linking the Linux host’s SDL.

The cache directory is keyed by system and processor:

1 .sdl-prebuilt-Linux-x86_64/
2 .sdl-prebuilt-Windows-x86_64/
3 .sdl-prebuilt-Android-aarch64/ # expected by one demo, absent at audit
4 .sdl-prebuilt-emscripten/ # absent at audit

The audited machine contains the first two only. The Windows cache records the MinGW toolchain and GNU import libraries such as libSDL3.dll.a; it has no MSVC .lib or .pdb. This is direct evidence of cross-compilation, not native Visual Studio output.

Configure-time dependency builds make first configuration heavier and more stateful, but they also keep the target triple explicit. A stale cache from another ABI should not be renamed into place; it must be regenerated under the intended toolchain.

62.3 Runtime staging is part of a successful build

A PE executable that links against an import library still needs the corresponding DLL at runtime. CNA’s post-build helpers copy SDL3, SDL3_image, and SDL3_mixer beside Windows targets. MinGW test targets link libgcc and libstdc++ statically, then stage libwinpthread-1.dll. Application targets with large RTTI graphs may use the dynamic C++ runtime; a separate helper locates and copies libgcc_s_seh-1.dll, libstdc++-6.dll, and the threading DLL.

Each helper asks the selected compiler where its runtime file lives and falls back to the compiler’s bin directory where appropriate. Failure is a warning that the executable may not run on a clean prefix, rather than a link failure on the build host. Verification must therefore inspect or execute the staged directory, not stop at “target built.”

Renderer-specific dependencies add another layer. DIRECTX8 needs a D8VK d3d8.dll.a; Direct3D renderers need the corresponding Windows SDK import libraries; GLIDE needs an external glide3x.dll only at execution. These are not supplied by the generic MinGW runtime helper.

62.4 CTest must know that PE files are not host executables

GoogleTest discovery in PRE_TEST mode executes CnaTests.exe to enumerate cases. Without an emulator property, Linux attempts to run the PE file natively and discovery fails before any test. CNA sets CMake’s CROSSCOMPILING_EMULATOR property on CnaTests for DirectX1/2/3/5/6/7/8/9/10/11/12 and Direct2D, selecting the matching Wine wrapper.

Discovery and device-free unit tests set each wrapper’s documented skip-gate variable. A bare --gtest_list_tests call creates no Direct3D device and cannot emit a DXVK marker; an engagement gate would falsely reject it. Renderer smoke examples instead put the wrapper in the registered command because those executables really do create the device and should prove the runtime engaged.

1 # Conceptual CMake behavior
2 set_target_properties(CnaTests PROPERTIES
3 CROSSCOMPILING_EMULATOR
4 "env;CNA_D3D9_SKIP_DXVK_GATE=1;scripts/run-wine-dxvk9.sh")

The strict public-API compile check has its own emulator wiring. These two sites are the bridge that makes ordinary ctest understand the cross-built artifacts; per-example commands remain explicit.

62.5 Native MSVC is a separate, unproven lane

Two Windows workflows target windows-latest, activate MSVC, use Ninja, and include tests. Both are manual workflow_dispatch workflows. The repository cites no Actions run ID for them, and CNA production code has essentially no hand-written MSVC conditional logic: its only _MSC_VER sites are generated Sokol shader output.

That is not proof that MSVC fails. It means the evidence in this repository establishes MinGW-plus-Wine, not a native Microsoft toolchain or genuine Windows execution. Workflow YAML is executable intent; a recorded run is execution evidence.

62.6 The i686 wall

The 32-bit GLIDE lane reaches farther than a syntax sketch. The renderer passes -fsyntax-only, and a fake DLL client verifies all 39 expected exports under Wine. The full CNA executable cannot link because sharp-runtime’s Int128 and Decimal require GCC/Clang __int128, which i686-w64-mingw32-g++ does not provide. Sharp-runtime explicitly declines to hand-roll 128-bit arithmetic solely for MSVC/32-bit coverage.

GLIDE therefore has source, configuration, partial cross-compile, and an ABI probe, but no full artifact. The renderer’s own syntax can be correct while an unconditional foundation dependency makes the product architecture unreachable. This is exactly why platform status belongs to a layered evidence vector rather than a single build-support checkbox.

Type at least three characters. Results are ranked by how often and where the words occur.