Skip to content
C++
Build System
Updated 2026-07-14T00:00:00.000Z

xmake — Lua-Powered Build System

xmake reference for C++ developers: xmake.lua syntax, package management via xrepo, cross-compilation, and project templates.

TL;DR

xmake is a Lua-based build system with a built-in package manager (xrepo). It requires zero external dependencies, builds fast on all three major platforms, and uses a syntax that is significantly cleaner than CMake's DSL. The trade-off is a smaller ecosystem than CMake or Meson.

  • Language: Lua (you write xmake.lua, not CMakeLists.txt)
  • Package manager: xrepo — pulls from its own registry, Conan, vcpkg, or system packages
  • Backends: generates for Ninja, Make, VS, Xcode — or builds directly via its own runner
  • No runtime deps: the xmake binary is self-contained; CI machines need no pre-installed tooling

Installation

macOS

bash
brew install xmake

Linux (official installer)

bash
curl -fsSL https://xmake.io/shget.text | bash
# Or with wget:
wget https://xmake.io/shget.text -O - | bash

The installer places the binary in ~/.local/bin. Add it to PATH if your shell does not pick it up automatically.

Windows

powershell
winget install xmake-io.xmake

Alternatively, download the .exe installer from xmake.io. A Scoop formula is also available:

powershell
scoop install xmake

Docker

dockerfile
FROM tboox/xmake:latest
WORKDIR /src
COPY . .
RUN xmake f -m release && xmake

Verify

bash
xmake --version
# xmake v2.9.x, ...

Project Structure

A minimal C++ project looks like this:

cpp
myapp/
├── xmake.lua
├── include/
│   └── myapp/
│       └── utils.h
└── src/
    ├── main.cpp
    └── utils.cpp

xmake requires only xmake.lua at the root. There is no separate configure step — running xmake in the project directory both configures and builds.


xmake.lua Basics

lua
-- xmake.lua
set_project("myapp")
set_version("1.0.0")

-- Register debug/release build modes
add_rules("mode.debug", "mode.release")

target("myapp")
    set_kind("binary")
    add_files("src/*.cpp")
    set_languages("c++20")
    add_includedirs("include")

Key points:

  • set_project and set_version are optional metadata.
  • add_rules("mode.debug", "mode.release") makes -m debug and -m release available on the command line.
  • add_files supports glob patterns; use remove_files to exclude specific sources.
  • set_languages accepts "c++11" through "c++23" and also C standards ("c11", "c17").

Building

bash
xmake               # build default target in current mode
xmake -r            # force rebuild (equivalent to clean + build)
xmake -j8           # build with 8 parallel jobs (default: auto)
xmake -v            # verbose: print every compiler invocation
xmake -vD           # very verbose: include diagnostic info

xmake f -m release  # switch to release mode
xmake f -m debug    # switch to debug mode
xmake               # rebuild after mode switch

xmake run myapp     # build and run the named target
xmake run myapp arg1 arg2  # pass arguments to the program

xmake clean         # remove build artefacts
xmake clean -a      # also remove downloaded package caches

Build output lands in .xmake/ (config cache) and build/ (compiled artefacts) by default.


Targets: binary, static, shared

lua
-- xmake.lua
set_project("myproject")
add_rules("mode.debug", "mode.release")

-- Static library
target("mylib")
    set_kind("static")          -- "shared" for a .so/.dll
    add_files("src/lib/*.cpp")
    add_headerfiles("include/(**.h)")  -- installs headers preserving subdir structure
    set_languages("c++20")
    add_includedirs("include", {public = true})  -- propagate to dependants

-- Executable that links the library
target("myapp")
    set_kind("binary")
    add_files("src/main.cpp")
    add_deps("mylib")           -- automatically links and inherits public includes
    set_languages("c++20")

The {public = true} modifier on add_includedirs (and similarly add_defines, add_cxxflags) propagates the setting to all targets that declare add_deps on this library — the same idea as CMake's INTERFACE/PUBLIC distinction.


Package Management with xrepo

xrepo is xmake's integrated package manager. Packages are declared at the top of xmake.lua and linked with add_packages inside a target.

lua
-- xmake.lua
add_requires("zlib", "fmt 10.2.1", "catch2")
add_requires("boost", {configs = {filesystem = true, system = true}})

target("myapp")
    set_kind("binary")
    add_files("src/*.cpp")
    set_languages("c++20")
    add_packages("zlib", "fmt", "boost")

target("tests")
    set_kind("binary")
    add_files("tests/*.cpp")
    set_languages("c++20")
    add_packages("catch2")
    add_deps("myapp")
    set_default(false)  -- not built unless explicitly requested

