Skip to content
cccskills
FreePublished on GitHub

xmake

XMake build configuration, options, commands, and patterns for LuisaCompute.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md61.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

XMake Build System

Primary build system. Requires XMake 3.0.6+ (set_xmakever("3.0.6") in root xmake.lua). Optional: CUDA Toolkit, LLVM 22 (--lc_llvm_path=, used by lc_enable_clangcxx/lc_tvm_llvm), and Embree (--lc_embree_path=). The DX/VK shader-compiler SDKs are downloaded automatically into SDKs/ by the lc_backend_sdk target (scripts/find_sdk.lua; override the directory with --lc_sdk_dir=, an empty string disables installation).

Quick Start

xmake f -m debug -c -y
# explicit plat/arch (the form AGENTS.md prescribes):
xmake f -p windows -a x64 -m debug -c
xmake build              # build all default targets
xmake build <target>     # one target, e.g. lc-runtime, test_dsl, example_path_tracing
xmake run <target> <args>
# Update compile_commands.json:
xmake project -k compile_commands --lsp=clangd .vscode

Configuration

PlatformCommand
Linux GCCxmake f -p linux -a x86_64 --toolchain=gcc -m release -c
Linux Clangxmake f -p linux -a x86_64 --toolchain=clang -m release -c
Windows MSVCxmake f -p windows -a x64 --toolchain=msvc -m release -c
Windows Clang-CLxmake f -p windows -a x64 --toolchain=clang-cl -m release -c
Windows LLVMxmake f -p windows -a x64 --toolchain=llvm -m release -c
macOS Clangxmake f -p macosx -a arm64 --toolchain=clang -m release -c

Flags

-c clean the cached user configs and the detection cache, -m <mode> (release/debug/releasedbg — the only modes this project declares, see add_rules("mode.release", "mode.debug", "mode.releasedbg") in root xmake.lua), -p <plat> (linux/windows/macosx/android), -a <arch> (x86_64/x64/arm64 — the values _lc_check_env accepts, scripts/xmake_func.lua), --check drop the detection cache and re-check everything while keeping the cached user configs, -y auto-accept all prompts and skip interaction (useful in scripts/CI).

debug does not enable sanitizers here: xmake's mode.debug rule only sets symbols = "debug" and optimize = "none" (-G for CUDA), and this project adds /GS /Gd plus the debug CRT (MDd) in lc_basic_settings. To build with ASan, set the policy explicitly: xmake f -m debug --policies=build.sanitizer.address -c.

Project Options

Every option is declared as option("lc_*") in root xmake.lua — including the internal lc_scripts_path and lc_ext_path (defaults below are the {default = ...} values there); scripts/xmake_func.lua adds the underscore-prefixed _lc_check_env, _lc_bin_dir and _lc_enable_py options. There are no other valid option names — pass them as --lc_name=value.

