XMake Build System
SkillDev toolsXMake build configuration, options, commands, and patterns for LuisaCompute.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the XMake Build System skill
What this skill tells your AI
The instructions your AI receives, as published by luisagroup/luisacompute in .agents/skills/xmake/SKILL.md and read by ahel’s review.
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)
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 1k
- Forks
- 108
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
xmake- Source
- github.com/luisagroup/luisacompute