summaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--premake5.lua6
-rw-r--r--source/core/core.vcxproj4
-rw-r--r--source/core/core.vcxproj.filters12
-rw-r--r--source/core/slang-process-util.h61
-rw-r--r--source/core/slang-string-util.cpp42
-rw-r--r--source/core/slang-string-util.h3
-rw-r--r--source/core/slang-string.cpp27
-rw-r--r--source/core/slang-string.h43
-rw-r--r--source/core/unix/slang-unix-process-util.cpp234
-rw-r--r--source/core/windows/slang-win-process-util.cpp338
-rw-r--r--source/core/windows/slang-win-visual-studio-util.cpp339
-rw-r--r--source/core/windows/slang-win-visual-studio-util.h50
-rw-r--r--tools/slang-generate/main.cpp45
-rw-r--r--tools/slang-test/options.cpp2
-rw-r--r--tools/slang-test/os.cpp469
-rw-r--r--tools/slang-test/os.h65
-rw-r--r--tools/slang-test/slang-test-main.cpp335
-rw-r--r--tools/slang-test/test-context.cpp2
-rw-r--r--tools/slang-test/test-reporter.cpp40
19 files changed, 1357 insertions, 760 deletions
diff --git a/premake5.lua b/premake5.lua
index fcd83eb4f..03b05adfc 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -441,6 +441,12 @@ standardProject "core"
warnings "Extra"
flags { "FatalWarnings" }
+ if isTargetWindows then
+ addSourceDir "source/core/windows"
+ else
+ addSourceDir "source/core/unix"
+ end
+
-- We need the core library to be relocatable to be able to link with slang.so
filter { "system:linux" }
buildoptions{"-fPIC"}
diff --git a/source/core/core.vcxproj b/source/core/core.vcxproj
index a8e92949f..684b5592c 100644
--- a/source/core/core.vcxproj
+++ b/source/core/core.vcxproj
@@ -186,6 +186,7 @@
<ClInclude Include="slang-memory-arena.h" />
<ClInclude Include="slang-object-scope-manager.h" />
<ClInclude Include="slang-platform.h" />
+ <ClInclude Include="slang-process-util.h" />
<ClInclude Include="slang-random-generator.h" />
<ClInclude Include="slang-render-api-util.h" />
<ClInclude Include="slang-secure-crt.h" />
@@ -202,6 +203,7 @@
<ClInclude Include="slang-type-traits.h" />
<ClInclude Include="slang-uint-set.h" />
<ClInclude Include="slang-writer.h" />
+ <ClInclude Include="windows\slang-win-visual-studio-util.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="slang-byte-encode-util.cpp" />
@@ -223,6 +225,8 @@
<ClCompile Include="slang-token-reader.cpp" />
<ClCompile Include="slang-uint-set.cpp" />
<ClCompile Include="slang-writer.cpp" />
+ <ClCompile Include="windows\slang-win-process-util.cpp" />
+ <ClCompile Include="windows\slang-win-visual-studio-util.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="core.natvis" />
diff --git a/source/core/core.vcxproj.filters b/source/core/core.vcxproj.filters
index 8656ab49b..eec57c50b 100644
--- a/source/core/core.vcxproj.filters
+++ b/source/core/core.vcxproj.filters
@@ -57,6 +57,9 @@
<ClInclude Include="slang-platform.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="slang-process-util.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="slang-random-generator.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -105,6 +108,9 @@
<ClInclude Include="slang-writer.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="windows\slang-win-visual-studio-util.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="slang-byte-encode-util.cpp">
@@ -164,6 +170,12 @@
<ClCompile Include="slang-writer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="windows\slang-win-process-util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="windows\slang-win-visual-studio-util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="core.natvis">
diff --git a/source/core/slang-process-util.h b/source/core/slang-process-util.h
new file mode 100644
index 000000000..19ae7c0d1
--- /dev/null
+++ b/source/core/slang-process-util.h
@@ -0,0 +1,61 @@
+// slang-process-util.h
+#ifndef SLANG_PROCESS_UTIL_H
+#define SLANG_PROCESS_UTIL_H
+
+#include "slang-string.h"
+#include "slang-list.h"
+
+namespace Slang {
+
+struct CommandLine
+{
+ enum class ExecutableType
+ {
+ Unknown, ///< The executable is not specified
+ Path, ///< The executable is set as a path
+ Filename, ///< The executable is set as a filename
+ };
+
+ void addArg(const String& in) { m_args.add(in); }
+ void addArgs(const String* args, Int argsCount) { m_args.addRange(args, argsCount); }
+ void setExecutablePath(const String& path) { m_executableType = ExecutableType::Path; m_executable = path; }
+ void setExecutableFilename(const String& filename) { m_executableType = ExecutableType::Filename; m_executable = filename; }
+
+ /// Ctor
+ CommandLine():m_executableType(ExecutableType::Unknown) {}
+
+ ExecutableType m_executableType; ///< How the executable is specified
+ String m_executable; ///< Executable to run
+ List<String> m_args; ///< The parameters to pass
+};
+
+struct ExecuteResult
+{
+ void init()
+ {
+ resultCode = 0;
+ standardOutput = String();
+ standardError = String();
+ }
+
+ typedef int ResultCode;
+ ResultCode resultCode;
+ Slang::String standardOutput;
+ Slang::String standardError;
+};
+
+struct ProcessUtil
+{
+ /// Get the suffix used on this platform
+ static UnownedStringSlice getExecutableSuffix();
+
+ /// Output how the command line is executed on the target (with escaping and the such like)
+ static String getCommandLineString(const CommandLine& commandLine);
+
+ /// Execute the command line
+ static SlangResult execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult);
+};
+
+}
+
+#endif // SLANG_PROCESS_UTIL_H
diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp
index 11d6846f0..e571bbc99 100644
--- a/source/core/slang-string-util.cpp
+++ b/source/core/slang-string-util.cpp
@@ -199,6 +199,48 @@ ComPtr<ISlangBlob> StringUtil::createStringBlob(const String& string)
return (fromChar == toChar || string.indexOf(fromChar) == Index(-1)) ? string : calcCharReplaced(string.getUnownedSlice(), fromChar, toChar);
}
+/* static */void StringUtil::calcLines(const UnownedStringSlice& textIn, List<UnownedStringSlice>& outLines)
+{
+ char const* begin = textIn.begin();
+ char const* end = textIn.end();
+
+ char const* cursor = begin;
+ const char* lineStart = cursor;
+
+ while (cursor < end)
+ {
+ int c = *cursor++;
+ switch (c)
+ {
+ case '\r': case '\n':
+ {
+ outLines.add(UnownedStringSlice(lineStart, cursor - 1));
+
+ // When we see a line-break character we need
+ // to record the line break, but we also need
+ // to deal with the annoying issue of encodings,
+ // where a multi-byte sequence might encode
+ // the line break.
+
+ if (cursor < end)
+ {
+ int d = *cursor;
+ if ((c ^ d) == ('\r' ^ '\n'))
+ cursor++;
+ }
+ lineStart = cursor;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (cursor > lineStart)
+ {
+ outLines.add(UnownedStringSlice(lineStart, cursor));
+ }
+}
} // namespace Slang
diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h
index fcae23bb3..dc0d7e546 100644
--- a/source/core/slang-string-util.h
+++ b/source/core/slang-string-util.h
@@ -77,6 +77,9 @@ struct StringUtil
/// Create a blob from a string
static ComPtr<ISlangBlob> createStringBlob(const String& string);
+
+ /// Given text, splits into lines stored in outLines. NOTE! That lines is only valid as long as textIn remains valid
+ static void calcLines(const UnownedStringSlice& textIn, List<UnownedStringSlice>& lines);
};
} // namespace Slang
diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp
index 64b8e4dc1..54703def1 100644
--- a/source/core/slang-string.cpp
+++ b/source/core/slang-string.cpp
@@ -14,8 +14,8 @@ namespace Slang
// OSString
OSString::OSString()
- : m_begin(0)
- , m_end(0)
+ : m_begin(nullptr)
+ , m_end(nullptr)
{}
OSString::OSString(wchar_t* begin, wchar_t* end)
@@ -23,7 +23,7 @@ namespace Slang
, m_end(end)
{}
- OSString::~OSString()
+ void OSString::_releaseBuffer()
{
if (m_begin)
{
@@ -31,6 +31,27 @@ namespace Slang
}
}
+ void OSString::set(const wchar_t* begin, const wchar_t* end)
+ {
+ if (m_begin)
+ {
+ delete[] m_begin;
+ m_begin = nullptr;
+ m_end = nullptr;
+ }
+ const size_t len = end - begin;
+ if (len > 0)
+ {
+ // TODO(JS): The allocation is only done this way to be compatible with the buffer being detached from an array
+ // This is unfortunate, because it means that the allocation stores the size (and alignment fix), which is a shame because we know the size
+ m_begin = new wchar_t[len + 1];
+ memcpy(m_begin, begin, len * sizeof(wchar_t));
+ // Zero terminate
+ m_begin[len] = 0;
+ m_end = m_begin + len;
+ }
+ }
+
static const wchar_t kEmptyOSString[] = { 0 };
wchar_t const* OSString::begin() const
diff --git a/source/core/slang-string.h b/source/core/slang-string.h
index 1cd9e5413..4975e9ec6 100644
--- a/source/core/slang-string.h
+++ b/source/core/slang-string.h
@@ -292,9 +292,43 @@ namespace Slang
class OSString
{
public:
+ /// Default
OSString();
+ /// NOTE! This assumes that begin is a new wchar_t[] buffer, and it will
+ /// now be owned by the OSString
OSString(wchar_t* begin, wchar_t* end);
- ~OSString();
+ /// Move Ctor
+ OSString(OSString&& rhs):
+ m_begin(rhs.m_begin),
+ m_end(rhs.m_end)
+ {
+ rhs.m_begin = nullptr;
+ rhs.m_end = nullptr;
+ }
+ // Copy Ctor
+ OSString(const OSString& rhs) :
+ m_begin(nullptr),
+ m_end(nullptr)
+ {
+ set(rhs.m_begin, rhs.m_end);
+ }
+
+ /// =
+ void operator=(const OSString& rhs) { set(rhs.m_begin, rhs.m_end); }
+ void operator=(OSString&& rhs)
+ {
+ auto begin = m_begin;
+ auto end = m_end;
+ m_begin = rhs.m_begin;
+ m_end = rhs.m_end;
+ rhs.m_begin = begin;
+ rhs.m_end = end;
+ }
+
+ ~OSString() { _releaseBuffer(); }
+
+ size_t getLength() const { return (m_end - m_begin); }
+ void set(const wchar_t* begin, const wchar_t* end);
operator wchar_t const*() const
{
@@ -305,8 +339,11 @@ namespace Slang
wchar_t const* end() const;
private:
- wchar_t* m_begin;
- wchar_t* m_end;
+
+ void _releaseBuffer();
+
+ wchar_t* m_begin; ///< First character. This is a new wchar_t[] buffer
+ wchar_t* m_end; ///< Points to terminating 0
};
/*!
diff --git a/source/core/unix/slang-unix-process-util.cpp b/source/core/unix/slang-unix-process-util.cpp
new file mode 100644
index 000000000..7795463b7
--- /dev/null
+++ b/source/core/unix/slang-unix-process-util.cpp
@@ -0,0 +1,234 @@
+// slang-unix-process-util.cpp
+#include "../slang-process-util.h"
+
+#include "../slang-common.h"
+#include "../slang-string-util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+//#include <dirent.h>
+#include <errno.h>
+#include <poll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+namespace Slang {
+
+
+/* static */UnownedStringSlice ProcessUtil::getExecutableSuffix()
+{
+ return UnownedStringSlice::fromLiteral("");
+}
+
+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();
+}
+
+/* static */SlangResult ProcessUtil::execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult)
+{
+ outExecuteResult.init();
+
+ List<char const*> argPtrs;
+ // Add the command
+ argPtrs.add(commandLine.m_executable.getBuffer());
+ // Add all the args
+ for (auto arg : commandLine.m_args)
+ {
+ argPtrs.add(arg.getBuffer());
+ }
+ // Terminate with a null
+ argPtrs.add(nullptr);
+
+ int stdoutPipe[2];
+ int stderrPipe[2];
+
+ if (pipe(stdoutPipe) == -1)
+ return SLANG_FAIL;
+
+ if (pipe(stderrPipe) == -1)
+ return SLANG_FAIL;
+
+ pid_t childProcessID = fork();
+ if (childProcessID == -1)
+ return SLANG_FAIL;
+
+ if (childProcessID == 0)
+ {
+ // We are the child process.
+
+ dup2(stdoutPipe[1], STDOUT_FILENO);
+ dup2(stderrPipe[1], STDERR_FILENO);
+
+ close(stdoutPipe[0]);
+ close(stdoutPipe[1]);
+
+ close(stderrPipe[0]);
+ close(stderrPipe[1]);
+
+ execvp(argPtrs[0], (char* const*)&argPtrs[0]);
+
+ // If we get here, then `exec` failed
+ fprintf(stderr, "error: `exec` failed\n");
+ return SLANG_FAIL;
+ }
+ else
+ {
+ // We are the parent process
+
+ close(stdoutPipe[1]);
+ close(stderrPipe[1]);
+
+ int stdoutFD = stdoutPipe[0];
+ int stderrFD = stderrPipe[0];
+
+ pollfd pollInfos[2];
+ nfds_t pollInfoCount = 2;
+
+ pollInfos[0].fd = stdoutFD;
+ pollInfos[0].events = POLLIN;
+ pollInfos[0].revents = 0;
+ pollInfos[1].fd = stderrFD;
+ pollInfos[1].events = POLLIN;
+ pollInfos[1].revents = 0;
+
+ int remainingCount = 2;
+ int iterations = 0;
+ while (remainingCount)
+ {
+ // Safeguard against infinite loop:
+ iterations++;
+ if (iterations > 10000)
+ {
+ fprintf(stderr, "poll(): %d iterations\n", iterations);
+ return SLANG_FAIL;
+ }
+
+ // Set a timeout of ten seconds;
+ // we really shouldn't wait too long...
+ int pollTimeout = 10000;
+ int pollResult = poll(pollInfos, pollInfoCount, pollTimeout);
+ if (pollResult <= 0)
+ {
+ // If there was a signal that got in
+ // the way, then retry...
+ if (pollResult == -1 && errno == EINTR)
+ continue;
+
+ // timeout or error...
+ return SLANG_FAIL;
+ }
+
+ enum { kBufferSize = 1024 };
+ char buffer[kBufferSize];
+
+ if (pollInfos[0].revents)
+ {
+ auto count = read(stdoutFD, buffer, kBufferSize);
+ if (count <= 0)
+ {
+ // end-of-file
+ close(stdoutFD);
+ pollInfos[0].fd = -1;
+ remainingCount--;
+ }
+
+ outExecuteResult.standardOutput.append(buffer, buffer + count);
+ }
+
+ if (pollInfos[1].revents)
+ {
+ auto count = read(stderrFD, buffer, kBufferSize);
+ if (count <= 0)
+ {
+ // end-of-file
+ close(stderrFD);
+ pollInfos[1].fd = -1;
+ remainingCount--;
+ }
+
+ outExecuteResult.standardError.append(buffer, buffer + count);
+ }
+ }
+
+ int childStatus = 0;
+ iterations = 0;
+ for (;;)
+ {
+ // Safeguard against infinite loop:
+ iterations++;
+ if (iterations > 10000)
+ {
+ fprintf(stderr, "waitpid(): %d iterations\n", iterations);
+ return SLANG_FAIL;
+ }
+
+ pid_t terminatedProcessID = waitpid(childProcessID, &childStatus, 0);
+ if (terminatedProcessID == -1)
+ {
+ return SLANG_FAIL;
+ }
+
+ if (terminatedProcessID == childProcessID)
+ {
+ if (WIFEXITED(childStatus))
+ {
+ outExecuteResult.resultCode = (int)(int8_t)WEXITSTATUS(childStatus);
+ }
+ else
+ {
+ outExecuteResult.resultCode = 1;
+ }
+ return SLANG_OK;
+ }
+ }
+ }
+
+ return SLANG_FAIL;
+}
+
+} // namespace Slang
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;
+}
+
+}
diff --git a/source/core/windows/slang-win-visual-studio-util.cpp b/source/core/windows/slang-win-visual-studio-util.cpp
new file mode 100644
index 000000000..edf33110c
--- /dev/null
+++ b/source/core/windows/slang-win-visual-studio-util.cpp
@@ -0,0 +1,339 @@
+#include "slang-win-visual-studio-util.h"
+
+#include "../slang-common.h"
+#include "../slang-process-util.h"
+#include "../slang-string-util.h"
+
+#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
+# define NOMINMAX
+# include <Windows.h>
+# undef WIN32_LEAN_AND_MEAN
+# undef NOMINMAX
+
+# include <Shlobj.h>
+
+#endif
+
+// The method used to invoke VS was originally inspired by some ideas in
+// https://github.com/RuntimeCompiledCPlusPlus/RuntimeCompiledCPlusPlus/
+
+namespace Slang {
+
+// Information on VS versioning can be found here
+// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering
+
+
+namespace { // anonymous
+
+typedef WinVisualStudioUtil::Version Version;
+
+struct RegistryInfo
+{
+ const char* regName; ///< The name of the entry in the registry
+ const char* pathFix; ///< With the value from the registry how to fix the path
+};
+
+struct VersionInfo
+{
+ Version version; ///< The version
+ const char* name; ///< The name of the registry key
+};
+
+} // anonymous
+
+
+static SlangResult _readRegistryKey(const char* path, const char* keyName, String& outString)
+{
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winreg/nf-winreg-regopenkeyexa
+ HKEY key;
+ LONG ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, path, 0, KEY_READ | KEY_WOW64_32KEY, &key);
+ if (ret != ERROR_SUCCESS)
+ {
+ return SLANG_FAIL;
+ }
+
+ char value[MAX_PATH];
+ DWORD size = MAX_PATH;
+
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winreg/nf-winreg-regqueryvalueexa
+ ret = RegQueryValueExA(key, keyName, nullptr, nullptr, (LPBYTE)value, &size);
+ RegCloseKey(key);
+
+ if (ret != ERROR_SUCCESS)
+ {
+ return SLANG_FAIL;
+ }
+
+ outString = value;
+ return SLANG_OK;
+}
+
+// Make easier to set up the array
+static Version _makeVersion(int main, int dot = 0) { return WinVisualStudioUtil::makeVersion(main, dot); }
+
+VersionInfo _makeVersionInfo(const char* name, int high, int dot = 0)
+{
+ VersionInfo info;
+ info.name = name;
+ info.version = WinVisualStudioUtil::makeVersion(high, dot);
+ return info;
+}
+
+static const VersionInfo s_versionInfos[] =
+{
+ _makeVersionInfo("VS 2005", 8),
+ _makeVersionInfo("VS 2008", 9),
+ _makeVersionInfo("VS 2010", 10),
+ _makeVersionInfo("VS 2012", 11),
+ _makeVersionInfo("VS 2013", 12),
+ _makeVersionInfo("VS 2015", 14),
+ _makeVersionInfo("VS 2017", 15),
+ _makeVersionInfo("VS 2019", 16),
+};
+
+// When trying to figure out how this stuff works by running regedit - care is needed,
+// because what regedit displays varies on which version of regedit is used.
+// In order to use the registry paths used here it's necessary to use Start/Run with
+// %systemroot%\syswow64\regedit to view 32 bit keys
+
+static const RegistryInfo s_regInfos[] =
+{
+ {"SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7", "" },
+ {"SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", "VC\\Auxiliary\\Build\\" },
+};
+
+static bool _canUseVSWhere(Version version)
+{
+ // If greater than 15.0 we can use vswhere tool
+ return (int(version) >= int(_makeVersion(15)));
+}
+
+static int _getRegistryKeyIndex(Version version)
+{
+ if (int(version) >= int(_makeVersion(15)))
+ {
+ return 1;
+ }
+ return 0;
+}
+
+/* static */void WinVisualStudioUtil::getVersions(List<Version>& outVersions)
+{
+ const int count = SLANG_COUNT_OF(s_versionInfos);
+ outVersions.setCount(count);
+
+ Version* dst = outVersions.begin();
+ for (int i = 0; i < count; ++i)
+ {
+ dst[i] = s_versionInfos[i].version;
+ }
+}
+
+/* static */WinVisualStudioUtil::Version WinVisualStudioUtil::getCompiledVersion()
+{
+ // Get the version of visual studio used to compile this source
+ const uint32_t version = _MSC_VER;
+
+ switch (version)
+ {
+ case 1400: return _makeVersion(8);
+ case 1500: return _makeVersion(9);
+ case 1600: return _makeVersion(10);
+ case 1700: return _makeVersion(11);
+ case 1800: return _makeVersion(12);
+ case 1900:
+ {
+ return _makeVersion(14);
+ }
+ case 1911:
+ case 1912:
+ case 1913:
+ case 1914:
+ case 1915:
+ case 1916:
+ {
+ return _makeVersion(15);
+ }
+ case 1920:
+ {
+ return _makeVersion(16);
+ }
+ default:
+ {
+ int lastKnownVersion = 1920;
+ if (version > lastKnownVersion)
+ {
+ // Its an unknown newer version
+ return Version::Future;
+ }
+ break;
+ }
+ }
+
+ // Unknown version
+ return Version::Unknown;
+}
+
+static SlangResult _find(int versionIndex, WinVisualStudioUtil::VersionPath& outPath)
+{
+ const auto& versionInfo = s_versionInfos[versionIndex];
+
+ auto version = versionInfo.version;
+
+ outPath.version = version;
+ outPath.vcvarsPath = String();
+
+ if (_canUseVSWhere(version))
+ {
+ CommandLine cmd;
+
+ // Lookup directly %ProgramFiles(x86)% path
+ // https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shgetfolderpatha
+ HWND hwnd = GetConsoleWindow();
+
+ char programFilesPath[_MAX_PATH];
+ SHGetFolderPathA(hwnd, CSIDL_PROGRAM_FILESX86, NULL, 0, programFilesPath);
+
+ String vswherePath = programFilesPath;
+ vswherePath.append("\\Microsoft Visual Studio\\Installer\\vswhere");
+
+ cmd.setExecutableFilename(vswherePath);
+
+ StringBuilder versionName;
+ WinVisualStudioUtil::append(version, versionName);
+
+ String args[] = { "-version", versionName, "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath" };
+ cmd.addArgs(args, SLANG_COUNT_OF(args));
+
+ ExecuteResult exeRes;
+ if (SLANG_SUCCEEDED(ProcessUtil::execute(cmd, exeRes)))
+ {
+ // We need to chopoff CR/LF if there is one
+ List<UnownedStringSlice> lines;
+ StringUtil::calcLines(exeRes.standardOutput.getUnownedSlice(), lines);
+
+ if (lines.getCount())
+ {
+ outPath.vcvarsPath = lines[0];
+ outPath.vcvarsPath.append("\\VC\\Auxiliary\\Build\\");
+ return SLANG_OK;
+ }
+ }
+ }
+
+ const Int keyIndex = _getRegistryKeyIndex(version);
+ if (keyIndex >= 0)
+ {
+ SLANG_ASSERT(keyIndex < SLANG_COUNT_OF(s_regInfos));
+
+ // Try reading the key
+ const auto& keyInfo = s_regInfos[keyIndex];
+
+ StringBuilder keyName;
+ WinVisualStudioUtil::append(versionInfo.version, keyName);
+
+ String value;
+ if (SLANG_SUCCEEDED(_readRegistryKey(keyInfo.regName, keyName.getBuffer(), value)))
+ {
+ outPath.vcvarsPath = value;
+ return SLANG_OK;
+ }
+ }
+
+ return SLANG_FAIL;
+}
+
+/* static */SlangResult WinVisualStudioUtil::find(List<VersionPath>& outVersionPaths)
+{
+ outVersionPaths.clear();
+
+ const int versionCount = SLANG_COUNT_OF(s_versionInfos);
+
+ for (int i = versionCount - 1; i >= 0; --i)
+ {
+ VersionPath versionPath;
+ if (SLANG_SUCCEEDED(_find(i, versionPath)))
+ {
+ outVersionPaths.add(versionPath);
+ }
+ }
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult WinVisualStudioUtil::find(Version version, VersionPath& outPath)
+{
+ const int versionCount = SLANG_COUNT_OF(s_versionInfos);
+
+ for (int i = 0; i < versionCount; ++i)
+ {
+ const auto& versionInfo = s_versionInfos[i];
+ if (versionInfo.version == version)
+ {
+ return _find(i, outPath);
+ }
+ }
+ return SLANG_FAIL;
+}
+
+/* static */SlangResult WinVisualStudioUtil::executeCompiler(const VersionPath& versionPath, const CommandLine& commandLine, ExecuteResult& outResult)
+{
+ // To invoke cl we need to run the suitable vcvars. In order to run this we have to have MS CommandLine.
+ // So here we build up a cl command line that is run by first running vcvars, and then executing cl with the parameters as passed to commandLine
+
+ StringBuilder builder;
+ CommandLine cmdLine;
+
+ cmdLine.setExecutableFilename("cmd.exe");
+ {
+ String options[] = { "/q", "/c", "@prompt", "$" };
+ cmdLine.addArgs(options, SLANG_COUNT_OF(options));
+ }
+
+ cmdLine.addArg("&&");
+
+ {
+ StringBuilder path;
+ path << versionPath.vcvarsPath;
+ path << "\\vcvarsall.bat";
+ cmdLine.addArg(path);
+ }
+
+#if SLANG_PTR_IS_32
+ cmdLine.addArg("x86");
+#else
+ cmdLine.addArg("x86_amd64");
+#endif
+
+ cmdLine.addArg("&&");
+ cmdLine.addArg("cl");
+
+ // Append the command line options
+ cmdLine.addArgs(commandLine.m_args.getBuffer(), commandLine.m_args.getCount());
+
+ return ProcessUtil::execute(cmdLine, outResult);
+}
+
+/* static */void WinVisualStudioUtil::append(Version version, StringBuilder& outBuilder)
+{
+ switch (version)
+ {
+ case Version::Unknown:
+ {
+ outBuilder << "unknown";
+ }
+ case Version::Future:
+ {
+ outBuilder << "future";
+ break;
+ }
+ default:
+ {
+ outBuilder << (int(version) / 10) << "." << (int(version) % 10);
+ break;
+ }
+ }
+}
+
+} // namespace Slang
diff --git a/source/core/windows/slang-win-visual-studio-util.h b/source/core/windows/slang-win-visual-studio-util.h
new file mode 100644
index 000000000..aec4493df
--- /dev/null
+++ b/source/core/windows/slang-win-visual-studio-util.h
@@ -0,0 +1,50 @@
+#ifndef SLANG_WIN_VISUAL_STUDIO_UTIL_H
+#define SLANG_WIN_VISUAL_STUDIO_UTIL_H
+
+#include "../slang-list.h"
+#include "../slang-string.h"
+
+#include "../slang-process-util.h"
+
+namespace Slang {
+
+struct WinVisualStudioUtil
+{
+ enum class Version: uint32_t
+ {
+ Unknown = 0, ///< This is an unknown (and not later) version
+ Future = 0xff * 10, ///< This is a version 'from the future' - that isn't specifically known. Will be treated as latest
+ };
+
+ struct VersionPath
+ {
+ Version version; ///< The visual studio version
+ String vcvarsPath; ///< The path to vcvars bat files, that need to be executed before executing the compiler
+ };
+
+ /// Find all the installations
+ static SlangResult find(List<VersionPath>& outVersionPaths);
+
+ /// Given a version find it's path
+ static SlangResult find(Version version, VersionPath& outPath);
+
+ /// Run visual studio on specified path with the parameters specified on the command line. Output placed in outResult.
+ static SlangResult executeCompiler(const VersionPath& versionPath, const CommandLine& commandLine, ExecuteResult& outResult);
+
+ /// Get all the known version numbers
+ static void getVersions(List<Version>& outVersions);
+
+ /// Gets the msc compiler used to compile this version. Returning Version(0) means unknown
+ static Version getCompiledVersion();
+
+ /// Create a version from a high and low indices
+ static Version makeVersion(int high, int low = 0) { SLANG_ASSERT(low >= 0 && low <= 9); return Version(high * 10 + low); }
+
+ /// Convert a version number into a string
+ static void append(Version version, StringBuilder& outBuilder);
+
+};
+
+} // namespace Slang
+
+#endif
diff --git a/tools/slang-generate/main.cpp b/tools/slang-generate/main.cpp
index ddd087072..046b2063c 100644
--- a/tools/slang-generate/main.cpp
+++ b/tools/slang-generate/main.cpp
@@ -7,6 +7,7 @@
#include "../../source/core/slang-list.h"
#include "../../source/core/slang-string.h"
+#include "../../source/core/slang-string-util.h"
using namespace Slang;
@@ -601,7 +602,7 @@ void emitCodeNodes(
}
// Given line starts and a location, find the line number. Returns -1 if not found
-static Index _findLineIndex(const List<const char*>& lineBreaks, const char* location)
+static Index _findLineIndex(const List<UnownedStringSlice>& lineBreaks, const char* location)
{
if (location == nullptr)
{
@@ -615,7 +616,7 @@ static Index _findLineIndex(const List<const char*>& lineBreaks, const char* loc
while (lo + 1 < hi)
{
const auto mid = (hi + lo) >> 1;
- const auto midOffset = lineBreaks[mid];
+ const auto midOffset = lineBreaks[mid].begin();
if (midOffset <= location)
{
lo = mid;
@@ -629,50 +630,14 @@ static Index _findLineIndex(const List<const char*>& lineBreaks, const char* loc
return lo;
}
-static void _calcLineBreaks(const UnownedStringSlice& content, List<const char*>& outLineStarts)
-{
- char const* begin = content.begin();
- char const* end = content.end();
-
- char const* cursor = begin;
-
- // Treat the beginning of the file as a line break
- outLineStarts.add(cursor);
-
- while (cursor != end)
- {
- int c = *cursor++;
- switch (c)
- {
- case '\r': case '\n':
- {
- // When we see a line-break character we need
- // to record the line break, but we also need
- // to deal with the annoying issue of encodings,
- // where a multi-byte sequence might encode
- // the line break.
-
- int d = *cursor;
- if ((c^d) == ('\r' ^ '\n'))
- cursor++;
-
- outLineStarts.add(cursor);
- break;
- }
- default:
- break;
- }
- }
-}
-
void emitTemplateNodes(
SourceFile* sourceFile,
FILE* stream,
Node* node)
{
// Work out
- List<const char*> lineBreaks;
- _calcLineBreaks(sourceFile->text, lineBreaks);
+ List<UnownedStringSlice> lineBreaks;
+ StringUtil::calcLines(sourceFile->text, lineBreaks);
Node* prev = nullptr;
for (auto nn = node; nn; prev = nn, nn = nn->next)
diff --git a/tools/slang-test/options.cpp b/tools/slang-test/options.cpp
index 3e985b493..4c8108f4a 100644
--- a/tools/slang-test/options.cpp
+++ b/tools/slang-test/options.cpp
@@ -1,8 +1,8 @@
// test-context.cpp
#include "options.h"
-#include "os.h"
#include "../../source/core/slang-string-util.h"
+#include "../../source/core/slang-io.h"
#include <stdio.h>
#include <stdlib.h>
diff --git a/tools/slang-test/os.cpp b/tools/slang-test/os.cpp
index a938a71e7..eceeef2d7 100644
--- a/tools/slang-test/os.cpp
+++ b/tools/slang-test/os.cpp
@@ -123,266 +123,6 @@ OSFindFilesResult osFindChildDirectories(
return result;
}
-// OSProcessSpawner
-
-struct OSProcessSpawner_ReaderThreadInfo
-{
- HANDLE file;
- String output;
-};
-
-static DWORD WINAPI osReaderThreadProc(LPVOID threadParam)
-{
- OSProcessSpawner_ReaderThreadInfo* info = (OSProcessSpawner_ReaderThreadInfo*)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);
-
- if (!readResult || GetLastError() == ERROR_BROKEN_PIPE)
- {
- 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;
-}
-
-void OSProcessSpawner::pushExecutableName(
- Slang::String executableName)
-{
- executableName_ = executableName;
- commandLine_.Append(executableName);
- isExecutablePath_ = false;
-}
-
-void OSProcessSpawner::pushExecutablePath(
- Slang::String executablePath)
-{
- executableName_ = executablePath;
- commandLine_.Append(executablePath);
- isExecutablePath_ = true;
-}
-
-void OSProcessSpawner::pushArgument(
- Slang::String argument)
-{
- // TODO(tfoley): handle cases where arguments need some escaping
- commandLine_.Append(" ");
- commandLine_.Append(argument);
-
- argumentList_.add(argument);
-}
-
-Slang::String OSProcessSpawner::getCommandLine()
-{
- return commandLine_;
-}
-
-OSError OSProcessSpawner::spawnAndWaitForCompletion()
-{
- SECURITY_ATTRIBUTES securityAttributes;
- securityAttributes.nLength = sizeof(securityAttributes);
- securityAttributes.lpSecurityDescriptor = nullptr;
- securityAttributes.bInheritHandle = true;
-
- // create stdout pipe for child process
- HANDLE childStdOutReadTmp = nullptr;
- HANDLE childStdOutWrite = nullptr;
- if (!CreatePipe(&childStdOutReadTmp, &childStdOutWrite, &securityAttributes, 0))
- {
- return kOSError_OperationFailed;
- }
-
- // create stderr pipe for child process
- HANDLE childStdErrReadTmp = nullptr;
- HANDLE childStdErrWrite = nullptr;
- if (!CreatePipe(&childStdErrReadTmp, &childStdErrWrite, &securityAttributes, 0))
- {
- return kOSError_OperationFailed;
- }
-
- // create stdin pipe for child process
- HANDLE childStdInRead = nullptr;
- HANDLE childStdInWriteTmp = nullptr;
- if (!CreatePipe(&childStdInRead, &childStdInWriteTmp, &securityAttributes, 0))
- {
- return kOSError_OperationFailed;
- }
-
- HANDLE currentProcess = GetCurrentProcess();
-
- // create a non-inheritable duplicate of the stdout reader
- HANDLE childStdOutRead = nullptr;
- if (!DuplicateHandle(
- currentProcess, childStdOutReadTmp,
- currentProcess, &childStdOutRead,
- 0, FALSE, DUPLICATE_SAME_ACCESS))
- {
- return kOSError_OperationFailed;
- }
- if (!CloseHandle(childStdOutReadTmp))
- {
- return kOSError_OperationFailed;
- }
-
- // create a non-inheritable duplicate of the stderr reader
- HANDLE childStdErrRead = nullptr;
- if (!DuplicateHandle(
- currentProcess, childStdErrReadTmp,
- currentProcess, &childStdErrRead,
- 0, FALSE, DUPLICATE_SAME_ACCESS))
- {
- return kOSError_OperationFailed;
- }
- if (!CloseHandle(childStdErrReadTmp))
- {
- return kOSError_OperationFailed;
- }
-
- // create a non-inheritable duplicate of the stdin writer
- HANDLE childStdInWrite = nullptr;
- if (!DuplicateHandle(
- currentProcess, childStdInWriteTmp,
- currentProcess, &childStdInWrite,
- 0, FALSE, DUPLICATE_SAME_ACCESS))
- {
- return kOSError_OperationFailed;
- }
- if (!CloseHandle(childStdInWriteTmp))
- {
- return kOSError_OperationFailed;
- }
-
- // Now we can actually get around to starting a process
- PROCESS_INFORMATION processInfo;
- ZeroMemory(&processInfo, sizeof(processInfo));
-
- // 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;
-
- // `CreateProcess` requires write access to this, for some reason...
- BOOL success = CreateProcessW(
- isExecutablePath_ ? executableName_.toWString().begin() : nullptr,
- (LPWSTR)commandLine_.ToString().toWString().begin(),
- nullptr,
- nullptr,
- true,
- CREATE_NO_WINDOW,
- nullptr, // TODO: allow specifying environment variables?
- nullptr,
- &startupInfo,
- &processInfo);
- if (!success)
- {
- return kOSError_OperationFailed;
- }
-
- // close handles we are now done with
- CloseHandle(processInfo.hThread);
- CloseHandle(childStdOutWrite);
- CloseHandle(childStdErrWrite);
- CloseHandle(childStdInRead);
-
- // Create a thread to read from the child's stdout.
- OSProcessSpawner_ReaderThreadInfo stdOutThreadInfo;
- stdOutThreadInfo.file = childStdOutRead;
- HANDLE stdOutThread = CreateThread(nullptr, 0, &osReaderThreadProc, (LPVOID)&stdOutThreadInfo, 0, nullptr);
-
- // Create a thread to read from the child's stderr.
- OSProcessSpawner_ReaderThreadInfo stdErrThreadInfo;
- stdErrThreadInfo.file = childStdErrRead;
- HANDLE stdErrThread = CreateThread(nullptr, 0, &osReaderThreadProc, (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
- DWORD childExitCode = 0;
- if (!GetExitCodeProcess(processInfo.hProcess, &childExitCode))
- {
- return kOSError_OperationFailed;
- }
-
- // wait for the reader threads
- WaitForSingleObject(stdOutThread, INFINITE);
- WaitForSingleObject(stdErrThread, INFINITE);
-
- CloseHandle(processInfo.hProcess);
- CloseHandle(childStdOutRead);
- CloseHandle(childStdErrRead);
- CloseHandle(childStdInWrite);
-
- standardOutput_ = stdOutThreadInfo.output;
- standardError_ = stdErrThreadInfo.output;
- resultCode_ = childExitCode;
-
- return kOSError_None;
-}
-
-char const* osGetExecutableSuffix()
-{
- return ".exe";
-}
-
#else
static bool advance(OSFindFilesResult& result)
@@ -475,213 +215,4 @@ OSFindFilesResult osFindChildDirectories(
return result;
}
-// OSProcessSpawner
-
-void OSProcessSpawner::pushExecutableName(
- Slang::String executableName)
-{
- executableName_ = executableName;
- arguments_.add(executableName);
- isExecutablePath_ = false;
-}
-
-void OSProcessSpawner::pushExecutablePath(
- Slang::String executablePath)
-{
- executableName_ = executablePath;
- arguments_.add(executablePath);
- isExecutablePath_ = true;
-}
-
-void OSProcessSpawner::pushArgument(
- Slang::String argument)
-{
- arguments_.add(argument);
- argumentList_.add(argument);
-}
-
-Slang::String OSProcessSpawner::getCommandLine()
-{
- Slang::UInt argCount = arguments_.getCount();
-
- Slang::StringBuilder sb;
- for(Slang::UInt ii = 0; ii < argCount; ++ii)
- {
- if(ii != 0) sb << " ";
- sb << arguments_[ii];
- }
- return sb.ProduceString();
-}
-
-OSError OSProcessSpawner::spawnAndWaitForCompletion()
-{
- List<char const*> argPtrs;
- for(auto arg : arguments_)
- {
- argPtrs.add(arg.getBuffer());
- }
- argPtrs.add(NULL);
-
- int stdoutPipe[2];
- int stderrPipe[2];
-
- if(pipe(stdoutPipe) == -1)
- return kOSError_OperationFailed;
-
- if(pipe(stderrPipe) == -1)
- return kOSError_OperationFailed;
-
- pid_t childProcessID = fork();
- if (childProcessID == -1)
- return kOSError_OperationFailed;
-
- if(childProcessID == 0)
- {
- // We are the child process.
-
- dup2(stdoutPipe[1], STDOUT_FILENO);
- dup2(stderrPipe[1], STDERR_FILENO);
-
- close(stdoutPipe[0]);
- close(stdoutPipe[1]);
-
- close(stderrPipe[0]);
- close(stderrPipe[1]);
-
- execvp(
- argPtrs[0],
- (char* const*) &argPtrs[0]);
-
- // If we get here, then `exec` failed
- fprintf(stderr, "error: `exec` failed\n");
- exit(1);
- }
- else
- {
- // We are the parent process
-
- close(stdoutPipe[1]);
- close(stderrPipe[1]);
-
- int stdoutFD = stdoutPipe[0];
- int stderrFD = stderrPipe[0];
-
- pollfd pollInfos[2];
- nfds_t pollInfoCount = 2;
-
- pollInfos[0].fd = stdoutFD;
- pollInfos[0].events = POLLIN;
- pollInfos[0].revents = 0;
- pollInfos[1].fd = stderrFD;
- pollInfos[1].events = POLLIN;
- pollInfos[1].revents = 0;
-
- int remainingCount = 2;
- int iterations = 0;
- while(remainingCount)
- {
- // Safeguard against infinite loop:
- iterations++;
- if (iterations > 10000)
- {
- fprintf(stderr, "poll(): %d iterations\n", iterations);
- return kOSError_OperationFailed;
- }
-
- // Set a timeout of ten seconds;
- // we really shouldn't wait too long...
- int pollTimeout = 10000;
- int pollResult = poll(pollInfos, pollInfoCount, pollTimeout);
- if (pollResult <= 0)
- {
- // If there was a signal that got in
- // the way, then retry...
- if(pollResult == -1 && errno == EINTR)
- continue;
-
- // timeout or error...
- return kOSError_OperationFailed;
- }
-
- enum { kBufferSize = 1024 };
- char buffer[kBufferSize];
-
- if(pollInfos[0].revents)
- {
- auto count = read(stdoutFD, buffer, kBufferSize);
- if (count <= 0)
- {
- // end-of-file
- close(stdoutFD);
- pollInfos[0].fd = -1;
- remainingCount--;
- }
-
- standardOutput_.append(
- buffer, buffer + count);
- }
-
- if(pollInfos[1].revents)
- {
- auto count = read(stderrFD, buffer, kBufferSize);
- if (count <= 0)
- {
- // end-of-file
- close(stderrFD);
- pollInfos[1].fd = -1;
- remainingCount--;
- }
-
- standardError_.append(
- buffer, buffer + count);
- }
- }
-
- int childStatus = 0;
- iterations = 0;
- for(;;)
- {
- // Safeguard against infinite loop:
- iterations++;
- if (iterations > 10000)
- {
- fprintf(stderr, "waitpid(): %d iterations\n", iterations);
- return kOSError_OperationFailed;
- }
-
-
- pid_t terminatedProcessID = waitpid(
- childProcessID,
- &childStatus,
- 0);
- if (terminatedProcessID == -1)
- {
- return kOSError_OperationFailed;
- }
-
- if(terminatedProcessID == childProcessID)
- {
- if(WIFEXITED(childStatus))
- {
- resultCode_ = (int)(int8_t)WEXITSTATUS(childStatus);
- }
- else
- {
- resultCode_ = 1;
- }
-
- return kOSError_None;
- }
- }
-
- }
-
- return kOSError_OperationFailed;
-}
-
-char const* osGetExecutableSuffix()
-{
- return "";
-}
-
#endif
diff --git a/tools/slang-test/os.h b/tools/slang-test/os.h
index 3e6d93942..9dd20c62d 100644
--- a/tools/slang-test/os.h
+++ b/tools/slang-test/os.h
@@ -1,3 +1,6 @@
+#ifndef SLANG_OS_H
+#define SLANG_OS_H
+
// os.h
#include "../../source/core/slang-io.h"
@@ -21,7 +24,7 @@
#include <dirent.h>
#include <errno.h>
-#include <poll.h>
+//#include <poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -129,62 +132,4 @@ OSFindFilesResult osFindFilesInDirectoryMatchingPattern(
OSFindFilesResult osFindFilesInDirectory(
Slang::String directoryPath);
-
-// An `OSProcessSpawner` can be used to launch a process, and handles
-// putting together the arguments in the form required by the target
-// platform, as well as capturing any output from the process (both
-// standard output and standard error) as strings.
-struct OSProcessSpawner
-{
- // Set the executable name for the process to be spawned.
- // Note: this call must be made before any arguments are pushed.
- void pushExecutableName(
- Slang::String executableName);
-
- // Set the executable name for the process to be spawned.
- // Note: this call must be made before any arguments are pushed.
- void pushExecutablePath(
- Slang::String executablePath);
-
- // Append an argument for the process to be spawned.
- void pushArgument(
- Slang::String argument);
-
- // Get a printable version of the command line
- // that will be run (can be used for debugging)
- Slang::String getCommandLine();
-
- // Attempt to spawn the process, and wait for it to complete.
- // Returns an error if the attempt to spawn and/or wait fails,
- // but returns `kOSError_None` if the process is run to completion,
- // whether or not the process returns "successfully" (with a zero
- // result code);
- OSError spawnAndWaitForCompletion();
-
- // If the process is successfully spawned and completes, then
- // the user can query the result code that the process produce
- // on exit, along with the output it wrote to stdout and stderr.
- typedef int ResultCode;
- ResultCode getResultCode() { return resultCode_; }
- Slang::String const& getStandardOutput() { return standardOutput_; }
- Slang::String const& getStandardError() { return standardError_; }
-
- // "private" data follows
- Slang::String standardOutput_;
- Slang::String standardError_;
- ResultCode resultCode_;
- Slang::String executableName_;
-#ifdef WIN32
- Slang::StringBuilder commandLine_;
-#else
- Slang::List<Slang::String> arguments_;
-#endif
-
- // Only holds the argumements in order
- Slang::List<Slang::String> argumentList_;
-
- // Is the executable specified by path, rather than just by name?
- bool isExecutablePath_;
-};
-
-char const* osGetExecutableSuffix();
+#endif // SLANG_OS_H
diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp
index f030dc8aa..dac6ff9fe 100644
--- a/tools/slang-test/slang-test-main.cpp
+++ b/tools/slang-test/slang-test-main.cpp
@@ -18,6 +18,8 @@ using namespace Slang;
#include "options.h"
#include "slangc-tool.h"
+#include "../../source/core/slang-process-util.h"
+
#define STB_IMAGE_IMPLEMENTATION
#include "external/stb/stb_image.h"
@@ -93,7 +95,7 @@ typedef TestResult(*TestCallback)(TestContext* context, TestInput& input);
// Globals
// Pre declare
-static void _addRenderTestOptions(const Options& options, OSProcessSpawner& spawner);
+static void _addRenderTestOptions(const Options& options, CommandLine& cmdLine);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! Functions !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
@@ -382,51 +384,46 @@ TestResult gatherTestsForFile(
return TestResult::Pass;
}
-OSError spawnAndWaitExe(TestContext* context, const String& testPath, OSProcessSpawner& spawner)
+Result spawnAndWaitExe(TestContext* context, const String& testPath, const CommandLine& cmdLine, ExecuteResult& outRes)
{
const auto& options = context->options;
if (options.shouldBeVerbose)
{
- String commandLine = spawner.getCommandLine();
+ String commandLine = ProcessUtil::getCommandLineString(cmdLine);
context->reporter->messageFormat(TestMessageType::Info, "%s\n", commandLine.begin());
}
- OSError err = spawner.spawnAndWaitForCompletion();
- if (err != kOSError_None)
+ Result res = ProcessUtil::execute(cmdLine, outRes);
+ if (SLANG_FAILED(res))
{
// fprintf(stderr, "failed to run test '%S'\n", testPath.ToWString());
context->reporter->messageFormat(TestMessageType::RunError, "failed to run test '%S'", testPath.toWString().begin());
}
- return err;
+ return res;
}
-OSError spawnAndWaitSharedLibrary(TestContext* context, const String& testPath, OSProcessSpawner& spawner)
+Result spawnAndWaitSharedLibrary(TestContext* context, const String& testPath, const CommandLine& cmdLine, ExecuteResult& outRes)
{
const auto& options = context->options;
- String exeName = Path::getFileNameWithoutExt(spawner.executableName_);
+ String exeName = Path::getFileNameWithoutExt(cmdLine.m_executable);
if (options.shouldBeVerbose)
{
- StringBuilder builder;
-
- builder << "slang-test";
+ CommandLine testCmdLine;
+ testCmdLine.setExecutableFilename("slang-test");
if (options.binDir.getLength())
{
- builder << " -bindir " << options.binDir;
+ testCmdLine.addArg("-bindir");
+ testCmdLine.addArg(options.binDir);
}
- builder << " " << exeName;
+ testCmdLine.addArg(exeName);
+ testCmdLine.m_args.addRange(cmdLine.m_args);
- // TODO(js): Potentially this should handle escaping parameters for the command line if need be
- const auto& argList = spawner.argumentList_;
- for (Index i = 0; i < argList.getCount(); ++i)
- {
- builder << " " << argList[i];
- }
-
- context->reporter->messageFormat(TestMessageType::Info, "%s\n", builder.begin());
+ String testCmdLineString = ProcessUtil::getCommandLineString(testCmdLine);
+ context->reporter->messageFormat(TestMessageType::Info, "%s\n", testCmdLineString.getBuffer());
}
auto func = context->getInnerMainFunc(context->options.binDir, exeName);
@@ -452,24 +449,24 @@ OSError spawnAndWaitSharedLibrary(TestContext* context, const String& testPath,
List<const char*> args;
args.add(exeName.getBuffer());
- for (Index i = 0; i < spawner.argumentList_.getCount(); ++i)
+ for (Index i = 0; i < cmdLine.m_args.getCount(); ++i)
{
- args.add(spawner.argumentList_[i].getBuffer());
+ args.add(cmdLine.m_args[i].getBuffer());
}
SlangResult res = func(&stdWriters, context->getSession(), int(args.getCount()), args.begin());
StdWriters::setSingleton(prevStdWriters);
- spawner.standardError_ = stdErrorString;
- spawner.standardOutput_ = stdOutString;
+ outRes.standardError = stdErrorString;
+ outRes.standardOutput = stdOutString;
- spawner.resultCode_ = (int)TestToolUtil::getReturnCode(res);
+ outRes.resultCode = (int)TestToolUtil::getReturnCode(res);
- return kOSError_None;
+ return SLANG_OK;
}
- return kOSError_OperationFailed;
+ return SLANG_FAIL;
}
@@ -580,10 +577,10 @@ static SlangCompileTarget _getCompileTarget(const UnownedStringSlice& name)
return SLANG_TARGET_UNKNOWN;
}
-static SlangResult _extractRenderTestRequirements(OSProcessSpawner& spawner, TestRequirements* ioRequirements)
+static SlangResult _extractRenderTestRequirements(const CommandLine& cmdLine, TestRequirements* ioRequirements)
{
- const auto& args = spawner.argumentList_;
-
+ const auto& args = cmdLine.m_args;
+
// TODO(JS):
// This is rather convoluted in that it has to work out from the command line parameters passed
// to render-test what renderer will be used.
@@ -697,11 +694,11 @@ static SlangResult _extractRenderTestRequirements(OSProcessSpawner& spawner, Tes
return SLANG_OK;
}
-static SlangResult _extractSlangCTestRequirements(OSProcessSpawner& spawner, TestRequirements* ioRequirements)
+static SlangResult _extractSlangCTestRequirements(const CommandLine& cmdLine, TestRequirements* ioRequirements)
{
// This determines what the requirements are for a slangc like command line
- const auto& args = spawner.argumentList_;
-
+ const auto& args = cmdLine.m_args;
+
// First check pass through
{
String passThrough;
@@ -724,27 +721,27 @@ static SlangResult _extractSlangCTestRequirements(OSProcessSpawner& spawner, Tes
}
-static SlangResult _extractReflectionTestRequirements(OSProcessSpawner& spawner, TestRequirements* ioRequirements)
+static SlangResult _extractReflectionTestRequirements(const CommandLine& cmdLine, TestRequirements* ioRequirements)
{
// There are no specialized constraints for a reflection test
return SLANG_OK;
}
-static SlangResult _extractTestRequirements(OSProcessSpawner& spawner, TestRequirements* ioInfo)
+static SlangResult _extractTestRequirements(const CommandLine& cmdLine, TestRequirements* ioInfo)
{
- String exeName = Path::getFileNameWithoutExt(spawner.executableName_);
+ String exeName = Path::getFileNameWithoutExt(cmdLine.m_executable);
if (exeName == "render-test")
{
- return _extractRenderTestRequirements(spawner, ioInfo);
+ return _extractRenderTestRequirements(cmdLine, ioInfo);
}
else if (exeName == "slangc")
{
- return _extractSlangCTestRequirements(spawner, ioInfo);
+ return _extractSlangCTestRequirements(cmdLine, ioInfo);
}
else if (exeName == "slang-reflection-test")
{
- return _extractReflectionTestRequirements(spawner, ioInfo);
+ return _extractReflectionTestRequirements(cmdLine, ioInfo);
}
SLANG_ASSERT(!"Unknown tool type");
@@ -768,20 +765,21 @@ static RenderApiFlags _getAvailableRenderApiFlags(TestContext* context)
if (RenderApiUtil::calcHasApi(apiType))
{
// Try starting up the device
- OSProcessSpawner spawner;
- spawner.pushExecutablePath(Path::combine(context->options.binDir, String("render-test") + osGetExecutableSuffix()));
- _addRenderTestOptions(context->options, spawner);
+ CommandLine cmdLine;
+ cmdLine.setExecutablePath(Path::combine(context->options.binDir, String("render-test") + ProcessUtil::getExecutableSuffix()));
+ _addRenderTestOptions(context->options, cmdLine);
// We just want to see if the device can be started up
- spawner.pushArgument("-only-startup");
+ cmdLine.addArg("-only-startup");
// Select what api to use
StringBuilder builder;
builder << "-" << RenderApiUtil::getApiName(apiType);
- spawner.pushArgument(builder);
+ cmdLine.addArg(builder);
// Run the render-test tool and see if the device could startup
- if (spawnAndWaitSharedLibrary(context, "device-startup", spawner) == OSError::kOSError_None
- && TestToolUtil::getReturnCodeFromInt(spawner.resultCode_) == ToolReturnCode::Success)
+ ExecuteResult exeRes;
+ if (SLANG_SUCCEEDED(spawnAndWaitSharedLibrary(context, "device-startup", cmdLine, exeRes))
+ && TestToolUtil::getReturnCodeFromInt(exeRes.resultCode) == ToolReturnCode::Success)
{
availableRenderApiFlags |= RenderApiFlags(1) << int(apiType);
}
@@ -795,17 +793,17 @@ static RenderApiFlags _getAvailableRenderApiFlags(TestContext* context)
return context->availableRenderApiFlags;
}
-ToolReturnCode getReturnCode(OSProcessSpawner& spawner)
+ToolReturnCode getReturnCode(const ExecuteResult& exeRes)
{
- return TestToolUtil::getReturnCodeFromInt(spawner.getResultCode());
+ return TestToolUtil::getReturnCodeFromInt(exeRes.resultCode);
}
-ToolReturnCode spawnAndWait(TestContext* context, const String& testPath, SpawnType spawnType, OSProcessSpawner& spawner)
+ToolReturnCode spawnAndWait(TestContext* context, const String& testPath, SpawnType spawnType, const CommandLine& cmdLine, ExecuteResult& outExeRes)
{
if (context->isCollectingRequirements())
{
// If we just want info... don't bother running anything
- const SlangResult res = _extractTestRequirements(spawner, context->testRequirements);
+ const SlangResult res = _extractTestRequirements(cmdLine, context->testRequirements);
// Keep compiler happy on release
SLANG_UNUSED(res);
SLANG_ASSERT(SLANG_SUCCEEDED(res));
@@ -815,37 +813,37 @@ ToolReturnCode spawnAndWait(TestContext* context, const String& testPath, SpawnT
const auto& options = context->options;
- OSError spawnResult = kOSError_OperationFailed;
+ SlangResult spawnResult = SLANG_FAIL;
switch (spawnType)
{
case SpawnType::UseExe:
{
- spawnResult = spawnAndWaitExe(context, testPath, spawner);
+ spawnResult = spawnAndWaitExe(context, testPath, cmdLine, outExeRes);
break;
}
case SpawnType::UseSharedLibrary:
{
- spawnResult = spawnAndWaitSharedLibrary(context, testPath, spawner);
+ spawnResult = spawnAndWaitSharedLibrary(context, testPath, cmdLine, outExeRes);
break;
}
default: break;
}
- if (spawnResult != kOSError_None)
+ if (SLANG_FAILED(spawnResult))
{
return ToolReturnCode::FailedToRun;
}
- return getReturnCode(spawner);
+ return getReturnCode(outExeRes);
}
-String getOutput(OSProcessSpawner& spawner)
+String getOutput(const ExecuteResult& exeRes)
{
- OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
-
- String standardOuptut = spawner.getStandardOutput();
- String standardError = spawner.getStandardError();
-
+ ExecuteResult::ResultCode resultCode = exeRes.resultCode;
+
+ String standardOuptut = exeRes.standardOutput;
+ String standardError = exeRes.standardError;
+
// We construct a single output string that captures the results
StringBuilder actualOutputBuilder;
actualOutputBuilder.Append("result code = ");
@@ -897,13 +895,13 @@ String findExpectedPath(const TestInput& input, const char* postFix)
return "";
}
-static void _initSlangCompiler(TestContext* context, OSProcessSpawner& spawnerOut)
+static void _initSlangCompiler(TestContext* context, CommandLine& ioCmdLine)
{
- spawnerOut.pushExecutablePath(Path::combine(context->options.binDir, String("slangc") + osGetExecutableSuffix()));
+ ioCmdLine.setExecutablePath(Path::combine(context->options.binDir, String("slangc") + ProcessUtil::getExecutableSuffix()));
if (context->options.verbosePaths)
{
- spawnerOut.pushArgument("-verbose-paths");
+ ioCmdLine.addArg("-verbose-paths");
}
}
@@ -933,24 +931,25 @@ TestResult runSimpleTest(TestContext* context, TestInput& input)
auto filePath999 = input.filePath;
auto outputStem = input.outputStem;
- OSProcessSpawner spawner;
- _initSlangCompiler(context, spawner);
+ CommandLine cmdLine;
+ _initSlangCompiler(context, cmdLine);
- spawner.pushArgument(filePath999);
+ cmdLine.addArg(filePath999);
for( auto arg : input.testOptions->args )
{
- spawner.pushArgument(arg);
+ cmdLine.addArg(arg);
}
-
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, spawner));
+
+ ExecuteResult exeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, cmdLine, exeRes));
if (context->isCollectingRequirements())
{
return TestResult::Pass;
}
- String actualOutput = getOutput(spawner);
+ String actualOutput = getOutput(exeRes);
String expectedOutputPath = outputStem + ".expected";
String expectedOutput;
@@ -1009,24 +1008,25 @@ TestResult runReflectionTest(TestContext* context, TestInput& input)
auto filePath = input.filePath;
auto outputStem = input.outputStem;
- OSProcessSpawner spawner;
-
- spawner.pushExecutablePath(Path::combine(options.binDir, String("slang-reflection-test") + osGetExecutableSuffix()));
- spawner.pushArgument(filePath);
+ CommandLine cmdLine;
+
+ cmdLine.setExecutablePath(Path::combine(options.binDir, String("slang-reflection-test") + ProcessUtil::getExecutableSuffix()));
+ cmdLine.addArg(filePath);
for( auto arg : input.testOptions->args )
{
- spawner.pushArgument(arg);
+ cmdLine.addArg(arg);
}
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, spawner));
+ ExecuteResult exeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, cmdLine, exeRes));
if (context->isCollectingRequirements())
{
return TestResult::Pass;
}
- String actualOutput = getOutput(spawner);
+ String actualOutput = getOutput(exeRes);
String expectedOutputPath = outputStem + ".expected";
String expectedOutput;
@@ -1097,13 +1097,13 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
auto filePath = input.filePath;
auto outputStem = input.outputStem;
- OSProcessSpawner actualSpawner;
- OSProcessSpawner expectedSpawner;
+ CommandLine actualCmdLine;
+ CommandLine expectedCmdLine;
- _initSlangCompiler(context, actualSpawner);
- _initSlangCompiler(context, expectedSpawner);
+ _initSlangCompiler(context, actualCmdLine);
+ _initSlangCompiler(context, expectedCmdLine);
- actualSpawner.pushArgument(filePath);
+ actualCmdLine.addArg(filePath);
// TODO(JS): This should no longer be needed with TestInfo accumulated for a test
@@ -1124,23 +1124,23 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
{
case SLANG_DXIL_ASM:
{
- expectedSpawner.pushArgument(filePath + ".hlsl");
- expectedSpawner.pushArgument("-pass-through");
- expectedSpawner.pushArgument("dxc");
+ expectedCmdLine.addArg(filePath + ".hlsl");
+ expectedCmdLine.addArg("-pass-through");
+ expectedCmdLine.addArg("dxc");
break;
}
case SLANG_DXBC_ASM:
{
- expectedSpawner.pushArgument(filePath + ".hlsl");
- expectedSpawner.pushArgument("-pass-through");
- expectedSpawner.pushArgument("fxc");
+ expectedCmdLine.addArg(filePath + ".hlsl");
+ expectedCmdLine.addArg("-pass-through");
+ expectedCmdLine.addArg("fxc");
break;
}
default:
{
- expectedSpawner.pushArgument(filePath + ".glsl");
- expectedSpawner.pushArgument("-pass-through");
- expectedSpawner.pushArgument("glslang");
+ expectedCmdLine.addArg(filePath + ".glsl");
+ expectedCmdLine.addArg("-pass-through");
+ expectedCmdLine.addArg("glslang");
break;
}
}
@@ -1148,16 +1148,17 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
for( auto arg : input.testOptions->args )
{
- actualSpawner.pushArgument(arg);
- expectedSpawner.pushArgument(arg);
+ actualCmdLine.addArg(arg);
+ expectedCmdLine.addArg(arg);
}
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, expectedSpawner));
+ ExecuteResult expectedExeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, expectedCmdLine, expectedExeRes));
String expectedOutput;
if (context->isExecuting())
{
- expectedOutput = getOutput(expectedSpawner);
+ expectedOutput = getOutput(expectedExeRes);
String expectedOutputPath = outputStem + ".expected";
try
{
@@ -1169,14 +1170,15 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
}
}
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, actualSpawner));
+ ExecuteResult actualExeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, actualCmdLine, actualExeRes));
if (context->isCollectingRequirements())
{
return TestResult::Pass;
}
- String actualOutput = getOutput(actualSpawner);
+ String actualOutput = getOutput(actualExeRes);
TestResult result = TestResult::Pass;
@@ -1190,7 +1192,7 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
// to catch situations where, e.g., command-line options parsing
// caused the same error in both the Slang and glslang cases.
//
- if( actualSpawner.getResultCode() != 0 )
+ if(actualExeRes.resultCode != 0 )
{
result = TestResult::Fail;
}
@@ -1214,29 +1216,30 @@ TestResult generateHLSLBaseline(TestContext* context, TestInput& input)
auto filePath999 = input.filePath;
auto outputStem = input.outputStem;
- OSProcessSpawner spawner;
- _initSlangCompiler(context, spawner);
+ CommandLine cmdLine;
+ _initSlangCompiler(context, cmdLine);
- spawner.pushArgument(filePath999);
+ cmdLine.addArg(filePath999);
for( auto arg : input.testOptions->args )
{
- spawner.pushArgument(arg);
+ cmdLine.addArg(arg);
}
- spawner.pushArgument("-target");
- spawner.pushArgument("dxbc-assembly");
- spawner.pushArgument("-pass-through");
- spawner.pushArgument("fxc");
+ cmdLine.addArg("-target");
+ cmdLine.addArg("dxbc-assembly");
+ cmdLine.addArg("-pass-through");
+ cmdLine.addArg("fxc");
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, spawner));
+ ExecuteResult exeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, cmdLine, exeRes));
if (context->isCollectingRequirements())
{
return TestResult::Pass;
}
- String expectedOutput = getOutput(spawner);
+ String expectedOutput = getOutput(exeRes);
String expectedOutputPath = outputStem + ".expected";
try
{
@@ -1262,24 +1265,25 @@ TestResult runHLSLComparisonTest(TestContext* context, TestInput& input)
// need to execute the stand-alone Slang compiler on the file, and compare its output to what we expect
- OSProcessSpawner spawner;
- _initSlangCompiler(context, spawner);
+ CommandLine cmdLine;
+ _initSlangCompiler(context, cmdLine);
- spawner.pushArgument(filePath999);
+ cmdLine.addArg(filePath999);
for( auto arg : input.testOptions->args )
{
- spawner.pushArgument(arg);
+ cmdLine.addArg(arg);
}
// TODO: The compiler should probably define this automatically...
- spawner.pushArgument("-D");
- spawner.pushArgument("__SLANG__");
+ cmdLine.addArg("-D");
+ cmdLine.addArg("__SLANG__");
- spawner.pushArgument("-target");
- spawner.pushArgument("dxbc-assembly");
+ cmdLine.addArg("-target");
+ cmdLine.addArg("dxbc-assembly");
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, spawner));
+ ExecuteResult exeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, cmdLine, exeRes));
if (context->isCollectingRequirements())
{
@@ -1289,11 +1293,11 @@ TestResult runHLSLComparisonTest(TestContext* context, TestInput& input)
// We ignore output to stdout, and only worry about what the compiler
// wrote to stderr.
- OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
-
- String standardOutput = spawner.getStandardOutput();
- String standardError = spawner.getStandardError();
-
+ ExecuteResult::ResultCode resultCode = exeRes.resultCode;
+
+ String standardOutput = exeRes.standardOutput;
+ String standardError = exeRes.standardError;
+
// We construct a single output string that captures the results
StringBuilder actualOutputBuilder;
actualOutputBuilder.Append("result code = ");
@@ -1364,42 +1368,43 @@ TestResult doGLSLComparisonTestRun(TestContext* context,
auto filePath999 = input.filePath;
auto outputStem = input.outputStem;
- OSProcessSpawner spawner;
- _initSlangCompiler(context, spawner);
+ CommandLine cmdLine;
+ _initSlangCompiler(context, cmdLine);
- spawner.pushArgument(filePath999);
+ cmdLine.addArg(filePath999);
if( langDefine )
{
- spawner.pushArgument("-D");
- spawner.pushArgument(langDefine);
+ cmdLine.addArg("-D");
+ cmdLine.addArg(langDefine);
}
if( passThrough )
{
- spawner.pushArgument("-pass-through");
- spawner.pushArgument(passThrough);
+ cmdLine.addArg("-pass-through");
+ cmdLine.addArg(passThrough);
}
- spawner.pushArgument("-target");
- spawner.pushArgument("spirv-assembly");
+ cmdLine.addArg("-target");
+ cmdLine.addArg("spirv-assembly");
for( auto arg : input.testOptions->args )
{
- spawner.pushArgument(arg);
+ cmdLine.addArg(arg);
}
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, spawner));
+ ExecuteResult exeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, cmdLine, exeRes));
if (context->isCollectingRequirements())
{
return TestResult::Pass;
}
- OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
+ ExecuteResult::ResultCode resultCode = exeRes.resultCode;
- String standardOuptut = spawner.getStandardOutput();
- String standardError = spawner.getStandardError();
+ String standardOuptut = exeRes.standardOutput;
+ String standardError = exeRes.standardError;
// We construct a single output string that captures the results
StringBuilder outputBuilder;
@@ -1458,12 +1463,12 @@ TestResult runGLSLComparisonTest(TestContext* context, TestInput& input)
return TestResult::Pass;
}
-static void _addRenderTestOptions(const Options& options, OSProcessSpawner& spawner)
+static void _addRenderTestOptions(const Options& options, CommandLine& ioCmdLine)
{
if (options.adapter.getLength())
{
- spawner.pushArgument("-adapter");
- spawner.pushArgument(options.adapter);
+ ioCmdLine.addArg("-adapter");
+ ioCmdLine.addArg(options.adapter);
}
}
@@ -1473,25 +1478,25 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
auto filePath999 = input.filePath;
auto outputStem = input.outputStem;
- OSProcessSpawner spawner;
+ CommandLine cmdLine;
- spawner.pushExecutablePath(Path::combine(context->options.binDir, String("render-test") + osGetExecutableSuffix()));
- spawner.pushArgument(filePath999);
+ cmdLine.setExecutablePath(Path::combine(context->options.binDir, String("render-test") + ProcessUtil::getExecutableSuffix()));
+ cmdLine.addArg(filePath999);
- _addRenderTestOptions(context->options, spawner);
+ _addRenderTestOptions(context->options, cmdLine);
for (auto arg : input.testOptions->args)
{
- spawner.pushArgument(arg);
+ cmdLine.addArg(arg);
}
for (int i = 0; i < int(numLangOpts); ++i)
{
- spawner.pushArgument(langOpts[i]);
+ cmdLine.addArg(langOpts[i]);
}
- spawner.pushArgument("-o");
+ cmdLine.addArg("-o");
auto actualOutputFile = outputStem + ".actual.txt";
- spawner.pushArgument(actualOutputFile);
+ cmdLine.addArg(actualOutputFile);
if (context->isExecuting())
{
@@ -1499,7 +1504,8 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
File::writeAllText(actualOutputFile, "");
}
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, spawner));
+ ExecuteResult exeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, cmdLine, exeRes));
if (context->isCollectingRequirements())
{
@@ -1512,7 +1518,7 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
return TestResult::Fail;
}
- auto actualOutput = getOutput(spawner);
+ auto actualOutput = getOutput(exeRes);
auto expectedOutput = getExpectedOutput(outputStem);
if (actualOutput != expectedOutput)
{
@@ -1591,34 +1597,35 @@ TestResult doRenderComparisonTestRun(TestContext* context, TestInput& input, cha
auto filePath = input.filePath;
auto outputStem = input.outputStem;
- OSProcessSpawner spawner;
+ CommandLine cmdLine;
- spawner.pushExecutablePath(Path::combine(context->options.binDir, String("render-test") + osGetExecutableSuffix()));
- spawner.pushArgument(filePath);
+ cmdLine.setExecutablePath(Path::combine(context->options.binDir, String("render-test") + ProcessUtil::getExecutableSuffix()));
+ cmdLine.addArg(filePath);
- _addRenderTestOptions(context->options, spawner);
+ _addRenderTestOptions(context->options, cmdLine);
for( auto arg : input.testOptions->args )
{
- spawner.pushArgument(arg);
+ cmdLine.addArg(arg);
}
- spawner.pushArgument(langOption);
- spawner.pushArgument("-o");
- spawner.pushArgument(outputStem + outputKind + ".png");
+ cmdLine.addArg(langOption);
+ cmdLine.addArg("-o");
+ cmdLine.addArg(outputStem + outputKind + ".png");
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, spawner));
+ ExecuteResult exeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, cmdLine, exeRes));
if (context->isCollectingRequirements())
{
return TestResult::Pass;
}
- OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
-
- String standardOutput = spawner.getStandardOutput();
- String standardError = spawner.getStandardError();
+ ExecuteResult::ResultCode resultCode = exeRes.resultCode;
+ String standardOutput = exeRes.standardOutput;
+ String standardError = exeRes.standardError;
+
// We construct a single output string that captures the results
StringBuilder outputBuilder;
outputBuilder.Append("result code = ");
diff --git a/tools/slang-test/test-context.cpp b/tools/slang-test/test-context.cpp
index e4d1df449..0b17b3672 100644
--- a/tools/slang-test/test-context.cpp
+++ b/tools/slang-test/test-context.cpp
@@ -1,7 +1,7 @@
// test-context.cpp
#include "test-context.h"
-#include "os.h"
+#include "../../source/core/slang-io.h"
#include "../../source/core/slang-string-util.h"
#include <stdio.h>
diff --git a/tools/slang-test/test-reporter.cpp b/tools/slang-test/test-reporter.cpp
index d2402d61b..f7ab3cf1e 100644
--- a/tools/slang-test/test-reporter.cpp
+++ b/tools/slang-test/test-reporter.cpp
@@ -1,8 +1,8 @@
// test-reporter.cpp
#include "test-reporter.h"
-#include "os.h"
#include "../../source/core/slang-string-util.h"
+#include "../../source/core/slang-process-util.h"
#include <stdio.h>
#include <stdlib.h>
@@ -373,29 +373,31 @@ void TestReporter::_addResult(const TestInfo& info)
break;
}
- OSProcessSpawner spawner;
- spawner.pushExecutableName("appveyor");
- spawner.pushArgument("AddTest");
- spawner.pushArgument(info.name);
- spawner.pushArgument("-FileName");
+ CommandLine cmdLine;
+ cmdLine.setExecutableFilename("appveyor");
+ cmdLine.addArg("AddTest");
+ cmdLine.addArg(info.name);
+ cmdLine.addArg("-FileName");
// TODO: this isn't actually a file name in all cases
- spawner.pushArgument(info.name);
- spawner.pushArgument("-Framework");
- spawner.pushArgument("slang-test");
- spawner.pushArgument("-Outcome");
- spawner.pushArgument(resultString);
-
- auto err = spawner.spawnAndWaitForCompletion();
-
- if (err != kOSError_None)
+ cmdLine.addArg(info.name);
+ cmdLine.addArg("-Framework");
+ cmdLine.addArg("slang-test");
+ cmdLine.addArg("-Outcome");
+ cmdLine.addArg(resultString);
+
+ ExecuteResult exeRes;
+ SlangResult res = ProcessUtil::execute(cmdLine, exeRes);
+
+ if (SLANG_FAILED(res))
{
messageFormat(TestMessageType::Info, "failed to add appveyor test results for '%S'\n", info.name.toWString().begin());
#if 0
- fprintf(stderr, "[%d] TEST RESULT: %s {%d} {%s} {%s}\n", err, spawner.commandLine_.getBuffer(),
- spawner.getResultCode(),
- spawner.getStandardOutput().begin(),
- spawner.getStandardError().begin());
+ String cmdLineString = ProcessUtil::getCommandLineString(cmdLine);
+ fprintf(stderr, "[%d] TEST RESULT: %s {%d} {%s} {%s}\n", err, cmdLineString.getBuffer(),
+ exeRes.resultCode,
+ exeRes.standardOutput.begin(),
+ exeRes.standardError.begin());
#endif
}