cmake 使用 find_package 传播依赖项

Chr*_*ris 4 c++ cmake

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

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

库工具

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)
Run Code Online (Sandbox Code Playgroud)

库主程序

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)
Run Code Online (Sandbox Code Playgroud)

可执行文件

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

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

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

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

find_package(libUtil REQUIRED)
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢!

Gui*_*cot 5

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

include(CMakeFindDependencyMacro)
find_dependency(libutil)
Run Code Online (Sandbox Code Playgroud)

CMake 不会导出find_package其生成文件中的调用。您必须使用find_package或手动执行此操作find_dependency。区别在于find_dependency会转发REQUIREDQUIET正确。

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

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

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

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

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

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

怎么能libUtil成为目标呢?

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

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

find_package旨在find_dependency查找配置文件以及包含导入的目标信息的文件。之后,只需链接到它就会设置包含目录、正确的链接器标志等。

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

如何才能避免这种愚蠢的错误呢?

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

install(
    EXPORT ${PROJECT_NAME}Config
    NAMESPACE libUtil
    DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake
)
Run Code Online (Sandbox Code Playgroud)

然后更改链接到此:

target_link_libraries(${PROJECT_NAME} PUBLIC libUtil::libUtil)
Run Code Online (Sandbox Code Playgroud)

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