1. 程式人生 > >xmake v2.5.2 釋出, 支援自動拉取交叉工具鏈和依賴包整合

xmake v2.5.2 釋出, 支援自動拉取交叉工具鏈和依賴包整合

[xmake](https://github.com/xmake-io/xmake) 是一個基於 Lua 的輕量級跨平臺構建工具,使用 xmake.lua 維護專案構建,相比 makefile/CMakeLists.txt,配置語法更加簡潔直觀,對新手非常友好,短時間內就能快速入門,能夠讓使用者把更多的精力集中在實際的專案開發上。 在 2.5.2 版本中,我們增加了一個重量級的新特性:`自動拉取遠端交叉編譯工具鏈`。 這是用來幹什麼的呢,做過交叉編譯以及有 C/C++ 專案移植經驗的同學應該知道,折騰各種交叉編譯工具鏈,移植編譯專案是非常麻煩的一件事,需要自己下載對應工具鏈,並且配置工具鏈和編譯環境很容易出錯導致編譯失敗。 現在,xmake 已經可以支援自動下載專案所需的工具鏈,然後使用對應工具鏈直接編譯專案,使用者不需要關心如何配置工具鏈,任何情況下只需要執行 `xmake` 命令即可完成編譯。 ![](https://tboox.org/static/img/xmake/muslcc.gif) 甚至對於 C/C++ 依賴包的整合,也可以自動切換到對應工具鏈編譯安裝整合,一切完全自動化,完全不需要使用者操心。 除了交叉編譯工具鏈,我們也可以自動拉取工具鏈,比如特定版本的 llvm,llvm-mingw, zig 等各種工具鏈來參與編譯 C/C++/Zig 專案的編譯。 即使是 cmake 也還不支援工具鏈的自動拉取,頂多只能配合 vcpkg/conan 等第三方包管理對 C/C++ 依賴包進行整合,另外,即使對於 C/C++依賴包,xmake 也有自己原生內建的包管理工具,完全無任何依賴。 * [專案原始碼](https://github.com/xmake-io/xmake) * [官方文件](https://xmake.io/#/zh-cn/) * [入門課程](https://xmake.io/#/zh-cn/about/course) ## 新特性介紹 ### 自動拉取遠端交叉編譯工具鏈 從 2.5.2 版本開始,我們可以拉取指定的工具鏈來整合編譯專案,我們也支援將依賴包切換到對應的遠端工具鏈參與編譯後集成進來。 相關例子程式碼見:[Toolchain/Packages Examples](https://github.com/xmake-io/xmake/tree/master/tests/projects/package) 相關的 issue [#1217](https://github.com/xmake-io/xmake/issues/1217) 當前,我們已經在 [xmake-repo](https://github.com/xmake-io/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 來編譯專案。 ```lua 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 編譯器,而無需使用者去手動安裝。 ### 拉取交叉編譯工具鏈 我們也可以拉取指定的交叉編譯工具鏈來編譯專案。 ```lua 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交叉編譯工具鏈去編譯和整合所有的依賴包。 ```lua 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 命令,就可以完成整個交叉編譯環境的整合以及架構切換。 ```lua 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)](https://github.com/xmake-io/xmake/blob/master/tests/projects/package/toolchain_muslcc/xmake.lua) #### 拉取整合 Zig 工具鏈 xmake 會先下載特定版本的 zig 工具鏈,然後使用此工具鏈編譯 zig 專案,當然使用者如果已經自行安裝了 zig 工具鏈,xmake 也會自動檢測對應版本是否滿足,如果滿足需求,那麼會直接使用它,無需重複下載安裝。 ```lua 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 工具鏈即可完成編譯: ```bash $ 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 的交叉編譯,例如: ```bash $ 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 乾的事情。 ```bash $ 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](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` 規則即可。 ```lua 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 也提供了一個輔助模組介面去支援它,使用方式如下: ```lua 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](https://github.com/xmake-io/xmake/issues/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`,它也簡化對擴充套件檔案的編譯實現,更加的可讀易配置,對使用者也更加友好。 ```lua rule("foo") set_extensions(".xxx") on_buildcmd_file(function (target, batchcmds, sourcefile, opt) batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile}) end) ``` 除了 `batchcmds:vrunv`,我們還支援一些其他的批處理命令,例如: ```lua 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 = ""}}) ``` 同時,我們在裡面也簡化對依賴執行的配置,下面是一個完整例子: ```lua 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` 來實現,可以對比下之前這個規則的配置,就能直觀感受到新介面的配置方式確實簡化了不少: ```lua 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](https://github.com/xmake-io/xmake/issues/1246) ### 依賴包配置改進 #### 使用 add_extsources 改進包名查詢 關於遠端依賴包定義這塊,我們也新增了 `add_extsources` 和 `on_fetch` 兩個配置介面,可以更好的配置 xmake 在安裝 C/C++ 包的過程中,對系統庫的查詢過程。 至於具體背景,我們可以舉個例子,比如我們在 [xmake-repo](https://github.com/xmake-io/xmake-repo) 倉庫新增了一個 `package("libusb")` 的包。 那麼使用者就可以通過下面的方式,直接整合使用它: ```lua 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 改進查詢邏輯,例如: ```lua 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` 新增進來即可。 ```lua add_rules("mode.debug", "mode.release") target("test") set_kind("binary") add_files("src/*.cpp") add_files("src/*.manifest") ``` ### xrepo 命令改進 關於 xrepo 命令,我們也稍微改進了下,現在可以通過下面的命令,批量解除安裝刪除已經安裝的包,支援模式匹配: ```bash $ xrepo remove --all $ xrepo remove --all zlib pcr* ``` ### 包的依賴匯出支援 我們也改進了 `add_packages`,使其也支援 `{public = true}` 來匯出包配置給父 target。 ```lua 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 庫 ``` 至於具體匯出哪些配置呢? ```lua -- 預設私有,但是 links/linkdirs 還是會自動匯出 add_packages("pcre2") -- 全部匯出。包括 includedirs, defines add_packages("pcre2", {public = true}) ``` ## 更新內容 ### 新特性 * [#955](https://github.com/xmake-io/xmake/issues/955#issuecomment-766481512): 支援 `zig cc` 和 `zig c++` 作為 c/c++ 編譯器 * [#955](https://github.com/xmake-io/xmake/issues/955#issuecomment-768193083): 支援使用 zig 進行交叉編譯 * [#1177](https://github.com/xmake-io/xmake/issues/1177): 改進終端和 color codes 探測 * [#1216](https://github.com/xmake-io/xmake/issues/1216): 傳遞自定義 includes 指令碼給 xrepo * 新增 linuxos 內建模組獲取 linux 系統資訊 * [#1217](https://github.com/xmake-io/xmake/issues/1217): 支援當編譯專案時自動拉取工具鏈 * [#1123](https://github.com/xmake-io/xmake/issues/1123): 新增 `rule("utils.symbols.export_all")` 自動匯出所有 windows/dll 中的符號 * [#1181](https://github.com/xmake-io/xmake/issues/1181): 新增 `utils.platform.gnu2mslib(mslib, gnulib)` 模組介面去轉換 mingw/xxx.dll.a 到 msvc xxx.lib * [#1246](https://github.com/xmake-io/xmake/issues/1246): 改進規則支援新的批處理命令去簡化自定義規則實現 * [#1239](https://github.com/xmake-io/xmake/issues/1239): 新增 `add_extsources` 去改進外部包的查詢 * [#1241](https://github.com/xmake-io/xmake/issues/1241): 支援為 windows 程式新增 .manifest 檔案參與連結 * 支援使用 `xrepo remove --all` 命令去移除所有的包,並且支援模式匹配 * [#1254](https://github.com/xmake-io/xmake/issues/1254): 支援匯出包配置給父 target,實現包配置的依賴繼承 ### 改進 * [#1226](https://github.com/xmake-io/xmake/issues/1226): 新增缺失的 Qt 標頭檔案搜尋路徑 * [#1183](https://github.com/xmake-io/xmake/issues/1183): 改進 C++ 語言標準,以便支援 Qt6 * [#1237](https://github.com/xmake-io/xmake/issues/1237): 為 vsxmake 外掛新增 qt.ui 檔案 * 改進 vs/vsxmake 外掛去支援預編譯標頭檔案和智慧提示 * [#1090](https://github.com/xmake-io/xmake/issues/1090): 簡化自定義規則 * [#1065](https://github.com/xmake-io/xmake/issues/1065): 改進 protobuf 規則,支援 compile_commands 生成器 * [#1249](https://github.com/xmake-io/xmake/issues/1249): 改進 vs/vsxmake 生成器去支援啟動工程設定 * [#605](https://github.com/xmake-io/xmake/issues/605): 改進 add_deps 和 add_packages 直接的匯出 links 順序 * 移除廢棄的 `add_defines_h_if_ok` and `add_defines_h` 介面 ### Bugs 修復 * [#1219](https://github.com/xmake-io/xmake/issues/1219): 修復版本檢測和更新 * [#1235](https://github.com/xmake-io/xmake/issues/1235): 修復 includes 搜尋路徑中帶有空格編譯不