重构基于 CMake 的构建工具链

 2024-02-26 00:06:01  阅读 0

背景

对于大型项目来说,必然存在很多依赖关系。 特别是现代组件会尝试重用社区资源。 对于C/C++来说,依赖管理一直是个大问题。 很多老式的系统和工具都会尝试遵循相对规范的安装流程,比如使用pkg-或者使用系统自带的包管理工具来安装在系统的默认路径下。 当然,这样很不方便,而且定制组件也不容易。 我经常使用 cmake,因此我的项目集合中总是有一个项目,其中包含一些常用的构建脚本。 并且一些简单的包管理和构建过程都是在-co中实现的。

然而,随着依赖关系变得越来越复杂,在添加和更新依赖关系时,测试多个包之间的兼容性变得更加频繁,有时需要自己打补丁。 有些组件可能只需要某些依赖包,此时导入是不合适的。 因此产生了将构建系统配置和包管理分开并单独维护的想法。

事实上,已经有相当多的C/C++包管理系统了。 比较主流的有bazel、vcpkg等,还有一些不太主流的如conan、、cget、spack等,各个的细节和区别我还没有研究过。 但即使是比较主流的bazel和vcpkg也无法满足我们的需求。

巴泽尔的问题

Bazel 声称是一个原生支持分布式编译的构建系统。 其项目管理主要分为两个阶段。 首先是声明阶段,大多数开源项目都管理依赖包的声明和配置; 接下来是BUILD阶段,这是实际的编译和执行行为。

但巴泽尔有一些问题。 首先,它要求所有依赖包提供bazel构建系统支持。 现有的支持bazel的包并不多,而且即使支持,也不是所有的都得到很好的支持(在某些环境下编译仍然存在问题)。 bazel 的一大好处是它的依赖包通过包名称进行索引。 因此,当存在相互依赖的依赖包时,父节点声明的包名称是标准化和统一的,并且子依赖的版本也可以控制。 由于多个C/C++包之间的调用直接使用符号,所以在某些语言中无法实现同一个包名的多个版本的共存。 上游系统有能力选择合适的依赖组合是非常重要的。

其次,很多C/C++包和库都有功能开关,根据不同的系统环境和选项,会选择不同的功能开关组合。 大多数情况下,bazel构建的包由包本身提供来实现不同的功能组,需要用户在编译时设置使用哪些功能组。 然而,C/C++ 中的大多数包和库通过检测环境和函数来切换每个细节。 每个功能之间的组合配置显然不太实用,所以一般 bazel 构建的包只会提供几个典型的选项,当我们想要精确控制功能细节时,这是非常不友好的。 相比之下,我认为 Rust 的 Cargo 和那些模仿 Cargo 的人在这一点上做得非常好。

也可能是我对bazel的理解有限。 我还没有找到使用bazel进行函数检测来切换函数或者依赖包的方法。 比如我前段时间向-cpp提交了一个PR,遇到了一些问题。 -cpp 官方支持的编译器是GCC 4.8-最新的,MSVC 2019+,Clang忘记了最低版本要求。 其依赖包包括gRPC和gRPC,其中gRPC依赖于-cpp。 使用海湾合作委员会时:

所以最好的办法是更高版本的gcc使用最新的gRPC,但是gcc 4.8只使用gRPC 1.33。

Bazel和宏只能在BUILD阶段使用,不能在声明阶段使用。 但包裹申报已进入阶段。 别名函数好像是BUILD阶段的函数,不能影响其他依赖库? 我也尝试过使用 -- 来覆盖包信息,但这个选项似乎只能替换本地路径。

总而言之,我没有找到合适的方法来完成这个功能。 希望熟悉bazel的人能够提供解决方案。

我尝试使用上面提到的——是这样的:

maybe(
    http_archive,
    name = "com_github_grpc_grpc_legacy",
    strip_prefix = "grpc-1.33.2",
    urls = [
        "https://github.com/grpc/grpc/archive/v1.33.2.tar.gz",
    ],
)
maybe(
    http_archive,
    name = "com_github_grpc_grpc",
    sha256 = "abd9e52c69000f2c051761cfa1f12d52d8b7647b6c66828a91d462e796f2aede",
    strip_prefix = "grpc-1.38.0",
    urls = [
        "https://github.com/grpc/grpc/archive/v1.38.0.tar.gz",
    ],
)

复制

然后执行bazel build []gacy //...

vcpkg/柯南 好吃吗?

