Xmake not only natively supports multi-language file building, but also allows users to implement complex unknown file building through custom build rules. Custom rules let you define specialized build logic for specific file types.
Custom build rules are defined using the rule() function and associate a set of file extensions to rules through set_extensions(). Once these extensions are associated with rules, calls to add_files() will automatically use this custom rule.
rule("rulename")
set_extensions(".ext1", ".ext2")
on_build_file(function (target, sourcefile, opt)
-- build logic
end)
-- Define a build rule for markdown files
rule("markdown")
set_extensions(".md", ".markdown")
on_build_file(function (target, sourcefile, opt)
import("core.project.depend")
-- make sure build directory exists
os.mkdir(target:targetdir())
-- replace .md with .html
local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".html")
-- only rebuild if file has changed
depend.on_changed(function ()
-- call pandoc to convert markdown to html
os.vrunv('pandoc', {"-s", "-f", "markdown", "-t", "html", "-o", targetfile, sourcefile})
end, {files = sourcefile})
end)
target("test")
set_kind("object")
add_rules("markdown")
add_files("src/*.md")
target("test")
set_kind("binary")
add_rules("markdown") -- apply markdown rule
add_files("src/*.md") -- automatically use markdown rule
target("test")
set_kind("binary")
add_files("src/*.md", {rules = "markdown"}) -- specify rule for specific files
::: tip Note
Rules specified via add_files("*.md", {rules = "markdown"}) have higher priority than rules set via add_rules("markdown").
:::
Custom rules support the complete build lifecycle and can execute custom logic at different stages:
rule("custom")
set_extensions(".custom")
on_load(function (target)
-- configuration when rule is loaded
target:add("defines", "CUSTOM_RULE")
end)
before_build(function (target)
-- preparation work before building
print("Preparing to build custom files...")
end)
on_build_file(function (target, sourcefile, opt)
-- process individual source files
print("Building file:", sourcefile)
end)
after_build(function (target)
-- cleanup work after building
print("Custom build completed")
end)
rule("single")
set_extensions(".single")
on_build_file(function (target, sourcefile, opt)
-- process single file
local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".out")
os.cp(sourcefile, targetfile)
end)
rule("batch")
set_extensions(".batch")
on_build_files(function (target, sourcebatch, opt)
-- batch process multiple files
for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
print("Processing file:", sourcefile)
end
end)
Using on_buildcmd_file and on_buildcmd_files can generate batch commands instead of directly executing builds:
rule("markdown")
set_extensions(".md", ".markdown")
on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
-- ensure build directory exists
batchcmds:mkdir(target:targetdir())
-- generate target file path
local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".html")
-- add pandoc command
batchcmds:vrunv('pandoc', {"-s", "-f", "markdown", "-t", "html", "-o", targetfile, sourcefile})
-- add dependency files
batchcmds:add_depfiles(sourcefile)
end)
rule("foo")
add_deps("bar") -- foo depends on bar rule
rule("bar")
set_extensions(".bar")
on_build_file(function (target, sourcefile, opt)
-- bar rule build logic
end)
rule("foo")
add_deps("bar", {order = true}) -- ensure bar executes before foo
on_build_file(function (target, sourcefile, opt)
-- foo rule build logic
end)
rule("myrule")
set_extensions(".ext1", ".ext2", ".ext3")
rule("myrule")
add_imports("core.project.depend", "utils.progress")
on_build_file(function (target, sourcefile, opt)
-- can directly use depend and progress modules
end)
rule("myrule")
on_build_file(function (target, sourcefile, opt)
print("Target name:", target:name())
print("Source file:", sourcefile)
print("Build progress:", opt.progress)
print("Target directory:", target:targetdir())
end)
rule("resource")
set_extensions(".rc", ".res")
on_build_file(function (target, sourcefile, opt)
import("core.project.depend")
local targetfile = target:objectfile(sourcefile)
depend.on_changed(function ()
os.vrunv("windres", {sourcefile, "-o", targetfile})
end, {files = sourcefile})
end)
rule("protobuf")
set_extensions(".proto")
on_build_file(function (target, sourcefile, opt)
import("core.project.depend")
local targetfile = path.join(target:autogendir(), path.basename(sourcefile) .. ".pb.cc")
depend.on_changed(function ()
os.vrunv("protoc", {"--cpp_out=" .. target:autogendir(), sourcefile})
end, {files = sourcefile})
-- add generated file to target
target:add("files", targetfile)
end)
depend.on_changed()opt.progress to display build progress