xrepo CLI

bash
xrepo install zlib          # install into the global package cache
xrepo install "fmt 10.2.1"  # pin a version
xrepo search boost          # search the registry
xrepo info fmt              # show package metadata, deps, configs
xrepo list                  # list installed packages
xrepo remove zlib           # uninstall

Using a private or local package

lua
-- Reference a package stored in a local directory
add_repositories("my-repo /path/to/packages")
add_requires("mypkg")

Pulling packages from vcpkg or Conan

lua
add_requires("vcpkg::sdl2")
add_requires("conan::nlohmann_json/3.11.2")

Compiler Flags and Options

lua
target("myapp")
    set_kind("binary")
    add_files("src/*.cpp")
    set_languages("c++23")

    -- Warnings
    add_cxxflags("-Wall", "-Wextra", "-Wpedantic", {force = true})

    -- Per-mode configuration
    if is_mode("release") then
        set_optimize("fastest")   -- -O3
        set_strip("all")          -- strip debug symbols from binary
        add_defines("NDEBUG")
    end

    if is_mode("debug") then
        set_symbols("debug")      -- -g
        set_optimize("none")      -- -O0
        add_defines("DEBUG")
    end

    -- Platform-specific flags
    if is_plat("linux") then
        add_syslinks("pthread", "dl")
    end
    if is_plat("windows") then
        add_defines("WIN32_LEAN_AND_MEAN", "NOMINMAX")
    end

add_cxxflags with {force = true} passes the flags directly even if xmake would normally deduplicate or ignore them. Use add_cflags for C sources, add_ldflags for the linker, and add_arflags for the archiver.


Conditional Logic and Options

xmake.lua is real Lua, so you can use variables, loops, and conditionals freely.

lua
-- User-configurable option (xmake f --enable_tests=y)
option("enable_tests")
    set_default(false)
    set_showmenu(true)
    set_description("Build test suite")
option_end()

if has_config("enable_tests") then
    add_requires("catch2")
    includes("tests/xmake.lua")   -- pull in a sub-build file
end

-- Detect a system library
if find_package("OpenSSL") then
    add_defines("HAVE_OPENSSL")
    add_packages("openssl")
end

Multi-file Projects with includes()

For larger projects, split build definitions into subdirectory xmake.lua files and compose them with includes.

cpp
myproject/
├── xmake.lua            ← root: set_project, add_requires, includes(...)
├── src/
│   └── xmake.lua        ← defines target("myapp")
├── lib/
│   └── xmake.lua        ← defines target("mylib")
└── tests/
    └── xmake.lua        ← defines target("tests")

Root xmake.lua:

lua
set_project("myproject")
set_version("2.0.0")
add_rules("mode.debug", "mode.release")
add_requires("fmt", "catch2")

includes("lib/xmake.lua")
includes("src/xmake.lua")
includes("tests/xmake.lua")

Cross-Compilation

xmake has first-class cross-compilation support. Configure with xmake f before building.

Android (NDK)

bash
xmake f -p android --ndk=/path/to/ndk -a arm64-v8a
xmake

Supported ABI values: armeabi-v7a, arm64-v8a, x86, x86_64.

iOS / tvOS

bash
xmake f -p iphoneos -a arm64
xmake f -p iphonesimulator -a x86_64
xmake

MinGW (Windows cross-compile from Linux)

bash
xmake f -p mingw --sdk=/path/to/mingw-w64
xmake

Custom sysroot / toolchain

lua
-- xmake.lua
target("firmware")
    set_kind("binary")
    add_files("src/*.cpp")
    set_toolchains("arm-linux-gnueabihf")
    add_cxxflags("--sysroot=/opt/arm-sysroot")
    add_ldflags("--sysroot=/opt/arm-sysroot")

Or via the command line:

bash
xmake f --toolchain=arm-linux-gnueabihf --sysroot=/opt/arm-sysroot

Testing Integration

lua
-- tests/xmake.lua
add_requires("catch2")

target("unit_tests")
    set_kind("binary")
    add_files("*.cpp")
    add_packages("catch2")
    add_deps("mylib")
    set_default(false)   -- excluded from a bare `xmake` run

    -- Register as a test target so `xmake test` picks it up
    on_run(function(target)
        os.execv(target:targetfile(), {"--reporter", "compact"})
    end)
bash
xmake build unit_tests    # build only the test target
xmake run unit_tests      # build + run
xmake test                # run all registered test targets
xmake test -v             # verbose output, shows individual test results

For GoogleTest:

lua
add_requires("gtest")

