简体   繁体   中英

cmake propagate dependencies using find_package

As a simple example: there are two libraries and one executable. Both libraries are SHARED (ie a .so file). One is called libMain and one is called libUtil . libMain uses libUtil and so does the executable. The executable might use libUtil on its own but usually it calls a method from libMain that does use libUtil in its implementation.

So after reading some tutorials and docs about CMake this example seemed fairly straight forward. There is a simple CMakeLists.txt for each project while libUtil links to libMain and executable links to libMain . (I ommited target_include_directories to spare some lines) Same PREFIX_PATH is used by all projects.

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)

libMain

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)

executable

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

However when building the executable I ended up with a linker error, telling exe was not able to perform -llibUtil .

I spend quite some time on this already and after reading several posts I found out (or at least I think so..) that I need to provide a custom libMainConfig.cmake (I renamed the generated one to libMainTargets.cmake)

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

find_package(libUtil REQUIRED)

The linker error is gone now and I can run my project. Still I do not understand why I need to do this. I understood there is a difference when handling an already built library. However, as CMake always uses a directory based separation of projects, how and why would I ever build multiple projects in the same CMakeLists.txt? So far I always used find_package (which essentialy does add_library IMPORTED, ie searches a pre-build library If I am not mistaken) and wanted to propagate its defined dependencies to the "consuming" project. But it seems all of PUBLIC , INTERFACE and PRIVATE from the imported target have no affect in the "consuming" target?

Please be detailed in the explanation and/or point out some references I missed and point out some best practices assuming all projects are either build by myself or are system libraries like boost or JNI.

Thanks!

CMake has a specific function made to propagate find_package like this called find_dependency . You can use it like that:

include(CMakeFindDependencyMacro)
find_dependency(libutil)

CMake will not export calls find_package in its generated files. You must do it manually with find_package or find_dependency . The difference is that find_dependency will forward REQUIRED and QUIET correctly.

Why does CMake tries to send -llibUtil to the linker?

When adding something to target_link_libraries , there is two cases:

  1. The library to link is a target. In this case, the inteface properties are propagated correctly including linking if needed.
  2. It's simply a string, there is no targets. In this case, CMake assumes it's a system library and add the -l flag .

Since linking to libutil is a public property of libmain , all consumers of libmain will also link to libutil .

Since calling find package must be done manually, libutil is not a target. Linking to a system library is assumed so CMake will make exe to a system library named libutil which does not exist.

Calling find_package(libUtil REQUIRED) will ensure exe will consume usage requirements of libutil by linking targets instead of libraries.

How can libUtil be a target?

CMake target is not (completely) tied to directories. You can have GLOBAL and IMPORTED targets, and you can have multiple target by project. Imagine a project that is composed of multiple libraries and executables. An analogy would be that CMake projects are visual studio solution, and CMake targets are visual studio projects.

Now on imported targets. They are meant to represent targets that are already compiled by another project, that you either installed or exported the build tree. Imported target let you consume things from different project just like a normal target, as if you declared it. Targets that are imported are more precise than compiler flags: they carry of course the libary, but also how to link, required compiler flags, include directories and other requirements.

find_package and find_dependency are meant to find the config file and those contain the imported targets informations. After that, simply linking to it will set include directories, correct linker flags, etc.

A good example of a project exporting multiple targets is SFML, which export different module like sound, graphics and window management as distinct static/dynamic libraries.

How can this silly mistake can be avoided?

CMake let us use namespaces for targets. When using the namespace syntax, it cannot be a system library. CMake will output an error that the target is not found instead of trying to link to a non-existant library.

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

And then change linking to this:

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

The convention for namespace name is to use the same name as the package.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM