Use xmake to describe the project elegantly

Descriptive grammar

The description grammar of xmake is based on Lua, so the description grammar inherits the flexibility and conciseness of Lua, and through 28 principles, the description scope (simple description) and script scope (complex description) are separated, making the project more concise Intuitive and very readable.

Because 80% of the project does not require very complicated script control logic, only a few simple lines of configuration description can meet the construction requirements. Based on this assumption, xmake separates the scope, so that 80% of the xmake.luafiles only need to be described like this :

target("demo")
    set_kind("binary")
    add_files("src/*.c")

And only 20% of the projects need to be described like this:

target("demo")
    set_kind("shared")
    set_objectdir("$(buildir)/.objs")
    set_targetdir("libs/armeabi")
    add_files("jni/*.c")

    on_package(function (target) 
        os.run("ant debug") 
    end)

    on_install(function (target) 
        os.run("adb install -r ./bin/Demo-debug.apk")
    end)

    on_run(function (target) 
        os.run("adb shell am start -n com.demo/com.demo.DemoTest")
        os.run("adb logcat")
    end)

The above function () endpart belongs to the custom script domain, under normal circumstances, it does not need to be set. Only when complex project descriptions and highly customized requirements are required, they need to be customized. In this scope, various xmake can be used. For more information on this, see: xmake description syntax and scope details .

The above code is also an android project that builds a custom mixed jni and java code. You can directly use xmake runcommands to automatically build, install, and run the apk program with one click.

Here are some more commonly used xmake description examples:

Build an executable program

target("demo")
    set_kind("binary")
    add_files("src/*.c")

This is the simplest and classic example. Under normal circumstances, in this case, you don't need to write any xmake.luafiles yourself . In the current code directory xmake, you can directly execute the command to complete the build, and it will automatically generate one for you xmake.lua.

For detailed information about automatic generation, see: xmake smart code scanning compilation mode, no need to write any make files by hand .

Build a configurable switch library program

target("demo")
    set_kind("$(kind)")
    add_files("src/*.c")

You can switch whether to compile a dynamic library or a static library through configuration:

$ xmake f --kind=static; xmake
$ xmake f --kind=shared; xmake

Added debug and release compilation mode support

Perhaps the default few lines of description configuration can no longer meet your needs. You need to be able to build debug and release versions of the program by switching the compilation mode, then you only need:

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

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

target("demo")
    set_kind("binary")
    add_files("src/*.c") 

You only need to switch the build mode through configuration:

$ xmake f -m debug; xmake
$ xmake f -m release; xmake

[-m|--mode]It is a built-in option and optioncan be used without self-definition , and the value of the mode is defined and maintained by the user. You can is_mode("xxx")judge the status of various modes.

Sign ios program through custom script

The ios executable program runs on the device and needs to be signed after the build is completed. At this time, you can use a custom script to achieve:

target("demo")
    set_kind("binary")
    add_files("src/*.m") 
    after_build(function (target))
        os.run("ldid -S %s", target:targetfile())
    end

Here is just a fake signature with the ldid program, which can only be used on jailbroken devices, just as an example for reference.

Built-in variables and external variables

$(varname)The syntax provided by xmake supports the acquisition of built-in variables, for example:

add_cxflags("-I$(buildir)")

It will convert the built-in buildirvariables into the actual build output directory during actual compilation :-I./build

Generally built-in variables can be used to quickly obtain and concatenate variable strings when passing parameters, for example:

target("test")
    add_files("$(projectdir)/src/*.c")
    add_includedirs("$(buildir)/inc")

It can also be used in the module interface of a custom script, for example:

target("test")
    on_run(function (target)
        os.cp("$(scriptdir)/xxx.h", "$(buildir)/inc")
    end)

Of course, this variable mode can also be extended. By default xmake f --var=val, the configured parameters can be directly obtained through commands, for example:

target("test")
    add_defines("-DTEST=$(var)")

Since it supports to get directly from the configuration options, of course, it is also very convenient to extend the custom options to obtain custom variables. For details on how to customize the options, see: option

Modify the target file name

We can use built-in variables to separate the generated target files according to different architectures and platforms, for example:

target("demo")
    set_kind("binary")
    set_basename("demo_$(arch)")
    set_targetdir("$(buildir)/$(plat)")

In the previous default settings, the target file will be generated as build\demo, and through the above code settings, the path and file name of the target file will be different under different configurations. Execute:

$ xmake f -p iphoneos -a arm64; xmake