target("gtests")
    set_kind("binary")
    add_files("tests/*.cpp")
    add_packages("gtest")
    add_deps("mylib")
    set_default(false)

Generating Project Files for Other Tools

xmake can emit project files so that IDEs and other build systems can consume the same xmake.lua description.

bash
xmake project -k cmake          # generate CMakeLists.txt
xmake project -k ninja          # generate build.ninja
xmake project -k make           # generate Makefile
xmake project -k vs2022         # generate Visual Studio 2022 solution
xmake project -k xcode          # generate Xcode project
xmake project -k compile_commands  # generate compile_commands.json (for clangd)

The compile_commands.json target is the most useful for day-to-day development — it feeds clangd, clang-tidy, and most LSP-based editors.

bash
xmake project -k compile_commands
ln -s build/compile_commands.json .  # put it where editors expect it

Custom Rules and Tasks

Rules let you teach xmake how to handle non-standard source types.

lua
-- Compile GLSL shaders to SPIR-V during the build
rule("glsl")
    set_extensions(".vert", ".frag", ".comp", ".glsl")
    on_build_file(function(target, sourcefile, opt)
        local outfile = sourcefile .. ".spv"
        os.run("glslc %s -o %s", sourcefile, outfile)
        print("compiled shader: %s -> %s", sourcefile, outfile)
    end)

target("game")
    set_kind("binary")
    add_rules("glsl")                   -- attach the custom rule
    add_files("shaders/*.vert")
    add_files("shaders/*.frag")
    add_files("src/*.cpp")
    set_languages("c++20")

Custom tasks (like Makefile phony targets):

lua
task("lint")
    on_run(function()
        os.run("clang-tidy $(find src -name '*.cpp') -- -std=c++20")
    end)
    set_menu {description = "Run clang-tidy on all sources"}
bash
xmake lint    # invoke the custom task

Toolchain Selection

xmake auto-detects the system compiler, but you can pin a specific toolchain:

bash
xmake f --toolchain=clang       # use clang/clang++
xmake f --toolchain=gcc         # use gcc/g++
xmake f --toolchain=msvc        # use MSVC (Windows)
xmake f --toolchain=icc         # Intel C++ Compiler

Inside xmake.lua:

lua
-- Only use clang on Linux
if is_plat("linux") then
    set_toolchains("clang")
end

Check what toolchains are available:

bash
xmake show -l toolchains

xmake vs CMake vs Meson

FeaturexmakeCMakeMeson
Config languageLua (clean)CMake DSL (verbose)Python-like (clean)
Built-in pkg managerYes (xrepo)No (needs vcpkg/Conan)Wraps (limited)
Package ecosystemGrowingLargestMedium
Out-of-source buildsOptionalOptionalMandatory
Windows supportExcellentExcellentGood
Cross-compilationFirst-classVia toolchain filesFirst-class
IDE integrationGoodExcellentGood
Learning curveLowHighLow
CI/CD adoptionLimitedUbiquitousModerate
Generator backendsNinja, Make, VS, XcodeNinja, Make, VS, XcodeNinja only

When to choose xmake:

  • You want a CMake-free stack with built-in package management.
  • Your team is comfortable with Lua and wants readable build files.
  • You are targeting embedded or mobile platforms and want easy cross-compilation.

When to stay with CMake:

  • You need to integrate with projects or libraries that already ship CMakeLists.txt (nearly all of the C++ ecosystem).
  • Your CI/CD infrastructure, IDE plugins, and colleagues all assume CMake.

Common Pitfalls

add_files globs are evaluated at configure time. Adding new .cpp files to a directory does not automatically include them; you must run xmake f again (or simply xmake, which re-evaluates).

Mode state is sticky. After xmake f -m release, subsequent bare xmake invocations use release mode until you switch back. The current config is stored in .xmake/.

{public = true} is not the default. Unlike CMake's modern INTERFACE targets, xmake properties are private by default. You must explicitly opt into propagation.

Package versions are fuzzy by default. add_requires("fmt") resolves to the latest compatible version. Pin with add_requires("fmt 10.2.1") for reproducible builds.


See Also

  • CMake — the dominant C++ build system; the xmake ecosystem is measured against it
  • Meson — similar philosophy to xmake (clean syntax, fast), uses Python-like DSL
  • Ninja — low-level build executor that xmake can target as a backend
  • vcpkg — Microsoft's package manager; xrepo can delegate to it
  • Conan — the other major C++ package manager; also accessible from xrepo
  • Cross-Compilation — broader guide to targeting non-host platforms
Edit on GitHubUpdated 2026-07-14T00:00:00.000Z