[CMake] [cmake-developers] How should config packages handle components?

Alex Turbov i.zaufi at gmail.com
Wed Sep 6 17:55:32 EDT 2017


In my projects I always, have external dependencies with finder module
providing exported (imported in my project) targets, so my targets (one per
file) always have a list of `Vendor::target`. Some of them are mine (i.e.
your type 2 -- built by the same project).

I wrote a helper module and a function to write an additional
`blah-blah-target-depedencies.cmake`. And this is a little bit subtle
thing...
Normally, generated `blah-blah-target.cmake` (where the `blah-blah-target`
is the export name used in call to `install(EXPORT)`) to load all installed
configurations do the following:

  file(GLOB CONFIG_FILES "${_DIR}/blah-blah-target-*.cmake")


so my additional file would be loaded "automatically" :)

My helper function accepts a target name as the only parameter. First of
all, it collects all dependencies of the given target and classifies them
as internal (same project) and external. It prepares some variables and
finally, render a `blah-blah-target-dependencies.cmake` which is a result
of the combination of 3 templates:
0) a base, header+footer and conditionally "include" the next two
1) import internal dependencies
2) import external dependencies

Note templates have random string in final names, so being running
(loading) by the same script (`blah-blah-config.cmake`) won't interfere...

As for "prepared variables" there are the following:
* "internal dependencies" is just a list of target names... having a name
is enough to form a `blah-blah-target.cmake` name to be `include()`d, cuz
we really know where this file is installed
* for external dependencies the most important a list of packages, assuming
that the initial external (imported) target had the form of
`Vendor::target`, and `Vendor` in fact is a package name suitable as the
first argument to `find_package()`.
* other variables related to external targets have a variadic part based on
upcased name of the vendor. Example:

    set(_blah_blah_PACKAGES ACE;Boost)
    set(_blah_blah_ACE_VENDOR ACE)
    set(_blah_blah_ACE_COMPONENTS ace;ssl)
    set(_blah_blah_ACE_VERSION 5.7.5)
    set(_blah_blah_BOOST_VENDOR Boost)
    set(_blah_blah_BOOST_COMPONENTS
chrono;filesystem;program_options;thread)
    set(_blah_blah_BOOST_VERSION 1.65.0)

Now about generated `*-config.cmake`. As one may guess it handle targets to
import as `COMPONENTS` of `find_package()`, where every component is an
exported target name. So this module just `include()` it and check if
target appeared:

    # Find components if requested
    set(blah_FOUND_COMPONENTS)
    foreach(_module ${blah_FIND_COMPONENTS})
        # TODO Avoid more than once find? (But be aware that is not a
trivial `if` and skip %-)
        # TODO Make sure component is supported (exists)
        include(
            "${CMAKE_CURRENT_LIST_DIR}/blah-${_module}-targets.cmake"
            OPTIONAL
            RESULT_VARIABLE blah_${_module}_FOUND
        )

        if(blah_${_module}_FOUND AND TARGET Blah::${_module})
            list(APPEND blah_FOUND_COMPONENTS ${_module})

            # Add some interface properties to all found components
            string(TOUPPER "${_module}" _module_id)
            string(MAKE_C_IDENTIFIER "${_module_id}" _module_id)
            set_target_properties(
                Blah::${_module}
                PROPERTIES
                    # Set compatible version usage requirement
                    INTERFACE_BLAH_VERSION_MAJOR "${BLAH_VERSION_MAJOR}"
                    # What package to find
                    INTERFACE_BLAH_${_module_id}_PACKAGE_NAME "blah"
                )
            set_property(
                TARGET Blah::${_module}
                APPEND PROPERTY
                    COMPATIBLE_INTERFACE_STRING BLAH_VERSION_MAJOR
                )
            unset(_module_id)

        else()
            set(blah_${_module}_FOUND NOTFOUND)

            if (blah_FIND_REQUIRED_${_module})
                list(APPEND blah_NOT_FOUND_REQUIRED_COMPONENTS ${_module})
            else()
                list(APPEND blah_NOT_FOUND_COMPONENTS ${_module})
            endif()

        endif()
    endforeach()
    unset(_module)

When all components checked call the final package found/not-found checker:

    check_required_components(blah)

Yes, this particular implementation have obvious limitations to be named
"universal and generic", but it works few years for me w/o problems...
(however, I do some improvements from time to time %) It do not handle
"plain old library names"... and as I said, all my external packages
provide imported targets, where the Vendor name is the package name in
fact... so I don't care (while having no reason to improve it for this
case... maybe later %).

I'll attach my module (stripping not related and vendor specific parts) for
further inspiration... Feel free to ask for details (or more code, if
attached doesn't work... probably I miss some functions (from other parts
of my framework)).

Have fun! :)


On Tue, Sep 5, 2017 at 10:33 PM, Robert Dailey <rcdailey.lists at gmail.com>
wrote:

> In the case where I'm exporting 1 target.cmake script per component
> for a single package, could someone provide an example on how to
> manage dependencies? There are 2 types of dependencies:
>
> 1. Dependencies on external packages
> 2. Cross-dependencies within the same package (i.e. on other
> components in the same package)
>
> The cmake-packages doc kind of goes over #1, but #2 doesn't seem to
> have examples.
>
> On Fri, Sep 1, 2017 at 1:21 PM, Robert Dailey <rcdailey.lists at gmail.com>
> wrote:
> > First of all, I want to apologize for including the developer list.
> > Maybe I'm not being patient enough, but it seems like every post I've
> > made on the normal users list doesn't get any attention.
> >
> > Secondly, the cmake-packages portion of the cmake documentation
> > doesn't go into a ton of detail about components, but it does give an
> > example towards the bottom of how you export targets for components.
> > This leads to my questions:
> >
> > When defining the target exports via install(TARGET foo EXPORT
> > foo-export), is it recommended for all components to collectively
> > export as 1 target.cmake script? Or is it better to have 1
> > target.cmake script per component? If we use Boost as an example, the
> > latter would mean having:
> >
> > boost-config.cmake
> > boost-target-filesystem.cmake
> > boost-target-thread.cmake
> > ...etc...
> >
> > This means that boost-config.cmake would "include()" only the relevant
> > target cmake scripts based on the provided COMPONENTS list, I assume?
> >
> > Which is the better approach here and why?
> >
> > One problem I thought of with the former (one big target.cmake with
> > all import targets in there) is that if you only ask for a subset of
> > components in find_package(), you will still get all of them since all
> > imports are defined in a single file. Does this go against any design
> > principles? Assuming this really happens, are there any negative side
> > effects?
> --
>
> Powered by www.kitware.com
>
> Please keep messages on-topic and check the CMake FAQ at:
> http://www.cmake.org/Wiki/CMake_FAQ
>
> Kitware offers various services to support the CMake community. For more
> information on each offering, please visit:
>
> CMake Support: http://cmake.org/cmake/help/support.html
> CMake Consulting: http://cmake.org/cmake/help/consulting.html
> CMake Training Courses: http://cmake.org/cmake/help/training.html
>
> Visit other Kitware open-source projects at http://www.kitware.com/
> opensource/opensource.html
>
> Follow this link to subscribe/unsubscribe:
> http://public.kitware.com/mailman/listinfo/cmake-developers
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://public.kitware.com/pipermail/cmake/attachments/20170907/9f940a3a/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: cmake-export-dependencies.tar.bz2
Type: application/x-bzip2
Size: 5200 bytes
Desc: not available
URL: <http://public.kitware.com/pipermail/cmake/attachments/20170907/9f940a3a/attachment-0001.bin>


More information about the CMake mailing list