使用 vcpkg 或 conan 怎么样? 首先,我们可以在vcpkg页面上找到vcpkg和conan(#why-not-conan)之间的主要区别。 差异的简单翻译如下:

Vcpkg VS 柯南:

Conan 仅提供工具,不保证包的质量或兼容性。 编译环境支持的不止vcpkg。 vcpkg统一了包管理副本,而Conan则要求用户负责包的兼容性和匹配。 也有可能多个包依赖于同一个包的不同版本,这在C/C++中是非常危险的。 Vcpck基于CMake,Conan基于它,但大部分包构建过程依赖于cmake。

显然vcpkg的易用性和安全性要好很多,而且基于git和cmake的vcpkg也可以实现非常灵活的功能,但是vcpkg也有一些缺陷。

首先,和bazel类似,定制依赖包比较困难。 导入包时,您要么不需要它们,要么不需要全部。 比如在使用的时候,你可以选择使用它,或者其他的库作为SSL库,或者有些功能不需要,甚至可以禁用依赖库的功能。 但如果你使用vcpkg来安装它,你就没有选择了。 但现在似乎设计了一个有点类似的解决方案(和),一定程度上解决了这个问题。

第二个问题是vcpkg官方支持的编译环境比较新。 它需要 VS 2015 3 或更高版本,Linux 需要 GCC 6 或更高版本,macOS 也需要并安装 gcc 6 或更高版本。 还支持一些其他环境,但未得到官方支持。

相对来说,柯南的环境支持比较好:#-yml。 但自己去管理各个包的版本以及对应工具链的兼容性还是很不方便的。

第三个问题比较难解决。 vcpkg 中的大多数包都配置为从以下位置下载,有些则只能从其他一些 URL 下载。 虽然地址可以配置,但是vcpkg下载包版本的代码使用的是开放平台接口。 新版本似乎增加了可以解决这个问题的端口,但是操作方法还是比较麻烦。 相当于要重新编写依赖包的端口,对用户的要求还是有点高。

还有一些外围问题,这几天在做-cpp的时候也发现了其中一个问题。 截至我写这篇文章时,vcpkg的最后一个版本是2021.05.12,里面的版本是3.15.8。 前几天,MSVC更新到了1929版本(VS 16.10),而这个版本就是不支持,这就很尴尬了。

所以总的来说,vcpkg 在大多数情况下都还不错。 但在某些场景下它并不是很流行,比如定制内部源、组件版本控制、低版本编译器支持它。

cmake-

本来我是用cmake来做项目管理的,所以现在也用cmake和git。 另外,学习了一手cpp的CI检测,保证发布的版本在各种环境下都能正常构建和使用。

这套工具的主要功能之一就是实现一些编译器功能的原始检测,比如是否启用异常、是否支持RTTI、是否支持C++20等等。我们的项目都有比较严格的要求编译警告选项(GCC和Clang下为-Wall - -,MSVC下为/W4 /WX),因此我们需要提供工具来允许某些函数使用这些选项。 另外,必须提供工具来允许子模块继承父项目的一些选项。 例如,如果外层使用clang+libc++,那么依赖库和子仓库也必须使用clang+libc++。

我个人认为vcpkg的发展前景是比较好的,很多问题都会慢慢得到妥善解决。 因此,对于比较新的编译环境和首次支持的平台,还是更推荐直接使用vcpkg。 在cmake-中,我还添加了对vcpkg的适配支持。 您可以直接导入vcpkg文件来使用。 大多数导入的依赖库都支持直接从vcpkg中搜索。

另外,当不使用vcpkg或者vcpkg中没有安装某个依赖时,我会按照自己内部统一的编译安装流程,并预留上层应用可以控制的源码和版本号来下载,甚至一些编译参数。 。 这使得上层需要定制时变得更加容易。

我们简单列出一下迁移过程中出现的新问题:

有些环境会生成cmake模块。 那么如果指定了父项目,将找不到匹配的链接目标。 所以我编写了工具来自动将某些配置导出到未指定的配置。 这样可以适应一些依赖包的搜索过程。

交叉编译的二进制文件

交叉编译时,有时需要编译主机版本的二进制文件以供使用。 例如,如果我们使用它,链接库必须使用目标平台的库,但如果我们要使用生成的代码,则需要使用宿主平台版本。 因此,对于这类库,目前的做法是经过专门的编译过程,同时编译出两个平台的可执行程序。

然后我尝试的第一件事是不编译目标平台的二进制文件,而只编译库。 主机平台只编译可执行程序。 但是发现这样会导致()在搜索cmake模块时丢失一些目标。 所以最终我采用了编译整个目标平台,编译完宿主平台的可执行程序后再打补丁的方法。

另一件事是,对于iOS,需要设置tvOS和可执行程序。 我这里只是一个编译时工具链,不需要安装和运行。 所以我就把这个设置关掉了。

CMake机制

CMake有内置的包仓库机制,安装时会注册一些依赖包。 这会影响()的结果,所以我在继承变量中添加了 , , , , , ISTRY 。 并继承了这一点。

如果()发现意外的奇怪路径,可以在这里搜索并删除。

长路径问题(260路径长度限制)

还有一个问题是,使用cmake-时,默认的依赖编译目录是BUILD目录/_deps/和工具链名/包名。 很容易名字很长,然后我们在编译的时候就会遇到路径过长的问题。

解决方案之一是直接更改注册表。 您可以使用脚本 New- -Path "HKLM:\\\\" -Name "" -Value 1 - DWORD -Force 或注册表文件

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem]
"LongPathsEnabled"=dword:00000001

复制

但并不是所有的工具似乎都对此有很好的支持,这需要管理员权限。 所以最终我再次构建时,将依赖的编译目录改为用户目录/cmake——以减少一定的长度。

SDK版本

新版本的MSVC支持C11,但是需要使用新版本的SDK,可以指定。 特别是一些C依赖包使用C11支持后无需额外补丁即可编译(如lua等)。 有关如何查找和使用最新版本SDK的具体说明,请参阅CI脚本。

CI 内存不足

由于是免费使用,所以一些依赖包在多进程编译时仍然会导致OOM。 然后cmake在检测环境CPU数量和控制并发数方面不准确,所以我添加了低内存模式。 手动减少并发以避免OOM。

终于

我的大部分工具已经迁移到了新的cmake-,以后会慢慢迁移。 如果以后遇到什么问题我会写下来。

最终重构的提取构建工具集(cmake-)位于 . 主版本号保证API兼容性,二级版本号表示是否有新功能,三级版本号用于优化和修订。

以下是支持的环境(在 CI 测试中配置):

编译器:特殊包:

欢迎有意者相互交流。

标签: bazel cmake 编译程序

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码