Hi,
I’m working on a large project that has managed its external library dependencies in a single “libraries.cmake” file so far. For each external dependency there is a find_package call. Now I want to change it to a more modular design where every library knows it’s own dependency to get rid of the master declaration.
For this I wrote a wrapper for target_link_libraries and introduced symbols like “EXTERNAL_LIB_{NAME}” where “{NAME}” is the Libray internal name (e.g. PNG).
A sample target_link_libraries_wrapper call looks like: target_link_libraries_wrapper(my_libA EXTERNAL_LIB_PNG)
The wrapper evaluates the “EXTERNAL_LIB_” and calls for the given {NAME} a macro “USE_EXTERNAL_LIB_{NAME}”. In this example USE_EXTERNAL_LIB_PNG. These macros then do the work of find_package, include_directories and target_link_libraries call.
This works well for most of my libraries. But there are some where the transitive include_directories do not work.
A library my_libB which is linked to my_libA do not get the my_libA include_directories (e.g. for library PNG) and therefore I get compilation errors. This was not the case with the master declaration. Even if i change the master declaration to use the new USE_EXTERNAL_LIB_PNG macro instead of the direct find_package it works.
Now to my questions:
Is this a good practice to have a more modular design?
Is it ok to have such a “late” binding?
How can I do it better?
Why do I get these compilation errors to unknown includes?
I think instead of working with multiple target_* commands, you should use find_package() and use IMPORTED targets. That will save you a lot of calls and variable shuffling.
I suspect that your main issue is that everything is using PRIVATE visibility. Instead, each usage should have an associated visibility:
target_link_libraries_wrapper(my_libA
PUBLIC EXTERNAL_LIB_PNG)
Another thing you can do is make your own targets that “encapsulate” each external dependency:
Interesting approach to have a dedicated encapsulated external dependency.
Sadly this would not solve the problem to run the find_package for a external library only when there is a target which links to it. In our project it’s possible to have derivatives which do not need to have find_package calls to libraries they don’t use, but others do.
For this reason a late dependency tracking would be nice.
IMPORTED targets are scoped, but your own add_library is not, so the add_library version can be accessed from anywhere once made without another find_package call.
I solved the problem with a combined approach. I use your proposal about to have an interface library with my symbolic name scheme. Now I do have a late binding with all advantages of a interface libraries. If a symbolic name is never used, there is never a find_package call and no interface library is built.