summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSaikari <[email protected]>2026-01-31 05:42:37 +0300
committerSaikari <[email protected]>2026-01-31 05:42:37 +0300
commit4630ca802874f6d45e447a7ab79c1a54b03d866a (patch)
treefaaa20ffbf1b81c9079d37ac62dc6440892fb934
parent51a68ee33918b95811420beb6a56673a83abe6cc (diff)
add support for running Lua scripts from stdin and enhance error handling
-rw-r--r--tests/projects/test_stdin/xmake.lua100
-rw-r--r--xmake/core/base/os.lua5
-rw-r--r--xmake/plugins/lua/main.lua62
-rw-r--r--xmake/plugins/lua/xmake.lua1
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)",