SDL: Build SDL with the static C runtime on Visual Studio

From 5e70ee29cc64ac1f660fff6de65a4f28a1a552b7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 20 Jan 2024 16:40:32 -0800
Subject: [PATCH] Build SDL with the static C runtime on Visual Studio

---
 .github/workflows/msvc.yml                    |  2 -
 CMakeLists.txt                                |  5 +-
 VisualC/SDL/SDL.vcxproj                       | 12 ++---
 .../build_config/SDL_build_config_windows.h   |  6 ++-
 src/stdlib/SDL_memcpy.c                       |  6 +--
 src/stdlib/SDL_memset.c                       |  6 +--
 test/testautomation_stdlib.c                  | 50 ++++++++++++++++---
 7 files changed, 60 insertions(+), 27 deletions(-)

diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml
index 9195a94e6abb..f807a1ce8ede 100644
--- a/.github/workflows/msvc.yml
+++ b/.github/workflows/msvc.yml
@@ -17,8 +17,6 @@ jobs:
         platform:
         - { name: Windows (x64),          flags: -A x64,   project: VisualC/SDL.sln, projectflags: '/p:Platform=x64', artifact: 'SDL-VC-x64' }
         - { name: Windows (x86),          flags: -A Win32, project: VisualC/SDL.sln, projectflags: '/p:Platform=Win32', artifact: 'SDL-VC-x86' }
-        - { name: Windows static VCRT (x64), flags: -A x64 -DSDL_FORCE_STATIC_VCRT=ON, artifact: 'SDL-VC-static-VCRT-x64' }
-        - { name: Windows static VCRT (x86), flags: -A Win32 -DSDL_FORCE_STATIC_VCRT=ON, artifact: 'SDL-VC-static-VCRT-x86' }
         - { name: Windows (clang-cl x64), flags: -T ClangCL -A x64, artifact: 'SDL-clang-cl-x64' }
         - { name: Windows (clang-cl x86), flags: -T ClangCL -A Win32, artifact: 'SDL-clang-cl-x86' }
         - { name: Windows (ARM),          flags: -A ARM, artifact: 'SDL-VC-arm32', notests: true }
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5985005551fa..8c2af335565e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -154,13 +154,12 @@ endif()
 set(SDL_LIBC_DEFAULT ON)
 set(SDL_SYSTEM_ICONV_DEFAULT ON)
 if(WINDOWS)
-  set(SDL_LIBC_DEFAULT OFF)
   set(SDL_SYSTEM_ICONV_DEFAULT OFF)
 endif()
 
 if(MSVC)
-  option(SDL_FORCE_STATIC_VCRT "Force /MT for static VC runtimes" OFF)
-  if(SDL_FORCE_STATIC_VCRT)
+  dep_option(SDL_STATIC_VCRT "Use /MT for static VC runtimes" ON "NOT WINDOWS_STORE" OFF)
+  if(SDL_STATIC_VCRT)
     if(NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY)
       set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
     endif()
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 6a79e1f2e3f0..4c0bea8a13ba 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -119,18 +119,17 @@
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
       <WarningLevel>Level3</WarningLevel>
       <DebugInformationFormat>OldStyle</DebugInformationFormat>
-      <OmitDefaultLibName>true</OmitDefaultLibName>
       <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
       <PrecompiledHeader>Use</PrecompiledHeader>
       <PrecompiledHeaderFile>SDL_internal.h</PrecompiledHeaderFile>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
       <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
     </Link>
@@ -151,18 +150,17 @@
       <BufferSecurityCheck>false</BufferSecurityCheck>
       <WarningLevel>Level3</WarningLevel>
       <DebugInformationFormat>OldStyle</DebugInformationFormat>
-      <OmitDefaultLibName>true</OmitDefaultLibName>
       <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
       <PrecompiledHeader>Use</PrecompiledHeader>
       <PrecompiledHeaderFile>SDL_internal.h</PrecompiledHeaderFile>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
       <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
     </Link>
@@ -187,18 +185,17 @@
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
       <WarningLevel>Level3</WarningLevel>
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <OmitDefaultLibName>true</OmitDefaultLibName>
       <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
       <PrecompiledHeader>Use</PrecompiledHeader>
       <PrecompiledHeaderFile>SDL_internal.h</PrecompiledHeaderFile>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
       <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <OptimizeReferences>true</OptimizeReferences>
