cmake 使用 find_package 传播依赖项

cmake propagate dependencies using find_package

本文关键字:传播 依赖 package 使用 find cmake      更新时间:2023-10-16

举一个简单的例子:有两个库和一个可执行文件。 两个库都是SHARED的(即 .so 文件)。一个叫做libMain,一个叫libUtillibMain使用libUtil,可执行文件也是如此。 可执行文件可能单独使用 libUtil,但通常它会调用libMain中在其实现中使用libUtil的方法。

因此,在阅读了一些有关 CMake 的教程和文档后,此示例似乎相当简单。 每个项目都有一个简单的CMakeLists.txt而libUtil链接到libMain,可执行文件链接到libMain(我省略了target_include_directories以节省一些行)所有项目都使用相同的PREFIX_PATH。

libUtil

include(GNUInstallDirs)
project(libUtil)
set(CMAKE_SHARED_LIBRARY_PREFIX "")    
add_library(libUtil SHARED main.cpp)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Config
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(EXPORT ${PROJECT_NAME}Config DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake)

库主

include(GNUInstallDirs)
project(libMain)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
add_library(libUtil SHARED main.cpp)
find_package(libUtil REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC libUtil)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Config
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(EXPORT ${PROJECT_NAME}Config DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake)

可执行

project(exe)
find_package(libMain REQUIRED)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
add_executable(exe main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE libMain)

但是,在构建可执行文件时,我最终遇到了链接器错误,告诉exe无法执行-llibUtil

我已经花了很多时间在这上面,在阅读了几篇帖子后,我发现(或者至少我认为是这样)我需要提供一个自定义的libMainConfig.cmake(我将生成的重命名为libMainTargets.cmake)

include("${CMAKE_CURRENT_LIST_DIR}/libMainTargets.cmake")
find_package(libUtil REQUIRED)

链接器错误现在消失了,我可以运行我的项目了。我仍然不明白为什么我需要这样做。我知道在处理已经构建的库时是有区别的。但是,由于 CMake 始终使用基于目录的项目分离,因此我如何以及为什么要在同一个 CMakeList 中构建多个项目.txt?到目前为止,我一直使用find_package(本质上add_library导入,即如果我没记错的话,搜索预构建库)并希望将其定义的依赖项传播到"消费"项目。但似乎所有PUBLIC,导入目标的INTERFACEPRIVATE对"消耗"目标没有影响?

请在解释中详细说明和/或指出我错过的一些参考资料,并指出一些最佳实践,假设所有项目都是由我自己构建的,或者是像 boost 或 JNI 这样的系统库。

谢谢!

> CMake 有一个特定的函数来传播这样的find_package,称为find_dependency。你可以这样使用它:

include(CMakeFindDependencyMacro)
find_dependency(libutil)

CMake 不会导出其生成的文件中find_package调用。您必须手动执行此操作find_packagefind_dependency.不同之处在于find_dependency将正确转发REQUIREDQUIET

为什么 CMake 尝试将-llibUtil发送到链接器?

target_link_libraries添加内容时,有两种情况:

  1. 要链接的库是目标。在这种情况下,接口属性将正确传播,包括在需要时进行链接。
  2. 它只是一个字符串,没有目标。在这种情况下,CMake 假定它是一个系统库并添加-l标志

由于链接到libutillibmain的公共财产,所有libmain消费者也将链接到libutil

由于调用查找包必须手动完成,因此libutil不是目标。假定链接到系统库,因此 CMake 将exe到名为libutil的系统库,该系统库不存在。

调用find_package(libUtil REQUIRED)将确保exe通过链接目标而不是库来消耗libutil的使用要求。

libUtil怎么能成为目标?

CMake 目标没有(完全)绑定到目录。您可以有GLOBALIMPORTED目标,也可以按项目有多个目标。想象一个由多个库和可执行文件组成的项目。一个类比是,CMake 项目是 Visual Studio 解决方案,而 CMake 目标是 Visual Studio 项目。

现在在导入目标上。它们旨在表示已由另一个项目编译的目标,您安装或导出了构建树。导入的目标允许您像普通目标一样使用来自不同项目的内容,就像您声明它一样。导入的目标比编译器标志更精确:它们当然带有库,但也带有如何链接,所需的编译器标志,包含目录和其他要求。

find_packagefind_dependency用于查找配置文件,其中包含导入的目标信息。之后,只需链接到它就会设置包含目录、正确的链接器标志等。

项目导出多个目标的一个很好的例子是SFML,它将不同的模块(如声音,图形和窗口管理)导出为不同的静态/动态库。

如何避免这个愚蠢的错误?

CMake 让我们对目标使用命名空间。使用命名空间语法时,它不能是系统库。CMake 将输出一个错误,指出找不到目标,而不是尝试链接到不存在的库。

install(
EXPORT ${PROJECT_NAME}Config
NAMESPACE libUtil
DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake
)

然后将链接更改为以下内容:

target_link_libraries(${PROJECT_NAME} PUBLIC libUtil::libUtil)

命名空间名称的约定是使用与包相同的名称。