Xmake和C/C++软件包管理

594 阅读11分钟

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项目时,如果我们想编译它,除了项目配置外,还需要做这些额外的事情。

  1. 安装Vcpkg。
  2. 执行vcpkg install xxx ,安装所需的软件包。
  3. 执行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 包时启用zliblibx265

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_urlsadd_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.autoconfpackage.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::zlibconan::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。点击这里查看原文。