summaryrefslogtreecommitdiffstats
path: root/source/core/windows/slang-win-process-util.cpp
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2019-06-12 09:05:40 -0400
committerGitHub <noreply@github.com>2019-06-12 09:05:40 -0400
commit9d514e65f00dde0e309f33591f31fbf7f132a005 (patch)
tree7e3a751377651ef9eda06f0b1ad345af1796c596 /source/core/windows/slang-win-process-util.cpp
parentfc083a75b94ac4b4e735b4a7ff566191b9123f74 (diff)
Runtime execution of Visual Studio Compiler (#978)
* Work in progress to be able to invoke VS from within code. * First pass at windows version of refactor of OSProcessSpawner * Closer to getting VS path lookup working. * Make OSString assignable/ctor able * Work out program files directory directly, so don't have to expand %%. * WIP: Improve handling of process spawning. * Add support for splitting input by line. * * Correctly locates visual studio install * Added functionality to invoke vs via cmd * Add option to execute the command line. * Handle in ProcessUtil for windows -> WinHandle. * Rename files slang-win-visual-studio-util.cpp/.h and slang-process-util.h * First pass at unix/linux version of ProcessUtil. * Fix reading Visual Studio path from the registry. * Get compiling on linux with. * Fix vcvarsall.bat name * Use ProcessUtil to execute external code. * Remove OSProcessSpawner. * Remove includes for "os.h" where no longer needed. * Fix tabbing issue in premake5.lua Remove test code from slang-test-main.cpp * Fix premake4.lua tabbing issue. * Small fixes to slang-process-util.h Init ExecuteResult on Win execute. * Improve comments. * Fix bug in StringUtil::calcLines - with oddly terminated source input being able to read past end. Make slang-generate use StringUtil over it's own impl. * Fix off by one bug in working out Visual Studio version. * Fix bug in calculating Visual Studio Version * Fix compilation on linux with string parameter being passed to messageFormat. * Remove erroneous use of kOSError codes - use Result.
Diffstat (limited to 'source/core/windows/slang-win-process-util.cpp')
-rw-r--r--source/core/windows/slang-win-process-util.cpp338
1 files changed, 338 insertions, 0 deletions
diff --git a/source/core/windows/slang-win-process-util.cpp b/source/core/windows/slang-win-process-util.cpp
new file mode 100644
index 000000000..424b87128
--- /dev/null
+++ b/source/core/windows/slang-win-process-util.cpp
@@ -0,0 +1,338 @@
+// slang-win-process-util.cpp
+#include "../slang-process-util.h"
+
+#include "../slang-string.h"
+
+#ifdef _WIN32
+// Include Windows header in a way that minimized namespace pollution.
+// TODO: We could try to avoid including this at all, but it would
+// mean trying to hide certain struct layouts, which would add
+// more dynamic allocation.
+# define WIN32_LEAN_AND_MEAN
+# define NOMINMAX
+# include <Windows.h>
+# undef WIN32_LEAN_AND_MEAN
+# undef NOMINMAX
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Slang {
+
+namespace { // anonymous
+
+struct ThreadInfo
+{
+ HANDLE file;
+ String output;
+};
+
+// Has behavior very similar to unique_ptr - assignment is a move.
+class WinHandle
+{
+public:
+ /// Detach the encapsulated handle. Returns the handle (which now must be externally handled)
+ HANDLE detach() { HANDLE handle = m_handle; m_handle = nullptr; return handle; }
+
+ /// Return as a handle
+ operator HANDLE() const { return m_handle; }
+
+ /// Assign
+ void operator=(HANDLE handle) { setNull(); m_handle = handle; }
+ void operator=(WinHandle&& rhs) { HANDLE handle = m_handle; m_handle = rhs.m_handle; rhs.m_handle = handle; }
+
+ /// Get ready for writing
+ SLANG_FORCE_INLINE HANDLE* writeRef() { setNull(); return &m_handle; }
+ /// Get for read access
+ SLANG_FORCE_INLINE const HANDLE* readRef() const { return &m_handle; }
+
+ void setNull()
+ {
+ if (m_handle)
+ {
+ CloseHandle(m_handle);
+ m_handle = nullptr;
+ }
+ }
+
+ /// Ctor
+ WinHandle(HANDLE handle = nullptr):m_handle(handle) {}
+ WinHandle(WinHandle&& rhs):m_handle(rhs.m_handle) { rhs.m_handle = nullptr; }
+
+ /// Dtor
+ ~WinHandle() { setNull(); }
+
+private:
+
+ WinHandle(const WinHandle&) = delete;
+ void operator=(const WinHandle& rhs) = delete;
+
+ HANDLE m_handle;
+};
+
+} // anonymous
+
+static DWORD WINAPI _readerThreadProc(LPVOID threadParam)
+{
+ ThreadInfo* info = (ThreadInfo*)threadParam;
+ HANDLE file = info->file;
+
+ static const int kChunkSize = 1024;
+ char buffer[kChunkSize];
+
+ StringBuilder outputBuilder;
+
+ // We need to re-write the output to deal with line
+ // endings, so we check for paired '\r' and '\n'
+ // characters, which may span chunks.
+ int prevChar = -1;
+
+ for (;;)
+ {
+ DWORD bytesRead = 0;
+ BOOL readResult = ReadFile(file, buffer, kChunkSize, &bytesRead, nullptr);
+
+ const DWORD lastError = GetLastError();
+ if (lastError == ERROR_BROKEN_PIPE)
+ {
+ break;
+ }
+
+ if (!readResult)
+ {
+ break;
+ }
+
+
+ // walk the buffer and rewrite to eliminate '\r' '\n' pairs
+ char* readCursor = buffer;
+ char const* end = buffer + bytesRead;
+ char* writeCursor = buffer;
+
+ while (readCursor != end)
+ {
+ int p = prevChar;
+ int c = *readCursor++;
+ prevChar = c;
+ switch (c)
+ {
+ case '\r': case '\n':
+ // swallow input if '\r' and '\n' appear in sequence
+ if ((p ^ c) == ('\r' ^ '\n'))
+ {
+ // but don't swallow the next byte
+ prevChar = -1;
+ continue;
+ }
+ // always replace '\r' with '\n'
+ c = '\n';
+ break;
+
+ default:
+ break;
+ }
+
+ *writeCursor++ = (char)c;
+ }
+ bytesRead = (DWORD)(writeCursor - buffer);
+
+ // Note: Current "core" implementation gives no way to know
+ // the length of the buffer, so we ultimately have
+ // to just assume null termination...
+ outputBuilder.Append(buffer, bytesRead);
+ }
+
+ info->output = outputBuilder.ProduceString();
+
+ return 0;
+}
+
+
+/* static */UnownedStringSlice ProcessUtil::getExecutableSuffix()
+{
+ return UnownedStringSlice::fromLiteral(".exe");
+}
+
+static void _appendEscaped(const UnownedStringSlice& slice, StringBuilder& out)
+{
+ // TODO(JS): This escaping is not complete... !
+ if (slice.indexOf(' ') >= 0 || slice.indexOf('"') >= 0)
+ {
+ out << "\"";
+
+ const char* cur = slice.begin();
+ const char* end = slice.end();
+
+ while (cur < end)
+ {
+ char c= *cur++;
+ switch (c)
+ {
+ case '\"':
+ {
+ // Escape quotes.
+ out << "\\\"";
+ break;
+ }
+ default:
+ out.append(c);
+ }
+ }
+
+ out << "\"";
+
+ return;
+ }
+
+ out << slice;
+}
+
+/* static */String ProcessUtil::getCommandLineString(const CommandLine& commandLine)
+{
+ StringBuilder cmd;
+ _appendEscaped(commandLine.m_executable.getUnownedSlice(), cmd);
+ for (const auto& arg : commandLine.m_args)
+ {
+ cmd << " ";
+ _appendEscaped(arg.getUnownedSlice(), cmd);
+ }
+ return cmd.ToString();
+}
+
+#define SLANG_RETURN_FAIL_ON_FALSE(x) if (!(x)) return SLANG_FAIL;
+
+/* static */SlangResult ProcessUtil::execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult)
+{
+ outExecuteResult.init();
+
+ SECURITY_ATTRIBUTES securityAttributes;
+ securityAttributes.nLength = sizeof(securityAttributes);
+ securityAttributes.lpSecurityDescriptor = nullptr;
+ securityAttributes.bInheritHandle = true;
+
+ WinHandle childStdOutRead;
+ WinHandle childStdErrRead;
+ WinHandle childStdInWrite;
+
+ // Now we can actually get around to starting a process
+ PROCESS_INFORMATION processInfo;
+ ZeroMemory(&processInfo, sizeof(processInfo));
+ {
+ WinHandle childStdOutWrite;
+ WinHandle childStdErrWrite;
+ WinHandle childStdInRead;
+
+ {
+ WinHandle childStdOutReadTmp;
+ WinHandle childStdErrReadTmp;
+ WinHandle childStdInWriteTmp;
+ // create stdout pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdOutReadTmp.writeRef(), childStdOutWrite.writeRef(), &securityAttributes, 0));
+ // create stderr pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdErrReadTmp.writeRef(), childStdErrWrite.writeRef(), &securityAttributes, 0));
+ // create stdin pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdInRead.writeRef(), childStdInWriteTmp.writeRef(), &securityAttributes, 0));
+
+ HANDLE currentProcess = GetCurrentProcess();
+
+ // create a non-inheritable duplicate of the stdout reader
+ SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdOutReadTmp, currentProcess, childStdOutRead.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
+ // create a non-inheritable duplicate of the stderr reader
+ SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdErrReadTmp, currentProcess, childStdErrRead.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
+ // create a non-inheritable duplicate of the stdin writer
+ SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdInWriteTmp, currentProcess, childStdInWrite.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
+ }
+
+
+ // TODO: switch to proper wide-character versions of these...
+ STARTUPINFOW startupInfo;
+ ZeroMemory(&startupInfo, sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+ startupInfo.hStdError = childStdErrWrite;
+ startupInfo.hStdOutput = childStdOutWrite;
+ startupInfo.hStdInput = childStdInRead;
+ startupInfo.dwFlags = STARTF_USESTDHANDLES;
+
+ OSString pathBuffer;
+ LPCWSTR path = nullptr;
+
+ if (commandLine.m_executableType == CommandLine::ExecutableType::Path)
+ {
+ StringBuilder cmd;
+ _appendEscaped(commandLine.m_executable.getUnownedSlice(), cmd);
+
+ pathBuffer = cmd.toWString();
+ path = pathBuffer.begin();
+ }
+
+ // Produce the command line string
+ String cmdString = getCommandLineString(commandLine);
+ OSString cmdStringBuffer = cmdString.toWString();
+
+ // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa
+ // `CreateProcess` requires write access to this, for some reason...
+ BOOL success = CreateProcessW(
+ path,
+ (LPWSTR)cmdStringBuffer.begin(),
+ nullptr,
+ nullptr,
+ true,
+ CREATE_NO_WINDOW,
+ nullptr, // TODO: allow specifying environment variables?
+ nullptr,
+ &startupInfo,
+ &processInfo);
+
+ if (!success)
+ {
+ DWORD err = GetLastError();
+ SLANG_UNUSED(err);
+
+ return SLANG_FAIL;
+ }
+
+ // close handles we are now done with
+ CloseHandle(processInfo.hThread);
+ }
+
+ // Create a thread to read from the child's stdout.
+ ThreadInfo stdOutThreadInfo;
+ stdOutThreadInfo.file = childStdOutRead;
+ WinHandle stdOutThread = CreateThread(nullptr, 0, &_readerThreadProc, (LPVOID)&stdOutThreadInfo, 0, nullptr);
+
+ // Create a thread to read from the child's stderr.
+ ThreadInfo stdErrThreadInfo;
+ stdErrThreadInfo.file = childStdErrRead;
+ WinHandle stdErrThread = CreateThread(nullptr, 0, &_readerThreadProc, (LPVOID)&stdErrThreadInfo, 0, nullptr);
+
+ // wait for the process to exit
+ // TODO: set a timeout as a safety measure...
+ WaitForSingleObject(processInfo.hProcess, INFINITE);
+
+ // get exit code for process
+ // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
+
+ DWORD childExitCode = 0;
+ if (!GetExitCodeProcess(processInfo.hProcess, &childExitCode))
+ {
+ // TODO(JS): Do we want to close here? It seems plausible because just because reading the exit code failed, doesn't mean the handle is closed
+ CloseHandle(processInfo.hProcess);
+
+ return SLANG_FAIL;
+ }
+
+ // wait for the reader threads
+ WaitForSingleObject(stdOutThread, INFINITE);
+ WaitForSingleObject(stdErrThread, INFINITE);
+
+ CloseHandle(processInfo.hProcess);
+
+ outExecuteResult.standardOutput = stdOutThreadInfo.output;
+ outExecuteResult.standardError = stdErrThreadInfo.output;
+ outExecuteResult.resultCode = childExitCode;
+
+ return SLANG_OK;
+}
+
+}