title: Xmake v3.0.5 released, Multi-row progress, XML module and Swift interop tags: [xmake, swift, xml, async, progress, toolchain, cuda] date: 2025-11-17 author: Ruki
In the new version, we have introduced several major features that significantly enhance the development experience. The highlights include multi-row progress output with theme support for better build visibility, a comprehensive XML module for parsing and encoding XML data, asynchronous OS APIs for improved I/O performance, and Swift interop support for seamless integration between Swift and C++/Objective-C code. We have also made significant improvements to toolchain configuration, TTY handling, and various performance optimizations.
Download: GitHub Releases | Source Repository
We have improved the progress output to support multi-row refresh, providing a significantly better visual experience during long-running builds. Instead of updating a single progress line, the build output now displays multiple concurrent build tasks with their individual progress, making it easier to monitor parallel compilation.
The output now shows multiple progress lines for parallel builds with real-time status updates for each compilation task:
You can enable multi-row progress output in two ways:
Via theme configuration: Use the soong theme which includes multi-row progress by default:
$ xmake g --theme=soong
Via project policy: Enable it directly in your xmake.lua:
set_policy("build.progress_style", "multirow")
This provides better visibility into parallel build progress, makes it easier to identify slow compilation units, and improves the overall user experience for large projects with many source files or parallel builds with multiple compilation units.
For more details, see: #6974
We have added comprehensive Swift interop support, enabling seamless bidirectional interoperability between Swift and C++/Objective-C code in your projects. The swift.interop rule is automatically enabled when the swift.interop target value is set, making it easy to mix Swift and C++ code in the same project.
The Swift interop support includes:
Target values:
You can configure Swift interop using the following target values:
set_values("swift.modulename", "SwiftFibonacci") -- Set the Swift module name
set_values("swift.interop", "cxx") -- Enable interop: "objc" or "cxx"
set_values("swift.interop.headername", "fibonacci-Swift.h") -- Define output header name
set_values("swift.interop.cxxmain", true) -- Force -parse-as-library to avoid duplicate main symbols
Complete example: Swift-C++ interop
Here's a complete example demonstrating Swift-C++ bidirectional interoperation:
fibonacci.swift:
// fibonacci.swift
public func fibonacciSwift(_ x: CInt) -> CInt {
print("x [swift]: \(x)")
if x <= 1 {
return 1
}
return fibonacciSwift(x - 1) + fibonacciSwift(x - 2)
}
main.cpp:
// main.cpp
#include <fibonacci-Swift.h>
#include <iostream>
int main(int argc, char ** argv) {
std::cout << SwiftFibonacci::fibonacciSwift(5) << std::endl;
return 0;
}
xmake.lua:
-- xmake.lua
target("cxx_interop")
set_kind("binary")
set_languages("cxx20")
add_files("lib/**.swift", {public = true})
add_files("src/**.cpp")
set_values("swift.modulename", "SwiftFibonacci")
set_values("swift.interop", "cxx")
set_values("swift.interop.headername", "fibonacci-Swift.h")
set_values("swift.interop.cxxmain", true)
Build output:
$ xmake
checking for platform ... macosx
checking for architecture ... x86_64
checking for Xcode directory ... /Applications/Xcode.app
[ 3%]: <cxx_interop> generating.swift.header fibonacci-Swift.h
[ 38%]: cache compiling.release src/fibonacci.cpp
[ 56%]: compiling.release lib/fibonacci/fibonacci.swift
[ 76%]: linking.release cxx_interop
[100%]: build ok, spent 1.785s
$ xmake run
x [swift]: 5
x [swift]: 4
...
8
When swift.interop is set, xmake automatically generates the C++ header file that allows C++ code to call Swift functions. You can use swift.modulename to define the Swift module name, which becomes the namespace in C++. Choose between "objc" for Objective-C interop or "cxx" for C++ interop. When both Swift and C++ have main functions, use swift.interop.cxxmain to avoid duplicate main symbols.
This feature is particularly useful for:
For more details, see: #6967
We have introduced a new core.base.xml module that provides a tiny DOM-style XML toolkit working inside Xmake's sandbox. It focuses on predictable data structures, JSON-like usability, and optional streaming so you can parse large XML documents without building the entire tree.
The XML module features:
xml.scan)xml.find)xml.loadfile, xml.savefile)Node Structure:
XML nodes are plain Lua tables with the following structure:
{
name = "element-name" | nil, -- only for element nodes
kind = "element" | "text" | "comment" | "cdata" | "doctype" | "document",
attrs = { key = value, ... } or nil,
text = string or nil,
children = { child1, child2, ... } or nil,
prolog = { comment/doctype nodes before root } or nil
}
Basic usage:
import("core.base.xml")
-- Parse XML string
local doc = assert(xml.decode([[
<?xml version="1.0"?>
<root id="1">
<item id="foo">hello</item>
</root>
]]))
-- Find and modify nodes
local item = assert(xml.find(doc, "//item[@id='foo']"))
item.attrs.lang = "en" -- mutate attrs directly
item.children = {xml.text("world")} -- replace existing text node
-- Add comment
table.insert(doc.children, xml.comment("generated by xmake"))
-- Encode with pretty printing
local pretty = assert(xml.encode(doc, {pretty = true}))
assert(xml.savefile("out.xml", doc, {pretty = true}))
File operations:
import("core.base.xml")
-- Load from file
local plist = assert(xml.loadfile("Info.plist"))
-- Modify and save
local dict = assert(xml.find(plist, "plist/dict"))
-- ... modify nodes ...
assert(xml.savefile("Info.plist", plist, {pretty = true, indent = 2}))
Streaming parser for large files:
import("core.base.xml")
local found
xml.scan(plist_text, function(node)
if node.name == "key" and xml.text_of(node) == "NSPrincipalClass" then
found = node
return false -- early terminate
end
end)
xml.scan walks nodes as they are completed; returning false stops the scan immediately. This is ideal for large files when you only need a few entries.
XPath-like queries:
import("core.base.xml")
local doc = assert(xml.loadfile("config.xml"))
-- Find by path
local element = xml.find(doc, "/root/item")
-- Find by attribute
local item = xml.find(doc, "//item[@id='foo']")
-- Find by text content
local node = xml.find(doc, "//string[text()='value']")
-- Get text content
local text = xml.text_of(node)
Node creation helpers:
import("core.base.xml")
local textnode = xml.text("hello")
local empty = xml.empty("br", {class = "line"})
local comment = xml.comment("generated by xmake")
local cdata_node = xml.cdata("if (value < 1) {...}")
local doctype = xml.doctype('plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"')
Options:
| Option | Applies to | Description |
|---|---|---|
trim_text = true |
xml.decode, xml.scan |
Strip leading/trailing spaces inside text nodes |
keep_whitespace_nodes = true |
xml.decode, xml.scan |
Preserve whitespace-only text nodes |
pretty = true / indent / indentchar |
xml.encode, xml.savefile |
Enable formatting and control indentation |
Use cases:
For more details, see: #7025
We have added JSON output format support for the xmake show command, making it easier to programmatically extract target information. This feature enables seamless integration with IDEs, build automation tools, and custom scripts that need to parse xmake project metadata.
The JSON output includes comprehensive target information:
You can use --json for compact output or --pretty-json for formatted output:
$ xmake show -t target --json
{"targets":[{"name":"test","kind":"binary","files":["src/main.cpp"],"links":["pthread"],"defines":["DEBUG"]}]}
$ xmake show -t target --pretty-json
{
"targets": [
{
"name": "test",
"kind": "binary",
"files": ["src/main.cpp"],
"links": ["pthread"],
"defines": ["DEBUG"],
"includedirs": ["include"],
"cxxflags": ["-std=c++17"],
"deps": ["mylib"]
}
]
}
You can extract target information for IDE integration or use it in scripts:
# Extract target information for IDE integration
xmake show -t target --pretty-json > project_info.json
# Use in scripts
TARGET_INFO=$(xmake show -t target --json)
TARGET_NAME=$(echo $TARGET_INFO | jq -r '.targets[0].name')
This is particularly useful for:
For more details, see: #7024
We have added support for specifying the CUDA SDK version via the --cuda_sdkver command-line option, giving you precise control over CUDA compilation. This is essential when working with multiple CUDA installations or when you need to target a specific CUDA version for compatibility.
You can specify the CUDA SDK version via command-line:
$ xmake f --cuda_sdkver=11.8
$ xmake
Supported version values include:
11.8 - Specify CUDA 11.8 version11.x - Specify CUDA 11.x series versionauto - Auto detect (default)This feature is particularly useful for:
For more details, see: #6964
We have added support for the latest GCC 15 toolchain, ensuring compatibility with the newest compiler features and improvements.
$ xmake f --toolchain=gcc-15
For more details, see: #6929
We have added support for the Solaris platform, including i386 and x86_64 architectures. This enables xmake to build on Solaris systems, further expanding cross-platform support. We have also added support for additional BSD systems, including NetBSD, OpenBSD, and DragonflyBSD.
$ xmake f -p solaris -a i386
$ xmake f -p solaris -a x86_64
For more details, see: #7055 and #7054
We have added asynchronous support for os APIs, allowing non-blocking file and process operations in xmake scripts. This enables concurrent I/O operations, significantly improving performance when dealing with multiple file operations or long-running processes.
Supported APIs:
The following APIs now support async operations:
os.rm - Remove filesos.rmdir - Remove directoriesos.cp - Copy filesos.files - Find filesos.filedirs - Find files and directoriesos.dirs - Find directoriesos.match - Match file patternslib.detect.find_file - Find filelib.detect.find_library - Find librarylib.detect.find_path - Find pathlib.detect.find_directory - Find directoryAsync modes:
There are two async modes available:
async = true (blocking): The operation can be scheduled with other coroutine tasks. You need to wait for the return value.async = true, detach = true (non-blocking): The operation executes in a background thread, so you don't need to wait for the return value.Usage examples:
import("lib.detect.find_file")
function main()
-- Remove file in an idle background thread, we don't need to wait for it
os.rm("/tmp/xxx.txt", {async = true, detach = true})
-- Async wait and get return value
local files = os.files("src/*.c", {async = true})
-- Async wait and find file
local file = find_file("*.txt", "/tmp/", {async = true})
end
This enables non-blocking I/O operations, significantly improves performance when reading multiple configuration files in parallel or processing large file lists concurrently. It's also useful for running external tools asynchronously, implementing parallel build steps, and improving plugin and rule performance.
For more details, see: #6989 and #6868
We have improved the toolchain configuration syntax to support inline configuration options, making toolchain setup more concise and declarative. The new syntax simplifies toolchain configuration with three main formats:
Simplified toolchain config formats:
clang, gccclang@llvm-10, @muslcc, zigmingw[clang]@llvm-mingw, msvc[vs=2025,..]Fast toolchain config switching:
You can now quickly switch toolchain configurations using inline syntax:
-- Equivalent to: set_toolchains("mingw", {clang = true})
set_toolchains("mingw[clang]")
-- Command line usage
-- xmake f --toolchain=mingw[clang]
Examples:
-- Simple toolchain
set_toolchains("clang")
-- Toolchain with package
set_toolchains("clang@llvm-10")
set_toolchains("@muslcc")
set_toolchains("zig")
-- Toolchain with configs and package
set_toolchains("mingw[clang]@llvm-mingw")
set_toolchains("msvc[vs=2025]")
-- Multiple configs
set_toolchains("mingw[clang]", {sdk = "/path/to/llvm-mingw"})
Additional improvements:
Added clang support for llvm-mingw toolchain:
xmake f --toolchain=mingw[clang] --sdk=/xxx/llvm-mingw
set_toolchains("mingw[clang]@llvm-mingw")
Added gcc support for old version NDK toolchain:
xmake f -p android --toolchain=ndk[gcc] --ndk=/xxxx
This makes toolchain configuration more concise and readable, enables declarative toolchain setup, and makes it easier to manage multiple toolchain variants. It's particularly useful for quick toolchain switching, per-target toolchain configuration, CI/CD pipeline setup, and cross-compilation toolchain specification.
For more details, see: #6924, discussion #6903, and discussion #6879
We have significantly improved file reading performance, especially for large files and projects with many source files.
The improvements include better buffering strategies and optimized I/O operations.
For more details, see: #6942
We have added realtime output support for xmake test, allowing test output to be displayed in real-time as tests run, rather than buffering output until the test completes. This is particularly useful for long-running tests or tests that produce continuous output, as it provides immediate feedback on test progress.
To enable realtime output for a test, set realtime_output = true in the test configuration:
target("test")
set_kind("binary")
add_tests("stub_n", {realtime_output = true, files = "tests/stub_n*.cpp", defines = "STUB_N"})
When realtime_output is enabled, the test output will be streamed directly to the terminal as the test runs, making it easier to monitor test progress and debug issues in real-time.
For more details, see: #6993
We have improved TTY handling and output formatting in the core.base.tty module. The following new interfaces have been added:
tty.cursor_move_up(n) / tty.cursor_move_down(n) - Move cursor verticallytty.cursor_move_left(n) / tty.cursor_move_right(n) - Move cursor horizontallytty.cursor_move_to_col(n) - Move cursor to specific columntty.cursor_save() / tty.cursor_restore() - Save and restore cursor positiontty.cursor_hide() / tty.cursor_show() - Control cursor visibilitytty.cr() - Move to start of line (carriage return)tty.erase_line() - Clear entire linetty.erase_line_to_end() - Erase from cursor to end of linetty.has_vtansi() - Check if terminal supports ANSI control codesFor more details, see: #6970
We have added support for detecting the Ghostty terminal, ensuring proper output formatting and color support in this modern terminal emulator.
For more details, see: #6987
We have improved the performance of the graph module, which is used for dependency resolution and build graph generation.
The improvements result in faster project configuration and dependency analysis.
For more details, see: #7027
xmake show -t target