diff --git a/src/dmd/root/filename.d b/src/dmd/root/filename.d index 1fdafb3aad4e..61ec9a51755f 100644 --- a/src/dmd/root/filename.d +++ b/src/dmd/root/filename.d @@ -40,7 +40,7 @@ version (Windows) extern (C) char* getcwd(char* buffer, size_t maxlen) nothrow; // assume filenames encoded in system default Windows ANSI code page - private enum codepage = CP_ACP; + private enum CodePage = CP_ACP; } version (CRuntime_Glibc) @@ -357,55 +357,67 @@ nothrow: /// Ditto extern(D) static const(char)[] combine(const(char)[] path, const(char)[] name) { - if (!path.length) - return name; + return !path.length ? name : buildPath(path, name); + } - char* f = cast(char*)mem.xmalloc(path.length + 1 + name.length + 1); - memcpy(f, path.ptr, path.length); - bool trailingSlash = false; - version (Posix) + unittest + { + version (Windows) + assert(combine("foo"[], "bar"[]) == "foo\\bar"); + else + assert(combine("foo"[], "bar"[]) == "foo/bar"); + assert(combine("foo/"[], "bar"[]) == "foo/bar"); + } + + static const(char)[] buildPath(const(char)[][] fragments...) + { + size_t size; + foreach (f; fragments) + size += f.length ? f.length + 1 : 0; + if (size == 0) + size = 1; + + char* p = cast(char*) mem.xmalloc_noscan(size); + size_t length; + foreach (f; fragments) { - if (path[$ - 1] != '/') + if (!f.length) + continue; + + p[length .. length + f.length] = f; + length += f.length; + + const last = p[length - 1]; + version (Posix) { - f[path.length] = '/'; - trailingSlash = true; + if (last != '/') + p[length++] = '/'; } - } - else version (Windows) - { - if (path[$ - 1] != '\\' && path[$ - 1] != '/' && path[$ - 1] != ':') + else version (Windows) { - f[path.length] = '\\'; - trailingSlash = true; + if (last != '\\' && last != '/' && last != ':') + p[length++] = '\\'; } + else + assert(0); } - else - { - assert(0); - } - const len = path.length + trailingSlash; - memcpy(f + len, name.ptr, name.length); - // Note: At the moment `const(char)*` are being transitioned to - // `const(char)[]`. To avoid bugs crippling in, we `\0` terminate - // slices, but don't include it in the slice so `.ptr` can be used. - f[len + name.length] = '\0'; - return f[0 .. len + name.length]; + + // overwrite last slash with null terminator + p[length ? --length : 0] = 0; + + return p[0 .. length]; } unittest { + assert(buildPath() == ""); + assert(buildPath("foo") == "foo"); + assert(buildPath("foo", null) == "foo"); + assert(buildPath(null, "foo") == "foo"); version (Windows) - assert(combine("foo"[], "bar"[]) == "foo\\bar"); + assert(buildPath("C:", r"a\", "bb/", "ccc", "d") == r"C:a\bb/ccc\d"); else - assert(combine("foo"[], "bar"[]) == "foo/bar"); - assert(combine("foo/"[], "bar"[]) == "foo/bar"); - } - - static const(char)* buildPath(const(char)* path, const(char)*[] names...) - { - foreach (const(char)* name; names) - path = combine(path, name); - return path; + assert(buildPath("a/", "bb", "ccc") == "a/bb/ccc"); } // Split a path into an Array of paths @@ -808,7 +820,7 @@ nothrow: } else version (Windows) { - return name.toCStringThen!((cstr) => cstr.toWStringzThen!((wname) + return name.toWStringzThen!((wname) { const dw = GetFileAttributesW(&wname[0]); if (dw == -1) @@ -817,7 +829,7 @@ nothrow: return 2; else return 1; - })); + }); } else { @@ -990,26 +1002,15 @@ nothrow: // First find out how long the buffer has to be. const fullPathLength = GetFullPathNameW(&wname[0], 0, null, null); if (!fullPathLength) return null; - auto fullPath = new wchar[fullPathLength]; + auto fullPath = (cast(wchar*) mem.xmalloc_noscan((fullPathLength + 1) * wchar.sizeof))[0 .. fullPathLength + 1]; + scope(exit) mem.xfree(fullPath.ptr); // Actually get the full path name - const fullPathLengthNoTerminator = GetFullPathNameW( - &wname[0], fullPathLength, &fullPath[0], null /*filePart*/); - // Unfortunately, when the buffer is large enough the return value is the number of characters - // _not_ counting the null terminator, so fullPathLengthNoTerminator should be smaller - assert(fullPathLength > fullPathLengthNoTerminator); - - // Find out size of the converted string - const retLength = WideCharToMultiByte( - codepage, 0 /*flags*/, &fullPath[0], fullPathLength, null, 0, null, null); - auto ret = new char[retLength]; - - // Actually convert to char - const retLength2 = WideCharToMultiByte( - codepage, 0 /*flags*/, &fullPath[0], fullPathLength, &ret[0], retLength, null, null); - assert(retLength == retLength2); - - return ret; + const length = GetFullPathNameW( + &wname[0], cast(DWORD) fullPath.length, &fullPath[0], null /*filePart*/); + assert(length == fullPathLength); + + return toNarrowStringz(fullPath[0 .. length]); }); } else @@ -1149,6 +1150,60 @@ version(Windows) }); } + /********************************** + * Converts a UTF-16 string to a (null-terminated) narrow string. + * Returns: + * If `buffer` is specified and the result fits, a slice of that buffer, + * otherwise a new buffer which can be released via `mem.xfree()`. + * Nulls are propagated, i.e., if `wide` is null, the returned slice is + * null too. + */ + char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow + { + if (wide is null) + return null; + + const requiredLength = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, buffer.ptr, cast(int) buffer.length, null, null); + if (requiredLength < buffer.length) + { + buffer[requiredLength] = 0; + return buffer[0 .. requiredLength]; + } + + char* newBuffer = cast(char*) mem.xmalloc_noscan(requiredLength + 1); + const length = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, newBuffer, requiredLength, null, null); + assert(length == requiredLength); + newBuffer[length] = 0; + return newBuffer[0 .. length]; + } + + /********************************** + * Converts a narrow string to a (null-terminated) UTF-16 string. + * Returns: + * If `buffer` is specified and the result fits, a slice of that buffer, + * otherwise a new buffer which can be released via `mem.xfree()`. + * Nulls are propagated, i.e., if `narrow` is null, the returned slice is + * null too. + */ + wchar[] toWStringz(const(char)[] narrow, wchar[] buffer = null) nothrow + { + if (narrow is null) + return null; + + const requiredLength = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length); + if (requiredLength < buffer.length) + { + buffer[requiredLength] = 0; + return buffer[0 .. requiredLength]; + } + + wchar* newBuffer = cast(wchar*) mem.xmalloc_noscan((requiredLength + 1) * wchar.sizeof); + const length = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, newBuffer, requiredLength); + assert(length == requiredLength); + newBuffer[length] = 0; + return newBuffer[0 .. length]; + } + /********************************** * Converts a slice of UTF-8 characters to an array of wchar that's null * terminated so it can be passed to Win32 APIs then calls the supplied @@ -1164,28 +1219,10 @@ version(Windows) { if (!str.length) return F(""w.ptr); - import core.stdc.stdlib: malloc, free; wchar[1024] buf = void; + wchar[] wide = toWStringz(str, buf); + scope(exit) wide.ptr != buf.ptr && mem.xfree(wide.ptr); - // first find out how long the buffer must be to store the result - const length = MultiByteToWideChar(codepage, 0 /*flags*/, &str[0], cast(int)str.length, null, 0); - if (!length) return F(""w); - - wchar[] ret = length >= buf.length - ? (cast(wchar*)malloc((length + 1) * wchar.sizeof))[0 .. length + 1] - : buf[0 .. length + 1]; - scope (exit) - { - if (&ret[0] != &buf[0]) - free(&ret[0]); - } - // actually do the conversion - const length2 = MultiByteToWideChar( - codepage, 0 /*flags*/, &str[0], cast(int)str.length, &ret[0], length); - assert(length == length2); // should always be true according to the API - // Add terminating `\0` - ret[$ - 1] = '\0'; - - return F(ret[0 .. $ - 1]); + return F(wide); } } diff --git a/src/dmd/vsoptions.d b/src/dmd/vsoptions.d index 4d43e3d68bfb..7ec0b548456c 100644 --- a/src/dmd/vsoptions.d +++ b/src/dmd/vsoptions.d @@ -15,6 +15,7 @@ version (Windows): import core.stdc.ctype; import core.stdc.stdlib; import core.stdc.string; +import core.stdc.wchar_; import core.sys.windows.winbase; import core.sys.windows.windef; import core.sys.windows.winreg; @@ -24,8 +25,11 @@ import dmd.root.file; import dmd.root.filename; import dmd.root.outbuffer; import dmd.root.rmem; +import dmd.root.string : toDString; -struct VSOptions +private immutable supportedPre2017Versions = ["14.0", "12.0", "11.0", "10.0", "9.0"]; + +extern(C++) struct VSOptions { // evaluated once at startup, reflecting the result of vcvarsall.bat // from the current environment or the latest Visual Studio installation @@ -103,12 +107,13 @@ struct VSOptions cmdbuf.writestring(p); cmdbuf.writeByte('\"'); } - if (auto p = getenv("DXSDK_DIR")) + if (auto p = getenv("DXSDK_DIR"w)) { // support for old DX SDK installations cmdbuf.writestring(" /LIBPATH:\""); cmdbuf.writestring(p); cmdbuf.writestring(x64 ? `\Lib\x64"` : `\Lib\x86"`); + mem.xfree(p); } return cmdbuf.extractChars(); } @@ -133,7 +138,7 @@ struct VSOptions { // debug info needs DLLs from $(VSInstallDir)\Common7\IDE for most linker versions // so prepend it too the PATH environment variable - const path = getenv("PATH"); + char* path = getenv("PATH"w); const pathlen = strlen(path); const addpathlen = strlen(addpath); @@ -145,6 +150,7 @@ struct VSOptions if (putenvRestorable("PATH", npath[0 .. length])) assert(0); mem.xfree(npath); + mem.xfree(path); } return cmdbuf.extractChars(); } @@ -160,7 +166,7 @@ struct VSOptions } // search PATH to avoid createProcess preferring "link.exe" from the dmd folder - if (auto p = FileName.searchPath(getenv("PATH"), "link.exe"[], false)) + if (auto p = FileName.searchPath(getenv("PATH"w), "link.exe"[], false)) return p.ptr; return "link.exe"; } @@ -172,35 +178,35 @@ private: void detectWindowsSDK() { if (WindowsSdkDir is null) - WindowsSdkDir = getenv("WindowsSdkDir"); + WindowsSdkDir = getenv("WindowsSdkDir"w); if (WindowsSdkDir is null) { - WindowsSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10"); + WindowsSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10"w); if (WindowsSdkDir && !findLatestSDKDir(FileName.combine(WindowsSdkDir, "Include"), r"um\windows.h")) WindowsSdkDir = null; } if (WindowsSdkDir is null) { - WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows\v8.1", "InstallationFolder"); + WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows\v8.1", "InstallationFolder"w); if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib"))) WindowsSdkDir = null; } if (WindowsSdkDir is null) { - WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows\v8.0", "InstallationFolder"); + WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows\v8.0", "InstallationFolder"w); if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib"))) WindowsSdkDir = null; } if (WindowsSdkDir is null) { - WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows", "CurrentInstallationFolder"); + WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows", "CurrentInstallationFolder"w); if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib"))) WindowsSdkDir = null; } if (WindowsSdkVersion is null) - WindowsSdkVersion = getenv("WindowsSdkVersion"); + WindowsSdkVersion = getenv("WindowsSdkVersion"w); if (WindowsSdkVersion is null && WindowsSdkDir !is null) { @@ -215,13 +221,13 @@ private: void detectUCRT() { if (UCRTSdkDir is null) - UCRTSdkDir = getenv("UniversalCRTSdkDir"); + UCRTSdkDir = getenv("UniversalCRTSdkDir"w); if (UCRTSdkDir is null) - UCRTSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10"); + UCRTSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10"w); if (UCRTVersion is null) - UCRTVersion = getenv("UCRTVersion"); + UCRTVersion = getenv("UCRTVersion"w); if (UCRTVersion is null && UCRTSdkDir !is null) { @@ -236,18 +242,27 @@ private: void detectVSInstallDir() { if (VSInstallDir is null) - VSInstallDir = getenv("VSINSTALLDIR"); + VSInstallDir = getenv("VSINSTALLDIR"w); if (VSInstallDir is null) VSInstallDir = detectVSInstallDirViaCOM(); if (VSInstallDir is null) - VSInstallDir = GetRegistryString(r"Microsoft\VisualStudio\SxS\VS7", "15.0"); // VS2017 + VSInstallDir = GetRegistryString(r"Microsoft\VisualStudio\SxS\VS7", "15.0"w); // VS2017 + + 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; + } if (VSInstallDir is null) - foreach (const(char)* ver; ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"]) + foreach (ver; supportedPre2017Versions) { - VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir"); + VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir"w); if (VSInstallDir) break; } @@ -259,7 +274,7 @@ private: void detectVCInstallDir() { if (VCInstallDir is null) - VCInstallDir = getenv("VCINSTALLDIR"); + VCInstallDir = getenv("VCINSTALLDIR"w); if (VCInstallDir is null) if (VSInstallDir && FileName.exists(FileName.combine(VSInstallDir, "VC"))) @@ -267,10 +282,11 @@ private: // detect from registry (build tools?) if (VCInstallDir is null) - foreach (const(char)* ver; ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"]) + foreach (ver; supportedPre2017Versions) { auto regPath = FileName.buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC"); - VCInstallDir = GetRegistryString(regPath, "ProductDir"); + VCInstallDir = GetRegistryString(regPath, "ProductDir"w); + FileName.free(regPath.ptr); if (VCInstallDir) break; } @@ -282,7 +298,7 @@ private: void detectVCToolsInstallDir() { if (VCToolsInstallDir is null) - VCToolsInstallDir = getenv("VCTOOLSINSTALLDIR"); + VCToolsInstallDir = getenv("VCTOOLSINSTALLDIR"w); if (VCToolsInstallDir is null && VCInstallDir) { @@ -305,12 +321,13 @@ private: *p = 0; if (ver && *ver) - VCToolsInstallDir = FileName.buildPath(VCInstallDir, r"Tools\MSVC", ver); + VCToolsInstallDir = FileName.buildPath(VCInstallDir.toDString, r"Tools\MSVC", ver.toDString).ptr; } } } } +public: /** * get Visual C bin folder * Params: @@ -325,75 +342,73 @@ private: * Note: differences for the linker binaries are small, they all * allow cross compilation */ - const(char)* getVCBinDir(bool x64, out const(char)* addpath) + const(char)* getVCBinDir(bool x64, out const(char)* addpath) const { - static const(char)* linkExists(const(char)* p) - { - auto lp = FileName.combine(p, "link.exe"); - return FileName.exists(lp) ? p : null; - } + alias linkExists = returnDirIfContainsFile!"link.exe"; const bool isHost64 = isWin64Host(); if (VCToolsInstallDir !is null) { + const vcToolsInstallDir = VCToolsInstallDir.toDString; if (isHost64) { if (x64) { - if (auto p = linkExists(FileName.combine(VCToolsInstallDir, r"bin\HostX64\x64"))) + if (auto p = linkExists(vcToolsInstallDir, r"bin\HostX64\x64")) return p; // in case of missing linker, prefer other host binaries over other target architecture } else { - if (auto p = linkExists(FileName.combine(VCToolsInstallDir, r"bin\HostX64\x86"))) + if (auto p = linkExists(vcToolsInstallDir, r"bin\HostX64\x86")) { - addpath = FileName.combine(VCToolsInstallDir, r"bin\HostX64\x64"); + addpath = FileName.combine(vcToolsInstallDir, r"bin\HostX64\x64").ptr; return p; } } } if (x64) { - if (auto p = linkExists(FileName.combine(VCToolsInstallDir, r"bin\HostX86\x64"))) + if (auto p = linkExists(vcToolsInstallDir, r"bin\HostX86\x64")) { - addpath = FileName.combine(VCToolsInstallDir, r"bin\HostX86\x86"); + addpath = FileName.combine(vcToolsInstallDir, r"bin\HostX86\x86").ptr; return p; } } - if (auto p = linkExists(FileName.combine(VCToolsInstallDir, r"bin\HostX86\x86"))) + if (auto p = linkExists(vcToolsInstallDir, r"bin\HostX86\x86")) return p; } if (VCInstallDir !is null) { + const vcInstallDir = VCInstallDir.toDString; if (isHost64) { if (x64) { - if (auto p = linkExists(FileName.combine(VCInstallDir, r"bin\amd64"))) + if (auto p = linkExists(vcInstallDir, r"bin\amd64")) return p; // in case of missing linker, prefer other host binaries over other target architecture } else { - if (auto p = linkExists(FileName.combine(VCInstallDir, r"bin\amd64_x86"))) + if (auto p = linkExists(vcInstallDir, r"bin\amd64_x86")) { - addpath = FileName.combine(VCInstallDir, r"bin\amd64"); + addpath = FileName.combine(vcInstallDir, r"bin\amd64").ptr; return p; } } } if (VSInstallDir) - addpath = FileName.combine(VSInstallDir, r"Common7\IDE"); + addpath = FileName.combine(VSInstallDir.toDString, r"Common7\IDE").ptr; else - addpath = FileName.combine(VCInstallDir, r"bin"); + addpath = FileName.combine(vcInstallDir, r"bin").ptr; if (x64) - if (auto p = linkExists(FileName.combine(VCInstallDir, r"x86_amd64"))) + if (auto p = linkExists(vcInstallDir, r"x86_amd64")) return p; - if (auto p = linkExists(FileName.combine(VCInstallDir, r"bin\HostX86\x86"))) + if (auto p = linkExists(vcInstallDir, r"bin\HostX86\x86")) return p; } return null; @@ -406,7 +421,7 @@ private: * Returns: * folder containing the the VC runtime libraries */ - const(char)* getVCLibDir(bool x64) + const(char)* getVCLibDir(bool x64) const { if (VCToolsInstallDir !is null) return FileName.combine(VCToolsInstallDir, x64 ? r"lib\x64" : r"lib\x86"); @@ -422,10 +437,10 @@ private: * Returns: * folder containing the universal CRT libraries */ - const(char)* getUCRTLibPath(bool x64) + const(char)* getUCRTLibPath(bool x64) const { if (UCRTSdkDir && UCRTVersion) - return FileName.buildPath(UCRTSdkDir, "Lib", UCRTVersion, x64 ? r"ucrt\x64" : r"ucrt\x86"); + return FileName.buildPath(UCRTSdkDir.toDString, "Lib", UCRTVersion.toDString, x64 ? r"ucrt\x64" : r"ucrt\x86").ptr; return null; } @@ -436,54 +451,80 @@ private: * Returns: * folder containing the Windows SDK libraries */ - const(char)* getSDKLibPath(bool x64) + const(char)* getSDKLibPath(bool x64) const { if (WindowsSdkDir) { - const(char)* arch = x64 ? "x64" : "x86"; - auto sdk = FileName.combine(WindowsSdkDir, "lib"); - if (WindowsSdkVersion && - FileName.exists(FileName.buildPath(sdk, WindowsSdkVersion, "um", arch, "kernel32.lib"))) // SDK 10.0 - return FileName.buildPath(sdk, WindowsSdkVersion, "um", arch); - else if (FileName.exists(FileName.buildPath(sdk, r"win8\um", arch, "kernel32.lib"))) // SDK 8.0 - return FileName.buildPath(sdk, r"win8\um", arch); - else if (FileName.exists(FileName.buildPath(sdk, r"winv6.3\um", arch, "kernel32.lib"))) // SDK 8.1 - return FileName.buildPath(sdk, r"winv6.3\um", arch); - else if (x64 && FileName.exists(FileName.buildPath(sdk, arch, "kernel32.lib"))) // SDK 7.1 or earlier - return FileName.buildPath(sdk, arch); - else if (!x64 && FileName.exists(FileName.buildPath(sdk, "kernel32.lib"))) // SDK 7.1 or earlier - return sdk; + alias kernel32Exists = returnDirIfContainsFile!"kernel32.lib"; + + const arch = x64 ? "x64" : "x86"; + const sdk = FileName.combine(WindowsSdkDir.toDString, "lib"); + if (WindowsSdkVersion) + { + if (auto p = kernel32Exists(sdk, WindowsSdkVersion.toDString, "um", arch)) // SDK 10.0 + return p; + } + if (auto p = kernel32Exists(sdk, r"win8\um", arch)) // SDK 8.0 + return p; + if (auto p = kernel32Exists(sdk, r"winv6.3\um", arch)) // SDK 8.1 + return p; + if (x64) + { + if (auto p = kernel32Exists(sdk, arch)) // SDK 7.1 or earlier + return p; + } + else + { + if (auto p = kernel32Exists(sdk)) // SDK 7.1 or earlier + return p; + } } // try mingw fallback relative to phobos library folder that's part of LIB - if (auto p = FileName.searchPath(getenv("LIB"), r"mingw\kernel32.lib"[], false)) + if (auto p = FileName.searchPath(getenv("LIB"w), r"mingw\kernel32.lib"[], false)) return FileName.path(p).ptr; return null; } +private: +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, const(char)* testfile) + static const(char)* findLatestSDKDir(const(char)* baseDir, string testfile) { - auto allfiles = FileName.combine(baseDir, "*"); - WIN32_FIND_DATAA fileinfo; - HANDLE h = FindFirstFileA(allfiles, &fileinfo); + const(char)* pattern = FileName.combine(baseDir, "*"); + wchar* wpattern = toWStringz(pattern.toDString).ptr; + scope(exit) mem.xfree(wpattern); + FileName.free(pattern); + + WIN32_FIND_DATAW fileinfo; + HANDLE h = FindFirstFileW(wpattern, &fileinfo); if (h == INVALID_HANDLE_VALUE) return null; - char* res = null; + const dBaseDir = baseDir.toDString; + + char* res; do { if (fileinfo.cFileName[0] >= '1' && fileinfo.cFileName[0] <= '9') - if (res is null || strcmp(res, fileinfo.cFileName.ptr) < 0) - if (FileName.exists(FileName.buildPath(baseDir, fileinfo.cFileName.ptr, testfile))) - { - const len = strlen(fileinfo.cFileName.ptr) + 1; - res = cast(char*) memcpy(mem.xrealloc(res, len), fileinfo.cFileName.ptr, len); - } + { + char[] name = toNarrowStringz(fileinfo.cFileName.ptr.toDString); + // FIXME: proper version strings comparison + if ((!res || strcmp(res, name.ptr) < 0) && + FileName.exists(FileName.buildPath(dBaseDir, name, testfile))) + { + if (res) + mem.xfree(res); + res = name.ptr; + } + else + mem.xfree(name.ptr); + } } - while(FindNextFileA(h, &fileinfo)); + while(FindNextFileW(h, &fileinfo)); if (!FindClose(h)) res = null; @@ -500,7 +541,7 @@ private: * Returns: * the registry value if it exists and has string type */ - const(char)* GetRegistryString(const(char)* softwareKeyPath, const(char)* valueName) + const(char)* GetRegistryString(const(char)[] softwareKeyPath, wstring valueName) const { enum x64hive = false; // VS registry entries always in 32-bit hive @@ -510,11 +551,11 @@ private: enum prefix = r"SOFTWARE\"; char[260] regPath = void; - const len = strlen(softwareKeyPath); - assert(len + prefix.length < regPath.length); + assert(prefix.length + softwareKeyPath.length < regPath.length); - memcpy(regPath.ptr, prefix.ptr, prefix.length); - memcpy(regPath.ptr + prefix.length, softwareKeyPath, len + 1); + regPath[0 .. prefix.length] = prefix; + regPath[prefix.length .. prefix.length + softwareKeyPath.length] = softwareKeyPath; + regPath[prefix.length + softwareKeyPath.length] = 0; enum KEY_WOW64_64KEY = 0x000100; // not defined in core.sys.windows.winnt due to restrictive version enum KEY_WOW64_32KEY = 0x000200; @@ -524,18 +565,27 @@ private: return null; scope(exit) RegCloseKey(key); - char[260] buf = void; - DWORD cnt = buf.length * char.sizeof; + wchar[260] buf = void; + DWORD size = buf.sizeof; DWORD type; - int hr = RegQueryValueExA(key, valueName, null, &type, cast(ubyte*) buf.ptr, &cnt); - if (hr == 0 && cnt > 0) - return buf.dup.ptr; - if (hr != ERROR_MORE_DATA || type != REG_SZ) + int hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) buf.ptr, &size); + if (type != REG_SZ || size == 0) + return null; + + wchar* wideValue = buf.ptr; + scope(exit) wideValue != buf.ptr && mem.xfree(wideValue); + if (hr == ERROR_MORE_DATA) + { + wideValue = cast(wchar*) mem.xmalloc_noscan(size); + hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) wideValue, &size); + } + if (hr != 0) return null; - scope char[] pbuf = new char[cnt + 1]; - RegQueryValueExA(key, valueName, null, &type, cast(ubyte*) pbuf.ptr, &cnt); - return pbuf.ptr; + auto wideLength = size / wchar.sizeof; + if (wideValue[wideLength - 1] == 0) // may or may not be null-terminated + --wideLength; + return toNarrowStringz(wideValue[0 .. wideLength]).ptr; } /*** @@ -569,12 +619,48 @@ private: } } +private: + +inout(wchar)[] toDString(inout(wchar)* s) +{ + return s ? s[0 .. wcslen(s)] : null; +} + +extern(C) wchar* _wgetenv(const(wchar)* name); + +char* getenv(wstring name) +{ + if (auto wide = _wgetenv(name.ptr)) + return toNarrowStringz(wide.toDString).ptr; + return null; +} + +const(char)* returnDirIfContainsFile(string fileName)(scope const(char)[][] dirs...) +{ + auto dirPath = FileName.buildPath(dirs); + + auto filePath = FileName.combine(dirPath, fileName); + scope(exit) FileName.free(filePath.ptr); + + if (FileName.exists(filePath)) + return dirPath.ptr; + + FileName.free(dirPath.ptr); + return null; +} + +extern (C) int _waccess(const(wchar)* _FileName, int _AccessMode); + +bool exists(const(wchar)* path) +{ + return _waccess(path, 0) == 0; +} + /////////////////////////////////////////////////////////////////////// // COM interfaces to find VS2017+ installations import core.sys.windows.com; import core.sys.windows.wtypes : BSTR; -import core.sys.windows.winnls : WideCharToMultiByte, CP_UTF8; -import core.sys.windows.oleauto : SysFreeString; +import core.sys.windows.oleauto : SysFreeString, SysStringLen; pragma(lib, "ole32.lib"); pragma(lib, "oleaut32.lib"); @@ -635,18 +721,47 @@ const(char)* detectVSInstallDirViaCOM() return null; scope(exit) instances.Release(); + static struct WrappedBString + { + BSTR ptr; + this(this) @disable; + ~this() { SysFreeString(ptr); } + bool opCast(T : bool)() const { return ptr !is null; } + size_t length() { return SysStringLen(ptr); } + void moveTo(ref WrappedBString other) + { + SysFreeString(other.ptr); + other.ptr = ptr; + ptr = null; + } + } + + WrappedBString versionString, installDir; + while (instances.Next(1, &instance, &fetched) == S_OK && fetched) { - BSTR bstrInstallDir; - if (instance.GetInstallationPath(&bstrInstallDir) != S_OK) + WrappedBString thisVersionString, thisInstallDir; + if (instance.GetInstallationVersion(&thisVersionString.ptr) != S_OK || + instance.GetInstallationPath(&thisInstallDir.ptr) != S_OK) continue; - char[260] path; - int len = WideCharToMultiByte(CP_UTF8, 0, bstrInstallDir, -1, path.ptr, 260, null, null); - SysFreeString(bstrInstallDir); - - if (len > 0) - return path[0..len].idup.ptr; + // 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) + continue; // not a newer version, skip + + const installDirLength = thisInstallDir.length; + const vcInstallDirLength = installDirLength + 4; + auto vcInstallDir = (cast(wchar*) mem.xmalloc_noscan(vcInstallDirLength * wchar.sizeof))[0 .. vcInstallDirLength]; + scope(exit) mem.xfree(vcInstallDir.ptr); + vcInstallDir[0 .. installDirLength] = thisInstallDir.ptr[0 .. installDirLength]; + vcInstallDir[installDirLength .. $] = "\\VC\0"w; + if (!exists(vcInstallDir.ptr)) + continue; // Visual C++ not included, skip + + thisVersionString.moveTo(versionString); + thisInstallDir.moveTo(installDir); } - return null; + + return !installDir ? null : toNarrowStringz(installDir.ptr[0 .. installDir.length]).ptr; } diff --git a/src/dmd/vsoptions.h b/src/dmd/vsoptions.h new file mode 100644 index 000000000000..ef7703df01e5 --- /dev/null +++ b/src/dmd/vsoptions.h @@ -0,0 +1,32 @@ + +/* Compiler implementation of the D programming language + * Copyright (C) 2009-2020 by The D Language Foundation, All Rights Reserved + * written by Walter Bright + * http://www.digitalmars.com + * Distributed under the Boost Software License, Version 1.0. + * http://www.boost.org/LICENSE_1_0.txt + * https://github.com/dlang/dmd/blob/master/src/dmd/vsoptions.h + */ + +#pragma once + +#ifdef _WIN32 + +struct VSOptions +{ + const char *WindowsSdkDir = nullptr; + const char *WindowsSdkVersion = nullptr; + const char *UCRTSdkDir = nullptr; + const char *UCRTVersion = nullptr; + const char *VSInstallDir = nullptr; + const char *VCInstallDir = nullptr; + const char *VCToolsInstallDir = nullptr; // used by VS 2017+ + + void initialize(); + const char *getVCBinDir(bool x64, const char *&addpath) const; + const char *getVCLibDir(bool x64) const; + const char *getUCRTLibPath(bool x64) const; + const char *getSDKLibPath(bool x64) const; +}; + +#endif // _WIN32