文章目录
- 1. xmake是什么
- 2. 一个可执行程序
- 3. 一个库文件
- 4. 遍历文件用法
- 5. 第三方库
- 3.1 系统安装库
- 3.2 独立库
- 6. 后续
由于前一篇博客的最后说要做一些rknn的优化,其实这个工作很早就完成了,但是我是使用
xmake
这个来做我的工程的构建的,不管是出于对之后的博客的铺垫,还是对
xmake
的欣赏和喜欢,我觉得有必要写这么一篇博客。
1. xmake是什么
相信我们写C/C++
的更多的都知道CMake
,也大致知道这个东西在当前跨平台编译的地位。或者还会了解其他的,比如:makefile
、ninjia
等等。
但是不知道你有没有像我一样,在写CMakeLists.txt
的时候,总是吐槽这凌乱的语法,不关心大小写就算了,然后各种东西又臭又长,尤其是写面向对象多了以后,就更讨厌CMake
这丑陋的语法。尤其是看到go
、python
这些语言中,特别好用方便的包管理的时候,就更嫌弃了。
我也是偶然间看到了xmake
的,有中文的文档,简洁的语法(如果你有lua
语言的基础,就更喜欢这样的语法),还有一个特别方便的包管理,我是就自个儿看着文档学了起来。
后来我自己的github上一些小代码也都使用xmake
进行构建,还有自己本地的gitlab
的代码也都使用xmake
了,可能写的水平不是很高,但带来的方便是让我很受用的。
OK,废话说完,真心希望大家可以看看xmake,也许你也会喜欢上用这个工具。
2. 一个可执行程序
由于xmake
官方的文档已经非常详细了,而且还有中文的文档,非常适合我们自学和使用,我就不卖弄知识,就简单根据自己的使用,写几个很简单的demo来介绍一下xmake
的基础用法。
首先就是一个可执行程序,我还是用最经典的HELLO WORLD
来说。
有这样一个目录结构:
- src
-- main.cpp
- xmake.lua
就这样的一个简单的示例,注意到这里有一个xmake.lua
,这个就是我们进行构建的核心。那么我们这个xmake.lua
怎么写?我直接给一个很简单的demo
:
-- 设置项目名称,可有可无
set_project("xmake_exec")
-- 设置支持的编译模式
add_rules("mode.debug", "mode.release")-- 配置 debug 下的一些编译选项
if is_mode("debug") then -- 启用 debug 时的调试符号set_symbols("debug")-- 禁用优化set_optimize("none")
end-- 配置 release 下的一些编译选项
if is_mode("release") then-- 隐藏调试符号set_symbols("hidden")-- 设置优化set_optimize("fastest")-- 删除所有的调试符号set_strip("all")
end-- 将所有的警告都视为错误
set_warnings("all", "error")-- 设置 c99 c++11
set_languages("c99", "c++11") -- cxx11-- 添加一些宏定义
-- add_defines("NDEBUG", "_GNU_SOURCE=1")-- 设置包含目录
-- add_includedirs("/usr/include", "/usr/local/include")-- 设置依赖的库和目录
-- add_links("tbox")
-- add_linkdirs("/usr/local/lib", "/usr/lib")-- 添加系统链接库
-- add_syslinks("z", "pthread")-- 添加编译链接的参数
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})target("xmake_exec")set_kind("binary")add_files("src/*.cpp")
这里面写了很多注释的东西,都是一些暂时没用上的,可以供后续使用的时候添加。这里的各种“函数”,其实都可以在xmake官方文档中有详细的介绍和demo,我肯定没有作者写的深入和详细,所以如果想要了解每一个“函数”的作用,还请认真看一下。
我这里直接说target
这个部分。
顾名思义,target
就是目标,也就是我们当前这个xmake.lua
所要构建的目标,只需要上述那么简单的三行就可以搞定,我们可以对应的理解CMake
中的:
add_executable(xmake_exec src/main.cpp)
当然,如果你的文件特别多的时候,你就只能使用CMake
的file(GLOB ...)
来实现了,但是xmake
就是上述简单的add_files("src/*.cpp)
来实现,是不是很优雅,尤其是你在Linux
下使用shell
命令习惯了之后的写法?
题外话,我觉得
xmake
使用的特别舒服的一点就是,它能让你觉得“就应该是这样的嘛”,而不是CMake
那样很繁琐且难用的方式。
3. 一个库文件
如果只是写一个可执行程序,那也太没水平了,只能用来做做测试,那么如何生成一个库文件?
OK,这样一个目录结构,有头文件,有源文件,然后还有我们的xmake.lua
,那我们的xmake.lua
也很简单:
-- 设置项目名称
set_project("xmake_lib")-- 设置版本
set_version("1.0.0")-- 设置xmake的最低要求版本
set_xmakever("2.6.9")-- 设置支持的编译模式
add_rules("mode.debug", "mode.release")-- 配置 debug 下的一些编译选项
if is_mode("debug") then -- 启用 debug 时的调试符号set_symbols("debug")-- 禁用优化set_optimize("none")
end-- 配置 release 下的一些编译选项
if is_mode("release") then-- 隐藏调试符号set_symbols("hidden")-- 设置优化set_optimize("fastest")-- 删除所有的调试符号set_strip("all")
end-- 将所有的警告都视为错误
set_warnings("all", "error")-- 设置 c99 c++11
set_languages("c99", "c++11") -- cxx11-- 添加一些宏定义
add_defines("DREAMSKY_EXPORTS")-- 设置包含目录
-- add_includedirs("/usr/include", "/usr/local/include")-- 设置依赖的库和目录
-- add_links("tbox")
-- add_linkdirs("/usr/local/lib", "/usr/lib")-- 添加系统链接库
-- add_syslinks("z", "pthread")-- 添加编译链接的参数
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})target("xmake_lib") -- 这样可以在外部直接传参,从而构建需要的set_kind("$(kind)")-- set_kind("shared")-- set_kind("static")-- 如果需要外部配置是否需要进行传参,比如 --var=val 的时候,可以使用-- add_defines("-DTEST=$(var)")add_includedirs("include", {public = true})-- 如果头文件的目录比较复杂,那么就需要这样进行处理-- 通配符include/**.h匹配include目录及其子目录的所有.h后缀文件。-- 对于add_headerfiles语句,如果不加括号,则所有文件都会被直接安装到include文件夹下,-- 目录结构将会丢失;而括号的作用在于保持括号内的目录结构。-- 例如a/(b/c.h)安装后会变成include/b/c.h。-- 而在设置中的prefixdir选项则将所有头文件放在include的子目录中。-- 如对于上述设置{prefixdir = "mylib"},a/(b/c.h)安装后会变成include/mylib/b/c.h-- add_headerfiles("include/(**.h)", {prefixdir = "DreamSky"})add_headerfiles("include/*.h")add_files("src/*.cpp")-- 有时候使用xmake构建的库需要导出给使用其他构建系统的项目使用,-- 这就需要对应构建工具的配置文件。xmake提供pkg-config配置文件和cmake配置文件的生成。-- 对于需要导出的target,使用如下语句:add_rules("utils.install.pkgconfig_importfiles")add_rules("utils.install.cmake_importfiles")-- 对于头文件之外的安装文件,xmake提供了类似的接口add_installfiles,-- 它与add_headerfiles的区别在于,prefixdir将直接放在安装目录下而不是include文件夹下。-- 例如文档安装可以写-- add_installfiles("doc/*.md", {prefixdir = "share/doc"})if is_plat("windows") and is_kind("shared") thenprint("windows shared")end-- 有时候,项目生成的库和二进制不要按约定的bin和lib目录存放,甚至不需要被安装。-- 还有时候,安装的文件需要根据安装目录做一定的更改。-- 这时可以使用on_install语句来重载target的安装过程。例如,将生成的库文件安装到xmake_lib文件夹:-- on_install(function (target)-- local libdir = path.join(target:installdir(), "xmake_lib")-- os.mkdir(libdir)-- os.cp(target:targetfile(), libdir)-- local includedir = path.join(target:installdir(), "xmake_lib_include")-- os.mkdir(includedir)-- for _, headerfile in ipairs(target:headerfiles()) do-- os.cp(headerfile, includedir)-- end-- end)-- 编译安装命令示例:
-- 清除编译配置: xmake clean
-- 清除所有: xmake clean --all
-- 指定编译器: xmake f -p mingw xmake f -p windows
-- 指定编译模式: xmake f -m debug xmake f -m release
-- 指定库类型: xmake f -k shared xmake f -k static
-- 指定安装目录: xmake install -o "D:/install/xmake_lib"-- 简短示例:
-- xmake f -p mingw -m debug -k shared
-- xmake
-- xmake install -o "D:/install/xmake_lib"
我也是加了一些注释,我觉得似乎都不需要额外的说明了,就很清晰了。
当然新版本的xmake
已经支持给so
文件加上版本号了,也就是很简单的:
set_version("1.0.0", {soname = true})
4. 遍历文件用法
很多时候我们需要写很小的demo来验证某个函数,或者某个小算法,好吧,总不能每个文件写一个xmake
工程吧?不仅要新建工程,还得写xmake.lua
,就很麻烦。
能不能我随意添加文件,然后又能够根据我的文件名自动生成对应的target,然后每次只需要在工程中新建一个
.cpp
文件就可以了呢?当然可以!
假设,有这样的目录:
以后我要是再随便增加test_04.cpp
,不用改xmake.lua
可不可以做到?答案是肯定的。
那这里的xmake.lua
就可以这样写:
-- 遍历和生成部分参考:
-- https://github.com/idealvin/coost/blob/master/test/xmake.lua-- 设置项目名称
set_project("xmake_foreach")-- 设置版本
set_version("1.0.0")-- 设置xmake的最低要求版本
set_xmakever("2.6.9")-- 设置支持的编译模式
add_rules("mode.debug", "mode.release")-- 配置 debug 下的一些编译选项
if is_mode("debug") then -- 启用 debug 时的调试符号set_symbols("debug")-- 禁用优化set_optimize("none")
end-- 配置 release 下的一些编译选项
if is_mode("release") then-- 隐藏调试符号set_symbols("hidden")-- 设置优化set_optimize("fastest")-- 删除所有的调试符号set_strip("all")
end-- 将所有的警告都视为错误
set_warnings("all", "error")-- 设置 c99 c++11
set_languages("c99", "c++11") -- cxx11-- 使用函数来遍历cpp文件夹,后续根据遍历的结果来处理
function all_tests()local res = {}for _, x in ipairs(os.files("**.cpp")) dolocal item = {}local s = path.filename(x)table.insert(item, s:sub(1, #s - 4)) -- 取文件名来作为target的名字table.insert(item, path.relative(x, ".")) -- 利用path.relative来转换相对路径,即将 x 转换为相对于 . 的相对路径table.insert(res, item)endreturn res
endfor _, test in ipairs(all_tests()) do
target(test[1])set_kind("binary")-- set_default(false)add_files(test[2])
end
这是直接参考xmake
作者的coost
来进行实现的,额外说一句,作者的coost库也很强,而且没有Boost那么庞大复杂,如果想学习一些编程的思路,可以阅读这个源码,还是很容易看明白的。
5. 第三方库
当然xmake
有一个特别好用的仓库,你添加opencv
这些依赖的时候,只需要:
add_requires("opencv")target("test")add_files("src/*.cpp")add_packages("opencv")
就可以了,而且可以自动从网络上下载相关的库文件。
3.1 系统安装库
当然,如果你系统上已经安装了这个opencv
库,你就是不想用xmake repo
的库,也是可以的。
add_requires("cmake::OpenCV", {alias = "opencv", system = true})target("test")add_files("src/*.cpp")add_packages("opencv")
是不是很简单,可以调用cmake
的库,当然xmake
支持的可多了,具体的参考官方文档。
3.2 独立库
这些都是安装的,那么对于我们使用rknn
来说,他就是提供了头文件和库文件,我们怎么来加入到编译呢?当然你可以直接将头文件和库文件加到target
的编译中来,可是那也太不优雅了,而且工程多了起来就乱。所以我们需要一个优雅的使用方法,有没有呢?有!
我们可以通过创建一个本地的xmake repo
来实现,将他们按照xmake
的语法来进行描述,然后就可以在我们的工程中直接使用了,下一篇写rknn
的优化的时候将详细说如何将rknnrt
和rga
来优雅地写到我们的xmake repo
中。
假设我们已经能将rknnrt
按照xmake
语法写好了,并且我们的xmake repo
地址是/home/xxx/xmake_repo
,那么我们工程中直接就可以:
add_repositories("local-repo /home/xxx/xmake_repo")
add_requires("rknnrt", "librga")target("rknn_engine")add_includedirs("include", {public = true})add_headerfiles("include/(**.h)")add_files("src/**.cpp")add_packages("rknnrt", "librga")
是不是很优雅了?
6. 后续
xmake
的官方文档已经足够详细了,我觉得没有那个水平能超过作者的文档,只是在自己的使用过程中有一些心得体会,也在自己的github
、gitlab
和工作中使用了xmake
,水平也不算高,如果你也恰好是入门,有一些我也恰好知道的问题,欢迎一起交流。