Validating components in config file

Hi,

in the Professional CMake Book (18th edition, p. 535) there is a blueprint for finding components:

# Check all required components are available before trying to load any
foreach(comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_comps)
    if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_${comp} AND
        NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/MyProj_${comp}.cmake)
        set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE
        "MyProj missing required component: ${comp}")
        set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)
        return()
    endif()
endforeach()

foreach(comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_comps)
    # All required components are known to exist. The OPTIONAL keyword
    # allows the non-required components to be missing without error.
    include(${CMAKE_CURRENT_LIST_DIR}/MyProj_${comp}.cmake OPTIONAL)
endforeach()

This seems to call the cmake files (MyProj_${comp}.cmake) that have been generated by the export command. In my case each component has a config-file (that contains find_dependency stuff etc.) - would it also be possible to call these files here, i.e. use MyProj_${comp}Config.cmake instead? What are the differences between using the one and the other?

Is there a difference between adding a component by calling include with its config-file vs. using the whole find_package-mechanism? As I understand it, find_package provides the functionality to look in various places and setting variables, before calling include on the config-file - which is something I don’t need here, because at this point, I know the folder structure and where the config-files should be. But what happens if there is an error in the config-file, does include return the same error messages/flags as find_package?

Also, how do I forward the QUIET flag to underlying components - do I need to set a variable ${comp}_QUIET for this, if I use include?

In particular would there be a difference between the version in the book and a solution using find_package, something like this:

# Check all required components are available before trying to load any
foreach(comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_comps)
    if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_${comp})
        set(REQUIRED_FLAG "REQUIRED")
    else()
        set(REQUIRED_FLAG "")
    endif()

    find_package(MyProj_${comp} ${REQUIRED_FLAG})
endforeach()

Thanks and best regards
oz

Yet another related question. Before the validation of the components there is a step, in which
it is figured out, which components to load:

# Work out the set of components to load
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
    set(comps ${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS})
    # Ensure Runtime is included if Development was given
    if(Development IN_LIST comps AND NOT Runtime IN_LIST comps)
        list(APPEND comps Runtime)
    endif()
else()
    # No components given, look for all components
    set(comps Runtime Development)
endif()

I do see the point in setting all components, if none had been selected, but I have the feeling that this logic is a bit cumbersome to maintain, if there are many components. I would like to include the required components of each component in the corresponding config-file.
In this example, the component Development apparently requires Runtime, therefore, I would add to the DevelopmentConfig.cmake something like

find_package(MyProj REQUIRED COMPONENT Runtime)

I do have the feeling that this could result in circular dependencies - but wouldn’t that reflect the situation on target-level? I think to remember that circular dependencies don’t cause problems for static libraries - so this otherwise valid use case would be prohibited by this construction.

Thanks again,
oz

Please start a new thread for each new question. It is much easier to respond that way, and it is also more likely people will reply. You can put a link to an earlier question in your new question’s initial post if you think it provides useful context.

It sounds like you’re treating components as distinct packages. That’s not ideal, components are really meant to be coherent parts of the package they are components of, not separate packages in their own right. There are packages that do this (Qt being one of the more well-known examples), but ultimately the components usually can’t be treated as standalone, isolated packages all that well.

Trying to capture dependencies within the component-specific files will be difficult, precisely because of the issue you’ve highlighted. There is relevant discussion about this very problem for the new automatic dependency tracking that was recently added behind an experimental flag (see the EXPORT_PACKAGE_DEPENDENCIES keyword for the install() and export() commands).

As things stand, you essentially have two main choices:

  • Only put find_dependency() calls in the top level <packageName>Config.cmake file. This means your top level file needs to understand the dependencies of each component and ask for the relevant dependencies before trying to add a component. This may be suitable for a small number of components and dependencies where the logic is simple enough to do this way.
  • After trying to add a component, test some variable or existence of a relevant target to work out whether adding that component was successful, and if it isn’t, return early if the component was a required component. The main drawback to this approach is that you can end up with a partially created package, having some components exist and not others. That’s a problem for consumers if they want to try calling find_package() multiple times with different arguments to try to find packages in different places (this is more common than you might think).

Personally, I’d try to go with the first option above if you can. You really want to try to avoid adding any new targets if ultimately the package is going to fail to be found due to any missing required components. You can’t do that with the second option’s approach.

Dear Craig,

thanks for the quick reply. I will see if I can manage to collect all dependencies in the top-level config-file.

Best regards,
oz