From 790567e926102800a29975f9e3e758b50d0bdc9b Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 16 Oct 2025 20:56:29 +0200 Subject: [PATCH 1/3] feat: add `process.execpath` to get the current lute executable --- definitions/process.luau | 4 ++++ lute/cli/src/climain.cpp | 19 +++++++++++-------- lute/process/include/lute/process.h | 3 +++ lute/process/src/process.cpp | 6 ++++++ lute/runtime/include/lute/runtime.h | 4 +++- lute/runtime/src/runtime.cpp | 21 ++++++++++++++++++++- lute/vm/src/spawn.cpp | 2 +- 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/definitions/process.luau b/definitions/process.luau index 46c48ef5f..a2f1069ee 100644 --- a/definitions/process.luau +++ b/definitions/process.luau @@ -37,4 +37,8 @@ function process.exit(exitcode: number): never error("not implemented") end +function process.execpath(): string + error("not implemented") +end + return process diff --git a/lute/cli/src/climain.cpp b/lute/cli/src/climain.cpp index 8277fb7de..6932dd05a 100644 --- a/lute/cli/src/climain.cpp +++ b/lute/cli/src/climain.cpp @@ -18,6 +18,7 @@ #include "lute/runtime.h" #include "lute/tc.h" #include "lute/version.h" +#include "uv.h" #ifdef _WIN32 #include @@ -259,7 +260,7 @@ static std::optional getValidPath(std::string filePath) return std::nullopt; } -int handleRunCommand(int argc, char** argv, int argOffset) +int handleRunCommand(int argc, char** argv, char* argv0, int argOffset) { std::string filePath; int program_argc = 0; @@ -296,7 +297,7 @@ int handleRunCommand(int argc, char** argv, int argOffset) return 1; } - Runtime runtime; + Runtime runtime(argv0); lua_State* L = setupCliState(runtime); std::optional validPath = getValidPath(filePath); @@ -411,9 +412,9 @@ int handleCompileCommand(int argc, char** argv, int argOffset) return compileScript(inputFilePath, outputFilePath, argv[0]); } -int handleCliCommand(CliCommandResult result, int program_argc, char** program_argv) +int handleCliCommand(CliCommandResult result, int program_argc, char** program_argv, char* argv0) { - Runtime runtime; + Runtime runtime(argv0); lua_State* L = setupCliState(runtime); std::string bytecode = Luau::compile(std::string(result.contents), copts()); @@ -424,10 +425,12 @@ int cliMain(int argc, char** argv) { Luau::assertHandler() = assertionHandler; + argv = uv_setup_args(argc, argv); + AppendedBytecodeResult embedded = checkForAppendedBytecode(argv[0]); if (embedded.found) { - Runtime runtime; + Runtime runtime(argv[0]); lua_State* GL = setupCliState(runtime); bool success = runBytecode(runtime, embedded.BytecodeData, "=__EMBEDDED__", GL, argc, argv); @@ -451,7 +454,7 @@ int cliMain(int argc, char** argv) if (strcmp(command, "run") == 0) { - return handleRunCommand(argc, argv, argOffset); + return handleRunCommand(argc, argv, argv[0], argOffset); } else if (strcmp(command, "check") == 0) { @@ -473,12 +476,12 @@ int cliMain(int argc, char** argv) } else if (std::optional result = getCliCommand(command); result) { - return handleCliCommand(*result, argc - argOffset, &argv[argOffset]); + return handleCliCommand(*result, argc - argOffset, &argv[argOffset], argv[0]); } else { // Default to 'run' command argOffset = 1; - return handleRunCommand(argc, argv, argOffset); + return handleRunCommand(argc, argv, argv[0], argOffset); } } diff --git a/lute/process/include/lute/process.h b/lute/process/include/lute/process.h index 88c888fd7..be4c2268b 100644 --- a/lute/process/include/lute/process.h +++ b/lute/process/include/lute/process.h @@ -18,11 +18,14 @@ int cwd(lua_State* L); int exitFunc(lua_State* L); +int execpath(lua_State* L); + static const luaL_Reg lib[] = { {"run", run}, {"homedir", homedir}, {"cwd", cwd}, {"exit", exitFunc}, + {"execpath", execpath}, {nullptr, nullptr} }; diff --git a/lute/process/src/process.cpp b/lute/process/src/process.cpp index 57bf96bd3..e24b849f8 100644 --- a/lute/process/src/process.cpp +++ b/lute/process/src/process.cpp @@ -474,6 +474,12 @@ int cwd(lua_State* L) return 1; }; +int execpath(lua_State* L) +{ + lua_pushstring(L, getRuntime(L)->execPath.c_str()); + return 1; +} + static int envIndex(lua_State* L) { const char* key = luaL_checkstring(L, 2); diff --git a/lute/runtime/include/lute/runtime.h b/lute/runtime/include/lute/runtime.h index a4d417173..0426f24b1 100644 --- a/lute/runtime/include/lute/runtime.h +++ b/lute/runtime/include/lute/runtime.h @@ -40,7 +40,7 @@ using RuntimeStep = Luau::Variant; struct Runtime { - Runtime(); + Runtime(const char* argv0 = nullptr); ~Runtime(); bool runToCompletion(); @@ -81,6 +81,8 @@ struct Runtime std::vector runningThreads; + std::string execPath; + private: std::mutex continuationMutex; std::vector> continuations; diff --git a/lute/runtime/src/runtime.cpp b/lute/runtime/src/runtime.cpp index 475a6c563..2ca66cc81 100644 --- a/lute/runtime/src/runtime.cpp +++ b/lute/runtime/src/runtime.cpp @@ -19,6 +19,13 @@ #include #include +#include // IWYU pragma: keep + +#ifdef PATH_MAX +#define LUTE_PATH_MAX PATH_MAX +#else +#define LUTE_PATH_MAX 8192 +#endif static void lua_close_checked(lua_State* L) { @@ -26,9 +33,21 @@ static void lua_close_checked(lua_State* L) lua_close(L); } -Runtime::Runtime() +static std::string getExecPath(const char* argv0) +{ + char buf[LUTE_PATH_MAX]; + size_t len = sizeof(buf); + if (uv_exepath(buf, &len) == 0) + return {buf, len}; + if (argv0) + return argv0; + return {}; +} + +Runtime::Runtime(const char* argv0) : globalState(nullptr, lua_close_checked) , dataCopy(nullptr, lua_close_checked) + , execPath(getExecPath(argv0)) { stop.store(false); activeTokens.store(0); diff --git a/lute/vm/src/spawn.cpp b/lute/vm/src/spawn.cpp index 26bd7a369..bcd8944b5 100644 --- a/lute/vm/src/spawn.cpp +++ b/lute/vm/src/spawn.cpp @@ -200,7 +200,7 @@ int lua_spawn(lua_State* L) { const char* file = luaL_checkstring(L, 1); - auto child = std::make_shared(); + auto child = std::make_shared(getRuntime(L)->execPath.c_str()); setupState( *child, From 2f8b9514d4c210599c378df1323de3cf998532d8 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Fri, 17 Oct 2025 21:32:25 +0200 Subject: [PATCH 2/3] fix: use raii struct for uv state --- lute/cli/src/climain.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lute/cli/src/climain.cpp b/lute/cli/src/climain.cpp index 6932dd05a..ccc4b4065 100644 --- a/lute/cli/src/climain.cpp +++ b/lute/cli/src/climain.cpp @@ -28,6 +28,24 @@ #include #include +namespace +{ + +struct UvLibraryState +{ + UvLibraryState(int argc, char**& argv) + { + argv = uv_setup_args(argc, argv); + } + + ~UvLibraryState() + { + uv_library_shutdown(); + } +}; + +} // namespace + void* createCliRequireContext(lua_State* L) { void* ctx = lua_newuserdatadtor( @@ -425,7 +443,7 @@ int cliMain(int argc, char** argv) { Luau::assertHandler() = assertionHandler; - argv = uv_setup_args(argc, argv); + UvLibraryState uvlib(argc, argv); AppendedBytecodeResult embedded = checkForAppendedBytecode(argv[0]); if (embedded.found) From 14fa820bd0c22ff44c7ad697f5b7190c9b0b9545 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 20 Oct 2025 17:27:38 +0200 Subject: [PATCH 3/3] fix: don't call setup_args --- lute/cli/src/climain.cpp | 20 -------------------- lute/runtime/src/runtime.cpp | 7 +++++++ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/lute/cli/src/climain.cpp b/lute/cli/src/climain.cpp index dc850eed9..31a0a003a 100644 --- a/lute/cli/src/climain.cpp +++ b/lute/cli/src/climain.cpp @@ -37,24 +37,6 @@ #include #include -namespace -{ - -struct UvLibraryState -{ - UvLibraryState(int argc, char**& argv) - { - argv = uv_setup_args(argc, argv); - } - - ~UvLibraryState() - { - uv_library_shutdown(); - } -}; - -} // namespace - void* createCliRequireContext(lua_State* L) { void* ctx = lua_newuserdatadtor( @@ -477,8 +459,6 @@ int cliMain(int argc, char** argv) { Luau::assertHandler() = assertionHandler; - UvLibraryState uvlib(argc, argv); - AppendedBytecodeResult embedded = checkForAppendedBytecode(argv[0]); if (embedded.found) { diff --git a/lute/runtime/src/runtime.cpp b/lute/runtime/src/runtime.cpp index 761fbcabb..02e5f77b9 100644 --- a/lute/runtime/src/runtime.cpp +++ b/lute/runtime/src/runtime.cpp @@ -25,6 +25,13 @@ static void lua_close_checked(lua_State* L) static std::string getExecPath(const char* argv0) { + // FIXME: uv_exepath requires that uv_setup_args(argc, argv) was called. + // This function can allocate on some platforms. However, the state is + // persisted for the duration of the program and cleaning it up with + // uv_library_shutdown will only delete it once. This is a problem in unit + // tests that call cliMain multiple times. There, the function would leak + // memory. On the three main platforms supported by Lute however, uv doesn't + // require uv_setup_args to be called, so it's currently left out. char buf[LUTE_PATH_MAX]; size_t len = sizeof(buf); if (uv_exepath(buf, &len) == 0)