xmake是一個基于Lua的輕量級現代化c/c++的項目建構工具,主要特點是:文法簡單易上手,提供更加可讀的項目維護,實作跨平台行為一緻的建構體驗。
本文主要詳細講解下,如何通過配置子工程子產品,來組織建構一個大規模的工程項目。
- 項目源碼
- 官方文檔
維護簡單的項目結構
對于一些輕量型的小工程,通常隻需要單個xmake.lua檔案就能搞定,大體結構如下:
projectdir
- xmake.lua
- src
- test
- *.c
- demo
- *.c
複制
源碼下面層級簡單,通常隻需要在項目根目錄維護一個xmake.lua來定義所有target就能完成建構,看上去并不是很複雜,也很清晰。
-- 在根域設定通用配置,目前所有targets都會生效
add_defines("COMMON")
target("test")
set_kind("static")
add_files("src/test/*.c")
add_defines("TEST")
target("demo")
set_kind("static")
add_files("src/demo/*.c")
add_defines("DEMO")
複制
維護複雜的項目結構
但是對于一些大型項目,通常的組織結構層次很多也很深,需要編譯的target目标也可能有十幾甚至上百個,這個時候如果還是都在根xmake.lua檔案中維護,就有點吃不消了。
這個時候,我們就需要通過在每個子工程子產品裡面,單獨建立xmake.lua來維護他們,然後使用xmake提供的includes接口,将他們按層級關系包含進來,最終變成一個樹狀結構:
projectdir
- xmake.lua
- src
- test
- xmake.lua
- test1
- xmake.lua
- test2
- xmake.lua
- test3
- xmake.lua
- demo
- xmake.lua
- demo1
- xmake.lua
- demo2
- xmake.lua
...
複制
然後,根xmake.lua會将所有子工程的xmake.lua通過層級includes全部引用進來,那麼所有定義在子工程的target配置也會完全引用進來,我們在編譯的時候永遠不需要單獨去切到某個子工程目錄下操作,隻需要:
$ xmake build test1
$ xmake run test3
$ xmake install demo1
複制
就可以編譯,運作,打包以及安裝指定的子工程target,是以除非特殊情況,平常不推薦來回切換目錄到子工程下單獨編譯,非常的繁瑣。
根xmake.lua檔案配置
通常推薦的做法就是在根xmake.lua中僅僅配置一些對所有target都通用的設定,以及includes對子工程的引用,不放置對targets的定義,例如:
-- define project
set_project("tbox")
set_xmakever("2.3.2")
set_version("1.6.5", {build = "%Y%m%d%H%M"})
-- set common flags
set_warnings("all", "error")
set_languages("c99")
add_cxflags("-Wno-error=deprecated-declarations", "-fno-strict-aliasing", "-Wno-error=expansion-to-defined")
add_mxflags("-Wno-error=deprecated-declarations", "-fno-strict-aliasing", "-Wno-error=expansion-to-defined")
-- add build modes
add_rules("mode.release", "mode.debug")
-- includes sub-projects
includes("test", "demo")
複制
xmake裡面所有的設定都是按tree狀繼承的,根xmake.lua中的root域設定會對所有includes的子xmake.lua裡面的targets生效,
但反過來不會,子xmake.lua裡面的root域設定僅對它下面的子xmake.lua生效,不會影響到父xmake.lua中定義的targets。
子xmake.lua檔案配置
是以,我們可以在每個子工程目錄中,單獨配置xmake.lua,裡面的所有配置不會幹擾父xmake.lua,隻對它下面的更細粒度的子工程生效,就這樣一層層按tree狀生效下去。
由于,已經在根xmake.lua配置了大部分通用配置,那麼我們可以在test子工程下,專心配置隻對test有用的設定,例如對于
projectdir/test/xmake.lua
:
add_defines("TEST")
target("test1")
set_kind("static")
add_files("test1/*.c")
add_defines("TEST1")
target("test2")
set_kind("static")
add_files("test2/*.c")
add_defines("TEST2")
複制
我們可以在這裡定義test的所有target,當然也可以繼續分層,在每個test1, test2目錄下單獨維護xmake.lua,這個看自己項目的規模來決定。
比如:
add_defines("TEST")
includes("test1", "test2")
複制
test1/xmake.lua
target("test1")
set_kind("static")
add_files("test1/*.c")
add_defines("TEST1")
複制
test2/xmake.lua
target("test2")
set_kind("static")
add_files("test2/*.c")
add_defines("TEST2")
複制
而這裡面的
add_defines("TEST")
在root域,會對test1/test2兩個target都生效,但是對于demo目錄的target不生效,因為它們是平級的,沒有tree狀繼承關系。
跨xmake.lua間目标依賴
雖然,
projectdir/test/xmake.lua
和
projectdir/demo/xmake.lua
兩個子工程目錄是平級關系,配置無法互相幹擾,但是targets是可以跨xmake.lua通路的,來實作目标間的依賴。
比如demo需要依賴test靜态庫,進行連結使用,那麼demo下xmake.lua可以這麼寫:
target("demo1")
set_kind("binary")
add_files("demo1/*.c")
add_deps("test1")
複制
隻要通過
add_deps("test1")
關聯上對應其他子工程目标作為依賴即可,test1靜态庫會優先編譯,并且demo可執行程式會自動link上它生成的libtest1.a庫。
檔案路徑的層級關系
我們需要記住,所有跟路徑相關的配置接口,比如
add_files
,
add_includedirs
等都是相對于目前子工程xmake.lua所在的目錄的,是以隻要添加的檔案不跨子產品,那麼設定起來隻需要考慮目前的相對路徑就行了。
projectdir
- test
- xmake.lua
- test1/*.c
- test2/*.c
複制
比如,這裡添加的源檔案路徑,都是相對于test子工程目錄的,我們不需要去設定絕對路徑,這樣會簡化很多。
target("test1")
add_files("test1/*.c")
target("test2")
add_files("test2/*.c")
複制
當然,如果我們有特殊需求,非要設定工程其他子子產品下的檔案路徑呢?兩種辦法,通過
../../
的方式一層層繞出去,另外一種就是使用
$(projectdir)
内置變量,它表示項目全局根目錄。
比如在demo子工程下:
target("demo1")
set_kind("binary")
add_files("demo1/*.c")
add_files("../../test/test1/*.c")
複制
或者:
target("demo1")
set_kind("binary")
add_files("demo1/*.c")
add_files("$(projectdir)/test/test1/*.c")
複制
includes接口使用進階
錯誤的使用方式
includes這個接口屬于全局接口,不隸屬于任何target,是以請不要在target内部調用,下面是錯誤的用法:
target("test")
set_kind("static")
includes("test1", "test2")
add_files("test/*.c")
複制
正确的用法是:
includes("test1", "test2")
target("test")
set_kind("static")
add_files("test/*.c")
複制
或者:
target("test")
set_kind("static")
add_files("test/*.c")
target_end()
-- 在下面調用,需要先顯式退出target作用域
includes("test1", "test2")
複制
引用目錄或檔案
另外,includes既可以引用目錄,也可以直接引用檔案,如果test1目錄下存在xmake.lua,那麼可以直接
includes("test1")
來引用目錄。
如果test1目錄下是其他xxxx.lua指令的項目檔案,可以通過指定檔案來引用:
includes("test1/xxxx.lua")
,效果一樣的。
模式比對進行批量導入
includes還支援通過模式比對的方式來批量導入多個子工程,比如:
includes("test/*/xmake.lua")
複制
可以導入test目錄下,所有test1, test2等子工程目錄下的配置,如果是
**
還支援遞歸多級比對
includes("test/**/xmake.lua")
複制
通過模式比對,我們隻需要在test/xmake.lua一處地方進行includes,以後使用者在新增其他子工程xmake.lua,就會自動導入進來,非常友善。
注意事項
另外,在使用includes的過程中,需要注意的一點是,它不是c語言的
#include
,是以在目前配置中includes子配置,目前配置是不會有任何影響的,比如:
includes("xxx")
target("test")
-- ...
複制
上面includes了一些子工程,但是這些子工程的配置是不會幹擾目前test目标程式的。