The target build/iphoneos/demo_arm64file: .

Add sub-directory project module

If you have multiple target submodules, you can xmake.luadefine them in one , for example:

target("demo")
    set_kind("binary")
    add_files("src/demo.c")

target("test")
    set_kind("binary")
    add_files("src/test.c")

However, if there are too many sub-modules, it is a bit bloated to place them in an xmake file, and they can be placed in the sub-directories of independent modules:

target("demo")
    set_kind("binary")
    add_files("src/demo.c")

add_subdirs("src/test")

Through the above code, associate a sub-project directory and add testthe project target in it.

Install header files

target("tbox")
    set_kind("static")
    add_files("src/*.c")

    add_headers("../(tbox/**.h)|**/impl/**.h")
    set_headerdir("$(buildir)/inc")

The installed location and header files directory structure: build/inc/tbox/*.h.

Wherein the ../(tbox/**.h)portion in parentheses, is the actual root path to be installed, |**/impl/**.hpart of a file does not need to exclude the installation.

For wildcard matching rules and exclusion rules, please refer to add_files .

Multi-target dependency build

For multiple target projects, the default build order is undefined, and it is generally carried out in a sequential manner. If you need to adjust the build order, you can add dependency order to achieve:

target("test1")
    set_kind("static")
    set_files("*.c")

target("test2")
    set_kind("static")
    set_files("*.c")

target("demo")
    add_deps("test1", "test2")
    add_links("test1", "test2")

In the above example, when compiling the target demo, you need to compile the test1 and test2 targets first, because the demo will use them.

Merge static libraries

The add_files interface function of xmake is very powerful. It can not only support the mixed addition and construction of multiple language files, but also directly add static libraries to automatically merge the libraries into the current project target.

We can write:

target("demo")
    set_kind("static")
    add_files("src/*.c", "libxxx.a", "lib*.a", "xxx.lib")

When compiling a static library directly, merge multiple existing static libraries. Note that it is not a link, which is different from add_links .

And you can also directly append the object file:

target("demo")
    set_kind("binary")
    add_files("src/*.c", "objs/*.o")

Add custom configuration options

We can define a configuration option ourselves, for example to enable test:

option("test")
    set_default(false)
    set_showmenu(true)
    add_defines("-DTEST")

Then associate to the specified target:

target("demo")
    add_options("test")

In this way, even if an option is defined, if this option is enabled, -DTESTthe macro definition will be automatically added when the target is compiled .

The above settings are disabled testby default . Next, we will enable this option through configuration:

$ xmake f --test=y
$ xmake

The option support of xmake is very powerful. In addition to the above basic usage, various detection conditions can be configured to realize automatic detection. For specific details, please refer to: Option and dependent package addition and automatic detection mechanism .

Add third-party dependencies

In the target scope, add integrated third-party package dependencies, for example:

target("test")
    set_kind("binary")
    add_packages("zlib", "polarssl", "pcre", "mysql")

In this way, when compiling the test target, if the package exists, the macro definition, header file search path, and link library directory in the package will be automatically appended, and all libraries in the package will also be automatically linked.

Users no longer need their own separate call add_links, add_includedirs, add_ldflagsand other interfaces to configure dependencies links.

For how to set the package search directory, please refer to the add_packagedirs interface. For details of dependent packages, please refer to: Dependent package addition and automatic detection mechanism .

Generate configuration header file

If you want to write the detection result into the configuration header file after the xmake configuration project is successful, or after an option is automatically detected, you need to call this interface to enable the automatic config.hfile generation .

For example:

target("test")
    set_config_h("$(buildir)/config.h")
    set_config_h_prefix("TB_CONFIG")

When this target passes the following interfaces, after adding related option dependencies, package dependencies, and interface dependencies to this target, if a dependency is enabled, then some corresponding macro definition configurations will be automatically written to the set config.hfile go with.

These interfaces actually use some of the detection settings in the option option at the bottom, for example:

option("wchar")

    -- 添加对wchar_t类型的检测
    add_ctypes("wchar_t")

    -- 如果检测通过,自动生成TB_CONFIG_TYPE_HAVE_WCHAR的宏开关到config.h
    add_defines_h_if_ok("$(prefix)_TYPE_HAVE_WCHAR")

target("test")

    -- 启用头文件自动生成
    set_config_h("$(buildir)/config.h")
    set_config_h_prefix("TB_CONFIG")

    -- 添加对wchar选项的依赖关联,只有加上这个关联,wchar选项的检测结果才会写入指定的config.h中去
    add_options("wchar")

Detect library header files and interfaces

We can config.hadd some library interface detection to the just generated , for example:

target("demo")

    -- 设置和启用config.h
    set_config_h("$(buildir)/config.h")
    set_config_h_prefix("TEST")

    -- 仅通过参数一设置模块名前缀
    add_cfunc("libc",       nil,        nil,        {
   
   "sys/select.h"},   "select")

    -- 通过参数三,设置同时检测链接库:libpthread.a
    add_cfunc("pthread",    nil,        "pthread",  "pthread.h",        "pthread_create")

    -- 通过参数二设置接口别名
    add_cfunc(nil,          "PTHREAD",  nil,        "pthread.h",        "pthread_create")

The generated config.hresults are as follows:

#ifndef TEST_H
#define TEST_H

// 宏命名规则:$(prefix)前缀 _ 模块名(如果非nil)_ HAVE _ 接口名或者别名 (大写)
#define TEST_LIBC_HAVE_SELECT 1
#define TEST_PTHREAD_HAVE_PTHREAD_CREATE 1
#define TEST_HAVE_PTHREAD 1

#endif

In this way, we can control the code compilation according to the support of the interface in the code.

Custom plugin tasks

The task field is used to describe a custom task implementation, at the same level as target and option .

For example, here is the simplest task defined here:

task("hello")

    -- 设置运行脚本
    on_run(function ()
        print("hello xmake!")
    end)

This task only needs to be printed hello xmake!, how to run it?

Since set_menu is not used here to set the menu, this task can only be xmake.luacalled from within a custom script or other tasks, for example:

target("test")

    after_build(function (target)

        -- 导入task模块
        import("core.project.task")

        -- 运行hello任务
        task.run("hello")
    end)

Here, after building the test target, run the hello task. Of course, we can also pass parameters:

task("hello")
    on_run(function (arg1, arg2, arg3)
        print("hello xmake!", arg1, arg2, arg3)
    end)

target("test")
    after_build(function (target)
        import("core.project.task")
        task.run("hello", {}, "arg1", "arg2", "arg3")
    end)

The above task.runone {}is used to pass the parameters in the plug-in menu, here is not set_menu to set the menu, here is empty.

The plug-in support of xmake is also very powerful, and it provides many built-in plug-ins. For details, please refer to: xmake plug-in manual and task manual

Or you can refer to some plug-in demos that come with xmake .

Another grammatical style

In addition to supporting the most commonly used set-adddescription style, xmake also supports another syntax style:, key-valfor example:

target
{
    name = "test",
    defines = "DEBUG",
    files = {
   
   "src/*.c", "test/*.cpp"}
}

This is equivalent to:

target("test")
    set_kind("static")
    add_defines("DEBUG")
    add_files("src/*.c", "test/*.cpp")

Users can choose the appropriate style description according to their own preferences, but the suggestions here are:

* 针对简单的工程,不需要太过复杂的条件编译,可以使用key-val方式,更加精简,可读性好
* 针对复杂工程,需要更高的可控性,和灵活性的话,建议使用set-add方式
* 尽量不要两种风格混着写,虽然是支持的,但是这样对整个工程描述会感觉很乱,因此尽量统一风格作为自己的描述规范

In addition, not only for targets, but also for option, task, and template settings are supported in two ways, for example:

-- set-add风格
option("demo")
    set_default(true)
    set_showmenu(true)
    set_category("option")
    set_description("Enable or disable the demo module", "    =y|n")
-- key-val风格
option
{
    name = "demo",
    default = true,
    showmenu = true,
    category = "option",
    desciption = {
   
   "Enable or disable the demo module", "    =y|n"}
}

A custom task or plug-in can be written like this:

-- set-add风格
task("hello")
    on_run(function ()
        print("hello xmake!")

    end)
    set_menu {
        usage = "xmake hello [options]",
        description = "Hello xmake!",
        options = {}
    }
-- key-val风格
task
{
    name = "hello",
    run = (function ()
        print("hello xmake!")
    end),
    menu = {
                usage = "xmake hello [options]",
                description = "Hello xmake!",
                options = {}
            }
}

Conclusion

For more descriptions, you can directly read the official manual of xmake , which provides a complete api document and usage description.


Personal homepage: TBOOX open source project

Guess you like

Origin blog.csdn.net/waruqi/article/details/69388589