Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CMake 动态链接库绝对路径问题 #162

Open
xizhibei opened this issue Feb 12, 2021 · 9 comments
Open

CMake 动态链接库绝对路径问题 #162

xizhibei opened this issue Feb 12, 2021 · 9 comments

Comments

@xizhibei
Copy link
Owner

xizhibei commented Feb 12, 2021

今天这篇文章算是对 【CMake 系列】(五)安装、打包与导出 的一个补充。其实我本打算跟上篇文章放在一起,毕竟都属于动态链接库相关的知识,但是这样一来就不容易被出现问题的同学们检索到了(才不是为了再水一篇文章 doge)。

问题的由来

是因为这个问题困扰了我不少时间,在好几个项目里面都遇到了这个问题。

那就是链接动态库的时候,编译出来的可执行文件会带有编译时的绝对路径,于是你将程序拷贝到其它地方运行的时候,必须把动态库放到绝对路径里面去,而不是放在系统里面相关的 lib 路径下面。

举一个例子,假如我们要实现一个 FooConfig.cmake,这个库中既有静态库也有动态库,那么如果我们要在项目中使用,大概的实现方式是:

find_path(FOO_INCLUDE_DIRS NAMES foo.h)

get_filename_component(_IMPORT_PREFIX "${FOO_INCLUDE_DIRS}" PATH)
set(FOO_LIBRARY_DIRS ${_IMPORT_PREFIX}/lib)

if(NOT FOO_FIND_COMPONENTS)
  set(FOO_FIND_COMPONENTS foo bar)
endif()

set(FOO_USE_SHARED 1)
set(_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
if(FOO_USE_SHARED)
  set(CMAKE_FIND_LIBRARY_SUFFIXES .so)
else()
  set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif()

foreach(lib ${FOO_FIND_COMPONENTS})
  set(_lib_location "_lib_location-NOTFOUND")
  find_library(_lib_location NAMES "${lib}")
  if(NOT _lib_location)
    message(FATAL_ERROR "FOO lib '${lib}' is not found")
  endif()

  set(_lib_name FOO::${lib})
  add_library(${_lib_name} UNKNWON IMPORTED)
  set_target_properties(
    ${_lib_name}
    PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${FOO_INCLUDE_DIRS}"
               IMPORTED_LOCATION_RELEASE "${_lib_location}"
               IMPORTED_CONFIGURATIONS RELEASE)

  list(APPEND FOO_LIBS "${_lib_name}")

  unset(_lib_location) # clean
  unset(_lib_name) # clean
endforeach()
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_CMAKE_FIND_LIBRARY_SUFFIXES})

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Reader REQUIRED_VARS FOO_INCLUDE_DIRS
                                                       FOO_LIBS)

mark_as_advanced(FOO_INCLUDE_DIRS FOO_LIBS)

# cleanup
unset(_IMPORT_PREFIX)
unset(_CMAKE_FIND_LIBRARY_SUFFIXES)

将它命名为 FooConfig.cmake 然后放在位于项目根目录的 cmake 文件夹下,并且在项目中这样使用:

find_package(Foo REQUIRED HINTS ${PROJECT_SOURCE_DIR}/cmake)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE ${FOO_LIBS})

最后,假如我们查找的库在 /path/to/foo/home 下面,那么我们用在项目中得到的结果会是这样的:

$ readelf -d a.out | grep NEEDED
Dynamic section at offset 0xb5ddb4 contains 2 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [/path/to/foo/home/lib/libfoo.so]
 0x00000001 (NEEDED)                     Shared library: [/path/to/foo/home/lib/libbar.so]

这里就出现了绝对路径,当初这个问题折磨了我很久,一直以为是 RPATH 的问题,最后发现是 CMake 本身的问题。

如何解决

出现这个问题的原因就是库的 Package Find Config 不对,我研究了挺长时间,最后在官方的讨论中找到了原因以及答案:

  1. 缺少了 IMPORTED_NO_SONAME 的属性;
  2. 引入动态库的时候,使用了 UNKNWON 类型的库;

于是,将上面的代码改下即可:

# ...

if(FOO_USE_SHARED)
  set(FOO_LIB_TYPE "SHARED")
  set(CMAKE_FIND_LIBRARY_SUFFIXES .so)
else()
  set(FOO_LIB_TYPE "STATIC")
  set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif()

# ...

set_target_properties(
    ${_lib_name}
    PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${FOO_INCLUDE_DIRS}"
               IMPORTED_NO_SONAME_RELEASE true
               IMPORTED_LOCATION_RELEASE "${_lib_location}"
               IMPORTED_CONFIGURATIONS RELEASE)

# ...
@kbore
Copy link

kbore commented May 30, 2021

请问作者, FooConfig.cmake的内容是靠 install(export) 自动生成的吗?
我想将这部分内容从手写改为cmake自动生成, 但是无法处理区分链接 动态库或静态库的需求, 导致只能手写Findxxx.cmake文件.

