[cmake-developers] CMakeFindDependencyMacro limitations

Roger Leigh rleigh at codelibre.net
Wed Feb 24 17:49:57 EST 2016


On 24/12/2015 12:07, Stephen Kelly wrote:
> Roger Leigh wrote:
>
>> On 21/12/2015 15:07, Stephen Kelly wrote:
>>> Roger Leigh wrote:
>>>> I can understand why REQUIRED and related
>>>> arguments are omitted--that is why find_dependency exists--but I'd quite
>>>> like to be able to specify COMPONENTS where needed e.g. with FindBoost,
>>>> and this is not currently possible.
>>>
>>> I don't remember whether this was discussed when designing
>>> find_dependency. Perhaps COMPONENTS could be added to the macro now.
>>
>> Currently, for my own needs, COMPONENTS is the single missing piece.  If
>> it would be possible to add this, it would certainly be very useful.
>>
>> I'd be happy to provide a patch for COMPONENTS if you like.
>
> I think the reason that the macro does not support COMPONENTS is that I
> don't understand how COMPONENTS is supposed to work. For example:
>
>   find_package(Qt5 COMPONENTS Widgets)
>   # Is Qt5_FOUND set to true or false?
>   find_package(Qt5 COMPONENTS Quick)
>   # ... what about now?
>
> What does <Package>_FOUND ever mean if the find_package is called multiple
> times with different components? Does it mean anything?

I would think that it would mean whether the last call succeeded or 
failed for the requested components at that time.  It wouldn't have any 
greater or more permanent meaning.  To the best of my knowledge, that's 
what the status quo is for find_package.

> Should CMake disallow multiple use of find_package with the same package and
> with different components?

> If user code has
>
>   find_package(Qt5 COMPONENTS Widgets)
>   find_package(FooBar REQUIRED)
>
> and FooBarConfig.cmake has
>
>   find_dependency(Qt5 COMPONENTS Quick)
>
> then the if(Qt5_FOUND) in the macro will mean it will not try to find the
> Quick component.
>
> So, there may be a need to establish a convention such as
>
>   <Package>_<Component>_FOUND
>
> and check that for each component in the macro.

