R 语言中的 libpng 版本冲突

事情起因是在 R 语言中调用 png 包的 readPNG 读取 png 图片的时候,碰到了错误:libpng error: Incompatible libpng version in application and library

> library(png)
> p1 = readPNG("/bioinfo/polyA/circos.png")
Error in readPNG("/bioinfo/polyA/circos.png") :
  libpng error: Incompatible libpng version in application and library
In addition: Warning messages:
1: In readPNG("/bioinfo/polyA/circos.png") :
  libpng warning: Application was compiled with png.h from libpng-1.6.37
2: In readPNG("/bioinfo/polyA/circos.png") :
  libpng warning: Application  is  running with png.c from libpng-1.2.49
> 

这个问题,其实在 《RamiGO 安装及库依赖解决备忘》曾经遇到过,当时的解决方法,在现在看起来其实是并不完善的。所以,在问题解决前,我们先来看看这个问题到底是怎么导致的。

首先,我的 R 是通过源码编译安装的,而且在安装的过程中 configure 时候指定了自己安装的 libpng-1.6.37。

./configure --prefix=bioinfo/software/r/r-4.4.2 --enable-R-shlib --with-libtiff --with-libpng --with-jpeglib --with-pcre1 \
LDFLAGS="-L/bioinfo/software/mylibs/zlib-1.2.6/lib \
            -L/bioinfo/software/mylibs/bzip2-1.0.6/lib \
            -L/bioinfo/software/mylibs/pcre-8.40/lib \
            -L/bioinfo/software/mylibs/tiff-4.0.9/lib \
            -L/bioinfo/software/mylibs/jpeg-9c/lib \
            -L/bioinfo/software/mylibs/libpng-1.6.37/lib \
            -L/bioinfo/software/mylibs/xz-5.2.3/lib \
            -L/bioinfo/software/mylibs/curl-7.64.1/lib -fopenmp" \
CPPFLAGS="-I/bioinfo/software/mylibs/zlib-1.2.6/include \
            -I/bioinfo/software/mylibs/bzip2-1.0.6/include \
            -I/bioinfo/software/mylibs/pcre-8.40/include \
            -I/bioinfo/software/mylibs/tiff-4.0.9/include \
            -I/bioinfo/software/mylibs/jpeg-9c/include \
            -I/bioinfo/software/mylibs/libpng-1.6.37/include \
            -I/bioinfo/software/mylibs/xz-5.2.3/include \
            -I/bioinfo/software/mylibs/curl-7.64.1/include"

第二,我使用的 png 是在 R 里面直接 install.package('png') 安装的。

$ ldd /bioinfo/software/r/r-4.4.2/lib64/R/library/png/libs/png.so
        linux-vdso.so.1 =>  (0x00007fff29dd9000)
        libpng12.so.0 => /usr/lib64/libpng12.so.0 (0x00007f1a614bc000)
        libz.so.1 => /bioinfo/software/r-3.3.2/lib/libz.so.1 (0x00007f1a612a4000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f1a61020000)
        libR.so => /bioinfo/software/r/r-3.6.1/lib64/R/lib/libR.so (0x00007f1a6098e000)
        libgomp.so.1 => bioinfo/software/gcc-7.3.0/lib64/libgomp.so.1 (0x00007f1a60760000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1a60543000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f1a601af000)
        libRblas.so => /bioinfo/software/r/r-3.6.1/lib64/R/lib/libRblas.so (0x00007f1a5ff83000)
        libgfortran.so.3 => /usr/lib64/libgfortran.so.3 (0x00007f1a5fc91000)
        libreadline.so.6 => /lib64/libreadline.so.6 (0x00007f1a5fa4e000)
        libpcre.so.1 => /bioinfo/software/r-3.3.2/lib/libpcre.so.1 (0x00007f1a5f81b000)
        liblzma.so.5 => /bioinfo/software/r-3.3.2/lib/liblzma.so.5 (0x00007f1a5f5f6000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f1a5f3ee000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f1a5f1e9000)
        libiconv.so.2 => /usr/local/lib/libiconv.so.2 (0x00007f1a5ef04000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003636a00000)
        libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007f1a5ece2000)

ldd 的结果可以很明显看到,install.package('png') 使用了系统默认的 libpng,也就是对应的 libpng-1.2.49。因此最后在调用 png 的时候就和编译使用的 libpng-1.6.37 发生了冲突。

第三,所以最终的解决方法就是,先设置 libpng-1.6.37PATHLD_LIBRARY_PATH

export PATH=/bioinfo/software/mylib/libpng-1.6.37/bin:$PATH
export LD_LIBRARY_PATH=/bioinfo/software/mylib/libpng-1.6.37/lib:$LD_LIBRARY_PATH

卸载原来的 png 包,再重新安装。

remove.packages('png')
options("repos"=c(CRAN="https://mirrors.tuna.tsinghua.edu.cn/CRAN/"))
install.packages('png')

总的来说,最开始碰到这个问题的时候,虽然知道问题在哪,也设置过 LD_LIBRARY_PATH 后去重装 png,但是一直都没成功,后面尝试了几个方法。

一是,怎么在 install.packages() 中指定 libpng-1.6.37 的 LDFLAGSCPPFLAGS,因为没搞懂 --configure-args 具体是怎么用的,这条路没走通。另外,在 HOME/.R/Makevars 设置了 LDFLAGSCPPFLAGS 进行了尝试,也依然不起作用。

二是,想通过 patchelf 去直接修改 png.so 的动态库链接,虽然修改成功,但直接把 png 搞崩溃。

直至把 libpng-1.6.37 的 bin 也一起增加到 PATH,才重装 png 后,这个问题才最终解决。个人猜测在安装 png 时检测 libpng 的过程中应该跟 libpng-config 的可执行文件有关系,因为系统默认的 /usr/bin/libpng-config 为 1.2.49 版本,而 libpng-1.6.37 的 bin/libpng-config 默认软连接至 bin/libpng16-config,对应 1.6.37。

事后,去 png源码仓库一看,果然在 src/Makevars 中发现了链接库和依赖中调用了 libpng-config

PKG_LIBS=$(PNG_LIBS) `libpng-config --static --ldflags`
PKG_CFLAGS=$(PNG_CFLAGS) `libpng-config --cflags`
png-src-makevars

而系统的 libpng-config 和 libpng-1.6.37 的 bin/libpng-config 的链接库和依赖:

$ /usr/bin/libpng-config --static --ldflags
-L/usr/lib64 -lpng12 -lz -lm

$ /bioinfo/software/mylib/libpng-1.6.37/bin/libpng-config --static --ldflags
-L/bioinfo/software/mylib/libpng-1.6.37/lib -lpng16 -lm -lz -lm

正好印证了在安装 png 时,的确需要从默认 PATH 中调用 libpng-config,从而获取对应的链接库和依赖。