Skip to content

Automatic Code Generation

In many cases, we need to preprocess and automatically generate code before compilation, and then include the generated code in the subsequent build process.

As Xmake continues to evolve, its support for code generation features is also being updated and improved. Currently, the following approaches are supported:

The Simplest Approach

This is the most straightforward method: simply generate the code before building and forcibly add it using add_files.

By default, add_files will not add files that do not exist, so you need to set always_added = true to force the addition, even if the file does not exist yet.

EXPLORER
xmake.lua
Lua xmake.lua
123456789101112131415
add_rules("mode.debug", "mode.release")

target("test")
    set_kind("binary")
    add_files("$(builddir)/autogen.cpp", {always_added = true})
    before_build(function (target)
        io.writefile("$(builddir)/autogen.cpp", [[
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
    cout << "hello world!" << endl;
    return 0;
}
        ]])
    end)

This approach has many limitations and is not commonly used in real-world scenarios, but it is simple and easy to understand.

Generation via Dependent Target

Sometimes, code generation requires running a target program within the project. This can be achieved as follows:

EXPLORER
src
autogen.cpp
main.cpp
test.in
xmake.lua
Lua xmake.lua
12345678910111213141516171819202122232425262728293031323334
add_rules("mode.debug", "mode.release")

rule("autogen")
    set_extensions(".in")
    before_buildcmd_file(function (target, batchcmds, sourcefile, opt)
        local sourcefile_cx = path.join(target:autogendir(), "rules", "autogen", path.basename(sourcefile) .. ".cpp")
        local objectfile = target:objectfile(sourcefile_cx)
        table.insert(target:objectfiles(), objectfile)

        batchcmds:show_progress(opt.progress, "${color.build.object}compiling.autogen %s", sourcefile)
        batchcmds:mkdir(path.directory(sourcefile_cx))
        batchcmds:vrunv(target:dep("autogen"):targetfile(), {sourcefile, sourcefile_cx})
        batchcmds:compile(sourcefile_cx, objectfile)

        batchcmds:add_depfiles(sourcefile, target:dep("autogen"):targetfile())
        batchcmds:set_depmtime(os.mtime(objectfile))
        batchcmds:set_depcache(target:dependfile(objectfile))
    end)

target("autogen")
    set_default(false)
    set_kind("binary")
    set_plat(os.host()) -- generate for host platform
    set_arch(os.arch())
    add_files("src/autogen.cpp")
    set_languages("c++11")
    set_policy("build.fence", true) -- disable parallel build for source files

target("test")
    set_kind("binary")
    add_deps("autogen")
    add_rules("autogen")
    add_files("src/main.cpp")
    add_files("src/*.in")

First, configure an autogen target program that must be runnable on the current build platform, so use set_plat(os.host()) to force building for the host platform.

Also, set the build.fence policy to disable parallel compilation between source files, ensuring the autogen target is built first and its executable is available in advance.

Then, define a custom rule to invoke the autogen target program before building, generate the source code, and compile the generated code into object files for linking.

See the full example: autogen_codedep

Generation via Native Module

Xmake introduces native module development, allowing code generation via native modules without defining an extra autogen target program.

For details on native module development, see: Native Module Development.

EXPLORER
modules
autogen
foo.lua
src
main.cpp
test.in
xmake.lua
Lua xmake.lua
12345678910111213141516171819202122232425262728293031
add_rules("mode.debug", "mode.release")

add_moduledirs("modules")

rule("autogen")
    set_extensions(".in")
    before_build_file(function (target, sourcefile, opt)
        import("utils.progress")
        import("core.project.depend")
        import("core.tool.compiler")
        import("autogen.foo", {always_build = true})

        local sourcefile_cx = path.join(target:autogendir(), "rules", "autogen", path.basename(sourcefile) .. ".cpp")
        local objectfile = target:objectfile(sourcefile_cx)
        table.insert(target:objectfiles(), objectfile)

        depend.on_changed(function ()
            progress.show(opt.progress, "${color.build.object}compiling.autogen %s", sourcefile)
            os.mkdir(path.directory(sourcefile_cx))
            foo.generate(sourcefile, sourcefile_cx)
            compiler.compile(sourcefile_cx, objectfile, {target = target})
        end, {dependfile = target:dependfile(objectfile),
              files = sourcefile,
              changed = target:is_rebuilt()})
    end)

target("test")
    set_kind("binary")
    add_rules("autogen")
    add_files("src/main.cpp")
    add_files("src/*.in")

See the full example: Native Module Autogen.

Generation in the Prepare Phase

Since Xmake 3.x, the on_prepare and on_prepare_files interfaces have been introduced to enable two-phase builds. The Prepare phase can be used specifically for source code generation and preprocessing.

The Prepare phase is executed before all on_build and on_build_files interfaces.

For details, see: Prepare Interface Manual.