Xmake和C/C++包管理
了解Xmake对C/C++包管理的支持是如何被持续改进的,并发现一些被添加的有用的包管理功能。
Xmake是一个基于Lua的轻量级跨平台构建工具。之前的文章对Xmake和构建系统做了详细介绍。
如果你已经对Xmake有了大致的了解,你会知道它不仅是一个构建工具,而且还内置了对C/C++包管理的支持。我们也可以把Xmake理解为。
Xmake = Build backend + Project Generator + Package Manager
经过几年的不断迭代,Xmake对C/C++包管理的支持不断完善,增加了许多有用的包管理功能。因此,在这篇文章中,我们将对其做一些总结,希望能对大家有所帮助。
构建系统和包管理
C++的生态是非常复杂的,这有一定的原因。在任何情况下,官方文档都没有提供原生的包管理支持。对于我们的开发者来说,使用第三方的C++依赖库有些不方便。
事实上,已经有很多强大的C/C++包管理器了。最著名和最常用的是Vcpkg、Conan和Conda。尽管它们都非常强大,但它们有一个共同的问题:构建系统没有对它们的内置支持。
由于CMake没有为它们提供内置支持,所以在CMake中使用它们来集成依赖关系是非常麻烦的,而且集成和使用的方式也不一致。
在CMake中使用Conan
为了使用Conan在CMake中集成C/C++包,我们需要提供额外的CMake Wrapper脚本,并以类似于插件的方式将其注入自己的项目中。
cmake_minimum_required(VERSION 3.5)
project(FormatOutput CXX)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR})
add_definitions("-std=c++11")
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.16.1/conan.cmake"
"${CMAKE_BINARY_DIR}/conan.cmake"
EXPECTED_HASH SHA256=396e16d0f5eabdc6a14afddbcfff62a54a7ee75c6da23f32f7a31bc85db23484
TLS_VERIFY ON)
endif()
include(${CMAKE_BINARY_DIR}/conan.cmake)
conan_cmake_configure(REQUIRES fmt/6.1.2
GENERATORS cmake_find_package)
conan_cmake_autodetect(settings)
conan_cmake_install(PATH_OR_REFERENCE .
BUILD missing
REMOTE conancenter
SETTINGS ${settings})
find_package(fmt)
add_executable(main main.cpp)
target_link_libraries(main fmt::fmt)
为了集成一个包,需要配置很多额外的脚本。
在CMake中使用Vcpkg
为了在CMake中使用Vcpkg集成包,我们还需要注入一个额外的工具链脚本文件。
cmake -B [build directory] -S . -DCMAKE_TOOLCHAIN_FILE=[path to vcpkg]/scripts/buildsystems/vcpkg.cmake
cmake --build [build directory]
此外,还有一个问题,那就是我们需要调用vcpkg install [packages] 命令来安装这个包。每一个环节都需要用户进行额外的探索过程,不可能实现真正的、一键式编译。
当我们下载一个集成了vcpkg 包的CMake项目时,如果我们想编译它,除了项目配置外,还需要做这些额外的事情。
- 安装Vcpkg。
- 执行
vcpkg install xxx,安装所需的软件包。 - 执行
cmake,将vcpkg.cmake脚本传递给CMake进行项目配置。
在CMake中使用FetchContent
CMake也可以提供FetchContent 来管理依赖关系,但似乎需要下载源代码,而且所有的依赖关系都必须基于CMake来维护和构建。
此外,我们还需要为每个依赖关系配置各种包信息,如url、版本等。
cmake_minimum_required(VERSION 3.14)
project(fetchContent_example CXX)
include(FetchContent)
FetchContent_Declare(
DocTest
GIT_REPOSITORY "https://github.com/onqtam/doctest"
GIT_TAG "932a2ca50666138256dae56fbb16db3b1cae133a"
)
FetchContent_Declare(
Range-v3
GIT_REPOSITORY "https://github.com/ericniebler/range-v3"
GIT_TAG "4d6a463bca51bc316f9b565edd94e82388206093"
)
FetchContent_MakeAvailable(DocTest Range-v3)
add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} doctest range-v3)
在Meson中使用依赖关系
Meson非常强大,也提供了自己的包管理支持。然而,在Meson中使用其他软件包管理器也很麻烦,如vcpkg/conan等,而且不提供内置支持。
在Xmake中使用依赖关系
Xmake不仅提供了内置的xmake-repo软件包管理库,可以直接集成使用,而且还支持同样的集成方式来快速集成第三方依赖,如vcpkg/conan。
我们只需要几行配置就可以集成一个内置依赖包。
add_requires("zlib 1.2.11")
target("test")
add_files("src/*.c")
add_packages("zlib")
要集成一个Vcpkg包,我们只需要添加相应的包管理器命名空间,像这样。
add_requires("vcpkg::zlib 1.2.11")
target("test")
add_files("src/*.c")
add_packages("vcpkg::zlib")
要集成一个Conan包,或第三方包,如Conda、Homebrew、Pacman、Apt、clib等,我们只需要把它改成conan::zlib ,用户就可以随意切换包源。
此外,Xmake会自动为你调用vcpkg/conan install 安装命令来安装依赖的软件包,然后进行整合,不需要用户做任何其他事情。只需执行xmake 一键式编译。
C/C++包太少了?
你是否认为Xmake的内置包库中的包太少了?这一点也不重要。理论上,你可以通过Xmake使用整个C/C++生态系统中90%的常见依赖,因为Xmake可以快速整合来自其他各种包管理器的包来使用。
目前Xmake支持的包源如下。
- 官方软件包库 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)
- Archlinux/msys2上的Pacman(pacman::libcurl)
- ubuntu/debian上的Apt (apt::zlib1g-dev)
- Clib (clib::clibs/bytes@0.0.4)
- Dub (dub::log 0.4.3)
- Gentoo/Linux上的Portage (portage::libhandy)
- 用于nimlang的Nimble (nimble::zip >1.3)
- 用于 rust 的 Cargo (cargo::base64 0.13.0)
基本上,这些软件库已经基本涵盖了C/C++用户日常需要的所有软件包。
我们统计了vcpkg/conan/xmake-repo软件库中的软件包数量。
- vcpkg: 1859
- conan: 1218
- xmake-repo: 651
可以看出,目前Xmake内置仓库中的软件包数量已经接近vcpkg/conan了。有相当多的包,而且我们也在不断地加入新的包。然而,这根本不重要,因为我们可以使用任何软件包库的软件包。
如果我们在CMake中使用Vcpkg,我们只能使用1859个包。如果我们在CMake中使用Conan,我们只能使用1218个包。而如果在Xmake中使用软件包,我们可以使用651(xmake-repo)+vcpkg/conan(1k+)+更多(Conda,Homebrew,Pacman,Apt,clib等)的软件包。
即使C/C++软件包不够用,也可以使用其他语言的软件包。例如,Xmake也支持从Dlang/Rust软件包管理器(如dub/cargo)中拉取软件包用于C/C++项目。
Xmake内置的包管理集成
除了访问第三方软件包管理外,我们还建议先使用集成的xmake-repo内置库中提供的软件包,Xmake会提供更多的功能支持。因此,如果你需要的包没有被收录,你可以尝试先提交给xmake-repo。
接下来,我们系统地介绍一下集成内置包的一些特性。
语义版本设置
Xmake的依赖包管理完全支持语义版本选择;例如。"~1.6.1."可以对语义版本进行具体描述。
例如,一些语义版本的写法如下。
add_requires("tbox 1.6.*", "pcre 1.3.x", "libpng ^1.18")
add_requires("libpng ~1.16", "zlib 1.1.2 || >=1.2.11 <1.3.0")
当然,如果我们对当前依赖包的版本没有特殊要求,我们可以直接这样写。
add_requires("tbox", "libpng", "zlib")
这将使用该包的最新已知版本,或者从主分支的源代码编译的包。如果当前包有一个Git repo地址,我们还可以指定一个特定的分支版本。
add_requires("tbox master")
add_requires("tbox dev")
Xmake的语义版本支持在几年前就得到了很好的支持,而Vcpkg在过去一年中通过manifest模式勉强支持了它。即使是现在,Vcpkg 对版本语义的支持也非常有限。它只能支持几种版本模式,如>=1.0,1.0 ,而你想选择任何版本的包,如>=1.0 <1.5 。Vcpkg仍然不能支持其他复杂的版本条件包。
可选的软件包设置
如果指定的依赖包不被当前平台支持,或者编译和安装失败,Xmake将编译并报告错误,这对于一些必须依赖某些包才能工作的项目是合理的。然而,如果某些包是可选的依赖,即使它们不可用,也可以正常编译和使用。它们可以被设置为可选包。
add_requires("tbox", {optional = true})
使用系统库
默认情况下,Xmake会首先检测系统库是否存在(如果没有设置版本要求)。如果用户不希望使用系统库和第三方软件包管理提供的库,可以设置。
add_requires("tbox", {system = false})
如果配置为。
add_requires("tbox", {system = true})
它只是找到并使用系统库,而不进行远程下载和安装。这与CMake的find_package ,但整合方法更简单,更一致。
使用包的调试构建
如果我们想同时调试依赖包,我们可以将其设置为使用包的调试版本(当然,前提是这个包支持调试编译)。
add_requires("tbox", {debug = true})
启用软件包的可选功能
我们还可以安装具有特定功能的包,比如在安装ffmpeg 包时启用zlib 和libx265 。
add_requires("ffmpeg", {configs = {zlib = true, libx265 = true}})
传递额外的编译选项
我们还可以向软件包传递额外的编译选项。
add_requires("spdlog", {configs = {cxflags = "-Dxxx"}})
独立的软件包管理命令 Xrepo
Xrepo是一个基于Xmake的跨平台C/C++包管理器。它是一个独立于Xmake的命令程序,用于协助用户管理依赖包,类似于vcpkg/conan,但与它们相比,它有一些额外的实用功能。下面我们将简单介绍一些。
多库管理
除了直接从官方仓库xmake-repo检索安装包外,我们还可以添加任意数量的自建仓库,甚至完全隔离外网,只在公司内网维护安装和集成私有包。
只要用下面的命令添加你自己的版本库URL即可。
$ xrepo add-repo myrepo https://github.com/mygroup/myrepo
基本用法
$ xrepo install zlib tbox
安装指定的版本包
完全支持语义版本管理。
$ xrepo install "zlib 1.2.x"
$ xrepo install "zlib >=1.2.0"
安装指定的平台包
$ xrepo install -p iphoneos -a arm64 zlib
$ xrepo install -p android [--ndk=/xxx] zlib
$ xrepo install -p mingw [--mingw=/xxx] zlib
$ xrepo install -p cross --sdk=/xxx/arm-linux-musleabi-cross zlib
安装调试版本包
$ xrepo install -m debug zlib
安装动态库版本包
$ xrepo install -k shared zlib
安装指定的配置包
$ xrepo install -f "vs_runtime=MD" zlib
$ xrepo install -f "regex=true,thread=true" boost
从第三方软件包管理器安装软件包
$ xrepo install brew::zlib
$ xrepo install vcpkg::zlib
$ xrepo install conan::zlib/1.2.11
查看软件包的库使用信息
$ xrepo fetch pcre2
{
{
linkdirs = {
"/usr/local/Cellar/pcre2/10.33/lib"
},
links = {
"pcre2-8"
},
defines = {
"PCRE2_CODE_UNIT_WIDTH=8"
},
includedirs = "/usr/local/Cellar/pcre2/10.33/include"
}
}
$ xrepo fetch --ldflags openssl
-L/Users/ruki/.xmake/packages/o/openssl/1.1.1/d639b7d6e3244216b403b39df5101abf/lib -lcrypto -lssl
$ xrepo fetch --cflags openssl
-I/Users/ruki/.xmake/packages/o/openssl/1.1.1/d639b7d6e3244216b403b39df5101abf/include
$ xrepo fetch -p [iphoneos|android] --cflags "zlib 1.2.x"
-I/Users/ruki/.xmake/packages/z/zlib/1.2.11/df72d410e7e14391b1a4375d868a240c/include
$ xrepo fetch --cflags --ldflags conan::zlib/1.2.11
-I/Users/ruki/.conan/data/zlib/1.2.11/_/_/package/f74366f76f700cc6e991285892ad7a23c30e6d47/include -L/Users/ruki/.conan/data/zlib/1.2.11/_/_/package /f74366f76f700cc6e991285892ad7a23c30e6d47/lib -lz
导入和导出已安装的软件包
Xrepo 可以快速导出已安装的软件包,包括相应的库文件,头文件等。
$ xrepo export -o /tmp/output zlib
你也可以在其他机器上导入之前导出的安装包,实现包的迁移。
$ xrepo import -i /xxx/packagedir zlib
搜索支持的软件包
$ xrepo search zlib "pcr*"
zlib:
-> zlib: A Massively Spiffy Yet Delicately Unobtrusive Compression Library (in xmake-repo)
pcr*:
-> pcre2: A Perl Compatible Regular Expressions Library (in xmake-repo)
-> pcre: A Perl Compatible Regular Expressions Library (in xmake-repo)
此外,你现在可以从第三方软件包管理器(如Vcpkg、Conan、Conda和Apt)中搜索其软件包。只需像本例那样添加相应的软件包命名空间。
$ xrepo search vcpkg::pcre
The package names:
vcpkg::pcre:
-> vcpkg::pcre-8.44#8: Perl Compatible Regular Expressions
-> vcpkg::pcre2-10.35#2: PCRE2 is a re-working of the original Perl Compatible Regular Expressions library
$ xrepo search conan::openssl
The package names:
conan::openssl:
-> conan::openssl/1.1.1g:
-> conan::openssl/1.1.1h:
包虚拟环境管理
我们可以通过在当前目录下添加xmake.lua文件来定制一些包的配置,然后进入一个特定的包壳环境。
add_requires("zlib 1.2.11")
add_requires("python 3.x", "luajit")
$ xrepo env shell
> python --version
> luajit --version
在Xmake中集成第三方构建系统
在Xmake中集成Cmake项目
Xmake并不打算分裂C/C++生态系统。它可以与cmake/autoconf/meson维护的现有项目兼容。例如,由CMake维护的其他一些代码库可以直接集成到本地并参与混合编译。换句话说,Xmake不会强迫用户将所有的项目重新移植到xmake.lua,现有的CMake项目可以快速集成到Xmake项目中。
例如,我们有以下的项目结构。
├── foo
│ ├── CMakeLists.txt
│ └── src
│ ├── foo.c
│ └── foo.h
├── src
│ └── main.c
├── test.lua
└── xmake.lua
foo目录是一个由CMake维护的静态库,根目录由Xmake维护。我们可以通过在xmake.lua中定义package("foo") 包来描述如何构建foo代码库。
add_rules("mode.debug", "mode.release")
package("foo")
add_deps("cmake")
set_sourcedir(path.join(os.scriptdir(), "foo"))
on_install(function (package)
local configs = {}
table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:debug() and "Debug" or "Release"))
table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF"))
import("package.tools.cmake").install(package, configs)
end)
on_test(function (package)
assert(package:has_cfuncs("add", {includes = "foo.h"}))
end)
package_end()
add_requires("foo")
target("demo")
set_kind("binary")
add_files("src/main.c")
add_packages("foo")
其中,我们使用set_sourcedir() 来设置foo包的代码目录位置,然后导入package.tools.cmake 辅助模块来调用CMake来构建代码。Xmake会自动获取生成的libfoo.a和相应的头文件。
仅对于本地源码集成,我们不需要设置额外的add_urls 和add_versions 。
一旦定义了包,我们就可以通过add_requires("foo") 和add_packages("foo") 来使用它,就像集成一个远程包一样。
另外,on_test 是可选的。如果你想严格检查软件包的编译和安装是否成功,你可以在其中做一些测试。
关于一个完整的例子,请看Library with CMakeLists。
在Xmake中集成Meson项目
Xmake支持集成更多由其他构建系统维护的第三方源代码库,例如Meson。你只需要导入并使用package.tools.meson 辅助构建模块来调用meson来构建它们。
例如,让我们从xmake-repo资源库中挑选一个用meson构建的软件包作为例子。
package("harfbuzz")
set_sourcedir(path.join(os.scriptdir(), "3rd/harfbuzz"))
add_deps("meson")
on_install(function (package)
local configs = {"-Dtests=disabled", "-Ddocs=disabled", "-Dbenchmark=disabled", "-Dcairo=disabled", "-Dfontconfig=disabled", "-Dglib=disabled", "-Dgobject= disabled"}
table.insert(configs, "-Ddefault_library=" .. (package:config("shared") and "shared" or "static"))
import("package.tools.meson").install(package, configs)
end)
在Xmake中整合Autoconf项目
我们还可以使用package.tools.autoconf ,将第三方代码库与autoconf维护进行原生集成。
package("libev")
set_sourcedir(path.join(os.scriptdir(), "3rd/libev"))
on_install(function (package)
import("package.tools.autoconf").install(package)
end)
package.tools.autoconf 和package.tools.cmake 模块都可以支持跨编译平台和工具链,如mingw/cross/iphoneos/android。Xmake会自动将相应的工具链传递给它,用户不需要做任何其他事情。
在Xmake中整合GN项目
我们还可以使用package.tools.gn ,以原生的方式集成由GN维护的第三方代码库。
package("skia")
set_sourcedir(path.join(os.scriptdir(), "3rd/skia"))
add_deps("gn", "ninja")
on_install(function (package)
import("package.tools.gn").install(package)
end)
请看完整的脚本例子,Skia与GN。
在Xmake中使用Cmake/C++查找软件包
现在CMake是事实上的标准,所以CMake提供的find_package 已经可以找到大量的系统库和模块。我们也可以完全重用CMake的这部分生态来扩展Xmake对包的集成。
将包的命名空间改为cmake::,类似于整合vcpkg/conan包。
add_requires("cmake::ZLIB", {alias = "zlib", system = true})
target("test")
set_kind("binary")
add_files("src/*.c")
add_packages("zlib")
我们指定system = true ,以告诉Xmake强制CMake从系统中找到该包。如果它找不到,它就不会遵循安装逻辑,因为CMake不提供vcpkg/conan等包管理器的安装功能,而只提供包搜索。 特性。
指定版本
add_requires("cmake::OpenCV 4.1.1", {system = true})
指定组件
add_requires("cmake::Boost", {system = true, configs = {components = {"regex", "system"}})}
预设开关
add_requires("cmake::Boost", {system = true, configs = {components = {"regex", "system"},
presets={Boost_USE_STATIC_LIB=true}}})
相当于在调用find_package 之前,在CMakeLists.txt中预设一些配置,以便在内部查找软件包,控制find_package 的搜索策略和状态。
set(Boost_USE_STATIC_LIB ON) -- will be used in FindBoost.cmake
find_package(Boost REQUIRED COMPONENTS regex system)
设置环境变量
add_requires("cmake::OpenCV", {system = true, configs = {envs = {CMAKE_PREFIX_PATH = "xxx"}}})
指定自定义FindFoo.cmake模块脚本目录
指定自定义目录为 mydir/cmake_modules/FindFoo.cmake。
add_requires("cmake::Foo", {system = true, configs = {moduledirs = "mydir/cmake_modules"}})
在Cmake中集成Xrepo的依赖关系
除了在Xmake中集成CMake项目外,我们还可以通过使用xrepo-cmake提供CMake Wrapper,直接在CMake中集成Xmake/Xrepo提供的包。
cmake_minimum_required(VERSION 3.13.0)
project(foo)
# Download xrepo.cmake if not exists in build directory.
if(NOT EXISTS "${CMAKE_BINARY_DIR}/xrepo.cmake")
message(STATUS "Downloading xrepo.cmake from https://github.com/xmake-io/xrepo-cmake/")
# mirror https://cdn.jsdelivr.net/gh/xmake-io/xrepo-cmake@main/xrepo.cmake
file(DOWNLOAD "https://raw.githubusercontent.com/xmake-io/xrepo-cmake/main/xrepo.cmake"
"${CMAKE_BINARY_DIR}/xrepo.cmake"
TLS_VERIFY ON)
endif()
# Include xrepo.cmake so we can use xrepo_package function.
include(${CMAKE_BINARY_DIR}/xrepo.cmake)
xrepo_package("zlib")
add_executable(example-bin "")
target_sources(example-bin PRIVATE
src/main.cpp
)
xrepo_target_packages(example-bin zlib)
用配置添加包
我们也可以像在Xmake中一样,自定义包的可选功能。
xrepo_package("gflags 2.2.2" CONFIGS "shared=true,mt=true")
add_executable(example-bin "")
target_sources(example-bin PRIVATE
src/main.cpp
)
xrepo_target_packages(example-bin gflags)
使用来自第三资源库的包
除了从Xmake官方维护的软件库中安装包,我们还可以在CMake中直接使用它来安装来自第三方软件库的包,只需将软件库的名字作为命名空间加入;例如,vcpkg::zlib和conan::pcre2 。
xrepo_package("conan::gflags/2.2.2")
xrepo_package("conda::gflags 2.2.2")
xrepo_package("vcpkg::gflags")
xrepo_package("brew::gflags")
通过这种方式,我们在CMake中统一了集成和使用vcpkg/conan包的方式,提供了自动安装包的功能,以及对其他包库的支持,如homebrew/conda。
CMake 库 构建(游戏引擎) 依赖性仓库(版本控制) 系统集成
经王润青许可发表于DZone。点击这里查看原文。