Xmake 是一个根据 Lua 的轻量级跨渠道构建东西,关于 Xmake 与构建体系的介绍,咱们现已在之前的文章中做了详细的介绍:C/C++ 构建体系,我用 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

在 CMake 中运用 conan 集成 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

供给了 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 一键编译。

Xmake 和 C/C++ 包管理

C/C++ 包太少?

觉得 Xmake 内置的包库房里面的包太少么?彻底没关系,理论上,你能够经过 Xmake 运用整个 C/C++ 生态 90% 的常用依靠包,便是由于 Xmake 能够快速从各种其他包办理器中集成包来运用。

目前 Xmake 支撑的包源有以下这些:

  • Official package repository xmake-repo (tbox >1.6.1)
  • Official package manager Xrepo
  • User-built repositories
  • Conan (conan::openssl/1.1.1g)
  • Conda (conda::libpng 1.3.67)
  • Vcpkg (vcpkg:ffmpeg)
  • Homebrew/Linuxbrew (brew::pcre2/libpcre2-8)
  • Pacman on archlinux/msys2 (pacman::libcurl)
  • Apt on ubuntu/debian (apt::zlib1g-dev)
  • Clib (clib::clibs/bytes@0.0.4)
  • Dub (dub::log 0.4.3)
  • Portage on Gentoo/Linux (portage::libhandy)
  • Nimble for nimlang (nimble::zip >1.3)
  • Cargo for rust (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+) + more (conda, homebrew, pacman, apt, clib …) 中的包。

甚至,C/C++ 包不够,其他语言的包也能够拿过来用,例如:Xmake 也支撑从 dub/cargo 等 Dlang/Rust 的包办理器中拉取包,给 C/C++ 项目运用。

Xmake 内置包办理集成

除了接入第三方包办理,咱们也更推荐优先运用集成 xmake-repo 内置库房中供给的包,Xmake 会供给更多特性支撑。

因此,假如用户需求的包还没被录入,能够先测验提交到 xmake-repo 进来。

接下来,咱们体系介绍下,集成内置包的一些特性。

语义版别设置

Xmake 的依靠包办理是彻底支撑语义版别挑选的,例如:”~1.6.1″,关于语义版别的详细描述见:semver.org/

比方下面一些语义版别写法:

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")

这会运用已知的最新版别包,或许是master分支的源码编译的包,假如当时包有git repo地址,咱们也能指定特定分支版别:

add_requires("tbox master")
add_requires("tbox dev")

Xmake 的语义版别支撑,在几年前就现已很好的支撑,而 vcpkg 也只是在最近一年才经过清单形式勉强支撑它。

即便现在,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,但是集成方法更加简略共同。

运用调试版别的包

假如咱们想同时源码调试依靠包,那么能够设置为运用debug版别的包(当然前提是这个包支撑debug编译):

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

启用包的可选特性

咱们也能够装置带有指定特性的包,比方装置开启了 zlib 和 libx265 的 ffmpeg 包。

add_requires("ffmpeg", {configs = {zlib = true, libx265 = true}})

传递额定的编译选项

咱们也能够传递额定的编译选项给包:

add_requires("spdlog", {configs = {cxflags = "-Dxxx"}})

独立的包办理指令 Xrepo

Xrepo 是一个根据 Xmake 的跨渠道 C/C++ 包办理器。

它是一个独立于 Xmake 的指令程序,用于辅佐用户去办理依靠包,类似 vcpkg/conan,但比较它们,有额定多了一些有用的特性,咱们会简略介绍一些。

Xmake 和 C/C++ 包管理

多库房办理

除了能够直接从官方库房:xmake-repo 检索装置包之外, 咱们还能够增加恣意多个自建的库房,甚至能够彻底阻隔外网,只是在公司内部网络保护私有包的装置集成。

只需求经过下面的指令,增加上自己的库房地址:

$ xrepo add-repo myrepo https://github.com/mygroup/myrepo

根本运用

$ xrepo install zlib tbox

装置指定版别包

完好支撑 Semantic Versioning (语义版别)。

$ 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 文件,定制化一些包装备,然后进入特定的包 shell 环境。

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 不会强制用户将一切的项目从头 port 到 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 包的代码目录方位,然后经过 import 导入 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 with GN

在 Xmake 中查找运用 CMake/C++ 包

现在 CMake 现已是事实上的规范,所以 CMake 供给的 find_package 现已能够查找很多的体系库和模块,咱们也能够彻底复用 CMake 的这部分生态来扩充 xmake 对包的集成。

只需求像集成 vcpkg/conan 包那样,将包命名空间改成 cmake:: 就能够了。

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 项目,咱们也能够在 CMake 中直接集成 Xmake/Xrepo 供给的包,只需求运用 xrepo-cmake 供给的 CMake Wrapper。

例如:

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 等其他包库房的支撑。