使用 CMake 的 C++交叉编译项目管理第三方库依赖的最佳实践?

2020-02-09 18:57:23 +08:00
 mrcn

长话短说:项目现在使用 CMake,第三方库也同样是 CMake 的,目前在 CMakeList.txt 中使用 FetchContent 从 Github 上拉取源码后add_subdirectory来安装依赖。暂时一切 OK,只是 Github 连接不畅,每次要浪费长一点的时间安装依赖。直到今天尝试上了 CI,每次运行 CI 都要重新拉依赖,而且还要拉两次(因为有 amd64 和 arm 两个架构),就将这个问题放大了。

可能的解决途径:

  1. 提前编译依赖成 so,安装库到系统中

    因为最终软件是要部署在嵌入式设备上的,这么做太繁琐,个人更偏向于使用第三方库的源码在编译时与程序一起静态编译。

  2. 继续目前的配置,在代理 /反代 Github

    是个办法,但是感觉不是很好,有点野路子的感觉。而且 CI 在容器里不太好代理

  3. 将相关依赖的编译结果缓存

    这是目前个人觉得最靠谱的办法,而且 Travis CI 的文档也是建议将依赖安装的部分提取出来(make get-deps)。但是在 CMake 里好像不太好做到?而且依赖编译完也不是像 node 在一个独立的目录中,不太好设置缓存。


研究了一下午焦头烂额,只得向大家求救了!

(以前使用其他语言的时候,一句composer install或者npm install就解决了的事情,没想到在 C++这竟如此复杂,要是有类似的工具能统一起来多好)

5276 次点击
所在节点    C++
19 条回复
secondwtq
2020-02-09 23:25:46 +08:00
没用过 Travis CI,但是 CMake 没见过 FetchContent 用得多的。一般 CMake 是假设你系统(或者你的配置里)已经有了现成的依赖,然后再去 find。CMake 是个 build system,不是 package manager。

感觉楼主需要做的是把依赖从 CMake 的 build 中剥离出来单独 build,这样就可以满足“在一个独立的目录中”的条件了。

我倾向于避免把依赖都放在一起 build,因为我用这类工具的经验是如果用得频繁,它们(大多数)都会日常抽风,抽风的最简单解法就是删掉所有 cache 重来,这样我就必须最小化每次 full build 的时间。
secondwtq
2020-02-09 23:31:59 +08:00
当然这个并不是绝对的,比如 WebKit 这种项目分成 WTF、WebCore、JavaScriptCore、WebKit 等很多子项目,子项目之间依赖非常紧密(假设把这些项目单独分成几个 repo 的话,某开发者放假回来忘记 pull 其中一个项目,就可能 build 不过),一个 CMake 全都 add_subdirectory 进来就得了。

但是 WebKit 用了 sqlite,这个就用系统的就行。
Mithril
2020-02-10 00:46:33 +08:00
C++的依赖管理有几种。
vcpkg,conan。或者包进 nuget 里。我们自己用的一个魔改过的 Gradle。
其实改个 Gradle 插件是最简单的,或者 Conan 也可以。直接用 Artifactory 存编译好的二进制。你在编译的时候需要的依赖都会拉取对应版本到本地,解压链接进去。如果本地有的话那就直接用缓存。
自己控制好版本和 Arch 依赖关系就可以了,改个现有的系统的话,这些基本都有办法解决。
其他的不推荐,特别是你这种做法。到时候部署版本出了问题,你都没办法追踪依赖版本。而且根据环境不一样,有的时候你编译的二进制也不一定一样。这时候你再去追踪调试就更没谱了。
依赖最好是固定好的,对于 C++来说,不要去直接依赖代码。除非你是 boost 那样直接复制进去就能用的,不然全部都依赖二进制。然后这些二进制如果是开源的最好你要么直接依赖官方发布的二进制,要么全部自己编译。而且记录好编译环境参数等信息。有些项目在编译的时候会读入环境变量,这种你更没法控制发布版本了。
所有你依赖的库,全部都依赖其二进制。然后编译过程中通过依赖管理系统去下载这些二进制,让依赖管理系统去解决版本号,Arch,冲突等等问题。不要自己手动去解决。
你现在这种做法不出问题就罢了,真的你哪个依赖库有问题导致发布版本出错,查都没得查。
mrcn
2020-02-10 00:53:57 +08:00
@secondwtq 感谢回复!你说的应该是目前的主流操作。没这么做的最大的原因我没写出来,这个是学校实验室的项目,其他成员基本上没有 linux 的使用经验,甚至 cmake 是什么都不知道,最好是源码一拉在 IDE 里面点个运行就能跑起来。。目前只能是写个脚本完成这些操作了。
mrcn
2020-02-10 01:02:02 +08:00
@Mithril #3 感谢回复! Conan 可能是我想要的东西,之前没有听过,明天好好学习一下!

