Install
$ agentstack add skill-xmake-io-xmake-skills-xmake-scripting Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Dangerous shell/eval execution.
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ● Shell / process execution Used
- ● Environment & secrets Used
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Xmake Scripting, import, and Native Modules
xmake.lua is Lua, but "writing Lua" in xmake means working inside the script domain — hook bodies like on_load, on_build, after_build, on_install, on_run, on_test, or standalone modules loaded through import(). This skill covers how to write, organize, and load that code, including native (C/C++) Lua modules.
If you are looking for the description/script domain boundary and style rules, see xmake-style. This skill picks up once you are inside a script.
1. Where scripts live
Three idiomatic forms, from inline to fully external:
Inline (short logic)
target("app")
set_kind("binary")
add_files("src/*.cpp")
after_build(function (target)
os.cp(target:targetfile(), "$(buildir)/dist/")
end)
Fine for anything under ~15 lines.
External file under modules/
target("app")
on_load("modules.app.load")
on_install("modules.app.install")
With a tree:
xmake.lua
modules/
app/
load.lua
install.lua
Each external script file uses the module convention (see below) — no return, a main function is the entry point.
Standalone reusable module imported with import()
xmake.lua
scripts/
codegen.lua
utils.lua
-- inside some on_load / on_build
import("scripts.codegen")
codegen.generate(target)
2. The module convention
Xmake does not use Lua's native require / return pattern. Module files follow xmake's own convention:
-- scripts/foo.lua
-- private: prefixed with underscore, not exported
function _helper(x)
return x * 2
end
-- public: any top-level function without leading underscore
function add(a, b)
return _helper(a) + b
end
function greet(name)
print("hello, %s!", name)
end
-- optional: `main` is the module's callable entry point
function main(a, b)
return add(a, b)
end
Imported:
import("scripts.foo")
foo.add(1, 2) -- public function
foo.greet("ruki") -- public function
foo(1, 2) -- calls main()
-- foo._helper(1) -- ERROR: underscore-prefixed is private
Equivalent one-shot: import("scripts.foo")(1, 2).
Rules:
- No
returnat the end of the file. - Top-level functions without
_prefix are public. - Top-level functions with
_prefix are private. main(...)is optional; if present, calling the imported table invokes it.
3. import() — loading modules
import("core.base.option") -- built-in xmake module
import("core.project.project")
import("lib.detect.find_tool")
import("scripts.foo") -- local module at ./scripts/foo.lua
Resolution order:
- Current script directory (relative to the file doing the import).
- xmake's built-in extension class libraries under
$(programdir)/modules/and$(programdir)/core/. - Any directories added with
add_moduledirs(...).
Options
import("core.platform.platform", {alias = "p"}) -- rename to avoid conflicts
import("scripts.foo", {rootdir = "/abs/path"}) -- import from elsewhere
import("scripts.foo", {try = true}) -- return nil on failure
import("scripts.foo", {anonymous = true}) -- don't pollute scope
import("scripts.base", {inherit = true}) -- merge into current module
alias— bind the module to a different local name. Critical when two modules share a base name.rootdir— import from an absolute directory, useful for shared tool libraries.try— don't throw if the module doesn't exist; returnnilinstead.anonymous— don't create a top-level local; return the import object and let the caller bind it.inherit— copy all public functions into the calling module (module-level inheritance).
4. Frequently imported xmake core modules
| Module | What it gives you | | --- | --- | | core.base.option | CLI option parsing (option.get("name")) inside tasks/plugins | | core.base.task | Run another task programmatically (task.run("build")) | | core.base.global | Global config (~/.xmake/xmake.conf values) | | core.project.project | All targets in the project: project.targets(), project.target("name") | | core.project.config | Configured plat/arch/mode/buildir | | core.platform.platform | Host/target platform info, platform.plats() | | core.tool.toolchain | Toolchain objects | | core.tool.compiler / core.tool.linker | Invoke a compiler/linker programmatically | | core.package.package | Package instances, package:installdir(), etc. | | lib.detect.find_tool | Probe for an executable on PATH / SDK | | lib.detect.find_package | Probe for a library | | lib.detect.find_program | Lower-level program probe | | net.http / net.fasturl | HTTP fetching with xmake's downloader | | devel.git | Git operations (clone, ls-remote) | | utils.archive | Zip/tar extraction |
Example — find a tool and shell out through it:
on_build(function (target)
import("lib.detect.find_tool")
import("core.base.option")
local protoc = assert(find_tool("protoc"), "protoc not found")
os.vrunv(protoc.program, {"--cpp_out=" .. target:autogendir(), "proto/msg.proto"})
end)
5. Target/config APIs from inside a hook
Inside on_load(function (target) ... end) the target argument gives you a target instance:
target:name()
target:kind()
target:targetfile() -- full path of the built file
target:sourcefiles() -- list of source files
target:installdir()
target:pkg("fmt") -- get a package instance bound to the target
target:add("links", "pthread")
target:add("defines", "DEBUG", {public = true})
target:set("kind", "binary")
target:get("includedirs")
target:values("cxflags")
target:add / target:set / target:get are the script-domain equivalents of add_* / set_* / the raw config.
6. Built-in modules you can use in scripts
All of these are pre-imported in the script domain — no import(...) needed:
os— cross-platformos.cp,os.mv,os.rm,os.mkdir,os.exists,os.exec,os.vrunv,os.iorunv,os.getenv,os.mtime,os.tmpdir,os.host,os.arch.io—io.readfile,io.writefile,io.open,io.lines,io.gsub.path—path.join,path.filename,path.basename,path.extension,path.directory,path.absolute,path.relative,path.translate.table— xmake-extended:table.concat,table.contains,table.insert,table.unique,table.join,table.copy.string— xmake-extended:string.split,string.trim,string.startswith,string.endswith.hash—hash.md5,hash.sha1,hash.sha256,hash.xxh64.print/cprint/vprint/printf/cprintf— xmake's logging.cprintsupports${color}tags;vprintonly prints in-vmode.raise— throw an error. Insidecli.bisectoron_test, a raised error marks the step bad.try ... catch ... finally— structured error handling, xmake's Lua-level equivalent.winos/linuxos/macos— host-specific helpers.
Full reference lives under xmake/core/base/ and xmake/core/sandbox/modules/ in the xmake source.
7. Shell-outs: pick the right os.*
| Call | Return | Use for | | --- | --- | --- | | os.exec("cmd arg1 arg2") | void (throws on fail) | Run a command, stream output to the user | | os.execv(program, {args...}) | void | Same, but args passed as a list (no shell parsing) | | os.vrunv(program, {args...}) | void | Like execv but only prints command in -v mode | | os.run("cmd ...") | void | Run silently unless it fails | | os.runv(program, {args...}) | void | Silent, list-form args | | os.iorun("cmd ...") | stdout | Capture stdout | | os.iorunv(program, {args...}) | stdout, stderr | Capture both |
Prefer *v forms (list args) over string forms — no shell quoting surprises. Prefer vrunv inside build hooks so -v controls visibility.
8. Custom tasks — callable from the CLI
Wrap a module into a task and it becomes xmake :
task("hello")
set_menu {
usage = "xmake hello [options]",
description = "Say hello",
options = {
{"n", "name", "kv", "world", "name to greet"}
}
}
on_run(function ()
import("core.base.option")
cprint("${bright green}hello, %s!${clear}", option.get("name"))
end)
xmake hello --name=ruki
Option kind: "k" (flag), "kv" (key-value), "vs" (values list). See xmake-plugins for more.
9. Native modules (C/C++ Lua modules)
When Lua is too slow (heavy hash/parse/math), or when you want to reuse an existing C library, drop into a native module. Xmake builds it automatically and loads it via import() just like a Lua module.
Two flavors:
- Shared (
module.shared) — a.so/.dll/.dylibloaded into the xmake process. Fastest; exposes Lua C API. - Binary (
module.binary) — an executable spawned per call. Simpler, cross-platform, but slower.
Shared native module
Directory layout:
xmake.lua
modules/
foo/
xmake.lua # builds the module
foo.c # native source
Module xmake.lua:
-- modules/foo/xmake.lua
add_rules("mode.debug", "mode.release")
target("foo")
add_rules("module.shared")
add_files("foo.c")
Native source:
// modules/foo/foo.c
#include // xmake's lua include shim — prefer over
static int c_add(lua_State* lua) {
int a = lua_tointeger(lua, 1);
int b = lua_tointeger(lua, 2);
lua_pushinteger(lua, a + b);
return 1;
}
static int c_sub(lua_State* lua) {
int a = lua_tointeger(lua, 1);
int b = lua_tointeger(lua, 2);
lua_pushinteger(lua, a - b);
return 1;
}
int luaopen(foo, lua_State* lua) {
static const luaL_Reg funcs[] = {
{"add", c_add},
{"sub", c_sub},
{NULL, NULL}
};
lua_newtable(lua);
luaL_setfuncs(lua, funcs, 0);
return 1;
}
Key points:
- Include
xmi.h, notlua.h/luaconf.hdirectly — it papers over Lua vs LuaJIT differences. - Xmake's main binary already exports the full Lua C API — no Lua dependency needed in your module.
luaopen(, lua_State*)is the entry point; xmake calls it when the module is imported.
Binary native module
modules/bar/
xmake.lua
bar.cpp
// modules/bar/bar.cpp
#include
#include
int main(int argc, char** argv) {
int a = atoi(argv[1]);
int b = atoi(argv[2]);
printf("%d", a + b);
return 0;
}
-- modules/bar/xmake.lua
target("add")
add_rules("module.binary")
add_files("bar.cpp")
The module protocol: xmake spawns the binary with the args, reads stdout for the return value. No Lua API involved.
Consuming the module
-- ./xmake.lua
add_moduledirs("modules") -- tell xmake where to look
target("app")
set_kind("phony")
on_load(function (target)
import("foo", {always_build = true})
import("bar")
print("foo.add(1,1) = %s", foo.add(1, 1))
print("foo.sub(1,1) = %s", foo.sub(1, 1))
print("bar.add(1,1) = %s", bar.add(1, 1))
end)
add_moduledirs(dir)registers a directory as an additional module root. Modules inside are built automatically on first import.{always_build = true}makes xmake re-check the module sources on every run — essential while iterating. Drop it in production for faster startup.
When to use which
| Case | Choose | | --- | --- | | Need speed + frequent calls | shared (no subprocess overhead) | | Want a trivial implementation, low call count | binary (no Lua C API) | | Need parallel execution across calls | binary (each call is its own process) | | Want to wrap a third-party Lua C module (cjson, etc.) | shared | | Codegen at configure time | Either — binary is simpler |
Reference project in the xmake source: tests/projects/other/native_module_cjson.
10. Common pitfalls
- Forgetting that description domain is parsed multiple times.
print/os.iorun/ network calls at the top level run on every parse. Move them intoon_load. - Using
requireinstead ofimport. Xmake's module convention is not standard Lua.requirewill sometimes work but skips xmake's sandbox, resolution order, and privacy rules. Alwaysimport. - Returning from a module file. Unnecessary — xmake collects top-level function definitions automatically. A trailing
return Mconfuses the loader. - Private
_foocalled externally.foo._helper(x)from outside the module fails. Expose a public wrapper or drop the underscore. - Native module rebuild not triggering. Without
{always_build = true}, xmake only builds the module once. Add it during development. - Native module including
lua.hdirectly. Works until you hit a LuaJIT-only build; use `` for portability. - Shelling out with
os.exec("cmd " .. user_input). String-form is shell-parsed. Useos.execv(program, {args...})to avoid injection and quoting bugs.
When to branch out
- Where scripts live vs. description-domain rules →
xmake-style - Writing a CLI subcommand (task/plugin) →
xmake-plugins - Writing a rule (lifecycle hook tied to a file extension) →
xmake-rules - Debugging script behavior (
-vD, tracebacks, EmmyLua) →xmake-troubleshooting,xmake-dev
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: xmake-io
- Source: xmake-io/xmake-skills
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.