[cmake-developers] How to handle package Config.cmake files with dependencies ?

Michael Hertling mhertling at online.de
Wed Mar 14 21:35:19 EDT 2012


On 03/10/2012 02:10 PM, Alexander Neundorf wrote:
> Hi,
> 
> ...some comments inline, but the main point comes at the end.
> 
> On Wednesday 07 March 2012, Michael Hertling wrote:
>> On 02/27/2012 09:15 PM, Alexander Neundorf wrote:
>>> Hi,
>>>
>>> I think find_package in Config mode might still need some more work.
>>>
>>> When the FooConfig.cmake has been found, Foo_FOUND is set to TRUE:
>>>  // Set a variable marking whether the package was found.
>>>  
>>>   std::string foundVar = this->Name;
>>>   foundVar += "_FOUND";
>>>
>>> This means it is true in all cases that the Config.cmake file has been
>>> found (and the version was compatible).
>>>
>>> Now I have to questions:
>>>
>>>
>>> * how to handle COMPONENTS ?
>>>
>>>
>>> If a package is requested with COMPONENTS, these should be considered for
>>> setting Foo_FOUND:
>>> find_package(Foo REQUIRED COMPONENTS A B C)
>>> should only succeed if A, B and C are found.
>>>
>>> This is how I would expect COMPONENTS to be handled in a FindFoo.cmake:
>>> (a) all components are searched by the Find-module, and each
>>> per-component X_Y_FOUND is set accordingly
>>>
>>> (b) there is a package-specific default subset of these components which
>>> have to be found to make the package found, i.e. FOO_FOUND=TRUE
>>>
>>> (c) by adding COMPONENTS to the find_package() call, these components are
>>> added to the set of components which have to be found to make
>>> FOO_FOUND=TRUE
>>>
>>> (d) if REQUIRED is used and FOO_FOUND is false, it errors out
>>
>> Sorry for chiming in so late, but I haven't got an answer on the
>> users' mailing list, so I retry here. Some objections and remarks:
>>
>> (1) Foo might have a non-(de)selectable unnamed core component and
>> an ordinary selectable component X. FIND_PACKAGE(Foo COMPONENTS X)
>> returning Foo_FOUND==FALSE and Foo_X_FOUND!=TRUE does not allow to
>> determine if the core component is available. Potential candidates
>> for packages like that are Qt, GTK and - most notably - SDL.
> 
> It can be checked.
> E.g. for Qt there should then also be a QT_QtCore_FOUND variable (I didn't 
> check, maybe there is). I know there is a QT_QTCORE_LIBRARY variable which can 
> be checked.

The idea of an *unnamed* core component implies that there aren't
variables like Qt5_Core_FOUND. Maybe, the config file would use
an internal variable like _qt5_core_library to silently append
to Qt5_LIBRARIES at the end, but that would not be documented
or meant to be referred to from outside.

>> (2) FIND_PACKAGE(Foo COMPONENTS ...) returns Foo_FOUND==FALSE if a
>> config file has not been found - crystal. Who sets the components'
>> FOUND variables in this case? 
> 
> They just stay as they were.
> If they were not yet found before, they are still not found.
> Ok, if they were found before, one can probably construct cases why they won't 
> be found later on.

Certainly, such cases are rather exceptional, but I think it would be
good if one can trust that FIND_PACKAGE() has assigned TRUE/FALSE to
the relevant FOUND variables - or knew that it wasn't able to do so.

A possible scenario: Two different installations A and B of the same
package, search for component X from A and Y from B. The first search
might return with *_Y_FOUND==TRUE, the second might not find B at all.
Nullifying just the typical cache variables as *_Y_LIBRARY inbetween
still results in *_Y_FOUND==TRUE, i.e. a false positive.

IMO, *_FOUND et al. are pure output variables - not in or in/out - so
it's highly unexpected that the previous value influences the new one.

>> FIND_PACKAGE() on its own behalf? If
>> so, note [1] and think of the same uncertainty w.r.t. components:
> 
> Since the components are not wide spread handled yet, I think we don't have to 
> take them into consideration for this.
> The "Component" part of the _FOUND variable should be exact case, once we 
> introduce/define a standard way how to handle components. 

Absolutely, if upper case is desired, the component should be named in
upper case, and BTW, the same rule should also apply to packages, IMO.

What I actually meant: Most certainly, it'd be a bad idea to have FIND_
PACKAGE() set FOUND variables for the requested components by itself in
case of a missing config file, just to have these variables definitely
set. FIND_PACKAGE() can't know the names the config file would choose.

>> FIND_PACKAGE() on its own might return with Foo_bar_FOUND whereas
>> the config file sets Foo_Bar_FOUND. Leaving the components' FOUND
>> variables untouched? Note [2]. IMO, Brad is absolutely right *not*
>> to assume that Foo_FOUND was undefined before FIND_PACKAGE() ran.
>> So, concerning Foo_Bar_FOUND, one should also not rely on that
>> assumption, but your approach can't guarantee this, AFAICS.
>>
>> (3) Suppose a component Y is added to Foo in a later release. Run
>> against such a release, FIND_PACKAGE(Foo COMPONENTS Y) is perfect,
>> but would face an older FooConfig.cmake with an unknown component
>> Y. 
> 
> Yes, a Config file must not fail when presented an unknown component.

...but it should set the unknown component's *_*_FOUND to TRUE/FALSE.

> A normal Find-module, i.e. one which is not simply a wrapper around a 
> find_package(NO_MODULE) call, should probably simply set the found variable 
> for the unknown component to FALSE.

Exactly. IMO, the general recommendation should be: For each requested
component that is unknown, just set *_*_FOUND to FALSE, or bail out if
REQUIRED has been flagged, respectively. AFAICS, that's sufficient to
play safe in all respects - find modules as well as config files.

Besides, there're possible scenarios also for find modules that don't
know their package's components: Think of a comprehensive component-
aware FindSDL.cmake. Keeping it monolithic and up-to-date would be in-
appropriate, if not impossible, regarding SDL's ~180 add-on libraries.
Instead, it'd probably be modularized, and each (sub)module handles a
component, is maintained individually and loaded by INCLUDE() after a
FILE(GLOB ...), e.g. In this way, the components aren't hard-coded in
the find module anymore, so facing the latter with unknown components
becomes a, so to say, regular case which should be handled gracefully.

>> Thus, "all components are searched" should read "all requested
>> components are searched", the known ones are enabled, and for the
>> unknown ones, just the respective FOUND variable is set to FALSE.
>>
>> Personally, my favored approach for {FindFoo,FooConfig}.cmake is:
>>
>> - Set a FOUND variable for each requested component, known or not.
>> - Foo_FOUND==FALSE: Foo totally unavailable, don't use it in any
>>   manner. In particular, do not use any further Foo_* variables.
>> - Foo_FOUND==TRUE: Foo basically available, but no information
>>   about components. Check their availability by the respective
>>   FOUND variable, e.g. IF(Foo_FOUND AND Foo_Bar_FOUND).
>>
>> For the user, this means:
>>
>> - Request all components that will be used.
> 
> ...and check each of them whether it has been found.
> 
>> - Use only components that have been requested.
>>
>> IMO, this approach is robust, versatile and anything but difficult
>> to implement, and it does not require any change in FIND_PACKAGE().
> 
> What it does is it requires a lot of manual checking in the users 
> CMakeLists.txt (because they have to check for each requested component 
> individually).

Yes, but this is because {FindFoo,FooConfig}.cmake can not distinguish
optional from non-optional components ;) so that work must be done by
the calling CMakeLists.txt, one way or the other. It is the optional
components which make life difficult.

> How about 
> 
> find_package(Foo COMPONENTS Bar Blub OPTIONAL_COMPONENTS Zot Zat ) ?

Very good.

> COMPONENTS: Foo_FOUND is only TRUE if all listed components have been found
> 
> OPTIONAL_COMPONENTS: they are not considered for setting Foo_FOUND

Good, though it should not be forced technically but stipulated by
convention. Especially when FooConfig.cmake is allowed to set Foo_
FOUND by itself, FIND_PACKAGE() must not have the final say about
that variable, unless {FindFoo,FooConfig}.cmake can't be loaded.

> The result variables for each component will be fully ExactCase:
> Foo_Bar_FOUND etc.
> 
> The library and include dir variables from all listed and found components 
> should be added to Foo_LIBRARIES and Foo_INCLUDE_DIRS.

Should also be covered by convention only - but particularly strict. ;-)

> If any components are given, it is recommended to search only the listed 
> components. AFAICS only in corner cases bad things can happen if all 
> components are searched.

Primarily, it should be avoided that the user trustfully deploys a
component which has not been searched. I.e., the convention should
urge {FindFoo,FooConfig}.cmake authors to document if unrequested
components are searched, and users not to deploy any unrequested
components unless it's documented that they're searched.

> How does that sound ?

That sounds excellent, and IMO, it addresses exactly the actual issue:
Distinguishing optional from non-optional components in find modules
/ config files. It's this distinction - already in the FIND_PACKAGE()
invocation - that does provide precisely the bit of information the
*_FOUND variables have been lacking in the end so far.

Regards,

Michael



More information about the cmake-developers mailing list