所以说 c++更流行让二进制跟着源码走吗?之前有想过我自己提前编译好静态的.a 文件,每个 arch 分开放好,跟 source 一起版本控制,后来觉得应该尽量用源码而不是二进制,就没有尝试了。
mrcn
2020-02-10 01:04:30 +08:00
@mrcn #5 补充,提前静态编译好第三方库的.a 文件,与项目的源码放在一起版本控制。
Mithril
2020-02-10 01:56:37 +08:00
@mrcn 不是
因为 C++不像 Java 或者 JavaScript 一类的直接使用源码(字节码也算直接使用源码了)它用的是针对平台特化的二进制。所以没办法像其它语言一样直接拉同样一份库就能用。你得针对不同平台甚至不同 STL 编译出一份来。也没有像 Maven 或者 NPM 一样的东西,不然同一份代码你得编译几十上百份二进制存进去。
我了解到的,主流的都是在你公司内部部署一个包管理系统,因为特定某个产品支持的平台一般都是有限的,所以你可以只放有限的几份上去。
你说的这个并不是主流做法,版本控制系统设计来是针对纯文本的,它的 diff 并没办法很好的处理二进制。你也不应该把二进制提交到代码库里。
正常做法是,你开发了一个版本的代码,CI 编译成针对某个或者某几个平台的二进制并生成版本号,然后 CI 把这些二进制传送到二进制管理系统里。
使用这些东西的项目,会在编译期由 Build Automation 系统去二进制管理系统里面查询对应版本号(你可以把架构直接写到版本号或者 product id 里)的二进制,并下载回来解决依赖。
至于你说的代码版本和二进制版本的对应关系,CI 系统会有记录。而且很多时候你也会把这个代码版本记录到二进制的包里,或者直接打到文件属性上。
不要把二进制放到代码库里还有个原因,就是你只放二进制的话没有编译环境记录的。CI 系统会记录每次编译使用的环境变量,编译参数,而且还有 log 可以查。而且你会把 pdb 一类的东西也放到二进制管理系统里。你在代码库里提交一个二进制,甚至都不一定能和代码对应的上。到时候出了问题查起来就是灾难了。
GeruzoniAnsasu
2020-02-10 03:05:37 +08:00
travis 拉 github 的依赖又不会很慢。。

如果你用本地的 CI,那建议 repo 也放本地,然后把所有依赖的库源码都在本地镜像一份

不要放预编译好的 binary,除非这个 binary 是 CI 的其它 pipeline 生成的,那可以通过 API 把其它 pipeline build 好的 artifacts 拉回来,否则 CI 还是尽量保证从头 full build
waruqi
2020-02-10 08:02:18 +08:00
可以试试 xmake 原生支持各种 c++依赖包管理 https://xmake.io
cuminflea
2020-02-10 08:28:00 +08:00
推荐一个 cmake 的 module,叫 CPM: https://github.com/TheLartians/CPM.cmake,
自己改了改加了点小功能用起来很舒服,可以控制依赖项版本,CI 上只要有新版 cmake 和 git 就能用,如果依赖已经通过其他 package manager 安装会优先调用 system wide 的包。
我已经把这玩意作为写 cpp 新项目的标配了
cuminflea
2020-02-10 08:31:08 +08:00
@cuminflea 另外这玩意其实就是个 fetchContent 加个壳和一些自动化。。。
tianshilei1992
2020-02-10 08:38:47 +08:00
一般 CI 都是通过所谓的控制变量的方式来检查,也就是说,变量在一次运行中只会有一个。拿你的问题来说,依赖讲道理在代码有变化的情况下应该是不变的。那基于这样的假设的话,第一种方式实际上比较靠谱。然后每隔一段时间,手动或者自动的更新一下依赖,并且顺带测试一把这些依赖和你们项目的兼容性。至于你说最终部署的,CI 可以和最终的部署有两套环境,只需要在每次 release 的时候测一把就好了。
mxalbert1996
2020-02-10 09:24:22 +08:00
选项 1、3 都可以用 pkg-config 啊,1 的话直接安装到系统里,3 的话设置一个 prefix 安装到指定路径然后缓存就好,而且提前编译不是一定要编译成动态链接库,静态库 .a 文件了解一下。
momocraft
2020-02-10 09:52:07 +08:00
ci 服务器放国外会不会省事点
owt5008137
2020-02-10 11:43:35 +08:00
我就是用的 3 这种。无非是加一个自定义 target 然后调用脚本。
https://github.com/atframework/prebuilt-build-tools
我还特意另写了个工程专门用来搞 prebuilt 的
secondwtq
2020-02-13 03:23:52 +08:00
看到楼主的 append,来联动一下隔壁 https://v2ex.com/t/643161

我小时候家里订了 2004 年到 07 年的电脑爱好者。当时这个杂志有个论坛,人还不算少,我没事就上去水水(跟 V 站似的),里面有个编程区,没事讨论些 Win32 编程之类的东西。现在看起来平均年龄和 V 站也差不多。
我当时只会 VB6,于是只能喊 666。有次问了个也不知道什么问题(好像是类似“怎么学编程”之类的),被人问了“你不是科班出身的?”,我当时还不知道“科班”是啥意思 ...

楼主链的这个博主 KingsamChen,当时好像是这个区的版主。
后来过了几年,论坛这个东西彻底过气了,再去看原来的站已经没了。没想到人还能找到。
wutiantong
2020-02-13 11:34:11 +08:00
@secondwtq 那你现在大可以去跟他交个朋友啦
wutiantong
2020-02-13 11:35:41 +08:00
@mrcn cmake 3.14 后有了 FetchContent_MakeAvailable 那才叫一个爽。
mrcn
2020-02-13 13:28:26 +08:00
@wutiantong 现在用 @cuminflea 推荐的 CPM,也很爽,嘿嘿。只是 CMake 最低要求又从 3.11 刷到 3.14 了,都快顶到天花板了

@secondwtq 那个博主是个大牛,博客里有很多有价值的东西

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/643264

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX