Xmake v2.7.8 發佈,改進包虛擬環境和構建速度

Xmake 是一個基於 Lua 的輕量級跨平臺構建工具。

它非常的輕量,沒有任何依賴,因爲它內置了 Lua 運行時。

它使用 xmake.lua 維護項目構建,相比 makefile/CMakeLists.txt,配置語法更加簡潔直觀,對新手非常友好,短時間內就能快速入門,能夠讓用戶把更多的精力集中在實際的項目開發上。

我們能夠使用它像 Make/Ninja 那樣可以直接編譯項目,也可以像 CMake/Meson 那樣生成工程文件,另外它還有內置的包管理系統來幫助用戶解決 C/C++ 依賴庫的集成使用問題。

目前,Xmake 主要用於 C/C++ 項目的構建,但是同時也支持其他 native 語言的構建,可以實現跟 C/C++ 進行混合編譯,同時編譯速度也是非常的快,可以跟 Ninja 持平。

Xmake = Build backend + Project Generator + Package Manager + [Remote|Distributed] Build + Cache

儘管不是很準確,但我們還是可以把 Xmake 按下面的方式來理解:

Xmake ≈ Make/Ninja + CMake/Meson + Vcpkg/Conan + distcc + ccache/sccache

新特性介紹

快速切換臨時虛擬環境

Xmake 很早就支持了包的虛擬環境管理,可以通過配置文件的方式,實現不同包環境之間的切換。

我們可以通過在當前目錄下,添加 xmake.lua 文件,定製化一些包配置,然後進入特定的包虛擬環境。

add_requires("zlib 1.2.11")
add_requires("python 3.x", "luajit")
$ xrepo env shell
> python --version
> luajit --version

也可以通過導入自定義環境配置文件,來切換環境:

$ xrepo env --add /tmp/base.lua
$ xrepo env -b base shell

而在新版本中,我們進一步做了改進,讓 Xrepo 能夠直接在命令行臨時指定需要綁定的環境包列表,實現快速切換,無需任何配置。

並且支持同時指定多個包環境。

例如,我們想進入一個帶有 python 3.0, luajit 和 cmake 的環境,只需要執行:

$ xrepo env -b "python 3.x,luajit,cmake" shell
[python,luajit,cmake] $ python --version
Python 3.10.6
[python,luajit,cmake] $ cmake --version
cmake version 3.25.3

Xmake 會自動安裝相關依賴,然後開啓一個新的 shell 環境,新環境終端左邊也有 prompt 提示。

如果我們想退出當前環境,僅僅需要執行

[python,luajit,cmake] $ xrepo env quit
$

改進代碼特性檢測

has_cfuncs/check_cxxsnippets 等系列檢測接口,在 option 中已經有提供,並且有對應的輔助 API 來幫助檢測。

相關文檔可以參考:輔助檢測接口

但是目前 option 提供的檢測接口僅僅針對全局平臺工具鏈,無法根據每個特定的 target 配置在針對性做一些檢測。

因爲 target 本身可能還會附帶依賴包,不同的工具鏈,編譯宏等差異性,檢測結果也會有一些差異。

因此,如果用戶想要更加靈活細粒度的檢測每個 target 目標的編譯特性,可以通過新版本提供的 target 目標實例接口。

  • target:has_cfuncs
  • target:has_cxxfuncs
  • target:has_ctypes
  • target:has_cxxtypes
  • target:has_cincludes
  • target:has_cxxincludes
  • target:has_cflags
  • target:has_cxxflags
  • target:has_features
  • target:check_csnippets
  • target:check_cxxsnippets

這裏,僅僅針對其中一些比較常用的接口,稍微展開介紹下使用方式。

target:has_cfuncs

  • 檢測目標編譯配置能否獲取給定的 C 函數

這應該在 on_config 中使用,比如可以用它來判斷當前目標能否獲取到 zlib 依賴包的一些函數接口,然後自動定義 HAVE_INFLATE

add_requires("zlib")
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib")
    on_config(function (target)
        if target:has_cfuncs("inflate", {includes = "zlib.h"}) then
            target:add("defines", "HAVE_INFLATE")
        end
    end)

儘管 option 也提供了類似的檢測功能,但 option 的檢測使用的是全局的平臺工具鏈,它無法附帶上 target 相關的一些編譯配置,
也無法根據 target 設置不同編譯工具鏈來適配檢測,並且無法檢測包裏面的一些接口。

如果我們僅僅是想粗粒度的檢測函數接口,並且 target 沒有額外設置不同的工具鏈,那麼 option 提供的檢測功能已經足夠使用了。

如果想要更細粒度控制檢測,可以使用 target 實例接口提供的檢測特性。

target:has_cxxfuncs

  • 檢測目標編譯配置能否獲取給定的 C++ 函數

用法跟 target:has_cfuncs 類似,只是這裏主要用於檢測 C++ 的函數。

不過,在檢測函數的同時,我們還可以額外配置 std languages,來輔助檢測。

target:has_cxxfuncs("foo", {includes = "foo.h", configs = {languages = "cxx17"}})

target:has_ctypes

  • 檢測目標編譯配置能否獲取給定的 C 類型

這應該在 on_config 中使用,如下所示:

add_requires("zlib")
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib")
    on_config(function (target)
        if target:has_ctypes("z_stream", {includes = "zlib.h"}) then
            target:add("defines", "HAVE_ZSTEAM_T")
        end
    end)

target:has_cflags

  • 檢測目標編譯配置能否獲取給定的 C 編譯 flags
target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    on_config(function (target)
        if target:has_cxxflags("-fPIC") then
            target:add("defines", "HAS_PIC")
        end
    end)