@@ -220,18 +217,17 @@
       <BufferSecurityCheck>false</BufferSecurityCheck>
       <WarningLevel>Level3</WarningLevel>
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <OmitDefaultLibName>true</OmitDefaultLibName>
       <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
       <PrecompiledHeader>Use</PrecompiledHeader>
       <PrecompiledHeaderFile>SDL_internal.h</PrecompiledHeaderFile>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
       <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <OptimizeReferences>true</OptimizeReferences>
diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h
index 833d5111d645..96f98fac1d7b 100644
--- a/include/build_config/SDL_build_config_windows.h
+++ b/include/build_config/SDL_build_config_windows.h
@@ -111,7 +111,8 @@ typedef unsigned int uintptr_t;
 # define SDL_DISABLE_AVX 1
 #endif
 
-/* This is disabled by default to avoid C runtime dependencies and manifest requirements */
+/* This can be disabled to avoid C runtime dependencies and manifest requirements */
+#define HAVE_LIBC
 #ifdef HAVE_LIBC
 /* Useful headers */
 #define HAVE_CTYPE_H 1
@@ -162,6 +163,9 @@ typedef unsigned int uintptr_t;
 #define HAVE__WCSICMP 1
 #define HAVE__WCSNICMP 1
 #define HAVE__WCSDUP 1
+#define HAVE_SSCANF 1
+#define HAVE_VSSCANF 1
+#define HAVE_VSNPRINTF 1
 #define HAVE_ACOS   1
 #define HAVE_ASIN   1
 #define HAVE_ATAN   1
diff --git a/src/stdlib/SDL_memcpy.c b/src/stdlib/SDL_memcpy.c
index 456b1d032ac4..139eb420d4f0 100644
--- a/src/stdlib/SDL_memcpy.c
+++ b/src/stdlib/SDL_memcpy.c
@@ -80,8 +80,8 @@ void *SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void
 }
 
 /* The optimizer on Visual Studio 2005 and later generates memcpy() and memset() calls.
-   Always provide it for the SDL3 DLL, but skip it when building static lib w/ static runtime. */
-#if defined(_MSC_VER) && (_MSC_VER >= 1400) && (!defined(_MT) || defined(DLL_EXPORT))
+   We will provide our own implementation if we're not building with a C runtime. */
+#if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(_MT)
 /* NOLINTNEXTLINE(readability-redundant-declaration) */
 extern void *memcpy(void *dst, const void *src, size_t len);
 #ifndef __INTEL_LLVM_COMPILER
@@ -96,4 +96,4 @@ void *memcpy(void *dst, const void *src, size_t len)
 {
     return SDL_memcpy(dst, src, len);
 }
-#endif /* (_MSC_VER >= 1400) && (!defined(_MT) || defined(DLL_EXPORT)) */
+#endif /* (_MSC_VER >= 1400) && !defined(_MT) */
diff --git a/src/stdlib/SDL_memset.c b/src/stdlib/SDL_memset.c
index 30d2d6a57cb1..0c3579f86e2e 100644
--- a/src/stdlib/SDL_memset.c
+++ b/src/stdlib/SDL_memset.c
@@ -117,8 +117,8 @@ void *SDL_memset4(void *dst, Uint32 val, size_t dwords)
 }
 
 /* The optimizer on Visual Studio 2005 and later generates memcpy() and memset() calls.
-   Always provide it for the SDL3 DLL, but skip it when building static lib w/ static runtime. */
-#if defined(_MSC_VER) && (_MSC_VER >= 1400) && (!defined(_MT) || defined(DLL_EXPORT))
+   We will provide our own implementation if we're not building with a C runtime. */
+#if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(_MT)
 /* NOLINTNEXTLINE(readability-redundant-declaration) */
 extern void *memset(void *dst, int c, size_t len);
 #ifndef __INTEL_LLVM_COMPILER
@@ -133,5 +133,5 @@ void *memset(void *dst, int c, size_t len)
 {
     return SDL_memset(dst, c, len);
 }
-#endif /* (_MSC_VER >= 1400) && (!defined(_MT) || defined(DLL_EXPORT)) */
+#endif /* (_MSC_VER >= 1400) && !defined(_MT) */
 
diff --git a/test/testautomation_stdlib.c b/test/testautomation_stdlib.c
index e447cf42ce1d..adf4bb68540b 100644
--- a/test/testautomation_stdlib.c
+++ b/test/testautomation_stdlib.c
@@ -141,7 +141,7 @@ static int stdlib_snprintf(void *arg)
     int result;
     int predicted;
     char text[1024];
