How do you handle recursive dependencies in CMake

不打扰是莪最后的温柔 提交于 2021-02-09 20:30:36

问题


Say I have packages A, B, and C. Package B uses package A and package C uses package B. I create shared libraries.

So in package B I do something like

    find_package(A)
    ...
    if(${A_FOUND})
    target_link_libraries(B ${A_LIBRARIES})
    endif()

and in package C I do

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

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

where ${B_LIBRARIES} contains only B. The compiler will now complain

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

as long as A is installed in a place that is not in the link_directories of C. I was wondering what is the correct way of handling this. For me using find_package(A) in C (which would work) doesn't seem to be the nice. For me especially, because I don't know in advance if B depends on A or not. It might also depend on a different package.


回答1:


The problem is that you need to make each library export its targets by creating a "proper" configuration file. This is kind of documented here, but basically for every find_package() call you're supposed to add a corresponding find_dependency() call into your configuration file. When you do this, the targets will be recursively found as cmake packages, and all transitive dependencies should be included.

Here is an example of what I mean:

Project A

CMakeLists.txt:

project(liba)
add_library(a src/a.cpp)
target_link_libraries(a PUBLIC flag1 flag2)
install(TARGETS a EXPORT ${PROJECT_NAME}Targets ARCHIVE DESTINATION lib)
install(FILES ${PROJECT_NAME}Config.cmake DESTINATION lib/cmake/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}Targets FILE ${PROJECT_NAME}Targets.cmake DESTINATION lib/cmake/${PROJECT_NAME})

libaConfig.cmake:

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

Project B

CMakeLists.txt:

project(libb)
add_library(b src/b.cpp)
find_package(liba CONFIG REQUIRED)
target_link_libraries(b PUBLIC a)
install(TARGETS b EXPORT ${PROJECT_NAME}Targets ARCHIVE DESTINATION lib)
install(FILES ${PROJECT_NAME}Config.cmake DESTINATION lib/cmake/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}Targets FILE ${PROJECT_NAME}Targets.cmake DESTINATION lib/cmake/${PROJECT_NAME})

libbConfig.cmake:

include(CMakeFindDependencyMacro)
find_dependency(liba) # The key step for getting recursion working
include("${CMAKE_CURRENT_LIST_DIR}/libbTargets.cmake")

Project C

CMakeLists.txt:

add_executable(main main.cpp)
find_package(libb CONFIG REQUIRED) # Also recursively calls find_package(liba ...)
target_link_libraries(main PRIVATE b) # should become -lb -la -lflag1 -lflag2



回答2:


Remove find_package because with that you are telling the compiler to look for a package already installed in the users machine. Instead first create the libraries with add_library and later in your binary you can include the header directory with include_directories the rest is known.




回答3:


The problem here is that ${A_LIBRARIES} should contain absolute paths. Packages like Trilinos provide something like ${Trilinos_LIBRARY_DIRS}, which you should include in by doing

link_directories(${Trilinos_LIBRARY_DIRS})

This, however, is wrong according to https://cmake.org/cmake/help/v3.0/command/link_directories.html . find_package(Trilinos) should return absolute paths to the libraries, so I should not have to do this. I fixed this by doing something like

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)

I made a list of the packages here so I can do this for all my packages which all copied this wrong behaviour from Trilinos.

Coming back on the original problem. I do still have to include the ${A_INCLUDE_DIRS} in ${B_INCLUDE_DIRS}, so C can find the header files of A that are included in header files of B, but I can live with this. However, ${B_LIBRARIES} now does not have to include ${A_LIBRARIES}, since they were built as shared libraries. And that is exactly what I was looking for.



来源:https://stackoverflow.com/questions/36358225/how-do-you-handle-recursive-dependencies-in-cmake

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!