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.
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)
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)
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.
-llibUtil
to the linker? When adding something to target_link_libraries
, there is two cases:
-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.
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.
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.