Best practices to set imported package settings

In Deniz Bahadir’s “More Modern CMake” talk, slide 12 presents how he
integrates external dependencies by having a CMakeLists.txt file for
each of them in an external/ subdirectory. One thing i fail to
understand is that this means any settings passed through as variable,
in his case it’s things like Boost_USE_STATIC_LIBS and others, have
to be declared in that CMakeLists.txt file, while it seems to me that
such options should be set from where the target_link_libraries()
call is made.

More specifically in my case i have a project with a library using the
GLAD OpenGL loader generator, and it seems a clean way to do this would
be to have in in an external/ subdirectory in the same way, but that
would mean that the OpenGL extensions i want to use, passed through the
GLAD_EXTENSIONS variable, would have to be set there. What if someone
wanted to take my library out of the tree and provide the GLAD
dependency in their own way, wouldn’t that mean they couldn’t know from
the library alone that GLAD_EXTENSIONS need to be set? What if i had
multiple subproject using GLAD and wanting different extensions? I was
planning on using FetchContent to retrieve glad too, which means that
it’s more than a simple find_package() call that’d have to be
specified in each of the libraries’ CMakeLists.txt too if i went that
route.

I’m very new to CMake (and discourse), apologies for any major
misunderstanding on my part.

Probably you want to focus on setting/consuming details through targets rather than variables. I’ll let @dbahadir respond with further details though, since it’s his talk you’ve referenced.

The example I presented in my talk has a specific requirement:

I want to look for a specific (configuration of a) set of external libraries, that should be used throughout my entire program!

So, this external dependency is some kind of global dependency for my entire project. Each target which uses that dependency has to use it with the same settings.


Explanation on the example from the slides

For simpler reasoning I am reproducing the code from my presentation here:

# ./external/boost/CMakeLists.txt -- Traditional/Modern CMake

set( BOOST_VERSION 1.58.0 )

# Settings for finding correct Boost libraries.
set( Boost_USE_STATIC_LIBS      FALSE )
set( Boost_USE_MULTITHREADED    TRUE )
set( Boost_USE_STATIC_RUNTIME   FALSE )
set( Boost_ADDITIONAL_VERSIONS  "${BOOST_VERSION}" )
set( Boost_COMPILER             "-gcc" )

# Search for Boost libraries.
find_package( Boost ${BOOST_VERSION} EXACT REQUIRED
    COMPONENTS program_options
               graph )

# Make found targets globally available.
if ( Boost_FOUND )
    set_target_properties( Boost::boost
                           Boost::program_options
                           Boost::graph
        PROPERTIES IMPORTED_GLOBAL TRUE )
endif ()

What I am doing here is to look for an installed Boost libraries collection in version 1.58.0, but specifically only for the header-only part and the Boost.ProgramOptions and Boost.Graph shared libraries.
Additionally I am saying, the shared libraries need to be compiled in such a way that they support multi-threading and are linked against a dynamic runtime. (The additional Boost_COMPILER variable indicates that they should have been compiled with the GCC compiler, although for newer built Boost versions that variable should probably contain the GCC version, too.)
What settings-variables need to be set to find the correct libraries depends on the individual find-script for or the import-file of the library you are looking for. (For example, newer Boost libraries, in general, require a Boost_ARCHITECTURE variable, too, to be able to find the correct libraries.)

This might then find the shared libraries with the following names:

libboost_program_options-gcc-mt-1_58.so
libboost_graph-gcc-mt-1_58.so

Due to setting the IMPORTED_GLOBAL property the three found targets (Boost::boost, Boost::program_options and Boost::graph) are made globally visible and are therefore accessible from other directories, too, and not only sub-directories.


This is fine as long as you only ever need these three Boost targets throughout your entire project and all targets are fine with linking (via target_link_libraries) with these.

However, if one of your targets somehow requires a statically linked Boost library it cannot use these targets. In that case, you cannot use my approach because different targets need to find different libraries.
In this situation you need to do local searches only (= do not set IMPORTED_GLBOAL).


Possible solution for you problem

If you want to give a user the chance to modify the set of variables which are needed to find your dependency, you should probably provide some CMake option that should be set by the user and you then set these variables according to the value of the option.
Or you can require, that these variables need to be set directly by the user. (You can always provide variables externally when calling CMake.)

If, however, two or more targets require different versions of your dependency then I do not see another way then to search for this dependency twice with different settings and not make the targets externally visible.

Other settings, that are not required for finding the correct dependency but “only” for how to use such a dependency, you should probably set through specific targets that you need to link, too, at the place where you link against the dependency.

I guess i’ll do that then, thank you for that detailed answer