天天看點

xmake從入門到精通11:如何組織建構大型工程

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目标程式的。