From abf9c6b81374347cedf62f1604592a84e63caea4 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 29 Dec 2022 23:46:22 +0800 Subject: [PATCH] Refactored SHExecUtilities to return additional data --- SHADE_Engine/src/Scripting/SHScriptEngine.cpp | 11 +- .../src/Tools/Utilities/SHExecUtilities.cpp | 161 ++++++++++++++---- .../src/Tools/Utilities/SHExecUtilities.h | 57 ++++++- 3 files changed, 184 insertions(+), 45 deletions(-) diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp index b4f893d7..ea4970a9 100644 --- a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp @@ -190,8 +190,9 @@ namespace SHADE oss << "[ScriptEngine] Building " << (debug ? " debug " : "") << "Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; SHLOG_INFO(oss.str()); oss.str(""); - const bool BUILD_SUCCESS = SHExecUtilties::ExecBlockingCommand(generateBuildCommand(debug)) == 0; - if (BUILD_SUCCESS) + const auto BUILD_SUCCESS = SHExecUtilties::ExecBlockingProcess(L"C:\\Program Files\\dotnet\\dotnet.exe", generateBuildCommand(debug), true, true); + SHLOG_WARNING("Build Output: {}", SHStringUtilities::WstrToStr(BUILD_SUCCESS.StdOutput)); + if (BUILD_SUCCESS.Code == 0) { // Copy to built dll to the working directory and replace if (!copyFile("./tmp/SHADE_Scripting.dll", "SHADE_Scripting.dll", std::filesystem::copy_options::overwrite_existing)) @@ -235,7 +236,7 @@ namespace SHADE LoadScriptAssembly(); } - return BUILD_SUCCESS; + return BUILD_SUCCESS.Code == 0; } void SHScriptEngine::GenerateScriptsCsProjFile(const std::filesystem::path& path) const @@ -626,9 +627,9 @@ namespace SHADE std::wstring SHScriptEngine::generateBuildCommand(bool debug) { std::wostringstream oss; - oss << "dotnet build \"" << SHStringUtilities::StrToWstr(CSPROJ_PATH) << "\" -c "; + oss << " build \"" << SHStringUtilities::StrToWstr(std::filesystem::absolute(CSPROJ_PATH).string()) << "\" -c "; oss << debug ? "Debug" : "Release"; - oss << " -o \"./tmp/\" -fl -flp:LogFile=build.log;Verbosity=quiet -r \"win-x64\""; + oss << " -o \"./tmp/\" -r \"win-x64\""; return oss.str(); } } diff --git a/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp index c1bae764..f3f6334d 100644 --- a/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp +++ b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp @@ -23,44 +23,32 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ PROCESS_INFORMATION SHExecUtilties::ExecProcess(const std::wstring& path, const std::wstring& args) { - STARTUPINFOW startInfo; - PROCESS_INFORMATION procInfo; - ZeroMemory(&startInfo, sizeof(startInfo)); - ZeroMemory(&procInfo, sizeof(procInfo)); - startInfo.cb = sizeof(startInfo); - - std::wstring argsWstr = args; - - // Start Process - const auto SUCCESS = CreateProcess - ( - path.data(), argsWstr.data(), - nullptr, nullptr, false, NULL, nullptr, nullptr, - &startInfo, &procInfo - ); - - // Error Check - if (!SUCCESS) - { - auto err = GetLastError(); - std::ostringstream oss; - oss << "[SHExecUtilties] Failed to launch process. Error code: " << std::hex << err - << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; - throw std::runtime_error(oss.str()); - } - - return procInfo; + return execProcess(path, args, nullptr, nullptr); } - DWORD SHExecUtilties::ExecBlockingProcess(const std::wstring& path, const std::wstring& args) + ExecResult SHExecUtilties::ExecBlockingProcess(const std::wstring& path, const std::wstring& args, bool output, bool errorOutput) { - PROCESS_INFORMATION procInfo = ExecProcess(path, args); + // Create pipes for stdout and stderr if specified + HANDLE stdoutReadPipe = nullptr; + HANDLE stdoutWritePipe = nullptr; + HANDLE stderrReadPipe = nullptr; + HANDLE stderrWritePipe = nullptr; + if (output) + { + std::tie(stdoutReadPipe, stdoutWritePipe) = createIoPipes(); + } + if (errorOutput) + { + std::tie(stderrReadPipe, stderrWritePipe) = createIoPipes(); + } + + PROCESS_INFORMATION procInfo = execProcess(path, args, stdoutWritePipe, stderrWritePipe); // Wait for execution to end - DWORD status; + ExecResult result; while (true) { - const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &status); + const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &result.Code); if (!EXEC_SUCCESS) { auto err = GetLastError(); @@ -71,11 +59,24 @@ namespace SHADE } // Break only if process ends - if (status != STILL_ACTIVE) + if (result.Code != STILL_ACTIVE) { + // Get stdout/stderror output + if (stdoutReadPipe) + { + readPipeData(stdoutReadPipe, stdoutWritePipe, result.StdOutput); + } + if (stderrReadPipe) + { + readPipeData(stderrReadPipe, stderrWritePipe, result.StdErrOutput); + } + + // Close the process and threads for that process CloseHandle(procInfo.hProcess); CloseHandle(procInfo.hThread); - return status; + + // Return results + return result; } } } @@ -92,12 +93,102 @@ namespace SHADE ); } - DWORD SHExecUtilties::ExecBlockingCommand(const std::wstring& command) + ExecResult SHExecUtilties::ExecBlockingCommand(const std::wstring& command, bool output, bool errorOutput) { return ExecBlockingProcess ( L"C:\\Windows\\system32\\cmd.exe", - L"/K \"" + command + L" & exit\"" + L"/K \"" + command + L" & exit\"", + output, + errorOutput ); } + + PROCESS_INFORMATION SHExecUtilties::execProcess(const std::wstring& path, const std::wstring& args, HANDLE outputWritePipe, HANDLE errorOutputWritePipe) + { + STARTUPINFOW startInfo; + PROCESS_INFORMATION procInfo; + ZeroMemory(&startInfo, sizeof(startInfo)); + ZeroMemory(&procInfo, sizeof(procInfo)); + startInfo.cb = sizeof(startInfo); + if (outputWritePipe) + { + startInfo.hStdOutput = outputWritePipe; + startInfo.dwFlags |= STARTF_USESTDHANDLES; + startInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + } + if (errorOutputWritePipe) + { + startInfo.hStdError = errorOutputWritePipe; + startInfo.dwFlags |= STARTF_USESTDHANDLES; + startInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + } + + std::wstring argsWstr = args; + + // Start Process + const auto SUCCESS = CreateProcess + ( + path.data(), argsWstr.data(), + nullptr, nullptr, true, NULL, nullptr, nullptr, + &startInfo, &procInfo + ); + + // Error Check + if (!SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[SHExecUtilties] Failed to launch process. Error code: " << std::hex << err + << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + return procInfo; + } + + std::pair SHExecUtilties::createIoPipes() + { + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = true; + saAttr.lpSecurityDescriptor = nullptr; + + // First: Read | Second: Write + std::pair output = { nullptr, nullptr }; + if (CreatePipe(&output.first, &output.second, &saAttr, 0)) + { + if (!SetHandleInformation(output.first, HANDLE_FLAG_INHERIT, 0)) + { + SHLOG_ERROR("[SHExecUtilities] Failed to initialize pipe for process IO."); + CloseHandle(output.first); + CloseHandle(output.second); + output = { nullptr, nullptr }; + } + } + else + { + SHLOG_ERROR("[SHExecUtilities] Failed to create pipe for process IO."); + } + + return output; + } + + void SHExecUtilties::readPipeData(HANDLE readPipe, HANDLE writePipe, std::wstring& output) + { + CloseHandle(writePipe); + + LARGE_INTEGER stdoutSize = {}; + while (true) + { + std::array buffer{}; + DWORD bytesRead = 0; + const auto RESULT = ReadFile(readPipe, buffer.data(), buffer.size(), &bytesRead, nullptr); // Blocking call here? + if (!RESULT || bytesRead <= 0) + break; + output.insert(output.end(), buffer.data(), buffer.data() + bytesRead); + } + + CloseHandle(readPipe); + } } diff --git a/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h index 838cee41..9c5f38eb 100644 --- a/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h +++ b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h @@ -17,6 +17,16 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { + /// + /// Stores the result of an execution of a process. + /// + struct SH_API ExecResult final + { + DWORD Code; + std::wstring StdOutput; + std::wstring StdErrOutput; + }; + /// /// Static class containing functions for executing external processes or commands. /// @@ -43,11 +53,13 @@ namespace SHADE /// /// Path to the processs to start. /// Arguments to pass to the process. + /// If true, stdout will be routed to return. + /// If true, outstderr will be routed to return. /// Return value of the process. /// /// Thrown if failed to start the process. /// - static DWORD ExecBlockingProcess(const std::wstring& path, const std::wstring& args); + static ExecResult ExecBlockingProcess(const std::wstring& path, const std::wstring& args, bool output = false, bool errorOutput = false); /*---------------------------------------------------------------------------------*/ /* Command Execution Functions */ @@ -69,16 +81,51 @@ namespace SHADE /// executing. /// /// Command to execute. + /// If true, stdout will be routed to return. + /// If true, outstderr will be routed to return. /// Return value of the process. /// /// Thrown if failed to start the process. /// - static DWORD ExecBlockingCommand(const std::wstring& command); + static ExecResult ExecBlockingCommand(const std::wstring& command, bool output = false, bool errorOutput = false); + + /*---------------------------------------------------------------------------------*/ + /* PowerShell Execution Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Executes a specified command in PowerShell. + /// This call does not wait for the command to finish executing. + /// + /// Command to execute. + /// + /// Information about the started cmd process that executes the command. + /// + /// + /// Thrown if failed to start the process. + /// + static PROCESS_INFORMATION ExecPowerShellCommand(const std::wstring& command); + /// + /// Executes a specified command in PowerShell and waits for that process to finish + /// executing. + /// + /// Command to execute. + /// Return value of the process. + /// + /// Thrown if failed to start the process. + /// + static DWORD ExecBlockingPowerShellCommand(const std::wstring& command); private: - /*-------------------------------------------------------------------------------*/ - /* Constructors/Destructors */ - /*-------------------------------------------------------------------------------*/ + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructors */ + /*---------------------------------------------------------------------------------*/ SHExecUtilties() = delete; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + static PROCESS_INFORMATION execProcess(const std::wstring& path, const std::wstring& args, HANDLE outputWritePipe, HANDLE errorOutputWritePipe); + static std::pair createIoPipes(); + static void readPipeData(HANDLE readPipe, HANDLE writePipe, std::wstring& output); }; }