ProcessOps

This commit is contained in:
2025-11-26 22:12:49 +05:30
parent d9756df436
commit d24c7f3246
19 changed files with 497 additions and 317 deletions

2
.gitignore vendored
View File

@ -36,7 +36,6 @@
.vscode/* .vscode/*
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
!.vscode/*.code-snippets !.vscode/*.code-snippets
@ -47,4 +46,5 @@
*.vsix *.vsix
.cache/ .cache/
.local/
Build/ Build/

View File

@ -50,3 +50,10 @@ add_subdirectory(Src/)
if(IACore_BUILD_TESTS) if(IACore_BUILD_TESTS)
add_subdirectory(Tests) add_subdirectory(Tests)
endif() endif()
# -------------------------------------------------
# Local Development Sandboxes (not included in source control)
# -------------------------------------------------
if (EXISTS ".local")
add_subdirectory(.local)
endif()

View File

@ -1,6 +1,10 @@
set(SRC_FILES set(SRC_FILES
"imp/cpp/IACore.cpp" "imp/cpp/IACore.cpp"
"imp/cpp/Logger.cpp" "imp/cpp/Logger.cpp"
"imp/cpp/FileOps.cpp"
"imp/cpp/AsyncOps.cpp"
"imp/cpp/SocketOps.cpp"
"imp/cpp/ProcessOps.cpp"
) )
add_library(IACore STATIC ${SRC_FILES}) add_library(IACore STATIC ${SRC_FILES})

View File

@ -0,0 +1,22 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/AsyncOps.hpp>
namespace IACore
{
}

View File

View File

@ -15,7 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/Logger.hpp> #include <IACore/Logger.hpp>
#include <IACore/File.hpp> #include <IACore/FileOps.hpp>
namespace IACore namespace IACore
{ {

View File

@ -0,0 +1,308 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/ProcessOps.hpp>
namespace IACore
{
// ---------------------------------------------------------------------
// Output Buffering Helper
// Splits raw chunks into lines, preserving partial lines across chunks
// ---------------------------------------------------------------------
struct LineBuffer
{
String Accumulator;
Function<VOID(StringView)> &Callback;
VOID Append(IN PCCHAR data, IN SIZE_T size);
VOID Flush();
};
} // namespace IACore
namespace IACore
{
Expected<INT32, String> ProcessOps::SpawnProcessSync(IN CONST String &command, IN CONST String &args,
IN Function<VOID(IN StringView line)> onOutputLineCallback)
{
Atomic<NativeProcessID> id;
#if IA_PLATFORM_WINDOWS
return SpawnProcessWindows(command, args, onOutputLineCallback, id);
#else
return SpawnProcessPosix(command, args, onOutputLineCallback, id);
#endif
}
SharedPtr<ProcessHandle> ProcessOps::SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
IN Function<VOID(IN StringView line)> onOutputLineCallback,
IN Function<VOID(Expected<INT32, String>)> onFinishCallback)
{
SharedPtr<ProcessHandle> handle = std::make_shared<ProcessHandle>();
handle->IsRunning = true;
handle->ThreadHandle = JoiningThread([&, cmd = IA_MOVE(command), args = std::move(args)]() mutable {
#if IA_PLATFORM_WINDOWS
auto result = SpawnProcessWindows(cmd, args, onOutputLineCallback, handle->ID);
#else
auto result = SpawnProcessPosix(cmd, args, onOutputLineCallback, handle->ID);
#endif
handle->IsRunning = false;
if (onFinishCallback)
onFinishCallback(IA_MOVE(result));
});
return handle;
}
VOID ProcessOps::TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle)
{
if (!handle || !handle->IsActive())
return;
NativeProcessID pid = handle->ID.load();
if (pid == 0)
return;
#if IA_PLATFORM_WINDOWS
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (hProcess != NULL)
{
TerminateProcess(hProcess, 9);
CloseHandle(hProcess);
}
#else
kill(pid, SIGKILL);
#endif
}
} // namespace IACore
namespace IACore
{
#if IA_PLATFORM_WINDOWS
Expected<INT32, String> ProcessOps::SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback,
OUT Atomic<NativeProcessID> &id)
{
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
HANDLE hRead = NULL, hWrite = NULL;
if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
return tl::make_unexpected("Failed to create pipe");
// Ensure the read handle to the pipe for STDOUT is NOT inherited
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
return tl::make_unexpected("Failed to secure pipe handles");
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
si.hStdError = hWrite; // Merge stderr
si.hStdInput = NULL; // No input
PROCESS_INFORMATION pi = {0};
// Windows command line needs to be mutable and concatenated
String commandLine = BuildString("\"", command, "\" ", args);
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
CloseHandle(hWrite);
if (!success)
{
CloseHandle(hRead);
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
}
id.store(pi.dwProcessId);
// Read Loop
LineBuffer lineBuf{"", onOutputLineCallback};
DWORD bytesRead;
CHAR buffer[4096];
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
{
lineBuf.Append(buffer, bytesRead);
}
lineBuf.Flush();
// NOW we wait for exit code
DWORD exitCode = 0;
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exitCode);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hRead);
id.store(0);
return static_cast<INT32>(exitCode);
}
#endif
#if IA_PLATFORM_UNIX
Expected<INT32, String> ProcessOps::SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback,
OUT Atomic<NativeProcessID> &id)
{
int pipefd[2];
if (pipe(pipefd) == -1)
return tl::make_unexpected("Failed to create pipe");
pid_t pid = fork();
if (pid == -1)
{
return tl::make_unexpected("Failed to fork process");
}
else if (pid == 0)
{
// --- Child Process ---
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
// --- ARGUMENT PARSING START ---
std::vector<std::string> argStorage; // To keep strings alive
std::vector<char *> argv;
std::string cmdStr = command;
argv.push_back(cmdStr.data());
// Manual Quote-Aware Splitter
std::string currentToken;
bool inQuotes = false;
for (char c : args)
{
if (c == '\"')
{
inQuotes = !inQuotes;
// Determine if you want to keep the quotes or strip them.
// Usually for execvp, you strip them so the shell receives the raw content.
continue;
}
if (c == ' ' && !inQuotes)
{
if (!currentToken.empty())
{
argStorage.push_back(currentToken);
currentToken.clear();
}
}
else
{
currentToken += c;
}
}
if (!currentToken.empty())
{
argStorage.push_back(currentToken);
}
// Build char* array from the std::string storage
for (auto &s : argStorage)
{
argv.push_back(s.data());
}
argv.push_back(nullptr);
// --- ARGUMENT PARSING END ---
execvp(argv[0], argv.data());
_exit(127);
}
else
{
// --- Parent Process ---
id.store(pid);
close(pipefd[1]);
LineBuffer lineBuf{"", onOutputLineCallback};
char buffer[4096];
ssize_t count;
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
{
lineBuf.Append(buffer, count);
}
lineBuf.Flush();
close(pipefd[0]);
int status;
waitpid(pid, &status, 0);
id.store(0);
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
}
#endif
} // namespace IACore
namespace IACore
{
VOID LineBuffer::Append(IN PCCHAR data, IN SIZE_T size)
{
SIZE_T start = 0;
for (SIZE_T i = 0; i < size; ++i)
{
if (data[i] == '\n' || data[i] == '\r')
{
// Flush Accumulator + current chunk
if (!Accumulator.empty())
{
Accumulator.append(data + start, i - start);
if (!Accumulator.empty())
Callback(Accumulator);
Accumulator.clear();
}
else
{
// Zero copy optimization for pure lines in one chunk
if (i > start)
Callback(StringView(data + start, i - start));
}
// Skip \r\n sequence if needed, or just start next
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
i++;
start = i + 1;
}
}
// Save remaining partial line
if (start < size)
{
Accumulator.append(data + start, size - start);
}
}
VOID LineBuffer::Flush()
{
if (!Accumulator.empty())
{
Callback(Accumulator);
Accumulator.clear();
}
}
} // namespace IACore

View File

@ -0,0 +1,22 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/SocketOps.hpp>
namespace IACore
{
}

View File

@ -0,0 +1,24 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
namespace IACore
{
}

View File

@ -27,6 +27,7 @@
# include <new> # include <new>
# include <span> # include <span>
# include <atomic> # include <atomic>
# include <mutex>
# include <thread> # include <thread>
# include <limits> # include <limits>
# include <cstring> # include <cstring>
@ -501,6 +502,7 @@ STATIC CONST FLOAT64 FLOAT64_EPSILON = DBL_EPSILON;
# include <sys/stat.h> # include <sys/stat.h>
# include <fcntl.h> # include <fcntl.h>
# include <spawn.h> # include <spawn.h>
# include <signal.h>
#endif #endif
#if IA_CHECK(IA_PLATFORM_WIN64) || IA_CHECK(IA_PLATFORM_UNIX) #if IA_CHECK(IA_PLATFORM_WIN64) || IA_CHECK(IA_PLATFORM_UNIX)
@ -524,6 +526,9 @@ template<typename _key_type> using UnorderedSet = ankerl::unordered_dense::set<_
template<typename _value_type> using Span = std::span<_value_type>; template<typename _value_type> using Span = std::span<_value_type>;
template<typename _key_type, typename _value_type> template<typename _key_type, typename _value_type>
using UnorderedMap = ankerl::unordered_dense::map<_key_type, _value_type>; using UnorderedMap = ankerl::unordered_dense::map<_key_type, _value_type>;
template<typename _value_type> using Atomic = std::atomic<_value_type>;
template<typename _value_type> using SharedPtr = std::shared_ptr<_value_type>;
template<typename _value_type> using UniquePtr = std::unique_ptr<_value_type>;
template<typename _expected_type, typename _unexpected_type> template<typename _expected_type, typename _unexpected_type>
using Expected = tl::expected<_expected_type, _unexpected_type>; using Expected = tl::expected<_expected_type, _unexpected_type>;
@ -537,7 +542,9 @@ using HRClock = std::chrono::high_resolution_clock;
using HRTimePoint = std::chrono::time_point<HRClock>; using HRTimePoint = std::chrono::time_point<HRClock>;
using Mutex = std::mutex; using Mutex = std::mutex;
using LockGuard = std::lock_guard<Mutex>; using ScopedLock = std::scoped_lock<Mutex>;
using UniqueLock = std::unique_lock<Mutex>;
using JoiningThread = std::jthread;
template<typename... Args> using FormatterString = std::format_string<Args...>; template<typename... Args> using FormatterString = std::format_string<Args...>;

View File

@ -1,292 +0,0 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
namespace IACore
{
class Process
{
public:
// ---------------------------------------------------------------------
// Static One-Shot Execution
// ---------------------------------------------------------------------
// Returns Exit Code or Error String
// callback receives distinct lines of output (stdout + stderr merged)
STATIC tl::expected<INT32, String> Run(CONST String &cmd, CONST String &args,
Function<VOID(StringView)> onOutputLine)
{
#if IA_PLATFORM_WINDOWS
return RunWindows(cmd, args, onOutputLine);
#else
return RunPosix(cmd, args, onOutputLine);
#endif
}
// ---------------------------------------------------------------------
// Async Execution
// ---------------------------------------------------------------------
// Returns a jthread.
// - Store it if you want to wait for it later (join).
// - If you destroy the returned jthread immediately, it will BLOCK (join) by design.
// - If you want "fire and forget", call .detach() on the returned thread.
STATIC std::jthread RunAsync(String cmd, String args, Function<VOID(StringView)> onOutputLine,
Function<VOID(tl::expected<INT32, String>)> onComplete)
{
// We capture arguments by VALUE (=) to ensure the thread owns its own copies of the strings.
return std::jthread([=, cmd = std::move(cmd), args = std::move(args)]() mutable {
tl::expected<INT32, String> result = tl::make_unexpected("Not started");
#if IA_PLATFORM_WINDOWS
result = RunWindows(cmd, args, onOutputLine);
#else
result = RunPosix(cmd, args, onOutputLine);
#endif
// Report final result to the callback
if (onComplete)
{
onComplete(std::move(result));
}
});
}
private:
// ---------------------------------------------------------------------
// Output Buffering Helper
// Splits raw chunks into lines, preserving partial lines across chunks
// ---------------------------------------------------------------------
struct LineBuffer
{
String accumulator;
Function<VOID(StringView)> &callback;
VOID Append(const char *data, SIZE_T size)
{
SIZE_T start = 0;
for (SIZE_T i = 0; i < size; ++i)
{
if (data[i] == '\n' || data[i] == '\r')
{
// Flush accumulator + current chunk
if (!accumulator.empty())
{
accumulator.append(data + start, i - start);
if (!accumulator.empty())
callback(accumulator);
accumulator.clear();
}
else
{
// Zero copy optimization for pure lines in one chunk
if (i > start)
callback(StringView(data + start, i - start));
}
// Skip \r\n sequence if needed, or just start next
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
i++;
start = i + 1;
}
}
// Save remaining partial line
if (start < size)
{
accumulator.append(data + start, size - start);
}
}
VOID Flush()
{
if (!accumulator.empty())
{
callback(accumulator);
accumulator.clear();
}
}
};
// ---------------------------------------------------------------------
// Windows Implementation
// ---------------------------------------------------------------------
#if IA_PLATFORM_WINDOWS
STATIC tl::expected<INT32, String> RunWindows(CONST String &cmd, CONST String &args,
Function<VOID(StringView)> cb)
{
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
HANDLE hRead = NULL, hWrite = NULL;
if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
return tl::make_unexpected("Failed to create pipe");
// Ensure the read handle to the pipe for STDOUT is NOT inherited
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
return tl::make_unexpected("Failed to secure pipe handles");
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
si.hStdError = hWrite; // Merge stderr
si.hStdInput = NULL; // No input
PROCESS_INFORMATION pi = {0};
// Windows command line needs to be mutable and concatenated
String commandLine = BuildString("\"", cmd, "\" ", args);
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
CloseHandle(hWrite);
if (!success)
{
CloseHandle(hRead);
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
}
// Read Loop
LineBuffer lineBuf{"", cb};
DWORD bytesRead;
CHAR buffer[4096];
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
{
lineBuf.Append(buffer, bytesRead);
}
lineBuf.Flush();
// NOW we wait for exit code
DWORD exitCode = 0;
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exitCode);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hRead);
return static_cast<INT32>(exitCode);
}
#endif
// ---------------------------------------------------------------------
// POSIX (Linux/Mac) Implementation
// ---------------------------------------------------------------------
#if IA_PLATFORM_UNIX
STATIC tl::expected<INT32, String> RunPosix(CONST String &cmd, CONST String &args,
Function<VOID(StringView)> cb)
{
int pipefd[2];
if (pipe(pipefd) == -1)
return tl::make_unexpected("Failed to create pipe");
pid_t pid = fork();
if (pid == -1)
{
return tl::make_unexpected("Failed to fork process");
}
else if (pid == 0)
{
// --- Child Process ---
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
// --- ARGUMENT PARSING START ---
std::vector<std::string> argStorage; // To keep strings alive
std::vector<char *> argv;
std::string cmdStr = cmd;
argv.push_back(cmdStr.data());
// Manual Quote-Aware Splitter
std::string currentToken;
bool inQuotes = false;
for (char c : args)
{
if (c == '\"')
{
inQuotes = !inQuotes;
// Determine if you want to keep the quotes or strip them.
// Usually for execvp, you strip them so the shell receives the raw content.
continue;
}
if (c == ' ' && !inQuotes)
{
if (!currentToken.empty())
{
argStorage.push_back(currentToken);
currentToken.clear();
}
}
else
{
currentToken += c;
}
}
if (!currentToken.empty())
{
argStorage.push_back(currentToken);
}
// Build char* array from the std::string storage
for (auto &s : argStorage)
{
argv.push_back(s.data());
}
argv.push_back(nullptr);
// --- ARGUMENT PARSING END ---
execvp(argv[0], argv.data());
_exit(127);
}
else
{
// --- Parent Process ---
close(pipefd[1]);
LineBuffer lineBuf{"", cb};
char buffer[4096];
ssize_t count;
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
{
lineBuf.Append(buffer, count);
}
lineBuf.Flush();
close(pipefd[0]);
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
}
#endif
};
} // namespace IACore

View File

@ -0,0 +1,64 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
#if IA_PLATFORM_WINDOWS
using NativeProcessID = DWORD;
#elif IA_PLATFORM_UNIX
using NativeProcessID = pid_t;
#else
# error "This platform does not support IACore ProcessOps"
#endif
namespace IACore
{
struct ProcessHandle
{
Atomic<NativeProcessID> ID{0};
Atomic<BOOL> IsRunning{false};
BOOL IsActive() CONST
{
return IsRunning && ID != 0;
}
private:
JoiningThread ThreadHandle;
friend class ProcessOps;
};
class ProcessOps
{
public:
STATIC Expected<INT32, String> SpawnProcessSync(IN CONST String &command, IN CONST String &args,
IN Function<VOID(IN StringView line)> onOutputLineCallback);
STATIC SharedPtr<ProcessHandle> SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
IN Function<VOID(IN StringView line)> onOutputLineCallback,
IN Function<VOID(Expected<INT32, String>)> onFinishCallback);
STATIC VOID TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle);
private:
STATIC Expected<INT32, String> SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID>& id);
STATIC Expected<INT32, String> SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID>& id);
};
} // namespace IACore

View File

@ -1,3 +1,4 @@
add_subdirectory(Subjects/)
add_subdirectory(Unit/) add_subdirectory(Unit/)
add_subdirectory(Regression/) add_subdirectory(Regression/)

View File

@ -0,0 +1 @@
add_executable(LongProcess LongProcess/Main.cpp)

View File

@ -0,0 +1,12 @@
#include <thread>
#include <iostream>
int main(int, char **)
{
std::cout << "Started!\n";
std::cout.flush();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "Ended!\n";
std::cout.flush();
return 100;
}

View File

@ -38,20 +38,20 @@ target_compile_options(${TEST_NAME_PREFIX}Environment PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}Environment PROPERTIES USE_EXCEPTIONS ON) set_target_properties(${TEST_NAME_PREFIX}Environment PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------ # ------------------------------------------------
# Unit: File # Unit: FileOps
# ------------------------------------------------ # ------------------------------------------------
add_executable(${TEST_NAME_PREFIX}File "File.cpp") add_executable(${TEST_NAME_PREFIX}FileOps "FileOps.cpp")
target_link_libraries(${TEST_NAME_PREFIX}File PRIVATE IACore) target_link_libraries(${TEST_NAME_PREFIX}FileOps PRIVATE IACore)
target_compile_options(${TEST_NAME_PREFIX}File PRIVATE -fexceptions) target_compile_options(${TEST_NAME_PREFIX}FileOps PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}File PROPERTIES USE_EXCEPTIONS ON) set_target_properties(${TEST_NAME_PREFIX}FileOps PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------ # ------------------------------------------------
# Unit: Process # Unit: ProcessOps
# ------------------------------------------------ # ------------------------------------------------
add_executable(${TEST_NAME_PREFIX}Process "Process.cpp") add_executable(${TEST_NAME_PREFIX}ProcessOps "ProcessOps.cpp")
target_link_libraries(${TEST_NAME_PREFIX}Process PRIVATE IACore) target_link_libraries(${TEST_NAME_PREFIX}ProcessOps PRIVATE IACore)
target_compile_options(${TEST_NAME_PREFIX}Process PRIVATE -fexceptions) target_compile_options(${TEST_NAME_PREFIX}ProcessOps PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}Process PROPERTIES USE_EXCEPTIONS ON) set_target_properties(${TEST_NAME_PREFIX}ProcessOps PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------ # ------------------------------------------------
# Unit: Utils # Unit: Utils

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/File.hpp> #include <IACore/FileOps.hpp>
#include <IACore/IATest.hpp> #include <IACore/IATest.hpp>
@ -129,7 +129,7 @@ IAT_BEGIN_BLOCK(Core, File)
// 1. Write // 1. Write
{ {
File f; File f;
auto res = f.Open(path, flagsWrite); auto res = f.Open(path, (File::EOpenFlags)flagsWrite);
IAT_CHECK(res.has_value()); IAT_CHECK(res.has_value());
IAT_CHECK(f.IsOpen()); IAT_CHECK(f.IsOpen());
@ -145,7 +145,7 @@ IAT_BEGIN_BLOCK(Core, File)
UINT32 flagsRead = (UINT32)File::EOpenFlags::Read | UINT32 flagsRead = (UINT32)File::EOpenFlags::Read |
(UINT32)File::EOpenFlags::Binary; (UINT32)File::EOpenFlags::Binary;
File f(path, flagsRead); // Test RAII constructor File f(path, (File::EOpenFlags)flagsRead); // Test RAII constructor
IAT_CHECK(f.IsOpen()); IAT_CHECK(f.IsOpen());
UINT32 magicRead = 0; UINT32 magicRead = 0;
@ -172,7 +172,7 @@ IAT_BEGIN_BLOCK(Core, File)
// Open in append mode // Open in append mode
File f; File f;
const auto openResult = f.Open(path, flagsAppend); const auto openResult = f.Open(path, (File::EOpenFlags)flagsAppend);
if(!openResult) if(!openResult)
{ {
IA_PANIC(openResult.error().c_str()) IA_PANIC(openResult.error().c_str())
@ -209,7 +209,7 @@ IAT_BEGIN_BLOCK(Core, File)
// Test Instance // Test Instance
File f; File f;
auto resInstance = f.Open(ghostPath, (UINT32)File::EOpenFlags::Read); auto resInstance = f.Open(ghostPath, File::EOpenFlags::Read);
IAT_CHECK_NOT(resInstance.has_value()); IAT_CHECK_NOT(resInstance.has_value());
IAT_CHECK_NOT(f.IsOpen()); IAT_CHECK_NOT(f.IsOpen());

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/Process.hpp> #include <IACore/ProcessOps.hpp>
#include <IACore/IATest.hpp> #include <IACore/IATest.hpp>
@ -44,7 +44,7 @@ IAT_BEGIN_BLOCK(Core, Process)
// Simple "echo hello" // Simple "echo hello"
String captured; String captured;
auto result = Process::Run(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA", auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
[&](StringView line) { [&](StringView line) {
captured = line; captured = line;
} }
@ -73,7 +73,7 @@ IAT_BEGIN_BLOCK(Core, Process)
String args = String(CMD_ARG_PREFIX) + " one two"; String args = String(CMD_ARG_PREFIX) + " one two";
if(args[0] == ' ') args.erase(0, 1); // cleanup space if prefix empty if(args[0] == ' ') args.erase(0, 1); // cleanup space if prefix empty
auto result = Process::Run(CMD_ECHO_EXE, args, auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args,
[&](StringView line) { [&](StringView line) {
lines.push_back(String(line)); lines.push_back(String(line));
} }
@ -107,7 +107,7 @@ IAT_BEGIN_BLOCK(Core, Process)
arg = "-c \"exit 42\""; // quotes needed for sh -c arg = "-c \"exit 42\""; // quotes needed for sh -c
#endif #endif
auto result = Process::Run(cmd, arg, [](StringView){}); auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView){});
IAT_CHECK(result.has_value()); IAT_CHECK(result.has_value());
IAT_CHECK_EQ(*result, 42); IAT_CHECK_EQ(*result, 42);
@ -121,7 +121,7 @@ IAT_BEGIN_BLOCK(Core, Process)
BOOL TestMissingExe() BOOL TestMissingExe()
{ {
// Try to run a random string // Try to run a random string
auto result = Process::Run("sdflkjghsdflkjg", "", [](StringView){}); auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView){});
// Windows: CreateProcess usually fails -> returns unexpected // Windows: CreateProcess usually fails -> returns unexpected
// Linux: execvp fails inside child, returns 127 via waitpid // Linux: execvp fails inside child, returns 127 via waitpid
@ -166,7 +166,7 @@ IAT_BEGIN_BLOCK(Core, Process)
#endif #endif
String captured; String captured;
auto result = Process::Run(cmd, arg, auto result = ProcessOps::SpawnProcessSync(cmd, arg,
[&](StringView line) { [&](StringView line) {
captured += line; captured += line;
} }
@ -203,7 +203,7 @@ IAT_BEGIN_BLOCK(Core, Process)
bool foundA = false; bool foundA = false;
bool foundB = false; bool foundB = false;
UNUSED(Process::Run(cmd, arg, [&](StringView line) { UNUSED(ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) {
lineCount++; lineCount++;
if (line.find("LineA") != String::npos) foundA = true; if (line.find("LineA") != String::npos) foundA = true;
if (line.find("LineB") != String::npos) foundB = true; if (line.find("LineB") != String::npos) foundB = true;