Added scripting quality of life features #299

Merged
Pycorax merged 10 commits from SP3-6-c-scripting into main 2023-01-01 12:37:10 +08:00
3 changed files with 184 additions and 45 deletions
Showing only changes of commit abf9c6b813 - Show all commits

View File

@ -190,8 +190,9 @@ namespace SHADE
oss << "[ScriptEngine] Building " << (debug ? " debug " : "") << "Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; oss << "[ScriptEngine] Building " << (debug ? " debug " : "") << "Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!";
SHLOG_INFO(oss.str()); SHLOG_INFO(oss.str());
oss.str(""); oss.str("");
const bool BUILD_SUCCESS = SHExecUtilties::ExecBlockingCommand(generateBuildCommand(debug)) == 0; const auto BUILD_SUCCESS = SHExecUtilties::ExecBlockingProcess(L"C:\\Program Files\\dotnet\\dotnet.exe", generateBuildCommand(debug), true, true);
if (BUILD_SUCCESS) SHLOG_WARNING("Build Output: {}", SHStringUtilities::WstrToStr(BUILD_SUCCESS.StdOutput));
if (BUILD_SUCCESS.Code == 0)
{ {
// Copy to built dll to the working directory and replace // 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)) if (!copyFile("./tmp/SHADE_Scripting.dll", "SHADE_Scripting.dll", std::filesystem::copy_options::overwrite_existing))
@ -235,7 +236,7 @@ namespace SHADE
LoadScriptAssembly(); LoadScriptAssembly();
} }
return BUILD_SUCCESS; return BUILD_SUCCESS.Code == 0;
} }
void SHScriptEngine::GenerateScriptsCsProjFile(const std::filesystem::path& path) const void SHScriptEngine::GenerateScriptsCsProjFile(const std::filesystem::path& path) const
@ -626,9 +627,9 @@ namespace SHADE
std::wstring SHScriptEngine::generateBuildCommand(bool debug) std::wstring SHScriptEngine::generateBuildCommand(bool debug)
{ {
std::wostringstream oss; 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 << 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(); return oss.str();
} }
} }

View File

@ -23,44 +23,32 @@ namespace SHADE
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/
PROCESS_INFORMATION SHExecUtilties::ExecProcess(const std::wstring& path, const std::wstring& args) PROCESS_INFORMATION SHExecUtilties::ExecProcess(const std::wstring& path, const std::wstring& args)
{ {
STARTUPINFOW startInfo; return execProcess(path, args, nullptr, nullptr);
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;
} }
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 // Wait for execution to end
DWORD status; ExecResult result;
while (true) while (true)
{ {
const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &status); const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &result.Code);
if (!EXEC_SUCCESS) if (!EXEC_SUCCESS)
{ {
auto err = GetLastError(); auto err = GetLastError();
@ -71,11 +59,24 @@ namespace SHADE
} }
// Break only if process ends // 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.hProcess);
CloseHandle(procInfo.hThread); 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 return ExecBlockingProcess
( (
L"C:\\Windows\\system32\\cmd.exe", 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<HANDLE, HANDLE> SHExecUtilties::createIoPipes()
{
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = true;
saAttr.lpSecurityDescriptor = nullptr;
// First: Read | Second: Write
std::pair<HANDLE, HANDLE> 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<char, 256> 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);
}
} }

View File

@ -17,6 +17,16 @@ of DigiPen Institute of Technology is prohibited.
namespace SHADE namespace SHADE
{ {
/// <summary>
/// Stores the result of an execution of a process.
/// </summary>
struct SH_API ExecResult final
{
DWORD Code;
std::wstring StdOutput;
std::wstring StdErrOutput;
};
/// <summary> /// <summary>
/// Static class containing functions for executing external processes or commands. /// Static class containing functions for executing external processes or commands.
/// </summary> /// </summary>
@ -43,11 +53,13 @@ namespace SHADE
/// </summary> /// </summary>
/// <param name="path">Path to the processs to start.</param> /// <param name="path">Path to the processs to start.</param>
/// <param name="args">Arguments to pass to the process.</param> /// <param name="args">Arguments to pass to the process.</param>
/// <param name="output">If true, stdout will be routed to return.</param>
/// <param name="errorOutput">If true, outstderr will be routed to return.</param>
/// <returns>Return value of the process.</returns> /// <returns>Return value of the process.</returns>
/// <exception cref="std::runtime_error"> /// <exception cref="std::runtime_error">
/// Thrown if failed to start the process. /// Thrown if failed to start the process.
/// </exception> /// </exception>
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 */ /* Command Execution Functions */
@ -69,16 +81,51 @@ namespace SHADE
/// executing. /// executing.
/// </summary> /// </summary>
/// <param name="command">Command to execute.</param> /// <param name="command">Command to execute.</param>
/// <param name="output">If true, stdout will be routed to return.</param>
/// <param name="errorOutput">If true, outstderr will be routed to return.</param>
/// <returns>Return value of the process.</returns> /// <returns>Return value of the process.</returns>
/// <exception cref="std::runtime_error"> /// <exception cref="std::runtime_error">
/// Thrown if failed to start the process. /// Thrown if failed to start the process.
/// </exception> /// </exception>
static DWORD ExecBlockingCommand(const std::wstring& command); static ExecResult ExecBlockingCommand(const std::wstring& command, bool output = false, bool errorOutput = false);
/*---------------------------------------------------------------------------------*/
/* PowerShell Execution Functions */
/*---------------------------------------------------------------------------------*/
/// <summary>
/// Executes a specified command in PowerShell.
/// This call does not wait for the command to finish executing.
/// </summary>
/// <param name="command">Command to execute.</param>
/// <returns>
/// Information about the started cmd process that executes the command.
/// </returns>
/// <exception cref="std::runtime_error">
/// Thrown if failed to start the process.
/// </exception>
static PROCESS_INFORMATION ExecPowerShellCommand(const std::wstring& command);
/// <summary>
/// Executes a specified command in PowerShell and waits for that process to finish
/// executing.
/// </summary>
/// <param name="command">Command to execute.</param>
/// <returns>Return value of the process.</returns>
/// <exception cref="std::runtime_error">
/// Thrown if failed to start the process.
/// </exception>
static DWORD ExecBlockingPowerShellCommand(const std::wstring& command);
private: private:
/*-------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
/* Constructors/Destructors */ /* Constructors/Destructors */
/*-------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
SHExecUtilties() = delete; SHExecUtilties() = delete;
/*---------------------------------------------------------------------------------*/
/* Helper Functions */
/*---------------------------------------------------------------------------------*/
static PROCESS_INFORMATION execProcess(const std::wstring& path, const std::wstring& args, HANDLE outputWritePipe, HANDLE errorOutputWritePipe);
static std::pair<HANDLE, HANDLE> createIoPipes();
static void readPipeData(HANDLE readPipe, HANDLE writePipe, std::wstring& output);
}; };
} }