Xmake v3.0.9 Released, Lua 5.5 Upgrade, Zig C Interop and Ascend C
In this release, we upgraded the built-in Lua runtime to 5.5, added a new utils.replace built-in rule, brought C interop to the Zig toolchain, and introduced two new toolchains: Fil-C (a memory-safe C/C++ implementation) and Huawei Ascend C (for NPU programming).
Additionally, we added a multi-threaded aria2 download backend, support for exporting target and package dependency graphs as JSON or DOT, .csproj generation for C# targets in vsxmake, and many other improvements around custom toolchains, the clang-cl[llvm] toolset, and package archive merging.
New Features
Lua 5.5 Runtime
The built-in Lua runtime has been upgraded from 5.4 to 5.5. Lua 5.5 introduces some syntactic and behavioral changes (for example, the for-in control variable is now treated as a const local, and a few legacy library APIs have been removed) that could break existing xmake.lua configurations written against older xmake versions. To preserve backward compatibility as much as possible, xmake ships this upgrade together with several patches that restore the familiar pre-5.5 semantics:
- a new
utils.replacebuilt-in rule (see below), used to patch the bundledlparser.cso that writing to the for-in control variable is still allowed under Lua 5.5; - an updated sandbox
pairsiterator so that reassigning the loop key inside the loop body keeps working safely; - the
LUA_COMPAT_5_1/LUA_COMPAT_5_2/LUA_COMPAT_5_3compatibility macros are enabled in the build, so legacy APIs remain available.
As a result, the following idiom — and existing user scripts using similar patterns — continue to work without modification:
for k, v in pairs(t) do
k = k:gsub("_", "-")
do_something(k, v)
endIf you do hit a Lua 5.5 compatibility issue in your own xmake.lua after upgrading, please file an issue — we'd like to keep the upgrade transparent to users.
utils.replace Built-in Rule
A new built-in rule utils.replace allows applying in-memory text substitutions on a source file before it is fed to the compiler. It supports Lua-pattern replacement (default), plain-text replacement, and arbitrary Lua transform functions.
Lua-pattern replacement (default)
target("foo")
set_kind("binary")
add_files("src/foo.c", {rules = "utils.replace", replaces = {
{"old_pattern", "new_text"},
}})Plain-text replacement
target("foo")
set_kind("binary")
add_files("src/foo.c", {rules = "utils.replace",
replaces = {{"old text", "new text"}},
replace_plain = true})Function-based transform
target("foo")
set_kind("binary")
add_files("src/foo.c", {rules = "utils.replace", replaces = function (content)
content = content:gsub("old", "new")
return content
end})The rule writes the rewritten file under target:autogenfile(sourcefile) and uses dependency tracking, so the substitution is only re-run when the source file or the replace list actually changes. The original file's directory is also added to includedirs, so relative #include directives in the rewritten file still resolve.
Zig Toolchain: C Interop
The Zig toolchain now supports full C interop. You can mix .zig and .c files in the same target, depend on C/C++ static libraries built by xmake, and consume C packages from add_requires directly from Zig via @cImport / @cInclude.
Depending on a C static library
add_rules("mode.debug", "mode.release")
target("mathlib")
set_kind("static")
add_files("src/native/*.c")
add_includedirs("src/native", {public = true})
target("test")
set_kind("binary")
add_deps("mathlib")
add_files("src/*.zig")
add_includedirs("src/native")Then in Zig:
const std = @import("std");
const c = @cImport({
@cInclude("mathlib.h");
});
pub fn main() void {
const sum = c.c_add(3, 4);
const product = c.c_multiply(5, 6);
std.debug.print("c_add(3,4)={d}\n", .{sum});
std.debug.print("c_multiply(5,6)={d}\n", .{product});
}Using C packages from Zig
add_rules("mode.debug", "mode.release")
add_requires("zlib", {system = false})
target("test")
set_kind("binary")
add_files("src/*.zig")
add_packages("zlib")const c = @cImport({
@cInclude("zlib.h");
});
pub fn main() void {
const version = c.zlibVersion();
// ...
}Mixed C/Zig sources
target("demo")
set_kind("binary")
add_files("src/*.c")
add_files("src/*.zig")
set_toolchains("@zig")Fil-C Toolchain
We added a new filc toolchain that wires up Fil-C — a memory-safe implementation of the C and C++ languages — through the filcc / fil++ compiler drivers. It is currently supported on Linux x86_64 and works together with the filc package from xmake-repo.
add_rules("mode.debug", "mode.release")
add_requires("filc")
target("safety_test")
set_kind("binary")
set_toolchains("@filc")
add_packages("filc")
add_files("src/*.c")Fil-C provides additional headers like <stdfil.h>, and the toolchain automatically wires up the pizfix runtime tree, including link directories and sysincludedirs. Memory-safety violations are caught at runtime with diagnostics like filc safety error and filc panic.
Huawei Ascend C Toolchain
We added a new ascendc toolchain plus an ascendc language for Huawei NPU programming. It introduces two new source-file kinds:
.asc— Ascend C kernel code, compiled with thebishengdriver.aicpu— AI-CPU code, compiled with the same driver but with AI-CPU specific flags
The toolchain auto-detects the CANN SDK via ASCEND_HOME_PATH / ASCEND_TOOLKIT_HOME and picks the host-arch subdirectory automatically. A new add_ascnpuarchs(...) target API maps to the underlying --npu-arch=... flag.
Mixed asc/aicpu binary
add_rules("mode.debug", "mode.release")
target("ascendc_mixed")
set_kind("binary")
add_files("src/main.asc", "src/helper.aicpu")
add_ascnpuarchs("dav-2201")Static and shared libraries
add_rules("mode.debug", "mode.release")
target("ascendc_static")
set_kind("static")
add_files("src/lib.asc")
add_ascnpuarchs("dav-2201")
target("ascendc_shared")
set_kind("shared")
add_files("src/lib.asc")
add_ascnpuarchs("dav-2201")
target("app")
set_kind("binary")
add_deps("ascendc_static", "ascendc_shared")
add_files("src/main.asc")
add_ascnpuarchs("dav-2201")When a target contains .asc / .aicpu source files, xmake automatically loads the ascendc toolchain for that target — you don't need to pass --toolchain=ascendc explicitly. The SDK is auto-detected via ASCEND_HOME_PATH / ASCEND_TOOLKIT_HOME, and can also be overridden on the command line:
# SDK auto-detected from ASCEND_HOME_PATH — just build:
$ xmake
# Or point at a specific SDK
$ xmake f --sdk=/usr/local/Ascend/ascend-toolkit/latest
$ xmakeThe .asc and .aicpu source kinds can be freely mixed with .c, .cpp, and .S files in the same target.
Target Dependency Graph Export
xmake show --info=depgraph now supports structured output for the target dependency graph, in addition to the existing ASCII tree view. Two new flags have been added:
--format=plain|json|dot— output format--target=<name>— restrict the graph to a single root target and its transitive dependencies
# ASCII tree (default)
$ xmake show --info=depgraph
# Scoped to a single target
$ xmake show --info=depgraph --target=app
# JSON output, suitable for tool integration
$ xmake show --info=depgraph --format=json
# Graphviz DOT output
$ xmake show --info=depgraph --format=dotThe JSON output has the following shape:
{
"root_targets": ["app"],
"targets": [
{"name": "core", "deps": []},
{"name": "ui", "deps": ["core"]},
{"name": "ext::net", "deps": ["core"]},
{"name": "app", "deps": ["core", "ui", "ext::net"]}
]
}The DOT output renders as a directed graph:
digraph {
"core"
"ui" -> "core"
"ext::net" -> "core"
"app" -> "core"
"app" -> "ui"
"app" -> "ext::net"
}Package Dependency Graph Export
A symmetric feature has been added on the package side. xrepo info --depgraph (and xmake require --depgraph) prints the resolved dependency graph for one or more packages, with the same three output formats:
# ASCII tree (default)
$ xrepo info --depgraph libpng
$ xmake require --depgraph libpng
# JSON output
$ xrepo info --depgraph --format=json libpng
# Graphviz DOT output
$ xrepo info --depgraph --format=dot libpng
# With package configs
$ xrepo info --depgraph -k shared -m debug --configs="thread=true" boostThe JSON output shape is similar to the target depgraph:
{
"root_packages": ["libpng"],
"packages": [
{"name": "libpng", "version": "1.6.x", "deps": ["zlib"]},
{"name": "zlib", "version": "1.3.2", "deps": []}
]
}aria2 Download Backend
We added aria2 as a new download backend for package downloading. When aria2c is available on the system, xmake will use it automatically with multi-connection downloads enabled, falling back to curl / wget / PowerShell as before.
# macOS
$ brew install aria2
# Debian / Ubuntu
$ sudo apt install aria2
# Arch Linux
$ sudo pacman -S aria2No xmake.lua change is needed — once aria2 is installed, subsequent xmake require / xrepo install calls will pick it up automatically. The aria2 backend honors xmake's existing proxy settings (xmake g --proxy=...).
vsxmake: C# .csproj Generation
The vsxmake generator now detects C# targets and produces .csproj files instead of .vcxproj. The generated .csproj overrides MSBuild's CoreCompile step to delegate compilation back to xmake, so Visual Studio orchestrates the solution while xmake actually drives the build.
$ xmake project -k vsxmake
$ xmake project -k vsxmake -m "debug,release" -a "x64"The user xmake.lua is unchanged from a regular C# project:
add_rules("mode.debug", "mode.release")
target("test")
set_kind("binary")
add_files("src/Program.cs")Target kind is mapped to the OutputType of the generated csproj: binary → Exe, shared / static → Library.
Improved Custom Toolchain Definition
User-defined toolchains under add_toolchaindirs(...) are now more flexible:
Auto-registered module directories. Each toolchain subdirectory's
modules/folder is automatically added as animport()module search path, so custom toolchains can bundle their tool modules side by side with the toolchain definition.String-based hook references.
on_check,on_load, andon_check_mainnow accept a string that refers to a sibling.luafile. For large toolchain definitions, this lets you split a toolchain across multiple files.
-- xmake/toolchains/mytoolchain/xmake.lua
toolchain("mytoolchain")
set_kind("standalone")
set_toolset("cc", "mycc")
set_toolset("cxx", "mycxx")
on_check("check") -- resolves to xmake/toolchains/mytoolchain/check.lua
on_load("load") -- resolves to xmake/toolchains/mytoolchain/load.lua
toolchain_end()Typical directory layout:
xmake/toolchains/mytoolchain/
├── xmake.lua -- toolchain("mytoolchain") definition
├── check.lua -- function main(toolchain) ... end
├── load.lua -- function main(toolchain) ... end
└── modules/ -- automatically added to import() search path
└── helper.luaThis is exactly how the new ascendc toolchain is organized.
clang-cl Toolchain: Switch to lld-link via the llvm Config
We improved the clang-cl toolchain with a new llvm config that switches the linker / archiver / assembler / resource compiler over to the corresponding LLVM tools — including lld-link for the linker. By default clang-cl still uses MSVC's link.exe, ml64.exe, rc.exe, etc.; passing [llvm] opts into the full LLVM toolset.
-- default: clang-cl with link.exe / ml64.exe / rc.exe
set_toolchains("clang-cl")
-- with [llvm]: switches ld/sh to lld-link, ar to llvm-ar,
-- as to llvm-ml(64).exe, mrc to llvm-rc.exe
set_toolchains("clang-cl[llvm]")LTO under clang-cl also automatically switches the linker to lld-link (since link.exe doesn't support LLVM bitcode), so set_policy("build.optimization.lto", true) works out of the box without having to set the llvm config explicitly.
Alongside this we added a find_lld_link detection module and integrated lld-link with the windows.subsystem rule, the LTO rule, the Qt rule, and the clang-cl ASan thunks, so all of these features behave correctly under the clang-cl[llvm] toolset.
build.merge_archive Now Includes Package Libraries
The build.merge_archive policy has been extended to also merge static libraries contributed by add_packages(...) into the parent static target. Previously only static-library deps from add_deps(...) were merged.
add_rules("mode.debug", "mode.release")
add_requires("libpng", {system = false})
add_requireconfs("libpng.*", {system = false, override = true})
target("foo")
set_kind("static")
add_files("src/png.c")
add_packages("libpng")
set_policy("build.merge_archive", true)
target("test")
add_deps("foo")
add_files("src/main.c")After build, libfoo.a will contain png.c.o plus all object files from libpng.a and its transitive zlib.a, so test only needs to link foo.
utils.checker Module
A small public utility module utils.checker has been added, letting user-side scripts query whether they are currently running under xmake check <name>. This is useful for rules and scripts that want to behave differently when invoked via xmake check syntax, xmake check clang.tidy, etc.
import("utils.checker")
if checker.is_running("syntax") then
-- we are in `xmake check syntax`, skip codegen
endChangelog
New features
- #7430: Add C interop support for the Zig toolchain
- #7443: Add
utils.replacebuilt-in rule - #7437: Upgrade built-in Lua runtime to 5.5
- #7446: Add Fil-C toolchain support
- #7489: vsxmake: generate
.csprojfor C# targets - #7491: Add
xrepo info --depgraphto print the package dependency graph - #7490: Support exporting the target dependency graph as JSON or DOT
- #7518: Add aria2 download backend with multi-threaded support
- #7535: Add Huawei Ascend C toolchain support
Changes
- #7420: Improve zsh completions
- #7423: Improve
has_flagsdetection for cl / clang-cl - #7424: Improve code comments
- #7439: Switch
icxtoicx-ccon Windows - #7434: Improve nix package config and source selection
- #7440: Improve code comments
- #7435: Improve C++ module error messages
- #7461: Expose checker as a public API
- #7463: Bump package versions in C++ modules tests
- #7465: Bump C++ modules test versions again
- #7467: Parse
XMAKE_ROOT/XMAKE_STATSviaoption.boolean - #7478: Improve custom toolchain definition
- #7485: Improve
pairsbehavior for Lua 5.5 - #7505: Add NuGet package repository entry to README
- #7524: Add
lld-linkconfiguration support - #7529: Move clang PCH support into its own module
- #7542: Merge libs contributed by packages
Bugs fixed
- #7432: Fix
set_kind("test")handling - #7433: Fix xpack AppImage package naming
- #7445: Fix package local cache directory
- #7455: Avoid duplicate bundle rpath insertion in
xcode.application - #7451: Use the correct framework rpath for iOS apps in
xcode.application - #7449: Avoid leaking private dep flags into rebuilt BMIs for C++ modules
- #7470: Use
target_link_librariesto link object libraries in generated CMakeLists - #7477: Fix clang when using
c++_static - #7483: Fix syslink handling on MinGW
- #7493: Fix Qt cross-compilation
- #7495: Fix "cannot find known tool script for
ar.cmd" - #7500: Fix Linux kernel module
insmodfailure on kernel 6.12+ - #7502: Fix Rust build
- #7501: Fix Linux kernel module intermediate filenames
- #7503: Fix xpack deb / srpm packaging
- #7512: Handle Windows cross-compile prefix and install path in the Meson backend
- #7516: Restore missing
path.isdirAPI - #7520: Fix cl exception when probing flags
- #7523: Fix MSVC / Intel snippet detection when the temp path contains spaces
- #7525: Fix Zig CC stdlib and LTO flags
- #7527: Handle compiler / linker frontend variants more accurately when generating CMakeLists
- #7533: Support paths like
mingw/current/bininfind_mingw - #7538: Align ascendc flags with the Bisheng compiler user guide
- #7540:
xmake show -l targetsnow lists all targets regardless ofset_default - #7541: Fix
find_qtcache and dep config being overwritten during Qt cross-compilation - #7545: Expand builtin variables before calling
path.is_absoluteinadd_moduledirs