target:has_cincludes

  • 檢測目標編譯配置能否獲取給定的 C 頭文件

這應該在 on_config 中使用,比如可以用它來判斷當前目標能否獲取到 zlib 依賴包的 zlib.h 頭文件,然後自動定義 HAVE_INFLATE

add_requires("zlib")
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib")
    on_config(function (target)
        if target:has_cincludes("zlib.h") then
            target:add("defines", "HAVE_ZLIB_H")
        end
    end)

target:check_cxxsnippets

  • 檢測是否可以編譯和鏈接給定的 C++ 代碼片段

這應該在 on_config 中使用,如下所示:

add_requires("libtins")
target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    add_packages("libtins")
    on_config(function (target)
        local has_snippet = target:check_cxxsnippets({test = [[
            #include <string>
            using namespace Tins;
            void test() {
                std::string name = NetworkInterface::default_interface().name();
                printf("%s\n", name.c_str());
            }
        ]]}, {configs = {languages = "c++11"}, includes = {"tins/tins.h"}}))
        if has_snippet then
            target:add("defines", "HAS_XXX")
        end
    end)

默認僅僅檢測編譯鏈接是否通過,如果想要嘗試運行時檢測,可以再設置 tryrun = true

target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    on_config(function (target)
        local has_int_4 = target:check_cxxsnippets({test = [[
            return (sizeof(int) == 4)? 0 : -1;
        ]]}, {configs = {languages = "c++11"}, tryrun = true}))
        if has_int_4 then
            target:add("defines", "HAS_INT4")
        end
    end)

我們也可以繼續通過設置 output = true 來捕獲檢測的運行輸出,並且加上自定義的 main 入口,實現完整的測試代碼,而不僅僅是代碼片段。

target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    on_config(function (target)
        local int_size = target:check_cxxsnippets({test = [[
            #include <stdio.h>
            int main(int argc, char** argv) {
                printf("%d", sizeof(int)); return 0;
                return 0;
            }
        ]]}, {configs = {languages = "c++11"}, tryrun = true, output = true}))
    end)

target:has_features

  • 檢測是否指定的 C/C++ 編譯特性

它相比使用 check_cxxsnippets 來檢測,會更加快一些,因爲它僅僅執行一次預處理就能檢測所有的編譯器特性,而不是每次都去調用編譯器嘗試編譯。

target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    on_config(function (target)
        if target:has_features("c_static_assert") then
            target:add("defines", "HAS_STATIC_ASSERT")
        end
        if target:has_features("cxx_constexpr") then
            target:add("defines", "HAS_CXX_CONSTEXPR")
        end
    end)

優化編譯性能

Xmake 的 build cache 加速類似 ccache,採用預處理器計算 hash 後緩存編譯對象文件來實現加速,它在 linux/mac 上提速效果非常明顯。

而由於 msvc 的預處理器很慢,也可能是起進程相比 linux/mac 下更重,導致開啓 build cache 後,windows 上使用 msvc 的整體編譯效率反而慢了非常多。

嘗試使用第三方的 ccache 來測試對比,也是一樣的問題,因此我暫時針對 msvc 默認禁用了 build cache,使得整體構建速度恢復到正常水平。

clang-tidy 自動修復

上個版本,我們新增了對 clang-tidy 支持,可以通過 xmake check clang.tidy 來檢測代碼。
而在這個版本中,我們繼續對它做了改進,新增了 --fix 參數,可以讓 clang-tidy 去自動修復檢測出來的問題代碼。

$ xmake check clang.tidy --fix
$ xmake check clang.tidy --fix_errors
$ xmake check clang.tidy --fix_notes

Swig/Java 模塊構建支持

另外,其他用戶也幫忙貢獻了 Swig/Java 模塊的構建支持。

add_rules("mode.release", "mode.debug")

target("example")
    set_kind('shared')
    -- set moduletype to java
    add_rules("swig.c", {moduletype = "java"})
    -- use swigflags to provider package name and output path of java files
    add_files("src/example.i", {swigflags = {
        "-package",
        "com.example",
        "-outdir",
        "build/java/com/example/"
    }})
    add_files("src/example.c")
    before_build(function()
        -- ensure output path exists before running swig
        os.mkdir("build/java/com/example/")
    end)

完整例子見:Swig/Java Example

開源之夏 2023

今年 Xmake 社區繼續參加了開源之夏 2023 活動,它是由中科院軟件所“開源軟件供應鏈點亮計劃”發起並長期支持的一項暑期開源活動
旨在鼓勵在校學生積極參與開源軟件的開發維護。

如果有感興趣的同學,歡迎報名參與 Xmake 社區發佈的項目開發(具體項目待定中),相關詳情進展,請關注:Xmake 開源之夏

更新內容

新特性

  • #3518: 分析編譯和鏈接性能
  • #3522: 爲 target 添加 has_cflags, has_xxx 等輔助接口
  • #3537: 爲 clang.tidy 檢測器添加 --fix 自動修復

改進

  • #3433: 改進 QT 在 msys2/mingw64 和 wasm 上的構建支持
  • #3419: 支持 fish shell 環境
  • #3455: Dlang 增量編譯支持
  • #3498: 改進綁定包虛擬環境
  • #3504: 添加 swig java 支持
  • #3508: 改進 trybuild/cmake 去支持工具鏈切換
  • 爲 msvc 禁用 build cache 加速,因爲 msvc 的預處理器太慢,反而極大影響構建性能。

Bugs 修復

  • #3436: 修復自動補全和 menuconf
  • #3463: 修復 c++modules 緩存問題
  • #3545: 修復 armcc 的頭文件依賴解析
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章