diff options
| author | Saikari <[email protected]> | 2026-01-31 05:42:37 +0300 |
|---|---|---|
| committer | Saikari <[email protected]> | 2026-01-31 05:42:37 +0300 |
| commit | 4630ca802874f6d45e447a7ab79c1a54b03d866a (patch) | |
| tree | faaa20ffbf1b81c9079d37ac62dc6440892fb934 | |
| parent | 51a68ee33918b95811420beb6a56673a83abe6cc (diff) | |
add support for running Lua scripts from stdin and enhance error handling
| -rw-r--r-- | tests/projects/test_stdin/xmake.lua | 100 | ||||
| -rw-r--r-- | xmake/core/base/os.lua | 5 | ||||
| -rw-r--r-- | xmake/plugins/lua/main.lua | 62 | ||||
| -rw-r--r-- | xmake/plugins/lua/xmake.lua | 1 |
4 files changed, 159 insertions, 9 deletions
diff --git a/tests/projects/test_stdin/xmake.lua b/tests/projects/test_stdin/xmake.lua new file mode 100644 index 000000000..5b856142e --- /dev/null +++ b/tests/projects/test_stdin/xmake.lua @@ -0,0 +1,100 @@ +target("test") + set_kind("phony") + on_run(function (target) + import("core.base.option") + local xmake = os.programfile() + local xmake_dir = os.getenv("XMAKE_PROGRAM_DIR") + print("XMAKE_PROGRAM_DIR: " .. (xmake_dir or "nil")) + print("xmake binary: " .. xmake) + + local function run_with_env(cmd_str) + local outfile = os.tmpfile() + local errfile = os.tmpfile() + local shell_cmd = cmd_str + if xmake_dir then + shell_cmd = string.format("export XMAKE_PROGRAM_DIR='%s' && %s", xmake_dir, cmd_str) + end + -- Redirect in shell using subshell to capture all output + shell_cmd = string.format("(%s) > %s 2> %s", shell_cmd, outfile, errfile) + + local code = 0 + try + { + function () + code = os.execv("sh", {"-c", shell_cmd}) + end, + catch + { + function (e) + code = -1 + end + } + } + + local out = io.readfile(outfile) + local err = io.readfile(errfile) + + os.rm(outfile) + os.rm(errfile) + + return (code == 0), out, err + end + + -- check if feature is present + local ok, out, err = run_with_env(string.format("%s lua --help", xmake)) + if out and out:find("--from-stdin", 1, true) then + print("Feature presence check: PASS") + else + print("Feature presence check: FAIL") + print("Help output:\n" .. (out or "")) + print("Help error:\n" .. (err or "")) + end + + -- test 1: pipe a few lines of lua code from echo + local pipe_cmd = string.format("echo 'print(\"hello from pipe\")' | %s lua --from-stdin", xmake) + print("running: " .. pipe_cmd) + ok, out, err = run_with_env(pipe_cmd) + print("STDOUT 1:\n" .. (out or "")) + print("STDERR 1:\n" .. (err or "")) + assert(ok, "test 1 failed: command returned error") + if out then + assert(out:find("hello from pipe"), "test 1 failed: output mismatch") + end + + -- test 2: redirect from a .lua file + local scriptfile = path.join(os.curdir(), "test.lua") + io.writefile(scriptfile, 'print("hello from file")') + local redirect_cmd = string.format("%s lua --from-stdin < %s", xmake, scriptfile) + print("running: " .. redirect_cmd) + ok, out, err = run_with_env(redirect_cmd) + print("STDOUT 2:\n" .. (out or "")) + print("STDERR 2:\n" .. (err or "")) + assert(ok, "test 2 failed: command returned error") + if out then + assert(out:find("hello from file"), "test 2 failed: output mismatch") + end + os.rm(scriptfile) + + -- test 3: verify traceback on error via pipe + local error_pipe_cmd = string.format("echo 'raise(\"error_pipe\")' | %s lua --from-stdin", xmake) + print("running: " .. error_pipe_cmd) + ok, out, err = run_with_env(error_pipe_cmd) + print("STDOUT 3:\n" .. (out or "")) + print("STDERR 3:\n" .. (err or "")) + assert(not ok, "test 3 failed: command should have returned error") + assert((err and err:find("error_pipe")) or (out and out:find("error_pipe")), "test 3 failed: missing error message") + assert((err and err:find("stack traceback")) or (out and out:find("stack traceback")), "test 3 failed: missing traceback") + + -- test 4: verify traceback on error via file + local errorfile = path.join(os.curdir(), "error.lua") + io.writefile(errorfile, 'raise("error_file")') + local error_file_cmd = string.format("%s lua --from-stdin < %s", xmake, errorfile) + print("running: " .. error_file_cmd) + ok, out, err = run_with_env(error_file_cmd) + print("STDOUT 4:\n" .. (out or "")) + print("STDERR 4:\n" .. (err or "")) + assert(not ok, "test 4 failed: command should have returned error") + assert((err and err:find("error_file")) or (out and out:find("error_file")), "test 4 failed: missing error message") + assert((err and err:find("stack traceback")) or (out and out:find("stack traceback")), "test 4 failed: missing traceback") + os.rm(errorfile) + end) diff --git a/xmake/core/base/os.lua b/xmake/core/base/os.lua index 12bc30c1d..9332f2c61 100644 --- a/xmake/core/base/os.lua +++ b/xmake/core/base/os.lua @@ -1187,7 +1187,10 @@ function os.isexec(filepath) end end elseif os.isfile(filepath) then - return os._access(filepath, "x") + if os._access then + return os._access(filepath, "x") + end + return true end return false end diff --git a/xmake/plugins/lua/main.lua b/xmake/plugins/lua/main.lua index 2b0a9efe2..3da4ee4b8 100644 --- a/xmake/plugins/lua/main.lua +++ b/xmake/plugins/lua/main.lua @@ -54,14 +54,60 @@ function main() -- run script local script = option.get("script") - if script then - run_script(script, { - curdir = os.workingdir(), - verbose = option.get("verbose"), - diagnosis = option.get("diagnosis"), - command = option.get("command"), - arguments = option.get("arguments"), - deserialize = option.get("deserialize")}) + local arguments = option.get("arguments") + local from_stdin = option.get("from_stdin") or option.get("from-stdin") + if script or from_stdin then + + -- run script from stdin? + local scriptfile_stdin + if script == "-" or from_stdin then + local script_content = io.stdin:read("*a") + if script_content then + scriptfile_stdin = os.tmpfile("xmake_lua_stdin") .. ".lua" + io.writefile(scriptfile_stdin, script_content) + if from_stdin and script and script ~= "-" then + arguments = arguments or {} + table.insert(arguments, 1, script) + end + script = scriptfile_stdin + end + end + + -- enable diagnosis to get the stack traceback + local get_old = option.get + option.get = function (name) + if name == "diagnosis" then + return true + end + return get_old(name) + end + + try { + function () + if script then + run_script(script, { + curdir = os.workingdir(), + verbose = option.get("verbose"), + diagnosis = option.get("diagnosis"), + command = option.get("command"), + arguments = arguments, + deserialize = option.get("deserialize")}) + end + end, + catch { + function (errors) + raise(errors) + end + }, + finally { + function () + option.get = get_old + if scriptfile_stdin then + os.rm(scriptfile_stdin) + end + end + } + } else -- enter interactive mode sandbox.interactive() diff --git a/xmake/plugins/lua/xmake.lua b/xmake/plugins/lua/xmake.lua index 8ef6c3573..c8ebc8ba3 100644 --- a/xmake/plugins/lua/xmake.lua +++ b/xmake/plugins/lua/xmake.lua @@ -45,6 +45,7 @@ task("lua") {'l', "list" , "k" , nil , "List all scripts." } , {'c', "command" , "k" , nil , "Run script as command" } , {'d', "deserialize" , "kv" , nil , "Deserialize arguments starts with given prefix" } + , {nil, "from-stdin" , "k" , nil , "Run script from stdin" } , {nil, "script" , "v" , nil , "Run the given lua script name, file or module and enter interactive mode if no given script.", "e.g.", " - xmake lua (enter interactive mode)", |
