mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-14 07:09:50 +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
|
@ -754,9 +754,8 @@ if(MSVC)
|
||||||
install(DIRECTORY vcbuild/ DESTINATION ${CMAKE_INSTALL_PREFIX}/bin FILES_MATCHING PATTERN "*.bat")
|
install(DIRECTORY vcbuild/ DESTINATION ${CMAKE_INSTALL_PREFIX}/bin FILES_MATCHING PATTERN "*.bat")
|
||||||
# Also put the VCBuild scripts in the build/bin folder, so that ${PROJECT_BINARY_DIR}/bin/ldc2 is functional.
|
# Also put the VCBuild scripts in the build/bin folder, so that ${PROJECT_BINARY_DIR}/bin/ldc2 is functional.
|
||||||
# This is necessary for the IR tests that use ${PROJECT_BINARY_DIR}/bin/ldc2.
|
# This is necessary for the IR tests that use ${PROJECT_BINARY_DIR}/bin/ldc2.
|
||||||
configure_file(vcbuild/amd64.bat ${PROJECT_BINARY_DIR}/bin/amd64.bat COPYONLY)
|
configure_file(vcbuild/dumpEnv.bat ${PROJECT_BINARY_DIR}/bin/dumpEnv.bat COPYONLY)
|
||||||
configure_file(vcbuild/msvcEnv.bat ${PROJECT_BINARY_DIR}/bin/msvcEnv.bat COPYONLY)
|
configure_file(vcbuild/msvcEnv.bat ${PROJECT_BINARY_DIR}/bin/msvcEnv.bat COPYONLY)
|
||||||
configure_file(vcbuild/x86.bat ${PROJECT_BINARY_DIR}/bin/x86.bat COPYONLY)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||||
|
|
|
@ -501,6 +501,7 @@ static int linkObjToBinaryGcc(bool sharedLib, bool fullyStatic) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
namespace windows {
|
namespace windows {
|
||||||
|
|
||||||
bool needsQuotes(const llvm::StringRef &arg) {
|
bool needsQuotes(const llvm::StringRef &arg) {
|
||||||
return // not already quoted
|
return // not already quoted
|
||||||
!(arg.size() > 1 && arg[0] == '"' &&
|
!(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);
|
(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;
|
size_t count = 0;
|
||||||
|
|
||||||
for (size_t i = index - 1; i >= 0; --i) {
|
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;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string quoteArg(const std::string &arg) {
|
std::string quoteArg(llvm::StringRef arg) {
|
||||||
if (!needsQuotes(arg))
|
if (!needsQuotes(arg))
|
||||||
return arg;
|
return arg;
|
||||||
|
|
||||||
|
@ -529,7 +530,7 @@ std::string quoteArg(const std::string &arg) {
|
||||||
|
|
||||||
quotedArg.push_back('"');
|
quotedArg.push_back('"');
|
||||||
|
|
||||||
const size_t argLength = arg.length();
|
const size_t argLength = arg.size();
|
||||||
for (size_t i = 0; i < argLength; ++i) {
|
for (size_t i = 0; i < argLength; ++i) {
|
||||||
if (arg[i] == '"') {
|
if (arg[i] == '"') {
|
||||||
// Escape all preceding backslashes (if any).
|
// Escape all preceding backslashes (if any).
|
||||||
|
@ -587,109 +588,110 @@ int executeAndWait(const char *commandLine) {
|
||||||
|
|
||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int executeMsvcToolAndWait(const std::string &tool,
|
bool setupMsvcEnvironment() {
|
||||||
const std::vector<std::string> &args, bool verbose) {
|
if (getenv("VSINSTALLDIR"))
|
||||||
llvm::SmallString<1024> commandLine; // full command line incl. executable
|
return true;
|
||||||
|
|
||||||
// if the VSINSTALLDIR environment variable is NOT set,
|
llvm::SmallString<128> tmpFilePath;
|
||||||
// the MSVC environment needs to be set up
|
if (llvm::sys::fs::createTemporaryFile("ldc_dumpEnv", "", tmpFilePath))
|
||||||
const bool needMsvcSetup = !getenv("VSINSTALLDIR");
|
return false;
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
auto comspecEnv = getenv("ComSpec");
|
/* Run `%ComSpec% /s /c "...\dumpEnv.bat <x86|amd64> > <tmpFilePath>"` to dump
|
||||||
if (!comspecEnv) {
|
* the MSVC environment to the temporary file.
|
||||||
warning(Loc(),
|
*
|
||||||
"'ComSpec' environment variable is not set, assuming 'cmd.exe'.");
|
* cmd.exe /c treats the following string argument (the command)
|
||||||
comspecEnv = "cmd.exe";
|
* in a very peculiar way if it starts with a double-quote.
|
||||||
}
|
* By adding /s and enclosing the command in extra double-quotes
|
||||||
std::string cmdExecutable = comspecEnv;
|
* (WITHOUT additionally escaping the command), the command will
|
||||||
std::string batchFile = exe_path::prependBinDir(
|
* be parsed properly.
|
||||||
global.params.targetTriple->isArch64Bit() ? "amd64.bat" : "x86.bat");
|
*/
|
||||||
|
|
||||||
commandLine.append(windows::quoteArg(cmdExecutable));
|
auto comspecEnv = getenv("ComSpec");
|
||||||
commandLine.append(" /s /c \"");
|
if (!comspecEnv) {
|
||||||
commandLine.append(windows::quoteArg(batchFile));
|
warning(Loc(),
|
||||||
commandLine.push_back(' ');
|
"'ComSpec' environment variable is not set, assuming 'cmd.exe'.");
|
||||||
commandLine.append(windows::quoteArg(tool));
|
comspecEnv = "cmd.exe";
|
||||||
} else {
|
|
||||||
std::string toolPath = getProgram(tool.c_str());
|
|
||||||
commandLine.append(windows::quoteArg(toolPath));
|
|
||||||
}
|
}
|
||||||
|
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();
|
llvm::SmallString<512> commandLine;
|
||||||
|
commandLine += quoteArg(cmdExecutable);
|
||||||
// append (quoted) args
|
commandLine += " /s /c \"";
|
||||||
for (size_t i = 0; i < args.size(); ++i) {
|
commandLine += quoteArg(batchFile);
|
||||||
commandLine.push_back(' ');
|
commandLine += ' ';
|
||||||
commandLine.append(windows::quoteArg(args[i]));
|
commandLine += arch;
|
||||||
}
|
commandLine += " > ";
|
||||||
|
commandLine += quoteArg(tmpFilePath);
|
||||||
const bool useResponseFile = (!args.empty() && commandLine.size() > 2000);
|
commandLine += '"';
|
||||||
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);
|
|
||||||
|
|
||||||
|
const int exitCode = executeAndWait(commandLine.c_str());
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
commandLine.resize(commandLineLengthAfterTool);
|
|
||||||
if (needMsvcSetup)
|
|
||||||
commandLine.push_back('"');
|
|
||||||
error(Loc(), "`%s` failed with status: %d", commandLine.c_str(), exitCode);
|
error(Loc(), "`%s` failed with status: %d", commandLine.c_str(), exitCode);
|
||||||
|
llvm::sys::fs::remove(tmpFilePath);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useResponseFile)
|
auto fileBuffer = llvm::MemoryBuffer::getFile(tmpFilePath);
|
||||||
llvm::sys::fs::remove(responseFilePath);
|
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
|
} // namespace windows
|
||||||
|
|
||||||
int executeMsvcToolAndWait(const std::string &,
|
|
||||||
const std::vector<std::string> &, bool) {
|
|
||||||
assert(0);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -698,7 +700,7 @@ int executeMsvcToolAndWait(const std::string &,
|
||||||
static int linkObjToBinaryMSVC(bool sharedLib) {
|
static int linkObjToBinaryMSVC(bool sharedLib) {
|
||||||
Logger::println("*** Linking executable ***");
|
Logger::println("*** Linking executable ***");
|
||||||
|
|
||||||
std::string tool = "link.exe";
|
const std::string tool = "link.exe";
|
||||||
|
|
||||||
// build arguments
|
// build arguments
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
|
@ -798,8 +800,12 @@ static int linkObjToBinaryMSVC(bool sharedLib) {
|
||||||
}
|
}
|
||||||
logstr << "\n"; // FIXME where's flush ?
|
logstr << "\n"; // FIXME where's flush ?
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
windows::setupMsvcEnvironment();
|
||||||
|
#endif
|
||||||
|
|
||||||
// try to call linker
|
// 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
|
#endif
|
||||||
|
|
||||||
// try to call archiver
|
#ifdef _WIN32
|
||||||
const int exitCode =
|
if (isTargetMSVC)
|
||||||
isTargetMSVC ? executeMsvcToolAndWait(tool, args, global.params.verbose)
|
windows::setupMsvcEnvironment();
|
||||||
: executeToolAndWait(tool, args, global.params.verbose);
|
#endif
|
||||||
|
|
||||||
return exitCode;
|
// try to call archiver
|
||||||
|
return executeToolAndWait(tool, args, global.params.verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -164,8 +164,7 @@ static void assemble(const std::string &asmpath, const std::string &objpath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the compiler to assembly the program.
|
// Run the compiler to assembly the program.
|
||||||
std::string gcc(getGcc());
|
int R = executeToolAndWait(getGcc(), args, global.params.verbose);
|
||||||
int R = executeToolAndWait(gcc, args, global.params.verbose);
|
|
||||||
if (R) {
|
if (R) {
|
||||||
error(Loc(), "Error while invoking external assembler.");
|
error(Loc(), "Error while invoking external assembler.");
|
||||||
fatal();
|
fatal();
|
||||||
|
|
|
@ -11,8 +11,16 @@
|
||||||
#include "mars.h"
|
#include "mars.h"
|
||||||
#include "llvm/Support/Program.h"
|
#include "llvm/Support/Program.h"
|
||||||
|
|
||||||
int executeToolAndWait(const std::string &tool,
|
int executeToolAndWait(const std::string &tool_,
|
||||||
std::vector<std::string> const &args, bool verbose) {
|
std::vector<std::string> const &args, bool verbose) {
|
||||||
|
auto fullToolOrError = llvm::sys::findProgramByName(tool_);
|
||||||
|
if (fullToolOrError.getError()) {
|
||||||
|
error(Loc(), "failed to locate binary %s", tool_.c_str());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tool = std::move(*fullToolOrError);
|
||||||
|
|
||||||
// Construct real argument list.
|
// Construct real argument list.
|
||||||
// First entry is the tool itself, last entry must be NULL.
|
// First entry is the tool itself, last entry must be NULL.
|
||||||
std::vector<const char *> realargs;
|
std::vector<const char *> realargs;
|
||||||
|
|
|
@ -21,7 +21,7 @@ static cl::opt<std::string>
|
||||||
static cl::opt<std::string> ar("ar", cl::desc("Archiver"), cl::Hidden,
|
static cl::opt<std::string> ar("ar", cl::desc("Archiver"), cl::Hidden,
|
||||||
cl::ZeroOrMore);
|
cl::ZeroOrMore);
|
||||||
|
|
||||||
static std::string findProgramByName(const std::string &name) {
|
static std::string findProgramByName(llvm::StringRef name) {
|
||||||
#if LDC_LLVM_VER >= 306
|
#if LDC_LLVM_VER >= 306
|
||||||
llvm::ErrorOr<std::string> res = llvm::sys::findProgramByName(name);
|
llvm::ErrorOr<std::string> res = llvm::sys::findProgramByName(name);
|
||||||
return res ? res.get() : std::string();
|
return res ? res.get() : std::string();
|
||||||
|
@ -35,9 +35,8 @@ static std::string getProgram(const char *name, const cl::opt<std::string> *opt,
|
||||||
std::string path;
|
std::string path;
|
||||||
const char *prog = nullptr;
|
const char *prog = nullptr;
|
||||||
|
|
||||||
if (opt && opt->getNumOccurrences() > 0 && opt->length() > 0 &&
|
if (opt && !opt->empty()) {
|
||||||
(prog = opt->c_str())) {
|
path = findProgramByName(opt->c_str());
|
||||||
path = findProgramByName(prog);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.empty() && envVar && (prog = getenv(envVar)) && prog[0] != '\0') {
|
if (path.empty() && envVar && (prog = getenv(envVar)) && prog[0] != '\0') {
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
@echo off
|
|
||||||
setlocal
|
|
||||||
call "%~dp0msvcEnv.bat" amd64
|
|
||||||
:: Invoke the actual command, represented by all args
|
|
||||||
%*
|
|
||||||
endlocal && exit /b %ERRORLEVEL%
|
|
7
vcbuild/dumpEnv.bat
Normal file
7
vcbuild/dumpEnv.bat
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
:: The single arg specifies the architecture (x86/amd64).
|
||||||
|
call "%~dp0msvcEnv.bat" %1 > nul
|
||||||
|
:: Dump all environment variables to stdout.
|
||||||
|
set
|
||||||
|
endlocal
|
|
@ -1,6 +0,0 @@
|
||||||
@echo off
|
|
||||||
setlocal
|
|
||||||
call "%~dp0msvcEnv.bat" x86
|
|
||||||
:: Invoke the actual command, represented by all args
|
|
||||||
%*
|
|
||||||
endlocal && exit /b %ERRORLEVEL%
|
|
Loading…
Add table
Add a link
Reference in a new issue