mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-07 19:36:06 +03:00
Set up the MSVC environment in a more flexible way
For invoking MS tools such as link.exe and lib.exe, which require compatible PATH, LIB, LIBPATH etc. We previously wrapped the invokation of the tool with a batch file taking care of setting up the environment and then calling the actual tool. I changed this to first invoke another batch file setting up its environment and then dumping all environment variables to a file. LDC then parses this file and updates its own environment variables. Later spawned child processes then inherit the updated environment, and the tools will be located by LLVM in updated PATH. This allows LDC to keep track of the used Visual C++ installation (and could so set up additional runtime libs required since VS 2015, something we would have needed some time back when we still supported VS 2013). It's also required as a prerequisite for integrating LLD as in-process linker on Windows. I quickly looked into the option of setting up the environment manually, but the MS batch files are full of quirks, mixtures of registry and file-system lookups, checking for existence of misc. headers to filter out bogus installations etc. With VS 2017, there's nothing in the registry at all anymore (at least for the Build Tools); MS expose a COM interface for querying the VS installer... But at least they still provide the batch files.
This commit is contained in:
parent
ad3294b3ae
commit
cab165f3d8
8 changed files with 127 additions and 120 deletions
|
@ -501,6 +501,7 @@ 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] == '"' &&
|
||||
|
@ -508,7 +509,7 @@ bool needsQuotes(const llvm::StringRef &arg) {
|
|||
(arg.empty() || arg.find(' ') != arg.npos || arg.find('"') != arg.npos);
|
||||
}
|
||||
|
||||
size_t countPrecedingBackslashes(const std::string &arg, size_t index) {
|
||||
size_t countPrecedingBackslashes(llvm::StringRef arg, size_t index) {
|
||||
size_t count = 0;
|
||||
|
||||
for (size_t i = index - 1; i >= 0; --i) {
|
||||
|
@ -520,7 +521,7 @@ size_t countPrecedingBackslashes(const std::string &arg, size_t index) {
|
|||
return count;
|
||||
}
|
||||
|
||||
std::string quoteArg(const std::string &arg) {
|
||||
std::string quoteArg(llvm::StringRef arg) {
|
||||
if (!needsQuotes(arg))
|
||||
return arg;
|
||||
|
||||
|
@ -529,7 +530,7 @@ std::string quoteArg(const std::string &arg) {
|
|||
|
||||
quotedArg.push_back('"');
|
||||
|
||||
const size_t argLength = arg.length();
|
||||
const size_t argLength = arg.size();
|
||||
for (size_t i = 0; i < argLength; ++i) {
|
||||
if (arg[i] == '"') {
|
||||
// Escape all preceding backslashes (if any).
|
||||
|
@ -587,109 +588,110 @@ int executeAndWait(const char *commandLine) {
|
|||
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
int executeMsvcToolAndWait(const std::string &tool,
|
||||
const std::vector<std::string> &args, bool verbose) {
|
||||
llvm::SmallString<1024> commandLine; // full command line incl. executable
|
||||
bool setupMsvcEnvironment() {
|
||||
if (getenv("VSINSTALLDIR"))
|
||||
return true;
|
||||
|
||||
// if the VSINSTALLDIR environment variable is NOT set,
|
||||
// the MSVC environment needs to be set up
|
||||
const bool needMsvcSetup = !getenv("VSINSTALLDIR");
|
||||
if (needMsvcSetup) {
|
||||
/* <command line> => %ComSpec% /s /c "<batch file> <command line>"
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
llvm::SmallString<128> tmpFilePath;
|
||||
if (llvm::sys::fs::createTemporaryFile("ldc_dumpEnv", "", tmpFilePath))
|
||||
return false;
|
||||
|
||||
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(
|
||||
global.params.targetTriple->isArch64Bit() ? "amd64.bat" : "x86.bat");
|
||||
/* Run `%ComSpec% /s /c "...\dumpEnv.bat <x86|amd64> > <tmpFilePath>"` 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.
|
||||
*/
|
||||
|
||||
commandLine.append(windows::quoteArg(cmdExecutable));
|
||||
commandLine.append(" /s /c \"");
|
||||
commandLine.append(windows::quoteArg(batchFile));
|
||||
commandLine.push_back(' ');
|
||||
commandLine.append(windows::quoteArg(tool));
|
||||
} else {
|
||||
std::string toolPath = getProgram(tool.c_str());
|
||||
commandLine.append(windows::quoteArg(toolPath));
|
||||
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";
|
||||
|
||||
const size_t commandLineLengthAfterTool = commandLine.size();
|
||||
|
||||
// append (quoted) args
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
commandLine.push_back(' ');
|
||||
commandLine.append(windows::quoteArg(args[i]));
|
||||
}
|
||||
|
||||
const bool useResponseFile = (!args.empty() && commandLine.size() > 2000);
|
||||
llvm::SmallString<128> responseFilePath;
|
||||
if (useResponseFile) {
|
||||
const size_t firstArgIndex = commandLineLengthAfterTool + 1;
|
||||
llvm::StringRef content(commandLine.data() + firstArgIndex,
|
||||
commandLine.size() - firstArgIndex);
|
||||
|
||||
if (llvm::sys::fs::createTemporaryFile("ldc_link", "rsp",
|
||||
responseFilePath) ||
|
||||
llvm::sys::writeFileWithEncoding(
|
||||
responseFilePath,
|
||||
content)) // keep encoding (LLVM assumes UTF-8 input)
|
||||
{
|
||||
error(Loc(), "cannot write temporary response file for %s", tool.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// replace all args by @<responseFilePath>
|
||||
std::string responseFileArg = ("@" + responseFilePath).str();
|
||||
commandLine.resize(firstArgIndex);
|
||||
commandLine.append(windows::quoteArg(responseFileArg));
|
||||
}
|
||||
|
||||
if (needMsvcSetup)
|
||||
commandLine.push_back('"');
|
||||
|
||||
const char *finalCommandLine = commandLine.c_str();
|
||||
|
||||
if (verbose) {
|
||||
fprintf(global.stdmsg, finalCommandLine);
|
||||
fprintf(global.stdmsg, "\n");
|
||||
fflush(global.stdmsg);
|
||||
}
|
||||
|
||||
const int exitCode = windows::executeAndWait(finalCommandLine);
|
||||
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) {
|
||||
commandLine.resize(commandLineLengthAfterTool);
|
||||
if (needMsvcSetup)
|
||||
commandLine.push_back('"');
|
||||
error(Loc(), "`%s` failed with status: %d", commandLine.c_str(), exitCode);
|
||||
llvm::sys::fs::remove(tmpFilePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (useResponseFile)
|
||||
llvm::sys::fs::remove(responseFilePath);
|
||||
auto fileBuffer = llvm::MemoryBuffer::getFile(tmpFilePath);
|
||||
llvm::sys::fs::remove(tmpFilePath);
|
||||
if (fileBuffer.getError())
|
||||
return false;
|
||||
|
||||
return exitCode;
|
||||
const auto contents = (*fileBuffer)->getBuffer();
|
||||
const auto size = contents.size();
|
||||
|
||||
// Parse the file.
|
||||
std::vector<std::pair<llvm::StringRef, llvm::StringRef>> 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;
|
||||
}
|
||||
|
||||
#else // !_WIN32
|
||||
|
||||
int executeMsvcToolAndWait(const std::string &,
|
||||
const std::vector<std::string> &, bool) {
|
||||
assert(0);
|
||||
return -1;
|
||||
}
|
||||
} // namespace windows
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -698,7 +700,7 @@ int executeMsvcToolAndWait(const std::string &,
|
|||
static int linkObjToBinaryMSVC(bool sharedLib) {
|
||||
Logger::println("*** Linking executable ***");
|
||||
|
||||
std::string tool = "link.exe";
|
||||
const std::string tool = "link.exe";
|
||||
|
||||
// build arguments
|
||||
std::vector<std::string> args;
|
||||
|
@ -798,8 +800,12 @@ static int linkObjToBinaryMSVC(bool sharedLib) {
|
|||
}
|
||||
logstr << "\n"; // FIXME where's flush ?
|
||||
|
||||
#ifdef _WIN32
|
||||
windows::setupMsvcEnvironment();
|
||||
#endif
|
||||
|
||||
// try to call linker
|
||||
return executeMsvcToolAndWait(tool, args, global.params.verbose);
|
||||
return executeToolAndWait(tool, args, global.params.verbose);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -912,12 +918,13 @@ int createStaticLibrary() {
|
|||
}
|
||||
#endif
|
||||
|
||||
// try to call archiver
|
||||
const int exitCode =
|
||||
isTargetMSVC ? executeMsvcToolAndWait(tool, args, global.params.verbose)
|
||||
: executeToolAndWait(tool, args, global.params.verbose);
|
||||
#ifdef _WIN32
|
||||
if (isTargetMSVC)
|
||||
windows::setupMsvcEnvironment();
|
||||
#endif
|
||||
|
||||
return exitCode;
|
||||
// try to call archiver
|
||||
return executeToolAndWait(tool, args, global.params.verbose);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue