標籤:

使用xmake優雅地描述工程

描述語法

xmake的描述語法基於lua實現,因此描述語法繼承了lua的靈活性和簡潔性,並且通過28原則,將描述作用域(簡單描述)、腳本作用域(複雜描述)進行分離,使得工程更加的簡潔直觀,可讀性非常好。

因為80%的工程,並不需要很複雜的腳本控制邏輯,只需要簡單的幾行配置描述,就可滿足構建需求,基於這個假設,xmake分離作用域,使得80%的xmake.lua文件,只需要這樣描述:

target("demo")n set_kind("binary")n add_files("src/*.c")n

而僅有的20%的工程,才需要這樣描述:

target("demo")n set_kind("shared")n set_objectdir("$(buildir)/.objs")n set_targetdir("libs/armeabi")n add_files("jni/*.c")nn on_package(function (target) n os.run("ant debug") n end)nn on_install(function (target) n os.run("adb install -r ./bin/Demo-debug.apk")n end)nn on_run(function (target) n os.run("adb shell am start -n com.demo/com.demo.DemoTest")n os.run("adb logcat")n end)n

上面的function () end部分屬於自定義腳本域,一般情況下是不需要設置的,只有在需要複雜的工程描述、高度定製化需求的情況下,才需要自定義他們,在這個作用域可以使用各種xmake提供的擴展模塊,關於這個的更多介紹,見:xmake 描述語法和作用域詳解。

而上面的代碼,也是一個自定義混合構建jni和java代碼的android工程,可以直接通過xmake run命令,實現一鍵自動構建、安裝、運行apk程序。

下面介紹一些比較常用的xmake描述實例:

構建一個可執行程序

target("demo")n set_kind("binary")n add_files("src/*.c")n

這是一個最簡單經典的實例,一般情況下,這種情況,你不需要自己寫任何xmake.lua文件,在當前代碼目錄下,直接執行xmake命令,就可以完成構建,並且會自動幫你生成一個xmake.lua。

關於自動生成的詳細信息,見:xmake智能代碼掃描編譯模式,無需手寫任何make文件。

構建一個可配置切換的庫程序

target("demo")n set_kind("$(kind)")n add_files("src/*.c")n

可通過配置,切換是否編譯動態庫還是靜態庫:

$ xmake f --kind=static; xmaken$ xmake f --kind=shared; xmaken

增加debug和release編譯模式支持

也許默認的幾行描述配置,已經不能滿足你的需求,你需要可以通過切換編譯模式,構建debug和release版本的程序,那麼只需要:

if is_mode("debug") thenn set_symbols("debug")n set_optimize("none")nendnnif is_mode("release") thenn set_symbols("hidden")n set_optimize("fastest")n set_strip("all")nendnntarget("demo")n set_kind("binary")n add_files("src/*.c") n

你只需要通過配置來切換構建模式:

$ xmake f -m debug; xmaken$ xmake f -m release; xmaken

[-m|--mode]屬於內置選項,不需要自己定義option,就可使用,並且模式的值是用戶自己定義和維護的,你可以在is_mode("xxx")判斷各種模式狀態。

通過自定義腳本簽名ios程序

ios的可執行程序,在設備上運行,需要在構建完成後進行簽名,這個時候就可以使用自定義腳本來實現:

target("demo")n set_kind("binary")n add_files("src/*.m") n after_build(function (target))n os.run("ldid -S %s", target:targetfile())n endn

這裡只是用ldid程序做了個假簽名,只能在越獄設備上用哦,僅僅作為例子參考哈。

內置變數和外置變數

xmake提供了$(varname)的語法,來支持內置變數的獲取,例如:

add_cxflags("-I$(buildir)")n

它將會在在實際編譯的時候,將內置的buildir變數轉換為實際的構建輸出目錄:-I./build

一般內置變數可用於在傳參時快速獲取和拼接變數字元串,例如:

target("test")n add_files("$(projectdir)/src/*.c")n add_includedirs("$(buildir)/inc")n

也可以在自定義腳本的模塊介面中使用,例如:

target("test")n on_run(function (target)n os.cp("$(scriptdir)/xxx.h", "$(buildir)/inc")n end)n

當然這種變數模式,也是可以擴展的,默認通過xmake f --var=val命令,配置的參數都是可以直接獲取,例如:

target("test")n add_defines("-DTEST=$(var)")n

既然支持直接從配置選項中獲取,那麼當然也就能很方便的擴展自定義的選項,來獲取自定義的變數了,具體如何自定義選項見:option

修改目標文件名

我們可以通過內建變數,將生成的目標文件按不同架構和平台進行分離,例如:

target("demo")n set_kind("binary")n set_basename("demo_$(arch)")n set_targetdir("$(buildir)/$(plat)")n

之前的默認設置,目標文件會生成為builddemo,而通過上述代碼的設置,目標文件在不同配置構建下,路徑和文件名也不盡相同,執行:

$ xmake f -p iphoneos -a arm64; xmaken

則目標文件為:build/iphoneos/demo_arm64。

添加子目錄工程模塊

如果你有多個target子模塊,那麼可以在一個xmake.lua中進行定義,例如:

target("demo")n set_kind("binary")n add_files("src/demo.c")nntarget("test")n set_kind("binary")n add_files("src/test.c")n

但是,如果子模塊非常多,那麼放置在一個xmake文件,就顯得有些臃腫了,可以放置到獨立模塊的子目錄去:

target("demo")n set_kind("binary")n add_files("src/demo.c")nnadd_subdirs("src/test")n

通過上述代碼,關聯一個子工程目錄,在裡面加上test的工程目標就行了。

安裝頭文件

target("tbox")n set_kind("static")n add_files("src/*.c")nn add_headers("../(tbox/**.h)|**/impl/**.h")n set_headerdir("$(buildir)/inc")n

安裝好的頭文件位置和目錄結構為:build/inc/tbox/*.h。

其中../(tbox/**.h)帶括弧的部分,為實際要安裝的根路徑,|**/impl/**.h部分用於排除不需要安裝的文件。

其通配符匹配規則、排除規則可參考add_files。

多目標依賴構建

多個target工程目標,默認構建順序是未定義的,一般按順序的方式進行,如果你需要調整構建順序,可以通過添加依賴順序來實現:

target("test1")n set_kind("static")n set_files("*.c")nntarget("test2")n set_kind("static")n set_files("*.c")nntarget("demo")n add_deps("test1", "test2")n add_links("test1", "test2")n

上面的例子,在編譯目標demo的時候,需要先編譯test1, test2目標,因為demo會去用到它們。

合併靜態庫

xmake的add_files介面功能是非常強大的,不僅可以支持多種語言文件的混合添加構建,還可以直接添加靜態庫,進行自動合併庫到當前的工程目標中去。

我們可以這麼寫:

target("demo")n set_kind("static")n add_files("src/*.c", "libxxx.a", "lib*.a", "xxx.lib")n

直接在編譯靜態庫的時候,合併多個已有的靜態庫,注意不是鏈接哦,這跟add_links是有區別的。

並且你也可以直接追加對象文件:

target("demo")n set_kind("binary")n add_files("src/*.c", "objs/*.o")n

添加自定義配置選項

我們可以自己定義一個配置選項,例如用於啟用test:

option("test")n set_default(false)n set_showmenu(true)n add_defines("-DTEST")n

然後關聯到指定的target中去:

target("demo")n add_options("test")n

這樣,一個選項就算定義好了,如果這個選項被啟用,那麼編譯這個target的時候,就會自動加上-DTEST的宏定義。

上面的設置,默認是禁用test選項的,接下來我們通過配置去啟用這個選項:

$ xmake f --test=yn$ xmaken

xmake的選項支持是非常強大的,除了上述基礎用法外,還可以配置各種檢測條件,實現自動檢測,具體詳情可參考:option和依賴包的添加和自動檢測機制。

添加第三方依賴包

在target作用域中,添加集成第三方包依賴,例如:

target("test")n set_kind("binary")n add_packages("zlib", "polarssl", "pcre", "mysql")n

這樣,在編譯test目標時,如果這個包存在的,將會自動追加包裡面的宏定義、頭文件搜索路徑、鏈接庫目錄,也會自動鏈接包中所有庫。

用戶不再需要自己單獨調用add_links,add_includedirs, add_ldflags等介面,來配置依賴庫鏈接了。

對於如何設置包搜索目錄,可參考add_packagedirs介面,依賴包詳情請參考:依賴包的添加和自動檢測機制。

生成配置頭文件

如果你想在xmake配置項目成功後,或者自動檢測某個選項通過後,把檢測的結果寫入配置頭文件,那麼需要調用這個介面來啟用自動生成config.h文件。

使用方式例如:

target("test")n set_config_h("$(buildir)/config.h")n set_config_h_prefix("TB_CONFIG")n

當這個target中通過下面的這些介面,對這個target添加了相關的選項依賴、包依賴、介面依賴後,如果某依賴被啟用,那麼對應的一些宏定義配置,會自動寫入被設置的config.h文件中去。

  • add_options
  • add_packages
  • add_cfuncs
  • add_cxxfuncs

這些介面,其實底層都用到了option選項中的一些檢測設置,例如:

option("wchar")nn -- 添加對wchar_t類型的檢測n add_ctypes("wchar_t")nn -- 如果檢測通過,自動生成TB_CONFIG_TYPE_HAVE_WCHAR的宏開關到config.hn add_defines_h_if_ok("$(prefix)_TYPE_HAVE_WCHAR")nntarget("test")nn -- 啟用頭文件自動生成n set_config_h("$(buildir)/config.h")n set_config_h_prefix("TB_CONFIG")nn -- 添加對wchar選項的依賴關聯,只有加上這個關聯,wchar選項的檢測結果才會寫入指定的config.h中去n add_options("wchar")n

檢測庫頭文件和介面

我們可以在剛剛生成的config.h中增加一些庫介面檢測,例如:

target("demo")nn -- 設置和啟用config.hn set_config_h("$(buildir)/config.h")n set_config_h_prefix("TEST")nn -- 僅通過參數一設置模塊名前綴n add_cfunc("libc", nil, nil, {"sys/select.h"}, "select")nn -- 通過參數三,設置同時檢測鏈接庫:libpthread.an add_cfunc("pthread", nil, "pthread", "pthread.h", "pthread_create")nn -- 通過參數二設置介面別名n add_cfunc(nil, "PTHREAD", nil, "pthread.h", "pthread_create")n

生成的config.h結果如下:

#ifndef TEST_Hn#define TEST_Hnn// 宏命名規則:$(prefix)前綴 _ 模塊名(如果非nil)_ HAVE _ 介面名或者別名 (大寫)n#define TEST_LIBC_HAVE_SELECT 1n#define TEST_PTHREAD_HAVE_PTHREAD_CREATE 1n#define TEST_HAVE_PTHREAD 1nn#endifn

這樣我們在代碼裡面就可以根據介面的支持力度來控制代碼編譯了。

自定義插件任務

task域用於描述一個自定義的任務實現,與target和option同級。

例如,這裡定義一個最簡單的任務:

task("hello")nn -- 設置運行腳本n on_run(function ()n print("hello xmake!")n end)n

這個任務只需要列印hello xmake!,那如何來運行呢?

由於這裡沒有使用set_menu設置菜單,因此這個任務只能在xmake.lua的自定義腳本或者其他任務內部調用,例如:

target("test")nn after_build(function (target)nn -- 導入task模塊n import("core.project.task")nn -- 運行hello任務n task.run("hello")n end)n

此處在構建完test目標後運行hello任務,當然我們還可以傳遞參數哦:

task("hello")n on_run(function (arg1, arg2, arg3)n print("hello xmake!", arg1, arg2, arg3)n end)nntarget("test")n after_build(function (target)n import("core.project.task")n task.run("hello", {}, "arg1", "arg2", "arg3")n end)n

上述task.run的{}這個是用於傳遞插件菜單中的參數,這裡沒有通過set_menu設置菜單,此處傳空。

xmake的插件支持也是功能很強大的,並且提供了很多內置的使用插件,具體請參考:xmake插件手冊和task手冊

或者可以參考xmake自帶的一些插件demo。

另外一種語法風格

xmake除了支持最常使用的set-add描述風格外,還支持另外一種語法風格:key-val,例如:

targetn{n name = "test",n defines = "DEBUG",n files = {"src/*.c", "test/*.cpp"}n}n

這個等價於:

target("test")n set_kind("static")n add_defines("DEBUG")n add_files("src/*.c", "test/*.cpp")n

用戶可以根據自己的喜好來選擇合適的風格描述,但是這邊的建議是:

* 針對簡單的工程,不需要太過複雜的條件編譯,可以使用key-val方式,更加精簡,可讀性好n* 針對複雜工程,需要更高的可控性,和靈活性的話,建議使用set-add方式n* 盡量不要兩種風格混著寫,雖然是支持的,但是這樣對整個工程描述會感覺很亂,因此盡量統一風格作為自己的描述規範n

另外,不僅對target,像option, task, template都是支持兩種方式設置的,例如:

-- set-add風格noption("demo")n set_default(true)n set_showmenu(true)n set_category("option")n set_description("Enable or disable the demo module", " =y|n")n

-- key-val風格noptionn{n name = "demo",n default = true,n showmenu = true,n category = "option",n desciption = {"Enable or disable the demo module", " =y|n"}n}n

自定義的任務或者插件可以這麼寫:

-- set-add風格ntask("hello")n on_run(function ()n print("hello xmake!")nn end)n set_menu {n usage = "xmake hello [options]",n description = "Hello xmake!",n options = {}n }n

-- key-val風格ntaskn{n name = "hello",n run = (function ()n print("hello xmake!")n end),n menu = {n usage = "xmake hello [options]",n description = "Hello xmake!",n options = {}n }n}n

結語

更多描述說明,可直接閱讀xmake的官方手冊,上面提供了完整的api文檔和使用描述。

推薦閱讀:

解決 Windows 下 Python 安裝 Dlib 的問題:Cmake 找不到 boost
vs2017怎麼用內置CMAKE編譯opencv??
macOS Sierra10.12.6下安裝OpenCV3.3.0

TAG:CMake | Makefile |