diff --git a/driver/linker.cpp b/driver/linker.cpp index 5d0673b556..f9d6db08b5 100644 --- a/driver/linker.cpp +++ b/driver/linker.cpp @@ -25,16 +25,10 @@ #include "llvm/Linker/Linker.h" #include "llvm/ProfileData/InstrProf.h" #include "llvm/Support/FileSystem.h" -#include "llvm/Support/Program.h" #include "llvm/Support/Path.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Target/TargetOptions.h" -#if _WIN32 -#include "llvm/Support/SystemUtils.h" -#include "llvm/Support/ConvertUTF.h" -#include -#endif #include @@ -498,205 +492,6 @@ static int linkObjToBinaryGcc(bool sharedLib, bool fullyStatic) { ////////////////////////////////////////////////////////////////////////////// -#ifdef _WIN32 - -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 (size_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) { - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - PROCESS_INFORMATION pi; - ZeroMemory(&pi, sizeof(pi)); - - DWORD exitCode; - -#if UNICODE - std::wstring wcommandLine; - if (!llvm::ConvertUTF8toWide(commandLine, wcommandLine)) - return -3; - auto cmdline = const_cast(wcommandLine.data()); -#else - auto cmdline = const_cast(commandLine); -#endif - // according to MSDN, only CreateProcessW (unicode) may modify the passed - // command line - if (!CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, - NULL, NULL, &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; -} - -bool setupMsvcEnvironment() { - if (getenv("VSINSTALLDIR")) - return true; - - llvm::SmallString<128> tmpFilePath; - if (llvm::sys::fs::createTemporaryFile("ldc_dumpEnv", "", tmpFilePath)) - return false; - - /* 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. - */ - - auto comspecEnv = getenv("ComSpec"); - if (!comspecEnv) { - warning(Loc(), - "'ComSpec' environment variable is not set, assuming 'cmd.exe'."); - comspecEnv = "cmd.exe"; - } - std::string cmdExecutable = comspecEnv; - 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 += quoteArg(batchFile); - commandLine += ' '; - commandLine += arch; - commandLine += " > "; - commandLine += quoteArg(tmpFilePath); - commandLine += '"'; - - const int exitCode = executeAndWait(commandLine.c_str()); - if (exitCode != 0) { - error(Loc(), "`%s` failed with status: %d", commandLine.c_str(), exitCode); - llvm::sys::fs::remove(tmpFilePath); - return false; - } - - auto fileBuffer = llvm::MemoryBuffer::getFile(tmpFilePath); - llvm::sys::fs::remove(tmpFilePath); - if (fileBuffer.getError()) - 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 (global.params.verbose) - fprintf(global.stdmsg, "Applying environment variables:\n"); - - bool haveVsInstallDir = false; - - for (const auto &pair : env) { - const std::string key = pair.first.str(); - const std::string value = pair.second.str(); - - if (global.params.verbose) - fprintf(global.stdmsg, " %s=%s\n", key.c_str(), value.c_str()); - - SetEnvironmentVariableA(key.c_str(), value.c_str()); - - if (key == "VSINSTALLDIR") - haveVsInstallDir = true; - } - - return haveVsInstallDir; -} - -} // namespace windows - -#endif - -////////////////////////////////////////////////////////////////////////////// - static int linkObjToBinaryMSVC(bool sharedLib) { Logger::println("*** Linking executable ***"); diff --git a/driver/tool.cpp b/driver/tool.cpp index 2f33084643..c409210581 100644 --- a/driver/tool.cpp +++ b/driver/tool.cpp @@ -9,7 +9,14 @@ #include "driver/tool.h" #include "mars.h" +#include "driver/exe_path.h" +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Program.h" +#ifdef _WIN32 +#include +#endif int executeToolAndWait(const std::string &tool_, std::vector const &args, bool verbose) { @@ -53,3 +60,202 @@ int executeToolAndWait(const std::string &tool_, } return 0; } + +////////////////////////////////////////////////////////////////////////////// + +#ifdef _WIN32 + +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 (size_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) { + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + DWORD exitCode; + +#if UNICODE + std::wstring wcommandLine; + if (!llvm::ConvertUTF8toWide(commandLine, wcommandLine)) + return -3; + auto cmdline = const_cast(wcommandLine.data()); +#else + auto cmdline = const_cast(commandLine); +#endif + // according to MSDN, only CreateProcessW (unicode) may modify the passed + // command line + if (!CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, + NULL, NULL, &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; +} + +bool setupMsvcEnvironment() { + if (getenv("VSINSTALLDIR")) + return true; + + llvm::SmallString<128> tmpFilePath; + if (llvm::sys::fs::createTemporaryFile("ldc_dumpEnv", "", tmpFilePath)) + return false; + + /* 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. + */ + + auto comspecEnv = getenv("ComSpec"); + if (!comspecEnv) { + warning(Loc(), + "'ComSpec' environment variable is not set, assuming 'cmd.exe'."); + comspecEnv = "cmd.exe"; + } + std::string cmdExecutable = comspecEnv; + 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 += quoteArg(batchFile); + commandLine += ' '; + commandLine += arch; + commandLine += " > "; + commandLine += quoteArg(tmpFilePath); + commandLine += '"'; + + const int exitCode = executeAndWait(commandLine.c_str()); + if (exitCode != 0) { + error(Loc(), "`%s` failed with status: %d", commandLine.c_str(), exitCode); + llvm::sys::fs::remove(tmpFilePath); + return false; + } + + auto fileBuffer = llvm::MemoryBuffer::getFile(tmpFilePath); + llvm::sys::fs::remove(tmpFilePath); + if (fileBuffer.getError()) + 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 (global.params.verbose) + fprintf(global.stdmsg, "Applying environment variables:\n"); + + bool haveVsInstallDir = false; + + for (const auto &pair : env) { + const std::string key = pair.first.str(); + const std::string value = pair.second.str(); + + if (global.params.verbose) + fprintf(global.stdmsg, " %s=%s\n", key.c_str(), value.c_str()); + + SetEnvironmentVariableA(key.c_str(), value.c_str()); + + if (key == "VSINSTALLDIR") + haveVsInstallDir = true; + } + + return haveVsInstallDir; +} + +} // namespace windows + +#endif // _WIN32 diff --git a/driver/tool.h b/driver/tool.h index d4dc5f858d..c96f851abc 100644 --- a/driver/tool.h +++ b/driver/tool.h @@ -1,5 +1,4 @@ -//===-- driver/tool.h - External tool invocation helpers ---------*- C++ -//-*-===// +//===-- driver/tool.h - External tool invocation helpers --------*- C++ -*-===// // // LDC – the LLVM D compiler // @@ -8,7 +7,7 @@ // //===----------------------------------------------------------------------===// // -// Functionaliy for invoking external tools executables, such as the system +// Functionality for invoking external tools executables, such as the system // assembler, linker, ... // //===----------------------------------------------------------------------===// @@ -23,4 +22,14 @@ int executeToolAndWait(const std::string &tool, std::vector const &args, bool verbose = false); +#ifdef _WIN32 + +namespace windows { +// Tries to set up the MSVC environment variables and returns true if +// successful. +bool setupMsvcEnvironment(); +} + +#endif + #endif