文章目錄
- 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
,水平也不算高,如果你也恰好是入門,有一些我也恰好知道的問題,歡迎一起交流。