Wrangling CRT libraries on Windows

Sometimes when working on my toybox game engine project I find myself needing to contribute to upstream. In this case I wound up implementing mingw support for KTX-Software. It was a bit of a hassle but it was necessary to get my whole engine compiling with gcc on Windows.

I had hoped that the work had been done but lo and behold it never is. Turns out with the latest version of gcc (12.2.0) came build breakages in the KTX-Software CI. Of course I was pinged about this since I was the one who cared. If you want to see a record of my descent into madness trying to fix this I can direct you to the github issue.

The source of the issue was a failure to compile this workaround for a bug in msvcrt, the older C runtime library for Windows:

     // Need to flush so that fstat will return the current size. 
     // Can ignore return value. The only error that can happen is to tell you 
     // it was a NOP because the file is read only. 
 #if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(__MINGW32__) 
     // Bug in VS2013 msvcrt. fflush on FILE open for READ changes file offset 
     // to 4096. 
     if (str->data.file->_flag & _IOWRT) 
 #endif 

The build error is specifically that _flag doesn't exist and _IOWRT is not defined. When we look at the mingw headers to see where _IOWRT is defined you'll see that it's only defined if _UCRT is defined. Which was the first clue that we were suddenly using the ucrt, Microsoft's newer "Universal" C runtime library.

A good tool for wrangling this was MSYS2 as it allows you to install a mingw-w64 toolchain for both the older msvcrt (mingw-w64-x86_64-toolchain) and for the newer ucrt (mingw-w64-x86_64-ucrt-toolchain). Compiling with a gcc from the older toolchain worked just fine, it was only when building with the newer toolchain would this fail to compile.

So just skip the msvcrt runtime workaround when _UCRT is defined. Good enough, right? Not quite, the tests fail when built with ucrt. KTX-Software builds for a lot of platforms so it would be strange if there was a bug specifically in mingw-w64 just when building against the ucrt. It's possible but ucrt has been the default for msvc for a while (since VS2013) and the Visual Studio based builds run their tests just fine. So we're probably just missing a runtime dependency.

Sure enough when using a tool like Dependencies to examine the DLLs required by the test executables it was clear that we were missing libstdc++-6.dll. Copying this dll from my mingw-w64 toolchain next to the test executables was all it took for the tests to pass.

So how do we fix this without copying dlls around, manually or even automatically. I really don't want to have to write a custom CMake target to copy a dll around. How are downstream dependencies supposed to know they need that dll unless it comes in from a CMake target; sounds like a hard problem to solve. Instead I found this Stack Overflow question in my searches and sure enough making sure that the test executables were linked with -static-libgcc and -static-libstdc++ was all it took to gete a working build.

You can find the whole fix over in this PR.