Right now, it's certainly possible for this to happen.  I'm seeing this 
myself with multiple packages (or individual components' config scripts) 
requiring different sets of Boost components.  Each invokes 
find_dependency with its own needed set of components.

> You would also need to think through everything and find out whether that is
> really a full solution. I haven't thought more deeply about it than the
> above.
>
> Note that the above example with Qt 5 can not currently happen in practice
> because the find_dependency macro does not support COMPONENTS, so config
> files must instead use
>
>   find_dependency(Qt5Quick)
>
> if they want to use the macro currently. This is a good thing.
>
> So, I would like to know if
>
> * adding COMPONENTS support to the find_dependency macro is the right thing
>    to do
> * or if the right thing to do is to move away from support for COMPONENTS
>    generally because of its messy semantics
> * or if find_package should change to not support multiple calls for the
>    same package.
>
> I really don't know the answer - I think someone needs to do deep thinking
> about it.
>
> I would prefer the macro not be changed before that deep thinking about
> COMPONENTS generally is done.

I've attached a patch for a very simple modification to find_dependency. 
  I'm not proposing that it be merged, it's just a suggestion for 
further discussion.  I've tested it with my own packages with multiple 
find_dependency(Boost COMPONENTS ...) calls.  Some rationale and 
thoughts follow:

Regarding the messy semantics of find_package, I agree it's not ideal. 
However, I find the differing behaviour of find_dependency and 
find_package even less so.  Ideally I should be able to call both 
multiple times, or neither.  While as you mentioned for Qt5, it's 
possible to work around this by calling find_dependency for each 
component by hand, but that's not possible for all packages.  Boost for 
example does not work this way, making this a major blocker to using 
find_dependency with Boost.  I'm basically blocked by this right now, 
and the patch here removes the single call restriction and passes 
through COMPONENTS, making it work.  That would at the very least make 
things consistent and functional.

I don't think it's in general going to be possible to avoid multiple 
calls.  Complex projects including or depending upon other projects are 
going to do this inevitably, and they may have differing version 
requirements as well as different sets of components.  And it's already 
in established use.  I don't think for find_dependency that returning 
immediately if ${dep}_FOUND is true is correct behaviour--it skips the 
version check, and I think it should be calling find_package to do this 
check under all circumstances.  I think it's trading correctness for 
speed, but I really would prefer to have the version check performed 
correctly every time.

Regarding having <Package>_<Component>_FOUND, I think this would be 
useful irrespective of any find_dependency changes.  And coupled with 
the above patch, find_dependency could be extended to return immediately 
if the version check is satisfied *and* all the requested components are 
found (if specified).


Kind regards,
Roger
-------------- next part --------------
>From c329228dca2b44d480d6fa889bff7ec24c77ecc8 Mon Sep 17 00:00:00 2001
From: Roger Leigh <r.leigh at dundee.ac.uk>
Date: Wed, 24 Feb 2016 22:11:14 +0000
Subject: [PATCH] find_dependency: Allow use of the find_package COMPONENTS
 option

- Parse the COMPONENTS option with cmake_parse_arguments after
  the optional <version> and EXACT arguments are parsed.  This
  was done so that additional options could be added in the
  future.
- Pass through the COMPONENTS to find_package
- Drop the "if (NOT ${dep}_FOUND)" check.  This prevented
  find_dependency from being invoked multiple times.  For
  example, different <package>Config.cmake scripts may
  call find_dependency(Boost) with different sets of
  required components.
- Update documentation.  EXACT is not a forwarded parameter.
  COMPONENTS is allowed.  Minor markup enhancements.
---
 Modules/CMakeFindDependencyMacro.cmake | 142 +++++++++++++++++++++------------
 1 file changed, 90 insertions(+), 52 deletions(-)

diff --git a/Modules/CMakeFindDependencyMacro.cmake b/Modules/CMakeFindDependencyMacro.cmake
index 73efaae..898755b 100644
--- a/Modules/CMakeFindDependencyMacro.cmake
+++ b/Modules/CMakeFindDependencyMacro.cmake
@@ -4,18 +4,26 @@
 #
 # ::
 #
-#     find_dependency(<dep> [<version> [EXACT]])
+#     find_dependency(<dep> [<version> [EXACT]] [COMPONENTS components...])
 #
 #
-# ``find_dependency()`` wraps a :command:`find_package` call for a package
-# dependency. It is designed to be used in a <package>Config.cmake file, and it
-# forwards the correct parameters for EXACT, QUIET and REQUIRED which were
-# passed to the original :command:`find_package` call.  It also sets an
-# informative diagnostic message if the dependency could not be found.
+# ``find_dependency()`` wraps a :command:`find_package` call for a
+# package dependency. It is designed to be used in a
+# :file:`<package>Config.cmake` file, and it forwards the correct
+# parameters for QUIET and REQUIRED which were passed to the original
+# :command:`find_package` call.  It also sets an informative
+# diagnostic message if the dependency could not be found.
+#
+# The allowed arguments are a subset of the :command:`find_package`
+# arguments.  ``<dep>`` is the name of the dependency (package) to
+# find.  The package version ``<version>`` (and its ``EXACT`` option),
+# and the ``COMPONENTS`` component list are forwarded to
+# :command:`find_package` unmodified.
 #
 
 #=============================================================================
 # Copyright 2013 Stephen Kelly <steveire at gmail.com>
+# Copyright 2016 Roger Leigh <rleigh at codelibre.net>
 #
 # Distributed under the OSI-approved BSD License (the "License");
 # see accompanying file Copyright.txt for details.
@@ -27,59 +35,89 @@
 # (To distribute this file outside of CMake, substitute the full
 #  License text for the above reference.)
 
+include(CMakeParseArguments)
+
 macro(find_dependency dep)
-  if (NOT ${dep}_FOUND)
-    set(cmake_fd_version)
-    if (${ARGC} GREATER 1)
-      if ("${ARGV1}" STREQUAL "")
-        message(FATAL_ERROR "Invalid arguments to find_dependency. VERSION is empty")
-      endif()
-      if ("${ARGV1}" STREQUAL EXACT)
-        message(FATAL_ERROR "Invalid arguments to find_dependency. EXACT may only be specified if a VERSION is specified")
-      endif()
-      set(cmake_fd_version ${ARGV1})
-    endif()
-    set(cmake_fd_exact_arg)
-    if(${ARGC} GREATER 2)
-      if (NOT "${ARGV2}" STREQUAL EXACT)
-        message(FATAL_ERROR "Invalid arguments to find_dependency")
-      endif()
-      set(cmake_fd_exact_arg EXACT)
-    endif()
-    if(${ARGC} GREATER 3)
-      message(FATAL_ERROR "Invalid arguments to find_dependency")
+  set(options)
+  set(oneValueArgs)
+  set(multiValueArgs COMPONENTS)
+  set(cmake_fd_option_names ${options} ${oneValueArgs} ${multiValueArgs})
+  set(cmake_fd_args ${ARGN})
+
+  set(cmake_fd_version)
+  if (${ARGC} GREATER 1)
+    if ("${ARGV1}" STREQUAL "")
+      message(FATAL_ERROR "Invalid arguments to find_dependency. VERSION is empty")
     endif()
-    set(cmake_fd_quiet_arg)
-    if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
-      set(cmake_fd_quiet_arg QUIET)
+    if ("${ARGV1}" STREQUAL EXACT)
+      message(FATAL_ERROR "Invalid arguments to find_dependency. EXACT may only be specified if a VERSION is specified")
     endif()
-    set(cmake_fd_required_arg)
-    if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
-      set(cmake_fd_required_arg REQUIRED)
+    list(FIND cmake_fd_option_names ${ARGV1} cmake_fd_option_found)
+    if (cmake_fd_option_found EQUAL -1)
+      set(cmake_fd_version ${ARGV1})
+      # Pop version
+      list(REMOVE_AT cmake_fd_args 0)
+      if (${ARGC} GREATER 2)
+        if("${ARGV2}" STREQUAL "EXACT")
+          # Pop EXACT
+          set(cmake_fd_exact_arg "EXACT")
+          list(REMOVE_AT CMAKE_FD_ARGS 0)
+        else()
+          set(cmake_fd_exact_arg)
+        endif()
+      endif()
     endif()
+    set(cmake_fd_option_found)
+  endif()
 
-    get_property(cmake_fd_alreadyTransitive GLOBAL PROPERTY
-      _CMAKE_${dep}_TRANSITIVE_DEPENDENCY
-    )
+  # Parse remaining args (if any)
+  cmake_parse_arguments(cmake_fd "${options}" "${oneValueArgs}" "${multiValueArgs}" ${cmake_fd_args})
 
-    find_package(${dep} ${cmake_fd_version}
-        ${cmake_fd_exact_arg}
-        ${cmake_fd_quiet_arg}
-        ${cmake_fd_required_arg}
-    )
+  if(cmake_fd_UNPARSED_ARGUMENTS)
+    message(FATAL_ERROR "Unknown keywords given to FIND_DEPENDENCY(): \"${cmake_fd_UNPARSED_ARGUMENTS}\"")
+  endif()
 
-    if(NOT DEFINED cmake_fd_alreadyTransitive OR cmake_fd_alreadyTransitive)
-      set_property(GLOBAL PROPERTY _CMAKE_${dep}_TRANSITIVE_DEPENDENCY TRUE)
-    endif()
+  set(cmake_fd_quiet_arg)
+  if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
+    set(cmake_fd_quiet_arg QUIET)
+  endif()
+  set(cmake_fd_required_arg)
+  if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
+    set(cmake_fd_required_arg REQUIRED)
+  endif()
+  if(cmake_fd_COMPONENTS)
+    list(INSERT cmake_fd_COMPONENTS 0 COMPONENTS)
+  endif()
 
-    if (NOT ${dep}_FOUND)
-      set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE "${CMAKE_FIND_PACKAGE_NAME} could not be found because dependency ${dep} could not be found.")
-      set(${CMAKE_FIND_PACKAGE_NAME}_FOUND False)
-      return()
-    endif()
-    set(cmake_fd_version)
-    set(cmake_fd_required_arg)
-    set(cmake_fd_quiet_arg)
-    set(cmake_fd_exact_arg)
+  get_property(cmake_fd_alreadyTransitive GLOBAL PROPERTY
+    _CMAKE_${dep}_TRANSITIVE_DEPENDENCY
+  )
+
+  find_package(${dep} ${cmake_fd_version}
+    ${cmake_fd_exact_arg}
+    ${cmake_fd_quiet_arg}
+    ${cmake_fd_required_arg}
+    ${cmake_fd_COMPONENTS}
+  )
+
+  if(NOT DEFINED cmake_fd_alreadyTransitive OR cmake_fd_alreadyTransitive)
+    set_property(GLOBAL PROPERTY _CMAKE_${dep}_TRANSITIVE_DEPENDENCY TRUE)
+  endif()
+
+  if (NOT ${dep}_FOUND)
+    set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE "${CMAKE_FIND_PACKAGE_NAME} could not be found because dependency ${dep} could not be found.")
+    set(${CMAKE_FIND_PACKAGE_NAME}_FOUND False)
+    return()
   endif()
+
+  set(cmake_fd_version)
+  set(cmake_fd_required_arg)
+  set(cmake_fd_quiet_arg)
+  set(cmake_fd_exact_arg)
+  set(cmake_fd_COMPONENTS)
+  set(options)
+  set(oneValueArgs)
+  set(multiValueArgs)
+  set(cmake_fd_option_names)
+  set(cmake_fd_args)
 endmacro()
-- 
2.7.2



More information about the cmake-developers mailing list