From 8b4a81dc593823a8484fbdce92f216f550cab941 Mon Sep 17 00:00:00 2001 From: Charles Baker Date: Mon, 18 May 2026 10:35:55 +1200 Subject: [PATCH 1/8] Add AGENTS.md and CLAUDE.md which imports the former --- AGENTS.md | 39 +++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + 2 files changed, 40 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..69c6ec01 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,39 @@ +# Agent notes for Forge + +Forge is a Lua-scriptable build tool. The C++ core builds a dependency graph of targets/rules/toolsets, binds targets to files, caches implicit dependencies and settings hashes, and rebuilds outdated targets based on timestamps/settings. Lua bindings and Lua rules provide the user-facing build DSL. + +## Where to look + +- `forge.lua` is the root build script for this repository. +- `src/forge/*.cpp` and `src/forge/*.hpp` are the core engine: `Forge`, `Graph`, `Target`, `Rule`, `Toolset`, scheduling, execution, graph I/O, and path functions. +- `src/forge/forge` is the CLI executable. +- `src/forge/forge_lua` exposes the C++ engine to Lua. +- `src/forge/forge_hooks` contains platform-specific filesystem access hooks used for automatic dependency detection. +- `src/forge/lua/forge` contains the standard Lua API/rules; `src/forge/lua/forge/cc` contains C/C++ toolchain rules. +- `src/forge/forge_test` contains Forge’s UnitTest++ test suite and Lua test fixtures. +- `src/assert`, `src/cmdline`, `src/error`, `src/luaxx`, and `src/process` are small local support libraries. +- `src/boost`, `src/lua`, and `src/unittest-cpp` are vendored/submodule code; avoid changing them unless explicitly needed. + +## Docs map + +- `docs/getting-started/running-forge.md` explains CLI commands and variables. +- `docs/getting-started/configuring-forge.md` explains root `forge.lua` build scripts and toolsets. +- `docs/getting-started/writing-buildfiles.md` explains `*.forge` buildfiles. +- `docs/more-details/rules.md`, `dependencies.md`, and `outdated-calculation.md` explain the core build model. +- `docs/reference/` documents Lua API functions, `Target`, and `Toolset`. +- `docs/cc-module/cc.md` documents the C/C++ Lua rules. + +## Build and test + +- Fresh checkout: run `git submodule update --init`, then `bash ./bootstrap-linux.bash`, `bash ./bootstrap-macos.bash`, or `bootstrap-windows.bat`. +- Normal build from repo root after bootstrapping: `bootstrap/bin/forge` or `bootstrap/bin/forge.exe`. +- Common variables/commands: `variant=debug|release|shipping`, `goal=path/to/target`, `clean`, `reconfigure`, `dependencies`, `namespace`. +- CI-style validation builds with `bootstrap/bin/forge`, installs release with `debug/bin/forge architecture=x86-64 variant=release prefix=forge install`, then runs `release/bin/forge_test`. + +## Conventions and guardrails + +- C++ is generally C++11; MSVC builds use C++14. Warnings are treated as errors. +- Follow surrounding C++ style and existing `sweet::forge` namespaces. Use Boost.Filesystem rather than introducing `std::filesystem`. +- Lua/buildfiles use 4-space indentation, semicolon-separated table entries, and portable `/` paths. Convert to native paths only when passing paths to external tools. +- Do not edit generated or ignored outputs such as `bootstrap/`, `debug/`, `release/`, `shipping/`, `.forge`, `*.o`, or `*.obj`. +- When changing behavior, check whether corresponding updates are needed in the C++ core, Lua bindings/rules, docs/reference, and `src/forge/forge_test`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md From 4b2d26d563c518ac8b0a08dea7702793badf86d2 Mon Sep 17 00:00:00 2001 From: Charles Baker Date: Mon, 18 May 2026 10:38:34 +1200 Subject: [PATCH 2/8] Add environment parameter to and document the shell() function --- docs/reference/system-functions.md | 8 ++++++++ src/forge/lua/forge/init.lua | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/reference/system-functions.md b/docs/reference/system-functions.md index 47f2dcfa..ee2ab772 100644 --- a/docs/reference/system-functions.md +++ b/docs/reference/system-functions.md @@ -64,6 +64,14 @@ function run( command, arguments, environment, dependencies_filter, stdout_filte Executes `command` as for [`execute()`](#execute) but raises an error if the process exits with a non-zero exit code. +### shell + +~~~lua +function shell( arguments, environment, dependencies_filter, stdout_filter, stderr_filter, ... ); +~~~ + +Executes `arguments` in the systems native shell, i.e. `cmd.exe` on Windows, `bash` on Linux/MacOS. Raises an error if the shell process exits with a non-zero exit code. + ### sleep ~~~lua diff --git a/src/forge/lua/forge/init.lua b/src/forge/lua/forge/init.lua index 97915b7e..4268cbff 100644 --- a/src/forge/lua/forge/init.lua +++ b/src/forge/lua/forge/init.lua @@ -164,17 +164,17 @@ end -- Execute a command through the host system's native shell - either -- "C:/windows/system32/cmd.exe" on Windows system or "/bin/sh" anywhere else. -function shell(arguments, dependencies_filter, stdout_filter, stderr_filter, ...) +function shell(arguments, environment, dependencies_filter, stdout_filter, stderr_filter, ...) if type(arguments) == 'table' then arguments = table.concat(arguments, ' '); end if operating_system() == 'windows' then local cmd = 'C:/windows/system32/cmd.exe'; - local result = execute(cmd, ('cmd /c "%s"'):format(arguments), dependencies_filter, stdout_filter, stderr_filter, ...); + local result = execute(cmd, ('cmd /c "%s"'):format(arguments), environment, dependencies_filter, stdout_filter, stderr_filter, ...); assertf(result == 0, '[[%s]] failed (result=%d)', arguments, result); else local sh = '/bin/sh'; - local result = execute(sh, ('sh -c "%s"'):format(arguments), dependencies_filter, stdout_filter, stderr_filter, ...); + local result = execute(sh, ('sh -c "%s"'):format(arguments), environment, dependencies_filter, stdout_filter, stderr_filter, ...); assertf(result == 0, '[[%s]] failed (result=%d)', arguments, tonumber(result)); end end From 5c697356952700aaa4ffa7c39f2b2a60ff45f039 Mon Sep 17 00:00:00 2001 From: Charles Baker Date: Mon, 18 May 2026 11:40:55 +1200 Subject: [PATCH 3/8] Fix hard-coded C++ standard in GCC link flags to use configured value --- src/forge/lua/forge/cc/gcc.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/forge/lua/forge/cc/gcc.lua b/src/forge/lua/forge/cc/gcc.lua index 91449a05..0384ef8a 100644 --- a/src/forge/lua/forge/cc/gcc.lua +++ b/src/forge/lua/forge/cc/gcc.lua @@ -261,7 +261,11 @@ function gcc.append_link_flags(toolset, target, flags) gcc.append_flags(flags, target.ldflags); table.insert(flags, ('-march=%s'):format(toolset.architecture)); - table.insert(flags, "-std=c++11"); + + local standard = toolset.standard or target.standard; + if standard then + table.insert(flags, ("-std=%s"):format(standard)); + end if target:rule() == toolset.DynamicLibrary then table.insert(flags, "-shared"); From 2a6172c6dc8186316cfa0347e412b780d968eb69 Mon Sep 17 00:00:00 2001 From: Charles Baker Date: Mon, 25 May 2026 08:49:21 +1200 Subject: [PATCH 4/8] Print exit code when run() or shell() commands fail --- src/forge/lua/forge/init.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/forge/lua/forge/init.lua b/src/forge/lua/forge/init.lua index 4268cbff..aba619d2 100644 --- a/src/forge/lua/forge/init.lua +++ b/src/forge/lua/forge/init.lua @@ -157,8 +157,9 @@ function run(command, arguments, environment, dependencies_filter, stdout_filter if type(arguments) == 'table' then arguments = table.concat(arguments, ' '); end - if execute(command, arguments, environment, dependencies_filter, stdout_filter, stderr_filter, ...) ~= 0 then - error(('%s failed'):format(arguments), 0); + local result = execute(command, arguments, environment, dependencies_filter, stdout_filter, stderr_filter, ...); + if result ~= 0 then + error(('[[%s]] failed with exit code (%d)'):format(arguments, result), 0); end end @@ -171,11 +172,11 @@ function shell(arguments, environment, dependencies_filter, stdout_filter, stder if operating_system() == 'windows' then local cmd = 'C:/windows/system32/cmd.exe'; local result = execute(cmd, ('cmd /c "%s"'):format(arguments), environment, dependencies_filter, stdout_filter, stderr_filter, ...); - assertf(result == 0, '[[%s]] failed (result=%d)', arguments, result); + assertf(result == 0, '[[%s]] failed with exit code (%d)', arguments, result); else local sh = '/bin/sh'; local result = execute(sh, ('sh -c "%s"'):format(arguments), environment, dependencies_filter, stdout_filter, stderr_filter, ...); - assertf(result == 0, '[[%s]] failed (result=%d)', arguments, tonumber(result)); + assertf(result == 0, '[[%s]] failed with exit code (%d)', arguments, tonumber(result)); end end From 25e00a974fca1100399defdb6d14316af3342a20 Mon Sep 17 00:00:00 2001 From: Charles Baker Date: Mon, 25 May 2026 09:50:40 +1200 Subject: [PATCH 5/8] Differentiate successful process exit vs. closed by signal or exception The Process::exit_code() call didn't differentiate between the process exiting cleanly and returning an exit code and the process being terminated by a signal or exception, e.g. segfaulting. Fixed by differentiating between those two cases. Also refactors exit code retrieval to Process::wait() on Windows so that the killed by signal and exception error is generated from Process::wait() on all platforms. --- src/error/functions.cpp | 216 ++++++++++++++++++++++++++++++++++++-- src/error/functions.hpp | 2 + src/process/Process.cpp | 98 ++++++++--------- src/process/Process.hpp | 4 +- src/process/process.forge | 1 + 5 files changed, 259 insertions(+), 62 deletions(-) diff --git a/src/error/functions.cpp b/src/error/functions.cpp index e72e8788..799e5f0a 100644 --- a/src/error/functions.cpp +++ b/src/error/functions.cpp @@ -12,6 +12,9 @@ #include #include #include +#if defined(BUILD_OS_MACOS) || defined(BUILD_OS_LINUX) +#include +#endif using namespace sweet::error; @@ -55,7 +58,7 @@ ErrorPolicy* get_error_policy() // Handle an error. // // Passes the error on to the global ErrorPolicy if one has been set otherwise -// prints the error description to stderr and calls ::exit() passing +// prints the error description to stderr and calls ::exit() passing // EXIT_FAILURE as the return code to pass back to the operating system. // // @param error @@ -80,7 +83,7 @@ void error( const Error& error ) // Handle an error. // // Passes the error on to the global ErrorPolicy if one has been set otherwise -// prints the error description to stderr and calls ::exit() passing +// prints the error description to stderr and calls ::exit() passing // EXIT_FAILURE as the return code to pass back to the operating system. // // @param exception @@ -106,20 +109,20 @@ void error( const std::exception& exception ) // // @param error // The operating system error number. -// +// // @param buffer // A buffer to place the operating system error message into. -// +// // @param length // The length of the buffer. -// +// // @return // The buffer. */ const char* format( int error, char* buffer, unsigned int length ) { SWEET_ASSERT( buffer ); -#if defined(BUILD_OS_WINDOWS) +#if defined(BUILD_OS_WINDOWS) int actual_length = ::FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM, 0, error, 0, buffer, static_cast(length), 0 ); while ( actual_length > 0 && (buffer[actual_length] == '\n' || buffer[actual_length] == '\r' || buffer[actual_length] == '.' || buffer[actual_length] == 0) ) { @@ -136,6 +139,207 @@ const char* format( int error, char* buffer, unsigned int length ) return buffer; } +/** +// Get the symbolic name of a POSIX signal. +// +// @param signal +// The signal number. +// +// @return +// The signal name (e.g. "SIGSEGV") or "unknown signal" if the signal is not +// recognised. +*/ +const char* signal_name( int signal ) +{ +#if defined(BUILD_OS_MACOS) || defined(BUILD_OS_LINUX) + switch ( signal ) + { + case SIGHUP: + return "SIGHUP"; + + case SIGINT: + return "SIGINT"; + + case SIGQUIT: + return "SIGQUIT"; + + case SIGILL: + return "SIGILL"; + + case SIGTRAP: + return "SIGTRAP"; + + case SIGABRT: + return "SIGABRT"; + + case SIGFPE: + return "SIGFPE"; + + case SIGKILL: + return "SIGKILL"; + + case SIGBUS: + return "SIGBUS"; + + case SIGSEGV: + return "SIGSEGV"; + + case SIGSYS: + return "SIGSYS"; + + case SIGPIPE: + return "SIGPIPE"; + + case SIGALRM: + return "SIGALRM"; + + case SIGTERM: + return "SIGTERM"; + + case SIGURG: + return "SIGURG"; + + case SIGSTOP: + return "SIGSTOP"; + + case SIGTSTP: + return "SIGTSTP"; + + case SIGCONT: + return "SIGCONT"; + + case SIGCHLD: + return "SIGCHLD"; + + case SIGTTIN: + return "SIGTTIN"; + + case SIGTTOU: + return "SIGTTOU"; + + case SIGXCPU: + return "SIGXCPU"; + + case SIGXFSZ: + return "SIGXFSZ"; + + case SIGVTALRM: + return "SIGVTALRM"; + + case SIGPROF: + return "SIGPROF"; + + case SIGWINCH: + return "SIGWINCH"; + + case SIGUSR1: + return "SIGUSR1"; + + case SIGUSR2: + return "SIGUSR2"; + + default: + return "unknown signal"; + } +#else + (void) signal; + return "unknown signal"; +#endif +} + +/** +// Get the symbolic name of a Windows NTSTATUS exception code. +// +// @param code +// The NTSTATUS exception code. +// +// @return +// The exception name (e.g. "EXCEPTION_ACCESS_VIOLATION") or +// "unknown exception" if the code is not recognised. +*/ +const char* exception_name( unsigned long code ) +{ +#if defined(BUILD_OS_WINDOWS) + switch ( code ) + { + case EXCEPTION_ACCESS_VIOLATION: + return "EXCEPTION_ACCESS_VIOLATION"; + + case EXCEPTION_DATATYPE_MISALIGNMENT: + return "EXCEPTION_DATATYPE_MISALIGNMENT"; + + case EXCEPTION_BREAKPOINT: + return "EXCEPTION_BREAKPOINT"; + + case EXCEPTION_SINGLE_STEP: + return "EXCEPTION_SINGLE_STEP"; + + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + + case EXCEPTION_FLT_DENORMAL_OPERAND: + return "EXCEPTION_FLT_DENORMAL_OPERAND"; + + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + + case EXCEPTION_FLT_INEXACT_RESULT: + return "EXCEPTION_FLT_INEXACT_RESULT"; + + case EXCEPTION_FLT_INVALID_OPERATION: + return "EXCEPTION_FLT_INVALID_OPERATION"; + + case EXCEPTION_FLT_OVERFLOW: + return "EXCEPTION_FLT_OVERFLOW"; + + case EXCEPTION_FLT_STACK_CHECK: + return "EXCEPTION_FLT_STACK_CHECK"; + + case EXCEPTION_FLT_UNDERFLOW: + return "EXCEPTION_FLT_UNDERFLOW"; + + case EXCEPTION_INT_DIVIDE_BY_ZERO: + return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + + case EXCEPTION_INT_OVERFLOW: + return "EXCEPTION_INT_OVERFLOW"; + + case EXCEPTION_PRIV_INSTRUCTION: + return "EXCEPTION_PRIV_INSTRUCTION"; + + case EXCEPTION_IN_PAGE_ERROR: + return "EXCEPTION_IN_PAGE_ERROR"; + + case EXCEPTION_ILLEGAL_INSTRUCTION: + return "EXCEPTION_ILLEGAL_INSTRUCTION"; + + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + + case EXCEPTION_STACK_OVERFLOW: + return "EXCEPTION_STACK_OVERFLOW"; + + case EXCEPTION_INVALID_DISPOSITION: + return "EXCEPTION_INVALID_DISPOSITION"; + + case EXCEPTION_GUARD_PAGE: + return "EXCEPTION_GUARD_PAGE"; + + case EXCEPTION_INVALID_HANDLE: + return "EXCEPTION_INVALID_HANDLE"; + + case STATUS_CONTROL_C_EXIT: + return "STATUS_CONTROL_C_EXIT"; + + default: + return "unknown exception"; + } +#else + (void) code; + return "unknown exception"; +#endif +} + } } diff --git a/src/error/functions.hpp b/src/error/functions.hpp index 1fc8c825..5069de68 100644 --- a/src/error/functions.hpp +++ b/src/error/functions.hpp @@ -22,6 +22,8 @@ ErrorPolicy* get_error_policy(); void error( const Error& error ); void error( const std::exception& exception ); const char* format( int error, char* buffer, unsigned int length ); +const char* signal_name( int signal ); +const char* exception_name( unsigned long code ); } diff --git a/src/process/Process.cpp b/src/process/Process.cpp index 870189fb..eb9026cc 100644 --- a/src/process/Process.cpp +++ b/src/process/Process.cpp @@ -8,6 +8,7 @@ #include "Process.hpp" #include "Environment.hpp" #include "Error.hpp" +#include #include #if defined(BUILD_OS_WINDOWS) @@ -45,7 +46,8 @@ Process::Process() pipes_(), #if defined(BUILD_OS_WINDOWS) process_( INVALID_HANDLE_VALUE ), - suspended_thread_( INVALID_HANDLE_VALUE ) + suspended_thread_( INVALID_HANDLE_VALUE ), + exit_code_( 0 ) #elif defined(BUILD_OS_MACOS) process_( 0 ), exit_code_( 0 ), @@ -64,7 +66,7 @@ Process::~Process() { resume(); -#if defined(BUILD_OS_WINDOWS) +#if defined(BUILD_OS_WINDOWS) if ( suspended_thread_ != INVALID_HANDLE_VALUE ) { ::CloseHandle( suspended_thread_ ); @@ -282,9 +284,9 @@ void Process::run( const char* arguments ) ::CloseHandle( process_information.hThread ); } - // Close the the write ends of any the pipes used because they only need + // Close the the write ends of any the pipes used because they only need // to be used by the child process and also because calls to ::ReadFile() - // on the read end only return 0 when all of the write ends have been + // on the read end only return 0 when all of the write ends have been // closed. for ( vector::iterator pipe = pipes_.begin(); pipe != pipes_.end(); ++pipe ) { @@ -302,9 +304,9 @@ void Process::run( const char* arguments ) if ( directory_ ) { - // Use the undocumented `pthread_fchdir()` system call to change the + // Use the undocumented `pthread_fchdir()` system call to change the // working directory for the thread that is spawning a process rather than - // the global per-process working directory changed by `fchdir()`. See + // the global per-process working directory changed by `fchdir()`. See // `syscall()` and ``. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -474,7 +476,7 @@ void Process::run( const char* arguments ) int result = execve( executable_, &splitter.arguments()[0], envp ); - // Ignore any returned result as any call to `execve()` that returns + // Ignore any returned result as any call to `execve()` that returns // is a failure. (void) result; char message [256]; @@ -490,13 +492,13 @@ void Process::run( const char* arguments ) pipe->write_fd = -1; } } -#endif +#endif } /** // Get the handle or identifier of this Process. // -// @return +// @return // The handle or identifier of this Process cast to a void pointer. */ void* Process::process() const @@ -533,7 +535,7 @@ void* Process::write_pipe( int index ) const /** // Resume this Process after it has been started suspended. // -// It is only valid to call this function once to resume an initially +// It is only valid to call this function once to resume an initially // suspended process. Additional calls will silently do nothing. */ void Process::resume() @@ -570,21 +572,27 @@ void Process::wait() SWEET_ERROR( WaitForProcessFailedError("Waiting for a process failed - %s", error) ); } -#elif defined(BUILD_OS_MACOS) || defined(BUILD_OS_LINUX) - SWEET_ASSERT( process_ != 0 ); - - pid_t result = waitpid( process_, &exit_code_, 0 ); - while ( result < 0 && errno == EINTR ) + DWORD exit_code = 0; + BOOL exited = ::GetExitCodeProcess( process_, &exit_code ); + if ( !exited ) { - result = waitpid( process_, &exit_code_, 0 ); + char error [1024]; + error::Error::format( ::GetLastError(), error, sizeof(error) ); + SWEET_ERROR( ExitCodeForProcessFailedError("Getting a process exit code failed - %s", error) ); } - if ( result != process_ ) + exit_code_ = (int) exit_code; + + ::CloseHandle( process_ ); + process_ = INVALID_HANDLE_VALUE; + + const DWORD NT_STATUS_SEVERITY_ERROR_MASK = (DWORD) 0xc0000000; + if ( (exit_code & NT_STATUS_SEVERITY_ERROR_MASK) == NT_STATUS_SEVERITY_ERROR_MASK ) { - char buffer [1024]; - SWEET_ERROR( WaitForProcessFailedError("Waiting for a process failed - %s", Error::format(errno, buffer, sizeof(buffer))) ); + SWEET_ERROR( WaitForProcessFailedError("Process terminated by exception %s (0x%08lx)", error::exception_name(exit_code), (unsigned long) exit_code) ); + return; } - process_ = 0; -#elif defined(BUILD_OS_LINUX) + +#elif defined(BUILD_OS_MACOS) || defined(BUILD_OS_LINUX) SWEET_ASSERT( process_ != 0 ); int status = 0; @@ -594,25 +602,26 @@ void Process::wait() result = waitpid( process_, &status, 0 ); } - if ( result == process_ ) - { - if ( WIFEXITED(status) ) - { - exit_code_ = WEXITSTATUS( status ); - } - else if ( WIFSIGNALED(status) ) - { - exit_code_ = WTERMSIG( status ); - } - } - else + if ( result != process_ ) { char buffer [1024]; SWEET_ERROR( WaitForProcessFailedError("Waiting for a process failed - %s", Error::format(errno, buffer, sizeof(buffer))) ); return; } - process_ = 0; + if ( WIFSIGNALED(status) ) + { + int signal = WTERMSIG(status); + SWEET_ERROR( WaitForProcessFailedError("Process terminated by signal %s (%d)", error::signal_name(signal), signal) ); + return; + } + + if ( WIFEXITED(status) ) + { + exit_code_ = WEXITSTATUS( status ); + } + + process_ = 0; #endif } @@ -625,26 +634,9 @@ void Process::wait() int Process::exit_code() { #if defined(BUILD_OS_WINDOWS) - SWEET_ASSERT( process_ != INVALID_HANDLE_VALUE ); - - DWORD exit_code = 0; - BOOL exited = ::GetExitCodeProcess( process_, &exit_code ); - if ( !exited ) - { - char error [1024]; - error::Error::format( ::GetLastError(), error, sizeof(error) ); - SWEET_ERROR( ExitCodeForProcessFailedError("Getting a process exit code failed - %s", error) ); - } - - if ( process_ != INVALID_HANDLE_VALUE ) - { - ::CloseHandle( process_ ); - process_ = INVALID_HANDLE_VALUE; - } - - return exit_code; + SWEET_ASSERT( process_ == INVALID_HANDLE_VALUE ); #elif defined(BUILD_OS_MACOS) || defined(BUILD_OS_LINUX) SWEET_ASSERT( process_ == 0 ); - return exit_code_; #endif + return exit_code_; } diff --git a/src/process/Process.hpp b/src/process/Process.hpp index e6f51d8f..d24fe4b6 100644 --- a/src/process/Process.hpp +++ b/src/process/Process.hpp @@ -44,18 +44,16 @@ class Process bool start_suspended_; bool inherit_environment_; std::vector pipes_; - #if defined(BUILD_OS_WINDOWS) void* process_; ///< The handle to this Process. void* suspended_thread_; ///< The handle to the suspended main thread of this Process. + int exit_code_; #endif - #if defined(BUILD_OS_MACOS) pid_t process_; int exit_code_; bool suspended_; #endif - #if defined(BUILD_OS_LINUX) pid_t process_; int exit_code_; diff --git a/src/process/process.forge b/src/process/process.forge index b45cca2d..84967257 100644 --- a/src/process/process.forge +++ b/src/process/process.forge @@ -5,6 +5,7 @@ for _, forge in toolsets('cc.*') do forge:StaticLibrary '${lib}/process_${architecture}' { '${lib}/assert_${architecture}'; '${lib}/cmdline_${architecture}'; + '${lib}/error_${architecture}'; forge:Cxx '${obj}/%1' { 'Error.cpp', 'Environment.cpp', From 6e4ad35edb2c1bbb2d5a7bf0947b1e055a60b4bb Mon Sep 17 00:00:00 2001 From: Charles Baker Date: Mon, 25 May 2026 11:29:21 +1200 Subject: [PATCH 6/8] LINT: Remove and trailing whitespace from forge_hooks_macos.cpp --- src/forge/forge_hooks/forge_hooks_macos.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/forge/forge_hooks/forge_hooks_macos.cpp b/src/forge/forge_hooks/forge_hooks_macos.cpp index 4a3e9315..351d0e37 100644 --- a/src/forge/forge_hooks/forge_hooks_macos.cpp +++ b/src/forge/forge_hooks/forge_hooks_macos.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -23,7 +22,7 @@ static void log_open( int fd, int oflag ) if ( fd >= 0 ) { struct stat stat; - if ( fstat(fd, &stat) == 0 && (stat.st_mode & S_IFREG) != 0 ) + if ( fstat(fd, &stat) == 0 && (stat.st_mode & S_IFREG) != 0 ) { char path [PATH_MAX]; int result = fcntl( fd, F_GETPATH, path ); @@ -59,7 +58,7 @@ static void log_open( int fd, int oflag ) } -extern "C" +extern "C" { int open_interpose( const char* filename, int oflag, ... ) From d18015b1756b1fead04e1e9b5719653f919b0ff9 Mon Sep 17 00:00:00 2001 From: Charles Baker Date: Mon, 25 May 2026 17:02:56 +1200 Subject: [PATCH 7/8] Loop waiting for empty jobs in Executor and Reader's stop() --- src/forge/Executor.cpp | 15 +++++++++------ src/forge/Reader.cpp | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/forge/Executor.cpp b/src/forge/Executor.cpp index f30f695f..da9952e5 100644 --- a/src/forge/Executor.cpp +++ b/src/forge/Executor.cpp @@ -179,7 +179,7 @@ void Executor::stop() { { std::unique_lock lock( jobs_mutex_ ); - if ( !jobs_.empty() ) + while ( !jobs_.empty() ) { jobs_empty_condition_.wait( lock ); } @@ -187,7 +187,10 @@ void Executor::stop() jobs_ready_condition_.notify_all(); } - for ( vector::iterator i = threads_.begin(); i != threads_.end(); ++i ) + vector threads; + threads.swap( threads_ ); + + for ( vector::iterator i = threads.begin(); i != threads.end(); ++i ) { try { @@ -201,11 +204,11 @@ void Executor::stop() forge_->errorf( "Failed to join thread - %s", exception.what() ); } } - - while ( !threads_.empty() ) + + while ( !threads.empty() ) { - delete threads_.back(); - threads_.pop_back(); + delete threads.back(); + threads.pop_back(); } } } diff --git a/src/forge/Reader.cpp b/src/forge/Reader.cpp index 5d8625a3..fd2267fe 100644 --- a/src/forge/Reader.cpp +++ b/src/forge/Reader.cpp @@ -143,7 +143,7 @@ void Reader::stop() { { std::unique_lock lock( jobs_mutex_ ); - if ( !jobs_.empty() ) + while ( !jobs_.empty() ) { jobs_empty_condition_.wait( lock ); } @@ -151,7 +151,10 @@ void Reader::stop() jobs_ready_condition_.notify_all(); } - for ( vector::iterator i = threads_.begin(); i != threads_.end(); ++i ) + vector threads; + threads.swap( threads_ ); + + for ( vector::iterator i = threads.begin(); i != threads.end(); ++i ) { try { @@ -165,11 +168,11 @@ void Reader::stop() forge_->errorf( 0, "Failed to join thread - %s", exception.what() ); } } - - while ( !threads_.empty() ) + + while ( !threads.empty() ) { - delete threads_.back(); - threads_.pop_back(); + delete threads.back(); + threads.pop_back(); } } } From 3cc6ebda38714e55fdfd35cd3f8c1eff2e4e7ecf Mon Sep 17 00:00:00 2001 From: Charles Baker Date: Mon, 25 May 2026 21:17:10 +1200 Subject: [PATCH 8/8] Stop using incorrect size in System::executable() on macOS The call to _NSGetExecutablePath() only updates it size parameter if the size of the buffer passed in is too small. If the buffer is large enough it is simply filled and the size is left as is. This always returns a 4096 byte path on macOS. Fixed by leaving out the size parameter when constructing the returned string, let search for the null terminator determine the length. --- src/forge/System.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/forge/System.cpp b/src/forge/System.cpp index ae1f8d0a..59832da5 100644 --- a/src/forge/System.cpp +++ b/src/forge/System.cpp @@ -115,8 +115,8 @@ std::time_t System::last_write_time( const std::string& path ) const */ boost::filesystem::directory_iterator System::ls( const std::string& path ) const { - return boost::filesystem::exists( path ) ? - boost::filesystem::directory_iterator( path ) : + return boost::filesystem::exists( path ) ? + boost::filesystem::directory_iterator( path ) : boost::filesystem::directory_iterator() ; } @@ -128,13 +128,13 @@ boost::filesystem::directory_iterator System::ls( const std::string& path ) cons // The directory to list files in. // // @return -// A boost::filesystem::recursive_directory_iterator that recursively +// A boost::filesystem::recursive_directory_iterator that recursively // iterates over the files in the directory and its children. */ boost::filesystem::recursive_directory_iterator System::find( const std::string& path ) const { - return boost::filesystem::exists( path ) ? - boost::filesystem::recursive_directory_iterator( path ) : + return boost::filesystem::exists( path ) ? + boost::filesystem::recursive_directory_iterator( path ) : boost::filesystem::recursive_directory_iterator() ; } @@ -160,8 +160,8 @@ std::string System::executable() const file = INVALID_HANDLE_VALUE; if ( linked_size < sizeof(linked_path) ) { - // The path returned by `::GetFinalPathNameByHandleA()` has a - // prefix of `\\?\` to indicate that it is an extended length + // The path returned by `::GetFinalPathNameByHandleA()` has a + // prefix of `\\?\` to indicate that it is an extended length // path. Skipping over it works for now but is probably the wrong // thing in many cases. const char* linked_path_without_extended_length_prefix = linked_path + 4; @@ -177,7 +177,7 @@ std::string System::executable() const int linked_size = readlink( executable_path, linked_path, sizeof(linked_path) - 1 ); if ( linked_size == -1 && errno == EINVAL ) { - return boost::filesystem::path( string(executable_path, size) ).generic_string(); + return boost::filesystem::path( string(executable_path) ).generic_string(); } else if ( linked_size >= 0 ) { @@ -213,7 +213,7 @@ std::string System::home() const #else #error "ScriptInterface::home() is not implemented for this platform" #endif - + const char* home = ::getenv( HOME ); return home ? boost::filesystem::path( string(home) ).generic_string() : string(); } @@ -291,7 +291,7 @@ const char* System::operating_system() const // The name of the environment attribute to get the value of. // // @return -// The value of the environment attribute or null if the environment attribute +// The value of the environment attribute or null if the environment attribute // isn't set. */ const char* System::getenv( const char* name ) const @@ -353,7 +353,7 @@ void System::sleep( float milliseconds ) const // The number of milliseconds elapsed since the system was started. */ float System::ticks() const -{ +{ #if defined(BUILD_OS_WINDOWS) return static_cast( ::GetTickCount() ) - initial_tick_count_; #elif defined(BUILD_OS_MACOS)