一、问题现象

从GCC4切换到GCC8,重新编译magick库,在编译过程中却出现依赖库不合要求的问题、比如下面的libpng库突然check不通过,编译出来的magick里没有png库,咋一看提示似乎是版本库不兼容了,之前可以work的编译脚本“突然”失灵了。but my god!这是在源码编译,而且libpng库源码是1.6,理论上不应该出现这种问题,最多出现语法不兼容这类问题。
GCC/G++/Make/configure 一个诡异的编译、链接、依赖库问题(magick库)

二、原因追查

在一脸懵逼中,打开主编译的makefile文件一看究竟,也没发现有什么问题,由于有一段时间没有搞C代码的编译了,之前对于makefile文件残留的主要是印象,只好花时间再次收集make相关的资料重温一遍,刚开始认为主makefile没有什么明显的问题,这么个认知埋下了折腾的种子。
(1)接着以png库编译问题为线索,开始追查,先是对png库源代码库进行check,版本ok,手动编译也ok,见鬼了,难道真的是GCC8抽风了,有点认知“颠覆”的错觉,只好clean编译产出,重新编译主makefile试了几把,发现还是不行!
(2)费解中,觉得还是扎扎实实找找日志看看细节,先重定向收集主makefile编译的日志,有点费劲,遂放弃。直入主题,既然是magick不能编入png库,那就看看为啥不能编入,总要有个错误原因,毕竟源码编译且png库版本合规,这不科学!于是就去看了magick库的编译日志,找到了magick库下的configure的、执行日志config.log,搜索关键词“png”,好家伙,逐一核对,找到了“checking for libpng>=1.0.0… no ”,一扫附近的编译上文,发现是GCC在check libpng相关的导出函数时挂掉了,出现了“undefined reference to xxx 函数”,原来是check libpng库挂了,原来是找不到libpng的依赖库挂掉了,结果给了这么个错误提示,这么个错误提示也能说的过去,就是误导性太强,不太仗义。。。至此,问题明确了,magick库编译时检查依赖的libpng库时,发现缺少libpng的依赖库(zlib)、导致zlib相关的符号表不能链接。在这里不得不说,GCC的checking是真刀真枪的干,哦,不对,准确的说应该是magick库的configure脚本,别跟哥(magick/configure)玩虚的,既然configure选项里有–without-png,那么哥就知道该怎么check你这个libpng库是否ok,于是magick/configure为libpng量身定做、搞了一段main代码在里面直接调用libpng中一些核心导出函数、除了这其它啥也不干,configure执行时会对这段代码进行完整的编译、链接,编译不要紧,链接时就挂了,因为在执行configure时没有显式指定链接linpng的依赖库,于是就找不到zlib库导致那段check libpng的代码不能成功链接,于是libpng就被magick拒绝签收,出现了上面的提示,反正都不是合格品,废话不多说,直接说没找到想要的合格品版本得了。。。,够坑。。。

三、问题修复

既然问题明确了,即magick/configure在check libpng时找不到libpng依赖的zlib库中所导出的函数,那就在magick/configure执行时配置zlib库的路径即可,解决办法很简单吧!
由于每个源码的configure玩法可能还不太一样,所以要具体看下configure选项具体是怎么玩的,比如对magick库的是这样玩的:GCC/G++/Make/configure 一个诡异的编译、链接、依赖库问题(magick库)
结合magick/configure的玩法,调整configure的编译行为,主要是通过LDFLAGS/"-L"选项增加自定义的lib库搜索目录,通过LIBS/"-l"选项增加自定义的链接库,从而找到libpng依赖的zlib库(magick库的二级依赖),就这样,magick最终生成的二进制bin和导出的lib库中均包含了libpng导出的函数,搞定~
GCC/G++/Make/configure 一个诡异的编译、链接、依赖库问题(magick库)

四、深入思考

(1)依赖问题:编译过程中遇到的依赖库问题可以概括为三大类,一是找不到库,二是找错库,三是多版本冲突。各类问题的表现形式多种多样,有些也可能很奇特,例如本例,实质是第一类问题即找不到库,只是隐藏的比较深,是二级依赖库找不到,这不像一级依赖库找不到时编译器可以直接清晰明白的给出提示。第二类找错库,这种一般也隐藏的比较深,如果编译时没有给出某个依赖库的路径,编译器会按照默认的搜索规则去找相应的库,然后找到后默默的进行链接,如果链接成功,则不会给什么提示,但运行时可能就会挂掉或者出现很奇特的其它链接错误,比如其它模块可能依赖了这个库某个版本里的函数,而这个版本却没有,导致一系列连锁链接错误反应。第三类多版本冲突,实际上是由于多个一级依赖库可能同时链接了某个二级依赖库的不同版本,但是版本控制做的不好,导致链接时符号冲突,如果做成GLIBC那样也OK。还有一种情形是,比如GCC8版本的多个一级依赖库分别链接了GCC8和GCC4编译后的某个二级依赖库,这种差异是由不通GCC版本编译造成的冲突。
(2)问题的解:依赖库的问题,虽有三类,但不能头疼医头,脚疼医脚,问题的本质是对依赖的管理问题。首先是要避免编译器默认的不合常理的搜索行为,默认的搜索范围要确定一定是在预期范围之内,比如GCC8编译时只能去找GCC8编译器下的lib库,禁止去GCC4下面的库中搜索,找不到应该报错,把这些“隐式“解决的问题”显式“处理,让大活人去处理。其次,主模块在编译时,把所有二级依赖或者N级依赖的路径均配置到主模块的依赖路径中,这样可以解决主模块在编译过程奇奇怪怪的各种找不到或隐式的链接失败问题,同时也会引入依赖打平的问题即多版本冲突,同一个库的多个版本均被不同库引入了,究竟该用哪个合适,这是个苦力活,排查起来很费劲,因为依赖很可能是交叉的,搞定了A的依赖,B的可能坏了。实际不要太单心,定个规则,一级依赖之间向谁看齐,二级依赖向谁看齐,把依赖打平,直到成功编译链接,然后看最终产出线上跑起来是否ok即可。如果有问题,那就再解,免不了苦哈哈的把依赖关系拓扑图搞出来,枚举的方式解决。
(3)依赖管理的复杂度:理论上任意一个主模块的依赖关系都可以表述成一张依赖拓扑关系图,从这个角度来说,这个问题是可解的而且是明确的。但随着时间的推移,这张图之间的关系会越来越复杂,越来越多新依赖的加入,越来越多老依赖新版本的加入,会使得这张网越来越大,越来越密,管理的复杂度以N^2的速度在持续增加,很快超出人类的理解和控制,除非上帝现身操纵一切,显然这是很扯的。。。。
~……~
以上

相关文章: