如何在CMake中处理递归依赖关系

3
假设我有A、B和C三个包。包B使用了包A,包C又使用了包B。我创建了共享库。
因此,在包B中,我做了如下操作:
    find_package(A)
    ...
    if(${A_FOUND})
    target_link_libraries(B ${A_LIBRARIES})
    endif()

在 C 包中,我执行以下操作:

    find_package(B)
    ...
    if(${B_FOUND})
    target_link_libraries(C ${B_LIBRARIES})
    endif()

    add_executable(main main.cpp)
    target_link_libraries(main C)

其中 ${B_LIBRARIES} 只包含 B。编译器将会报错。

    /usr/bin/ld: cannot find -lA
    collect2: error: ld returned 1 exit status

只要A被安装在C的link_directories之外的位置,就可以正确处理此问题。我想知道正确的处理方式是什么。对我来说,在C中使用find_package(A)(这将起作用)似乎不太好。尤其是对我来说,因为我事先不知道B是否依赖于A。它也可能依赖于其他软件包。

通常,find_package(A) 会定义一个名为 A_LIBRARIES 的变量,用于将可执行文件/库与 A 进行链接:target_link_libraries(B ${A_LIBRARIES})。有关 FindA.cmake 脚本的详细信息,请参见说明。 - Tsyvarev
这实际上是我在这里做的事情,因为${A_LIBRARIES}只包含A。如果不清楚,对不起。但是,仍然存在相同的问题,因为${B_LIBRARIES}不包含A。至少我从未见过任何将其所有依赖项包括在${A_LIBRARIES}${A_INCLUDE_DIRECTORIES}等中的项目。我应该无论如何这样做吗? - Sbte
${A_LIBRARIES} 应该包含库 A 的绝对路径,这样链接器即使没有设置 link_directories 也能找到它。同样地,${B_LIBRARIES} 应该包含 AB绝对路径 - Tsyvarev
是的,这基本上就是我的问题所在。我是否应该将${A_LIBRARIES}放入${B_LIBRARIES}中?我从未见过任何这样做的项目。您有这样做的示例吗?对我来说,这似乎不可能是“正确”的方法。如果${B_LIBRARIES}仅包含B部分的库,那么这对我更有意义。 - Sbte
1
在执行此任务之前,请仔细阅读以下注释:“# These are IMPORTED targets created by FooBarTargets.cmake”。这里的“foo”不是库名称,而是库目标。该目标本身包含库文件的完整路径。请注意,“Config.cmake”脚本的意图与“Find.cmake”脚本的意图不同。 - Tsyvarev
显示剩余5条评论
3个回答

1
问题在于,您需要通过创建“正确”的配置文件使每个库导出其目标。这在这里有所记录,但基本上对于每个find_package()调用,您应该在配置文件中添加相应的find_dependency()调用。当您这样做时,目标将被递归地找到为CMake包,并且所有传递依赖项都应该被包括在内。
以下是我的意思的示例:

项目A

CMakeLists.txt:

项目(liba)
添加库(a src/a.cpp)
目标链接库(a PUBLIC flag1 flag2)
安装(TARGETS a EXPORT ${PROJECT_NAME}Targets ARCHIVE DESTINATION lib)
安装(FILES ${PROJECT_NAME}Config.cmake DESTINATION lib/cmake/${PROJECT_NAME})
安装(EXPORT ${PROJECT_NAME}Targets FILE ${PROJECT_NAME}Targets.cmake DESTINATION lib/cmake/${PROJECT_NAME})

libaConfig.cmake:

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

项目 B

CMakeLists.txt:

项目(libb)
添加库(b):源文件为src/b.cpp
查找包(liba)配置,需要引入
链接库(b):公开使用库(a)
安装(TARGETS b EXPORT ${PROJECT_NAME}Targets ARCHIVE DESTINATION lib)
安装(FILES ${PROJECT_NAME}Config.cmake DESTINATION lib/cmake/${PROJECT_NAME})
安装(EXPORT ${PROJECT_NAME}Targets FILE ${PROJECT_NAME}Targets.cmake DESTINATION lib/cmake/${PROJECT_NAME})

libbConfig.cmake:

包含(CMakeFindDependencyMacro)
查找依赖项(liba) # 获取递归工作的关键步骤
包含("${CMAKE_CURRENT_LIST_DIR}/libbTargets.cmake")

C项目

CMakeLists.txt:

add_executable(main main.cpp)
找到包含libb的配置文件并引入(CONFIG REQUIRED),同时递归调用查找liba...。
将目标文件main链接到私有库b上,应该变成-lb -la -lflag1 -lflag2。

0
问题在于${A_LIBRARIES}应该包含绝对路径。像Trilinos这样的软件包提供了类似${Trilinos_LIBRARY_DIRS}的东西,你应该通过以下方式将其包含进来。
link_directories(${Trilinos_LIBRARY_DIRS})

然而,根据https://cmake.org/cmake/help/v3.0/command/link_directories.html,这是错误的。 find_package(Trilinos) 应该返回库的绝对路径,所以我不应该这样做。我通过执行类似以下操作来解决了这个问题:
set(library_directories ${Trilinos_LIBRARY_DIRS})
list(APPEND library_directories ${Trilinos_TPL_LIBRARY_DIRS})

set(library_dependencies ${Trilinos_LIBRARIES})
list(APPEND library_dependencies ${Trilinos_TPL_LIBRARIES})

set(found_library_dependencies)
foreach(lib ${library_dependencies})
  set(found_${lib})
  if(IS_ABSOLUTE ${lib})
    set(found_${lib} ${lib})
  else()
    find_library(found_${lib} ${lib} ${library_directories})
  endif()
  list(APPEND found_library_dependencies ${found_${lib}})
  message(STATUS "Using ${lib}")
  message(STATUS "Found in ${found_${lib}}")
endforeach(lib)

我在这里列出了包的清单,这样我就可以为所有从Trilinos复制了这种错误行为的包执行此操作。

回到最初的问题。我仍然必须在${B_INCLUDE_DIRS}中包括${A_INCLUDE_DIRS},以便C可以找到B头文件中包含的A头文件,但我可以接受这一点。然而,${B_LIBRARIES}现在不必再包括${A_LIBRARIES},因为它们是作为共享库构建的。而这正是我要找的。


0

移除find_package,因为这会告诉编译器在用户机器上查找已安装的软件包。相反,首先使用add_library创建库,然后在二进制文件中使用include_directories包含头目录,其余部分已知。


它们是不同的软件包,已经安装在系统上。至少在CMake可以找到它们的地方。因此${A_FOUND}${B_FOUND}都将为真。 - Sbte
那么它们是真的吗? - Joel
是的。我会稍微调整一下我的问题示例,以便我实际使用它。我只是不想把它弄得太乱。 - Sbte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接