@xizhibei
Copy link
Owner Author

请问作者, FooConfig.cmake的内容是靠 install(export) 自动生成的吗?
我想将这部分内容从手写改为cmake自动生成, 但是无法处理区分链接 动态库或静态库的需求, 导致只能手写Findxxx.cmake文件.

不是,上面的例子是给第三方没有实现 cmake 支持的库写的,如果是自己的项目就参考 #137 这个里面写的自动生成

@kbore
Copy link

kbore commented May 31, 2021

拜读过 #137 ,但是那样生成的 cmake config 文件应该不支持区分静态库或者动态库, 而且动态库和静态库是两个 cmake config 文件.
不知作者是否有研究过如何将动态库和静态库合并导出一个 cmake config 文件让其他人引用? 目前我是手写的, 正在找有没有自动生成的方法.

@xizhibei
Copy link
Owner Author

大概明白你意思了,我说个我们自己使用的方式:

从 target 名称进行区分,比如静态库是 foo 而动态库是 fooShared,这样的话,编译一次就可以自动生成一个 find config,不过你会导出名字变为 libfooShared.so,想要统一的话,可以用 set_target_properties(fooShared PROPERTIES OUTPUT_NAME foo)

@kbore
Copy link

kbore commented Jun 2, 2021

比如静态库是 foo 而动态库是 fooShared,这样的话,编译一次就可以自动生成一个 find config
-- 请问这步是如何实现的? 我理解成是写成下面这样的代码, 这样会生成2个find config.

install(TARGETS foo EXPORT myfooLib)
install(EXPORT myfooLib)

install(TARGETS fooShared EXPORT myfooSharedLib)
install(EXPORT myfooSharedLib)

@xizhibei
Copy link
Owner Author

xizhibei commented Jun 2, 2021

可以直接参考 #137 的导出部分来,我给个关键的部分

add_library(fooStatic STATIC foo.cpp)
set_target_properties(fooStatic PROPERTIES OUTPUT_NAME foo) # libfoo.a

add_library(fooShared SHARED foo.cpp)
set_target_properties(fooShared PROPERTIES OUTPUT_NAME foo) # libfoo.so

install(
  TARGETS fooStatic fooShared
  EXPORT Foo
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
  RUNTIME DESTINATION bin
  INCLUDES
  DESTINATION include)

install(
  EXPORT Foo
  FILE FooTargets.cmake
  NAMESPACE Foo::
  DESTINATION lib/cmake/Foo/)

在使用的时候:

find_package(Foo)
target_link_libraries(bar PUBLIC Foo::fooShared)
# 或者
target_link_libraries(bar PUBLIC Foo::fooStatic)

@kbore
Copy link

kbore commented Jun 4, 2021

这两天我试了下, 按照您给的建议, 已经将动态库和静态库合一安装, 其他 project 也能正常引用到. 说来也巧, 真的是因为这篇文章独立的标题 我才能检索到这的, 再次感谢下.

另外, 关于安装部分有个问题想再请教下. 假设下面这种场景

  1. library A 是一个单独的 build tree, 它依赖另一个 import target B(也属于 library A 的 build tree).
  2. binary B 是个独立的 build tree, 通过 find_package 引用 library A 的 install 产物, library A 的 target 能正常引用到, 但是会提示 target B not found.
  3. 问题的原因是 library A 在引用 target B 时选择了 PUBLIC 属性, 而 target B 因为是 import target 又无法被 install,
    library A 的 config 文件中也就不会体现出 target B.

我想了下, 要么停止 library A 的依赖库传染, 要么想办法把 target B 也传递给 binary B. 前者应该会导致依赖不完整,, 后者感觉应该是在您前面提到的 myLibConfig.cmake.in 做文章.

  • 比较笨的办法是把 import target 在 binary B 再手动实现一遍, 但是非常不优雅.
  • 网上检索到一种办法是在 myLibConfig.cmake.in 中 include target B 的 cmake 文件, 让 binary B 也拥有这个 import target.
    请问作者是否有更好的办法, 望不吝赐教, 感谢!
    cmake - Exporting an imported library - Stack Overflow

@xizhibei
Copy link
Owner Author

xizhibei commented Jun 6, 2021

hh,挺巧

你说这个问题没遇到过,目前除了你说的,没有更好的办法了

@kbore
Copy link

kbore commented Jun 6, 2021

hh,挺巧

你说这个问题没遇到过,目前除了你说的,没有更好的办法了

周末检索各种资料确认了下, CMake官方的态度是这类第三方依赖应该downstream提前安装好, upstream在 myLibConfig.cmake.in 中写明依赖项即可.
根据我自己的工程实践, 觉得使用上还是麻烦了点, 尤其是一些闭源的场景. 目前的解决方案是在 myLibConfig.cmake.in 做文章, 根据需要导出的target清单自动重写了imported target的依赖, 感觉可以接受了.
不管怎样, 感谢作者的博客和答疑, 帮我解决了很大的困惑.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants