xmake v2.5.7 釋出,包依賴鎖定和 Vala/Metal 語言編譯支援

waruqi發表於2021-08-29

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

這個版本,我們新增了很多新特性,不僅增加了對 Vala 和 Metal 語言的編譯支援,另外我們還改進了包依賴管理,能夠像 npm/package.lock 那樣支援對依賴包的鎖定和更新,使得使用者的專案不會受到上游包倉庫的更新變動影響。

此外,我們還提供了一些比較實用的規則, 比如 utils.bin2c 可以讓使用者方便快速的內嵌一些二進位制資原始檔到程式碼中去,以標頭檔案的方式獲取相關資料。

新特性介紹

新增 Vala 語言支援

這個版本,我們已經可以初步支援構建 Vala 程式,只需要應用 add_rules("vala") 規則。

同時,我們需要新增一些依賴包,其中 glib 包是必須的,因為 vala 自身也會用到它。

add_values("vala.packages") 用於告訴 valac,專案需要哪些包,它會引入相關包的 vala api,但是包的依賴整合,還是需要通過 add_requires("lua") 下載整合。

例如:

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

add_requires("lua", "glib")

target("test")
    set_kind("binary")
    add_rules("vala")
    add_files("src/*.vala")
    add_packages("lua", "glib")
    add_values("vala.packages", "lua")

更多例子:Vala examples

新增包依賴鎖定支援

這個特性類似 npm 的 package.lock, cargo 的 cargo.lock。

比如,我們引用一些包,預設情況下,如果不指定版本,那麼 xmake 每次都會自動拉取最新版本的包來整合使用,例如:

add_requires("zlib")

但如果上游的包倉庫更新改動,比如 zlib 新增了一個 1.2.11 版本,或者安裝指令碼有了變動,都會導致使用者的依賴包發生改變。

這容易導致原本編譯通過的一些專案,由於依賴包的變動出現一些不穩定因素,有可能編譯失敗等等。

為了確保使用者的專案每次使用的包都是固定的,我們可以通過下面的配置去啟用包依賴鎖定。

set_policy("package.requires_lock", true)

這是一個全域性設定,必須設定到全域性根作用域,如果啟用後,xmake 執行完包拉取,就會自動生成一個 xmake-requires.lock 的配置檔案。

它包含了專案依賴的所有包,以及當前包的版本等資訊。

{
    __meta__ = {
        version = "1.0"
    },
    ["macosx|x86_64"] = {
        ["cmake#31fecfc4"] = {
            repo = {
                branch = "master",
                commit = "4498f11267de5112199152ab030ed139c985ad5a",
                url = "https://github.com/xmake-io/xmake-repo.git"
            },
            version = "3.21.0"
        },
        ["glfw#31fecfc4"] = {
            repo = {
                branch = "master",
                commit = "eda7adee81bac151f87c507030cc0dd8ab299462",
                url = "https://github.com/xmake-io/xmake-repo.git"
            },
            version = "3.3.4"
        },
        ["opengl#31fecfc4"] = {
            repo = {
                branch = "master",
                commit = "94d2eee1f466092e04c5cf1e4ecc8c8883c1d0eb",
                url = "https://github.com/xmake-io/xmake-repo.git"
            }
        }
    }
}

當然,我們也可以執行下面的命令,強制升級包到最新版本。

$ xmake require --upgrade
upgrading packages ..
  zlib: 1.2.10 -> 1.2.11
1 package is upgraded!

option 支援程式碼片段的執行時檢測

option 本身有提供 add_csnippets/add_cxxsnippets 兩個介面,用於快速檢測特定一段 c/c++ 程式碼是否通過編譯,如果編譯通過就會啟用對應 option 選項。

但之前的版本僅僅只能提供編譯期檢測,而新版本中,我們還新增了執行時檢測支援。

我們可以通過設定 {tryrun = true}{output = true} 兩個引數,用於嘗試執行檢測和捕獲輸出。

嘗試執行檢測

設定 tryrun 可以嘗試執行來檢測

option("test")
    add_cxxsnippets("HAS_INT_4", "return (sizeof(int) == 4)? 0 : -1;", {tryrun = true})

如果編譯執行通過,test 選項就會被啟用。

執行時檢測並捕獲輸出

設定 output 也會嘗試去檢測,並且額外捕獲執行的輸出內容。

option("test")
    add_cxxsnippets("INT_SIZE", 'printf("%d", sizeof(int)); return 0;', {output = true, number = true})

如果編譯執行通過,test 選項就會被啟用,同時能獲取到對應的輸出內容作為 option 的值。

注:設定為捕獲輸出,當前 option 不能再設定其他 snippets

我們也可以通過 is_config 獲取繫結到option的輸出。

if is_config("test", "8") tben
    -- xxx
end

另外,我們也對 includes("check_csnippets") 的輔助檢測介面,也做了改進來支援執行時檢測。

includes("check_csnippets.lua")

target("test")
    set_kind("binary")
    add_files("*.c")
    add_configfiles("config.h.in")

    check_csnippets("HAS_INT_4", "return (sizeof(int) == 4)? 0 : -1;", {tryrun = true})
    check_csnippets("INT_SIZE", 'printf("%d", sizeof(int)); return 0;', {output = true, number = true})
    configvar_check_csnippets("HAS_LONG_8", "return (sizeof(long) == 8)? 0 : -1;", {tryrun = true})
    configvar_check_csnippets("PTR_SIZE", 'printf("%d", sizeof(void*)); return 0;', {output = true, number = true})

如果啟用捕獲輸出,config.h.in${define PTR_SIZE} 會自動生成 #define PTR_SIZE 4

其中,number = true 設定,可以強制作為 number 而不是字串值,否則預設會定義為 #define PTR_SIZE "4"

快速內嵌二進位制資原始檔到程式碼

我們可以通過 utils.bin2c 規則,在專案中引入一些二進位制檔案,並見他們作為 c/c++ 標頭檔案的方式提供開發者使用,獲取這些檔案的資料。

比如,我們可以在專案中,內嵌一些 png/jpg 資原始檔到程式碼中。

target("console")
    set_kind("binart")
    add_rules("utils.bin2c", {extensions = {".png", ".jpg"}})
    add_files("src/*.c")
    add_files("res/*.png", "res/*.jpg")

注:extensions 的設定是可選的,預設字尾名是 .bin

然後,我們就可以通過 #include "filename.png.h" 的方式引入進來使用,xmake 會自動幫你生成對應的標頭檔案,並且新增對應的搜尋目錄。

static unsigned char g_png_data[] = {
    #include "image.png.h"
};

int main(int argc, char** argv)
{
    printf("image.png: %s, size: %d\n", g_png_data, sizeof(g_png_data));
    return 0;
}

生成標頭檔案內容類似:

cat build/.gens/test/macosx/x86_64/release/rules/c++/bin2c/image.png.h
  0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x78, 0x6D, 0x61, 0x6B, 0x65, 0x21, 0x0A, 0x00

新增 iOS/macOS 應用 Metal 編譯支援

我們知道 xcode.application 規則可以編譯 iOS/macOS 應用程式,生成 .app/.ipa 程式包,並同時完成簽名操作。

不過之前它不支援對帶有 .metal 程式碼的編譯,而新版本中,我們新增了 xcode.metal 規則,並預設關聯到 xcode.application 規則中去來預設支援 metal 編譯。

xmake 會自動編譯 .metal 然後打包生成 default.metallib 檔案,並且自動內建到 .app/.ipa 裡面。

如果使用者的 metal 是通過 [_device newDefaultLibrary] 來訪問的,那麼就能自動支援,就跟使用 xcode 編譯一樣。

這裡是我們提供的一個完整的:專案例子

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

target("HelloTriangle")
    add_rules("xcode.application")
    add_includedirs("Renderer")
    add_frameworks("MetalKit")
    add_mflags("-fmodules")
    add_files("Renderer/*.m", "Renderer/*.metal") ------- 新增 metal 檔案
    if is_plat("macosx") then
        add_files("Application/main.m")
        add_files("Application/AAPLViewController.m")
        add_files("Application/macOS/Info.plist")
        add_files("Application/macOS/Base.lproj/*.storyboard")
        add_defines("TARGET_MACOS")
        add_frameworks("AppKit")
    elseif is_plat("iphoneos") then
        add_files("Application/*.m")
        add_files("Application/iOS/Info.plist")
        add_files("Application/iOS/Base.lproj/*.storyboard")
        add_frameworks("UIKit")
        add_defines("TARGET_IOS")

比如,在 macOS 上,編譯執行後,就會通過 metal 渲染出需要的效果。

如果,我們的專案沒有使用預設的 metal library,我們也可以通過上面提到的 utils.bin2c 規則,作為原始檔的方式內嵌到程式碼庫中,例如:

add_rules("utils.bin2c", {extensions = ".metal"})
add_files("Renderer/*.metal")

然後程式碼中,我們就能訪問了:

static unsigned char g_metal_data[] = {
    #include "xxx.metal.h"
};

id<MTLLibrary> library = [_device newLibraryWithSource:[[NSString stringWithUTF8String:g_metal_data]] options:nil error:&error];

改進 add_repositories

如果我們通過內建在專案中的本地倉庫,我們之前是通過 add_repositories("myrepo repodir") 的方式來引入。

但是,它並不像 add_files() 那樣是基於當前 xmake.lua 檔案目錄的相對目錄,也沒有路徑的自動轉換,因此容易遇到找不到 repo 的問題。

因此,我麼你改進了下它,可以通過額外的 rootdir 引數指定對應的根目錄位置,比如相對當前 xmake.lua 的指令碼目錄。

add_repositories("myrepo repodir", {rootdir = os.scriptdir()})

os.cp 支援符號連結

之前的版本,os.cp 介面不能很好的處理符號連結的複製,他會自動展開連結,複製實際的檔案內容,只會導致複製後,符號連結丟失。

如果想要複製後,原樣保留符號連結,只需要設定下引數:{symlink = true}

os.cp("/xxx/symlink", "/xxx/dstlink", {symlink = true})

更方便地編譯自動生成的程式碼

有時候,我們會有這樣一個需求,在編譯前,自動生成一些原始檔參與後期的程式碼編譯。但是由於 add_files 新增的檔案在執行編譯時候,就已經確定,無法在編譯過程中動態新增它們(因為需要並行編譯)。

因此,要實現這個需求,我們通常需要自定義一個 rule,然後裡面主動呼叫編譯器模組去處理生成程式碼的編譯,物件檔案的注入,依賴更新等一系列問題。

這對於 xmake 開發者本身沒什麼大問題,但是對於使用者來說,這還是比較繁瑣了,不好上手。

新版本中,我們改進了對 add_files 的支援,並新增了 {always_added = true} 配置來告訴 xmake 我們始終需要新增指定的原始檔,即使它還不存在。

這樣我們就可以依靠xmake的預設編譯過程來編譯自動生成的程式碼了,像這樣:

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

target("autogen_code")
    set_kind("binary")
    add_files("$(buildir)/autogen.cpp", {always_added = true})
    before_build(function (target)
        io.writefile("$(buildir)/autogen.cpp", [[
#include <iostream>

using namespace std;

int main(int argc, char** argv)
{
    cout << "hello world!" << endl;
    return 0;
}
        ]])
    end)

都不需要額外的 rule 定義,只需要保證編譯順序,在正確的階段生成程式碼檔案就可以了。

但是,我們也需要注意,由於當前自動生成的原始檔可能還不存在,我們不能在 add_files 裡面使用模式匹配,只能顯式新增每個原始檔路徑。

更新內容

新特性

  • #1534: 新增對 Vala 語言的支援
  • #1544: 新增 utils.bin2c 規則去自動從二進位制資原始檔產生 .h 標頭檔案並引入到 C/C++ 程式碼中
  • #1547: option/snippets 支援執行檢測模式,並且可以獲取輸出
  • #1567: 新增 xmake-requires.lock 包依賴鎖定支援
  • #1597: 支援編譯 metal 檔案到 metallib,並改進 xcode.application 規則去生成內建的 default.metallib 到 app

改進

  • #1540: 更好更方便地編譯自動生成的程式碼
  • #1578: 改進 add_repositories 去更好地支援相對路徑
  • #1582: 改進安裝和 os.cp 支援符號連結

Bugs 修復

  • #1531: 修復 targets 載入失敗的錯誤資訊提示錯誤

相關文章