管理C++的第三方库以及编译
第三方库这个说法,不知道出自哪里,但一般是指开发者,系统/平台提供商之外的第三个参与者提供的程序库。
大多数开源软件库在软件系统中都是第三方库。
完全不使用库的开发,在90年代就已经被放弃了,特别MFC/OWL/QT这些先行者。开源运动的兴起使得第三方库
成为主力使用库。
C++领域有一些非常特殊的库,比如早期的STLport和当前的Boost,它们就像是语言的事实标准,基本在每个程序
中都可以见到他们的身影。
但第三方库也会导致相当多的问题,主要总是包括:
- 版本不一致。同一个软件系统中,如果引用了同一个第三方库的两个不同版本,那一定会暗生问题。
- 编译选项不一致。一般为了减少编译时间,第三方库都会以编译后的.a/.lib形式参与软件编译,第三方库的编译选项与软件系统的编译选项不同,也是会有一些潜在的问题。特别是x86/x64,ansi/utf-8这些选项不同,根本就不能用。
- 版本管理库变大。在一些大项目中常常会有几个G的版本库,每次clone代价很大。其实很多都是第三方库,不同版本,不同编译选项生成的库引入。
除了把编译的库引入项目,也有人以源代码的形式引入开源软件,比如GCC、SDL、WxWights、QT。如果第三方库本身编译不复杂,原代码也很简单,这么做比较好。
但是像Boost.Thread这样的库,就不行了(它需要编译成动态库,静态引用会有问题)。
另一方面,如果第三方库升级,就是一个比较复杂的工程,如果不升级,又只能看着第三方库的问题得不到解决。
在Linux上这个问题并不严重,系统级的软件管理工具可以代管大多数的第三方。比如debian系的,可以使用apt得到大多数的开源库,同时如果需要最新版本,也可以通过第三方源来取得。
在交叉编译和window平台上这个问题就非常头痛了。我们需要从几个层次来解决这个问题。
- 需要有中心化的第三方源代码获取平台,这个平台需要支持按用户/组织+第三方库+版本的形式取得源代码,同时还需要保证及时跟踪来源。这个类似于bintray/github都可以。公司内部可以使用gitlab来搭建。
- 需要有一个构建脚本平台,存放在不同工具链和平台的情况下,这个脚本可以从源代码中心的源代码,把源代码编译成库。同样可以用github这类工具搭建和管理。
- 在项目中提供一个配置文件,需要的开源库(只需指定编译脚本,脚本是针对开源库)。这个只需要一个文本文件即可。
- 在开发者的机器和编译服务器上,下载编译脚本,生成库。源代码、最终结果可以缓存在本机上。可以使用项目原本的构建工具。
如果只考虑windows+vc这个工具链,最简单的工具是使用msbuild,即为第三方库构建工程,编译结果生成成为vcprops文件,由项目引用。在项目工程中,可以通过prebuild过程加上下载、编译、安装第三方库的过程,因为有缓存,每台机器上应该只需要做一次。
考虑Boost这个最常用的库,并没有一个msbuild可以调用工程,这个方案通用性很差。但对于只使用vc的公司还是非常实用。因为只需要一个内源的源代码管理工具就可以了,没有git甚至可以用svn。
如果有多平台交叉编译,要考虑先统一项目自己的编译工具,使用CMake/Scons/Gyp这些跨平台编译工具,否则需要在不同的编译工具下都做一套。
以CMake为例,最接近这个系统的是CPM这个工具,它用github做前两个服务器,CMake的脚本来完成下载,编译。但受CMake脚本的能力限制,它无法完成更复杂的版本管理工作,只能做为一个试验原型。
使用CPM,可以先把自己的项目改成使用CMake编译,在CMakeLists.txt中引入CPM语句,再引入需要的第三方库。如果第三方库已经在有一个CPM脚本工程,就很简单直接引用即可。但如果没有,就需要自己先建立CPM脚本工程。即一个CMakeLists.txt,可以下载编译第三方库,再提供CPM一些专用宏,以便后续项目能找到这个第三方库。
一个内源第三方库(指公司内共享库)需要两个git工程(或svn目录),一个提供原始代码和维护工作,另一个提供构建脚本,这个脚本甚至可以是专用于某个项目。最后需一个工具能下载,脚本调用,缓存管理这些工作。
基于C++社区不自己发明工具传统,以及cmake/git的流行程度,我考虑使用bash来完成这个部分。基本数据结构设计如下:
CPM_HOME 做为根目录
sources 源代码下载
hosts 下载服务器
groups 用户或组织
library-name 库
scripts
hosts 下载服务器
groups 用户或组织
library-name 库, 源代码构建完成后会安装到这个目录下
bin 二进制结果,包括可执行文件
include 头文件,一般应支持多配置
lib/xx/ 应对不同的配置,参考boost library
flags/xx/ 应对不同的配置,需导出一些编译选项
build
构建目录,对于不支持外部构建目录的开源软件,可以选择复制过来in-source构建
考虑并发构建,应在这里文件锁定操作。
除了锁定目录外,其它目录可以删除以减少空间占用。
repos
project-id 成生特定于项目第三方库包,这样项目只需要引入一个第三方目录。
以上目录都会在本地保存一个hash文件以保持完整和不变性。
类似python我们使用 requirements.txt 项目的需求第三方库,使用cpp_setup requirements.txt来完成预建编译环境。
requirements使用
[host:port/]username/cpm-library-name
全局配置中,可以默认的host:port。这里可以不写host:port。
这里是指向特定的构建脚本,脚本能支持哪些平台,哪些发布版本,哪些运行时库都由脚本自己决定。缺少项目需要的配置时,只会编译失败。
如果项目支持CMake,那么只需要引入CPM即可。
如果项目可以要求生成一个project-id目录来聚合第三方库,方便make/vc++这些工具集成。