-    const char *expected;
+    const char *expected, *expected2, *expected3, *expected4, *expected5;
     size_t size;
 
     result = SDL_snprintf(text, sizeof(text), "%s", "foo");
@@ -310,22 +310,58 @@ static int stdlib_snprintf(void *arg)
 
     result = SDL_snprintf(text, sizeof(text), "%p", (void *)0x1234abcd);
     expected = "0x1234abcd";
+    expected2 = "1234ABCD";
+    expected3 = "000000001234ABCD";
+    expected4 = "1234abcd";
+    expected5 = "000000001234abcd";
     SDLTest_AssertPass("Call to SDL_snprintf(text, sizeof(text), \"%%p\", 0x1234abcd)");
-    SDLTest_AssertCheck(SDL_strcmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text);
-    SDLTest_AssertCheck(result == SDL_strlen(expected), "Check result value, expected: %d, got: %d", (int)SDL_strlen(expected), result);
+    SDLTest_AssertCheck(SDL_strcmp(text, expected) == 0 ||
+                        SDL_strcmp(text, expected2) == 0 ||
+                        SDL_strcmp(text, expected3) == 0 ||
+                        SDL_strcmp(text, expected4) == 0 ||
+                        SDL_strcmp(text, expected5) == 0,
+        "Check text, expected: '%s', got: '%s'", expected, text);
+    SDLTest_AssertCheck(result == SDL_strlen(expected) ||
+                        result == SDL_strlen(expected2) ||
+                        result == SDL_strlen(expected3) ||
+                        result == SDL_strlen(expected4) ||
+                        result == SDL_strlen(expected5),
+        "Check result value, expected: %d, got: %d", (int)SDL_strlen(expected), result);
 
     result = SDL_snprintf(text, sizeof(text), "A %p B", (void *)0x1234abcd);
     expected = "A 0x1234abcd B";
+    expected2 = "A 1234ABCD B";
+    expected3 = "A 000000001234ABCD B";
+    expected4 = "A 1234abcd B";
+    expected5 = "A 000000001234abcd B";
     SDLTest_AssertPass("Call to SDL_snprintf(text, sizeof(text), \"A %%p B\", 0x1234abcd)");
-    SDLTest_AssertCheck(SDL_strcmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text);
-    SDLTest_AssertCheck(result == SDL_strlen(expected), "Check result value, expected: %d, got: %d", (int)SDL_strlen(expected), result);
+    SDLTest_AssertCheck(SDL_strcmp(text, expected) == 0 ||
+                        SDL_strcmp(text, expected2) == 0 ||
+                        SDL_strcmp(text, expected3) == 0 ||
+                        SDL_strcmp(text, expected4) == 0 ||
+                        SDL_strcmp(text, expected5) == 0,
+        "Check text, expected: '%s', got: '%s'", expected, text);
+    SDLTest_AssertCheck(result == SDL_strlen(expected) ||
+                        result == SDL_strlen(expected2) ||
+                        result == SDL_strlen(expected3) ||
+                        result == SDL_strlen(expected4) ||
+                        result == SDL_strlen(expected5),
+        "Check result value, expected: %d, got: %d", (int)SDL_strlen(expected), result);
 
     if (sizeof(void *) >= 8) {
         result = SDL_snprintf(text, sizeof(text), "%p", (void *)0x1ba07bddf60L);
         expected = "0x1ba07bddf60";
+        expected2 = "000001BA07BDDF60";
+        expected3 = "000001ba07bddf60";
         SDLTest_AssertPass("Call to SDL_snprintf(text, sizeof(text), \"%%p\", 0x1ba07bddf60)");
-        SDLTest_AssertCheck(SDL_strcmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text);
-        SDLTest_AssertCheck(result == SDL_strlen(expected), "Check result value, expected: %d, got: %d", (int)SDL_strlen(expected), result);
+        SDLTest_AssertCheck(SDL_strcmp(text, expected) == 0 ||
+                            SDL_strcmp(text, expected2) == 0 ||
+                            SDL_strcmp(text, expected3) == 0,
+            "Check text, expected: '%s', got: '%s'", expected, text);
+        SDLTest_AssertCheck(result == SDL_strlen(expected) ||
+                            result == SDL_strlen(expected2) ||
+                            result == SDL_strlen(expected3),
+            "Check result value, expected: %d, got: %d", (int)SDL_strlen(expected), result);
     }
     return TEST_COMPLETED;
 }