From 18406bcc066c5a0c1d54f5a7e6aa0eb97daa8d87 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Wed, 29 Apr 2020 18:39:42 +0200 Subject: [PATCH 1/6] Adopt DMD's MSVC toolchain detection This reduces the overhead for auto-detecting and setting up an MSVC toolchain from a very rough 1 second to about 8 milliseconds on my box. Enabling the auto-detection by default (and so preferring MSVC over the 'internal' toolchain if there's a Visual C++ installation) is now possible, fixing issues like #3402. The MSVC setup now consists of the bare minimum - prepending 3 directories to the LIB env var and 1-2 directories to PATH. --- CMakeLists.txt | 5 - azure-pipelines.yml | 5 +- dmd/vsoptions.d | 93 ++++++++++++--- dmd/vsoptions.h | 32 +++++ driver/linker-msvc.cpp | 33 ++++-- driver/linker.cpp | 40 ++++--- driver/linker.h | 7 +- driver/tool.cpp | 260 ++++++++++++----------------------------- driver/tool.h | 3 + gen/target.cpp | 3 +- packaging/README.txt | 41 +++---- vcbuild/dumpEnv.bat | 7 -- vcbuild/msvcEnv.bat | 57 --------- 13 files changed, 251 insertions(+), 335 deletions(-) create mode 100644 dmd/vsoptions.h delete mode 100644 vcbuild/dumpEnv.bat delete mode 100644 vcbuild/msvcEnv.bat diff --git a/CMakeLists.txt b/CMakeLists.txt index fde670ee785..7a3042d2b5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -932,11 +932,6 @@ if(${BUILD_SHARED}) endif() install(FILES ${PROJECT_BINARY_DIR}/bin/${LDC_EXE}_install.conf DESTINATION ${CONF_INST_DIR} RENAME ${LDC_EXE}.conf) -if(MSVC) - file(COPY vcbuild/ DESTINATION ${PROJECT_BINARY_DIR}/bin FILES_MATCHING PATTERN "*.bat") - install(DIRECTORY vcbuild/ DESTINATION ${CMAKE_INSTALL_PREFIX}/bin FILES_MATCHING PATTERN "*.bat") -endif() - if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(NOT DEFINED BASH_COMPLETION_COMPLETIONSDIR) find_package(bash-completion QUIET) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fc08c6902ee..0b475b7d2e9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -90,12 +90,11 @@ jobs: displayName: Generate hello.d - script: | echo on - %ARTIFACT_NAME%\bin\ldc2 -v -run hello.d || exit /b - %ARTIFACT_NAME%\bin\ldc2 -v -m32 -run hello.d + %ARTIFACT_NAME%\bin\ldc2 -v -mscrtlib=vcruntime140 -run hello.d || exit /b + %ARTIFACT_NAME%\bin\ldc2 -v -mscrtlib=vcruntime140 -m32 -run hello.d displayName: Run 32/64-bit hello-world smoke test with internal toolchain - script: | echo on - set LDC_VSDIR_FORCE=1 %ARTIFACT_NAME%\bin\ldc2 -v -run hello.d || exit /b %ARTIFACT_NAME%\bin\ldc2 -v -m32 -run hello.d displayName: Run 32/64-bit hello-world smoke test with MSVC auto-detection diff --git a/dmd/vsoptions.d b/dmd/vsoptions.d index 4d43e3d68bf..491f7f95a4c 100644 --- a/dmd/vsoptions.d +++ b/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; @@ -25,7 +26,16 @@ import dmd.root.filename; import dmd.root.outbuffer; import dmd.root.rmem; -struct VSOptions +version (IN_LLVM) +{ + enum supportedPre2017Versions = ["14.0".ptr]; +} +else +{ + enum supportedPre2017Versions = ["14.0".ptr, "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 @@ -238,6 +248,12 @@ private: if (VSInstallDir is null) VSInstallDir = getenv("VSINSTALLDIR"); +version (IN_LLVM) +{ + if (VSInstallDir is null) + VSInstallDir = getenv("LDC_VSDIR"); +} + if (VSInstallDir is null) VSInstallDir = detectVSInstallDirViaCOM(); @@ -245,7 +261,7 @@ private: VSInstallDir = GetRegistryString(r"Microsoft\VisualStudio\SxS\VS7", "15.0"); // VS2017 if (VSInstallDir is null) - foreach (const(char)* ver; ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"]) + foreach (const(char)* ver; supportedPre2017Versions) { VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir"); if (VSInstallDir) @@ -267,7 +283,7 @@ 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 (const(char)* ver; supportedPre2017Versions) { auto regPath = FileName.buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC"); VCInstallDir = GetRegistryString(regPath, "ProductDir"); @@ -311,6 +327,7 @@ private: } } +public: /** * get Visual C bin folder * Params: @@ -325,7 +342,7 @@ 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) { @@ -406,7 +423,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,7 +439,7 @@ 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"); @@ -436,7 +453,7 @@ private: * Returns: * folder containing the Windows SDK libraries */ - const(char)* getSDKLibPath(bool x64) + const(char)* getSDKLibPath(bool x64) const { if (WindowsSdkDir) { @@ -455,13 +472,20 @@ private: return sdk; } +version (IN_LLVM) {} +else +{ // 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)) 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) @@ -500,7 +524,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, const(char)* valueName) const { enum x64hive = false; // VS registry entries always in 32-bit hive @@ -527,6 +551,7 @@ private: char[260] buf = void; DWORD cnt = buf.length * char.sizeof; DWORD type; + // TODO: wide API int hr = RegQueryValueExA(key, valueName, null, &type, cast(ubyte*) buf.ptr, &cnt); if (hr == 0 && cnt > 0) return buf.dup.ptr; @@ -579,6 +604,8 @@ import core.sys.windows.oleauto : SysFreeString; pragma(lib, "ole32.lib"); pragma(lib, "oleaut32.lib"); +extern (C) int _waccess(const(wchar)* _FileName, int _AccessMode); + interface ISetupInstance : IUnknown { // static const GUID iid = uuid("B41463C3-8866-43B5-BC33-2B0676F7F42E"); @@ -635,18 +662,54 @@ const(char)* detectVSInstallDirViaCOM() return null; scope(exit) instances.Release(); + BSTR versionString; + BSTR installDir; + scope(exit) SysFreeString(versionString); + scope(exit) SysFreeString(installDir); + while (instances.Next(1, &instance, &fetched) == S_OK && fetched) { - BSTR bstrInstallDir; - if (instance.GetInstallationPath(&bstrInstallDir) != S_OK) + BSTR thisVersionString; + if (instance.GetInstallationVersion(&thisVersionString) != S_OK) + continue; + scope(exit) SysFreeString(thisVersionString); + + BSTR thisInstallDir; + if (instance.GetInstallationPath(&thisInstallDir) != S_OK) continue; + scope(exit) SysFreeString(thisInstallDir); + + if (versionString && wcscmp(thisVersionString, versionString) <= 0) + continue; // not a newer version, skip + + const installDirLength = wcslen(thisInstallDir); + 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[0 .. installDirLength]; + vcInstallDir[installDirLength .. $] = "\\VC\0"w; + if (_waccess(vcInstallDir.ptr, 0) != 0) + continue; // Visual C++ not included, skip - char[260] path; - int len = WideCharToMultiByte(CP_UTF8, 0, bstrInstallDir, -1, path.ptr, 260, null, null); - SysFreeString(bstrInstallDir); + if (versionString) + { + SysFreeString(versionString); + SysFreeString(installDir); + } + versionString = thisVersionString; + installDir = thisInstallDir; + thisVersionString = null; + thisInstallDir = null; + } - if (len > 0) - return path[0..len].idup.ptr; + if (installDir) + { + char[260] path = void; + int len = WideCharToMultiByte(CP_UTF8, 0, installDir, -1, path.ptr, path.length, null, null); + assert(len); + + return path[0 .. len].idup.ptr; } + return null; } diff --git a/dmd/vsoptions.h b/dmd/vsoptions.h new file mode 100644 index 00000000000..ef7703df01e --- /dev/null +++ b/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 diff --git a/driver/linker-msvc.cpp b/driver/linker-msvc.cpp index aebef25ffcc..c5cc6ca7222 100644 --- a/driver/linker-msvc.cpp +++ b/driver/linker-msvc.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "dmd/errors.h" +#include "driver/args.h" #include "driver/cl_options.h" #include "driver/cl_options_instrumentation.h" #include "driver/cl_options_sanitizers.h" @@ -31,21 +32,27 @@ namespace { -void addMscrtLibs(std::vector &args) { - const auto mscrtlibName = getMscrtLibName(); +void addMscrtLibs(bool useInternalToolchain, std::vector &args) { + const auto mscrtlibName = getMscrtLibName(&useInternalToolchain); args.push_back(("/DEFAULTLIB:" + mscrtlibName).str()); // We need the vcruntime lib for druntime's exception handling (ldc.eh_msvc). // Pick one of the 4 variants matching the selected main UCRT lib. + if (useInternalToolchain) { #if LDC_LLVM_VER >= 400 - if (mscrtlibName.contains_lower("vcruntime")) { + assert(mscrtlibName.contains_lower("vcruntime")); +#endif return; } -#endif +#if LDC_LLVM_VER >= 400 + const bool isStatic = mscrtlibName.contains_lower("libcmt"); +#else // LLVM 3.9: no llvm::StringRef::{contains,find}_lower const bool isStatic = mscrtlibName.startswith_lower("libcmt"); +#endif + const bool isDebug = mscrtlibName.endswith_lower("d") || mscrtlibName.endswith_lower("d.lib"); @@ -85,12 +92,20 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath, fatal(); } - const bool useInternalToolchain = useInternalToolchainForMSVC(); - #ifdef _WIN32 windows::MsvcEnvironmentScope msvcEnv; - if (!useInternalToolchain) - msvcEnv.setup(); + + const bool forceMSVC = env::has(L"LDC_VSDIR_FORCE"); + const bool useInternalToolchain = + (!forceMSVC && getExplicitMscrtLibName().contains_lower("vcruntime")) || + !msvcEnv.setup(); + + if (forceMSVC && useInternalToolchain) { + warning(Loc(), "no Visual C++ installation found for linking, falling back " + "to MinGW-based libraries"); + } +#else + const bool useInternalToolchain = true; #endif // build arguments @@ -121,7 +136,7 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath, } // add C runtime libs - addMscrtLibs(args); + addMscrtLibs(useInternalToolchain, args); // specify creation of DLL if (global.params.dll) { diff --git a/driver/linker.cpp b/driver/linker.cpp index 9517316a867..53b616cbeac 100644 --- a/driver/linker.cpp +++ b/driver/linker.cpp @@ -10,7 +10,6 @@ #include "driver/linker.h" #include "dmd/errors.h" -#include "driver/args.h" #include "driver/cl_options.h" #include "driver/tool.h" #include "gen/llvm.h" @@ -184,28 +183,31 @@ bool linkAgainstSharedDefaultLibs() { ////////////////////////////////////////////////////////////////////////////// -bool useInternalToolchainForMSVC() { -#ifndef _WIN32 - return true; +llvm::StringRef getExplicitMscrtLibName() { return mscrtlib; } + +llvm::StringRef getMscrtLibName(const bool *useInternalToolchain) { + llvm::StringRef name = getExplicitMscrtLibName(); + if (!name.empty()) + return name; + + bool useInternal = false; + if (useInternalToolchain) { + useInternal = *useInternalToolchain; + } else { +#ifdef _WIN32 + static bool haveMSVC = windows::isMsvcAvailable(); + useInternal = !haveMSVC; #else - return !env::has(L"VSINSTALLDIR") && !env::has(L"LDC_VSDIR") && - // LDC_VSDIR_FORCE alone can be used to prefer MSVC toolchain - // auto-detection over the internal toolchain. - !env::has(L"LDC_VSDIR_FORCE"); + useInternal = true; #endif -} + } -llvm::StringRef getMscrtLibName() { - llvm::StringRef name = mscrtlib; - if (name.empty()) { - if (useInternalToolchainForMSVC()) { - name = "vcruntime140"; - } else { - // default to static release variant - name = linkFullyStatic() != llvm::cl::BOU_FALSE ? "libcmt" : "msvcrt"; - } + if (useInternal) { + return "vcruntime140"; + } else { + // default to static release variant + return linkFullyStatic() != llvm::cl::BOU_FALSE ? "libcmt" : "msvcrt"; } - return name; } ////////////////////////////////////////////////////////////////////////////// diff --git a/driver/linker.h b/driver/linker.h index 936cca532a6..e77bb5449d7 100644 --- a/driver/linker.h +++ b/driver/linker.h @@ -40,15 +40,14 @@ llvm::cl::boolOrDefault linkFullyStatic(); bool linkAgainstSharedDefaultLibs(); /** - * Indicates whether the internal 'toolchain' (-link-internally and MinGW-w64 - * libs) is to be used for MSVC targets. + * Returns the value of -mscrtlib. */ -bool useInternalToolchainForMSVC(); +llvm::StringRef getExplicitMscrtLibName(); /** * Returns the name of the MS C runtime library to link with. */ -llvm::StringRef getMscrtLibName(); +llvm::StringRef getMscrtLibName(const bool *useInternalToolchain = nullptr); /** * Inserts bitcode files passed on the commandline into a module. diff --git a/driver/tool.cpp b/driver/tool.cpp index d1dd8b3c7db..a80edcf93cb 100644 --- a/driver/tool.cpp +++ b/driver/tool.cpp @@ -10,6 +10,7 @@ #include "driver/tool.h" #include "dmd/errors.h" +#include "dmd/vsoptions.h" #include "driver/args.h" #include "driver/cl_options.h" #include "driver/exe_path.h" @@ -213,217 +214,100 @@ int executeToolAndWait(const std::string &tool_, namespace windows { -bool needsQuotes(const llvm::StringRef &arg) { - return // not already quoted - !(arg.size() > 1 && arg[0] == '"' && - arg.back() == '"') && // empty or min 1 space or min 1 double quote - (arg.empty() || arg.find(' ') != arg.npos || arg.find('"') != arg.npos); -} - -size_t countPrecedingBackslashes(llvm::StringRef arg, size_t index) { - size_t count = 0; - - for (ptrdiff_t i = index - 1; i >= 0; --i) { - if (arg[i] != '\\') - break; - ++count; - } - - return count; -} - -std::string quoteArg(llvm::StringRef arg) { - if (!needsQuotes(arg)) - return arg; - - std::string quotedArg; - quotedArg.reserve(3 + 2 * arg.size()); // worst case - - quotedArg.push_back('"'); - - const size_t argLength = arg.size(); - for (size_t i = 0; i < argLength; ++i) { - if (arg[i] == '"') { - // Escape all preceding backslashes (if any). - // Note that we *don't* need to escape runs of backslashes that don't - // precede a double quote! See MSDN: - // http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx - quotedArg.append(countPrecedingBackslashes(arg, i), '\\'); - - // Escape the double quote. - quotedArg.push_back('\\'); - } - - quotedArg.push_back(arg[i]); - } - - // Make sure our final double quote doesn't get escaped by a trailing - // backslash. - quotedArg.append(countPrecedingBackslashes(arg, argLength), '\\'); - quotedArg.push_back('"'); - - return quotedArg; -} - -int executeAndWait(const char *commandLine, DWORD creationFlags = 0) { - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - PROCESS_INFORMATION pi; - ZeroMemory(&pi, sizeof(pi)); - - DWORD exitCode; - - llvm::SmallVector wcommandLine; - if (llvm::sys::windows::UTF8ToUTF16(commandLine, wcommandLine)) - return -3; - wcommandLine.push_back(0); - if (!CreateProcessW(nullptr, wcommandLine.data(), nullptr, nullptr, TRUE, - creationFlags, nullptr, nullptr, &si, &pi)) { - exitCode = -1; - } else { - if (WaitForSingleObject(pi.hProcess, INFINITE) != 0 || - !GetExitCodeProcess(pi.hProcess, &exitCode)) - exitCode = -2; - - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - - return exitCode; -} - +namespace { bool setupMsvcEnvironmentImpl( - std::vector> &rollback) { - if (env::has(L"VSINSTALLDIR") && !env::has(L"LDC_VSDIR_FORCE")) + std::vector> *rollback) { + if (env::has(L"VSINSTALLDIR") && !env::has(L"LDC_VSDIR_FORCE")) { + // assume a fully set up environment (e.g., VS native tools command prompt) return true; + } - llvm::SmallString<128> tmpFilePath; - if (llvm::sys::fs::createTemporaryFile("ldc_dumpEnv", "", tmpFilePath)) - return false; + const auto begin = std::chrono::steady_clock::now(); - /* Run `%ComSpec% /s /c "...\dumpEnv.bat > "` to - * dump the MSVC environment to the temporary file. - * - * cmd.exe /c treats the following string argument (the command) - * in a very peculiar way if it starts with a double-quote. - * By adding /s and enclosing the command in extra double-quotes - * (WITHOUT additionally escaping the command), the command will - * be parsed properly. - */ - - std::string cmdExecutable = env::get("ComSpec"); - if (cmdExecutable.empty()) { - warning(Loc(), - "'ComSpec' environment variable is not set, assuming 'cmd.exe'."); - cmdExecutable = "cmd.exe"; - } - std::string batchFile = exe_path::prependBinDir("dumpEnv.bat"); - std::string arch = - global.params.targetTriple->isArch64Bit() ? "amd64" : "x86"; - - llvm::SmallString<512> commandLine; - commandLine += quoteArg(cmdExecutable); - commandLine += " /s /c \""; - commandLine += "chcp 65001 && "; // => UTF-8 output encoding - commandLine += quoteArg(batchFile); - commandLine += ' '; - commandLine += arch; - commandLine += " > "; - commandLine += quoteArg(tmpFilePath); - commandLine += '"'; - - // do NOT pass down our console - const DWORD procCreationFlags = CREATE_NO_WINDOW; - const int exitCode = executeAndWait(commandLine.c_str(), procCreationFlags); - if (exitCode != 0) { - error(Loc(), "'%s' failed with status: %d", commandLine.c_str(), exitCode); - llvm::sys::fs::remove(tmpFilePath); + VSOptions vsOptions; + vsOptions.initialize(); + if (!vsOptions.VSInstallDir) return false; + + const bool x64 = global.params.targetTriple->isArch64Bit(); + + llvm::SmallVector libPaths; + if (auto vclibdir = vsOptions.getVCLibDir(x64)) + libPaths.push_back(vclibdir); + if (auto ucrtlibdir = vsOptions.getUCRTLibPath(x64)) + libPaths.push_back(ucrtlibdir); + if (auto sdklibdir = vsOptions.getSDKLibPath(x64)) + libPaths.push_back(sdklibdir); + + llvm::SmallVector binPaths; + const char *secondaryBindir = nullptr; + if (auto bindir = vsOptions.getVCBinDir(x64, secondaryBindir)) { + binPaths.push_back(bindir); + if (secondaryBindir) + binPaths.push_back(secondaryBindir); } - auto fileBuffer = llvm::MemoryBuffer::getFile(tmpFilePath); - llvm::sys::fs::remove(tmpFilePath); - if (fileBuffer.getError()) + const bool success = libPaths.size() == 3 && !binPaths.empty(); + if (!success) return false; - const auto contents = (*fileBuffer)->getBuffer(); - const auto size = contents.size(); - - // Parse the file. - std::vector> env; - - size_t i = 0; - // for each line - while (i < size) { - llvm::StringRef key, value; - - for (size_t j = i; j < size; ++j) { - const char c = contents[j]; - if (c == '=' && key.empty()) { - key = contents.slice(i, j); - i = j + 1; - } else if (c == '\n' || c == '\r' || c == '\0') { - if (!key.empty()) { - value = contents.slice(i, j); - } - // break and continue with next line - i = j + 1; - break; - } - } - - if (!key.empty() && !value.empty()) - env.emplace_back(key, value); - } + if (!rollback) // check for availability only + return true; if (global.params.verbose) - message("Applying environment variables:"); - - rollback.reserve(env.size()); - bool haveVsInstallDir = false; - - for (const auto &pair : env) { - const llvm::StringRef key = pair.first; - const llvm::StringRef value = pair.second; + message("Prepending to environment variables:"); + + const auto preprendToEnvVar = + [rollback](const char *key, const wchar_t *wkey, + const llvm::SmallVectorImpl &entries) { + wchar_t *originalValue = _wgetenv(wkey); + + llvm::SmallString<256> head; + for (const char *entry : entries) { + if (!head.empty()) + head += ';'; + head += entry; + } - if (key == "VSINSTALLDIR") - haveVsInstallDir = true; + if (global.params.verbose) + message(" %s += %.*s", key, (int)head.size(), head.data()); - llvm::SmallVector wkey; - llvm::SmallVector wvalue; - llvm::sys::windows::UTF8ToUTF16(key, wkey); - llvm::sys::windows::UTF8ToUTF16(value, wvalue); - wkey.push_back(0); - wvalue.push_back(0); + llvm::SmallVector wvalue; + llvm::sys::windows::UTF8ToUTF16(head, wvalue); + if (originalValue) { + wvalue.push_back(L';'); + wvalue.append(originalValue, originalValue + wcslen(originalValue)); + } + wvalue.push_back(0); - wchar_t *originalValue = _wgetenv(wkey.data()); - if (originalValue && wcscmp(originalValue, wvalue.data()) == 0) - continue; // no change + // copy the original value, if set + if (originalValue) + originalValue = wcsdup(originalValue); + rollback->emplace_back(wkey, originalValue); - if (global.params.verbose) { - message(" %.*s=%.*s", (int)key.size(), key.data(), (int)value.size(), - value.data()); - } + SetEnvironmentVariableW(wkey, wvalue.data()); + }; - // copy the original value, if set - if (originalValue) - originalValue = wcsdup(originalValue); - rollback.emplace_back(wkey.data(), originalValue); + rollback->reserve(2); + preprendToEnvVar("LIB", L"LIB", libPaths); + preprendToEnvVar("PATH", L"PATH", binPaths); - SetEnvironmentVariableW(wkey.data(), wvalue.data()); + if (global.params.verbose) { + const auto end = std::chrono::steady_clock::now(); + message("MSVC setup took %lld microseconds", + std::chrono::duration_cast(end - begin) + .count()); } - return haveVsInstallDir; + return true; } +} // anonymous namespace + +bool isMsvcAvailable() { return setupMsvcEnvironmentImpl(nullptr); } bool MsvcEnvironmentScope::setup() { rollback.clear(); - const bool success = setupMsvcEnvironmentImpl(rollback); - if (!success) - warning(Loc(), "no Visual C++ installation detected"); - return success; + return setupMsvcEnvironmentImpl(&rollback); } MsvcEnvironmentScope::~MsvcEnvironmentScope() { diff --git a/driver/tool.h b/driver/tool.h index 54e3f6ec57f..24daec6e3e0 100644 --- a/driver/tool.h +++ b/driver/tool.h @@ -44,6 +44,9 @@ int executeToolAndWait(const std::string &tool, #ifdef _WIN32 namespace windows { +// Returns true if a usable MSVC installation is available. +bool isMsvcAvailable(); + struct MsvcEnvironmentScope { // Tries to set up the MSVC environment variables for the current process and // returns true if successful. The original environment is restored on diff --git a/gen/target.cpp b/gen/target.cpp index cc5e30ff259..1de7a2760bd 100644 --- a/gen/target.cpp +++ b/gen/target.cpp @@ -250,7 +250,8 @@ Expression *Target::getTargetInfo(const char *name_, const Loc &loc) { if (name == "cppRuntimeLibrary") { const char *cppRuntimeLibrary = ""; if (triple.isWindowsMSVCEnvironment()) { - cppRuntimeLibrary = mem.xstrdup(getMscrtLibName().str().c_str()); + auto mscrtlib = getMscrtLibName().str(); + cppRuntimeLibrary = mem.xstrdup(mscrtlib.c_str()); } return createStringExp(cppRuntimeLibrary); } diff --git a/packaging/README.txt b/packaging/README.txt index d659a2fa9c7..c10099d8c70 100644 --- a/packaging/README.txt +++ b/packaging/README.txt @@ -7,34 +7,21 @@ The compiler configuration file is etc\ldc2.conf and can be easily customized to your liking, e.g., adding implicit command-line options and setting up cross- compilation. -The LDC package is portable and ships with LLD, the LLVM linker, as well as -WinSDK & Visual C++ runtime (import) libraries based on MinGW-w64. In order to -run the generated binaries, a Visual C++ 2015 runtime installation is required -(vcruntime140.dll, ucrtbase.dll etc.). +If you have an installed Visual C++ toolchain (Visual Studio/Build Tools 2015 or +newer), LDC defaults to using linker and libraries of the latest Visual C++ +installation it can find. +You can set the LDC_VSDIR environment variable to select a specific version, +e.g., 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community'. +The MSVC toolchain setup is skipped if LDC is run inside a 'VS Native/Cross +Tools Command Prompt' (more precisely, if the VSINSTALLDIR environment variable +is set). You can set the LDC_VSDIR_FORCE environment variable (to some +non-empty value) to enforce setting up the MSVC toolchain. -In case you prefer an official Microsoft toolchain for linking (Visual C++ 2015 -or newer), e.g., to link with the static Microsoft libraries (and thus avoid the -dependency on the Visual C++ runtime installation for your users), you have the -following options: - -* Run LDC in a 'VS Native/Cross Tools Command Prompt' (LDC checks whether the - VSINSTALLDIR environment variable is set). - LDC assumes the environment variables are all set up appropriately. -* Or set the LDC_VSDIR environment variable to some Visual Studio/Visual C++ - Build Tools installation directory, e.g., - 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community'. - LDC will invoke a batch file provided by VS to set up the environment - variables for the selected 32/64-bit target platform, which adds an overhead - of about 1 second for each linking operation. - You can also set LDC_VSDIR_FORCE (to some non-empty value); LDC will then try - to auto-detect your latest Visual C++ installation if you haven't set - LDC_VSDIR, and won't skip the environment setup if VSINSTALLDIR is pre-set. -* Or set up the etc\ldc2.conf config file and specify the directories containing - the MS libs (appending them to the 'lib-dirs' array; check out the LIB - environment variable in a VS tools command prompt) as well as the C runtime - flavor (e.g., appending '-mscrtlib=libcmt' to the 'switches' array). - In case you prefer the MS linker over LLD, add the switch - '-linker='. +If you don't have a Visual C++ installation, LDC falls back to LLD (the LLVM +linker) and the bundled WinSDK & Visual C++ runtime (import) libraries based on +MinGW-w64. In that case, the generated executables and DLLs depend on an +installed (redistributable) Visual C++ 2015+ runtime (vcruntime140.dll, +ucrtbase.dll etc.). For further information, including on how to report bugs, please refer to the LDC wiki: http://wiki.dlang.org/LDC. diff --git a/vcbuild/dumpEnv.bat b/vcbuild/dumpEnv.bat deleted file mode 100644 index 6c2dac090aa..00000000000 --- a/vcbuild/dumpEnv.bat +++ /dev/null @@ -1,7 +0,0 @@ -@echo off -setlocal -:: The single arg specifies the architecture (x86/amd64). -call "%~dp0msvcEnv.bat" %1 > nul -:: Dump all environment variables to stdout. -set -endlocal diff --git a/vcbuild/msvcEnv.bat b/vcbuild/msvcEnv.bat deleted file mode 100644 index 8a0086f9209..00000000000 --- a/vcbuild/msvcEnv.bat +++ /dev/null @@ -1,57 +0,0 @@ -@echo off - -:: Skip detection if an existing LDC_VSDIR environment variable points to an existing folder -if "%LDC_VSDIR%"=="" goto detect -if not "%LDC_VSDIR:~-1%"=="\" set LDC_VSDIR=%LDC_VSDIR%\ -if exist "%LDC_VSDIR%" goto setup - -:: Try to detect the installation directory of the latest VS installation with VC toolchain -:detect -set LDC_VSDIR= -:: VS v15.2+ -if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" ( - for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do ( - if exist "%%i\Common7\Tools\vsdevcmd.bat" ( - set LDC_VSDIR=%%i\ - goto :setup - ) - ) -) -:: VS 2017 -for /f "tokens=1,2*" %%i in ('reg query HKLM\SOFTWARE\Microsoft\VisualStudio\SxS\VS7 /v 15.0 /reg:32 2^> nul') do ( - if exist "%%kVC\Auxiliary\Build\vcvarsall.bat" ( - set LDC_VSDIR=%%k - goto :setup - ) -) -:: VS Build Tools 2017 (default installation path) -if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\BuildTools\" ( - set LDC_VSDIR=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\BuildTools\ - goto :setup -) -:: VC++ 2013 -for /F "tokens=1,2*" %%i in ('reg query HKLM\SOFTWARE\Microsoft\VisualStudio\12.0\Setup\VC /v ProductDir /reg:32 2^> nul') do set LDC_VSDIR=%%k -:: VC++ 2015 -for /F "tokens=1,2*" %%i in ('reg query HKLM\SOFTWARE\Microsoft\VisualStudio\14.0\Setup\VC /v ProductDir /reg:32 2^> nul') do set LDC_VSDIR=%%k -:: (remove 'VC\' suffix) -if not "%LDC_VSDIR%"=="" set LDC_VSDIR=%LDC_VSDIR:~0,-3% - -if "%LDC_VSDIR%"=="" ( - echo WARNING: no Visual C++ installation detected - goto :eof -) - -:: Let MSVC set up environment variables -:setup -echo Using Visual Studio: %LDC_VSDIR:~0,-1% -:: VC++ 2017+ -if exist "%LDC_VSDIR%Common7\Tools\vsdevcmd.bat" ( - call "%LDC_VSDIR%Common7\Tools\vsdevcmd.bat" -arch=%1 -no_logo - goto :eof -) -:: VC++ 2013/2015 -if exist "%LDC_VSDIR%VC\vcvarsall.bat" ( - call "%LDC_VSDIR%VC\vcvarsall.bat" %1 - goto :eof -) -echo WARNING: could not find Visual C++ batch file From ca955cc9178daae1b798b99ce82f09bfcb511560 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Thu, 30 Apr 2020 21:00:54 +0200 Subject: [PATCH 2/6] Revise dmd.vsoptions, switching to wide APIs --- dmd/env.d | 87 ------------- dmd/vsoptions.d | 339 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 235 insertions(+), 191 deletions(-) delete mode 100644 dmd/env.d diff --git a/dmd/env.d b/dmd/env.d deleted file mode 100644 index a73456466dd..00000000000 --- a/dmd/env.d +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Functions for modifying environment variables. - * - * Copyright: Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved - * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) - * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/env.d, env.d) - * Documentation: https://dlang.org/phobos/dmd_env.html - * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/env.d - */ - -module dmd.env; - -import core.stdc.string; -import core.sys.posix.stdlib; -import dmd.globals; -import dmd.root.array; -import dmd.root.rmem; -import dmd.root.string; - -version (Windows) - private extern (C) int putenv(const char*) nothrow; - -/** -Construct a variable from `name` and `value` and put it in the environment while saving -the previous value of the environment variable into a global list so it can be restored later. -Params: - name = the name of the variable - value = the value of the variable -Returns: - true on error, false on success -*/ -bool putenvRestorable(const(char)[] name, const(char)[] value) nothrow -{ - saveEnvVar(name); - const nameValue = allocNameValue(name, value); - const result = putenv(cast(char*)nameValue.ptr); - version (Windows) - mem.xfree(cast(void*)nameValue.ptr); - else - { - if (result) - mem.xfree(cast(void*)nameValue.ptr); - } - return result ? true : false; -} - -/** -Allocate a new variable via xmalloc that can be added to the global environment. The -resulting string will be null-terminated immediately after the end of the array. -Params: - name = name of the variable - value = value of the variable -Returns: - a newly allocated variable that can be added to the global environment -*/ -string allocNameValue(const(char)[] name, const(char)[] value) nothrow -{ - const length = name.length + 1 + value.length; - auto str = (cast(char*)mem.xmalloc(length + 1))[0 .. length]; - str[0 .. name.length] = name[]; - str[name.length] = '='; - str[name.length + 1 .. length] = value[]; - str.ptr[length] = '\0'; - return cast(string)str; -} - -/// Holds the original values of environment variables when they are overwritten. -private __gshared string[string] envNameValues; - -/// Restore the original environment. -void restoreEnvVars() -{ - foreach (var; envNameValues.values) - { - if (putenv(cast(char*)var.ptr)) - assert(0); - } -} - -/// Save the environment variable `name` if not saved already. -void saveEnvVar(const(char)[] name) nothrow -{ - if (!(name in envNameValues)) - { - envNameValues[name.idup] = allocNameValue(name, name.toCStringThen!(n => getenv(n.ptr)).toDString); - } -} diff --git a/dmd/vsoptions.d b/dmd/vsoptions.d index 491f7f95a4c..55c010980bd 100644 --- a/dmd/vsoptions.d +++ b/dmd/vsoptions.d @@ -20,11 +20,12 @@ import core.sys.windows.winbase; import core.sys.windows.windef; import core.sys.windows.winreg; -import dmd.env; +version (IN_LLVM) {} else import dmd.env; import dmd.root.file; import dmd.root.filename; import dmd.root.outbuffer; import dmd.root.rmem; +import dmd.root.string; version (IN_LLVM) { @@ -59,6 +60,9 @@ extern(C++) struct VSOptions detectVCToolsInstallDir(); } +version (IN_LLVM) { /* not needed */ } +else +{ /** * retrieve the name of the default C runtime library * Params: @@ -113,12 +117,13 @@ extern(C++) 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(); } @@ -143,7 +148,7 @@ extern(C++) 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"); + const path = getenv("PATH"w); const pathlen = strlen(path); const addpathlen = strlen(addpath); @@ -155,6 +160,7 @@ extern(C++) struct VSOptions if (putenvRestorable("PATH", npath[0 .. length])) assert(0); mem.xfree(npath); + mem.xfree(path); } return cmdbuf.extractChars(); } @@ -170,10 +176,11 @@ extern(C++) 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"; } +} // !IN_LLVM private: /** @@ -182,35 +189,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) { @@ -225,13 +232,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) { @@ -246,24 +253,33 @@ private: void detectVSInstallDir() { if (VSInstallDir is null) - VSInstallDir = getenv("VSINSTALLDIR"); + VSInstallDir = getenv("VSINSTALLDIR"w); version (IN_LLVM) { if (VSInstallDir is null) - VSInstallDir = getenv("LDC_VSDIR"); + VSInstallDir = getenv("LDC_VSDIR"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 = toUTF8(buffer.ptr); + } if (VSInstallDir is null) foreach (const(char)* ver; supportedPre2017Versions) { - VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir"); + VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir"w); if (VSInstallDir) break; } @@ -275,7 +291,7 @@ version (IN_LLVM) 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"))) @@ -285,8 +301,9 @@ version (IN_LLVM) if (VCInstallDir is null) foreach (const(char)* ver; supportedPre2017Versions) { - auto regPath = FileName.buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC"); - VCInstallDir = GetRegistryString(regPath, "ProductDir"); + auto regPath = buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC"); + VCInstallDir = GetRegistryString(regPath, "ProductDir"w); + mem.xfree(regPath); if (VCInstallDir) break; } @@ -298,7 +315,7 @@ version (IN_LLVM) void detectVCToolsInstallDir() { if (VCToolsInstallDir is null) - VCToolsInstallDir = getenv("VCTOOLSINSTALLDIR"); + VCToolsInstallDir = getenv("VCTOOLSINSTALLDIR"w); if (VCToolsInstallDir is null && VCInstallDir) { @@ -321,7 +338,7 @@ version (IN_LLVM) *p = 0; if (ver && *ver) - VCToolsInstallDir = FileName.buildPath(VCInstallDir, r"Tools\MSVC", ver); + VCToolsInstallDir = buildPath(VCInstallDir, r"Tools\MSVC", ver); } } } @@ -344,10 +361,14 @@ public: */ const(char)* getVCBinDir(bool x64, out const(char)* addpath) const { - static const(char)* linkExists(const(char)* p) + static const(char)* linkExists(scope const(char)*[] dirs...) { - auto lp = FileName.combine(p, "link.exe"); - return FileName.exists(lp) ? p : null; + auto p = buildPathWithSuffix("link.exe", dirs); + const(char)* result = null; + if (FileName.exists(p)) + result = FileName.path(p); + mem.xfree(p); + return result; } const bool isHost64 = isWin64Host(); @@ -357,13 +378,13 @@ public: { 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"); return p; @@ -372,13 +393,13 @@ public: } 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"); 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) @@ -387,13 +408,13 @@ public: { 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"); return p; @@ -407,10 +428,10 @@ public: addpath = FileName.combine(VCInstallDir, r"bin"); 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; @@ -442,7 +463,7 @@ public: const(char)* getUCRTLibPath(bool x64) const { if (UCRTSdkDir && UCRTVersion) - return FileName.buildPath(UCRTSdkDir, "Lib", UCRTVersion, x64 ? r"ucrt\x64" : r"ucrt\x86"); + return buildPath(UCRTSdkDir, "Lib", UCRTVersion, x64 ? r"ucrt\x64" : r"ucrt\x86"); return null; } @@ -457,26 +478,44 @@ public: { if (WindowsSdkDir) { + static const(char)* kernel32Exists(scope const(char)*[] dirs...) + { + auto p = buildPathWithSuffix("kernel32.lib", dirs); + const(char)* result = null; + if (FileName.exists(p)) + result = FileName.path(p); + mem.xfree(p); + return result; + } + 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; + if (WindowsSdkVersion) + { + if (auto p = kernel32Exists(sdk, WindowsSdkVersion, "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; + } } version (IN_LLVM) {} else { // 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; } @@ -490,24 +529,36 @@ extern(D): // one with the largest version that also contains the test file static const(char)* findLatestSDKDir(const(char)* baseDir, const(char)* testfile) { - auto allfiles = FileName.combine(baseDir, "*"); - WIN32_FIND_DATAA fileinfo; - HANDLE h = FindFirstFileA(allfiles, &fileinfo); + wchar[] wbase = toUTF16(baseDir); + wchar* allfiles = cast(wchar*) mem.xmalloc_noscan((wbase.length + 3) * wchar.sizeof); + scope(exit) mem.xfree(allfiles); + allfiles[0 .. wbase.length] = wbase; + allfiles[wbase.length .. wbase.length+3] = "\\*\0"; + mem.xfree(wbase.ptr); + + WIN32_FIND_DATAW fileinfo; + HANDLE h = FindFirstFileW(allfiles, &fileinfo); if (h == INVALID_HANDLE_VALUE) return null; - char* res = null; + 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); - } + { + auto name = toUTF8(fileinfo.cFileName.ptr); + if ((!res || strcmp(res, name) < 0) && + FileName.exists(buildPath(baseDir, name, testfile))) + { + if (res) + mem.xfree(res); + res = name; + } + else + mem.xfree(name); + } } - while(FindNextFileA(h, &fileinfo)); + while(FindNextFileW(h, &fileinfo)); if (!FindClose(h)) res = null; @@ -522,9 +573,9 @@ extern(D): * softwareKeyPath = path below HKLM\SOFTWARE * valueName = name of the value to read * Returns: - * the registry value if it exists and has string type + * the registry value (in UTF8) if it exists and has string type */ - const(char)* GetRegistryString(const(char)* softwareKeyPath, const(char)* valueName) const + const(char)* GetRegistryString(const(char)* softwareKeyPath, wstring valueName) const { enum x64hive = false; // VS registry entries always in 32-bit hive @@ -548,19 +599,24 @@ extern(D): 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; - // TODO: wide API - 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) return null; - scope char[] pbuf = new char[cnt + 1]; - RegQueryValueExA(key, valueName, null, &type, cast(ubyte*) pbuf.ptr, &cnt); - return pbuf.ptr; + wchar* wideValue = buf.ptr; + scope(exit) wideValue != buf.ptr && mem.xfree(wideValue); + if (hr == ERROR_MORE_DATA) + { + wideValue = cast(wchar*) mem.xmalloc_noscan((size + 1) * wchar.sizeof); + hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) wideValue, &size); + } + if (hr != 0 || size <= 0) + return null; + + return toUTF8(wideValue); } /*** @@ -594,18 +650,103 @@ extern(D): } } +private: + +char* toUTF8(const(wchar)* wide) +{ + if (!wide) + return null; + + const requiredSize = WideCharToMultiByte(CP_UTF8, 0, wide, -1, null, 0, null, null); + char* value = cast(char*) mem.xmalloc_noscan(requiredSize); + const size = WideCharToMultiByte(CP_UTF8, 0, wide, -1, value, requiredSize, null, null); + assert(size == requiredSize); + return value; +} + +wchar[] toUTF16(const(char)* utf8) +{ + if (!utf8) + return null; + + const requiredCount = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, null, 0); + wchar* wide = cast(wchar*) mem.xmalloc_noscan(requiredCount * wchar.sizeof); + const count = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, requiredCount); + assert(count == requiredCount); + return wide[0 .. count-1]; +} + +extern(C) wchar* _wgetenv(const(wchar)* name); + +char* getenv(wstring name) +{ + if (auto wide = _wgetenv(name.ptr)) + return toUTF8(wide); + return null; +} + +char* buildPathWithSuffix(string suffix, const(char)*[] dirs...) +{ + assert(dirs.length); + + const(char)[][] ddirs = (cast(const(char)[]*) mem.xmalloc_noscan(dirs.length * string.sizeof))[0 .. dirs.length]; + scope(exit) mem.xfree(ddirs.ptr); + + size_t size; + foreach (i, dir; dirs) + { + ddirs[i] = dir.toDString; + size += ddirs[i].length + 1; + } + if (suffix.length) + size += suffix.length + 1; + + char* p = cast(char*) mem.xmalloc_noscan(size); + size_t length; + foreach (dir; ddirs) + { + assert(dir.length); + p[length .. length + dir.length] = dir; + length += dir.length; + const last = p[length - 1]; + if (last != '\\' && last != '/') + p[length++] = '\\'; + } + + if (suffix.length) + { + p[length .. length + suffix.length] = suffix; + length += suffix.length; + p[length] = 0; + } + else + p[--length] = 0; // overwrite last '\' with null terminator + + return p; +} + +char* buildPath(const(char)*[] fragments...) +{ + return buildPathWithSuffix(null, fragments); +} + +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.winnls : MultiByteToWideChar, WideCharToMultiByte, CP_UTF8; +import core.sys.windows.oleauto : SysFreeString, SysStringLen; pragma(lib, "ole32.lib"); pragma(lib, "oleaut32.lib"); -extern (C) int _waccess(const(wchar)* _FileName, int _AccessMode); - interface ISetupInstance : IUnknown { // static const GUID iid = uuid("B41463C3-8866-43B5-BC33-2B0676F7F42E"); @@ -662,54 +803,44 @@ const(char)* detectVSInstallDirViaCOM() return null; scope(exit) instances.Release(); - BSTR versionString; - BSTR installDir; - scope(exit) SysFreeString(versionString); - scope(exit) SysFreeString(installDir); + static struct WrappedBString + { + BSTR ptr; + this(this) @disable; + ~this() { SysFreeString(ptr); } + bool opCast(T : bool)() { 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 thisVersionString; - if (instance.GetInstallationVersion(&thisVersionString) != S_OK) + WrappedBString thisVersionString, thisInstallDir; + if (instance.GetInstallationVersion(&thisVersionString.ptr) != S_OK) continue; - scope(exit) SysFreeString(thisVersionString); - - BSTR thisInstallDir; - if (instance.GetInstallationPath(&thisInstallDir) != S_OK) + if (instance.GetInstallationPath(&thisInstallDir.ptr) != S_OK) continue; - scope(exit) SysFreeString(thisInstallDir); - if (versionString && wcscmp(thisVersionString, versionString) <= 0) + if (versionString && wcscmp(thisVersionString.ptr, versionString.ptr) <= 0) continue; // not a newer version, skip - const installDirLength = wcslen(thisInstallDir); + 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[0 .. installDirLength]; + vcInstallDir[0 .. installDirLength] = thisInstallDir.ptr[0 .. installDirLength]; vcInstallDir[installDirLength .. $] = "\\VC\0"w; - if (_waccess(vcInstallDir.ptr, 0) != 0) + if (!exists(vcInstallDir.ptr)) continue; // Visual C++ not included, skip - if (versionString) - { - SysFreeString(versionString); - SysFreeString(installDir); - } - versionString = thisVersionString; - installDir = thisInstallDir; - thisVersionString = null; - thisInstallDir = null; + thisVersionString.moveTo(versionString); + thisInstallDir.moveTo(installDir); } if (installDir) - { - char[260] path = void; - int len = WideCharToMultiByte(CP_UTF8, 0, installDir, -1, path.ptr, path.length, null, null); - assert(len); - - return path[0 .. len].idup.ptr; - } + return toUTF8(installDir.ptr); return null; } From e436cb0aada0af59b8d82d615504473bb13bc60d Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Fri, 1 May 2020 01:57:16 +0200 Subject: [PATCH 3/6] Don't skip MSVC setup in a preset environment if the target arch doesn't match This doesn't work for VS 2015, but 2017 and 2019. For VS 2015, I haven't found a suitable env var, and I don't want to parse LIB and/or PATH to guess the target architecture (LDC_VSDIR_FORCE can still be used). --- driver/tool.cpp | 12 ++++++++---- packaging/README.txt | 7 +++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/driver/tool.cpp b/driver/tool.cpp index a80edcf93cb..b632f65418b 100644 --- a/driver/tool.cpp +++ b/driver/tool.cpp @@ -217,9 +217,15 @@ namespace windows { namespace { bool setupMsvcEnvironmentImpl( std::vector> *rollback) { + const bool x64 = global.params.targetTriple->isArch64Bit(); + if (env::has(L"VSINSTALLDIR") && !env::has(L"LDC_VSDIR_FORCE")) { - // assume a fully set up environment (e.g., VS native tools command prompt) - return true; + // Assume a fully set up environment (e.g., VS native tools command prompt). + // Skip the MSVC setup unless the environment is set up for a different + // target architecture. + const auto tgtArch = env::get("VSCMD_ARG_TGT_ARCH"); // VS 2017+ + if (tgtArch.empty() || tgtArch == (x64 ? "x64" : "x86")) + return true; } const auto begin = std::chrono::steady_clock::now(); @@ -229,8 +235,6 @@ bool setupMsvcEnvironmentImpl( if (!vsOptions.VSInstallDir) return false; - const bool x64 = global.params.targetTriple->isArch64Bit(); - llvm::SmallVector libPaths; if (auto vclibdir = vsOptions.getVCLibDir(x64)) libPaths.push_back(vclibdir); diff --git a/packaging/README.txt b/packaging/README.txt index c10099d8c70..b833430ee39 100644 --- a/packaging/README.txt +++ b/packaging/README.txt @@ -12,10 +12,9 @@ newer), LDC defaults to using linker and libraries of the latest Visual C++ installation it can find. You can set the LDC_VSDIR environment variable to select a specific version, e.g., 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community'. -The MSVC toolchain setup is skipped if LDC is run inside a 'VS Native/Cross -Tools Command Prompt' (more precisely, if the VSINSTALLDIR environment variable -is set). You can set the LDC_VSDIR_FORCE environment variable (to some -non-empty value) to enforce setting up the MSVC toolchain. +MSVC toolchain detection and setup is skipped if LDC is run inside a +'VS Native/Cross Tools Command Prompt' (more precisely, if the VSINSTALLDIR +environment variable is set). If you don't have a Visual C++ installation, LDC falls back to LLD (the LLVM linker) and the bundled WinSDK & Visual C++ runtime (import) libraries based on From fbd9e924ac1190e292c5ced5acab7456184fa260 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Fri, 1 May 2020 02:34:28 +0200 Subject: [PATCH 4/6] Small dmd.vsoptions refactoring --- dmd/vsoptions.d | 88 ++++++++++++++++++------------------------------- 1 file changed, 32 insertions(+), 56 deletions(-) diff --git a/dmd/vsoptions.d b/dmd/vsoptions.d index 55c010980bd..18034c295c0 100644 --- a/dmd/vsoptions.d +++ b/dmd/vsoptions.d @@ -27,14 +27,8 @@ import dmd.root.outbuffer; import dmd.root.rmem; import dmd.root.string; -version (IN_LLVM) -{ - enum supportedPre2017Versions = ["14.0".ptr]; -} -else -{ - enum supportedPre2017Versions = ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"]; -} +version (IN_LLVM) enum supportedPre2017Versions = ["14.0".ptr]; +else enum supportedPre2017Versions = ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"]; extern(C++) struct VSOptions { @@ -361,15 +355,7 @@ public: */ const(char)* getVCBinDir(bool x64, out const(char)* addpath) const { - static const(char)* linkExists(scope const(char)*[] dirs...) - { - auto p = buildPathWithSuffix("link.exe", dirs); - const(char)* result = null; - if (FileName.exists(p)) - result = FileName.path(p); - mem.xfree(p); - return result; - } + alias linkExists = returnDirIfContainsFile!"link.exe"; const bool isHost64 = isWin64Host(); if (VCToolsInstallDir !is null) @@ -478,15 +464,7 @@ public: { if (WindowsSdkDir) { - static const(char)* kernel32Exists(scope const(char)*[] dirs...) - { - auto p = buildPathWithSuffix("kernel32.lib", dirs); - const(char)* result = null; - if (FileName.exists(p)) - result = FileName.path(p); - mem.xfree(p); - return result; - } + alias kernel32Exists = returnDirIfContainsFile!"kernel32.lib"; const(char)* arch = x64 ? "x64" : "x86"; auto sdk = FileName.combine(WindowsSdkDir, "lib"); @@ -685,49 +663,50 @@ char* getenv(wstring name) return null; } -char* buildPathWithSuffix(string suffix, const(char)*[] dirs...) +char* buildPath(const(char)*[] fragments...) { - assert(dirs.length); + assert(fragments.length); - const(char)[][] ddirs = (cast(const(char)[]*) mem.xmalloc_noscan(dirs.length * string.sizeof))[0 .. dirs.length]; - scope(exit) mem.xfree(ddirs.ptr); + const(char)[][] f = (cast(const(char)[]*) mem.xmalloc_noscan(fragments.length * string.sizeof))[0 .. fragments.length]; + scope(exit) mem.xfree(f.ptr); size_t size; - foreach (i, dir; dirs) + foreach (i, fragment; fragments) { - ddirs[i] = dir.toDString; - size += ddirs[i].length + 1; + f[i] = fragment.toDString; + size += f[i].length + 1; } - if (suffix.length) - size += suffix.length + 1; char* p = cast(char*) mem.xmalloc_noscan(size); size_t length; - foreach (dir; ddirs) + foreach (fragment; f) { - assert(dir.length); - p[length .. length + dir.length] = dir; - length += dir.length; + assert(fragment.length); + p[length .. length + fragment.length] = fragment; + length += fragment.length; const last = p[length - 1]; if (last != '\\' && last != '/') p[length++] = '\\'; } - if (suffix.length) - { - p[length .. length + suffix.length] = suffix; - length += suffix.length; - p[length] = 0; - } - else - p[--length] = 0; // overwrite last '\' with null terminator + // overwrite last '\' with null terminator + p[--length] = 0; return p; } -char* buildPath(const(char)*[] fragments...) +char* returnDirIfContainsFile(string fileName)(scope const(char)*[] dirs...) { - return buildPathWithSuffix(null, fragments); + auto dirPath = buildPath(dirs); + + auto filePath = FileName.combine(dirPath, fileName); + scope(exit) FileName.free(filePath); + + if (FileName.exists(filePath)) + return dirPath; + + mem.xfree(dirPath); + return null; } extern (C) int _waccess(const(wchar)* _FileName, int _AccessMode); @@ -818,11 +797,11 @@ const(char)* detectVSInstallDirViaCOM() while (instances.Next(1, &instance, &fetched) == S_OK && fetched) { WrappedBString thisVersionString, thisInstallDir; - if (instance.GetInstallationVersion(&thisVersionString.ptr) != S_OK) - continue; - if (instance.GetInstallationPath(&thisInstallDir.ptr) != S_OK) + if (instance.GetInstallationVersion(&thisVersionString.ptr) != S_OK || + instance.GetInstallationPath(&thisInstallDir.ptr) != S_OK) continue; + // FIXME: proper version strings comparison if (versionString && wcscmp(thisVersionString.ptr, versionString.ptr) <= 0) continue; // not a newer version, skip @@ -839,8 +818,5 @@ const(char)* detectVSInstallDirViaCOM() thisInstallDir.moveTo(installDir); } - if (installDir) - return toUTF8(installDir.ptr); - - return null; + return installDir ? toUTF8(installDir.ptr) : null; } From ee23c1fd425dfccc72956f74a7010c6f6facb94b Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Fri, 1 May 2020 18:06:01 +0200 Subject: [PATCH 5/6] Refactor narrow <-> wide string conversions --- dmd/root/filename.d | 111 +++++++++++++++++++++++++++----------------- dmd/vsoptions.d | 68 +++++++++++---------------- 2 files changed, 96 insertions(+), 83 deletions(-) diff --git a/dmd/root/filename.d b/dmd/root/filename.d index fb36d5af76e..3ac407d765c 100644 --- a/dmd/root/filename.d +++ b/dmd/root/filename.d @@ -41,12 +41,12 @@ version (Windows) version (IN_LLVM) { - private enum codepage = CP_UTF8; + private enum CodePage = CP_UTF8; } else { // assume filenames encoded in system default Windows ANSI code page - private enum codepage = CP_ACP; + private enum CodePage = CP_ACP; } } @@ -827,7 +827,7 @@ nothrow: } else version (Windows) { - return name.toCStringThen!((cstr) => cstr.toWStringzThen!((wname) + return name.toWStringzThen!((wname) { const dw = GetFileAttributesW(&wname[0]); if (dw == -1) @@ -836,7 +836,7 @@ nothrow: return 2; else return 1; - })); + }); } else { @@ -1009,26 +1009,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 @@ -1176,6 +1165,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 @@ -1191,28 +1234,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/dmd/vsoptions.d b/dmd/vsoptions.d index 18034c295c0..7793a6e3774 100644 --- a/dmd/vsoptions.d +++ b/dmd/vsoptions.d @@ -25,7 +25,7 @@ import dmd.root.file; import dmd.root.filename; import dmd.root.outbuffer; import dmd.root.rmem; -import dmd.root.string; +import dmd.root.string : toDString; version (IN_LLVM) enum supportedPre2017Versions = ["14.0".ptr]; else enum supportedPre2017Versions = ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"]; @@ -142,7 +142,7 @@ else { // debug info needs DLLs from $(VSInstallDir)\Common7\IDE for most linker versions // so prepend it too the PATH environment variable - const path = getenv("PATH"w); + char* path = getenv("PATH"w); const pathlen = strlen(path); const addpathlen = strlen(addpath); @@ -267,7 +267,7 @@ version (IN_LLVM) // 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 = toUTF8(buffer.ptr); + VSInstallDir = toNarrowStringz(buffer[0 .. numWritten - 1]).ptr; } if (VSInstallDir is null) @@ -507,7 +507,7 @@ extern(D): // one with the largest version that also contains the test file static const(char)* findLatestSDKDir(const(char)* baseDir, const(char)* testfile) { - wchar[] wbase = toUTF16(baseDir); + wchar[] wbase = toWStringz(baseDir.toDString); wchar* allfiles = cast(wchar*) mem.xmalloc_noscan((wbase.length + 3) * wchar.sizeof); scope(exit) mem.xfree(allfiles); allfiles[0 .. wbase.length] = wbase; @@ -524,16 +524,16 @@ extern(D): { if (fileinfo.cFileName[0] >= '1' && fileinfo.cFileName[0] <= '9') { - auto name = toUTF8(fileinfo.cFileName.ptr); - if ((!res || strcmp(res, name) < 0) && - FileName.exists(buildPath(baseDir, name, testfile))) + char[] name = toNarrowStringz(fileinfo.cFileName.ptr.toDString); + if ((!res || strcmp(res, name.ptr) < 0) && + FileName.exists(buildPath(baseDir, name.ptr, testfile))) { if (res) mem.xfree(res); - res = name; + res = name.ptr; } else - mem.xfree(name); + mem.xfree(name.ptr); } } while(FindNextFileW(h, &fileinfo)); @@ -551,7 +551,7 @@ extern(D): * softwareKeyPath = path below HKLM\SOFTWARE * valueName = name of the value to read * Returns: - * the registry value (in UTF8) if it exists and has string type + * the registry value if it exists and has string type */ const(char)* GetRegistryString(const(char)* softwareKeyPath, wstring valueName) const { @@ -581,20 +581,23 @@ extern(D): DWORD size = buf.sizeof; DWORD type; int hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) buf.ptr, &size); - if (type != REG_SZ) + 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 + 1) * wchar.sizeof); + wideValue = cast(wchar*) mem.xmalloc_noscan(size); hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) wideValue, &size); } - if (hr != 0 || size <= 0) + if (hr != 0) return null; - return toUTF8(wideValue); + auto wideLength = size / wchar.sizeof; + if (wideValue[wideLength - 1] == 0) // may or may not be null-terminated + --wideLength; + return toNarrowStringz(wideValue[0 .. wideLength]).ptr; } /*** @@ -630,28 +633,9 @@ extern(D): private: -char* toUTF8(const(wchar)* wide) +inout(wchar)[] toDString(inout(wchar)* s) { - if (!wide) - return null; - - const requiredSize = WideCharToMultiByte(CP_UTF8, 0, wide, -1, null, 0, null, null); - char* value = cast(char*) mem.xmalloc_noscan(requiredSize); - const size = WideCharToMultiByte(CP_UTF8, 0, wide, -1, value, requiredSize, null, null); - assert(size == requiredSize); - return value; -} - -wchar[] toUTF16(const(char)* utf8) -{ - if (!utf8) - return null; - - const requiredCount = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, null, 0); - wchar* wide = cast(wchar*) mem.xmalloc_noscan(requiredCount * wchar.sizeof); - const count = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, requiredCount); - assert(count == requiredCount); - return wide[0 .. count-1]; + return s ? s[0 .. wcslen(s)] : null; } extern(C) wchar* _wgetenv(const(wchar)* name); @@ -659,7 +643,7 @@ extern(C) wchar* _wgetenv(const(wchar)* name); char* getenv(wstring name) { if (auto wide = _wgetenv(name.ptr)) - return toUTF8(wide); + return toNarrowStringz(wide.toDString).ptr; return null; } @@ -720,7 +704,6 @@ bool exists(const(wchar)* path) // COM interfaces to find VS2017+ installations import core.sys.windows.com; import core.sys.windows.wtypes : BSTR; -import core.sys.windows.winnls : MultiByteToWideChar, WideCharToMultiByte, CP_UTF8; import core.sys.windows.oleauto : SysFreeString, SysStringLen; pragma(lib, "ole32.lib"); @@ -787,9 +770,14 @@ const(char)* detectVSInstallDirViaCOM() BSTR ptr; this(this) @disable; ~this() { SysFreeString(ptr); } - bool opCast(T : bool)() { return ptr !is null; } + 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; } + void moveTo(ref WrappedBString other) + { + SysFreeString(other.ptr); + other.ptr = ptr; + ptr = null; + } } WrappedBString versionString, installDir; @@ -818,5 +806,5 @@ const(char)* detectVSInstallDirViaCOM() thisInstallDir.moveTo(installDir); } - return installDir ? toUTF8(installDir.ptr) : null; + return !installDir ? null : toNarrowStringz(installDir.ptr[0 .. installDir.length]).ptr; } From a1817b70c9f6160daa37c8759da59f6565fb6990 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Sun, 3 May 2020 13:27:36 +0200 Subject: [PATCH 6/6] Sync with dlang/dmd#11089 --- dmd/root/filename.d | 84 +++++++++++++++++------------- dmd/vsoptions.d | 122 +++++++++++++++++--------------------------- 2 files changed, 95 insertions(+), 111 deletions(-) diff --git a/dmd/root/filename.d b/dmd/root/filename.d index 3ac407d765c..d38bdcaaddf 100644 --- a/dmd/root/filename.d +++ b/dmd/root/filename.d @@ -376,55 +376,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 diff --git a/dmd/vsoptions.d b/dmd/vsoptions.d index 7793a6e3774..d0132e65bad 100644 --- a/dmd/vsoptions.d +++ b/dmd/vsoptions.d @@ -27,8 +27,8 @@ import dmd.root.outbuffer; import dmd.root.rmem; import dmd.root.string : toDString; -version (IN_LLVM) enum supportedPre2017Versions = ["14.0".ptr]; -else enum supportedPre2017Versions = ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"]; +version (IN_LLVM) private immutable supportedPre2017Versions = ["14.0"]; +else private immutable supportedPre2017Versions = ["14.0", "12.0", "11.0", "10.0", "9.0"]; extern(C++) struct VSOptions { @@ -271,7 +271,7 @@ version (IN_LLVM) } if (VSInstallDir is null) - foreach (const(char)* ver; supportedPre2017Versions) + foreach (ver; supportedPre2017Versions) { VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir"w); if (VSInstallDir) @@ -293,11 +293,11 @@ version (IN_LLVM) // detect from registry (build tools?) if (VCInstallDir is null) - foreach (const(char)* ver; supportedPre2017Versions) + foreach (ver; supportedPre2017Versions) { - auto regPath = buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC"); + auto regPath = FileName.buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC"); VCInstallDir = GetRegistryString(regPath, "ProductDir"w); - mem.xfree(regPath); + FileName.free(regPath.ptr); if (VCInstallDir) break; } @@ -332,7 +332,7 @@ version (IN_LLVM) *p = 0; if (ver && *ver) - VCToolsInstallDir = buildPath(VCInstallDir, r"Tools\MSVC", ver); + VCToolsInstallDir = FileName.buildPath(VCInstallDir.toDString, r"Tools\MSVC", ver.toDString).ptr; } } } @@ -360,64 +360,66 @@ public: const bool isHost64 = isWin64Host(); if (VCToolsInstallDir !is null) { + const vcToolsInstallDir = VCToolsInstallDir.toDString; if (isHost64) { if (x64) { - if (auto p = linkExists(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(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(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(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(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(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(VCInstallDir, r"x86_amd64")) + if (auto p = linkExists(vcInstallDir, r"x86_amd64")) return p; - if (auto p = linkExists(VCInstallDir, r"bin\HostX86\x86")) + if (auto p = linkExists(vcInstallDir, r"bin\HostX86\x86")) return p; } return null; @@ -449,7 +451,7 @@ public: const(char)* getUCRTLibPath(bool x64) const { if (UCRTSdkDir && UCRTVersion) - return 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; } @@ -466,11 +468,11 @@ public: { alias kernel32Exists = returnDirIfContainsFile!"kernel32.lib"; - const(char)* arch = x64 ? "x64" : "x86"; - auto sdk = FileName.combine(WindowsSdkDir, "lib"); + const arch = x64 ? "x64" : "x86"; + const sdk = FileName.combine(WindowsSdkDir.toDString, "lib"); if (WindowsSdkVersion) { - if (auto p = kernel32Exists(sdk, WindowsSdkVersion, "um", arch)) // SDK 10.0 + 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 @@ -505,28 +507,29 @@ 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) { - wchar[] wbase = toWStringz(baseDir.toDString); - wchar* allfiles = cast(wchar*) mem.xmalloc_noscan((wbase.length + 3) * wchar.sizeof); - scope(exit) mem.xfree(allfiles); - allfiles[0 .. wbase.length] = wbase; - allfiles[wbase.length .. wbase.length+3] = "\\*\0"; - mem.xfree(wbase.ptr); + 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(allfiles, &fileinfo); + HANDLE h = FindFirstFileW(wpattern, &fileinfo); if (h == INVALID_HANDLE_VALUE) return null; + const dBaseDir = baseDir.toDString; + char* res; do { 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) && - FileName.exists(buildPath(baseDir, name.ptr, testfile))) + FileName.exists(FileName.buildPath(dBaseDir, name, testfile))) { if (res) mem.xfree(res); @@ -553,7 +556,7 @@ extern(D): * Returns: * the registry value if it exists and has string type */ - const(char)* GetRegistryString(const(char)* softwareKeyPath, wstring valueName) const + const(char)* GetRegistryString(const(char)[] softwareKeyPath, wstring valueName) const { enum x64hive = false; // VS registry entries always in 32-bit hive @@ -563,11 +566,11 @@ extern(D): 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; @@ -647,49 +650,17 @@ char* getenv(wstring name) return null; } -char* buildPath(const(char)*[] fragments...) -{ - assert(fragments.length); - - const(char)[][] f = (cast(const(char)[]*) mem.xmalloc_noscan(fragments.length * string.sizeof))[0 .. fragments.length]; - scope(exit) mem.xfree(f.ptr); - - size_t size; - foreach (i, fragment; fragments) - { - f[i] = fragment.toDString; - size += f[i].length + 1; - } - - char* p = cast(char*) mem.xmalloc_noscan(size); - size_t length; - foreach (fragment; f) - { - assert(fragment.length); - p[length .. length + fragment.length] = fragment; - length += fragment.length; - const last = p[length - 1]; - if (last != '\\' && last != '/') - p[length++] = '\\'; - } - - // overwrite last '\' with null terminator - p[--length] = 0; - - return p; -} - -char* returnDirIfContainsFile(string fileName)(scope const(char)*[] dirs...) +const(char)* returnDirIfContainsFile(string fileName)(scope const(char)[][] dirs...) { - auto dirPath = buildPath(dirs); + auto dirPath = FileName.buildPath(dirs); auto filePath = FileName.combine(dirPath, fileName); - scope(exit) FileName.free(filePath); + scope(exit) FileName.free(filePath.ptr); if (FileName.exists(filePath)) - return dirPath; + return dirPath.ptr; - mem.xfree(dirPath); + FileName.free(dirPath.ptr); return null; } @@ -790,6 +761,7 @@ const(char)* detectVSInstallDirViaCOM() 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) continue; // not a newer version, skip