摘要: 本文通过编译后运行找不到库文件的问题引入,首先分析了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

这个报错表示,操作系统并没有找到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. 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-<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命令的搜索模式之模块模式(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

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

CMake库搜索函数居然不搜索LD_LIBRARY_PATH的更多相关文章

  1. 怎么在vscode里搜索函数

    怎么在vscode里搜索函数?在vsCode编辑器中如何跨文件查找函数的定义? 问题: 比如: 在 a.js中使用 var res = window.unique(arr); 在未知的js文件定义了u ...

  2. 谈谈Linux下动态库查找路径的问题 ldconfig LD_LIBRARY_PATH PKG_CONFIG_PATH

    谈谈Linux下动态库查找路径的问题 ldconfig LD_LIBRARY_PATH  PKG_CONFIG_PATH 转载自:http://blog.chinaunix.net/xmlrpc.ph ...

  3. Atitit 数据库 标准库  sdk 函数库 编程语言 mysql oracle  attilax总结

    Atitit 数据库 标准库  sdk 函数库 编程语言 mysql oracle  attilax总结 1.1. 常见的编程语言以及数据库 sql内部函数库标准化库一般有以下api1 1.2. 各个 ...

  4. 深度优先搜索DFS和广度优先搜索BFS简单解析(新手向)

    深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每个点仅被访问一次,这个过程就是图的遍历.图的遍历常用的有深度优先搜索和广度优先搜索,这两者对于有向图和无向图 ...

  5. 【C】——动态库中函数的作用范围

    如何生成动态库 net小伙 已经在此文中说明——[C]——如何生成静态库和动态库:接下来就要看看动态库中函数的作用范围: 首先我们使用命令   gcc -fPIC -shared -o libtest ...

  6. 云搜索服务在APP搜索场景的应用

    搜索无处不在,尤其是在移动互联的今天.无论是社交,电商,还是视频等APP中,搜索都已经在其中扮演了重要的角色.作为信息的入口,搜索能帮用户从海量信息中找到想要的信息.在APP搜索的典型场景如下: ●  ...

  7. 深度优先搜索DFS和广度优先搜索BFS简单解析

    转自:https://www.cnblogs.com/FZfangzheng/p/8529132.html 深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每 ...

  8. 高德地图搜索功能以及清除搜索结果maker

    第一次写文章,写得不好各位看官见谅~ (pσ_σ)P首先这是一个vue里面的项目,高德地图api是直接CDN进来的,所以使用了global来调用,默认已经初始化了一个地图,为了实现一个输入框搜索功能和 ...

  9. Python---进阶---文件操作---搜索文件和保存搜索结果

    ### 编写一个程序,用户输入文件名以及开始搜索的路径,搜索该文件是否存在,如果遇到文件夹,则进入该文件夹继续搜索 - input 去接受用户输入的文件名和开始搜索的路径 - os.path.isdi ...

随机推荐

  1. mysql5.7介绍和安装

    环境准备: 1.关闭防火墙和selinux systemctl stop firewalldsystemctl stop SElinux 2. 如果安装过mariadb需要停止且卸载服务 system ...

  2. ElasticSearch7.3学习(二十八)----聚合实战之电视案例

    一.电视案例 1.1 数据准备 创建索引及映射 建立价格.颜色.品牌.售卖日期 字段 PUT /tvs PUT /tvs/_mapping { "properties": { &q ...

  3. SQL语言DDL

    MySQL数据库基本操作-DDL -- ctrl+/和# :注释 -- SQL语言不区分大小写: DDL:数据定义语言: 对数据库的常用操作: -- 查看所有的数据库: show databases; ...

  4. vue运行npm run dev时候,自动打开页面

    在config/index.js找到dev:{}里面的autoOpenBrowser: 设置为true,重新npm run dev一次就自动弹出浏览器页面啦!

  5. JAVA - error(错误)和exception(异常)有什么区别?

    JAVA - error(错误)和exception(异常)有什么区别? error 表示恢复不是不可能但很困难的情况下的一种严重问题.比如说内存溢出.不可能指望程序能处理这样的情况. excepti ...

  6. PyTorch保存模型、冻结参数等

    此外可以参考PyTorch模型保存.https://zhuanlan.zhihu.com/p/73893187 查看模型每层输出详情 Keras有一个简洁的API来查看模型的每一层输出尺寸,这在调试网 ...

  7. camunda开源版与商业版的差异

    Camunda流程引擎分社区版和企业版,社区版实际上是开源版,是Apache2.0协议,企业版实际上是商业收费版本,需要购买授权才能使用,那么社区版和企业版的差异有哪些呢,社区版本是否能满足我们日常的 ...

  8. generatorConfig.xml自动生成实体类,dao和xml

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE generatorConfiguration ...

  9. Museui 图标速览,再也不用担心网页打不开了

    更多内容请见原文,原文转载自:https://blog.csdn.net/weixin_44519496/article/details/119328173

  10. 从Vue源码中我学到了几点精妙方法

    话不多说,赶快试试这几个精妙方法吧!在工作中肯定会用得到. 立即执行函数 页面加载完成后只执行一次的设置函数. (function (a, b) { console.log(a, b); // 1,2 ...