xmake 是一個基于 Lua 的輕量級跨平台建構工具,使用 xmake.lua 維護項目建構,相比 makefile/CMakeLists.txt,配置文法更加簡潔直覺,對新手非常友好,短時間内就能快速入門,能夠讓使用者把更多的精力集中在實際的項目開發上。
這個版本,我們增加了大量重量級的新特性,例如:Nim 語言項目的建構支援,Keil MDK,Circle 和 Wasi 工具鍊支援。
另外,我們對 C++20 Modules 進行了大改進,不僅支援最新 gcc-11, clang 和 msvc 編譯器,而且還得子產品間依賴做了自動分析,實作最大程度的并行化編譯支援。
最後,還有一個比較有用的特性就是 Unity Build 支援,通過它我們可以對 C++ 代碼的編譯速度做到很大程度的提升。
項目源碼
官方文檔
入門課程
最近,我們新增了對 Nimlang 項目的建構支援,相關 issues 見:#1756
我們可以使用 <code>xmake create</code> 指令建立空工程。
完整例子見:Nimble Package Example
main.nim
完整例子見:Native Package Example
我們知道,C++ 代碼編譯速度通常很慢,因為每個代碼檔案都需要解析引入的頭檔案。
而通過 Unity Build,我們通過将多個 cpp 檔案組合成一個來加速項目的編譯,其主要好處是減少了解析和編譯包含在多個源檔案中的頭檔案内容的重複工作,頭檔案的内容通常占預處理後源檔案中的大部分代碼。
Unity 建構還通過減少編譯鍊建立和處理的目标檔案的數量來減輕由于擁有大量小源檔案而導緻的開銷,并允許跨形成統一建構任務的檔案進行過程間分析和優化(類似于效果連結時優化)。
它可以極大提升 C/C++ 代碼的編譯速度,通常會有 30% 的速度提升,不過根據項目的複雜程度不同,其帶來的效益還是要根據自身項目情況而定。
xmake 在 v2.5.9 版本中,也已經支援了這種構模組化式。相關 issues 見 #1019。
我們提供了兩個内置規則,分别處理對 C 和 C++ 代碼的 Unity Build。
預設情況下,隻要設定上述規則,就會啟用 Batch 模式的 Unity Build,也就是 xmake 自動根據項目代碼檔案,自動組織合并。
我們可以額外通過設定 <code>{batchsize = 2}</code> 參數到規則,來指定每個合并 Batch 的大小數量,這裡也就是每兩個 C++ 檔案自動合并編譯。
編譯效果大概如下:
由于我們僅僅啟用了 C++ 的 Unity Build,是以 C 代碼還是正常挨個編譯。另外在 Unity Build 模式下,我們還是可以做到盡可能的并行編譯加速,互不沖突。
如果沒有設定 <code>batchsize</code> 參數,那麼預設會吧所有檔案合并到一個檔案中進行編譯。
如果上面的 Batch 模式自動合并效果不理想,我們也可以使用自定義分組,來手動配置哪些檔案合并到一起參與編譯,這使得使用者更加地靈活可控。
我們使用 <code>{unity_group = "foo"}</code> 來指定每個分組的名字,以及包含了哪些檔案,每個分組的檔案都會單獨被合并到一個代碼檔案中去。
另外,<code>batchsize = 0</code> 也強行禁用了 Batch 模式,也就是說,沒有設定 unity_group 分組的代碼檔案,我們還是會單獨編譯它們,也不會自動開啟自動合并。
我們隻要把上面的 <code>batchsize = 0</code> 改成非 0 值,就可以讓分組模式下,剩餘的代碼檔案繼續開啟 Batch 模式自動合并編譯。
如果是 Batch 模式下,由于是自動合并操作,是以預設會對所有檔案執行合并,但如果有些代碼檔案我們不想讓它參與合并,那麼我們也可以通過 <code>{unity_ignored = true}</code> 去忽略它們。
盡管 Unity Build 帶啦的收益不錯,但是我們還是會遇到一些意外的情況,比如我們的兩個代碼檔案裡面,全局命名空間下,都存在相同名字的全局變量和函數。
那麼,合并編譯就會帶來編譯沖突問題,編譯器通常會報全局變量重定義錯誤。
為了解決這個問題,我們需要使用者代碼上做一些修改,然後配合建構工具來解決。
比如,我們的 foo.cpp 和 bar.cpp 都有全局變量 i。
foo.cpp
bar.cpp
那麼,我們合并編譯就會沖突,我們可以引入一個 Unique ID 來隔離全局的匿名空間。
接下來,我們還需要保證代碼合并後, <code>MY_UNITY_ID</code> 在 foo 和 bar 中的定義完全不同,可以按檔案名算一個唯一 ID 值出來,互不沖突,也就是實作下面的合并效果:
這看上去似乎很麻煩,但是使用者不需要關心這些,xmake 會在合并時候自動處理它們,使用者隻需要指定這個 Unique ID 的名字就行了,例如下面這樣:
處理全局變量,還有全局的重名宏定義,函數什麼的,都可以采用這種方式來避免沖突。
xmake 采用 <code>.mpp</code> 作為預設的子產品擴充名,但是也同時支援 <code>.ixx</code>, <code>.cppm</code>, <code>.mxx</code> 等擴充名。
早期,xmake 試驗性支援過 C++ Modules TS,但是那個時候,gcc 還不能很好的支援,并且子產品間的依賴也不支援。
最近,我們對 xmake 做了大量改進,已經完整支援 gcc-11/clang/msvc 的 C++20 Modules 建構支援,并且能夠自動分析子產品間的依賴關系,實作最大化并行編譯。
同時,對新版本的 clang/msvc 也做了更好地處理。
更多例子見:C++ Modules
上個版本,我們增加了對 Lua5.3 運作時支援,而在這個版本中,我們進一步更新 Lua 運作時到 5.4,相比 5.3,運作性能和記憶體使用率上都有很大的提升。
不過,目前 xmake 的預設運作時還是 luajit,預計 2.6.1 版本(也就是下個版本),會正式切到 Lua5.4 作為預設的運作時。
盡管切換了 Lua 運作時,但是對于使用者端,完全是無感覺的,并且完全相容現有工程配置,因為 xmake 原本就對暴露的 api 提供了一層封裝, 對于 lua 版本之間存在相容性問題的接口,例如 setfenv, ffi 等都隐藏在内部,原本就沒有暴露給使用者使用。
我們在這個版本中,還新增了 Keil/MDK 嵌入式編譯工具鍊的支援,相關例子工程:Example
xmake 會自動探測 Keil/MDK 安裝的編譯器,相關 issues #1753。
之前我們支援了 wasm 平台的 emcc 工具鍊來建構 wasm 程式,而這裡,我們新加了另外一個啟用了 WASI 的 Wasm 工具鍊來替換 emcc。
我們還新增了 circle 編譯器的支援,這是個新的 C++20 編譯器,額外附帶了一些有趣的編譯期元程式設計特性,有興趣的同學可以到官網檢視:https://www.circle-lang.org/
如果使用者額外安裝了 gcc-11, gcc-10 等特定版本的 gcc 工具鍊,在本地的 gcc 程式命名可能是 <code>/usr/bin/gcc-11</code>。
一種辦法是通過 <code>xmake f --cc=gcc-11 --cxx=gcc-11 --ld=g++-11</code> 挨個指定配置來切換,但非常繁瑣。
是以,xmake 也提供了更加快捷的切換方式:
隻需要指定 <code>gcc-11</code> 對應的版本名,就可以快速切換整個 gcc 工具鍊。
xmake 提供了 check_features 輔助接口來檢測編譯器特性。
config.h.in
config.h
而在 2.5.9 版本中,我們新增了 c++17 特性檢測:
<col>
特性名
cxx_aggregate_bases
cxx_aligned_new
cxx_capture_star_this
cxx_constexpr
cxx_deduction_guides
cxx_enumerator_attributes
cxx_fold_expressions
cxx_guaranteed_copy_elision
cxx_hex_float
cxx_if_constexpr
cxx_inheriting_constructors
cxx_inline_variables
cxx_namespace_attributes
cxx_noexcept_function_type
cxx_nontype_template_args
cxx_nontype_template_parameter_auto
cxx_range_based_for
cxx_static_assert
cxx_structured_bindings
cxx_template_template_args
cxx_variadic_using
還新增了 c++20 特性檢測:
cxx_aggregate_paren_init
cxx_char8_t
cxx_concepts
cxx_conditional_explicit
cxx_consteval
cxx_constexpr_dynamic_alloc
cxx_constexpr_in_decltype
cxx_constinit
cxx_designated_initializers
cxx_generic_lambdas
cxx_impl_coroutine
cxx_impl_destroying_delete
cxx_impl_three_way_comparison
cxx_init_captures
cxx_modules
cxx_using_enum
xmake 自帶的 xrepo 包管理工具,現在已經可以很好的支援包虛拟機環境管理,類似 nixos 的 nixpkgs。
我們可以通過在目前目錄下,添加 xmake.lua 檔案,定制化一些包配置,然後進入特定的包虛拟環境。
我們也可以在 xmake.lua 配置加載對應的工具鍊環境,比如加載 vs 的編譯環境。
我們可以使用下面的指令,把指定的虛拟環境配置全局注冊到系統中,友善快速切換。
這個時候,我們就儲存了一個名叫 base 的全局虛拟環境,我們可以通過 list 指令去檢視它。
我們也可以删除它。
如果我們注冊了多個虛拟環境,我們也可以快速切換它們。
或者直接加載指定虛拟環境運作特定指令
<code>xrepo env -b/--bind</code> 就是綁定指定的虛拟環境,更多詳情見:#1762
對于 target,我們新增了 <code>headeronly</code> 目标類型,這個類型的目标程式,我們不會實際編譯它們,因為它沒有源檔案需要被編譯。
但是它包含了頭檔案清單,這通常用于 headeronly 庫項目的安裝,IDE 工程的檔案清單生成,以及安裝階段的 cmake/pkgconfig 導入檔案的生成。
例如:
更多詳情見:#1747
現在 cmake 已經是事實上的标準,是以 CMake 提供的 find_package 已經可以查找大量的庫和子產品,我們完全複用 cmake 的這部分生态來擴充 xmake 對包的內建。
我們可以通過 <code>find_package("cmake::xxx")</code> 去借助 cmake 來找一些包,xmake 會自動生成一個 cmake 腳本來調用 cmake 的 find_package 去查找一些包,擷取裡面包資訊。
mydir/cmake_modules/FindFoo.cmake
相關 issues: #1632
我們進一步改進了 cmake 生成器,現在可以将 rule 裡面自定義的腳本序列化成指令清單,一起生成到 CMakelists.txt
不過目前隻能支援 batchcmds 系列腳本的序列化。
它将會生成類似如下的 CMakelists.txt
不過 cmake 的 <code>ADD_CUSTOM_COMMAND</code> PRE_BUILD 實際效果在不同生成器上,差異比較大,無法滿足我們的需求,是以我們做了很多處理來支援它。
相關 issues: #1735
我們還改進了 get.sh 安裝腳本,來更好地支援 nixOS。
#1736: 支援 wasi-sdk 工具鍊
支援 Lua 5.4 運作時
添加 gcc-8, gcc-9, gcc-10, gcc-11 工具鍊
#1623: 支援 find_package 從 cmake 查找包
#1747: 添加 <code>set_kind("headeronly")</code> 更好的處理 headeronly 庫的安裝
#1019: 支援 Unity build
#1438: 增加 <code>xmake l cli.amalgamate</code> 指令支援代碼合并
#1765: 支援 nim 語言
#1762: 為 <code>xrepo env</code> 管理和切換指定的環境配置
#1767: 支援 Circle 編譯器
#1753: 支援 Keil/MDK 的 armcc/armclang 工具鍊
#1774: 添加 table.contains api
#1735: 添加自定義指令到 cmake 生成器
#1781: 改進 get.sh 安裝腳本支援 nixos
#1528: 檢測 c++17/20 特性
#1729: 改進 C++20 modules 對 clang/gcc/msvc 的支援,支援子產品間依賴編譯和并行優化
#1779: 改進 ml.exe/x86,移除内置的 <code>-Gd</code> 選項