xmake v2.5.2 發佈, 支持自動拉取交叉工具鏈和依賴包集成

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

在 2.5.2 版本中,我們增加了一個重量級的新特性:自動拉取遠程交叉編譯工具鏈

這是用來幹什麼的呢,做過交叉編譯以及有 C/C++ 項目移植經驗的同學應該知道,折騰各種交叉編譯工具鏈,移植編譯項目是非常麻煩的一件事,需要自己下載對應工具鏈,並且配置工具鏈和編譯環境很容易出錯導致編譯失敗。

現在,xmake 已經可以支持自動下載項目所需的工具鏈,然後使用對應工具鏈直接編譯項目,用戶不需要關心如何配置工具鏈,任何情況下只需要執行 xmake 命令即可完成編譯。

甚至對於 C/C++ 依賴包的集成,也可以自動切換到對應工具鏈編譯安裝集成,一切完全自動化,完全不需要用戶操心。

除了交叉編譯工具鏈,我們也可以自動拉取工具鏈,比如特定版本的 llvm,llvm-mingw, zig 等各種工具鏈來參與編譯 C/C++/Zig 項目的編譯。

即使是 cmake 也還不支持工具鏈的自動拉取,頂多只能配合 vcpkg/conan 等第三方包管理對 C/C++ 依賴包進行集成,另外,即使對於 C/C++依賴包,xmake 也有自己原生內置的包管理工具,完全無任何依賴。

  • 項目源碼

  • 官方文檔

  • 入門課程

新特性介紹

自動拉取遠程交叉編譯工具鏈

從 2.5.2 版本開始,我們可以拉取指定的工具鏈來集成編譯項目,我們也支持將依賴包切換到對應的遠程工具鏈參與編譯後集成進來。

相關例子代碼見:Toolchain/Packages Examples

相關的 issue #1217

當前,我們已經在 xmake-repo 倉庫收錄了以下工具鏈包,可以讓 xmake 遠程拉取集成:

  • llvm

  • llvm-mingw

  • gnu-rm

  • muslcc

  • zig

雖然現在支持的工具鏈包不多,當但是整體架構已經打通,後期我們只需要收錄更多的工具鏈進來就行,比如:gcc, tinyc, vs-buildtools 等工具鏈。

由於 xmake 的包支持語義版本,因此如果項目依賴特定版本的 gcc/clang 編譯器,就不要用戶去折騰安裝了,xmake 會自動檢測當前系統的 gcc/clang 版本是否滿足需求。

如果版本不滿足,那麼 xmake 就會走遠程拉取,自動幫用戶安裝集成特定版本的 gcc/clang,然後再去編譯項目。

拉取指定版本的 llvm 工具鏈

我們使用 llvm-10 中的 clang 來編譯項目。

