Xmake v2.9.1 發佈,新增 native lua 模塊和鴻蒙系統支持

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
  • 項目源碼

  • 官方文檔

  • 入門課程

新特性介紹

新版本中,我們新增了鴻蒙系統的 native 工具鏈支持,並且實現了一種新的 native 原生 lua 模塊的導入支持。另外,我們也對構建速度做了很多的優化,效果非常明顯。

添加鴻蒙 SDK 工具鏈支持

我們新增了鴻蒙 OS 平臺的 native 工具鏈編譯支持:

$ xmake f -p harmony

xmake 也會自動探測默認的 SDK 路徑,當然我們也可以指定 Harmony SDK 路徑。

$ xmake f -p Harmony --sdk=/Users/ruki/Library/Huawei/Sdk/...

添加 native 模塊支持

我們知道,在 xmake 中,可以通過 import 接口去導入一些 lua 模塊在腳本域中使用,但是如果一些模塊的操作比較耗時,那麼 lua 實現並不是理想的選擇。
因此,新版本中,我們新增了 native lua 模塊的支持,可以通過 native 實現,來達到提速優化的效果,並且模塊導入和使用,還是跟 lua 模塊一樣簡單。

使用原生模塊時,xmake 會進行兩段編譯,先會自動編譯原生模塊,後將模塊導入 lua 作爲庫或二進制,而對於用戶,僅僅只需要調用 import 導入即可。

定義動態庫模塊

動態庫模塊的好處是,不僅僅通過 native 實現了性能加速,另外避免了每次調用額外的子進程創建,因此更加的輕量,速度進一步得到提升。

我們可以先定義一個動態庫模塊,裏面完全支持 lua 的所有 c API,因此我們也可以將一些第三方的開源 lua native 模塊直接引入進來使用。

這裏我們也有一個完整的導入 lua-cjson 模塊的例子可以參考:native_module_cjson

首先,我們先實現 shared 的 native 代碼,所以接口通過 lua API 導出。

./modules/foo/foo.c

#include <xmi.h>

static int c_add(lua_State* lua) {
    int a = lua_tointeger(lua, 1);
    int b = lua_tointeger(lua, 2);
    lua_pushinteger(lua, a + b);
    return 1;
}

static int c_sub(lua_State* lua) {
    int a = lua_tointeger(lua, 1);
    int b = lua_tointeger(lua, 2);
    lua_pushinteger(lua, a - b);
    return 1;
}

int luaopen(foo, lua_State* lua) {
    // 收集add和sub
    static const luaL_Reg funcs[] = {
        {"add", c_add},
        {"sub", c_sub},
        {NULLNULL}
    };
    lua_newtable(lua);
    // 傳遞函數列表
    luaL_setfuncs(lua, funcs, 0);
    return 1;
}

注意到這裏,我們 include 了一個 xmi.h 的接口頭文件,其實我們也可以直接引入 lua.hluaconf.h,效果是一樣的,但是會提供更好的跨平臺性,內部會自動處理 lua/luajit還有版本間的差異。

然後,我們配置 add_rules("modules.shared") 作爲 shared native 模塊來編譯,不需要引入任何其他依賴。

甚至連 lua 的依賴也不需要引入,因爲 xmake 主程序已經對其導出了所有的 lua 接口,可直接使用,所以整個模塊是非常輕量的。

./modules/foo/xmake.lua

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

target("foo")
    -- 指定目標爲庫lua模塊
    add_rules("module.shared")
    add_files("foo.c")

定義二進制模塊

出了動態庫模塊,我們還提供了另外一種二進制模塊的導入。它其實就是一個可執行文件,每次調用模塊接口,都會去調用一次子進程。

那它有什麼好處呢,儘管它沒有動態庫模塊那麼高效,但是它的模塊實現更加的簡單,不需要調用 lua API,僅僅只需要處理參數數據,通過 stdout 去輸出返回值即可。

另外,相比二進制分發,它是通過源碼分發的,因此也解決了跨平臺的問題。

具體是使用動態庫模塊,還是二進制模塊,具體看自己的需求,如果想要實現簡單,可以考慮二進制模塊,如果想要高效,就用動態庫模塊。

另外,如果需要通過並行執行來提速,也可以使用二進制模塊。

./modules/bar/bar.cpp

#include <stdio.h>
#include <stdlib.h>
#include <cstdlib>

int main(int argc, char** argv) {
    int a = atoi(argv[1]);
    int b = atoi(argv[2]);
    printf("%d", a + b);
    return 0;
}

./modules/bar/xmake.lua

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

target("add")
    -- 指定目標爲二進制lua模塊
    add_rules("module.binary")
    add_files("bar.cpp")

導入原生模塊

對於模塊導入,我們僅僅需要調用 import,跟導入 lua 模塊的用法完全一致。

./xmake.lua

add_rules("mode.debug""mode.release")
-- 添加./modules目錄內原生模塊
add_moduledirs("modules")

target("test")
    set_kind("phony")
    on_load(function(target)
        import("foo", {always_build = true})
        import("bar")
        print("foo: 1 + 1 = %s", foo.add(11))
        print("foo: 1 - 1 = %s", foo.sub(11))
        print("bar: 1 + 1 = %s", bar.add(11))
    end)

由於插件模塊的構建是跟主工程完全獨立的,因此,native 模塊只會被構建一次,如果想要觸發增量的插件編譯,需要配置上 always_build = true,這樣,xmake 就會每次檢測插件代碼是否有改動,如果有改動,會自動增量構建插件。

首次執行效果如下:

ruki-2:native_module ruki$ xmake
[ 50%]: cache compiling.release src/foo.c
[ 50%]: cache compiling.release src/bar.c
[ 75%]: linking.release libmodule_foo.dylib
[ 75%]: linking.release module_bar
[100%]: build ok, spent 1.296s
foo: 1 + 1 = 2
foo: 1 - 1 = 0
bar: 1 + 1 = 2
[100%]: build ok, spent 0.447s

第二次執行,就不會再構建插件,可以直接使用模塊:

ruki-2:native_module ruki$ xmake
foo: 1 + 1 = 2
foo: 1 - 1 = 0
bar: 1 + 1 = 2
[100%]: build ok, spent 0.447s

作爲 codegen 來使用

通過新的 native 模塊特性,我們也可以用來實現 auto-codegen,然後根據自動生成的代碼,繼續執行後續編譯流程。

這裏也有完整的例子可以參考:autogen_shared_module。

添加 signal 模塊

新版本中,我們還新增了信號註冊接口,我們可以在 lua 層,註冊 SIGINT 等信號處理函數,來定製化響應邏輯。

signal.register

這個接口用於註冊信號處理器,目前僅僅支持 SIGINT 信號的處理,同時它也是支持 windows 等主流平臺的。

import("core.base.signal")

function main()
    signal.register(signal.SIGINT, function (signo)
        print("signal.SIGINT(%d)", signo)
    end)
    io.read()
end

這對於當一些子進程內部屏蔽了 SIGINT,導致卡死不退出,即使用戶按了 Ctrl+C 退出了 xmake 進程,它也沒有退出時候,
我們就可以通過這種方式去強制退掉它。

import("core.base.process")
import("core.base.signal")

function main()
    local proc
    signal.register(signal.SIGINT, function (signo)
        print("sigint")
        if proc then
            proc:kill()
        end
    end)
    proc = process.open("./trap.sh")
    if proc then
        proc:wait()
        proc:close()
    end
end

關於這個問題的背景,可以參考:#4889

signal.ignore

我們也可以通過 signal.ignore 這個接口,去忽略屏蔽某個信號的處理。

signal.ignore(signal.SIGINT)

signal.reset

我們也可以清除某個信號的處理函數,回退到默認的處理邏輯。

signal.reset(signal.SIGINT)

增加對 cppfront/h2 的支持

我們還改進了對 cppfront 的最新版本支持,而新版本的 cppfront 增加了 .h2 頭文件的處理,因此我們也增加了對它的支持。

感謝來自 @shaoxie1986 的貢獻。、

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

add_requires("cppfront")

target("test")
    add_rules("cppfront")
    set_kind("binary")
    add_files("src/*.cpp2")
    add_files("src/*.h2")
    add_packages("cppfront")

改進構建速度

新版本中,我們還修復一個並行構建相關的問題,經過對調度器的重構,構建速度得到明顯的提升,尤其在 cpp 文件編譯耗時非常慢的增量編譯場景,效果更爲明顯。

相關背景見:#4928

更新日誌

新特性

  • #4874: 添加鴻蒙 SDK 支持

  • #4889: 添加 signal 模塊 去註冊信號處理

  • #4925: 添加 native 模塊支持

  • #4938: 增加對 cppfront/h2 的支持

改進

  • 改進包管理,支持切換 clang-cl

  • #4893: 改進 rc 頭文件依賴檢測

  • #4928: 改進構建和鏈接速度,增量編譯時候效果更加明顯

  • #4931: 更新 pdcurses

  • #4973: 改進選擇腳本的匹配模式

Bugs 修復

  • #4882: 修復安裝組依賴問題

  • #4877: 修復 xpack 打包時,unit build 編譯失敗問題

  • #4887: 修復 object 依賴鏈接

關注公衆號

TBOOX

C




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

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