OptionDefaultMeaning
lc_dx_backend, lc_vk_backend, lc_cuda_backend, lc_metal_backendtrueGPU backends → targets lc-backend-dx, lc-backend-vk, lc-backend-cuda, lc-backend-metal
lc_fallback_backendfalseCPU fallback backend lc-backend-fallback; needs lc_llvm_path + lc_embree_path
lc_enable_xirtrueXIR support; also pulls in lc-coro (src/xmake.lua) and gates lc_vk_backend_use_xir_spirv
lc_vk_backend_use_xir_spirvfalsenative XIR→SPIR-V codegen for Vulkan (the CMake side, LUISA_COMPUTE_ENABLE_VK_XIR_SPIRV, defaults ON)
lc_vk_backend_use_ast_llvm_spirvfalseexperimental AST→LLVM→SPIR-V route; requires lc_llvm_path, mutually exclusive with the row above
lc_vk_backend_enable_dxc_compatibilitytruekeep the legacy runtime DXC route for unsupported Vulkan shader features
lc_vk_min_api_version"auto"Vulkan API floor for device negotiation (auto, 1.2, 1.3)
lc_vk_bindless_heap_capacity262144requested update-after-bind heap capacity (0 disables bindless)
lc_cuda_ext_lcubfalseCUDA CUB extension (lc-compute-cuda-ext-lcub), off by default due to compile time
lc_dx_cuda_interop, lc_vk_cuda_interopfalseauto-enabled when both sides of the interop are enabled
lc_enable_dsl, lc_enable_gui, lc_enable_imgui, lc_enable_osl, lc_enable_py, lc_enable_teststruemodule switches; lc_enable_clangcxx defaults to false
lc_enable_simdtrueavx+avx2 (x64) / neon vectorexts applied by lc_basic_settings
lc_enable_unity_build, lc_enable_pchtruecompile-speed rules (c/c++.unity_build, lc_set_pcxxheader)
lc_check_unity_buildfalseadds the phony target lc-check-unity-build as a dependency of lc-core, so every build runs scripts/check_unity_build_conflicts.py and fails when two files of one unity batch declare the same file-local symbol
lc_enable_mimalloctrueswitches bundled EASTL (and SPIRV-Tools) onto the mimalloc override (src/ext/xmake.lua)
lc_enable_custom_mallocfalsecustom allocator instead of the system allocator
lc_external_marlfalseexternal marl instead of the bundled src/ext/marl
lc_use_ltofalsedeclared in xmake.lua, but lc_basic_settings reads the lto key (_get_or("lto") → lc_lto), so --lc_use_lto=true alone changes nothing; set LTO per target with _config_project({lto = true}), which applies the build.optimization.lto policy
lc_rtti, lc_safe_modefalseRTTI (off: -fno-rtti / /GR-), runtime safe mode
lc_cxx_standard, lc_c_standard'cxx20', 'clatest'language standards applied by lc_basic_settings
lc_bin_dir"bin"output directory root; the mode is appended (bin/debug)
lc_win_runtime, lc_optimizefalseoverride the Windows CRT (MD/MT/…) and the optimize setting
lc_toolchainfalsetoolchain applied to every target (target:set("toolchains", ...))
lc_sdk_dirfalseSDK download directory; an empty string disables SDK installation
lc_llvm_path, lc_embree_path, lc_ndkfalseexternal LLVM / Embree / Android NDK locations
lc_py_include, lc_py_linkdir, lc_py_libsfalsemanual Python binding locations (needed for lc_enable_py to take effect)
lc_use_system_stlfalsedefines LUISA_USE_SYSTEM_STL (system STL instead of bundled EASTL)
lc_spdlog_use_xrepo, lc_reproc_use_xrepo, lc_lmdb_use_xrepo, lc_imgui_use_xrepo, lc_glfw_use_xrepo, lc_yyjson_use_xrepofalseuse xmake-repo packages instead of the bundled src/ext/* submodules
lc_disable_win_message_boxtruedefines LUISA_DISABLE_WIN_MESSAGE_BOX (Windows, debug mode)
lc_tile_tirx_bridge, lc_tvm_stack, lc_tvm_llvmfalseTileIR→TVM TIRx bridge, bundled TVM stack, TVM LLVM codegen

scripts/options.lua holds the per-machine defaults: it assigns lc_options = { toolchain = "...", lc_<name> = <value>, ... } and root xmake.lua applies each entry with set_config(k, v) before the targets are loaded, so command-line --lc_*= values win over the file. Generate a starter file with xmake lua scripts/write_options.lua.

Internal options and target names

_lc_check_env (scripts/xmake_func.lua) validates -a/-m and gates the includes("src")/examples/tutorials at the bottom of root xmake.lua; _lc_bin_dir collects the mode-suffixed output directory. Library/target names to build against are lc-core, lc-runtime, lc-dsl, lc-vstl, lc-tile, lc-gui, lc-coro, lc-osl, lc-clangcxx, lcapi, the lc-backend-* targets, lc-backends-dummy (phony, depends on the enabled backends) and lc-validation-layer.

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.msan/mode.lsan/mode.ubsan rules.

ASan in debug mode

-m debug on its own produces an unsanitized build (xmake's mode.debug rule only sets symbols/optimize), and the legacy -m asan mode is rejected here because _lc_check_env accepts only debug/release/releasedbg (scripts/xmake_func.lua). Combine debug with the address-sanitizer policy instead:

xmake f -m debug --policies=build.sanitizer.address -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:

PolicySanitizer
build.sanitizer.addressAddressSanitizer
build.sanitizer.threadThreadSanitizer
build.sanitizer.memoryMemorySanitizer
build.sanitizer.leakLeakSanitizer
build.sanitizer.undefinedUndefinedBehaviorSanitizer

Multiple sanitizers can be combined, e.g.:

xmake f --policies=build.sanitizer.address,build.sanitizer.undefined -c -y

Commands

CommandDescription
xmake cleanClean
xmake -rRebuild
xmake build <target>Build target
xmake run <target>Run target
xmake run <target> <args>Run target with arguments
xmake -aBuild all targets, not only the default ones (no -l/list-targets command exists; target names come from target("...") in the xmake.lua files)
xmake lua <script.lua>Run a Lua script inside the xmake sandbox, e.g. xmake lua scripts/write_options.lua
xmake install -o <dir>Install binaries to <dir>
xmake -yAuto-accept all prompts (downloads, overwrites, etc.), skip interaction
xmake project -k compile_commands --lsp=clangd .vscodeGenerate compile_commands.json

Common Issues

  • -v/--verbose (verbose user output) and -D/--diagnosis (developer diagnostics) are both valid xmake flags and can be combined (xmake -vD); xmake --verbose is simply the long form of -v
  • Boolean options: --lc_option=true/=false; value options: --lc_option=<value> (e.g. --lc_cxx_standard=cxx23, --lc_bin_dir=out)
  • Use -c to clean cache when reconfiguring with different options
  • Use -y to auto-accept all prompts and skip interaction — essential in automated scripts and CI pipelines
  • lc_fallback_backend requires both lc_llvm_path and lc_embree_path
  • lc_dx_backend is silently disabled on non-Windows platforms
  • lc_metal_backend is silently disabled on non-macOS platforms
  • lc_cuda_backend is silently disabled outside Windows/Linux
  • Unity (jumbo) build collision (redefinition of ..., use of undeclared identifier, ambiguous name): xmake merges batch_size translation units of a target (see _config_project{batch_size = ...}) into one, so file-local helpers of two files in the same batch share a scope. python scripts/check_unity_build_conflicts.py (or xmake build lc-check-unity-build, always with --lc_check_unity_build=true) lists them; fix by moving each file's helpers into a file-unique namespace <file>_detail with scripts/unity_isolate_locals.py.
  • PCH (precompiled header) error like has been modified since the precompiled header / redefinition of ... means the target's PCH is stale — use xmake build -r <target> to force a clean rebuild of that target. PCH is emitted by lc_set_pcxxheader() (root xmake.lua), which applies it only while lc_enable_pch is on.
  • lc_vk_backend_use_xir_spirv and lc_vk_backend_use_ast_llvm_spirv are mutually exclusive: enabling both raises at configure time (root xmake.lua). lc_vk_backend_use_ast_llvm_spirv also requires lc_llvm_path and force-disables the XIR→SPIR-V option (scripts/xmake_func.lua).
  • lc_enable_tests=false makes the test_*, benchmark_*, example_* and tutorial_* targets disappear — src/tests, examples and tutorials are only includes()-ed when it is enabled (src/xmake.lua, root xmake.lua). They are created by the local helpers test_proj(...), example_proj(...) and tutorial_proj(...).
  • Dependent options are force-applied at configure time (scripts/xmake_func.lua): lc_enable_tests forces lc_enable_dsl on; lc_fallback_backend or lc_llvm_path force lc_enable_xir on; Python bindings only activate when lc_enable_py and a non-empty lc_py_include are set; lc_dx_cuda_interop/lc_vk_cuda_interop are auto-enabled when both sides are present.
  • Windows builds need the Windows SDK ≥ 10.0.22000.0; the phony target lc-check-winsdk (scripts/xmake_func.lua) raises otherwise.
  • -p android requires the NDK: pass --lc_ndk=<dir> (auto-detected with detect.sdks.find_ndk when unset).
  • Binaries land in <lc_bin_dir>/<mode> — default bin/debug, bin/release or bin/releasedbg (lc_bin_dir defaults to bin).

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 outside on_load — they are target-level; cannot be set from inside on_load.
  • add_deps() outside = target:add("deps", ...) inside — they are equivalent.
  • Simple globs (add_files, add_headerfiles) can go outside; conditional additions go inside on_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:

CategoryFields
Identitykind, basename, filename, prefixname, suffixname, extension, group
Outputtargetdir, objectdir, dependir, rundir, installdir, prefixdir
Sourcesfiles, headerfiles, extrafiles, remove_files, configfiles, installfiles
Includesincludedirs, sysincludedirs
Definesdefines, undefines, configvar
Linkslinks, syslinks, linkdirs, rpathdirs, linkorders, linkgroups, frameworks, frameworkdirs
Compilationlanguages, optimize, warnings, symbols, runtimes, exceptions, fpmodels, encodings, strip, vectorexts, forceincludes, pcheader, pcxxheader
Dependenciesdeps, packages, options, rules
Miscvalues.*, runenv, runargs, enabled, default, toolchains, toolset, plat, arch, policy

Rules for config fields:

  1. Most config fields can be set either outside or inside on_load using the equivalent target:set("field", value) / target:add("field", value) form.
  2. add_rules() must be outside on_load — rules are target-level metadata and cannot be added from inside a script field.
  3. Static config goes outside for readability; dynamic/conditional config goes inside on_load.
  4. Use {public = true} / {interface = true} / {private = true} with target:add() to control inheritance of includedirs, defines, links, etc.
  5. 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 fieldRuns whenTypical use
on_load(function(target) ... end)Target is loaded (early)Dynamic config, conditional deps/files
on_config(function(target) ... end)After xmake config, before buildValidate toolchain/options
before_build(function(target) ... end)Before compilation startsPre-build checks/code generation
on_build(function(target) ... end)Build phaseOverride entire build
after_build(function(target) ... end)After build finishesCopy outputs, print reports
before_link(function(target) ... end)Before linkingInject link args
after_link(function(target) ... end)After linkingSign/post-process binary
on_install(function(target) ... end)Install phaseCustom install logic
on_run(function(target) ... end)xmake runOverride run behavior

Rules for writing code in script fields:

  1. Always operate on the target argument for target-local config: target:add("field", value), target:set("field", value), target:get("field").
  2. Project-scope helpers are still available: is_plat(), is_arch(), is_mode(), has_config(), get_config(), os.*, io.*, path.*, etc.
  3. You can import() extension modules at the top of the script field callback (or at file scope).
  4. on_load is for configuration — it should set/add target config fields. It runs very early, so dependencies may not be fully resolved yet.
  5. before_build / after_build are for actions — they run around compilation and are the right place to generate files, copy DLLs, run validators, or emit summaries.
  6. Returning false from 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 (like files, deps, kind) have special handling.


4. Compilation Flags (by Language)

These APIs pass compiler-specific flags:

APIDescription
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_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

APIDescription
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("lc_enable_pch") then
    set_pcxxheader("mypch.hpp")
end

This project wraps exactly that check in lc_set_pcxxheader(...) (root xmake.lua), which only forwards to set_pcxxheader(...) while lc_enable_pch is on — use it instead of set_pcxxheader in src/**/xmake.lua.


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 os.is_subarch(...) then ... end -- Subsystem architecture (only as `os.is_subarch`, there is no bare `is_subarch`)
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("lc_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("lc-runtime", "lc-dsl")
add_files("main.cpp")
add_includedirs("include")

on_load(function(target)
 if has_config("lc_enable_gui") then
 target:add("deps", "lc-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("lc-runtime", "lc-dsl", "lc-vstl")
 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)

The real helper is local function test_proj(name, source, gui_dep, callable, kind, cxx_standard) in src/tests/xmake.lua; it always adds lc-runtime, lc-dsl, lc-vstl, stb-image and lc-backends-dummy (with {inherit = false, links = false}), calls _config_project({...}) for the mode/standard settings, and skips the target when gui_dep is set but lc_enable_gui is off. Test target names are test_* and benchmark names benchmark_* (most created through that helper; a few, e.g. test_reproc_win32_contract, are plain target(...) blocks), so xmake build test_dsl / xmake run benchmark_tile_native work directly.

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("lc_llvm") -- LLVM integration
target_end()

The rules this project defines (use these names, they are not xmake built-ins):

RuleDefined inWhat it does
lc_basic_settingsscripts/xmake_func.luaapplied to every target through _config_project: toolchain, project_kind, c_standard/cxx_standard, exceptions, optimize/warnings/runtimes per mode, -mfma, /Zc:preprocessor, SIMD vectorexts, and the lto/rtti settings (each read by _get_or("<key>"): rule extraconf first, then the lc_<key> config)
lc-rename-extscripts/xmake_func.luaprefixes the output basename with luisa-ext-
lc_install_sdkscripts/xmake_func.lua (+ scripts/find_sdk.lua)downloads/extracts SDK zips (dx_sdk, vk_sdk) before build; takes {sdk_dir = ..., libnames = ...}
lc_llvmscripts/xmake_func.lualinks against the lc_llvm_path LLVM install and copies its DLLs on Windows
lc_run_targetscripts/xmake_func.luaon_run that execs the built binary with the target directory as cwd
lc-backend-depssrc/backends/xmake.luapulls in the enabled lc-backend-* targets plus lc_backend_sdk and lc-validation-layer

_config_project({project_kind = ..., batch_size = ..., ...}) (scripts/xmake_func.lua) is the wrapper used by nearly every xmake.lua here: it adds c.unity_build/c++.unity_build when batch_size > 1 (unless lc_enable_unity_build is off) and then add_rules(_config_rules, config) with _config_rules = {"lc_basic_settings"}.

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:

MethodDescription
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?
is_mode("debug")Build mode — a global function (D:/xmake/core/project/project.lua), not a target method; there is no target: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
is_kind("static")Target kind check — a global function, not a target method (on a target use target:kind() or target:is_binary()/is_shared()/is_static()/is_object()/is_headeronly()/is_moduleonly()/is_phony())
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", "mode.releasedbg") -- as in this project's xmake.lua
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("lc_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("lc_spdlog_use_xrepo") 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

  1. Use on_load for conditional logic — platform checks, feature flags, dynamic file lists.
  2. add_rules() stays outside — cannot be set from inside on_load.
  3. Simple globs outside, conditional additions inside — keep add_files/add_headerfiles outside for simple cases.
  4. Prefer target:set() / target:add() inside on_load for most configuration — it's equivalent to outside calls.
  5. Visibility — {public = true} propagates to dependents, {interface = true} propagates only to dependents, {private = true} (default) is local-only.
  6. add_deps() outside = target:add("deps", ...) inside — choose whichever fits your style.
  7. All APIs listed here work at the target scope level — use them outside on_load as set_kind(...) or inside as target: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):

VariableDescription
$(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

ModuleDescription
lib.detect.find_toolFind a system tool/executable
lib.detect.find_fileFind a file in search paths
lib.detect.find_libraryFind a library (name + paths)
lib.detect.find_packageFind a package (pkg-config, builtin detectors)
lib.detect.find_programFind a program in PATH
detect.sdks.find_cudaFind CUDA SDK
detect.sdks.find_ndkFind Android NDK
detect.packages.find_opensslFind OpenSSL
detect.packages.find_zlibFind zlib
core.project.configAccess project configuration
core.project.dependDependency/file change tracking
core.project.optionAccess option definitions
core.base.optionAccess command-line options
core.base.globalAccess global configuration
core.base.taskRun xmake tasks programmatically
core.ui.*Terminal UI components
core.language.languageLanguage extension registration
utils.archive.*Archive (.tar, .zip) extraction
net.*Network/HTTP utilities
devel.git.*Git operations
async.runjobsParallel job execution
async.jobgraphJob 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

ModuleKey APIs
oscp, mv, rm, mkdir, run, exec, iorun, files, dirs, isfile, exists, getenv, setenv, host, arch, sleep, cd, scriptdir, projectdir
ioreadfile, writefile, load, save, open, gsub, replace, cat, tail, stdin, stdout, stderr
pathjoin, absolute, relative, basename, filename, extension, directory, normalize
tablejoin, join2, clone, wrap, unwrap, contains, unique, keys, values, map, find, empty
stringall Lua standard + vformat, format
utilsdump, assert, error, warning, trycall
hashmd5, sha1, sha256, uuid, uuid4, rand32, rand64, xxhash32/64/128
xmakeversion, arch, branch, programdir, luajit
printprint, printf, cprint, cprintf, vprint, dprint
try/catchtry, catch, finally, raise
importLoad extension modules from modules/
find_packageFind system packages (pkg-config, builtin detectors)
find_packagesFind multiple packages at once
get_config / has_configAccess configuration options

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

ast

Free

Manual AST construction with FunctionBuilder for kernels and callables without DSL sugar.

The English summary is being prepared. Showing the original description.

LuisaGroup/LuisaCompute1.1KUpdated Oct 20, 2026

Backend plugin architecture, DeviceInterface, dynamic loading, and command encoding.

The English summary is being prepared. Showing the original description.

LuisaGroup/LuisaCompute1.1KUpdated Oct 20, 2026

cmake

Free

CMake build options, custom functions, and backend patterns for LuisaCompute.

The English summary is being prepared. Showing the original description.

LuisaGroup/LuisaCompute1.1KUpdated Oct 20, 2026

C++ naming, formatting, static analysis, and RTTI rules for LuisaCompute.

The English summary is being prepared. Showing the original description.

LuisaGroup/LuisaCompute1.1KUpdated Oct 20, 2026

debug

Free

Debug crashes and test failures via stack-traces, host/device logging, DSL buffer inspection, and add_debug_checks kernel restrict-mode guards.

The English summary is being prepared. Showing the original description.

LuisaGroup/LuisaCompute1.1KUpdated Oct 20, 2026

git

Free

Show uncommitted changes and commit history via git. Use when: (1) inspecting what changed in a file before committing, (2) checking diff of tracked/new/deleted files, (3) reviewing a specific commit's changes, (4) listing files changed in a commit.

The English summary is being prepared. Showing the original description.

LuisaGroup/LuisaCompute1.1KUpdated Oct 20, 2026

See all skills by LuisaGroup

Report a problem with this skill