add_requires("llvm 10.x", {alias = "llvm-10"})
target("test")
    set_kind("binary")
    add_files("src/*.c)
    set_toolchains("
llvm@llvm-10")

其中,llvm@llvm-10 的前半部分爲工具鏈名,也就是 toolchain("llvm"),後面的名字是需要被關聯工具鏈包名,也就是 package("llvm"),不過如果設置了別名,那麼會優先使用別名:llvm-10

另外,我們後續還會增加 gcc 工具鏈包到 xmake-repo,使得用戶可以自由切換 gcc-10, gcc-11 等特定版本的 gcc 編譯器,而無需用戶去手動安裝。

拉取交叉編譯工具鏈

我們也可以拉取指定的交叉編譯工具鏈來編譯項目。

add_requires("muslcc")
target("test")
    set_kind("binary")
    add_files("src/*.c)
    set_toolchains("
@muslcc")

muslcc 是 https://musl.cc 提供的一款交叉編譯工具鏈,默認 xmake 會自動集成編譯 x86_64-linux-musl- 目標平臺。

當然,我們也可以通過 xmake f -a arm64 切換到 aarch64-linux-musl- 目標平臺來進行交叉編譯。

拉取工具鏈並且集成對應工具鏈編譯的依賴包

我們也可以使用指定的muslcc交叉編譯工具鏈去編譯和集成所有的依賴包。

add_requires("muslcc")
add_requires("zlib""libogg", {system = false})

set_toolchains("@muslcc")

target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib""libogg")

這個時候,工程裏面配置的 zlib, libogg 等依賴包,也會切換使用 muslcc 工具鏈,自動下載編譯然後集成進來。

我們也可以通過 set_plat/set_arch 固定平臺,這樣只需要一個 xmake 命令,就可以完成整個交叉編譯環境的集成以及架構切換。

add_requires("muslcc")
add_requires("zlib""libogg", {system = false})

set_plat("cross")
set_arch("arm64")
set_toolchains("@muslcc")

target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib""libogg")

完整例子見:Examples (muslcc)

拉取集成 Zig 工具鏈

xmake 會先下載特定版本的 zig 工具鏈,然後使用此工具鏈編譯 zig 項目,當然用戶如果已經自行安裝了 zig 工具鏈,xmake 也會自動檢測對應版本是否滿足,如果滿足需求,那麼會直接使用它,無需重複下載安裝。

add_rules("mode.debug""mode.release")
add_requires("zig 0.7.x")

target("test")
    set_kind("binary")
    add_files("src/*.zig")
    set_toolchains("@zig")

增加對 zig cc 編譯器支持

zig cc 是 zig 內置的 c/c++ 編譯器,可以完全獨立進行 c/c++ 代碼的編譯和鏈接,完全不依賴 gcc/clang/msvc,非常給力。

因此,我們完全可以使用它來編譯 c/c++ 項目,關鍵是 zig 的工具鏈還非常輕量,僅僅幾十M 。

我們只需要切換到 zig 工具鏈即可完成編譯:

$ xmake f --toolchain=zig
$ xmake
[ 25%]: compiling.release src/main.c
"zig cc" -c -arch x86_64 -fvisibility=hidden -O3 -DNDEBUG -o build/.objs/xmake_test/macosx/x86_64/release/src/main.c.o src/main.c
[ 50%]: linking.release test
"zig c++" -o build/macosx/x86_64/release/test build/.objs/xmake_test/macosx/x86_64/release/src/main.c.o -arch x86_64 -stdlib=libc++ -Wl,-x -lz
[100%]: build ok!

另外,zig cc 的另外一個強大之處在於,它還支持不同架構的交叉編譯,太 happy 了。

通過 xmake,我們也只需再額外切換下架構到 arm64,即可實現對 arm64 的交叉編譯,例如:

$ xmake f -a arm64 --toolchain=zig
$ xmake
[ 25%]: compiling.release src/main.c
"zig cc" -c -target aarch64-macos-gnu -arch arm64 -fvisibility=hidden -O3 -DNDEBUG -o build/.objs/xmake_test/macosx/arm64/release/src/main.c.o src/main.c
checking for flags (-MMD -MF) ... ok
checking for flags (-fdiagnostics-color=always) ... ok
[ 50%]: linking.release xmake_test
"zig c++" -o build/macosx/arm64/release/xmake_test build/.objs/xmake_test/macosx/arm64/release/src/main.c.o -target aarch64-macos-gnu -arch arm64 -stdlib=libc++ -Wl,-x -lz
[100%]: build ok!

即使你是在在 macOS,也可以用 zig cc 去交叉編譯 windows/x64 目標程序,相當於替代了 mingw 乾的事情。

$ xmake f -p windows -a x64 --toolchain=zig
$ xmake

自動導出所有 windows/dll 中的符號

cmake 中有這樣一個功能:WINDOWS_EXPORT_ALL_SYMBOLS,安裝 cmake 文檔中的說法:

https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html

Enable this boolean property to automatically create a module definition (.def) file with all global symbols found
in the input .obj files for a SHARED library (or executable with ENABLE_EXPORTS) on Windows.
The module definition file will be passed to the linker causing all symbols to be exported from the .dll. For global data symbols,
__declspec(dllimport) must still be used when compiling against the code in the .dll. All other function symbols will be automatically exported and imported by callers.
This simplifies porting projects to Windows by reducing the need for explicit dllexport markup, even in C++ classes.

大體意思就是:

啓用此布爾屬性,可以自動創建一個模塊定義(.def)文件,其中包含在Windows上的共享庫(或使用ENABLE_EXPORTS的可執行文件)的輸入.obj文件中找到的所有全局符號。
模塊定義文件將被傳遞給鏈接器,使所有符號從.dll中導出。對於全局數據符號,當對.dll中的代碼進行編譯時,仍然必須使用__declspec(dllimport)。
所有其它的函數符號將被調用者自動導出和導入。這就簡化了將項目移植到 Windows 的過程,減少了對顯式 dllexport 標記的需求,甚至在 C++ 類中也是如此。

現在,xmake 中也提供了類似的特性,可以快速全量導出 windows/dll 中的符號,來簡化對第三方項目移植過程中,對符號導出的處理。另外,如果項目中的符號太多,也可以用此來簡化代碼中的顯式導出需求。

我們只需在對應生成的 dll 目標上,配置 utils.symbols.export_all 規則即可。

target("foo")
    set_kind("shared")
    add_files("src/foo.c")
    add_rules("utils.symbols.export_all")

target("test")
    set_kind("binary")
    add_deps("foo")
    add_files("src/main.c")

xmake 會自動掃描所有的 obj 對象文件,然後生成 def 符號導出文件,傳入 link.exe 實現快速全量導出。

轉換 mingw/.dll.a 到 msvc/.lib

這個特性也是參考自 CMAKE_GNUtoMS 功能,可以把MinGW生成的動態庫(xxx.dll & xxx.dll.a)轉換成Visual Studio可以識別的格式(xxx.dll & xxx.lib),從而實現混合編譯。

這個功能對Fortran & C++混合項目特別有幫助,因爲VS不提供fortran編譯器,只能用MinGW的gfortran來編譯fortran部分,然後和VS的項目鏈接。
往往這樣的項目同時有一些其他的庫以vs格式提供,因此純用MinGW編譯也不行,只能使用cmake的這個功能來混合編譯。

因此,xmake 也提供了一個輔助模塊接口去支持它,使用方式如下:

import("utils.platform.gnu2mslib")

gnu2mslib("xxx.lib""xxx.dll.a")
gnu2mslib("xxx.lib""xxx.def")
gnu2mslib("xxx.lib""xxx.dll.a", {dllname = "xxx.dll", arch = "x64"})

支持從 def 生成 xxx.lib ,也支持從 xxx.dll.a 自動導出 .def ,然後再生成 xxx.lib

具體細節見:issue #1181

實現批處理命令來簡化自定義規則

爲了簡化用戶自定義 rule 的配置,xmake 新提供了 on_buildcmd_file, on_buildcmd_files 等自定義腳本入口,
我們可以通過 batchcmds 對象,構造一個批處理命令行任務,xmake 在實際執行構建的時候,一次性執行這些命令。

這對於 xmake project 此類工程生成器插件非常有用,因爲生成器生成的第三方工程文件並不支持 on_build_files 此類內置腳本的執行支持。

但是 on_buildcmd_file 構造的最終結果,就是一批原始的 cmd 命令行,可以直接給其他工程文件作爲 custom commands 來執行。

另外,相比 on_build_file,它也簡化對擴展文件的編譯實現,更加的可讀易配置,對用戶也更加友好。

rule("foo")
    set_extensions(".xxx")
    on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
        batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile})
    end)

除了 batchcmds:vrunv,我們還支持一些其他的批處理命令,例如:

batchcmds:show("hello %s""xmake")
batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile}, {envs = {LD_LIBRARY_PATH="/xxx"}})
batchcmds:mkdir("/xxx"-- and cp, mv, rm, ln ..
batchcmds:compile(sourcefile_cx, objectfile, {configs = {includedirs = sourcefile_dir, languages = (sourcekind == "cxx" and "c++11")}})
batchcmds:link(objectfiles, targetfile, {configs = {linkdirs = ""}})

同時,我們在裏面也簡化對依賴執行的配置,下面是一個完整例子:

rule("lex")
    set_extensions(".l"".ll")
    on_buildcmd_file(function (target, batchcmds, sourcefile_lex, opt)

        -- imports
        import("lib.detect.find_tool")

        -- get lex
        local lex = assert(find_tool("flex"or find_tool("lex"), "lex not found!")

        -- get c/c++ source file for lex
        local extension = path.extension(sourcefile_lex)
        local sourcefile_cx = path.join(target:autogendir(), "rules""lex_yacc"path.basename(sourcefile_lex) .. (extension == ".ll" and ".cpp" or ".c"))

        -- add objectfile
        local objectfile = target:objectfile(sourcefile_cx)
        table.insert(target:objectfiles(), objectfile)

        -- add commands
        batchcmds:show_progress(opt.progress, "${color.build.object}compiling.lex %s", sourcefile_lex)
        batchcmds:mkdir(path.directory(sourcefile_cx))
        batchcmds:vrunv(lex.program, {"-o", sourcefile_cx, sourcefile_lex})
        batchcmds:compile(sourcefile_cx, objectfile)

        -- add deps
        batchcmds:add_depfiles(sourcefile_lex)
        batchcmds:set_depmtime(os.mtime(objectfile))
        batchcmds:set_depcache(target:dependfile(objectfile))
    end)

我們從上面的配置可以看到,整體執行命令列表非常清晰,而如果我們用 on_build_file 來實現,可以對比下之前這個規則的配置,就能直觀感受到新接口的配置方式確實簡化了不少:

rule("lex")

    -- set extension
    set_extensions(".l"".ll")

    -- load lex/flex
    before_load(function (target)
        import("core.project.config")
        import("lib.detect.find_tool")
        local lex = config.get("__lex")
        if not lex then
            lex = find_tool("flex"or find_tool("lex")
            if lex and lex.program then
                config.set("__lex", lex.program)
                cprint("checking for Lex ... ${color.success}%s", lex.program)
            else
                cprint("checking for Lex ... ${color.nothing}${text.nothing}")
                raise("lex/flex not found!")
            end
        end
    end)

    -- build lex file
    on_build_file(function (target, sourcefile_lex, opt)

        -- imports
        import("core.base.option")
        import("core.theme.theme")
        import("core.project.config")
        import("core.project.depend")
        import("core.tool.compiler")
        import("private.utils.progress")

        -- get lex
        local lex = assert(config.get("__lex"), "lex not found!")

        -- get extension: .l/.ll
        local extension = path.extension(sourcefile_lex)

        -- get c/c++ source file for lex
        local sourcefile_cx = path.join(target:autogendir(), "rules""lex_yacc"path.basename(sourcefile_lex) .. (extension == ".ll" and ".cpp" or ".c"))
        local sourcefile_dir = path.directory(sourcefile_cx)

        -- get object file
        local objectfile = target:objectfile(sourcefile_cx)

        -- load compiler
        local compinst = compiler.load((extension == ".ll" and "cxx" or "cc"), {target = target})

        -- get compile flags
        local compflags = compinst:compflags({target = target, sourcefile = sourcefile_cx})

        -- add objectfile
        table.insert(target:objectfiles(), objectfile)

        -- load dependent info
        local dependfile = target:dependfile(objectfile)
        local dependinfo = option.get("rebuild"and {} or (depend.load(dependfile) or {})

        -- need build this object?
        local depvalues = {compinst:program(), compflags}
        if not depend.is_changed(dependinfo, {lastmtime = os.mtime(objectfile), values = depvalues}) then
            return
        end

        -- trace progress info
        progress.show(opt.progress, "${color.build.object}compiling.lex %s", sourcefile_lex)

        -- ensure the source file directory
        if not os.isdir(sourcefile_dir) then
            os.mkdir(sourcefile_dir)
        end

        -- compile lex
        os.vrunv(lex, {"-o", sourcefile_cx, sourcefile_lex})

        -- trace
        if option.get("verbose"then
            print(compinst:compcmd(sourcefile_cx, objectfile, {compflags = compflags}))
        end

        -- compile c/c++ source file for lex
        dependinfo.files = {}
        assert(compinst:compile(sourcefile_cx, objectfile, {dependinfo = dependinfo, compflags = compflags}))

        -- update files and values to the dependent file
        dependinfo.values = depvalues
        table.insert(dependinfo.files, sourcefile_lex)
        depend.save(dependinfo, dependfile)
    end)

關於這個的詳細說明和背景,見:issue 1246

依賴包配置改進

使用 add_extsources 改進包名查找

關於遠程依賴包定義這塊,我們也新增了 add_extsourceson_fetch 兩個配置接口,可以更好的配置 xmake 在安裝 C/C++ 包的過程中,對系統庫的查找過程。

至於具體背景,我們可以舉個例子,比如我們在 xmake-repo 倉庫新增了一個 package("libusb") 的包。

那麼用戶就可以通過下面的方式,直接集成使用它:

add_requires("libusb")
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("libusb")

如果用戶系統上確實沒有安裝 libusb,那麼 xmake 會自動下載 libusb 庫源碼,自動編譯安裝集成,沒啥問題。

但如果用戶通過 apt install libusb-1.0 安裝了 libusb 庫到系統,那麼按理 xmake 應該會自動優先查找用戶安裝到系統環境的 libusb 包,直接使用,避免額外的下載編譯安裝。

但是問題來了,xmake 內部通過 find_package("libusb") 並沒有找打它,這是爲什麼呢?因爲通過 apt 安裝的 libusb 包名是 libusb-1.0, 而不是 libusb。

我們只能通過 pkg-config --cflags libusb-1.0 才能找到它,但是 xmake 內部的默認 find_package 邏輯並不知道 libusb-1.0 的存在,所以找不到。

因此爲了更好地適配不同系統環境下,系統庫的查找,我們可以通過 add_extsources("pkgconfig::libusb-1.0") 去讓 xmake 改進查找邏輯,例如:

package("libusb")
    add_extsources("pkgconfig::libusb-1.0")
    on_install(function (package)
        -- ...
    end)

另外,我們也可以通過這個方式,改進查找 homebrew/pacman 等其他包管理器安裝的包,例如:add_extsources("pacman::libusb-1.0")

使用 on_fetch 完全定製系統庫查找

如果不同系統下安裝的系統庫,僅僅只是包名不同,那麼使用 add_extsources 改進系統庫查找已經足夠,簡單方便。

但是如果有些安裝到系統的包,位置更加複雜,想要找到它們,也許需要一些額外的腳本才能實現,例如:windows 下注冊表的訪問去查找包等等,這個時候,我們就可以通過 on_fetch 完全定製化查找系統庫邏輯。

還是以 libusb 爲例,我們不用 add_extsources,可以使用下面的方式,實現相同的效果,當然,我們可以在裏面做更多的事情。

package("libusb")
    on_fetch("linux"function(package, opt)
        if opt.system then
            return find_package("pkgconfig::libusb-1.0")
        end
    end)

manifest 文件支持

在新版本中,我們還新增了對 windows .manifest 文件的支持,只需要通過 add_files 添加進來即可。

add_rules("mode.debug""mode.release")
target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    add_files("src/*.manifest")

xrepo 命令改進

關於 xrepo 命令,我們也稍微改進了下,現在可以通過下面的命令,批量卸載刪除已經安裝的包,支持模式匹配:

$ xrepo remove --all
$ xrepo remove --all zlib pcr*

包的依賴導出支持

我們也改進了 add_packages,使其也支持 {public = true} 來導出包配置給父 target。

add_rules("mode.debug""mode.release")
add_requires("pcre2")

target("test")
    set_kind("shared")
    add_packages("pcre2", {public = true})
    add_files("src/test.cpp")

target("demo")
    add_deps("test")
    set_kind("binary")
    add_files("src/main.cpp")  -- 我們可以在這裏使用被 test 目標導出 pcre2 庫

至於具體導出哪些配置呢?

-- 默認私有,但是 links/linkdirs 還是會自動導出
add_packages("pcre2")

-- 全部導出。包括 includedirs, defines
add_packages("pcre2", {public = true})

更新內容

新特性

  • #955: 支持 zig cczig c++ 作爲 c/c++ 編譯器

  • #955: 支持使用 zig 進行交叉編譯

  • #1177: 改進終端和 color codes 探測

  • #1216: 傳遞自定義 includes 腳本給 xrepo

  • 添加 linuxos 內置模塊獲取 linux 系統信息

  • #1217: 支持當編譯項目時自動拉取工具鏈

  • #1123: 添加 rule("utils.symbols.export_all") 自動導出所有 windows/dll 中的符號

  • #1181: 添加 utils.platform.gnu2mslib(mslib, gnulib) 模塊接口去轉換 mingw/xxx.dll.a 到 msvc xxx.lib

  • #1246: 改進規則支持新的批處理命令去簡化自定義規則實現

  • #1239: 添加 add_extsources 去改進外部包的查找

  • #1241: 支持爲 windows 程序添加 .manifest 文件參與鏈接

  • 支持使用 xrepo remove --all 命令去移除所有的包,並且支持模式匹配

  • #1254: 支持導出包配置給父 target,實現包配置的依賴繼承

改進

  • #1226: 添加缺失的 Qt 頭文件搜索路徑

  • #1183: 改進 C++ 語言標準,以便支持 Qt6

  • #1237: 爲 vsxmake 插件添加 qt.ui 文件

  • 改進 vs/vsxmake 插件去支持預編譯頭文件和智能提示

  • #1090: 簡化自定義規則

  • #1065: 改進 protobuf 規則,支持 compile_commands 生成器

  • #1249: 改進 vs/vsxmake 生成器去支持啓動工程設置

  • #605: 改進 add_deps 和 add_packages 直接的導出 links 順序

  • 移除廢棄的 add_defines_h_if_ok and add_defines_h 接口

Bugs 修復

  • #1219: 修復版本檢測和更新

  • #1235: 修復 includes 搜索路徑中帶有空格編譯不過問題

關注公衆號

TBOOX開源工程

專注C跨平臺開發解決方案

長按二維碼關注



本文分享自微信公衆號 - TBOOX開源工程(tboox-os)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章