From xmake-skills
Guides developers through authoring custom xmake plugins and tasks: defining `task()` with `set_menu`, implementing `on_run` logic, packaging standalone plugins, and using `import()` for core modules.
How this skill is triggered — by the user, by Claude, or both
Slash command
/xmake-skills:xmake-custom-pluginsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A **task** is a named function reachable via `xmake <taskname>` on the CLI. A **plugin** is just a task packaged for reuse / distribution. Same API — the only difference is where the code lives.
A task is a named function reachable via xmake <taskname> on the CLI. A plugin is just a task packaged for reuse / distribution. Same API — the only difference is where the code lives.
This skill focuses on writing plugins. For using built-in plugins (xmake project -k compile_commands, etc.), see xmake-plugins.
xmake.luatask("hello")
set_menu {
usage = "xmake hello [options]",
description = "Print a greeting",
options = {
{"n", "name", "kv", "world", "Name to greet"},
{nil, "loud", "k", nil, "Shout it"}
}
}
on_run(function ()
import("core.base.option")
local name = option.get("name")
if option.get("loud") then
cprint("${bright red}HELLO, %s!", name:upper())
else
cprint("${green}hello, %s", name)
end
end)
xmake hello # hello, world
xmake hello --name=ruki # hello, ruki
xmake hello -n ruki --loud # HELLO, RUKI!
xmake hello --help # menu auto-generated
set_menu schemaset_menu {
usage = "xmake <task> [options]", -- one-line usage string
description = "Short description", -- listed in `xmake --help`
options = {
-- { short, long, kind, default, description [, extra] }
{"n", "name", "kv", "world", "Name to greet"},
{"v", "verbose", "k", nil, "Verbose output"},
{nil, "files", "vs", nil, "Input files"},
{}, -- separator in help output
{nil, "mode", "kv", "release", "Build mode",
{"debug", "release"}}, -- restricted values
}
}
Option kinds:
| Kind | Meaning | Usage |
|---|---|---|
"k" | Flag (boolean) | --name, -v |
"kv" | Key-value | --name=value, -n value |
"vs" | Values list | --files=a.cpp --files=b.cpp or positional |
Short name is optional (nil for long-only).
on_run bodyon_run is the task entry point. It has full script-domain access — import any core module, read project state, shell out, etc.
on_run(function ()
import("core.base.option")
import("core.project.project")
import("core.project.config")
-- read options
local name = option.get("name")
local loud = option.get("loud")
local files = option.get("files") -- list for "vs" kind
-- inspect the project
for target_name, target in pairs(project.targets()) do
print("%s -> %s", target_name, target:targetfile())
end
-- current config
print("plat=%s arch=%s mode=%s", config.plat(), config.arch(), config.mode())
end)
Once on_run grows, move it:
myproject/
xmake.lua
plugins/
hello/
xmake.lua # task definition
main.lua # entry
utils.lua # helpers
-- plugins/hello/xmake.lua
task("hello")
set_menu { ... }
on_run("main") -- "main" = main.lua in the same directory
-- plugins/hello/main.lua
import("core.base.option")
import(".utils") -- . = current dir
function main()
local name = option.get("name")
utils.greet(name)
end
-- plugins/hello/utils.lua
function greet(name)
cprint("${bright green}hi, %s${clear}", name)
end
Include the plugin from the project:
-- main xmake.lua
includes("plugins/hello")
Now xmake hello works.
A plugin is just a task directory installed somewhere xmake searches:
~/.xmake/plugins/<name>/ — per-user pluginsplugins/ — distributable via xrepo add-repo$(programdir)/plugins/ — built-in plugins (read-only)Minimum layout:
~/.xmake/plugins/hello/
├── xmake.lua # task(...) definition
└── main.lua # implementation
After dropping the files, xmake hello works in any project — no includes(...) needed.
Frequently imported:
import("core.base.option") -- task options
import("core.base.task") -- task.run("other-task")
import("core.base.global") -- global config
import("core.project.project") -- project:targets(), project:rootfile(), etc.
import("core.project.config") -- plat/arch/mode
import("core.platform.platform") -- platform info
import("core.tool.toolchain") -- resolved toolchain
import("core.tool.compiler") -- invoke a compiler
import("core.tool.linker") -- invoke a linker
import("core.package.package") -- installed package info
import("lib.detect.find_tool") -- probe PATH
import("lib.detect.find_package") -- probe packages
import("net.http") -- HTTP client
import("devel.git") -- git clone/ls-remote
import("utils.archive") -- zip/tar extract
on_run(function ()
import("core.base.task")
task.run("build", {target = "app"}) -- run xmake build app
task.run("custom-task")
end)
task("stats")
set_menu {
usage = "xmake stats",
description = "Show file counts per target"
}
on_run(function ()
import("core.project.project")
for name, target in pairs(project.targets()) do
cprint("${bright}%s${clear}: %d files", name, #target:sourcefiles())
end
end)
"vs"task("sha256")
set_menu {
usage = "xmake sha256 <file>...",
description = "Hash one or more files",
options = {
{nil, "files", "vs", nil, "Files to hash"}
}
}
on_run(function ()
import("core.base.option")
for _, f in ipairs(option.get("files") or {}) do
print("%s %s", hash.sha256(f), f)
end
end)
xmake sha256 a.tar b.tar c.tar
When the plugin does heavy work in C/C++ (hash, parse, math), pair it with a native module — see xmake-scripting. Short version:
-- plugins/hello/xmake.lua
includes("modules") -- builds a native .so/.dll module
task("hello")
on_run(function ()
import("hello_native")
print(hello_native.compute(42))
end)
my-repo/plugins/hello/.xrepo add-repo my-repo https://github.com/me/my-repo.git~/.xmake/plugins/Just git clone https://github.com/me/hello-plugin ~/.xmake/plugins/hello. Done.
includes("plugins/*") in the project's top-level xmake.lua — picks up every task inside plugins/.
xmake hello -vD # verbose + Lua tracebacks
XMAKE_PROFILE=stuck xmake hello # if it hangs
xmake l plugins/hello/main.lua # run the file directly in xmake Lua env
Set print(...) liberally — on_run runs in the script domain, so all logging primitives are available.
set_menu { ... } vs set_menu({...}). Both work (Lua sugar for a single table arg). Inside options, use explicit braces.on_run. A task without on_run is just a menu entry — xmake hello prints help and exits.task() is at top level; everything mutating belongs in on_run. cprint at top level runs on every parse."k" flags are booleans — option.get("flag") returns true/false, not a value. "kv" returns the string value.build, run, install), xmake raises. Pick a unique prefix (mycompany.build).includes() parses the plugin's xmake.lua, so don't put os.exec at top level — it runs even if the user never invokes the task.xmake-pluginsxmake-rulesimport() basics → xmake-scriptingos/io/path) → xmake-script-modulesxmake-scriptingnpx claudepluginhub xmake-io/xmake-skills --plugin xmake-skillsGenerates compile_commands.json, CMakeLists.txt, Xcode/VS project files, Doxygen docs, and other built-in Xmake plugins. Also defines custom tasks via `task(...)` with menus and option handling.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.