使用 CMake 的 C++交叉编译项目管理第三方库依赖的最佳实践? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
mrcn
V2EX    C++

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

  •  
  •   mrcn 2020-02-09 18:57:23 +08:00 6030 次点击
    这是一个创建于 2148 天前的主题,其中的信息可能已经有所发展或是发生改变。

    长话短说:项目现在使用 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++这竟如此复杂,要是有类似的工具能统一起来多好)

    第 1 条附言    2020-02-11 21:30:24 +08:00

    感谢各位的回复。

    考虑到维护多个ABI下多个依赖的繁琐,目前暂时还是使用的 CMake 的 FetchContent 来管理依赖。

    另外FetchContent下载的依赖目录是可以定义的,所以是可以移到一起缓存的……

    一大波操作搞完,发现与这个博客的内容出奇的一致……

    C++ 工程依赖管理新方向:CMake & Git kingsamchen.github.io/2019/02/10/use-cmake-and-git-as-your-cpp-dependency-manager

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

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

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

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

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

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

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

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

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

    @secondwtq 那个博主是个大牛,博客里有很多有价值的东西
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2597 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 06:19 PVG 14:19 LAX 22:19 JFK 01:19
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86