[CMake] Apply FIND_PACKAGE_HANDLE_STANDARD_ARGS() on COMPONENTS

Michael Hertling mhertling at online.de
Thu Apr 29 11:38:04 EDT 2010


On 04/26/2010 10:07 PM, Alexander Neundorf wrote:
> On Sunday 25 April 2010, Michael Hertling wrote:
>> On 04/22/2010 09:55 PM, Alexander Neundorf wrote:
>>> On Thursday 22 April 2010, Michael Hertling wrote:
>>>> On 04/21/2010 09:13 PM, Alexander Neundorf wrote:
>>>>> On Tuesday 20 April 2010, Michael Hertling wrote:
>>>>>> Dear CMake community, dear CMake developers,
>>>>>
>>>>> ...
>>>>>
>>>>>> There's another aspect related to this I'd like to comment on: During
>>>>>> the abovementioned considerations on the bug tracker and the mailing
>>>>>> list, the question has arisen if it's reasonable to set XXX_FOUND to
>>>>>> FALSE if any of the requested components aren't found. As for myself,
>>>>>> I'd say: No, it isn't. Let's have a look at the following scenario:
>>>>>>
>>>>>> Package XXX normally provides components YY1 and YY2, but for some
>>>>>> reason, only YY1 is installed. Moreover, XXX provides a config file
>>>>>> XXXConfig.cmake. Now, a project's CMake script requests both YY1/2 by
>>>>>> FIND_PACKAGE(XXX COMPONENTS YY1 YY2). As Brad King has pointed out in
>>>>>> <http://www.mail-archive.com/cmake@cmake.org/msg15952.html>, finding a
>>>>>> config file results in XXX_FOUND to be set to TRUE automatically.
>>>>>> Thus, the absence of YY2 does not mean the absence of XXX as a whole
>>>>>> in any case, and, notwithstanding, the requesting CMake script should
>>>>>> have a chance to proceed even if YY2 isn't available, i.e. the
>>>>>> following seems reasonable: XXX_YY1_FOUND=TRUE, XXX_YY2_FOUND=FALSE
>>>>>> *but*
>>>>>> XXX_FOUND=TRUE.
>>>>>
>>>>> I think I don't agree here.
>>>>> If I say
>>>>> find_package(XXX COMPONENTS YY1 YY2 REQUIRED)
>>>>> I think it makes a lot of sense to interpret this as "search package
>>>>> XXX, and I need YY1 and YY2 from it".
>>>>
>>>> OK, the presence of the REQUIRED option makes everything clear: A find
>>>> module or a config file bails out if anything is missing - no worry
>>>> about the final value of XXX_FOUND. The actual questions arise if
>>>> REQUIRED is absent.
>>>>
>>>>> What other reason would I have to give YY1 and YY2 there otherwise ?
>>>>
>>>> Perhaps, because YY2 is optional and I do not want FIND_PACKAGE() to
>>>> look for unrequested components, but that's merely my personal habit.
>>>>
>>>>> If it still succeeds if they are not found, why should I list them then
>>>>> ?
>>>>
>>>> It is possible for FIND_PACKAGE() - w/o REQUIRED - to succeed although
>>>> listed components aren't found, and *this* is what I'm worrying about.
>>>> As Brad King has pointed out in the abovementioned post, FIND_PACKAGE()
>>>> in config mode sets XXX_FOUND to TRUE if the config file is found and
>>>> processed without errors, and there's nothing one can do against it -
>>>
>>> Well, find_package() could be enhanced to set this considering the
>>> specified COMPONENTS. [...]
>>
>> I'm in doubt if this is the right way, and you, Alex, once have been in
>> doubt, too: <http://www.cmake.org/Bug/view.php?id=5920>, Note #0010405:
>> "[...] this means FOO_FOUND is only set to TRUE if all components have
>> been found, right ? Is this the expected behaviour ? I'm not sure."
> 
> Yes, I know. And I'm still not sure enough to just go ahead and commit 
> something.
> It's good that we have that discussion now.

Yes, I appreciate it, too, and hopefully, we can advance this issue.

>> I'm not, too: For a single-component package, you've an all-or-nothing
>> situation which can be perfectly summed up by a sole boolean variable.
>> For a multi-component package, you've the states "all", "nothing" and
>> intermediate "partials", and a boolean variable is, IMHO, neither
>> sufficient nor appropriate to comprise all possible outcomes of
>> FIND_PACKAGE() in such a case. Therefore, the interpretation of
>> XXX_FOUND should be restricted, roughly like the following:
>>
>> FALSE: Nothing of XXX has been found at all - don't try to use it; in
>> particular, don't rely on any other variable related to XXX to have a
>> defined value. [The latter is config mode not finding a config file.]
>> TRUE: Basically, XXX has been found, but for any information about the
>> available components, refer to their specific variables, in particular
>> XXX_YY_FOUND, as provided by the find module or the config file.
> 
> But I could check XXX_YY_FOUND also without specifying COMPONENTS. That's what 
> FindQt4.cmake does.

Yes, that's true because FindQt4.cmake, as far as I can see, searches
all of Qt4's components and sets up QT_QTXXX_FOUND, QT_QTXXX_LIBRARY
etc. accordingly, regardless whether and which components have been
requested in FIND_PACKAGE(). But note: In order to use a component,
you must request it explicitly; otherwise, you only get QtCore and
QtGui. As I have suggested earlier in this thread, the decision if
unrequested components are searched automatically or not should be
up to the module's designer, but, as I've suggested, too, it should
also be clearly documented. Here, my actual concern is to emphasize
to check XXX_FOUND in either case as XXX_YY_FOUND may not have been
touched at all. Indeed, one can construct a somewhat unusal but not
completely pathological example which ends up with XXX_FOUND=FALSE
and XXX_YY_FOUND=TRUE. Look at the following CMakeLists.txt,

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
FIND_PACKAGE(XXX PATHS <path/to/special/XXX> COMPONENTS YY)
MESSAGE("XXX_FOUND: ${XXX_FOUND}")
MESSAGE("XXX_YY_FOUND: ${XXX_YY_FOUND}")
UNSET(XXX_DIR CACHE)  # Don't lead others to <path/to/special/XXX>.
ADD_SUBDIRECTORY(subdir)

and subdir/CMakeLists.txt:

FIND_PACKAGE(XXX COMPONENTS YY)
MESSAGE("XXX_FOUND from subdir: ${XXX_FOUND}")
MESSAGE("XXX_YY_FOUND from subdir: ${XXX_YY_FOUND}")
IF(XXX_YY_FOUND AND NOT XXX_FOUND)
    MESSAGE("Ouch! XXX_YY_FOUND is telling lies.")
ENDIF()

The fact of unsetting XXX_DIR in the cache to prevent a subsequent
FIND_PACKAGE() from finding the XXX special installation anew makes
FIND_PACKAGE() to search again, and if the XXX normal installation
isn't found this time XXX_YY_FOUND retains its previous value:

[...]
XXX_FOUND: 1
XXX_YY_FOUND: TRUE
CMake Warning at subdir/CMakeLists.txt:1 (FIND_PACKAGE):
  Could not find [...]
[...]
XXX_FOUND from subdir: 0
XXX_YY_FOUND from subdir: TRUE
Ouch! XXX_YY_FOUND is telling lies.
[...]

Consequently, the sole bullet-proof way to check the presence of a
component is, IMHO, "IF(XXX_FOUND AND XXX_YY_FOUND)...ENDIF()", and
even this should be accompanied by explicitly requesting YY at the
FIND_PACKAGE() as long as XXX doesn't clearly document whether it
searches for components automatically. Of course, this does not
affect the issue of how to set up and interpret XXX_FOUND.

>> This interpretation is compliant to the behaviour of FIND_PACKAGE() in
>> config mode and preserves the possibility to reliably request optional
>> components from a package as I've pointed out in this thread recently.
>> Obviously, it implies that XXX_FOUND could be TRUE although not all
>> requested components have been found.
> 
> This also means that if REQUIRED is specified, the find_package() would not 
> fail even if some of the listed components have not been found ?
> Or, in other words, what you write about XXX_FOUND, does this apply the same 
> way to the REQUIRED argument ?

No, I wouldn't say so. Indeed, it seems to be somewhat contradictory if
FIND_PACKAGE(XXX COMPONENTS YY ...) may succeed with XXX_FOUND=TRUE and
XXX_YY_FOUND=FALSE while FIND_PACKAGE(XXX REQUIRED YY ...) should fail
due to the same findings, but this is, IMHO, exactly the intention of
the REQUIRED option: To be rigorous w.r.t. the requested components and
the package as a whole. If you use REQUIRED you would expect the package
along with all requested components to be available after FIND_PACKAGE()
returns, and accordingly, if it's nevertheless necessary to check their
availability the REQUIRED option would be of no use.

Thus, I'd amend the above-mentioned interpretation of FIND_PACKAGE()'s
results as follows: If the REQUIRED option is specified, the package
and all requested components must be present; if the package or any
of the requested components is missing FIND_PACKAGE(), i.e. the find
module or the config file, terminates the configuration process. This
just means the extension of the official recommendations for packages
to their components which is, in turn, precisely implemented by the
proposed function FIND_COMPONENT_HANDLE_STANDARD_ARGS().

>>> [..] There hasn't been much discussion about how the COMPONENTS
>>> arguments should be interpreted until now.
>>
>> OK, let me try to contribute. From a module designer's perspective,
>> things are quite clear: XXX_FIND_QUIETLY and XXX_FIND_REQUIRED get the
>> QUIET and REQUIRED options, and components go into XXX_FIND_COMPONENTS.
>> What we'd need is a guideline how the results of FIND_PACKAGE() should
>> be set up and interpreted when components come into play. My suggestion:
>>
>> 1) Recommend to interpret XXX_FOUND in then above-mentioned manner.
>> 2) Urge the module designers to document which components can be
>>    requested with FIND_PACKAGE() and if they must be requested
>>    explicitly or if they are searched automatically.
>> 3) To support the handling of components, provide a function like:
>>
>> FUNCTION(FIND_COMPONENT_HANDLE_STANDARD_ARGS PKG CMPNT MSG VAR0)
>>     # Prepare forwarding variables:
>>     SET(${PKG}_${CMPNT}_FIND_QUIETLY ${${PKG}_FIND_QUIETLY})
>>     SET(${PKG}_${CMPNT}_FIND_REQUIRED ${${PKG}_FIND_REQUIRED})
>>     # Delegate the actual work:
>>     FIND_PACKAGE_HANDLE_STANDARD_ARGS(
>>         ${PKG}_${CMPNT} ${MSG} ${VAR0} ${ARGN}
>>     )
>>     # Propagate the result:
>>     SET(${PKG}_${CMPNT}_FOUND ${${PKG}_${CMPNT}_FOUND} PARENT_SCOPE)
>> ENDFUNCTION()
>>
>> This wrapper around FIND_PACKAGE_HANDLE_STANDARD_ARGS() handles a
>> single component in the same way the latter handles a package; in
>> particular, it respects the package's QUIET and REQUIRED options,
>> and finally, it sets up the component's XXX_YY_FOUND variable as
>> demanded in Modules/readme.txt for a package XXX's "part" YY.
>>
>> 4) Slightly edit Modules/readme.txt to reflect the changes.
>>
>> Further measures, e.g. concerning FIND_PACKAGE(), aren't needed.
>>
>> Of course, I would be especially interested in your opinion, Alex,
>> since the related issue #5920 is still open and marked as urgent.
> 
> I was actually almost about to close this issue with "no change required", see 
> my last comment there. So, I think that this issue currently has "Urgent" 
> does not matter much.

As I've written earlier, there are indeed hardly changes to consider;
in particular, FIND_PACKAGE() behaves reasonably and provides a sane
environment for find modules or config files, perhaps except for the
somewhat misnamed XXX_FIND_REQUIRED_YY. The actual need of change is,
IMHO, situated at the Modules/readme.txt and, to a lesser extend, at
FIND_PACKAGE()'s documentation w.r.t. the handling of components. A
supporting function like FIND_COMPONENT_HANDLE_STANDARD_ARGS() could
possibly be added to FindPackageHandleStandardArgs.cmake or placed
in its own module without harming backward compatibility.

Best regards,

Michael


More information about the CMake mailing list