问题
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