Imported from LuisaGroup/LuisaCompute (
.agents/skills/xmake/SKILL.md). Install upstream withnpx skills add LuisaGroup/LuisaCompute --skill xmake. Copyright stays with the author.
XMake Build System
Primary build system. Requires XMake 3.0.6+. Optional: CUDA Toolkit, Vulkan SDK, LLVM 20, Rust.
Quick Start
xmake f -m debug -c -y
xmake build
# Update compile_commands.json:
xmake project -k compile_commands --lsp=clangd .vscode
Configuration
| Platform | Command |
|---|---|
| Linux GCC | xmake f -p linux -a x86_64 --toolchain=gcc -m release -c |
| Linux Clang | xmake f -p linux -a x86_64 --toolchain=clang -m release -c |
| Windows MSVC | xmake f -p windows -a x64 --toolchain=msvc -m release -c |
| Windows Clang-CL | xmake f -p windows -a x64 --toolchain=clang-cl -m release -c |
| Windows LLVM | xmake f -p windows -a x64 --toolchain=llvm -m release -c |
| macOS Clang | xmake f -p macosx -a arm64 --toolchain=clang -m release -c |
Flags
-c clean cache, -m <mode> (release/debug/releasedbg/check/profile/coverage), -p <plat> (linux/windows/macosx), -a <arch> (x86_64/x64/arm64), --check check before building, -y auto-accept all prompts and skip interaction (useful in scripts/CI).
In this project debug mode automatically enables AddressSanitizer (ASan). To enable ASan for other modes, use --policies=build.sanitizer.address.
Sanitizer Modes
XMake supports sanitizer builds through sanitizer policies. Policies propagate the sanitizer configuration to dependent packages and avoid the deprecation warnings produced by the legacy mode.asan/mode.tsan/mode.lsan/mode.ubsan rules.
ASan in debug mode
Configure and build with debug mode as usual:
xmake f -m debug -c -y
xmake build
xmake run <target>
Enable via policy manually
To enable a sanitizer for a different mode, use the corresponding policy. In xmake.lua:
set_policy("build.sanitizer.address", true)
Or from the command line:
xmake f --policies=build.sanitizer.address -c -y
xmake build
xmake run <target>
Available policies:
| Policy | Sanitizer |
|---|---|
build.sanitizer.address |
AddressSanitizer |
build.sanitizer.thread |
ThreadSanitizer |
build.sanitizer.memory |
MemorySanitizer |
build.sanitizer.leak |
LeakSanitizer |
build.sanitizer.undefined |
UndefinedBehaviorSanitizer |
Multiple sanitizers can be combined, e.g.:
xmake f --policies=build.sanitizer.address,build.sanitizer.undefined -c -y
Commands
| Command | Description |
|---|---|
xmake clean |
Clean |
xmake -r |
Rebuild |
xmake build <target> |
Build target |
xmake run <target> |
Run target |
xmake run <target> <args> |
Run target with arguments |
xmake -l |
List targets |
xmake install -o <dir> |
Install binaries to <dir> |
xmake -y |
Auto-accept all prompts (downloads, overwrites, etc.), skip interaction |
xmake project -k compile_commands --lsp=clangd .vscode |
Generate compile_commands.json |
Common Issues
-v,-D,--diagnosisinvalid; use--verbose- Boolean options:
--lc_option=true/=false - Use
-cto clean cache when reconfiguring with different options - Use
-yto auto-accept all prompts and skip interaction — essential in automated scripts and CI pipelines lc_fallback_backendrequires bothlc_llvm_pathandlc_embree_pathlc_dx_backendis silently disabled on non-Windows platformslc_metal_backendis silently disabled on non-macOS platformslc_cuda_backendis silently disabled outside Windows/Linux- PCH (precompiled header) error like
has been modified since the precompiled header/redefinition of ...means the target's PCH is stale — usexmake build -r <target>to force a clean rebuild of that target.
Xmake Target Writing Tutorial
Overview
This tutorial covers how to write xmake targets using the standard xmake API, with examples drawn from real projects like LuisaCompute. The recommended style uses on_load callbacks for dynamic configuration, with static declarations outside.
1. Basic Target Structure
target("<name>", {kind = "static"}) -- Optional: pass kind inline
-- or
target("<name>")
set_kind("static") -- "static", "shared", "binary", "object", "phony", "headeronly", "moduleonly"
-- Static settings (outside on_load)
set_basename("my-lib") -- Override output filename
add_deps("dep1", "dep2") -- Target dependencies
add_rules("my-rule") -- Custom build rules (MUST be outside on_load)
add_files("src/*.cpp") -- Source files (simple globs outside)
add_headerfiles("include/**.h") -- Header files
on_load(function(target)
-- Dynamic settings (preferred for conditional config)
target:add("includedirs", "include", {public = true})
target:add("defines", "MY_DEFINE", {public = true})
target:add("deps", "another-dep") -- Same as add_deps() outside
target:set("kind", "shared")
target:add("links", "pthread")
target:add("syslinks", "dl")
target:add("packages", "spdlog") -- For xrepo packages
end)
after_build(function(target)
-- Post-build steps (e.g., copy DLLs)
end)
target_end()
Key rules:
add_rules()must be outsideon_load— they are target-level; cannot be set from insideon_load.add_deps()outside =target:add("deps", ...)inside — they are equivalent.- Simple globs (
add_files,add_headerfiles) can go outside; conditional additions go insideon_load. - Visibility — pass
{public = true},{interface = true}, or{private = true}(default) to control inheritance.
2. Config Fields and Script Fields
Every xmake target has two kinds of declarations:
- Config fields — static target properties (what to build, how to build it).
- Script fields — lifecycle callbacks (when to run custom Lua code).
Config Fields
Config fields are the key/value pairs that describe a target. They are set with set_* / add_* outside on_load, or equivalently with target:set() / target:add() inside a script field.
Common config field categories:
| Category | Fields |
|---|---|
| Identity | kind, basename, filename, prefixname, suffixname, extension, group |
| Output | targetdir, objectdir, dependir, rundir, installdir, prefixdir |
| Sources | files, headerfiles, extrafiles, remove_files, configfiles, installfiles |
| Includes | includedirs, sysincludedirs |
| Defines | defines, undefines, configvar |
| Links | links, syslinks, linkdirs, rpathdirs, linkorders, linkgroups, frameworks, frameworkdirs |
| Compilation | languages, optimize, warnings, symbols, runtimes, exceptions, fpmodels, encodings, strip, vectorexts, forceincludes, pcheader, pcxxheader |
| Dependencies | deps, packages, options, rules |
| Misc | values.*, runenv, runargs, enabled, default, toolchains, toolset, plat, arch, policy |
Rules for config fields:
- Most config fields can be set either outside or inside
on_loadusing the equivalenttarget:set("field", value)/target:add("field", value)form. add_rules()must be outsideon_load— rules are target-level metadata and cannot be added from inside a script field.- Static config goes outside for readability; dynamic/conditional config goes inside
on_load. - Use
{public = true}/{interface = true}/{private = true}withtarget:add()to control inheritance of includedirs, defines, links, etc. target:get("field")reads a config field inside a script field;has_config("opt")reads project-level options.
Script Fields
Script fields are the lifecycle hooks where you write imperative Lua code. They receive the target object (and sometimes other arguments) and run at specific build phases.
Common script fields:
| Script field | Runs when | Typical use |
|---|---|---|
on_load(function(target) ... end) |
Target is loaded (early) | Dynamic config, conditional deps/files |
on_config(function(target) ... end) |
After xmake config, before build |
Validate toolchain/options |
before_build(function(target) ... end) |
Before compilation starts | Pre-build checks/code generation |
on_build(function(target) ... end) |
Build phase | Override entire build |
after_build(function(target) ... end) |
After build finishes | Copy outputs, print reports |
before_link(function(target) ... end) |
Before linking | Inject link args |
after_link(function(target) ... end) |
After linking | Sign/post-process binary |
on_install(function(target) ... end) |
Install phase | Custom install logic |
on_run(function(target) ... end) |
xmake run |
Override run behavior |
Rules for writing code in script fields:
- Always operate on the
targetargument for target-local config:target:add("field", value),target:set("field", value),target:get("field"). - Project-scope helpers are still available:
is_plat(),is_arch(),is_mode(),has_config(),get_config(),os.*,io.*,path.*, etc. - You can
import()extension modules at the top of the script field callback (or at file scope). on_loadis for configuration — it should set/add target config fields. It runs very early, so dependencies may not be fully resolved yet.before_build/after_buildare for actions — they run around compilation and are the right place to generate files, copy DLLs, run validators, or emit summaries.- Returning
falsefrom some hooks (e.g.on_test) signals failure; most hooks ignore return values.
Example: Writing Code Inside Script Fields
target("my-scripted-target")
set_kind("binary")
add_files("src/*.cpp")
add_includedirs("include")
-- Static config field outside
set_basename("myapp")
set_warnings("all")
on_load(function(target)
-- Dynamic config field inside script field
target:add("defines", "VERSION=\"1.0.0\"", {public = true})
if target:is_plat("windows") then
target:add("syslinks", "Advapi32", "Ole32")
elseif target:is_plat("linux") then
target:add("syslinks", "pthread", "dl")
end
if is_mode("debug") then
target:set("symbols", "debug")
target:set("optimize", "none")
end
-- Record data for later script fields
target:data_set("build_start", os.mclock())
end)
before_build(function(target)
-- Script field code: validate before compiling
local main = path.join(target:scriptdir(), "src/main.cpp")
if not os.isfile(main) then
raise("missing entry point: " .. main)
end
-- Generate a version header
local out = path.join(target:autogendir(), "version.h")
os.mkdir(path.directory(out))
io.writefile(out, string.format("#define BUILD_TIME %d\n", os.time()))
target:add("includedirs", path.directory(out))
end)
after_build(function(target)
-- Script field code: post-build action
local exe = target:targetfile()
if os.isfile(exe) then
local dest = path.join("$(buildir)", "publish")
os.mkdir(dest)
os.cp(exe, dest)
print("published:", exe)
end
local start = target:data("build_start")
if start then
print("elapsed:", os.mclock() - start, "ms")
end
end)
target_end()
3. API Equivalence: Inside on_load
Use target:add() and target:set() inside on_load to dynamically configure targets:
target:add() — cumulative (equivalent to add_*)
Inside on_load(target) |
|---|
target:add("deps", "foo") |
target:add("files", "*.cpp") |
target:add("headerfiles", "*.h") |
target:add("includedirs", "inc") |
target:add("sysincludedirs", "inc") |
target:add("defines", "FOO") |
target:add("undefines", "BAR") |
target:add("links", "foo") |
target:add("syslinks", "dl") |
target:add("linkorders", ...) |
target:add("linkgroups", {group = true}) |
target:add("linkdirs", "lib") |
target:add("rpathdirs", "lib") |
target:add("frameworks", "Foundation") |
target:add("frameworkdirs", "dir") |
target:add("embeddirs", "dir") |
target:add("packages", "spdlog") |
target:add("options", "myopt") |
target:add("vectorexts", "avx2") |
target:add("languages", "cxx20") |
target:add("imports", "module") |
target:add("runenvs", "PATH", "/usr/bin") |
target:add("forceincludes", "inc.h") |
target:add("configfiles", "config.h.in") |
target:add("installfiles", "data/*") |
target:add("extrafiles", "readme.md") |
target:add("filegroups", "src", files) |
target:set() — singular (equivalent to set_*)
Inside on_load(target) |
|---|
target:set("kind", "static") |
target:set("basename", "foo") |
target:set("filename", "foo.dll") |
target:set("prefixname", "lib") |
target:set("suffixname", "-d") |
target:set("extension", ".dll") |
target:set("targetdir", "lib") |
target:set("objectdir", "obj") |
target:set("dependir", "deps") |
target:set("rundir", "bin") |
target:set("runargs", "--verbose") |
target:set("installdir", "/usr") |
target:set("prefixdir", "subdir") |
target:set("configdir", "out") |
target:set("group", "mygroup") |
target:set("languages", "cxx20") |
target:set("optimize", "fastest") |
target:set("warnings", "all") |
target:set("symbols", "debug") |
target:set("exceptions", "cxx") |
target:set("runtimes", "MD") |
target:set("fpmodels", "fast") |
target:set("encodings", "utf-8") |
target:set("strip", "all") |
target:set("enabled", true) |
target:set("default", false) |
target:set("toolchains", "clang") |
target:set("toolset", "cc", "/usr/bin/gcc") |
target:set("plat", "linux") |
target:set("arch", "x64") |
target:set("policy", "build.optimization.lto", true) |
target:set("options", "opt1") |
target:set("values.mykey", "val") |
target:set("configvar", "VAR", "value") |
target:set("runenv", "PATH", "/usr/bin") |
target:set("pcheader", "header.h") |
target:set("pcxxheader", "header.hpp") |
target:set("pmheader", "header.m") |
target:set("pmxxheader", "header.mm") |
Note: For the
target:add("name", ...)/target:set("name", ...)pattern, any key name works through xmake's generic values mechanism. Only explicitly defined APIs (likefiles,deps,kind) have special handling.
4. Compilation Flags (by Language)
These APIs pass compiler-specific flags:
| API | Description |
|---|---|
add_cflags(...) |
C compilation flags |
add_cxflags(...) |
C/C++ compilation flags |
add_cxxflags(...) |
C++ compilation flags |
add_mflags(...) |
ObjC compilation flags |
add_mxflags(...) |
ObjC/ObjC++ compilation flags |
add_mxxflags(...) |
ObjC++ compilation flags |
add_scflags(...) |
Swift compilation flags |
add_asflags(...) |
Assembly compilation flags |
add_gcflags(...) |
Go compilation flags |
add_dcflags(...) |
D language compilation flags |
add_rcflags(...) |
Rust compilation flags |
add_fcflags(...) |
Fortran compilation flags |
add_zcflags(...) |
Zig compilation flags |
add_cuflags(...) |
CUDA compilation flags |
add_culdflags(...) |
CUDA device link flags |
add_cugencodes(...) |
CUDA gencode settings (e.g., "sm_30", "native") |
Linker Flags
| API | Description |
|---|---|
add_ldflags(...) |
Static library/exe link flags |
add_arflags(...) |
Archive (static library) flags |
add_shflags(...) |
Dynamic library link flags |
Example with per-tool flags:
on_load(function(target)
target:add("cxflags", "-fPIC", {tools = {"clang", "gcc"}, public = true})
target:add("cxflags", "/Zc:preprocessor", {tools = "cl"})
target:add("ldflags", "-Wl,-rpath,.", {force = true, expand = false})
end)
5. Precompiled Headers (PCH)
target("my-target")
set_pcheader("precompiled.h") -- C PCH
set_pcxxheader("precompiled.hpp") -- C++ PCH
Enable conditionally with:
if has_config("enable_pch") then
set_pcxxheader("mypch.hpp")
end
6. Conditional Configuration with Conditions
on_load(function(target)
-- Platform checks
if target:is_plat("windows") then
target:add("defines", "NOMINMAX", "PLATFORM_WINDOWS")
target:add("syslinks", "Advapi32", "Ole32")
elseif target:is_plat("linux") then
target:add("syslinks", "dl", "uuid", "pthread")
target:add("cxflags", "-fPIC")
elseif target:is_plat("macosx") then
target:add("frameworks", "CoreFoundation", "Metal")
end
-- Architecture checks
if target:is_arch("x64", "x86_64") then
target:add("vectorexts", "avx2")
elseif target:is_arch("arm64", "aarch64") then
target:add("defines", "PLATFORM_ARM")
end
-- Build mode
if is_mode("debug") then
target:set("symbols", "debug")
target:set("optimize", "none")
target:set("runtimes", "MDd")
elseif is_mode("release") then
target:set("optimize", "aggressive")
target:set("symbols", "hidden")
target:set("runtimes", "MD")
end
-- Config option checks
if has_config("my_feature") then
target:add("defines", "MY_FEATURE_ENABLED")
target:add("deps", "my-feature-dep")
end
if has_package("spdlog") then
target:add("packages", "spdlog")
end
-- Target kind checks
if target:get("kind") == "static" then
target:add("defines", "MYLIB_STATIC", {public = true})
elseif target:get("kind") == "shared" then
target:add("defines", "MYLIB_EXPORT", {public = true})
end
end)
Standalone Condition Functions (usable in any scope)
if is_plat("windows") then ... end -- Current target platform
if is_arch("x64") then ... end -- Current target architecture
if is_mode("debug") then ... end -- Current build mode
if is_os("windows") then ... end -- Target OS (e.g., "ios", "android")
if is_host("windows") then ... end -- Host OS running xmake
if is_subhost("msys") then ... end -- Subsystem (e.g., "msys", "cygwin")
if is_subarch(...) then ... end -- Subsystem architecture
if is_cross() then ... end -- Cross-compilation check
if is_kind("static") then ... end -- Target kind check
if is_config("var", "value") then ... end -- Config option value check
if has_config("feature") then ... end -- Config option exists/enabled?
if has_package("pkg") then ... end -- Package exists/enabled?
7. Lifecycle Hooks
target("my-target")
-- Loading phase
on_load(function(target) -- When target is loaded (early)
end)
on_config(function(target) -- After 'xmake config', before build
end)
-- Build preparation
on_prepare(function(target) -- Source preprocessing/code generation
end)
on_prepare_file(func) -- Single file preprocessing
on_prepare_files(func) -- Batch file preprocessing
-- Build phase
on_build(function(target) -- Override entire build
end)
on_build_file(func) -- Replace single file compilation
on_build_files(func) -- Replace batch file compilation
on_link(function(target) -- Custom link process
end)
-- Clean / Package / Install / Run
on_clean(function(target)
end)
on_package(function(target)
end)
on_install(function(target)
end)
on_uninstall(function(target)
end)
on_run(function(target) -- Override 'xmake run'
end)
-- Test hooks
on_test(function(target) -- Custom test (return true=pass)
end)
-- Before/After variants exist for all of the above:
before_build(function(target) ... end)
after_build(function(target) ... end)
before_link(function(target) ... end)
after_link(function(target) ... end)
before_install(function(target) ... end)
after_install(function(target) ... end)
-- ... etc.
Common Use of after_build — Copy DLLs
after_build(function(target)
if is_plat("windows") then
os.cp("path/to/mylib.dll", target:targetdir())
elseif is_plat("linux") then
os.cp("path/to/libmylib.so", target:targetdir())
end
end)
8. Visibility and Inheritance
Many target:add() / target:set() calls accept a visibility table to control propagation:
-- Public: propagated to dependent targets + current target
target:add("includedirs", "include", {public = true})
target:add("defines", "PUBLIC_DEF", {public = true})
target:add("links", "mylib", {public = true})
-- Interface: only propagated to dependents (not current target)
target:add("includedirs", "include", {interface = true})
-- Private: only for current target (default)
target:add("defines", "PRIVATE_DEF", {private = true})
Dependency inheritance can be controlled per-target:
add_deps("foo", {inherit = false}) -- No inheritance from this dep
add_deps("bar", {inherit = true}) -- Default: inherit
add_deps("baz", {links = false}) -- Don't inherit links from this dep
9. Tests
target("my-test")
set_kind("binary")
add_files("test_*.cpp")
add_tests("test_foo", {
runargs = {"--arg1", "--arg2"},
runenvs = {PATH = "/usr/bin"},
timeout = 30,
group = "unit",
pass_outputs = {"PASSED"},
fail_outputs = {"FAILED"},
should_fail = false,
build_should_pass = true,
})
-- Or with custom test script
on_test(function(target)
-- Return true for pass, false + error for fail
local ok = os.execv("./my_test")
if not ok then
return false, "test failed"
end
return true
end)
10. Common Target Patterns
10.1 Shared Library
target("mylib")
set_kind("shared")
set_basename("mylib")
add_deps("core")
add_headerfiles("include/**.h")
on_load(function(target)
target:add("defines", "MYLIB_EXPORT_DLL")
target:add("includedirs", "include", {public = true})
target:add("files", "src/*.cpp")
if target:is_plat("windows") then
target:add("defines", "NOMINMAX")
target:add("syslinks", "Advapi32")
elseif target:is_plat("macosx") then
target:add("frameworks", "Foundation")
end
if has_config("enable_extra") then
target:add("defines", "EXTRA_FEATURE")
target:add("files", "src/extra/*.cpp")
end
end)
if has_config("enable_pch") then
set_pcxxheader("src/mylib_pch.h")
end
target_end()
10.2 Static Library
target("mystatic")
set_kind("static")
set_basename("mystatic")
add_deps("core")
add_headerfiles("include/**.h")
add_files("src/*.cpp")
add_defines("MYSTATIC_STATIC_LIB", {public = true})
target_end()
10.3 Executable (Binary)
target("my-tool")
set_kind("binary")
add_deps("runtime", "dsl")
add_files("main.cpp")
add_includedirs("include")
on_load(function(target)
if has_config("enable_gui") then
target:add("deps", "gui")
target:add("defines", "ENABLE_GUI")
end
end)
target_end()
10.4 Phony Target (Meta / Validation)
target("my-validator")
set_kind("phony") -- No build output
add_deps("runtime")
on_config(function(target)
if target:is_plat("windows") then
local toolchain = target:toolchain("msvc")
-- Validate SDK version, toolchain, etc.
end
end)
target_end()
10.5 Header-only Target
target("my-headers")
set_kind("headeronly")
add_headerfiles("include/**.h")
add_includedirs("include", {public = true})
target_end()
10.6 Test Target (using a helper function)
local function test_proj(name, source, extra)
target(name)
set_kind("binary")
add_deps("runtime", "dsl")
add_files(source)
add_includedirs("common")
if extra then extra() end
target_end()
end
test_proj("test_foo", "tests/test_foo.cpp")
test_proj("test_bar", "tests/test_bar.cpp", function()
add_defines("EXTRA")
add_deps("extra-dep")
end)
10.7 Object Target (Intermediate objects only)
target("my-objects")
set_kind("object") -- Compiles sources but does not link
add_files("src/*.cpp")
target_end()
11. Custom Rules
add_rules() must be outside on_load:
target("my-target")
add_rules("c.unity_build", {batchsize = 8}) -- Unity build
add_rules("c++.unity_build", {batchsize = 8})
add_rules("utils.bin2obj", {extensions = {".cu", ".h"}}) -- Binary embedding
add_rules("build_cargo") -- Rust/Cargo build
add_rules("lc_llvm") -- LLVM integration
target_end()
Rules with Custom Values
target("my-target")
add_rules("my-rule")
set_values("mykey", "value1", "value2")
add_values("mykey", "value3")
target_end()
12. The on_load / on_config Target Object
Inside lifecycle hooks, the target object provides these methods:
| Method | Description |
|---|---|
target:name() |
Get target name |
target:fullname() |
Get full name (with namespace) |
target:targetdir() |
Get output directory |
target:targetfile() |
Get target file path |
target:scriptdir() |
Get directory of the xmake.lua file |
target:arch() |
Get target architecture |
target:plat() |
Get target platform |
target:is_plat("windows") |
Check platform |
target:is_arch("x64") |
Check architecture |
target:is_arch64() |
Is 64-bit architecture? |
target:is_mode("debug") |
Check build mode (alias for is_mode()) |
target:is_cross() |
Is cross-compilation? |
target:has_tool("cxx", "clang") |
Check if using specific tool |
target:get("kind") |
Get any target property |
target:get_from("links", "*") |
Get values from all sources (self, deps, options, packages) |
target:add("key", "value", {public=true}) |
Add configuration |
target:set("key", "value") |
Override configuration |
target:deps() |
Get all dependent targets (after_load only) |
target:dep("name") |
Get a specific dependency (after_load only) |
target:orderdeps({inherit=true}) |
Get ordered deps |
target:toolchain("msvc") |
Get toolchain instance |
target:compiler("cxx") |
Get compiler instance |
target:linker() |
Get linker instance |
target:sourcebatches() |
Get source file batches |
target:objectdir() |
Get object directory |
target:dependir() |
Get dependency directory |
target:autogendir() |
Get auto-generated files directory |
target:data("key") |
Get user private data |
target:data_set("key", value) |
Set user private data |
target:values("name") |
Get custom values |
target:values_set("name", ...) |
Set custom values |
target:rule("name") |
Get a rule instance |
target:rule_enable("name", bool) |
Enable/disable a rule |
target:extraconf("name", "item", "key") |
Get extra configuration |
target:extraconf_from("name", "source") |
Get extra config from source |
target:pkgs() |
Get all packages |
target:pkg("name") |
Get a package instance |
target:is_kind("kind") |
Check target kind |
target:kind() |
Get target kind |
target:basename() |
Get output base name |
target:filename() |
Get output filename |
target:version() |
Get target version |
target:clone() |
Clone the target (after_load only) |
target:is_phony() |
Is phony target? |
target:is_binary() |
Is binary target? |
target:is_shared() |
Is shared library? |
target:is_static() |
Is static library? |
target:is_library() |
Is any library type? |
target:is_enabled() |
Is target enabled? |
target:is_default() |
Is default build target? |
target:is_rebuilt() |
Was target rebuilt? |
13. Dependencies: Options & Packages
target("my-target")
-- Option dependencies
add_options("my_option")
set_options("my_option")
-- Package dependencies (requires add_requires in root scope)
add_requires("spdlog", "fmt")
target("my-target")
add_packages("spdlog", "fmt")
-- With component selection
add_packages("sfml", {components = {"graphics", "window"}})
-- Internal target dependencies with fine-grained control
add_deps("lib-a", "lib-b", {inherit = true}) -- Full inheritance
add_deps("lib-c", {inherit = false}) -- No inheritance
add_deps("lib-d", {links = false}) -- Don't inherit links
14. Run Environment
target("my-target")
set_runenv("PATH", "/custom/path") -- Override environment variable
add_runenvs("PATH", "/extra/path") -- Append to environment variable
15. File Management
target("my-target")
add_files("src/*.cpp")
add_files("src/*.cpp", {sourcekind = "cxx"}) -- With per-file options
add_files("src/*.m", {sourcekind = "mxx"}) -- ObjC++ files
remove_files("src/old.cpp") -- Remove previously added files
add_headerfiles("include/**.h")
remove_headerfiles("include/deprecated.h")
add_installfiles("config/*.ini")
add_configfiles("config.h.in") -- Template config files with @var@ substitution
add_extrafiles("README.md") -- Extra files for IDE listing
add_forceincludes("precompiled.h") -- Force-include header
16. Complete Example
-- Root xmake.lua
set_xmakever("3.0.6")
add_rules("mode.release", "mode.debug")
add_requires("spdlog")
-- Library target
target("mylib")
set_kind("shared")
set_basename("mylib")
add_deps("core")
add_headerfiles("include/**.h")
add_rules("c++.unity_build", {batchsize = 8})
if has_config("enable_pch") then
set_pcxxheader("src/mylib_pch.h")
end
on_load(function(target)
-- Source files
target:add("files", "src/*.cpp")
if has_config("enable_extra") then
target:add("files", "src/extra/*.cpp")
target:add("defines", "ENABLE_EXTRA")
end
-- Public include dirs and defines
target:add("includedirs", "include", {public = true})
-- Platform config
if target:is_plat("windows") then
target:add("defines", "NOMINMAX", "PLATFORM_WIN", "MYLIB_EXPORT_DLL")
target:add("syslinks", "Advapi32", "Ole32")
target:add("cxflags", "/Zc:preprocessor", {tools = "cl"})
elseif target:is_plat("macosx") then
target:add("frameworks", "Foundation", "Metal")
target:add("cxflags", "-fobjc-arc")
elseif target:is_plat("linux") then
target:add("syslinks", "dl", "pthread", "uuid")
target:add("cxflags", "-fPIC")
end
-- Arch config
if target:is_arch("x64", "x86_64") then
target:add("vectorexts", "avx2")
end
-- Package dependencies
if has_config("use_xrepo_spdlog") then
target:add("packages", "spdlog")
else
target:add("deps", "spdlog-bundled")
end
end)
after_build(function(target)
if is_plat("windows") then
os.cp("$(buildir)/mydep.dll", target:targetdir())
end
end)
target_end()
-- Test executable
target("test-mylib")
set_kind("binary")
add_deps("mylib")
add_files("tests/*.cpp")
add_tests("test_basic", {
runargs = {"--verbose"},
group = "unit",
})
on_load(function(target)
target:add("includedirs", "tests")
end)
target_end()
Summary
- Use
on_loadfor conditional logic — platform checks, feature flags, dynamic file lists. add_rules()stays outside — cannot be set from insideon_load.- Simple globs outside, conditional additions inside — keep
add_files/add_headerfilesoutside for simple cases. - Prefer
target:set()/target:add()insideon_loadfor most configuration — it's equivalent to outside calls. - Visibility —
{public = true}propagates to dependents,{interface = true}propagates only to dependents,{private = true}(default) is local-only. add_deps()outside =target:add("deps", ...)inside — choose whichever fits your style.- All APIs listed here work at the target scope level — use them outside
on_loadasset_kind(...)or inside astarget:set("kind", ...).---
Lua Scripting in xmake
Reference:
D:/xmake/core/sandbox/modules/,D:/xmake/modules/,D:/xmake/core/base/
xmake scripts (in on_load, on_build, after_install, etc.) run in a sandboxed Lua environment. This section documents all available built-in modules and APIs.
1. Built-in Sandbox Modules
1.1 print / printf — Output
print("hello", "world") -- Print with newline
printf("hello %s", "world") -- Print without newline
vprint("verbose msg") -- Only printed with -v/--verbose
dprint("diagnosis msg") -- Only printed with --diagnosis
1.2 cprint / cprintf — Colored Output
cprint("${bright}hello${reset}") -- Bright text
cprint("${red}error${reset}") -- Red text
cprint("${color.dump.string}hello") -- Dump color
cprint("${dim}%s${reset}", "world") -- Dim text
Available color tags: ${red}, ${green}, ${blue}, ${yellow}, ${magenta}, ${cyan}, ${bright}, ${dim}, ${reset}, ${underline}, etc.
1.3 utils — Utilities
utils.dump(obj) -- Dump object for debugging
utils.assert(value, "msg", ...) -- Assert with error message
utils.error("err %s", arg) -- Error message
utils.warning("warn %s", arg) -- Warning message
utils.trycall(func) -- Call function safely (returns ok, ...)
1.4 Path Operations
path.join("a", "b", "c") -- "a/b/c" (OS-aware)
path.join("a", "..", "b") -- "b"
path.absolute("rel/path") -- Full absolute path
path.relative("/abs/path", "/base") -- Relative path from base
path.basename("foo/bar.cpp") -- "bar.cpp"
path.filename("foo/bar.cpp") -- "bar"
path.extension("foo/bar.cpp") -- ".cpp"
path.directory("foo/bar.cpp") -- "foo"
path.normalize("a/./b/../c") -- "a/c"
1.5 string — String Operations
All standard Lua string functions are available. Extended functions:
string.vformat("$(var) hello", ...) -- Format with built-in variables
string.format("hello %s", "world") -- Standard Lua format
-- All standard: sub, gsub, find, match, gmatch, upper, lower, rep, reverse, char, byte, len, split
Built-in variables (resolved in strings via $() or vformat):
| Variable | Description |
|---|---|
$(host) |
Host OS (windows, linux, macosx) |
$(tmpdir) |
Temp directory |
$(curdir) |
Current directory |
$(scriptdir) |
Directory of the current xmake.lua |
$(projectdir) |
Project root directory |
$(buildir) |
Build output directory |
$(globaldir) |
Global xmake directory |
$(programdir) |
xmake installation directory |
Example:
path.join("$(projectdir)", "build") -- Resolves to /path/to/project/build
print("$(scriptdir)") -- Prints script directory
1.6 table — Table Operations
table.join(t1, t2) -- Merge tables (new table)
table.join2(t1, t2) -- Merge into t1 (in-place)
table.clone(t) -- Deep clone
table.wrap(v) -- Wrap single value as table {v}
table.unwrap({v}) -- Unwrap table to single value
table.contains(t, value) -- Check if value exists
table.unique(t) -- Remove duplicates
table.reverse(t) -- Reverse array
table.slice(t, first, last) -- Slice array
table.is_array(t) -- Is array-like?
table.is_dictionary(t) -- Is dict-like?
table.keys(t) -- Get keys array
table.values(t) -- Get values array
table.pack(...) -- Pack arguments (like {...} but with .n)
table.map(t, mapper) -- Map values
table.imap(t, mapper) -- In-place map
table.find(t, value) -- Find index of value
table.find_if(t, pred) -- Find if predicate matches
table.remove_if(t, pred) -- Remove if predicate matches
table.empty(t) -- Is empty?
table.orderkeys(t, callback) -- Ordered keys
table.orderpairs(t, callback) -- Ordered pairs iterator
table.inherit(...) -- Prototype-based inheritance
1.7 os — Operating System
File/Directory Operations
os.cp("src/file", "dst/file") -- Copy file/dir
os.mv("src/file", "dst/file") -- Move file/dir
os.rm("file_or_dir") -- Remove file/dir
os.ln("target", "symlink") -- Create symlink
os.mkdir("dir") -- Create directory
os.rmdir("dir") -- Remove directory
os.cd("dir") -- Change directory (returns old cwd)
os.touch("file") -- Touch file
os.isfile("path") -- Is file?
os.isdir("path") -- Is directory?
os.islink("path") -- Is symlink?
os.isexec("path") -- Is executable?
os.exists("path") -- Exists?
os.readlink("symlink") -- Read symlink target
os.filesize("file") -- File size
os.mtime("file") -- Modification time
File Matching (Globbing)
os.files("src/*.cpp") -- Match .cpp files
os.dirs("src/*") -- Match directories
os.filedirs("src/*") -- Match files and dirs
os.match("src/*.c", "file") -- Match with mode ("file", "dir", "alldir")
Running Commands
-- Run command, raise on failure
os.run("gcc -c %s -o %s", "file.c", "file.o")
os.runv("gcc", {"-c", "file.c", "-o", "file.o"})
-- Run command, return output
local out, err = os.iorun("echo hello")
local out, err = os.iorunv("python", {"--version"})
-- Run command, capture exit code
local exitok, errors = os.exec("ls")
local exitok, errors = os.execv("python", {"script.py"})
-- Verbose variants (print command if -v enabled)
os.vrun("gcc %s", "file.c")
os.vrunv("gcc", {"-c", "file.c"})
os.vexec("echo hello")
os.vexecv("echo", {"hello"})
-- Try variants (no raise on failure)
os.trycp("src", "dst")
os.trymv("src", "dst")
os.tryrm("file")
Environment Variables
os.getenv("PATH") -- Get env var
os.setenv("MY_VAR", "value") -- Set env var (override)
os.addenv("PATH", "/new/path") -- Append to env var
os.getenvs() -- Get all env vars
os.setenvs({PATH = "/usr/bin"}) -- Set multiple env vars
os.addenvs({PATH = "/new/path"}) -- Append multiple env vars
os.joinenvs({PATH = "/a:/b"}) -- Join env values
Directory/System Info
os.curdir() -- Current directory
os.scriptdir() -- Directory of current xmake.lua
os.projectdir() -- Project root directory
os.tmpdir() -- System temp directory
os.tmpfile("key") -- Generate temp file path
os.host() -- Host OS name
os.arch() -- Host architecture
os.subhost() -- Subsystem host
os.subarch() -- Subsystem arch
os.is_host("windows") -- Check host OS
os.is_arch("x64") -- Check host arch
os.is_subhost("msys") -- Check subsystem
os.isroot() -- Is running as root?
os.fscase() -- Is filesystem case-sensitive?
os.mclock() -- CPU clock (ms)
os.sleep(1000) -- Sleep ms (coroutine-safe)
os.nuldev() -- Null device path
os.xmakever() -- xmake version (semver)
os.args({"-a", "-b"}) -- Format args array to string
os.getpid() -- Current process ID
os.cpuinfo() -- CPU info table
os.meminfo() -- Memory info table
1.8 io — File I/O
-- Read/write entire files
local data = io.readfile("path") -- Read all text
io.writefile("path", "content") -- Write text
local obj = io.load("data.json") -- Load serialized object (JSON/Lua)
io.save("data.json", obj) -- Save serialized object
-- Open file handle
local f = io.open("file.txt", "r") -- "r" read, "w" write, "a" append
f:read("*a") -- Read all
f:read("*l") -- Read line
f:read(n) -- Read n bytes
f:write("data") -- Write
f:print("format %s", "arg") -- Write formatted with newline
f:printf("format %s", "arg") -- Write formatted without newline
f:close() -- Close
f:flush() -- Flush
f:seek("set", 0) -- Seek
f:size() -- File size
f:load() -- Load serialized object from file
f:save(obj) -- Save serialized object to file
-- Text replacement
io.gsub("file", "pattern", "replace") -- Global replace in file
io.replace("file", "pattern", "rep") -- Replace occurrences
io.insert("file", lineidx, "text") -- Insert text at line
-- Cat/Tail
io.cat("file", 10) -- Print first 10 lines
io.tail("file", 10) -- Print last 10 lines
-- stdin/stdout/stderr
io.stdin:read("*l") -- Read from stdin
io.stdout:write("hello") -- Write to stdout
io.stderr:write("error") -- Write to stderr
io.write("hello") -- Shortcut for stdout:write
io.print("hello") -- Write string to file
io.flush() -- Flush stdout
1.9 hash — Hashing
hash.md5("data") -- MD5 hash
hash.md5("filepath") -- MD5 of file
hash.sha1("data") -- SHA1
hash.sha256("data") -- SHA256
hash.xxhash32("data") -- xxHash32
hash.xxhash64("data") -- xxHash64
hash.xxhash128("data") -- xxHash128
hash.uuid() -- Generate UUID v1
hash.uuid4() -- Generate UUID v4
hash.strhash32("str") -- String hash32
hash.strhash64("str") -- String hash64
hash.strhash128("str") -- String hash128
hash.rand32() -- Random 32-bit
hash.rand64() -- Random 64-bit
hash.rand128() -- Random 128-bit
1.10 xmake — xmake Runtime Info
xmake.arch() -- xmake architecture
xmake.version() -- xmake version string
xmake.branch() -- xmake git branch
xmake.programdir() -- xmake installation dir
xmake.programfile() -- xmake executable path
xmake.luajit() -- Is running on LuaJIT?
xmake.is_embed() -- Is embedded xmake?
1.11 math — Standard Lua math
1.12 coroutine — Standard Lua coroutine
2. Variable Formatting (vformat)
xmake strings can contain built-in variables resolved with $(var) syntax:
print("$(projectdir)/build") -- Resolves to /path/to/project/build
print("$(scriptdir)/src") -- Resolves to /path/to/xmake.lua/src
print("$(buildir)/$(mode)") -- Resolves to build/debug etc.
All os.* functions, io.*, path.* and print functions automatically resolve $(var) in their string arguments.
3. Error Handling: try / catch / finally
local ok = try {
function()
-- Risky operation
local data = io.readfile("may_not_exist.txt")
if not data then
raise("file not found")
end
return data
end,
catch {
function(errors)
-- Handle error
print("caught:", errors)
-- errors is the error message string,
-- or a table {errors=..., stderr=..., stdout=...}
end
},
finally {
function(ok, result_or_errors)
-- Always runs (like Lua's __gc)
end
}
}
-- If try succeeds, returns the try function's return values
-- If catch is provided, errors are caught and execution continues
Short form (no catch):
local ok = try { function() return io.readfile("file") end }
raise — Throw an error
raise("something went wrong") -- String error
raise({errors = "msg", stderr = "..."}) -- Table error (for command failures)
assert — from utils
utils.assert(io.readfile("f"), "cannot read file")
-- Raises if first argument is falsy
4. Module Import System (import)
xmake provides a module system for importing extension modules from the modules/ directory.
import("core.project.depend")
import("lib.detect.find_tool")
import("detect.sdks.find_cuda")
import("core.base.option")
-- Then use the module
local tool = find_tool("gcc")
local cuda = find_cuda()
local opt = option.get("verbose")
Common extension modules
| Module | Description |
|---|---|
lib.detect.find_tool |
Find a system tool/executable |
lib.detect.find_file |
Find a file in search paths |
lib.detect.find_library |
Find a library (name + paths) |
lib.detect.find_package |
Find a package (pkg-config, builtin detectors) |
lib.detect.find_program |
Find a program in PATH |
detect.sdks.find_cuda |
Find CUDA SDK |
detect.sdks.find_ndk |
Find Android NDK |
detect.packages.find_openssl |
Find OpenSSL |
detect.packages.find_zlib |
Find zlib |
core.project.config |
Access project configuration |
core.project.depend |
Dependency/file change tracking |
core.project.option |
Access option definitions |
core.base.option |
Access command-line options |
core.base.global |
Access global configuration |
core.base.task |
Run xmake tasks programmatically |
core.ui.* |
Terminal UI components |
core.language.language |
Language extension registration |
utils.archive.* |
Archive (.tar, .zip) extraction |
net.* |
Network/HTTP utilities |
devel.git.* |
Git operations |
async.runjobs |
Parallel job execution |
async.jobgraph |
Job dependency graph |
Example:
import("lib.detect.find_tool")
on_load(function(target)
local gcc = find_tool("gcc")
if gcc then
print("found gcc at", gcc.program)
end
end)
find_package — shortcut
-- Find a single package
local pkg = find_package("openssl", {required = false})
if pkg then
target:add("links", pkg.links)
target:add("linkdirs", pkg.linkdirs)
target:add("includedirs", pkg.includedirs)
end
-- Find multiple packages
local packages = find_packages("openssl", "zlib", "curl")
5. Compiler/Detect Libraries
lib.detect.find_tool
import("lib.detect.find_tool")
local tool = find_tool("clang", {version = true})
-- Returns: {program = "/usr/bin/clang", version = "15.0.0"}
lib.detect.find_package
import("lib.detect.find_package")
local pkg = find_package("openssl", {
paths = {"/usr/local/opt/openssl"},
required = false, -- Don't error if not found
})
-- Returns: {links = {"ssl", "crypto"}, linkdirs = {...}, includedirs = {...}}
lib.detect.find_file
import("lib.detect.find_file")
local header = find_file("python.h", {"/usr/include", "/usr/local/include"})
6. Build Batch Commands
Inside on_buildcmd_file or rule scripts, you can use batch commands:
on_buildcmd_file(function(target, batchcmds, sourcefile)
batchcmds:show("compiling %s", sourcefile)
batchcmds:vrun("gcc -c %s", sourcefile)
batchcmds:cp("src.txt", "dst.txt")
end)
7. Private Target Data
Store and retrieve arbitrary data on a target:
on_load(function(target)
target:data_set("mykey", {some = "data"})
end)
after_build(function(target)
local data = target:data("mykey")
print(data.some) -- "data"
end)
8. Getting Configuration
get_config("lc_enable_dsl") -- Get config option value
get_config("my_option") -- Get any config value
has_config("lc_enable_dsl") -- Boolean check
has_package("spdlog") -- Check if package is available
9. Complete Scripting Example
import("lib.detect.find_tool")
import("core.base.option")
target("my-scripted-target")
set_kind("binary")
add_files("src/*.cpp")
on_load(function(target)
-- Print build info
local verbose = option.get("verbose")
if verbose then
print("Building for:", target:plat(), target:arch())
end
-- Find a tool
local clang = find_tool("clang")
if clang then
print("using clang:", clang.program)
end
-- Dynamic files based on platform
if target:is_plat("windows") then
target:add("files", "src/*.win.cpp")
target:add("syslinks", "Advapi32")
end
-- Store data for later use
target:data_set("build_time", os.time())
end)
before_build(function(target)
-- Pre-build validation
if not os.exists("src/main.cpp") then
raise("main.cpp not found")
end
end)
after_build(function(target)
-- Post-build: copy output
local target_file = target:targetfile()
if os.isfile(target_file) then
os.cp(target_file, path.join("$(projectdir)", "dist"))
print("copied to dist/")
end
-- Report build time
local start = target:data("build_time")
local elapsed = os.mclock() - start
print("build completed in", elapsed, "ms")
end)
target_end()
Summary
| Module | Key APIs |
|---|---|
os |
cp, mv, rm, mkdir, run, exec, iorun, files, dirs, isfile, exists, getenv, setenv, host, arch, sleep, cd, scriptdir, projectdir |
io |
readfile, writefile, load, save, open, gsub, replace, cat, tail, stdin, stdout, stderr |
path |
join, absolute, relative, basename, filename, extension, directory, normalize |
table |
join, join2, clone, wrap, unwrap, contains, unique, keys, values, map, find, empty |
string |
all Lua standard + vformat, format |
utils |
dump, assert, error, warning, trycall |
hash |
md5, sha1, sha256, uuid, uuid4, rand32, rand64, xxhash32/64/128 |
xmake |
version, arch, branch, programdir, luajit |
print |
print, printf, cprint, cprintf, vprint, dprint |
| try/catch | try, catch, finally, raise |
import |
Load extension modules from modules/ |
find_package |
Find system packages (pkg-config, builtin detectors) |
find_packages |
Find multiple packages at once |
get_config / has_config |
Access configuration options |