写点什么

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

  • 2022 年 7 月 30 日
  • 本文字数:3985 字

    阅读完需:约 13 分钟

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
复制代码


这个报错表示,操作系统并没有找到 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")
复制代码


其中 find_package(JNI)会搜索 libjvm.so 可能存在的路径,通过 get_filename_component 来获得 libjvm.so 的文件夹,并把这个文件夹设为默认搜索库路径。而后 set_target_properties 会进行链接工作。这个答案只能告诉我们"是什么",但是作为一只程序猿,还要了解"为什么",这里引申几个问题讨论:


  1. 1、find_package(JNI)的工作过程是怎样的?为什么 LD_LIBRARY_PATH 里没找到的依赖库,cmake 可以找到

  2. 2、cmake 的库搜索函数 find_library 会搜索 LD_LIBRARY_PATH 吗,如果不会,可以通过设置来搜索 LD_LIBRARY_PATH 吗?

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


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


find / -name FindJNI.cmake
复制代码


打开自己的 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
复制代码


这段代码表明,执行 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)
复制代码


在文件中定位 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()
复制代码


由此可知,JAVA_JVM_LIBRARY 这个变量,是通过逐个搜索 ${_JNI_${search}_JVM}里的文件夹进而确定 JAVA_JVM_LIBRARY 的。而 ${_JNI_${search}_JVM}相关的定义语句如图:


set(_JNI_FRAMEWORK_JVM NAMES JavaVM)set(_JNI_NORMAL_JVM  NAMES jvm  PATHS ${JAVA_JVM_LIBRARY_DIRECTORIES}  )
复制代码


其中 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()
复制代码


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

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  )
复制代码


通过以上分析可以看出,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()
复制代码


如果希望找不到这个库时编译失败,可以将 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()
复制代码

小结


本文通过编译后运行找不到库文件的问题引入,首先分析了 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 命令的搜索模式之模块模式(模块模式):.com/p/f983a90bcf91

[3]CMake 的 find_library 搜索 LD_LIBRARY_PATH 吗?:https://stackoverflow.com/questions/41566316/does-cmakes-find-library-search-ld-library-path


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

发布于: 刚刚阅读数: 4
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
CMake库搜索函数居然不搜索LD_LIBRARY_PATH_后端_华为云开发者联盟_InfoQ写作社区