Skip to content

Xmake Getting Started Tutorial 8, Switching Build Modes

xmake is a lightweight and modern c/c++ project building tool based on Lua. It's main features are: easy to use syntax, easy to use project maintenance, and a consistent build experience across platforms.

In this article, we will explain in detail how to switch common build modes such as debug/release during the project build process, and how to customize other build modes.

Debug and Release Modes

Usually, if we create a project through the xmake create command, a build rule configuration line will be automatically added in xmake.lua, as follows:

lua
add_rules("mode.release", "mode.debug")
target("hello")
    set_kind("binary")
    add_files("src/*.c")

Through the add_rules interface, we have added two commonly used built-in rules, release and debug, by default. They will attach some compilation flags related to the corresponding mode during compilation to enable optimization for release or debugging compilation.

If we just execute the xmake command without additional configuration, it will default to release compilation, which is equivalent to:

bash
$ xmake f -m release
$ xmake
[  0%]: ccache compiling.release src/main.cpp
[100%]: linking.release test
build ok!

If we want to switch to debug compilation mode, we just need to:

bash
$ xmake f -m debug
$ xmake
[  0%]: ccache compiling.debug src/main.cpp
[100%]: linking.debug test
build ok!

The -m/--mode= parameter above is used to set the compilation mode, which will be associated with the mode.release and mode.debug rules.

So, how are they associated? Let's first look at the internal implementation of these two rules:

lua
rule("mode.debug")
    after_load(function (target)
        if is_mode("debug") then
            if not target:get("symbols") then
                target:set("symbols", "debug")
            end
            if not target:get("optimize") then
                target:set("optimize", "none")
            end
        end
    end)

rule("mode.release")
    after_load(function (target)
        if is_mode("release") then
            if not target:get("symbols") and target:targetkind() ~= "shared" then
                target:set("symbols", "hidden")
            end
            if not target:get("optimize") then
                if is_plat("android", "iphoneos") then
                    target:set("optimize", "smallest")
                else
                    target:set("optimize", "fastest")
                end
            end
            if not target:get("strip") then
                target:set("strip", "all")
            end
        end
    end)

As you can see, during the target loading phase, xmake will judge the user's parameter configuration for xmake f --mode=xxx. If it obtains debug mode through the is_mode() interface, it will disable related optimizations and enable symbol output. If it's release mode, it will enable compilation optimization and strip all debugging symbols.

Customized Mode Configuration

Of course, the compilation configurations set by these two built-in rules by default can only meet the regular needs of most scenarios. If users want to customize some personal compilation configurations in different compilation modes, they need to make judgments in xmake.lua themselves.

For example, if we want to enable debugging symbols in release mode as well, we just need to:

lua
if is_mode("release") then
    set_symbols("debug")
end

Or add some additional compilation flags:

lua
if is_mode("release") then
    add_cflags("-fomit-frame-pointer")
end

Note: If the user's own configuration conflicts with the built-in configuration of mode.release, the user's settings will take priority.

Of course, we can also completely avoid adding default configuration rules through add_rules("mode.debug", "mode.release"), letting users completely control the mode configuration themselves:

lua
-- If the current compilation mode is debug
if is_mode("debug") then

    -- Add DEBUG compilation macro
    add_defines("DEBUG")

    -- Enable debugging symbols
    set_symbols("debug")

    -- Disable optimization
    set_optimize("none")
end

-- If it's release or profile mode
if is_mode("release", "profile") then

    -- If it's release mode
    if is_mode("release") then

        -- Hide symbols
        set_symbols("hidden")

        -- Strip all symbols
        set_strip("all")

        -- Omit frame pointer
        add_cxflags("-fomit-frame-pointer")
        add_mxflags("-fomit-frame-pointer")

    -- If it's profile mode
    else
        -- Enable debugging symbols
        set_symbols("debug")
    end

    -- Add extended instruction sets
    add_vectorexts("sse2", "sse3", "ssse3", "mmx")
end

Other Built-in Mode Rules

Through the example above, we see that in addition to debug/release modes, there's also a profile mode configuration judgment. Actually, xmake also provides corresponding built-in modes. Let's see what else there is:

mode.debug

Add debug compilation mode configuration rules to the current project's xmake.lua, for example:

lua
add_rules("mode.debug")

Equivalent to:

lua
if is_mode("debug") then
    set_symbols("debug")
    set_optimize("none")
end

We can switch to this compilation mode by: xmake f -m debug.

mode.release

Add release compilation mode configuration rules to the current project's xmake.lua, for example:

lua
add_rules("mode.release")

Equivalent to:

lua
if is_mode("release") then
    set_symbols("hidden")
    set_optimize("fastest")
    set_strip("all")
end

We can switch to this compilation mode by: xmake f -m release.

mode.check

Add check compilation mode configuration rules to the current project's xmake.lua, generally used for memory detection, for example:

lua
add_rules("mode.check")

Equivalent to:

lua
if is_mode("check") then
    set_symbols("debug")
    set_optimize("none")
    add_cxflags("-fsanitize=address", "-ftrapv")
    add_mxflags("-fsanitize=address", "-ftrapv")
    add_ldflags("-fsanitize=address")
end

We can switch to this compilation mode by: xmake f -m check.

mode.profile

Add profile compilation mode configuration rules to the current project's xmake.lua, generally used for performance analysis, for example:

lua
add_rules("mode.profile")

Equivalent to:

lua
if is_mode("profile") then
    set_symbols("debug")
    add_cxflags("-pg")
    add_ldflags("-pg")
end

We can switch to this compilation mode by: xmake f -m profile.

mode.coverage

Add coverage compilation mode configuration rules to the current project's xmake.lua, generally used for coverage analysis, for example:

lua
add_rules("mode.coverage")

Equivalent to:

lua
if is_mode("coverage") then
    add_cxflags("--coverage")
    add_mxflags("--coverage")
    add_ldflags("--coverage")
end

We can switch to this compilation mode by: xmake f -m coverage.

Note: The generated gcno files are generally in the directory corresponding to the obj, so you need to find them from the build directory.

Extending Your Own Build Modes

xmake's mode configuration doesn't have fixed values. Users can pass and configure them arbitrarily, as long as the mode value passed in xmake f -m/--mode=xxx can correspond to is_mode("xxx") in xmake.lua.

For example, if we set our own unique compilation mode my_mode, we can directly configure and switch it on the command line:

bash
$ xmake f -m my_mode
$ xmake
[  0%]: ccache compiling.my_mode src/main.cpp
[100%]: linking.my_mode test
build ok!

Then make the corresponding value judgment in xmake.lua:

lua
if is_mode("my_mode") then
    add_defines("ENABLE_MY_MODE")
end

Using Mode Variables

We can also directly pass mode variables $(mode) in configuration values, for example, to select different libraries to link based on different modes:

lua
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_links("xxx_$(mode)")

With the above configuration, if compiled in debug mode, it will select to link: the libxxx_debug.a library, while in release mode it will link libxxx_release.a. Of course, we can also set it in the library search path and select the corresponding library based on the directory.

lua
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_linkdirs("lib/$(mode)")
    add_links("xxx")

In addition, we can directly obtain the passed mode configuration value through get_config("mode"), and these methods of obtaining are also effective in custom scripts, for example:

lua
target("test")
    set_kind("binary")
    add_files("src/*.c")
    on_load(function (target)
        if is_mode("release") then
            print(get_config("mode"), "$(mode)")
        end
    end)