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.lua
files 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 () end
part 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 run
commands 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.lua
files 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 option
can 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 buildir
variables 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_arm64
file: .
Add sub-directory project module
If you have multiple target submodules, you can xmake.lua
define 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 test
the 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/**.h
part 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, -DTEST
the macro definition will be automatically added when the target is compiled .
The above settings are disabled test
by 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_ldflags
and 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.h
file 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.h
file 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.h
add 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.h
results 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.lua
called 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.run
one {}
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-add
description style, xmake also supports another syntax style:, key-val
for 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