• CMake库搜索函数居然不搜索LD_LIBRARY_PATH


    🚀 优质资源分享 🚀

    学习路线指引(点击解锁)知识定位人群定位
    🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
    💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

    摘要: 本文通过编译后运行找不到库文件的问题引入,首先分析了find_package(JNI)的工作流程,而后针对cmake不搜索LD_LIBRARY_PATH的问题,提出了一种通用的解决办法。

    本文分享自华为云社区《CMake库搜索函数居然不搜索LD_LIBRARY_PATH? 由编译工具使用体验而引发的思考》,作者: 蜉蝣与海 。

    最近产品要使用JNI技术,CMake编译C++代码时需要对外链接libjvm.so库。代码编译倒是正常,系统中也有libjvm.so, 然而使用时却报了如下异常:

    error while loading shared libraries: libjvm.so: cannot open shared object file: No such file or directory
    
    • 1

    这个报错表示,操作系统并没有找到libjvm.so, 我们的操作系统是从LD_LIBRARY_PATH中搜索这些动态链接库,很显然目前libjvm.so并不在这个目录下。

    问题的解决倒是简单,直接在LD_LIBRARY_PATH里加入libjvm.so的库即可。但是这却引发了我的思考:

    为什么构建时可以找到libjvm.so, 运行时却找不到呢?

    这个问题的回答,既可以有简明扼要版解释,又可以刨根问底深挖。

    先来看简明扼要版解释:

    代码的CMakeList中使用了下列语句,在编译过程中寻找并链接libjvm.so,这个搜索方式和操作系统的搜索方式不同:

    find\_package(JNI)
    get\_filename\_component(JVM\_LIB\_PATH ${JAVA\_JVM\_LIBRARY} DIRECTORY)
    get\_filename\_component(JAVA\_LIB\_PATH ${JVM\_LIB\_PATH} DIRECTORY)
    link\_directories(${JVM\_LIB\_PATH} ${JAVA\_LIB\_PATH})
    set\_target\_properties(${NAME} PROPERTIES LINK\_FLAGS "-ljvm")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中find_package(JNI)会搜索libjvm.so可能存在的路径,通过get_filename_component来获得libjvm.so的文件夹,并把这个文件夹设为默认搜索库路径。而后set_target_properties会进行链接工作。

    这个答案只能告诉我们“是什么”,但是作为一只程序猿,还要了解“为什么”,这里引申几个问题讨论:

    1. find_package(JNI)的工作过程是怎样的?为什么LD_LIBRARY_PATH里没找到的依赖库,cmake可以找到
    2. cmake的库搜索函数find_library会搜索LD_LIBRARY_PATH吗,如果不会,可以通过设置来搜索LD_LIBRARY_PATH吗?

    问题一:find_package(JNI)的工作过程是怎样的

    为了方便开发者引用外部包,cmake官方预定义了许多寻找依赖包的Module, 他们存储在cmake的/share/-cmake-/Modules目录下。每个以Find.cmake命名的文件都可以帮我们找到一个包[1]。在本地计算机执行以下指令,即可找到find_package(JNI)使用的脚本文件。

    find / -name FindJNI.cmake
    
    • 1

    打开自己的cmake对应的FindJNI文件,可以看到密密麻麻的注释和脚本,通过阅读这些脚本,我们得以得知FindJNI是如何工作的。

    分析问题前,先看问题带来的结果,文件最上方注释有如下说明:

    This module sets the following result variables:
    ``JNI\_INCLUDE\_DIRS``
     the include dirs to use
    ``JNI\_LIBRARIES``
     the libraries to use (JAWT and JVM)
    ``JNI\_FOUND``
     TRUE if JNI headers and libraries were found.
    Cache Variables
    ^^^^^^^^^^^^^^^
    The following cache variables are also available to set or use:
    ``JAVA\_AWT\_LIBRARY``
     the path to the Java AWT Native Interface (JAWT) library
    ``JAVA\_JVM\_LIBRARY``
     the path to the Java Virtual Machine (JVM) library
    ``JAVA\_INCLUDE\_PATH``
     the include path to jni.h
    ``JAVA\_INCLUDE\_PATH2``
     the include path to jni\_md.h and jniport.h
    ``JAVA\_AWT\_INCLUDE\_PATH``
     the include path to jawt.h
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这段代码表明,执行find_package(JNI)之后,会有一系列变量被设置,其中包括表示JNI是否被找到的变量JNI_FOUND,以及表示libjvm.so的变量JAVA_JVM_LIBRARY。这些变量在设定之后,通过FindPackageHandleStandardArgs导出,返回调用处,FindPackageHandleStandardArgs是cmake专门用来导出变量的宏[2]:

    include(${CMAKE\_CURRENT\_LIST\_DIR}/FindPackageHandleStandardArgs.cmake)
    FIND\_PACKAGE\_HANDLE\_STANDARD\_ARGS(JNI DEFAULT\_MSG JAVA\_AWT\_LIBRARY
     JAVA\_JVM\_LIBRARY
     JAVA\_INCLUDE\_PATH
     JAVA\_INCLUDE\_PATH2
     JAVA\_AWT\_INCLUDE\_PATH)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在文件中定位JAVA_JVM_LIBRARY, 可以追踪到下述代码片段:

    foreach(search ${\_JNI\_SEARCHES})
     find\_library(JAVA\_JVM\_LIBRARY ${\_JNI\_${search}\_JVM})
     find\_library(JAVA\_AWT\_LIBRARY ${\_JNI\_${search}\_JAWT})
     if(JAVA\_JVM\_LIBRARY)
     break()
     endif()
    endforeach()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    由此可知,JAVA_JVM_LIBRARY这个变量,是通过逐个搜索KaTeX parse error: Expected '}', got 'EOF' at end of input: {\_JNI\_{search}_JVM}里的文件夹进而确定JAVA_JVM_LIBRARY的。而KaTeX parse error: Expected '}', got 'EOF' at end of input: {\_JNI\_{search}_JVM}相关的定义语句如图:

    set(\_JNI\_FRAMEWORK\_JVM NAMES JavaVM)
    set(\_JNI\_NORMAL\_JVM
     NAMES jvm
     PATHS ${JAVA\_JVM\_LIBRARY\_DIRECTORIES}
     )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中JAVA_JVM_LIBRARY_DIRECTORIES中涉及了大量可能的libjvm.so存在的路径。

    set(JAVA\_JVM\_LIBRARY\_DIRECTORIES)
    foreach(dir ${JAVA\_AWT\_LIBRARY\_DIRECTORIES})
     list(APPEND JAVA\_JVM\_LIBRARY\_DIRECTORIES
     "${dir}"
     "${dir}/client"
     "${dir}/server"
     # IBM SDK, Java Technology Edition, specific paths
     "${dir}/j9vm"
     "${dir}/default"
     )
    endforeach()
    set(JAVA\_AWT\_LIBRARY\_DIRECTORIES)
    if(\_JAVA\_HOME)
     JAVA\_APPEND\_LIBRARY\_DIRECTORIES(JAVA\_AWT\_LIBRARY\_DIRECTORIES
     ${\_JAVA\_HOME}/jre/lib/{libarch}
     ${\_JAVA\_HOME}/jre/lib
     ${\_JAVA\_HOME}/lib/{libarch}
     ${\_JAVA\_HOME}/lib
     ${\_JAVA\_HOME}
     )
    endif()
    JAVA\_APPEND\_LIBRARY\_DIRECTORIES(JAVA\_AWT\_LIBRARY\_DIRECTORIES
     ${\_JNI\_JAVA\_AWT\_LIBRARY\_TRIES}
     )
    foreach(\_java\_dir IN LISTS \_JNI\_JAVA\_DIRECTORIES\_BASE)
     list(APPEND \_JNI\_JAVA\_AWT\_LIBRARY\_TRIES
     ${\_java\_dir}/jre/lib/{libarch}
     ${\_java\_dir}/jre/lib
     ${\_java\_dir}/lib/{libarch}
     ${\_java\_dir}/lib
     ${\_java\_dir}
     )
     list(APPEND \_JNI\_JAVA\_INCLUDE\_TRIES
     ${\_java\_dir}/include
     )
    endforeach()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    如上图所示,变量依赖顺序如下:

    JAVA_JVM_LIBRARY_DIRECTORIES => JAVA_AWT_LIBRARY_DIRECTORIES => _JNI_JAVA_AWT_LIBRARY_TRIES & _JAVA_HOME => _JNI_JAVA_DIRECTORIES_BASE

    最终发现JAVA_JVM_LIBRARY_DIRECTORIES变量的值,是由JAVA_HOME变量的值和_JNI_JAVA_DIRECTORIES_BASE变量的值共同决定的。而JNI_JAVA_DIRECTORY_BASE预置了大量预定义路径:

    set(\_JNI\_JAVA\_DIRECTORIES\_BASE
     /usr/lib/jvm/java
     /usr/lib/java
     /usr/lib/jvm
     /usr/local/lib/java
     /usr/local/share/java
     /usr/lib/j2sdk1.4-sun
     /usr/lib/j2sdk1.5-sun
     /opt/sun-jdk-1.5.0.04
      /usr/lib/jvm/java-6-sun
     /usr/lib/jvm/java-1.5.0-sun
     /usr/lib/jvm/java-6-sun-1.6.0.00       # can this one be removed according to #8821 ? Alex
     /usr/lib/jvm/java-6-openjdk
     /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0 # fedora
     # Debian specific paths for default JVM
     /usr/lib/jvm/default-java
     # Arch Linux specific paths for default JVM
     /usr/lib/jvm/default
     # Ubuntu specific paths for default JVM
     /usr/lib/jvm/java-11-openjdk-{libarch}    # Ubuntu 18.04 LTS
     /usr/lib/jvm/java-8-openjdk-{libarch}    # Ubuntu 15.10
      /usr/lib/jvm/java-7-openjdk-{libarch}    # Ubuntu 15.10
      /usr/lib/jvm/java-6-openjdk-{libarch}    # Ubuntu 15.10
     # OpenBSD specific paths for default JVM
     /usr/local/jdk-1.7.0
      /usr/local/jre-1.7.0
      /usr/local/jdk-1.6.0
      /usr/local/jre-1.6.0
     # SuSE specific paths for default JVM
     /usr/lib64/jvm/java
     /usr/lib64/jvm/jre
     )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    通过以上分析可以看出,JAVA_JVM_LIBRARY的搜索,依赖JAVA_HOME和大量预定义路径。

    问题二:cmake库搜索函数find_library会搜索LD_LIBRARY_PATH吗

    通过阅读Does CMake’s find_library search LD_LIBRARY_PATH可以知道,find_library默认不搜索LD_LIBRARY_PATH, 并且网上也找不到让cmake搜索LD_LIBRARY_PATH的文章。

    那cmake能搜索LD_LIBRARY_PATH吗?

    答案是可以的,通过cmake获取LD_LIBRARY_PATH环境变量,并转为cmake可理解的list格式,而后注入find_library即可,代码如下:

    string(REPLACE ":" ";" RUNTIME\_PATH "$ENV{LD\_LIBRARY\_PATH}")
    find\_library(JVM\_API NAMES jvm HINTS ${RUNTIME\_PATH})
    if (JVM\_API STREQUAL "JVM\_API-NOTFOUND")
     message(WARNING "found libjvm.so only in ${JAVA\_JVM\_LIBRARY} but not in LD\_LIBRARY\_PATH. environment variable LD\_LIBRARY\_PATH must include its' directory.")
    endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果希望找不到这个库时编译失败,可以将WARNING改为fatal_error, 代码如下:

    string(REPLACE ":" ";" RUNTIME\_PATH "$ENV{LD\_LIBRARY\_PATH}")
    find\_library(JVM\_API NAMES jvm HINTS ${RUNTIME\_PATH})
    if (JVM\_API STREQUAL "JVM\_API-NOTFOUND")
     message(FATAL\_ERROR "found libjvm.so only in ${JAVA\_JVM\_LIBRARY} but not in LD\_LIBRARY\_PATH. environment variable LD\_LIBRARY\_PATH must include its' directory.")
    endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    小结

    本文通过编译后运行找不到库文件的问题引入,首先分析了find_package(JNI)的工作流程,而后针对cmake不搜索LD_LIBRARY_PATH的问题,提出了一种通用的解决办法。

    参考文献:

    [1] Cmake之深入理解find_package()的用法:https://zhuanlan.zhihu.com/p/97369704?utm_source=wechat_session

    [2] Cmake中find_package命令的搜索模式之模块模式(Module mode):https://www.jianshu.com/p/f983a90bcf91

    [3]Does CMake’s find_library search LD_LIBRARY_PATH?:https://stackoverflow.com/questions/41566316/does-cmakes-find-library-search-ld-library-path

    点击关注,第一时间了解华为云新鲜技术~

  • 相关阅读:
    C语言调用【Python3】
    MySQL删库不跑路
    也太全了吧,分享16种机器学习类别特征处理方法
    idea leetcode配置
    什么?又来看驱动的过程了?是滴,必需滴--I2C设备的注册过程(小白篇)
    Linux日志管理-logrotate(crontab定时任务、Ceph日志转储)
    百分点科技连续四年登榜“中关村高成长企业TOP100”
    程序员是一个需要天赋的职业吗?
    深度之眼Paper带读笔记GNN.08.GCN(下)
    Tomcat WebSokcet 拒绝服务(CVE-2020-13935)
  • 原文地址:https://blog.csdn.net/qq_43479892/article/details/126070704