C/C++ 構建系統,我用 xmake

XMake 是什麼

XMake 是一個基於 Lua 的 現代化 C/C++ 構建系統。html

它的語法簡潔易上手,對新手友好,即便徹底不會 lua 也可以快速入門,而且徹底無任何依賴,輕量,跨平臺。java

同時,它也是一個自知足的構建系統,擁有強大的包管理系統,快速的構建引擎。python

相比 Ninja/Scons/Make 做爲 Build backend,CMake/Meson 做爲 Project Generator,那麼 XMake 就是這二者外加一個包管理。linux

xmake = Build backend + Project Generator + Package Manager
複製代碼

所以,只須要安裝一個不到 3M 的 XMake 安裝包,你就能夠不用再安裝其餘各類工具,甚至連 make 都不須要安裝,也不須要安裝 Python、Java 等重量級的運行時環境,就能夠開始您的 C/C++ 開發之旅。android

也許,有人會說,編譯器總須要安裝的吧。這也不是必須的,由於 XMake 的包管理也支持自動遠程拉取須要的各類編譯工具鏈,好比:llvm, Mingw, Android NDK 或者交叉編譯工具鏈。ios

爲何要作 XMake

每當在 Reddit 社區跟別人討論起 XMake,你們老是會拿下面這張圖來吐槽。c++

儘管有些無奈,也被吐槽的有些麻木了,不過我仍是想說明下,作 XMake 的初衷,並非爲了分裂 C/C++ 生態,相反,XMake 儘量地複用了現有生態。git

同時也讓用戶在開發 C/C++ 項目的時候,擁有與其餘語言同樣的良好體驗,好比:Rust/Cargo,Nodejs/Npm, Dlang/Dub,再也不爲處處找第三包,研究如何移植編譯而折騰。github

所以,若是您還不瞭解 XMake,請不要過早下定論,能夠先嚐試使用下,或者花點時間看完下文的詳細介紹。sql

XMake 的特性和優點

常常有人問我 XMake 有什麼特別之處,相比現有 CMake、Meson 此類構建工具備什麼優點,我爲何要使用 XMake 而不是 CMake?

先說特色和優點,XMake 有如下幾點:

  • 簡潔易學的配置語法,非 DSL
  • 強大的包管理,支持語義版本,工具鏈管理
  • 足夠輕量,無依賴
  • 極速編譯,構建速度和 Ninja 同樣快
  • 簡單方便的多平臺、工具鏈切換
  • 完善的插件系統
  • 靈活的構建規則

至於 CMake,畢竟已成事實上的標準,生態完善,功能強大。

我從沒想過讓 XMake 去替代它,也替代不了,徹底不是一個量級的,可是 CMake 也有許多爲人所詬病的短板,好比:語法複雜難懂,包管理支持不完善等等。

所以使用 XMake 能夠做爲一種補充,對於那些想要簡單快速入門 C/C++ 開發的新手,或者想要更加方便易用的包管理,或者想臨時快速寫一些短小的測試項目。

XMake 均可以幫他們提高開發效率,讓其更加關注 C/C++ 項目自己,而不是花更多的時間在構建工具和開發環境上。

下面,我來具體介紹 XMake 的這些主要特性。

語法簡潔易上手

CMake 本身設計一門 DSL 語言用來作項目配置,這對用戶來說提升了學習成本,並且它的語法可讀性不是很直觀,很容易寫出過於複雜的配置腳本,也提升了維護成本。

而 XMake 複用現有知名的 Lua 語言做爲基礎,而且其上提供了更加簡單直接的配置語法。

Lua 自己就是一門簡單輕量的膠水語言,關鍵字和內置類型就那麼幾種,看個一篇文章,就能基本入門了,而且相比 DSL,可以從網上更方便的獲取到大量相關資料和教程。

基礎語法

不過,仍是有人會吐槽:那不是還得學習 Lua 麼?

其實也不用,XMake 採用了 描述域腳本域 分離的方式,使得初學者用戶在 80% 的狀況下,只須要在描述域以更簡單直接的方式來配置,徹底能夠不把它當成 Lua 腳本,例如:

target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_files("test/*.c", "example/**.cpp")
複製代碼

若是由於,看着有括號,仍是像腳本語言的函數調用,那咱們也能夠這麼寫(是否帶括號看我的喜愛,不過我我的仍是建議使用上面的方式)

target "test"
    set_kind "binary"
    add_files "src/*.c"
    add_files "test/*.c"
    add_files "example/**.cpp"
複製代碼

咱們只須要知道經常使用配置接口,即便不徹底不會 Lua 也能快速配置了。

咱們能夠對比下 CMake 的配置:

add_executable(test "")
file(GLOB SRC_FILES "src/*.c")
file(GLOB TEST_FILES "test/*.c")
file(GLOB_RECURSE EXAMPLE_FILES "example/*.cpp")
target_sources(test PRIVATE
    ${SRC_FILES}
    ${TEST_FILES}
    ${EXAMPLE_FILES}
)
複製代碼

哪一個更直觀可讀,一目瞭然。

條件配置

若是,你已經初步瞭解了一些 Lua 等基礎知識,好比 if then 等條件判斷,那麼能夠進一步作一些條件配置。

target("test")
    set_kind("binary")
    add_files("src/main.c")
    if is_plat("macosx", "linux") then
        add_defines("TEST1", "TEST2")
    end
    if is_plat("windows") and is_mode("release") then
        add_cxflags("-Ox", "-fp:fast")
    end
複製代碼

繼續對比下 CMake 版本配置:

add_executable(test "")
if (APPLE OR LINUX)
    target_compile_definitions(test PRIVATE TEST1 TEST2)
endif()
if (WIN32)
    target_compile_options(test PRIVATE $<$<CONFIG:Release>:-Ox -fp:fast>)
endif()
target_sources(test PRIVATE
    src/main.c
)
複製代碼

複雜腳本

若是你已經晉升爲 XMake 的高端玩家,Lua 語法瞭然於胸,想要更加靈活的定製化配置須要,而且描述域的幾行簡單配置已經知足不了你的需求。

那麼 XMake 也提供了更加完整的 Lua 腳本定製化能力,你能夠寫任何複雜的腳本。

好比在構建以前,對全部源文件進行一些預處理,在構建以後,執行外部 gradle 命令進行後期打包,甚至咱們還能夠重寫內部連接規則,實現深度定製編譯,咱們能夠經過import 接口,導入內置的 linker 擴展模塊,實現複雜靈活的連接過程。

target("test")
    set_kind("binary")
    add_files("src/*.c")
    before_build_file(function (target, sourcefile)
        io.replace(sourcefile, "#define HAVE_XXX 1", "#define HAVE_XXX 0")
    end)
    on_link(function (target)
        import("core.tool.linker")
        linker.link("binary", "cc", target:objectfiles(), target:targetfile(), {target = target})
    end)
    after_build(function (target)
        if is_plat("android" then
            os.cd("android/app")
            os.exec("./gradlew app:assembleDebug")
        end
    end)
複製代碼

若是換成 CMake,也能夠 add_custom_command 裏面實現,不過裏面彷佛只能簡單的執行一些批處理命令,無法作各類複雜的邏輯判斷,模塊加載,自定義配置腳本等等。

固然,使用 cmake 確定也能實現上面描述的功能,但絕對不會那麼簡單。

若是有熟悉 cmake 的人,也能夠嘗試幫忙完成下面的配置:

add_executable(test "")
file(GLOB SRC_FILES "src/*.c")
add_custom_command(TARGET test PRE_BUILD
    -- TODO
    COMMAND echo hello
)
add_custom_command(TARGET test POST_BUILD
    COMMAND cd android/app
    COMMAND ./gradlew app:assembleDebug
)
-- How can we override link stage?
target_sources(test PRIVATE
    ${SRC_FILES}
)
複製代碼

強大的包管理

衆所周知,作 C/C++ 相關項目開發,最頭大的就是各類依賴包的集成,因爲沒有像 Rust/Cargo 那樣完善的包管理系統。

所以,咱們每次想使用一個第三方庫,都須要各類找,研究各類平臺的移植編譯,還常常遇到各類編譯問題,極大耽誤了開發者時間,沒法集中精力去投入到實際的項目開發中去。

好不容易當前平臺搞定了,換到其餘平臺,有須要從新折騰一遍依賴包,爲了解決這個問題,出現了一些第三方的包管理器,好比 vcpkg/conan/conda等等,但有些不支持語義版本,有些支持的平臺有限,但無論怎樣,總算是爲解決 C/C++ 庫的依賴管理邁進了很大一步。

可是,光有包管理器,C/C++ 項目中使用它們仍是比較麻煩,由於還須要對應構建工具可以很好的對其進行集成支持才行。

CMake 和 Vcpkg

咱們先來看下 CMake 和 Vcpkg 的集成支持:

cmake_minimum_required(VERSION 3.0)
project(test)
find_package(unofficial-sqlite3 CONFIG REQUIRED)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE unofficial::sqlite3::sqlite3)
複製代碼

缺點:

  • 還須要額外配置 -DCMAKE_TOOLCHAIN_FILE=<vcpkg_dir>/scripts/buildsystems/vcpkg.cmake"
  • 不支持自動安裝依賴包,還須要用戶手動執行 vcpkg install xxx 命令安裝
  • vcpkg 的語義版本選擇不支持 (聽說新版本開始支持了)

CMake 和 Conan

```cmake
cmake_minimum_required(VERSION 2.8.12)
project(Hello)

add_definitions("-std=c++11")

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

add_executable(hello hello.cpp)
target_link_libraries(hello gtest)
複製代碼

conanfile.txt

[requires]
gtest/1.10.0

[generators]
cmake
複製代碼

缺點:

  • 一樣,仍是須要額外調用 conan install .. 來安裝包
  • 還須要額外配置一個 conanfile.txt 文件去描述包依賴規則

Meson 和 Vcpkg

我沒找到如何在 Meson 中去使用 vcpkg 包,僅僅找到一篇相關的 Issue #3500 討論。

Meson 和 Conan

Meson 彷佛尚未對 Conan 進行支持,可是 Conan 官方文檔上有解決方案,對齊進行支持,可是很複雜,我是沒看會,你們能夠自行研究:docs.conan.io/en/latest/r…

XMake 和 Vcpkg

前面講了這麼多,其餘構建工具和包管理的集成,我的感受用起來很麻煩,並且不一樣的包管理器,集成方式差異很大,用戶想要快速從 Vcpkg 切換到 Conan 包,改動量很是大。

接下來,咱們來看看 XMake 中集成使用 Vcpkg 提供的包:

add_requires("vcpkg::zlib", {alias = "zlib"})
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib")
複製代碼

咱們只須要經過 add_requires 配置上對應的包名,以及 vcpkg:: 包命名空間,就能直接集成使用 vcpkg 提供的 zlib 包。

而後,咱們只須要執行 xmake 命令,既可完成整個編譯過程,包括 zlib 包的自動安裝,無需額外手動執行 vcpkg install zlib

$ xmake
note: try installing these packages (pass -y to skip confirm)?
-> vcpkg::zlib
please input: y (y/n)

=> install vcpkg::zlib .. ok
[ 25%]: compiling.release src\main.cpp
[ 50%]: linking.release test
[100%]: build ok!
複製代碼

XMake 和 Conan

接下來是集成 Conan 的包,徹底同樣的方式,僅僅執行換個包管理器名字。

add_requires("conan::zlib", {alias = "zlib"})
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib")
複製代碼

XMake 一樣會自動安裝 conan 中的 zlib 包,而後自動集成編譯。

XMake 自建包管理

XMake 跟 CMake 還有其餘構建系統,最大的不一樣點,也就是最大的優點之一,就是它有徹底自建的包管理系統,咱們徹底能夠不依賴 vcpkg/conan,也能夠快速集成依賴包,好比:

add_requires("zlib 1.2.x", "tbox >= 1.6.0")
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib", "tbox")
複製代碼

並且,它還支持完整的語義版本選擇,多平臺的包集成,交叉編譯工具鏈的包集成,甚至編譯工具鏈包的自動拉取使用。

不只如此,咱們開能夠對定製化配置對自建包的依賴,例如:

使用調式版本依賴包

咱們可使用 debug 版本庫,實現對依賴庫的斷點調試。

add_requires("zlib", {debug = true})
複製代碼
設置 msvc 運行時庫
add_requires("zlib", {configs = {vs_runtime = "MD"}})
複製代碼
使用動態庫

默認集成的是靜態庫,咱們也能夠切換到動態庫。

add_requires("zlib", {configs = {shared = true}})
複製代碼
語義版本支持

XMake 的自建包集成支持完整的版本語義規範。

add_requires("zlib 1.2.x")
add_requires("zlib >=1.2.10")
add_requires("zlib ~1.2.0")
複製代碼
禁止使用系統庫

默認狀況下,若是版本匹配,XMake 會優先查找使用系統上用戶已經安裝的庫,固然咱們也能夠強制禁止查找使用系統庫,僅僅從自建包倉庫中下載安裝包。

add_requires("zlib", {system = true})
複製代碼
可選依賴包

若是依賴包集成失敗,XMake 會自動報錯,中斷編譯,提示用戶:zlib not found,可是咱們也能夠設置爲可選包集成,這樣的話,即便庫最終沒安裝成功,也不影響項目的編譯,僅僅只是跳過這個依賴。

add_requires("zlib", {optional = true})
複製代碼
包的定製化配置

好比,集成使用開啓了 context/coroutine 模塊配置的 boost 庫。

add_requires("boost", {configs = {context = true, coroutine = true}})
複製代碼

支持的包管理倉庫

XMake 除了支持 vcpkg/conan 還有自建倉庫的包集成支持,還支持其餘的包管理倉庫,例如:Conda/Homebrew/Apt/Pacman/Clib/Dub 等等,並且集成方式徹底一致。

用戶可與快速切換使用其餘的倉庫包,而不須要花太多時間去研究如何集成它們。

所以,XMake 並無破壞 C/C++ 生態,而是極大的複用現有 C/C++ 生態的基礎上,努力改進用戶對 C/C++ 依賴包的使用體驗,提升開發效率,讓用戶可以擁有更多的時間去關注項目自己。

  • 官方自建倉庫 xmake-repo (tbox >1.6.1)
  • 官方包管理器 Xrepo
  • 用戶自建倉庫
  • Conan (conan::openssl/1.1.1g)
  • Conda (conda::libpng 1.3.67)
  • Vcpkg (vcpkg:ffmpeg)
  • Homebrew/Linuxbrew (brew::pcre2/libpcre2-8)
  • Pacman on archlinux/msys2 (pacman::libcurl)
  • Apt on ubuntu/debian (apt::zlib1g-dev)
  • Clib (clib::clibs/bytes@0.0.4)
  • Dub (dub::log 0.4.3)

獨立的包管理命令(Xrepo)

爲了方便 XMake 的自建倉庫中的包管理,以及第三方包的管理使用,咱們也提供了獨立的 Xrepo cli 命令工具,來方便的管理咱們的依賴包

咱們可使用這個工具,快速方便的完成下面的管理操做:

  • 安裝包:xrepo install zlib
  • 卸載包:xrepo remove zlib
  • 獲取包信息:xrepo info zlib
  • 獲取包編譯連接 flags:xrepo fetch zlib
  • 加載包虛擬 Shell 環境:xrepo env shell (這是一個很強大的特性)

咱們能夠到 Xrepo 項目主頁 查看更多的介紹和使用方式。

輕量無依賴

使用 Meson/Scons 須要先安裝 python/pip,使用 Bazel 須要先安裝 java 等運行時環境,而 XMake 不須要額外安裝任何依賴庫和環境,自身安裝包僅僅2-3M,很是的輕量。

儘管 XMake 是基於 lua,可是藉助於 lua 膠水語言的輕量級特性,xmake 已將其徹底內置,所以安裝完 XMake 等同於擁有了一個完整的 lua vm。

有人會說,編譯工具鏈總仍是須要的吧,也不徹底是,Windows 上,咱們提供了預編譯安裝包,能夠直接下載安裝編譯,地址見:Releases

另外,XMake 還支持遠程拉取編譯工具鏈,所以即便你的系統環境,尚未安裝任何編譯器,也不要緊,用戶徹底不用考慮如何折騰編譯環境,只須要在 xmake.lua 裏面配置上須要的工具鏈便可。

好比,咱們在 Windows 上使用 mingw-w64 工具鏈來編譯 C/C++ 工程,只須要作以下配置便可。

add_requires("mingw-w64")
target("test")
    set_kind("binary")
    add_files("src/*.c")
    set_toolchains("mingw@mingw-w64")
複製代碼

經過 set_toolchains 配置綁定 mingw-w64 工具鏈包後,XMake 就會自動檢測當前系統是否存在 mingw-64,若是還沒安裝,它會自動下載安裝,而後完成項目編譯,整個過程,用戶僅僅只須要執行 xmake 這個命令就能完成。

$ xmake
note: try installing these packages (pass -y to skip confirm)?
in xmake-repo:
-> mingw-w64 8.1.0 [vs_runtime:MT]
please input: y (y/n)

=> download https://jaist.dl.sourceforge.net/project/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z .. ok
checking for mingw directory ... C:\Users\ruki\AppData\Local\.xmake\packages\m\mingw-w64\8.1.0\aad6257977e0449595004d7441358fc5
[ 25%]: compiling.release src\main.cpp
[ 50%]: linking.release test.exe
[100%]: build ok!
複製代碼

除了 mingw-w64,咱們還能夠配置遠程拉取使用其餘的工具鏈,甚至交叉編譯工具鏈,例如:llvm-mingw, llvm, tinycc, muslcc, gnu-rm, zig 等等。

若是你們還想進一步瞭解遠程工具鏈的拉取集成,能夠看下文檔:自動拉取遠程工具鏈

極速並行編譯

你們都知道 Ninja 構建很是快,所以不少人都喜歡用 CMake/Meson 生成 build.ninja 後,使用 Ninja 來知足極速構建的需求。

儘管 Ninja 很快,可是咱們仍是須要先經過 meson.build 和 CMakelist.txt 文件生成 build.ninja 才行,這個生成過程也會佔用幾秒甚至十幾秒的時間。

而 XMake 不只僅擁有和 Ninja 近乎相同的構建速度,並且不須要額外再生成其餘構建文件,直接內置構建系統,任何狀況下,只須要一個 xmake 命令就能夠實現極速編譯。

咱們也作過一些對比測試數據,供你們參考:

多任務並行編譯測試

構建系統 Termux (8core/-j12) 構建系統 MacOS (8core/-j12)
xmake 24.890s xmake 12.264s
ninja 25.682s ninja 11.327s
cmake(gen+make) 5.416s+28.473s cmake(gen+make) 1.203s+14.030s
cmake(gen+ninja) 4.458s+24.842s cmake(gen+ninja) 0.988s+11.644s

單任務編譯測試

構建系統 Termux (-j1) 構建系統 MacOS (-j1)
xmake 1m57.707s xmake 39.937s
ninja 1m52.845s ninja 38.995s
cmake(gen+make) 5.416s+2m10.539s cmake(gen+make) 1.203s+41.737s
cmake(gen+ninja) 4.458s+1m54.868s cmake(gen+ninja) 0.988s+38.022s

傻瓜式多平臺編譯

XMake 的另一個特色,就是高效簡單的多平臺編譯,無論你是編譯 windows/linux/macOS 下的程序,仍是編譯 iphoneos/android 又或者是交叉編譯。

編譯的配置方式大同小異,沒必要讓用戶去這折騰研究各個平臺下如何去編譯。

編譯本機 Windows/Linux/MacOS 程序

當前本機程序編譯,咱們僅僅只須要執行:

$ xmake
複製代碼

對比 CMake

$ mkdir build
$ cd build
$ cmake --build ..
複製代碼

編譯 Android 程序

$ xmake f -p android --ndk=~/android-ndk-r21e
$ xmake
複製代碼

對比 CMake

$ mkdir build
$ cd build
$ cmake -DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r21e/build/cmake/android.toolchain.cmake ..
$ make
複製代碼

編譯 iOS 程序

$ xmake f -p iphoneos
$ xmake
複製代碼

對比 CMake

$ mkdir build
$ cd build
$ wget https://raw.githubusercontent.com/leetal/ios-cmake/master/ios.toolchain.cmake
$ cmake -DCMAKE_TOOLCHAIN_FILE=`pwd`/ios.toolchain.cmake ..
$ make
複製代碼

我沒有找到很方便的方式去配置編譯 ios 程序,僅僅只能從其餘地方找到一個第三方的 ios 工具鏈去配置編譯。

交叉編譯

咱們一般只須要設置交叉編譯工具鏈根目錄,XMake 會自動檢測工具鏈結構,提取裏面的編譯器參與編譯,不須要額外配置什麼。

$ xmake f -p cross --sdk=~/aarch64-linux-musl-cross
$ xmake
複製代碼

對比 CMake

咱們須要先額外寫一個 cross-toolchain.cmake 的交叉工具鏈配置文件。

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

set(TOOL_CHAIN_DIR ~/aarch64-linux-musl)
set(TOOL_CHAIN_INCLUDE ${TOOL_CHAIN_DIR}/aarch64-linux-musl/include)
set(TOOL_CHAIN_LIB ${TOOL_CHAIN_DIR}/aarch64-linux-musl/lib)

set(CMAKE_C_COMPILER "aarch64-linux-gcc")
set(CMAKE_CXX_COMPILER "aarch64-linux-g++")

set(CMAKE_FIND_ROOT_PATH ${TOOL_CHAIN_DIR}/aarch64-linux-musl)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

include_directories(${TOOL_CHAIN_DIR}/aarch64-linux-musl/include)
set(CMAKE_INCLUDE_PATH ${TOOL_CHAIN_INCLUDE})
set(CMAKE_LIBRARY_PATH ${TOOL_CHAIN_LIB})
複製代碼
$ mkdir build
$ cd build
$ cmake -DCMAKE_TOOLCHAIN_FILE=../cross-toolchain.cmake ..
$ make
複製代碼

結語

若是你是 C/C++ 開發的新手,能夠經過 XMake 快速上手入門 C/C++ 編譯構建。

若是你想開發維護跨平臺 C/C++ 項目,也能夠考慮使用 XMake 來維護構建,提升開發效率,讓你更加專一於項目自己,再也不爲折騰移植依賴庫而煩惱。

歡迎關注 XMake 項目:

相關文章
相關標籤/搜索