From 2ad60025ab644d81225191cf6ac7f6eb923ab521 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Tue, 30 Jun 2026 18:15:04 -0700 Subject: [PATCH 1/7] Deprecate MSVCR120.dll fallback and replace with the Universal CRT. --- .azure-pipelines/windows-msbuild.bat | 6 +- changelog/dmd.deprecate-non-ucrt-runtime.dd | 25 +++ compiler/src/dmd/cli.d | 16 +- compiler/src/dmd/link.d | 8 +- compiler/src/dmd/main.d | 25 ++- compiler/src/dmd/vsoptions.d | 150 +++++++++++++++--- .../test/dshell/extra-files/mscrtlib_app.d | 3 + compiler/test/dshell/mscrtlib_deprecation.d | 33 ++++ 8 files changed, 232 insertions(+), 34 deletions(-) create mode 100644 changelog/dmd.deprecate-non-ucrt-runtime.dd create mode 100644 compiler/test/dshell/extra-files/mscrtlib_app.d create mode 100644 compiler/test/dshell/mscrtlib_deprecation.d diff --git a/.azure-pipelines/windows-msbuild.bat b/.azure-pipelines/windows-msbuild.bat index 8c8834164b0c..2d8f6f3a6ed8 100644 --- a/.azure-pipelines/windows-msbuild.bat +++ b/.azure-pipelines/windows-msbuild.bat @@ -69,13 +69,15 @@ if not "%C_RUNTIME%" == "mingw" goto not_mingw 7z x mingw.zip -o%DMD_DIR%\mingw || exit /B 14 :mingw_exists - set DFLAGS=-mscrtlib=msvcrt120 + rem -d silences the (intentional) deprecation warning for the msvcrt120/MinGW runtime, + rem which is kept until the MinGW path is removed + set DFLAGS=-d -mscrtlib=msvcrt120 if "%MODEL%" == "32" ( set LIB=%DMD_DIR%\mingw\dmd2\windows\lib32mscoff\mingw ) else ( set LIB=%DMD_DIR%\mingw\dmd2\windows\lib%MODEL%\mingw ) - set REQUIRED_ARGS=-mscrtlib=msvcrt120 "-L/LIBPATH:%LIB%" + set REQUIRED_ARGS=-d -mscrtlib=msvcrt120 "-L/LIBPATH:%LIB%" rem skip runnable_cxx tests (incompatible MSVC runtime versions - 2017 (cl.exe) vs. 2013) rem FIXME: unit_tests excluded too, see above set DMD_TESTS=runnable compilable fail_compilation dshell diff --git a/changelog/dmd.deprecate-non-ucrt-runtime.dd b/changelog/dmd.deprecate-non-ucrt-runtime.dd new file mode 100644 index 000000000000..5feb883d655d --- /dev/null +++ b/changelog/dmd.deprecate-non-ucrt-runtime.dd @@ -0,0 +1,25 @@ +Non-UCRT C runtimes on Windows are now deprecated + +DMD on Windows now treats the Universal CRT (UCRT) as the only first-class C runtime. + +The default C runtime is unchanged: it remains $(TT libcmt), which links against the +Universal CRT shipped with Visual Studio 2015 (or later) and the Windows 10 SDK. + +The following are now deprecated and will be removed in a future release: + +$(UL + $(LI The $(TT msvcrt120) / MinGW replacement runtime that was selected automatically + when no Visual C installation was found. Selecting it, either automatically or via + $(TT -mscrtlib=msvcrt120), now emits a deprecation warning.) + $(LI Detection of Visual Studio versions older than 2015 (2008 - 2013), which predate + the Universal CRT. Falling back on such an installation now emits a deprecation + warning.) +) + +In addition, if no UCRT-capable toolchain is detected and no $(TT -mscrtlib) switch is +given, DMD now reports a clear error instead of silently falling back on the MinGW +runtime. + +The $(TT -mscrtlib) switch is otherwise unchanged and can still be used to select any C +runtime, including UCRT-based alternatives such as $(TT msvcrt) (dynamic) or the debug +variants $(TT libcmtd) / $(TT msvcrtd). diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index f19b3d227776..75f3711a12cd 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -725,13 +725,15 @@ dmd -cov -unittest myprog.d "If building MS-COFF object files when targeting Windows, embed a reference to the given C runtime library $(I libname) into the object file containing `main`, `DllMain` or `WinMain` for automatic linking. The default is $(TT libcmt) - (release version with static linkage), the other usual alternatives are - $(TT libcmtd), $(TT msvcrt) and $(TT msvcrtd). - If no Visual C installation is detected, a wrapper for the redistributable - VC2010 dynamic runtime library and mingw based platform import libraries will - be linked instead using the LLD linker provided by the LLVM project. - The detection can be skipped explicitly if $(TT msvcrt120) is specified as - $(I libname). + (release version with static linkage), which uses the Universal CRT (UCRT) + shipped with Visual Studio 2015 (or later) and the Windows 10 SDK. The other + usual alternatives are $(TT libcmtd), $(TT msvcrt) and $(TT msvcrtd). + $(B Deprecated:) if no Universal CRT capable Visual C installation is detected, + a wrapper for the redistributable VC2010 dynamic runtime library and mingw + based platform import libraries ($(TT msvcrt120)) is linked instead using the + LLD linker provided by the LLVM project. This fallback is deprecated and will + be removed in a future release; install Visual Studio 2015 or later, or the + Windows SDK with the Universal CRT. If $(I libname) is empty, no C runtime library is automatically linked in.", TargetOS.Windows, ), diff --git a/compiler/src/dmd/link.d b/compiler/src/dmd/link.d index 0ed06272c4da..0081145ab5d5 100644 --- a/compiler/src/dmd/link.d +++ b/compiler/src/dmd/link.d @@ -286,7 +286,8 @@ public int runLINK(bool verbose, ErrorSink eSink) } VSOptions vsopt; - // if a runtime library (msvcrtNNN.lib) from the mingw folder is selected explicitly, do not detect VS and use lld + // Deprecated: if a MinGW replacement runtime (msvcrtNNN.lib, e.g. msvcrt120) + // is selected explicitly, do not detect VS and use lld with the MinGW libraries. if (driverParams.mscrtlib.length <= 6 || driverParams.mscrtlib[0..6] != "msvcrt" || !isdigit(driverParams.mscrtlib[6])) vsopt.initialize(); @@ -301,9 +302,8 @@ public int runLINK(bool verbose, ErrorSink eSink) { // object files not SAFESEH compliant, but LLD is more picky than MS link cmdbuf.writestring(" /SAFESEH:NO"); - // if we are using LLD as a fallback, don't link to any VS libs even if - // we detected a VS installation and they are present - vsopt.uninitialize(); + // lld-link is used here as a generic linker fallback; keep any detected + // VS/UCRT library paths so the Universal CRT can still be linked. } if (const(char)* lflags = vsopt.linkOptions(target.isX86_64)) diff --git a/compiler/src/dmd/main.d b/compiler/src/dmd/main.d index 2040519492cc..7583154c27ae 100644 --- a/compiler/src/dmd/main.d +++ b/compiler/src/dmd/main.d @@ -1194,7 +1194,20 @@ void reconcileLinkRunLib(ref Param params, size_t numSrcFiles, const char[] obj_ { VSOptions vsopt; vsopt.initialize(); - driverParams.mscrtlib = vsopt.defaultRuntimeLibrary(target.isX86_64).toDString; + if (const rtlib = vsopt.defaultRuntimeLibrary(target.isX86_64)) + driverParams.mscrtlib = rtlib.toDString; + else + { + // No UCRT-capable Visual C installation (VS2015+ or the Windows SDK + // with the Universal CRT) and no MinGW fallback libraries were found. + if (driverParams.link) + eSink.error(Loc.initial, "no compatible C runtime found; install Visual Studio 2015 or later, or the Windows SDK with the Universal CRT, or specify the runtime with `-mscrtlib`"); + // still embed a name in the object file so it can be linked elsewhere + driverParams.mscrtlib = "libcmt"; + } + + if (vsopt.usedDeprecatedVSVersion) + eSink.deprecation(Loc.initial, "Visual Studio versions prior to 2015 are deprecated because they lack the Universal CRT (UCRT); install Visual Studio 2015 or later"); } else { @@ -1202,6 +1215,16 @@ void reconcileLinkRunLib(ref Param params, size_t numSrcFiles, const char[] obj_ eSink.error(Loc.initial, "must supply `-mscrtlib` manually when cross compiling to windows"); } } + + // Deprecated: the MinGW replacement runtime (msvcrtNNN, e.g. msvcrt120) is not + // UCRT-based and is deprecated in favour of the Universal CRT. + if (driverParams.mscrtlib.length > 6 && + driverParams.mscrtlib[0 .. 6] == "msvcrt" && + driverParams.mscrtlib[6] >= '0' && driverParams.mscrtlib[6] <= '9') + { + eSink.deprecation(Loc.initial, "the `%.*s` C runtime is deprecated; use a UCRT-based runtime (e.g. `libcmt` or `msvcrt`) or install Visual Studio 2015 or later", + cast(int) driverParams.mscrtlib.length, driverParams.mscrtlib.ptr); + } } if (driverParams.link) diff --git a/compiler/src/dmd/vsoptions.d b/compiler/src/dmd/vsoptions.d index 24a6c02a3c8a..f8adf1323690 100644 --- a/compiler/src/dmd/vsoptions.d +++ b/compiler/src/dmd/vsoptions.d @@ -27,7 +27,13 @@ import dmd.common.outbuffer; import dmd.root.rmem; import dmd.root.string : toDString; -private immutable supportedPre2017Versions = ["14.0", "12.0", "11.0", "10.0", "9.0"]; +// VS2015 (14.0) is the oldest version that ships the Universal CRT (UCRT) +private immutable supportedPre2017Versions = ["14.0"]; + +// Visual Studio versions older than 2015 predate the UCRT. Detecting them is +// deprecated and will be removed in a future release; kept temporarily so existing +// setups keep working (with a deprecation warning). +private immutable deprecatedPre2015Versions = ["12.0", "11.0", "10.0", "9.0"]; extern(C++) struct VSOptions { @@ -41,6 +47,10 @@ extern(C++) struct VSOptions const(char)* VCInstallDir; const(char)* VCToolsInstallDir; // used by VS 2017+ + // set to true if detection fell back on a Visual Studio version older than 2015, + // which predates the Universal CRT (deprecated) + bool usedDeprecatedVSVersion; + /** * fill member variables from environment or registry */ @@ -66,6 +76,7 @@ extern(C++) struct VSOptions VSInstallDir = null; VCInstallDir = null; VCToolsInstallDir = null; + usedDeprecatedVSVersion = false; } /** @@ -73,7 +84,9 @@ extern(C++) struct VSOptions * Params: * x64 = target architecture (x86 if false) * Returns: - * name of the default C runtime library + * name of the default C runtime library, or null if no C runtime could be + * detected (neither a UCRT-capable Visual C installation nor the deprecated + * MinGW fallback libraries) */ const(char)* defaultRuntimeLibrary(bool x64) { @@ -83,9 +96,16 @@ extern(C++) struct VSOptions detectVCToolsInstallDir(); } if (getVCLibDir(x64)) - return "libcmt"; - else - return "msvcrt120"; // mingw replacement + return "libcmt"; // UCRT-based static C runtime (VS2015+) + + // Deprecated: fall back on the MinGW import libraries (msvcrt120) only when they + // are actually available on the LIB search path. This fallback is deprecated in + // favour of the Universal CRT and will be removed in a future release. + if (getMingwLibPath()) + return "msvcrt120"; + + // No usable C runtime detected. + return null; } /** @@ -283,15 +303,41 @@ private: VSInstallDir = detectVSInstallDirViaCOM(); if (VSInstallDir is null) - VSInstallDir = GetRegistryString(r"Microsoft\VisualStudio\SxS\VS7", "15.0"w); // VS2017 + { + // VS2017+ register their install dir under SxS\VS7 keyed by major version. + // Try newest first: VS2026 (18.0), VS2022 (17.0), VS2019 (16.0), VS2017 (15.0). + static immutable wstring[] vs7Versions = ["18.0"w, "17.0"w, "16.0"w, "15.0"w]; + foreach (ver; vs7Versions) + { + VSInstallDir = GetRegistryString(r"Microsoft\VisualStudio\SxS\VS7", ver); + if (VSInstallDir) + break; + } + } if (VSInstallDir is null) { - wchar[260] buffer = void; - // VS Build Tools 2017 (default installation path) - const numWritten = ExpandEnvironmentStringsW(r"%ProgramFiles(x86)%\Microsoft Visual Studio\2017\BuildTools"w.ptr, buffer.ptr, buffer.length); - if (numWritten <= buffer.length && exists(buffer.ptr)) - VSInstallDir = toNarrowStringz(buffer[0 .. numWritten - 1]).ptr; + // VS Build Tools default installation paths, newest first. + // VS2022/VS2026 are 64-bit and install under %ProgramFiles%; older ones under %ProgramFiles(x86)%. + static immutable wstring[] buildToolsPaths = + [ + r"%ProgramFiles%\Microsoft Visual Studio\18\BuildTools"w, + r"%ProgramFiles(x86)%\Microsoft Visual Studio\18\BuildTools"w, + r"%ProgramFiles%\Microsoft Visual Studio\2022\BuildTools"w, + r"%ProgramFiles(x86)%\Microsoft Visual Studio\2022\BuildTools"w, + r"%ProgramFiles(x86)%\Microsoft Visual Studio\2019\BuildTools"w, + r"%ProgramFiles(x86)%\Microsoft Visual Studio\2017\BuildTools"w, + ]; + foreach (path; buildToolsPaths) + { + wchar[260] buffer = void; + const numWritten = ExpandEnvironmentStringsW(path.ptr, buffer.ptr, buffer.length); + if (numWritten > 0 && numWritten <= buffer.length && exists(buffer.ptr)) + { + VSInstallDir = toNarrowStringz(buffer[0 .. numWritten - 1]).ptr; + break; + } + } } if (VSInstallDir is null) @@ -301,6 +347,18 @@ private: if (VSInstallDir) break; } + + // Deprecated: Visual Studio versions older than 2015 predate the Universal CRT. + if (VSInstallDir is null) + foreach (ver; deprecatedPre2015Versions) + { + VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir"w); + if (VSInstallDir) + { + usedDeprecatedVSVersion = true; + break; + } + } } /** @@ -325,6 +383,20 @@ private: if (VCInstallDir) break; } + + // Deprecated: Visual Studio versions older than 2015 predate the Universal CRT. + if (VCInstallDir is null) + foreach (ver; deprecatedPre2015Versions) + { + auto regPath = FileName.buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC"); + VCInstallDir = GetRegistryString(regPath, "ProductDir"w); + FileName.free(regPath.ptr); + if (VCInstallDir) + { + usedDeprecatedVSVersion = true; + break; + } + } } /** @@ -339,7 +411,9 @@ private: { const(char)* defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); if (!FileName.exists(defverFile)) // file renamed with VS2019 Preview 2 - defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.v142.default.txt"); + defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt"); // VS2022 + if (!FileName.exists(defverFile)) + defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.v142.default.txt"); // VS2019 if (FileName.exists(defverFile)) { // VS 2017 @@ -497,6 +571,20 @@ public: return null; } + /** + * Deprecated: get the path to the bundled MinGW import libraries, if present on + * the `LIB` search path. Used only as a last-resort fallback when no Windows SDK + * is detected; this fallback is deprecated in favour of the Universal CRT. + * Returns: + * folder containing the MinGW import libraries, or null + */ + const(char)* getMingwLibPath() const + { + if (auto p = FileName.searchPath(getenv("LIB"w), r"mingw\kernel32.lib"[], false)) + return FileName.path(p).ptr; + return null; + } + /** * get the path to the Windows SDK CRT libraries * Params: @@ -533,9 +621,9 @@ public: } } - // try mingw fallback relative to phobos library folder that's part of LIB - if (auto p = FileName.searchPath(getenv("LIB"w), r"mingw\kernel32.lib"[], false)) - return FileName.path(p).ptr; + // Deprecated: try the bundled MinGW import libraries as a last resort. + if (auto p = getMingwLibPath()) + return p; return null; } @@ -567,6 +655,31 @@ public: private: extern(D): + /** + * Compare two dotted numeric version strings (e.g. `"10.0.22621.0"`). + * Missing or non-numeric components are treated as 0, so the comparison is by + * numeric value rather than lexicographic order (which breaks when component + * widths differ, e.g. `"10.0.9.0"` vs `"10.0.22000.0"`). + * Returns: negative if `a < b`, 0 if equal, positive if `a > b` + */ + static int compareVersionStrings(C)(const(C)* a, const(C)* b) + if (is(C == char) || is(C == wchar)) + { + for (;;) + { + uint na = 0, nb = 0; + bool hasA, hasB; + while (*a >= '0' && *a <= '9') { na = na * 10 + cast(uint)(*a - '0'); ++a; hasA = true; } + while (*b >= '0' && *b <= '9') { nb = nb * 10 + cast(uint)(*b - '0'); ++b; hasB = true; } + if (na != nb) + return na < nb ? -1 : 1; + if (!hasA && !hasB) + return 0; + if (*a) ++a; // skip separator (typically '.') + if (*b) ++b; + } + } + // iterate through subdirectories named by SDK version in baseDir and return the // one with the largest version that also contains the test file static const(char)* findLatestSDKDir(const(char)* baseDir, string testfile) @@ -592,8 +705,7 @@ extern(D): if (fileinfo.cFileName[0] >= '1' && fileinfo.cFileName[0] <= '9') { char[] name = toNarrowStringz(fileinfo.cFileName.ptr.toDString); - // FIXME: proper version strings comparison - if ((!res || strcmp(res, name.ptr) < 0) && + if ((!res || compareVersionStrings(res, name.ptr) < 0) && FileName.exists(FileName.buildPath(dBaseDir, name, testfile))) { if (res) @@ -825,9 +937,7 @@ const(char)* detectVSInstallDirViaCOM() instance.GetInstallationPath(&thisInstallDir.ptr) != S_OK) continue; - // FIXME: proper version strings comparison - // existing versions are 15.0 to 16.5 (May 2020), problems expected in distant future - if (versionString && wcscmp(thisVersionString.ptr, versionString.ptr) <= 0) + if (versionString && VSOptions.compareVersionStrings(thisVersionString.ptr, versionString.ptr) <= 0) continue; // not a newer version, skip const installDirLength = thisInstallDir.length; diff --git a/compiler/test/dshell/extra-files/mscrtlib_app.d b/compiler/test/dshell/extra-files/mscrtlib_app.d new file mode 100644 index 000000000000..d0ac71697685 --- /dev/null +++ b/compiler/test/dshell/extra-files/mscrtlib_app.d @@ -0,0 +1,3 @@ +module compiler.test.dshell.extra-files.mscrtlib_app; + +void main() {} diff --git a/compiler/test/dshell/mscrtlib_deprecation.d b/compiler/test/dshell/mscrtlib_deprecation.d new file mode 100644 index 000000000000..2d6244c2dc95 --- /dev/null +++ b/compiler/test/dshell/mscrtlib_deprecation.d @@ -0,0 +1,33 @@ +module compiler.test.dshell.mscrtlib_deprecation; + +// Selecting the deprecated MinGW replacement runtime (msvcrt120) must emit a +// deprecation warning. See the deprecation of non-UCRT C runtimes on Windows. +import dshell; + +int main() +{ + // This deprecation only applies when targeting Windows. + version (Windows) {} else + return DISABLED; + + // The dedicated MinGW CI job compiles with `-d` (deprecations silenced) and + // `-mscrtlib=msvcrt120` globally, which would hide the warning checked here. + if (environment.get("C_RUNTIME", "") == "mingw") + return DISABLED; + + const errFile = shellExpand("$OUTPUT_BASE.stderr"); + mkdirFor(errFile); + + // Compile only (-c) so neither a linker nor the MinGW import libraries are + // required; the deprecation is emitted regardless of linking. + { + auto err = File(errFile, "w"); + tryRun("$DMD -m$MODEL -c -of$OUTPUT_BASE$OBJ -mscrtlib=msvcrt120 $EXTRA_FILES${SEP}mscrtlib_app.d", + stdout, err); + err.close(); + } + + grep(errFile, "msvcrt120").enforceMatches("expected a deprecation warning for -mscrtlib=msvcrt120"); + + return 0; +} From ef887978fdac367001a9b54e0f930ea5a14ea7b2 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Tue, 30 Jun 2026 18:47:31 -0700 Subject: [PATCH 2/7] Add deprecation comments. --- compiler/src/dmd/main.d | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/src/dmd/main.d b/compiler/src/dmd/main.d index 7583154c27ae..61771712f5d7 100644 --- a/compiler/src/dmd/main.d +++ b/compiler/src/dmd/main.d @@ -1206,6 +1206,9 @@ void reconcileLinkRunLib(ref Param params, size_t numSrcFiles, const char[] obj_ driverParams.mscrtlib = "libcmt"; } + // @@@ Deprecated v2.117 + // Deprecated in 2.113 + // Remove this when the feature is removed from the language if (vsopt.usedDeprecatedVSVersion) eSink.deprecation(Loc.initial, "Visual Studio versions prior to 2015 are deprecated because they lack the Universal CRT (UCRT); install Visual Studio 2015 or later"); } @@ -1216,6 +1219,9 @@ void reconcileLinkRunLib(ref Param params, size_t numSrcFiles, const char[] obj_ } } + // @@@ Deprecated v2.117 + // Deprecated in 2.113 + // Remove this when the feature is removed from the language // Deprecated: the MinGW replacement runtime (msvcrtNNN, e.g. msvcrt120) is not // UCRT-based and is deprecated in favour of the Universal CRT. if (driverParams.mscrtlib.length > 6 && From b52fe8c6cb865cc468dc8de2814e4826fa138968 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Tue, 30 Jun 2026 19:10:39 -0700 Subject: [PATCH 3/7] Fix broken test by making the deprecation message only appear when msvcrt120 is selected automatically, allowing the user to still explicitly override the with that library. --- .azure-pipelines/windows-msbuild.bat | 6 ++-- changelog/dmd.deprecate-non-ucrt-runtime.dd | 7 ++-- compiler/src/dmd/main.d | 24 +++++++------- .../test/dshell/extra-files/mscrtlib_app.d | 3 -- compiler/test/dshell/mscrtlib_deprecation.d | 33 ------------------- 5 files changed, 17 insertions(+), 56 deletions(-) delete mode 100644 compiler/test/dshell/extra-files/mscrtlib_app.d delete mode 100644 compiler/test/dshell/mscrtlib_deprecation.d diff --git a/.azure-pipelines/windows-msbuild.bat b/.azure-pipelines/windows-msbuild.bat index 2d8f6f3a6ed8..8c8834164b0c 100644 --- a/.azure-pipelines/windows-msbuild.bat +++ b/.azure-pipelines/windows-msbuild.bat @@ -69,15 +69,13 @@ if not "%C_RUNTIME%" == "mingw" goto not_mingw 7z x mingw.zip -o%DMD_DIR%\mingw || exit /B 14 :mingw_exists - rem -d silences the (intentional) deprecation warning for the msvcrt120/MinGW runtime, - rem which is kept until the MinGW path is removed - set DFLAGS=-d -mscrtlib=msvcrt120 + set DFLAGS=-mscrtlib=msvcrt120 if "%MODEL%" == "32" ( set LIB=%DMD_DIR%\mingw\dmd2\windows\lib32mscoff\mingw ) else ( set LIB=%DMD_DIR%\mingw\dmd2\windows\lib%MODEL%\mingw ) - set REQUIRED_ARGS=-d -mscrtlib=msvcrt120 "-L/LIBPATH:%LIB%" + set REQUIRED_ARGS=-mscrtlib=msvcrt120 "-L/LIBPATH:%LIB%" rem skip runnable_cxx tests (incompatible MSVC runtime versions - 2017 (cl.exe) vs. 2013) rem FIXME: unit_tests excluded too, see above set DMD_TESTS=runnable compilable fail_compilation dshell diff --git a/changelog/dmd.deprecate-non-ucrt-runtime.dd b/changelog/dmd.deprecate-non-ucrt-runtime.dd index 5feb883d655d..05925256936d 100644 --- a/changelog/dmd.deprecate-non-ucrt-runtime.dd +++ b/changelog/dmd.deprecate-non-ucrt-runtime.dd @@ -8,9 +8,10 @@ Universal CRT shipped with Visual Studio 2015 (or later) and the Windows 10 SDK. The following are now deprecated and will be removed in a future release: $(UL - $(LI The $(TT msvcrt120) / MinGW replacement runtime that was selected automatically - when no Visual C installation was found. Selecting it, either automatically or via - $(TT -mscrtlib=msvcrt120), now emits a deprecation warning.) + $(LI The $(TT msvcrt120) / MinGW replacement runtime that is selected automatically + when no Visual C installation is found. This automatic fallback now emits a + deprecation warning. Selecting it explicitly with $(TT -mscrtlib=msvcrt120) is + still honored without a warning.) $(LI Detection of Visual Studio versions older than 2015 (2008 - 2013), which predate the Universal CRT. Falling back on such an installation now emits a deprecation warning.) diff --git a/compiler/src/dmd/main.d b/compiler/src/dmd/main.d index 61771712f5d7..6714f75d7ac2 100644 --- a/compiler/src/dmd/main.d +++ b/compiler/src/dmd/main.d @@ -1195,7 +1195,18 @@ void reconcileLinkRunLib(ref Param params, size_t numSrcFiles, const char[] obj_ VSOptions vsopt; vsopt.initialize(); if (const rtlib = vsopt.defaultRuntimeLibrary(target.isX86_64)) + { driverParams.mscrtlib = rtlib.toDString; + + // @@@ Deprecated v2.117 + // Deprecated in 2.113 + // Remove this when the feature is removed from the language + // The automatic MinGW (msvcrt120) fallback is selected only when no + // UCRT-capable Visual C installation is found. Explicit selection via + // `-mscrtlib=msvcrt120` is intentionally left without a warning. + if (driverParams.mscrtlib == "msvcrt120") + eSink.deprecation(Loc.initial, "no UCRT-capable Visual C installation was found; falling back on the deprecated MinGW runtime `msvcrt120`; install Visual Studio 2015 or later, or specify a runtime with `-mscrtlib`"); + } else { // No UCRT-capable Visual C installation (VS2015+ or the Windows SDK @@ -1218,19 +1229,6 @@ void reconcileLinkRunLib(ref Param params, size_t numSrcFiles, const char[] obj_ eSink.error(Loc.initial, "must supply `-mscrtlib` manually when cross compiling to windows"); } } - - // @@@ Deprecated v2.117 - // Deprecated in 2.113 - // Remove this when the feature is removed from the language - // Deprecated: the MinGW replacement runtime (msvcrtNNN, e.g. msvcrt120) is not - // UCRT-based and is deprecated in favour of the Universal CRT. - if (driverParams.mscrtlib.length > 6 && - driverParams.mscrtlib[0 .. 6] == "msvcrt" && - driverParams.mscrtlib[6] >= '0' && driverParams.mscrtlib[6] <= '9') - { - eSink.deprecation(Loc.initial, "the `%.*s` C runtime is deprecated; use a UCRT-based runtime (e.g. `libcmt` or `msvcrt`) or install Visual Studio 2015 or later", - cast(int) driverParams.mscrtlib.length, driverParams.mscrtlib.ptr); - } } if (driverParams.link) diff --git a/compiler/test/dshell/extra-files/mscrtlib_app.d b/compiler/test/dshell/extra-files/mscrtlib_app.d deleted file mode 100644 index d0ac71697685..000000000000 --- a/compiler/test/dshell/extra-files/mscrtlib_app.d +++ /dev/null @@ -1,3 +0,0 @@ -module compiler.test.dshell.extra-files.mscrtlib_app; - -void main() {} diff --git a/compiler/test/dshell/mscrtlib_deprecation.d b/compiler/test/dshell/mscrtlib_deprecation.d deleted file mode 100644 index 2d6244c2dc95..000000000000 --- a/compiler/test/dshell/mscrtlib_deprecation.d +++ /dev/null @@ -1,33 +0,0 @@ -module compiler.test.dshell.mscrtlib_deprecation; - -// Selecting the deprecated MinGW replacement runtime (msvcrt120) must emit a -// deprecation warning. See the deprecation of non-UCRT C runtimes on Windows. -import dshell; - -int main() -{ - // This deprecation only applies when targeting Windows. - version (Windows) {} else - return DISABLED; - - // The dedicated MinGW CI job compiles with `-d` (deprecations silenced) and - // `-mscrtlib=msvcrt120` globally, which would hide the warning checked here. - if (environment.get("C_RUNTIME", "") == "mingw") - return DISABLED; - - const errFile = shellExpand("$OUTPUT_BASE.stderr"); - mkdirFor(errFile); - - // Compile only (-c) so neither a linker nor the MinGW import libraries are - // required; the deprecation is emitted regardless of linking. - { - auto err = File(errFile, "w"); - tryRun("$DMD -m$MODEL -c -of$OUTPUT_BASE$OBJ -mscrtlib=msvcrt120 $EXTRA_FILES${SEP}mscrtlib_app.d", - stdout, err); - err.close(); - } - - grep(errFile, "msvcrt120").enforceMatches("expected a deprecation warning for -mscrtlib=msvcrt120"); - - return 0; -} From 50acd4a303c25701442aed0281eb72c26f5dbe3e Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Wed, 1 Jul 2026 01:29:59 -0700 Subject: [PATCH 4/7] Use DMD provided MinGW ucrtbase.lib as a fallback instead msvcrt120. Improved VC tool-chain detection. --- changelog/dmd.deprecate-non-ucrt-runtime.dd | 26 ------ changelog/dmd.ucrt-runtime.dd | 22 +++++ compiler/src/dmd/cli.d | 10 +-- compiler/src/dmd/link.d | 16 +++- compiler/src/dmd/main.d | 11 --- compiler/src/dmd/vsoptions.d | 95 ++++++++++++++++++--- compiler/test/compilable/traits.d | 3 +- 7 files changed, 121 insertions(+), 62 deletions(-) delete mode 100644 changelog/dmd.deprecate-non-ucrt-runtime.dd create mode 100644 changelog/dmd.ucrt-runtime.dd diff --git a/changelog/dmd.deprecate-non-ucrt-runtime.dd b/changelog/dmd.deprecate-non-ucrt-runtime.dd deleted file mode 100644 index 05925256936d..000000000000 --- a/changelog/dmd.deprecate-non-ucrt-runtime.dd +++ /dev/null @@ -1,26 +0,0 @@ -Non-UCRT C runtimes on Windows are now deprecated - -DMD on Windows now treats the Universal CRT (UCRT) as the only first-class C runtime. - -The default C runtime is unchanged: it remains $(TT libcmt), which links against the -Universal CRT shipped with Visual Studio 2015 (or later) and the Windows 10 SDK. - -The following are now deprecated and will be removed in a future release: - -$(UL - $(LI The $(TT msvcrt120) / MinGW replacement runtime that is selected automatically - when no Visual C installation is found. This automatic fallback now emits a - deprecation warning. Selecting it explicitly with $(TT -mscrtlib=msvcrt120) is - still honored without a warning.) - $(LI Detection of Visual Studio versions older than 2015 (2008 - 2013), which predate - the Universal CRT. Falling back on such an installation now emits a deprecation - warning.) -) - -In addition, if no UCRT-capable toolchain is detected and no $(TT -mscrtlib) switch is -given, DMD now reports a clear error instead of silently falling back on the MinGW -runtime. - -The $(TT -mscrtlib) switch is otherwise unchanged and can still be used to select any C -runtime, including UCRT-based alternatives such as $(TT msvcrt) (dynamic) or the debug -variants $(TT libcmtd) / $(TT msvcrtd). diff --git a/changelog/dmd.ucrt-runtime.dd b/changelog/dmd.ucrt-runtime.dd new file mode 100644 index 000000000000..4fcff5c3e516 --- /dev/null +++ b/changelog/dmd.ucrt-runtime.dd @@ -0,0 +1,22 @@ +UCRT is now used for the C runtime on Windows + +DMD on Windows now uses the Universal CRT (UCRT) for its C runtime in all cases. + +For installations with Visual Studio 2015 (or later) or the Windows 10 SDK, the default +C runtime is unchanged: it remains $(TT libcmt), which statically links the UCRT. + +When no such Visual C installation is found, DMD now falls back on the UCRT-based +libraries bundled in the MinGW folder shipped with DMD ($(TT ucrtbase.lib) together with +$(TT vcruntime140.lib)), instead of the old $(TT msvcrt120) runtime which did not support +modern format specifiers such as `%zd`. This fallback is fully supported. + +If no UCRT-capable toolchain is detected and no $(TT -mscrtlib) switch is given, DMD now +reports a clear error instead of silently producing a non-working link. + +Detection of Visual Studio versions older than 2015 (2008 - 2013) is deprecated, because +those versions predate the UCRT. Falling back on such an installation emits a deprecation +warning. + +The $(TT -mscrtlib) switch is unchanged and can still be used to select any C runtime, +including $(TT msvcrt) (dynamic), the debug variants $(TT libcmtd) / $(TT msvcrtd), or a +legacy $(TT msvcrtNNN) runtime. diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index 75f3711a12cd..2f91cde23480 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -728,12 +728,10 @@ dmd -cov -unittest myprog.d (release version with static linkage), which uses the Universal CRT (UCRT) shipped with Visual Studio 2015 (or later) and the Windows 10 SDK. The other usual alternatives are $(TT libcmtd), $(TT msvcrt) and $(TT msvcrtd). - $(B Deprecated:) if no Universal CRT capable Visual C installation is detected, - a wrapper for the redistributable VC2010 dynamic runtime library and mingw - based platform import libraries ($(TT msvcrt120)) is linked instead using the - LLD linker provided by the LLVM project. This fallback is deprecated and will - be removed in a future release; install Visual Studio 2015 or later, or the - Windows SDK with the Universal CRT. + If no Universal CRT capable Visual C installation is detected, the UCRT-based + libraries bundled with DMD (the mingw $(TT ucrtbase.lib) and + $(TT vcruntime140.lib)) are linked instead using the LLD linker provided by the + LLVM project. If $(I libname) is empty, no C runtime library is automatically linked in.", TargetOS.Windows, ), diff --git a/compiler/src/dmd/link.d b/compiler/src/dmd/link.d index 0081145ab5d5..635339a975b8 100644 --- a/compiler/src/dmd/link.d +++ b/compiler/src/dmd/link.d @@ -286,10 +286,13 @@ public int runLINK(bool verbose, ErrorSink eSink) } VSOptions vsopt; - // Deprecated: if a MinGW replacement runtime (msvcrtNNN.lib, e.g. msvcrt120) - // is selected explicitly, do not detect VS and use lld with the MinGW libraries. - if (driverParams.mscrtlib.length <= 6 || - driverParams.mscrtlib[0..6] != "msvcrt" || !isdigit(driverParams.mscrtlib[6])) + // If a MinGW-folder runtime is selected — the UCRT-based `ucrtbase`, or a + // legacy `msvcrtNNN` such as `msvcrt120` — do not detect Visual Studio; use + // lld-link with the MinGW libraries instead. + const isMingwRuntime = driverParams.mscrtlib == "ucrtbase" || + (driverParams.mscrtlib.length > 6 && + driverParams.mscrtlib[0 .. 6] == "msvcrt" && isdigit(driverParams.mscrtlib[6])); + if (!isMingwRuntime) vsopt.initialize(); const(char)* linkcmd = getenv(target.isX86_64 ? "LINKCMD64" : "LINKCMD"); @@ -306,6 +309,11 @@ public int runLINK(bool verbose, ErrorSink eSink) // VS/UCRT library paths so the Universal CRT can still be linked. } + // The UCRT-based MinGW fallback runtime links ucrtbase.lib via the object file's + // /DEFAULTLIB directive; it also needs the VC runtime library. + if (driverParams.mscrtlib == "ucrtbase") + cmdbuf.writestring(" vcruntime140.lib"); + if (const(char)* lflags = vsopt.linkOptions(target.isX86_64)) { cmdbuf.writeByte(' '); diff --git a/compiler/src/dmd/main.d b/compiler/src/dmd/main.d index 6714f75d7ac2..102868c6c8ef 100644 --- a/compiler/src/dmd/main.d +++ b/compiler/src/dmd/main.d @@ -1195,18 +1195,7 @@ void reconcileLinkRunLib(ref Param params, size_t numSrcFiles, const char[] obj_ VSOptions vsopt; vsopt.initialize(); if (const rtlib = vsopt.defaultRuntimeLibrary(target.isX86_64)) - { driverParams.mscrtlib = rtlib.toDString; - - // @@@ Deprecated v2.117 - // Deprecated in 2.113 - // Remove this when the feature is removed from the language - // The automatic MinGW (msvcrt120) fallback is selected only when no - // UCRT-capable Visual C installation is found. Explicit selection via - // `-mscrtlib=msvcrt120` is intentionally left without a warning. - if (driverParams.mscrtlib == "msvcrt120") - eSink.deprecation(Loc.initial, "no UCRT-capable Visual C installation was found; falling back on the deprecated MinGW runtime `msvcrt120`; install Visual Studio 2015 or later, or specify a runtime with `-mscrtlib`"); - } else { // No UCRT-capable Visual C installation (VS2015+ or the Windows SDK diff --git a/compiler/src/dmd/vsoptions.d b/compiler/src/dmd/vsoptions.d index f8adf1323690..e597346afba1 100644 --- a/compiler/src/dmd/vsoptions.d +++ b/compiler/src/dmd/vsoptions.d @@ -85,7 +85,7 @@ extern(C++) struct VSOptions * x64 = target architecture (x86 if false) * Returns: * name of the default C runtime library, or null if no C runtime could be - * detected (neither a UCRT-capable Visual C installation nor the deprecated + * detected (neither a UCRT-capable Visual C installation nor the UCRT-based * MinGW fallback libraries) */ const(char)* defaultRuntimeLibrary(bool x64) @@ -98,11 +98,11 @@ extern(C++) struct VSOptions if (getVCLibDir(x64)) return "libcmt"; // UCRT-based static C runtime (VS2015+) - // Deprecated: fall back on the MinGW import libraries (msvcrt120) only when they - // are actually available on the LIB search path. This fallback is deprecated in - // favour of the Universal CRT and will be removed in a future release. + // Fall back on the UCRT-based libraries bundled in the MinGW folder shipped with + // DMD (ucrtbase.lib + vcruntime140.lib), but only when they are actually available + // on the LIB search path. if (getMingwLibPath()) - return "msvcrt120"; + return "ucrtbase"; // No usable C runtime detected. return null; @@ -410,11 +410,15 @@ private: if (VCToolsInstallDir is null && VCInstallDir) { const(char)* defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); - if (!FileName.exists(defverFile)) // file renamed with VS2019 Preview 2 - defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt"); // VS2022 if (!FileName.exists(defverFile)) - defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.v142.default.txt"); // VS2019 - if (FileName.exists(defverFile)) + { + // The unversioned file was renamed with VS2019 Preview 2. Newer installs + // ship versioned files instead (e.g. Microsoft.VCToolsVersion.v142/v143/v145 + // .default.txt); scan for them and pick the highest version. + const(char)* buildDir = FileName.combine(VCInstallDir, r"Auxiliary\Build"); + defverFile = findLatestVCToolsDefaultFile(buildDir); + } + if (defverFile && FileName.exists(defverFile)) { // VS 2017 OutBuffer buf; @@ -572,11 +576,11 @@ public: } /** - * Deprecated: get the path to the bundled MinGW import libraries, if present on - * the `LIB` search path. Used only as a last-resort fallback when no Windows SDK - * is detected; this fallback is deprecated in favour of the Universal CRT. + * Get the path to the UCRT-based libraries bundled in the MinGW folder shipped with + * DMD (e.g. ucrtbase.lib, vcruntime140.lib, kernel32.lib), if present on the `LIB` + * search path. Used as the fallback runtime when no Visual C installation is detected. * Returns: - * folder containing the MinGW import libraries, or null + * folder containing the MinGW libraries, or null */ const(char)* getMingwLibPath() const { @@ -621,7 +625,7 @@ public: } } - // Deprecated: try the bundled MinGW import libraries as a last resort. + // Fall back on the bundled MinGW libraries as a last resort. if (auto p = getMingwLibPath()) return p; @@ -680,6 +684,69 @@ extern(D): } } + // In the given `Auxiliary\Build` directory, find the + // `Microsoft.VCToolsVersion.vNNN.default.txt` file with the highest NNN and return + // its full path (allocated), or null if none is found. + static const(char)* findLatestVCToolsDefaultFile(const(char)* buildDir) + { + import dmd.common.smallbuffer : SmallBuffer, toWStringz; + + enum prefix = "Microsoft.VCToolsVersion.v"; + enum suffix = ".default.txt"; + + const(char)* pattern = FileName.combine(buildDir, prefix ~ "*" ~ suffix); + wchar[1024] support = void; + auto buf = SmallBuffer!wchar(support.length, support); + wchar* wpattern = toWStringz(pattern.toDString, buf).ptr; + FileName.free(pattern); + + WIN32_FIND_DATAW fileinfo; + HANDLE h = FindFirstFileW(wpattern, &fileinfo); + if (h == INVALID_HANDLE_VALUE) + return null; + + const dBuildDir = buildDir.toDString; + + char* bestName; // highest-versioned file name found so far + uint bestVer; + do + { + char[] name = toNarrowStringz(fileinfo.cFileName.ptr.toDString); + // parse the numeric version that follows the `...v` prefix + uint ver; + bool haveVer; + if (name.length > prefix.length && name[0 .. prefix.length] == prefix) + { + for (size_t i = prefix.length; i < name.length && name[i] >= '0' && name[i] <= '9'; ++i) + { + ver = ver * 10 + cast(uint)(name[i] - '0'); + haveVer = true; + } + } + if (haveVer && (!bestName || ver > bestVer)) + { + if (bestName) + mem.xfree(bestName); + bestName = name.ptr; + bestVer = ver; + } + else + mem.xfree(name.ptr); + } + while (FindNextFileW(h, &fileinfo)); + + if (!FindClose(h) || !bestName) + { + if (bestName) + mem.xfree(bestName); + return null; + } + + auto result = FileName.buildPath(dBuildDir, bestName.toDString); + mem.xfree(bestName); + return result.ptr; + } + // iterate through subdirectories named by SDK version in baseDir and return the // one with the largest version that also contains the test file static const(char)* findLatestSDKDir(const(char)* baseDir, string testfile) diff --git a/compiler/test/compilable/traits.d b/compiler/test/compilable/traits.d index 025a82c9795c..b186aedfbe46 100644 --- a/compiler/test/compilable/traits.d +++ b/compiler/test/compilable/traits.d @@ -20,7 +20,8 @@ static assert(is(typeof(__traits(getTargetInfo, "cppRuntimeLibrary")) == string) version (CppRuntime_Microsoft) { static assert(__traits(getTargetInfo, "cppRuntimeLibrary") == "libcmt" || - __traits(getTargetInfo, "cppRuntimeLibrary")[0..6] == "msvcrt"); // includes mingw import libs + __traits(getTargetInfo, "cppRuntimeLibrary") == "ucrtbase" || + __traits(getTargetInfo, "cppRuntimeLibrary")[0..6] == "msvcrt"); // includes the UCRT-based MinGW fallback and import libs } version (D_HardFloat) From 16bbd5098a42979d53a8b6fa44b54928671b098c Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Thu, 2 Jul 2026 13:45:28 -0700 Subject: [PATCH 5/7] Simplify VC tool chain detection by enumerating the directory itself. --- compiler/src/dmd/vsoptions.d | 100 +++-------------------------------- 1 file changed, 8 insertions(+), 92 deletions(-) diff --git a/compiler/src/dmd/vsoptions.d b/compiler/src/dmd/vsoptions.d index e597346afba1..79207ce525e7 100644 --- a/compiler/src/dmd/vsoptions.d +++ b/compiler/src/dmd/vsoptions.d @@ -12,7 +12,6 @@ module dmd.vsoptions; version (Windows): -import core.stdc.ctype; import core.stdc.stdlib; import core.stdc.string; import core.stdc.wchar_; @@ -21,7 +20,6 @@ import core.sys.windows.windef; import core.sys.windows.winreg; import dmd.root.env; -import dmd.root.file; import dmd.root.filename; import dmd.common.outbuffer; import dmd.root.rmem; @@ -400,7 +398,7 @@ private: } /** - * detect VCToolsInstallDir from environment or registry (only used by VC 2017) + * detect VCToolsInstallDir from environment or by enumerating VC\Tools\MSVC (VS2017+) */ void detectVCToolsInstallDir() { @@ -409,33 +407,14 @@ private: if (VCToolsInstallDir is null && VCInstallDir) { - const(char)* defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); - if (!FileName.exists(defverFile)) + // VS2017+ install the toolchain under VC\Tools\MSVC\ (e.g. 14.44.35207). + // Enumerate that folder directly and pick the highest version that ships the + // compiler headers, instead of parsing the Microsoft.VCToolsVersion.*.default.txt files. + const(char)* msvcDir = FileName.combine(VCInstallDir, r"Tools\MSVC"); + if (auto ver = findLatestSDKDir(msvcDir, r"include\vcruntime.h")) { - // The unversioned file was renamed with VS2019 Preview 2. Newer installs - // ship versioned files instead (e.g. Microsoft.VCToolsVersion.v142/v143/v145 - // .default.txt); scan for them and pick the highest version. - const(char)* buildDir = FileName.combine(VCInstallDir, r"Auxiliary\Build"); - defverFile = findLatestVCToolsDefaultFile(buildDir); - } - if (defverFile && FileName.exists(defverFile)) - { - // VS 2017 - OutBuffer buf; - if (File.read(defverFile.toDString, buf)) // read file into buf - return; // failed to read the file - - auto ver = cast(char*)buf.extractSlice(true).ptr; - // trim version number - while (*ver && isspace(*ver)) - ver++; - auto p = ver; - while (*p == '.' || (*p >= '0' && *p <= '9')) - p++; - *p = 0; - - if (ver && *ver) - VCToolsInstallDir = FileName.buildPath(VCInstallDir.toDString, r"Tools\MSVC", ver.toDString).ptr; + VCToolsInstallDir = FileName.buildPath(msvcDir.toDString, ver.toDString).ptr; + mem.xfree(cast(void*)ver); } } } @@ -684,69 +663,6 @@ extern(D): } } - // In the given `Auxiliary\Build` directory, find the - // `Microsoft.VCToolsVersion.vNNN.default.txt` file with the highest NNN and return - // its full path (allocated), or null if none is found. - static const(char)* findLatestVCToolsDefaultFile(const(char)* buildDir) - { - import dmd.common.smallbuffer : SmallBuffer, toWStringz; - - enum prefix = "Microsoft.VCToolsVersion.v"; - enum suffix = ".default.txt"; - - const(char)* pattern = FileName.combine(buildDir, prefix ~ "*" ~ suffix); - wchar[1024] support = void; - auto buf = SmallBuffer!wchar(support.length, support); - wchar* wpattern = toWStringz(pattern.toDString, buf).ptr; - FileName.free(pattern); - - WIN32_FIND_DATAW fileinfo; - HANDLE h = FindFirstFileW(wpattern, &fileinfo); - if (h == INVALID_HANDLE_VALUE) - return null; - - const dBuildDir = buildDir.toDString; - - char* bestName; // highest-versioned file name found so far - uint bestVer; - do - { - char[] name = toNarrowStringz(fileinfo.cFileName.ptr.toDString); - // parse the numeric version that follows the `...v` prefix - uint ver; - bool haveVer; - if (name.length > prefix.length && name[0 .. prefix.length] == prefix) - { - for (size_t i = prefix.length; i < name.length && name[i] >= '0' && name[i] <= '9'; ++i) - { - ver = ver * 10 + cast(uint)(name[i] - '0'); - haveVer = true; - } - } - if (haveVer && (!bestName || ver > bestVer)) - { - if (bestName) - mem.xfree(bestName); - bestName = name.ptr; - bestVer = ver; - } - else - mem.xfree(name.ptr); - } - while (FindNextFileW(h, &fileinfo)); - - if (!FindClose(h) || !bestName) - { - if (bestName) - mem.xfree(bestName); - return null; - } - - auto result = FileName.buildPath(dBuildDir, bestName.toDString); - mem.xfree(bestName); - return result.ptr; - } - // iterate through subdirectories named by SDK version in baseDir and return the // one with the largest version that also contains the test file static const(char)* findLatestSDKDir(const(char)* baseDir, string testfile) From 8f00e22c26c811164724113dff0277df8df1c22f Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Thu, 2 Jul 2026 23:30:42 -0700 Subject: [PATCH 6/7] Fix nits. --- compiler/src/dmd/vsoptions.d | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/compiler/src/dmd/vsoptions.d b/compiler/src/dmd/vsoptions.d index 79207ce525e7..948448ba4949 100644 --- a/compiler/src/dmd/vsoptions.d +++ b/compiler/src/dmd/vsoptions.d @@ -236,7 +236,7 @@ private: if (WindowsSdkDir is null) { WindowsSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10"w); - if (WindowsSdkDir && !findLatestSDKDir(FileName.combine(WindowsSdkDir, "Include"), r"um\windows.h")) + if (WindowsSdkDir && !findLatestVersionDir(FileName.combine(WindowsSdkDir, "Include"), r"um\windows.h")) WindowsSdkDir = null; } if (WindowsSdkDir is null) @@ -264,7 +264,7 @@ private: if (WindowsSdkVersion is null && WindowsSdkDir !is null) { const(char)* rootsDir = FileName.combine(WindowsSdkDir, "Include"); - WindowsSdkVersion = findLatestSDKDir(rootsDir, r"um\windows.h"); + WindowsSdkVersion = findLatestVersionDir(rootsDir, r"um\windows.h"); } } @@ -285,7 +285,7 @@ private: if (UCRTVersion is null && UCRTSdkDir !is null) { const(char)* rootsDir = FileName.combine(UCRTSdkDir, "Lib"); - UCRTVersion = findLatestSDKDir(rootsDir, r"ucrt\x86\libucrt.lib"); + UCRTVersion = findLatestVersionDir(rootsDir, r"ucrt\x86\libucrt.lib"); } } @@ -411,7 +411,7 @@ private: // Enumerate that folder directly and pick the highest version that ships the // compiler headers, instead of parsing the Microsoft.VCToolsVersion.*.default.txt files. const(char)* msvcDir = FileName.combine(VCInstallDir, r"Tools\MSVC"); - if (auto ver = findLatestSDKDir(msvcDir, r"include\vcruntime.h")) + if (auto ver = findLatestVersionDir(msvcDir, r"include\vcruntime.h")) { VCToolsInstallDir = FileName.buildPath(msvcDir.toDString, ver.toDString).ptr; mem.xfree(cast(void*)ver); @@ -650,10 +650,20 @@ extern(D): { for (;;) { - uint na = 0, nb = 0; - bool hasA, hasB; - while (*a >= '0' && *a <= '9') { na = na * 10 + cast(uint)(*a - '0'); ++a; hasA = true; } - while (*b >= '0' && *b <= '9') { nb = nb * 10 + cast(uint)(*b - '0'); ++b; hasB = true; } + uint na = 0; + uint nb = 0; + bool hasA = false; + bool hasB = false; + while (*a >= '0' && *a <= '9') { + na = na * 10 + cast(uint)(*a - '0'); + ++a; + hasA = true; + } + while (*b >= '0' && *b <= '9') { + nb = nb * 10 + cast(uint)(*b - '0'); + ++b; + hasB = true; + } if (na != nb) return na < nb ? -1 : 1; if (!hasA && !hasB) @@ -665,7 +675,7 @@ extern(D): // iterate through subdirectories named by SDK version in baseDir and return the // one with the largest version that also contains the test file - static const(char)* findLatestSDKDir(const(char)* baseDir, string testfile) + static const(char)* findLatestVersionDir(const(char)* baseDir, string testfile) { import dmd.common.smallbuffer : SmallBuffer, toWStringz; From 382c0b0d357eb53097116e5c0c116e93e0186924 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Thu, 2 Jul 2026 23:32:09 -0700 Subject: [PATCH 7/7] Fix whitespace --- compiler/src/dmd/vsoptions.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dmd/vsoptions.d b/compiler/src/dmd/vsoptions.d index 948448ba4949..c7f079dbf361 100644 --- a/compiler/src/dmd/vsoptions.d +++ b/compiler/src/dmd/vsoptions.d @@ -654,7 +654,7 @@ extern(D): uint nb = 0; bool hasA = false; bool hasB = false; - while (*a >= '0' && *a <= '9') { + while (*a >= '0' && *a <= '9') { na = na * 10 + cast(uint)(*a - '0'); ++a; hasA = true;