Added scripting quality of life features #299
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue