基於 xmake,助力打造跨平臺 C

xmake集成了內置的遠程包依賴管理,用戶只需要簡單地在項目中添加自己所需要的包和版本,即可自動下載和集成對應的包到項目中,並且實現編譯和鏈接。

例如:

add_requires("libuv master", "ffmpeg", "zlib 1.20.*")
add_requires("tbox >1.6.1", {optional = true, debug = true})
add_requires("boost", {alias = "boost_context", configs = {context = true}})
target("test")
set_kind("binary")
add_files("src/*.c")
add_packages("libuv", "ffmpeg", "tbox", "boost_context", "zlib")

xmake的包倉庫設計之初,就考慮到了語義版本支持,以及依賴包的跨平臺支持,只要包自身能支持的平臺,都可以集成進來,比如zlib包,在xmake中使用,iphoneos, android以及mingw平臺下都是完全可用的。

用戶只需要簡單的切下構建平臺:

xmake f -p iphoneos -a arm64
xmake
note: try installing these packages (pass -y to skip confirm)?
in xmake-repo:
-> zlib 1.2.11
please input: y (y/n)
=> download https://downloads.sourceforge.net/project/libpng/zlib/1.2.11/zlib-1.2.11.tar.gz .. ok
=> install zlib 1.2.11 .. ok

就可以對iphoneos平臺下載集成add_requires中對應的包,xmake的最終目標,是打造一個跨平臺的包倉庫,用戶不再需要滿地找c/c++庫,然後研究各種平臺的移植,只需要簡單的添加上包依賴,即可在各個平臺都能方便使用。

當然了,目前xmake的官方倉庫還在發展初期,裡面的包還很少,支持的平臺也不是很完善,因此,這裡我簡單介紹下用戶如何去自己製作和上傳自己需要的c/c++包,並如何提交到我們的倉庫中(也可以自建私有倉庫), 希望有興趣的小夥伴可以幫忙貢獻一份微薄之力,一起共同打造和建立c/c++依賴包生態。

  • 項目源碼
  • 官方文檔

添加包到倉庫

倉庫包結構

在製作自己的包之前,我們需要先了解下一個包倉庫的結構,不管是官方包倉庫,還是自建私有包倉庫,結構都是相同的:

xmake-repo
- packages
- t/tbox/xmake.lua
- z/zlib/xmake.lua

通過上面的結構,可以看到每個包都會有個xmake.lua用於描述它的安裝規則,並且根據z/zlib兩級子目錄分類存儲,方便快速檢索。

包描述說明

關於包的描述規則,基本上都是在它的xmake.lua裡面完成的,這跟項目工程裡面的xmake.lua描述很類似,不同的是描述域僅支持package(),

不過,在項目xmake.lua裡面,也是可以直接添加package()來內置包描述的,連包倉庫都省了,有時候這樣會更加方便。

首先,我們先拿zlib的描述規則,來直觀感受下,這個規則可以在xmake-repo/z/zlib/xmake.lua下找到。

package("zlib")
set_homepage("http://www.zlib.net")
set_description("A Massively Spiffy Yet Delicately Unobtrusive Compression Library")
set_urls("http://zlib.net/zlib-$(version).tar.gz",
"https://downloads.sourceforge.net/project/libpng/zlib/$(version)/zlib-$(version).tar.gz")
add_versions("1.2.10", "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017")
add_versions("1.2.11", "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1")
on_install("windows", function (package)
io.gsub("win32/Makefile.msc", "%-MD", "-" .. package:config("vs_runtime"))
os.vrun("nmake -f win32\\\\Makefile.msc zlib.lib")
os.cp("zlib.lib", package:installdir("lib"))
os.cp("*.h", package:installdir("include"))
end)
on_install("linux", "macosx", function (package)
import("package.tools.autoconf").install(package, {"--static"})
end)

on_install("iphoneos", "android@linux,macosx", "mingw@linux,macosx", function (package)
import("package.tools.autoconf").configure(package, {host = "", "--static"})
io.gsub("Makefile", "\\nAR=.-\\n", "\\nAR=" .. (package:build_getenv("ar") or "") .. "\\n")
io.gsub("Makefile", "\\nARFLAGS=.-\\n", "\\nARFLAGS=cr\\n")
io.gsub("Makefile", "\\nRANLIB=.-\\n", "\\nRANLIB=\\n")
os.vrun("make install -j4")
end)
on_test(function (package)
assert(package:has_cfuncs("inflate", {includes = "zlib.h"}))
end)

這個包規則對windows, linux, macosx, iphoneos,mingw等平臺都添加了安裝規則,基本上已經做到了全平臺覆蓋,甚至一些交叉編譯平臺,算是一個比較典型的例子了。

當然,有些包依賴源碼實現力度,並不能完全跨平臺,那麼只需對它支持的平臺設置安裝規則即可。

set_homepage

設置包所在項目的官方頁面地址。

set_description

設置包的相關描述信息,一般通過xmake require --info zlib查看相關包信息時候,會看到。

set_kind

設置包類型,對於依賴庫,則不用設置,如果是可執行包,需要設置為binary。

package("cmake")
set_kind("binary")
set_homepage("https://cmake.org")
set_description("A cross-platform family of tool designed to build, test and package software")

set_urls

設置包的源碼包或者git倉庫地址,跟add_urls不同的是,此接口是覆蓋性設置,而add_urls是追加設置,其他使用方式類似,這個根據不同需要來選擇。

add_urls

添加包的源碼包或者git倉庫地址,此接口一般跟add_version配對使用,用於設置每個源碼包的版本和對應的sha256值。

!> 可以通過添加多個urls作為鏡像源,xmake會自動檢測優先選用最快的url進行下載,如果下載失敗則會嘗試其他urls。

add_urls("https://github.com/protobuf-c/protobuf-c/releases/download/v$(version)/protobuf-c-$(version).tar.gz")
add_versions("1.3.1", "51472d3a191d6d7b425e32b612e477c06f73fe23e07f6a6a839b11808e9d2267")

urls裡面的$(version)內置變量,會根據實際安裝時候選擇的版本適配進去,而版本號都是從add_versions中指定的版本列表中選擇的。

如果對於urls裡面帶有比較複雜的版本串,沒有跟add_versions有直接對應關係,則需要通過下面的方式定製化轉換下:

add_urls("https://sqlite.org/2018/sqlite-autoconf-$(version)000.tar.gz",
{version = function (version) return version:gsub("%.", "") end})
add_versions("3.24.0", "d9d14e88c6fb6d68de9ca0d1f9797477d82fc3aed613558f87ffbdbbc5ceb74a")
add_versions("3.23.0", "b7711a1800a071674c2bf76898ae8584fc6c9643cfe933cfc1bc54361e3a6e49")

當然,我們也只可以添加git源碼地址:

add_urls("https://gitlab.gnome.org/GNOME/libxml2.git")

如果設置的多個鏡像地址對應的源碼包sha256是不同的,我們可以通過alias的方式來分別設置

add_urls("https://ffmpeg.org/releases/ffmpeg-$(version).tar.bz2", {alias = "home"})
add_urls("https://github.com/FFmpeg/FFmpeg/archive/n$(version).zip", {alias = "github"})
add_versions("home:4.0.2", "346c51735f42c37e0712e0b3d2f6476c86ac15863e4445d9e823fe396420d056")
add_versions("github:4.0.2", "4df1ef0bf73b7148caea1270539ef7bd06607e0ea8aa2fbf1bb34062a097f026")

add_versions

用於設置每個源碼包的版本和對應的sha256值,具體描述見:add_urls

add_patches

此接口用於針對源碼包,在編譯安裝前,先打對應設置的補丁包,再對其進行編譯,並且可支持同時打多個補丁。

if is_plat("macosx") then
add_patches("1.15", "https://raw.githubusercontent.com/Homebrew/patches/9be2793af/libiconv/patch-utf8mac.diff",
"e8128732f22f63b5c656659786d2cf76f1450008f36bcf541285268c66cabeab")

end

例如,上面的代碼,就是針對macosx下編譯的時候,打上對應的patch-utf8mac.diff補丁,並且每個補丁後面也是要設置sha256值的,確保完整性。

add_links

默認情況下,xmake會去自動檢測安裝後的庫,設置鏈接關係,但是有時候並不是很準,如果要自己手動調整鏈接順序,以及鏈接名,則可以通過這個接口來設置。

add_links("mbedtls", "mbedx509", "mbedcrypto")

add_syslinks

添加一些系統庫鏈接,有些包集成鏈接的時候,還需要依賴一些系統庫,才能鏈接通過,這個時候可以在包描述裡面都附加上去。

if is_plat("macosx") then
add_frameworks("CoreGraphics", "CoreFoundation", "Foundation")
elseif is_plat("windows") then
add_defines("CAIRO_WIN32_STATIC_BUILD=1")
add_syslinks("gdi32", "msimg32", "user32")
else
add_syslinks("pthread")
end

add_frameworks

添加依賴的系統frameworks鏈接。

示例見:add_syslinks

add_linkdirs

包的鏈接庫搜索目錄也是可以調整的,不過通常都不需要,除非一些庫安裝完不在prefix/lib下面,而在lib的子目錄下,默認搜索不到的話。

add_includedirs

添加其他頭文件搜索目錄。

add_defines

可以對集成的包對外輸出一些特定的定義選項。

add_configs

我們可以通過此接口添加每個包的對外輸出配置參數:

package("pcre2")
set_homepage("https://www.pcre.org/")
set_description("A Perl Compatible Regular Expressions Library")
add_configs("bitwidth", {description = "Set the code unit width.", default = "8", values = {"8", "16", "32"}})
on_load(function (package)
local bitwidth = package:config("bitwidth") or "8"
package:add("links", "pcre2-" .. bitwidth)
package:add("defines", "PCRE2_CODE_UNIT_WIDTH=" .. bitwidth)
end)

在工程項目裡面,我們也可以查看特定包的可配置參數和值列表:

$ xmake require --info pcre2
The package info of project:
require(pcre2):
-> description: A Perl Compatible Regular Expressions Library
-> version: 10.31
...

-> configs:
-> bitwidth:
-> description: Set the code unit width.
-> values: {"8","16","32"}
-> default: 8

然後在項目裡面,啟用這些配置,編譯集成帶有特定配置的包:

add_requires("pcre2", {configs = {bitwidth = 16}})

on_load

這是個可選的接口,如果要更加靈活的動態判斷各種平臺架構,針對性做設置,可以在這個裡面完成,例如:

on_load(function (package)
local bitwidth = package:config("bitwidth") or "8"
package:add("links", "pcre" .. (bitwidth ~= "8" and bitwidth or ""))
if not package:config("shared") then
package:add("defines", "PCRE_STATIC")
end
end)

pcre包需要做一些針對bitwidth的判斷,才能確定對外輸出的鏈接庫名字,還需要針對動態庫增加一些defines導出,這個時候在on_load裡面設置,就更加靈活了。

on_install

這個接口主要用於添加安裝腳本,前面的字符串參數用於設置支持的平臺,像on_load, on_test等其他腳本域也是同樣支持的。

平臺過濾

完整的過濾語法如下:plat|arch1,arch2@host|arch1,arch2

看上去非常的複雜,其實很簡單,其中每個階段都是可選的,可部分省略,對應:編譯平臺|編譯架構@主機平臺|主機架構

如果不設置任何平臺過濾條件,那麼默認全平臺支持,裡面的腳本對所有平臺生效,例如:

on_install(function (package)
-- TODO
end)

如果安裝腳本對特定平臺生效,那麼直接指定對應的編譯平臺,可以同時指定多個:

on_install("linux", "macosx", function (package)
-- TODO
end)

如果還要細分到指定架構才能生效,可以這麼寫:

on_install("linux|x86_64", "iphoneos|arm64", function (package)
-- TODO
end)

如果還要限制執行的主機環境平臺和架構,可以在後面追加@host|arch,例如:

on_install("mingw@windows", function (package)
-- TODO
end)

意思就是僅對windows下編譯mingw平臺生效。

我們也可以不指定比那一平臺和架構,僅設置主機平臺和架構,這通常用於描述一些跟編譯工具相關的依賴包,只能在主機環境運行。

例如,我們編譯的包,依賴了cmake,需要添加cmake的包描述,那麼裡面編譯安裝環境,只能是主機平臺:

on_install("@windows", "@linux", "@macosx", function (package)
-- TODO
end)

其他一些例子:

-- `@linux`
-- `@linux|x86_64`
-- `@macosx,linux`
-- `android@macosx,linux`
-- `android|armv7-a@macosx,linux`
-- `android|armv7-a@macosx,linux|x86_64`
-- `android|armv7-a@linux|x86_64`

編譯工具

我們內置了一些安裝常用編譯工具腳本,用於針對不同源碼依賴的構建工具鏈,進行方便的構架支持,例如:autoconf, cmake, meson等,

xmake

如果是基於xmake的依賴包,那麼集成起來就非常簡單了,xmake對其做了非常好的內置集成支持,可以直接對其進行跨平臺編譯支持,一般情況下只需要:

on_install(function (package)
import("package.tools.xmake").install(package)
end)

如果要傳遞一些特有的編譯配置參數:

on_install(function (package)
import("package.tools.xmake").install(package, {"--xxx=y"})
end)

cmake

如果是基於cmake的包,集成起來也很簡答,通常也只需要設置一些配置參數即可,不過還需要先添加上cmake的依賴才行:

add_deps("cmake")
on_install(function (package)
import("package.tools.cmake").install(package, {"-Dxxx=ON"})
end)

autoconf

如果是基於autoconf的包,集成方式跟cmake類似,只是傳遞的配置參數不同而已,不過通常情況下,unix系統都內置了autoconf系列工具,所以不加相關依賴也沒事。

on_install(function (package)
import("package.tools.autoconf").install(package, {"--enable-shared=no"})
end)

不過,有些源碼包用系統內置的autoconf可能不能完全滿足,那麼可以加上autoconf系列依賴,對其進行構建:

add_deps("autoconf", "automake", "libtool", "pkg-config")
on_install(function (package)
import("package.tools.autoconf").install(package, {"--enable-shared=no"})
end)

meson

如果是meson,還需要加上ninja的依賴來執行構建才行。

add_deps("meson", "ninja")
on_install(function (package)
import("package.tools.meson").install(package, {"-Dxxx=ON"})
end)

on_test

安裝後,需要設置對應的測試腳本,執行一些測試,確保安裝包的可靠性,如果測試不通過,則會撤銷整個安裝包。

on_test(function (package)
assert(package:has_cfuncs("inflate", {includes = "zlib.h"}))
end)

上面的腳本調用包內置的has_cfuncs接口,檢測安裝後的包是否存在zlib.h頭文件,以及庫和頭文件裡面是否存在inflate這個接口函數。

xmake會去嘗試編譯鏈接來做測試,has_cfuncs用於檢測c函數,而has_cxxfuncs則可以檢測c++庫函數。

而includes裡面可以設置多個頭文件,例如:includes = {"xxx.h", "yyy.h"}

我們還可以傳遞一些自己的編譯參數進去檢測,例如:

on_test(function (package)
assert(package:has_cxxfuncs("func1", {includes = "xxx.h", configs = {defines = "c++14", cxflags = "-Dxxx"}}))
end)

我們也可以通過check_csnippets和check_cxxsnippets檢測一個代碼片段:

on_test(function (package)
assert(package:check_cxxsnippets({test = [[
#include <boost>
#include <string>
#include <vector>
#include <assert.h>
using namespace boost::algorithm;
using namespace std;
static void test() {
string str("a,b");
vector<string> strVec;
split(strVec, str, is_any_of(","));
assert(strVec.size()==2);
assert(strVec[0]=="a");
assert(strVec[1]=="b");
}
]]}, {configs = {languages = "c++14"}}))
end)
/<string>/<assert.h>/<vector>/<string>/<boost>

如果是可執行包,也可以通過嘗試運行來檢測:

on_test(function (package)
os.run("xxx --help")
end)

如果運行失敗,那麼測試不會通過。

擴展配置參數

詳情見:add_configs

內置配置參數

除了可以通過add_configs設置一些擴展的配置參數以外,xmake還提供了一些內置的配置參數,可以使用

啟用調試包

add_requires("xxx", {debug = true})

包描述裡面必須有相關處理才能支持:

on_install(function (package)
local configs = {}
if package:debug() then
table.insert(configs, "--enable-debug")
end
import("package.tools.autoconf").install(package)
end)

設置msvc運行時庫

add_requires("xxx", {configs = {vs_runtime = "MT"}})

通常情況下,通過import("package.tools.autoconf").install等內置工具腳本安裝的包,內部都對vs_runtime自動處理過了。

但是如果是一些特殊的源碼包,構建規則比較特殊,那麼需要自己處理了:

on_install(function (package)
io.gsub("build/Makefile.win32.common", "%-MD", "-" .. package:config("vs_runtime"))
end)

添加環境變量

對於一些庫,裡面也帶了可執行的工具,如果需要在集成包的時候,使用上這些工具,那麼也可以設置上對應PATH環境變量:

package("luajit")
on_load(function (package)
if is_plat("windows") then
package:addenv("PATH", "lib")
end
package:addenv("PATH", "bin")
end)

而在項目工程中,只有通過add_packages集成對應的包後,對應的環境變量才會生效。

add_requires("luajit")
target("test")
set_kind("binary")
add_packages("luajit")
after_run(function (package)
os.exec("luajit --version")
end)

安裝二進制包

xmake也是支持直接引用二進制版本包,直接安裝使用,例如:

if is_plat("windows") then
set_urls("https://www.libsdl.org/release/SDL2-devel-$(version)-VC.zip")
add_versions("2.0.8", "68505e1f7c16d8538e116405411205355a029dcf2df738dbbc768b2fe95d20fd")
end
on_install("windows", function (package)
os.cp("include", package:installdir())
os.cp("lib/$(arch)/*.lib", package:installdir("lib"))
os.cp("lib/$(arch)/*.dll", package:installdir("lib"))
end)

本地測試

如果在本地xmake-repo倉庫中,已經添加和製作好了新的包,可以在本地運行測試下,是否通過,如果測試通過,即可提交pr到官方倉庫,請求merge。

我們可以執行下面的腳本進行測試指定包:

cd xmake-repo
xmake l/>

上面的命令,會強制重新下載和安裝zlib包,測試整個安裝流程是否ok,加上-v -D是為了可以看到完整詳細的日誌信息和出錯信息,方便調試分析。

如果網絡環境不好,不想每次測試都去重新下載所有依賴,可以加上--shallow參數來執行,這個參數告訴腳本,僅僅重新解壓本地緩存的zlib源碼包,重新執行安裝命令,但不會下載各種依賴。

cd xmake-repo
xmake l/>

如果我們想測試其他平臺的包規則是否正常,比如: android, iphoneos等平臺,可以通過-p/--plat或者-a/--arch來指定。

cd xmake-repo
xmake l/>xmake l/>

提交包到官方倉庫

目前這個特性剛完成不久,目前官方倉庫的包還不是很多,有些包也許還不支持部分平臺,不過這並不是太大問題,後期迭代幾個版本後,我會不斷擴充完善包倉庫。

如果你需要的包,當前的官方倉庫還沒有收錄,可以提交issues或者自己可以在本地調通後,貢獻提交到官方倉庫:xmake-repo

原文:https://my.oschina.net/tboox/blog/3088922


分享到:


相關文章: