From e4b7049230afabd7f2cc0186f26176b103682100 Mon Sep 17 00:00:00 2001
From: Marc Chevrier <marc.chevrier@gmail.com>
Date: Mon, 21 Sep 2020 19:31:43 +0200
Subject: FindPython: Add version range support

Fixes: #21107
---
 Help/release/dev/FindPython-version_range.rst |   5 +
 Modules/FindPython.cmake                      |  52 +++-
 Modules/FindPython/Support.cmake              | 409 +++++++++++++++++---------
 Modules/FindPython2.cmake                     |   4 +
 Modules/FindPython3.cmake                     |   4 +
 Tests/FindPython/CMakeLists.txt               | 104 +++++++
 Tests/FindPython/VersionRange/CMakeLists.txt  |  55 ++++
 7 files changed, 491 insertions(+), 142 deletions(-)
 create mode 100644 Help/release/dev/FindPython-version_range.rst
 create mode 100644 Tests/FindPython/VersionRange/CMakeLists.txt

diff --git a/Help/release/dev/FindPython-version_range.rst b/Help/release/dev/FindPython-version_range.rst
new file mode 100644
index 0000000..06318b4
--- /dev/null
+++ b/Help/release/dev/FindPython-version_range.rst
@@ -0,0 +1,5 @@
+FindPython-version_range
+------------------------
+
+* The :module:`FindPython3`, :module:`FindPython2` and :module:`FindPython`
+  modules gained the capability to manage a version range.
diff --git a/Modules/FindPython.cmake b/Modules/FindPython.cmake
index 584f64d..2d13f48 100644
--- a/Modules/FindPython.cmake
+++ b/Modules/FindPython.cmake
@@ -10,6 +10,10 @@ FindPython
 Find Python interpreter, compiler and development environment (include
 directories and libraries).
 
+When a version is requested, it can be specified as a simple value or as a
+range. For a detailed description of version range usage and capabilities,
+refer to the :command:`find_package` command.
+
 The following components are supported:
 
 * ``Interpreter``: search for Python interpreter.
@@ -387,13 +391,39 @@ module suffix will include the ``Python_SOABI`` value, if any.
 #]=======================================================================]
 
 
-set (_PYTHON_PREFIX Python)
+cmake_policy(PUSH)
+# numbers and boolean constants
+cmake_policy (SET CMP0012 NEW)
 
-if (DEFINED Python_FIND_VERSION)
+
+set (_PYTHON_PREFIX Python)
+unset (_Python_REQUIRED_VERSION_MAJOR)
+unset (_Python_REQUIRED_VERSIONS)
+
+if (Python_FIND_VERSION_RANGE)
+  # compute list of major versions
+  foreach (_Python_MAJOR IN ITEMS 3 2)
+    if (_Python_MAJOR VERSION_GREATER_EQUAL Python_FIND_VERSION_MIN_MAJOR
+        AND ((Python_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND _Python_MAJOR VERSION_LESS_EQUAL Python_FIND_VERSION_MAX)
+        OR (Python_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND _Python_MAJOR VERSION_LESS Python_FIND_VERSION_MAX)))
+      list (APPEND _Python_REQUIRED_VERSIONS ${_Python_MAJOR})
+    endif()
+  endforeach()
+  list (LENGTH _Python_REQUIRED_VERSIONS _Python_VERSION_COUNT)
+  if (_Python_VERSION_COUNT EQUAL 0)
+    unset (_Python_REQUIRED_VERSIONS)
+  elseif (_Python_VERSION_COUNT EQUAL 1)
+    set (_Python_REQUIRED_VERSION_MAJOR ${_Python_REQUIRED_VERSIONS})
+  endif()
+elseif (DEFINED Python_FIND_VERSION)
   set (_Python_REQUIRED_VERSION_MAJOR ${Python_FIND_VERSION_MAJOR})
+else()
+  set (_Python_REQUIRED_VERSIONS 3 2)
+endif()
 
+if (_Python_REQUIRED_VERSION_MAJOR)
   include (${CMAKE_CURRENT_LIST_DIR}/FindPython/Support.cmake)
-else()
+elseif (_Python_REQUIRED_VERSIONS)
   # iterate over versions in quiet and NOT required modes to avoid multiple
   # "Found" messages and prematurally failure.
   set (_Python_QUIETLY ${Python_FIND_QUIETLY})
@@ -401,7 +431,6 @@ else()
   set (Python_FIND_QUIETLY TRUE)
   set (Python_FIND_REQUIRED FALSE)
 
-  set (_Python_REQUIRED_VERSIONS 3 2)
   set (_Python_REQUIRED_VERSION_LAST 2)
 
   unset (_Python_INPUT_VARS)
@@ -435,10 +464,21 @@ else()
   set (Python_FIND_REQUIRED ${_Python_REQUIRED})
   if (Python_FIND_REQUIRED OR NOT Python_FIND_QUIETLY)
     # call again validation command to get "Found" or error message
-    find_package_handle_standard_args (Python HANDLE_COMPONENTS
+    find_package_handle_standard_args (Python HANDLE_COMPONENTS HANDLE_VERSION_RANGE
                                               REQUIRED_VARS ${_Python_REQUIRED_VARS}
                                               VERSION_VAR Python_VERSION)
   endif()
+else()
+  # supported versions not in the specified range. Call final check
+  if (NOT Python_FIND_COMPONENTS)
+    set (Python_FIND_COMPONENTS Interpreter)
+    set (Python_FIND_REQUIRED_Interpreter TRUE)
+  endif()
+
+  include (${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
+  find_package_handle_standard_args (Python HANDLE_COMPONENTS HANDLE_VERSION_RANGE
+                                            VERSION_VAR Python_VERSION
+                                            REASON_FAILURE_MESSAGE "Version range specified \"${Python_FIND_VERSION_RANGE}\" does not include supported versions")
 endif()
 
 if (COMMAND __Python_add_library)
@@ -448,3 +488,5 @@ if (COMMAND __Python_add_library)
 endif()
 
 unset (_PYTHON_PREFIX)
+
+cmake_policy(POP)
diff --git a/Modules/FindPython/Support.cmake b/Modules/FindPython/Support.cmake
index c8225c4..41b55ee 100644
--- a/Modules/FindPython/Support.cmake
+++ b/Modules/FindPython/Support.cmake
@@ -31,6 +31,7 @@ endif()
 
 get_property(_${_PYTHON_PREFIX}_CMAKE_ROLE GLOBAL PROPERTY CMAKE_ROLE)
 
+include (${CMAKE_CURRENT_LIST_DIR}/../FindPackageHandleStandardArgs.cmake)
 
 #
 # helper commands
@@ -674,12 +675,7 @@ function (_PYTHON_VALIDATE_INTERPRETER)
     return()
   endif()
 
-  cmake_parse_arguments (PARSE_ARGV 0 _PVI "EXACT;CHECK_EXISTS" "" "")
-  if (_PVI_UNPARSED_ARGUMENTS)
-    set (expected_version "${_PVI_UNPARSED_ARGUMENTS}")
-  else()
-    unset (expected_version)
-  endif()
+  cmake_parse_arguments (PARSE_ARGV 0 _PVI "IN_RANGE;EXACT;CHECK_EXISTS" "VERSION" "")
 
   if (_PVI_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_EXECUTABLE}")
     # interpreter does not exist anymore
@@ -710,50 +706,69 @@ function (_PYTHON_VALIDATE_INTERPRETER)
     endif()
   endif()
 
-  get_filename_component (python_name "${_${_PYTHON_PREFIX}_EXECUTABLE}" NAME)
+  if (_PVI_IN_RANGE OR _PVI_VERSION)
+    # retrieve full version
+    execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
+                             "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))"
+                     RESULT_VARIABLE result
+                     OUTPUT_VARIABLE version
+                     ERROR_QUIET
+                     OUTPUT_STRIP_TRAILING_WHITESPACE)
+    if (result)
+      # interpreter is not usable
+      set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE)
+      set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND")
+      return()
+    endif()
 
-  if (expected_version)
-    if (NOT python_name STREQUAL "python${expected_version}${abi}${CMAKE_EXECUTABLE_SUFFIX}")
-      # compute number of components for version
-      string (REGEX REPLACE "[^.]" "" dots "${expected_version}")
-      # add one dot because there is one dot less than there are components
+    if (_PVI_VERSION)
+      # check against specified version
+      ## compute number of components for version
+      string (REGEX REPLACE "[^.]" "" dots "${_PVI_VERSION}")
+      ## add one dot because there is one dot less than there are components
       string (LENGTH "${dots}." count)
       if (count GREATER 3)
         set (count 3)
       endif()
+      set (version_regex "^[0-9]+")
+      if (count EQUAL 3)
+        string (APPEND version_regex "\\.[0-9]+\\.[0-9]+")
+      elseif (count EQUAL 2)
+        string (APPEND version_regex "\\.[0-9]+")
+      endif()
+      # extract needed range
+      string (REGEX MATCH "${version_regex}" version "${version}")
 
-      # executable found must have a specific version
-      execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
-                               "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:${count}]]))"
-                       RESULT_VARIABLE result
-                       OUTPUT_VARIABLE version
-                       ERROR_QUIET
-                       OUTPUT_STRIP_TRAILING_WHITESPACE)
-      if (result)
-        # interpreter is not usable
-        set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE)
+      if (_PVI_EXACT AND NOT version VERSION_EQUAL _PVI_VERSION)
+        # interpreter has wrong version
+        set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE)
         set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND")
+        return()
       else()
-        if (_PVI_EXACT AND NOT version VERSION_EQUAL expected_version)
-          # interpreter has wrong version
+        # check that version is OK
+        string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" major_version "${version}")
+        string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" expected_major_version "${_PVI_VERSION}")
+        if (NOT major_version VERSION_EQUAL expected_major_version
+            OR NOT version VERSION_GREATER_EQUAL _PVI_VERSION)
           set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE)
           set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND")
-        else()
-          # check that version is OK
-          string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" major_version "${version}")
-          string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" expected_major_version "${expected_version}")
-          if (NOT major_version VERSION_EQUAL expected_major_version
-              OR NOT version VERSION_GREATER_EQUAL expected_version)
-            set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE)
-            set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND")
-          endif()
+          return()
         endif()
       endif()
-      if (NOT _${_PYTHON_PREFIX}_EXECUTABLE)
+    endif()
+
+    if (_PVI_IN_RANGE)
+      # check if version is in the requested range
+      find_package_check_version ("${version}" in_range HANDLE_VERSION_RANGE)
+      if (NOT in_range)
+        # interpreter has invalid version
+        set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE)
+        set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND")
         return()
       endif()
     endif()
   else()
+    get_filename_component (python_name "${_${_PYTHON_PREFIX}_EXECUTABLE}" NAME)
     if (NOT python_name STREQUAL "python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}${CMAKE_EXECUTABLE_SUFFIX}")
       # executable found do not have version in name
       # ensure major version is OK
@@ -805,15 +820,7 @@ function (_PYTHON_VALIDATE_COMPILER)
     return()
   endif()
 
-  cmake_parse_arguments (PARSE_ARGV 0 _PVC "EXACT;CHECK_EXISTS" "" "")
-  if (_PVC_UNPARSED_ARGUMENTS)
-    set (major_version FALSE)
-    set (expected_version "${_PVC_UNPARSED_ARGUMENTS}")
-  else()
-    set (major_version TRUE)
-    set (expected_version "${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}")
-    set (_PVC_EXACT TRUE)
-  endif()
+  cmake_parse_arguments (PARSE_ARGV 0 _PVC "IN_RANGE;EXACT;CHECK_EXISTS" "VERSION" "")
 
   if (_PVC_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_COMPILER}")
     # Compiler does not exist anymore
@@ -826,19 +833,7 @@ function (_PYTHON_VALIDATE_COMPILER)
 
   # retrieve python environment version from compiler
   set (working_dir "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/PythonCompilerVersion.dir")
-  if (major_version)
-    # check only major version
-    file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write(str(sys.version_info[0]))")
-  else()
-    # compute number of components for version
-    string (REGEX REPLACE "[^.]" "" dots "${expected_version}")
-    # add one dot because there is one dot less than there are components
-    string (LENGTH "${dots}." count)
-    if (count GREATER 3)
-      set (count 3)
-    endif()
-    file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:${count}]]))\n")
-  endif()
+  file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))\n")
   execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_COMPILER}"
                            ${_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS}
                            /target:exe /embed "${working_dir}/version.py"
@@ -858,11 +853,64 @@ function (_PYTHON_VALIDATE_COMPILER)
     # compiler is not usable
     set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot use the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE)
     set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND")
-  elseif ((_PVC_EXACT AND NOT version VERSION_EQUAL expected_version)
-          OR NOT version VERSION_GREATER_EQUAL expected_version)
-    # Compiler has wrong version
-    set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE)
-    set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND")
+    return()
+  endif()
+
+  if (_PVC_VERSION OR _PVC_IN_RANGE)
+    if (_PVC_VERSION)
+      # check against specified version
+      ## compute number of components for version
+      string (REGEX REPLACE "[^.]" "" dots "${_PVC_VERSION}")
+      ## add one dot because there is one dot less than there are components
+      string (LENGTH "${dots}." count)
+      if (count GREATER 3)
+        set (count 3)
+      endif()
+      set (version_regex "^[0-9]+")
+      if (count EQUAL 3)
+        string (APPEND version_regex "\\.[0-9]+\\.[0-9]+")
+      elseif (count EQUAL 2)
+        string (APPEND version_regex "\\.[0-9]+")
+      endif()
+      # extract needed range
+      string (REGEX MATCH "${version_regex}" version "${version}")
+
+      if (_PVC_EXACT AND NOT version VERSION_EQUAL _PVC_VERSION)
+        # interpreter has wrong version
+        set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE)
+        set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND")
+        return()
+      else()
+        # check that version is OK
+        string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" major_version "${version}")
+        string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" expected_major_version "${_PVC_VERSION}")
+        if (NOT major_version VERSION_EQUAL expected_major_version
+            OR NOT version VERSION_GREATER_EQUAL _PVC_VERSION)
+          set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE)
+          set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND")
+          return()
+        endif()
+      endif()
+    endif()
+
+    if (_PVC_IN_RANGE)
+      # check if version is in the requested range
+      find_package_check_version ("${version}" in_range HANDLE_VERSION_RANGE)
+      if (NOT in_range)
+        # interpreter has invalid version
+        set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE)
+        set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND")
+        return()
+      endif()
+    endif()
+  else()
+    string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" major_version "${version}")
+    if (NOT major_version EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR)
+      # Compiler has wrong major version
+      set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong major version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE)
+      set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND")
+      return()
+    endif()
   endif()
 endfunction()
 
@@ -873,12 +921,7 @@ function (_PYTHON_VALIDATE_LIBRARY)
     return()
   endif()
 
-  cmake_parse_arguments (PARSE_ARGV 0 _PVL "EXACT;CHECK_EXISTS" "" "")
-  if (_PVL_UNPARSED_ARGUMENTS)
-    set (expected_version ${_PVL_UNPARSED_ARGUMENTS})
-  else()
-    unset (expected_version)
-  endif()
+  cmake_parse_arguments (PARSE_ARGV 0 _PVL "IN_RANGE;EXACT;CHECK_EXISTS" "VERSION" "")
 
   if (_PVL_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}")
     # library does not exist anymore
@@ -899,13 +942,25 @@ function (_PYTHON_VALIDATE_LIBRARY)
     set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong ABI for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE)
     set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND")
   else()
-    if (expected_version)
-      # library have only major.minor information
-      string (REGEX MATCH "[0-9](\\.[0-9]+)?" version "${expected_version}")
-      if ((_PVL_EXACT AND NOT lib_VERSION VERSION_EQUAL version) OR (lib_VERSION VERSION_LESS version))
-        # library has wrong version
-        set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE)
-        set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND")
+    if (_PVL_VERSION OR _PVL_IN_RANGE)
+      if (_PVL_VERSION)
+        # library have only major.minor information
+        string (REGEX MATCH "[0-9](\\.[0-9]+)?" version "${_PVL_VERSION}")
+        if ((_PVL_EXACT AND NOT lib_VERSION VERSION_EQUAL version) OR (lib_VERSION VERSION_LESS version))
+          # library has wrong version
+          set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE)
+          set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND")
+        endif()
+      endif()
+
+      if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE AND _PVL_IN_RANGE)
+        # check if library version is in the requested range
+        find_package_check_version ("${lib_VERSION}" in_range HANDLE_VERSION_RANGE)
+        if (NOT in_range)
+          # library has wrong version
+          set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE)
+          set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND")
+        endif()
       endif()
     else()
       if (NOT lib_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR)
@@ -932,12 +987,7 @@ function (_PYTHON_VALIDATE_INCLUDE_DIR)
     return()
   endif()
 
-  cmake_parse_arguments (PARSE_ARGV 0 _PVID "EXACT;CHECK_EXISTS" "" "")
-  if (_PVID_UNPARSED_ARGUMENTS)
-    set (expected_version ${_PVID_UNPARSED_ARGUMENTS})
-  else()
-    unset (expected_version)
-  endif()
+  cmake_parse_arguments (PARSE_ARGV 0 _PVID "IN_RANGE;EXACT;CHECK_EXISTS" "VERSION" "")
 
   if (_PVID_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}")
     # include file does not exist anymore
@@ -954,11 +1004,23 @@ function (_PYTHON_VALIDATE_INCLUDE_DIR)
     set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong ABI for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE)
     set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND")
   else()
-    if (expected_version)
-      if ((_PVID_EXACT AND NOT inc_VERSION VERSION_EQUAL expected_version) OR (inc_VERSION VERSION_LESS expected_version))
-        # include dir has wrong version
-        set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE)
-        set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND")
+    if (_PVID_VERSION OR _PVID_IN_RANGE)
+      if (_PVID_VERSION)
+        if ((_PVID_EXACT AND NOT inc_VERSION VERSION_EQUAL expected_version) OR (inc_VERSION VERSION_LESS expected_version))
+          # include dir has wrong version
+          set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE)
+          set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND")
+        endif()
+      endif()
+
+      if (_${_PYTHON_PREFIX}_INCLUDE_DIR AND PVID_IN_RANGE)
+        # check if include dir is in the request range
+        find_package_check_version ("${inc_VERSION}" in_range HANDLE_VERSION_RANGE)
+        if (NOT in_range)
+          # include dir has wrong version
+          set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE)
+          set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND")
+        endif()
       endif()
     else()
       if (NOT inc_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR)
@@ -1020,13 +1082,27 @@ function (_PYTHON_SET_DEVELOPMENT_MODULE_FOUND module)
 endfunction()
 
 
-# If major version is specified, it must be the same as internal major version
-if (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR
-    AND NOT ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR)
-  _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Wrong major version specified is \"${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}\", but expected major version is \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"")
+if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+  # range must include internal major version
+  if (${_PYTHON_PREFIX}_FIND_VERSION_MIN_MAJOR VERSION_GREATER _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR
+      OR ((${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE"
+          AND ${_PYTHON_PREFIX}_FIND_VERSION_MAX VERSION_LESS _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR)
+        OR (${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE"
+          AND ${_PYTHON_PREFIX}_FIND_VERSION_MAX VERSION_LESS_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR)))
+    _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Wrong version range specified is \"${${_PYTHON_PREFIX}_FIND_VERSION_RANGE}\", but expected version range must include major version \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"")
 
-  cmake_policy(POP)
-  return()
+    cmake_policy(POP)
+    return()
+  endif()
+else()
+  if (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR
+      AND NOT ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR)
+    # If major version is specified, it must be the same as internal major version
+    _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Wrong major version specified is \"${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}\", but expected major version is \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"")
+
+    cmake_policy(POP)
+    return()
+  endif()
 endif()
 
 
@@ -1070,18 +1146,32 @@ list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS)
 set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSIONS})
 unset (_${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
 
-if (${_PYTHON_PREFIX}_FIND_VERSION_COUNT)
-  if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
-    set (_${_PYTHON_PREFIX}_FIND_VERSION_EXACT "EXACT")
-    set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR})
-  else()
-    unset (_${_PYTHON_PREFIX}_FIND_VERSIONS)
-    # add all compatible versions
-    foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_VERSIONS)
-      if (_${_PYTHON_PREFIX}_VERSION VERSION_GREATER_EQUAL "${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR}")
-        list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION})
-      endif()
-    endforeach()
+if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+  unset (_${_PYTHON_PREFIX}_FIND_VERSIONS)
+  foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_VERSIONS)
+    if ((${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE"
+          AND _${_PYTHON_PREFIX}_VERSION VERSION_GREATER_EQUAL ${_PYTHON_PREFIX}_FIND_VERSION_MIN)
+        AND ((${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE"
+            AND _${_PYTHON_PREFIX}_VERSION VERSION_LESS_EQUAL ${_PYTHON_PREFIX}_FIND_VERSION_MAX)
+          OR (${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE"
+            AND _${_PYTHON_PREFIX}_VERSION VERSION_LESS ${_PYTHON_PREFIX}_FIND_VERSION_MAX)))
+      list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION})
+    endif()
+  endforeach()
+else()
+  if (${_PYTHON_PREFIX}_FIND_VERSION_COUNT GREATER 1)
+    if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
+      set (_${_PYTHON_PREFIX}_FIND_VERSION_EXACT "EXACT")
+      set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR})
+    else()
+      unset (_${_PYTHON_PREFIX}_FIND_VERSIONS)
+      # add all compatible versions
+      foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_VERSIONS)
+        if (_${_PYTHON_PREFIX}_VERSION VERSION_GREATER_EQUAL "${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR}")
+          list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION})
+        endif()
+      endforeach()
+    endif()
   endif()
 endif()
 
@@ -1300,14 +1390,26 @@ function (_PYTHON_CHECK_DEVELOPMENT_SIGNATURE module)
     string (MD5 signature "${signature}")
     if (signature STREQUAL _${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE)
       if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS)
-        _python_validate_library (${${_PYTHON_PREFIX}_FIND_VERSION}
-                                  ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT}
-                                  CHECK_EXISTS)
+        if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
+          _python_validate_library (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS)
+        elseif (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+          _python_validate_library (IN_RANGE CHECK_EXISTS)
+        elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION)
+          _python_validate_library (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS)
+        else()
+          _python_validate_library (CHECK_EXISTS)
+        endif()
       endif()
       if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS)
-        _python_validate_include_dir (${${_PYTHON_PREFIX}_FIND_VERSION}
-                                      ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT}
-                                      CHECK_EXISTS)
+        if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
+          _python_validate_include_dir (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS)
+        elseif (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+          _python_validate_include_dir (IN_RANGE CHECK_EXISTS)
+        elseif (${_PYTHON_PREFIX}_FIND_VERSION)
+          _python_validate_include_dir (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS)
+        else()
+          _python_validate_include_dir (CHECK_EXISTS)
+        endif()
       endif()
     else()
       if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS)
@@ -1384,9 +1486,13 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
     if (__${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE)
       # check version validity
       if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
-        _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS)
+        _python_validate_interpreter (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS)
+      elseif (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+        _python_validate_interpreter (IN_RANGE CHECK_EXISTS)
+      elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION)
+        _python_validate_interpreter (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS)
       else()
-        _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS)
+        _python_validate_interpreter (CHECK_EXISTS)
       endif()
     else()
       unset (_${_PYTHON_PREFIX}_EXECUTABLE CACHE)
@@ -1410,6 +1516,13 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
       # Registry Paths
       _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS})
 
+      set (_${_PYTHON_PREFIX}_VALIDATE_OPTIONS ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+      if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+        list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS IN_RANGE)
+      elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION)
+        list (APPEND VERSION ${${_PYTHON_PREFIX}_FIND_VERSION})
+      endif()
+
       while (TRUE)
         # Virtual environments handling
         if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$")
@@ -1424,7 +1537,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
 
-          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+          _python_validate_interpreter (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1445,7 +1558,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         NO_CMAKE_ENVIRONMENT_PATH
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+          _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1460,7 +1573,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+          _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1474,7 +1587,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                       PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                       NO_SYSTEM_ENVIRONMENT_PATH
                       NO_CMAKE_SYSTEM_PATH)
-        _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+        _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_EXECUTABLE)
           break()
         endif()
@@ -1483,7 +1596,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                       NAMES ${_${_PYTHON_PREFIX}_NAMES}
                       NAMES_PER_DIR
                       PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES})
-        _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+        _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_EXECUTABLE)
           break()
         endif()
@@ -1496,7 +1609,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_DEFAULT_PATH)
-          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+          _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1509,7 +1622,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_DEFAULT_PATH)
-          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+          _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1519,6 +1632,11 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
       endwhile()
     else()
       # look-up for various versions and locations
+      set (_${_PYTHON_PREFIX}_VALIDATE_OPTIONS EXACT)
+      if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+        list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS IN_RANGE)
+      endif()
+
       foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS)
         _python_get_names (_${_PYTHON_PREFIX}_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX INTERPRETER)
         _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_VERSION} INTERPRETER)
@@ -1538,7 +1656,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         NO_CMAKE_ENVIRONMENT_PATH
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+          _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1573,7 +1691,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         NO_CMAKE_SYSTEM_PATH)
         endif()
 
-        _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+        _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_EXECUTABLE)
           break()
         endif()
@@ -1586,7 +1704,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                       PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                       NO_SYSTEM_ENVIRONMENT_PATH
                       NO_CMAKE_SYSTEM_PATH)
-        _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+        _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_EXECUTABLE)
           break()
         endif()
@@ -1599,7 +1717,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
         find_program (_${_PYTHON_PREFIX}_EXECUTABLE
                       NAMES ${_${_PYTHON_PREFIX}_NAMES}
                       PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES})
-        _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+        _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_EXECUTABLE)
           break()
         endif()
@@ -1624,7 +1742,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         NO_DEFAULT_PATH)
         endif()
 
-        _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+        _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_EXECUTABLE)
           break()
         endif()
@@ -1825,9 +1943,13 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
     if (__${_PYTHON_PREFIX}_COMPILER_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_COMPILER_SIGNATURE)
       # check version validity
       if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
-        _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS)
+        _python_validate_compiler (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS)
+      elseif (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+        _python_validate_compiler (IN_RANGE CHECK_EXISTS)
+      elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION)
+        _python_validate_compiler (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS)
       else()
-        _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS)
+        _python_validate_compiler (CHECK_EXISTS)
       endif()
     else()
       unset (_${_PYTHON_PREFIX}_COMPILER CACHE)
@@ -1862,6 +1984,13 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                               IMPLEMENTATIONS IronPython
                               VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS})
 
+      set (_${_PYTHON_PREFIX}_VALIDATE_OPTIONS ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+      if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+        list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS IN_RANGE)
+      elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION)
+        list (APPEND VERSION ${${_PYTHON_PREFIX}_FIND_VERSION})
+      endif()
+
       while (TRUE)
         # Apple frameworks handling
         if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST")
@@ -1875,7 +2004,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         NO_CMAKE_ENVIRONMENT_PATH
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+          _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -1890,7 +2019,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+          _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -1904,7 +2033,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                       PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                       NO_SYSTEM_ENVIRONMENT_PATH
                       NO_CMAKE_SYSTEM_PATH)
-        _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+        _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_COMPILER)
           break()
         endif()
@@ -1914,7 +2043,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                       NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES}
                       NAMES_PER_DIR
                       PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES})
-        _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+        _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_COMPILER)
           break()
         endif()
@@ -1927,7 +2056,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_DEFAULT_PATH)
-          _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
+          _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -1940,6 +2069,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_DEFAULT_PATH)
+          _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -1949,6 +2079,11 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
       endwhile()
     else()
       # try using root dir and registry
+      set (_${_PYTHON_PREFIX}_VALIDATE_OPTIONS EXACT)
+      if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE)
+        list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS IN_RANGE)
+      endif()
+
       foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS)
         _python_get_names (_${_PYTHON_PREFIX}_COMPILER_NAMES
                            IMPLEMENTATIONS IronPython
@@ -1979,7 +2114,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         NO_CMAKE_ENVIRONMENT_PATH
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+          _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -1994,7 +2129,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+          _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -2008,7 +2143,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                       PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                       NO_SYSTEM_ENVIRONMENT_PATH
                       NO_CMAKE_SYSTEM_PATH)
-        _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+        _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
         if (_${_PYTHON_PREFIX}_COMPILER)
           break()
         endif()
@@ -2021,7 +2156,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_DEFAULT_PATH)
-          _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+          _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -2034,7 +2169,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}
                         NO_DEFAULT_PATH)
-          _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT)
+          _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -2054,6 +2189,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                     NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES}
                     HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS}
                     PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES})
+      _python_validate_compiler ()
     endif()
   endif()
 
@@ -2754,7 +2890,6 @@ if (("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS
     if (_${_PYTHON_PREFIX}_INCLUDE_DIR)
       # retrieve version from header file
       _python_get_version (INCLUDE PREFIX _${_PYTHON_PREFIX}_INC_)
-
       if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE)
         if ("${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}"
             VERSION_EQUAL _${_PYTHON_PREFIX}_VERSION)
@@ -2980,10 +3115,10 @@ foreach (_${_PYTHON_PREFIX}_COMPONENT IN ITEMS Interpreter Compiler Development
   endif()
 endforeach()
 
-include (${CMAKE_CURRENT_LIST_DIR}/../FindPackageHandleStandardArgs.cmake)
 find_package_handle_standard_args (${_PYTHON_PREFIX}
                                    REQUIRED_VARS ${_${_PYTHON_PREFIX}_REQUIRED_VARS}
                                    VERSION_VAR ${_PYTHON_PREFIX}_VERSION
+                                   HANDLE_VERSION_RANGE
                                    HANDLE_COMPONENTS
                                    REASON_FAILURE_MESSAGE "${_${_PYTHON_PREFIX}_REASON_FAILURE}")
 
diff --git a/Modules/FindPython2.cmake b/Modules/FindPython2.cmake
index 1c75011..97e376d 100644
--- a/Modules/FindPython2.cmake
+++ b/Modules/FindPython2.cmake
@@ -10,6 +10,10 @@ FindPython2
 Find Python 2 interpreter, compiler and development environment (include
 directories and libraries).
 
+When a version is requested, it can be specified as a simple value or as a
+range. For a detailed description of version range usage and capabilities,
+refer to the :command:`find_package` command.
+
 The following components are supported:
 
 * ``Interpreter``: search for Python 2 interpreter
diff --git a/Modules/FindPython3.cmake b/Modules/FindPython3.cmake
index e6e9f04..266b50a 100644
--- a/Modules/FindPython3.cmake
+++ b/Modules/FindPython3.cmake
@@ -10,6 +10,10 @@ FindPython3
 Find Python 3 interpreter, compiler and development environment (include
 directories and libraries).
 
+When a version is requested, it can be specified as a simple value or as a
+range. For a detailed description of version range usage and capabilities,
+refer to the :command:`find_package` command.
+
 The following components are supported:
 
 * ``Interpreter``: search for Python 3 interpreter
diff --git a/Tests/FindPython/CMakeLists.txt b/Tests/FindPython/CMakeLists.txt
index fdfa36e..44484c3 100644
--- a/Tests/FindPython/CMakeLists.txt
+++ b/Tests/FindPython/CMakeLists.txt
@@ -243,6 +243,87 @@ if(CMake_TEST_FindPython)
     --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
     )
 
+  add_test(NAME FindPython.Python3.VersionRange.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python3.VersionRange.LOCATION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python3 -DPython_REQUESTED_VERSION=3
+                                     -DPython3_FIND_STRATEGY=LOCATION
+    )
+  add_test(NAME FindPython.Python3.VersionRange.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python3.VersionRange.VERSION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python3 -DPython_REQUESTED_VERSION=3
+                                     -DPython3_FIND_STRATEGY=VERSION
+    )
+  add_test(NAME FindPython.Python2.VersionRange.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python2.VersionRange.LOCATION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python2 -DPython_REQUESTED_VERSION=2
+                                     -DPython2_FIND_STRATEGY=LOCATION
+    )
+  add_test(NAME FindPython.Python2.VersionRange.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python2.VersionRange.VERSION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python2 -DPython_REQUESTED_VERSION=2
+                                     -DPython2_FIND_STRATEGY=VERSION
+    )
+  add_test(NAME FindPython.Python.V2.VersionRange.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python.V2.VersionRange.LOCATION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python -DPython_REQUESTED_VERSION=2
+                                     -DPython_FIND_STRATEGY=LOCATION
+    )
+  add_test(NAME FindPython.Python.V2.VersionRange.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python.V2.VersionRange.VERSION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python -DPython_REQUESTED_VERSION=2
+                                     -DPython_FIND_STRATEGY=VERSION
+    )
+  add_test(NAME FindPython.Python.V3.VersionRange.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python.V3.VersionRange.LOCATION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python -DPython_REQUESTED_VERSION=3
+                                     -DPython_FIND_STRATEGY=LOCATION
+    )
+  add_test(NAME FindPython.Python.V3.VersionRange.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python.V3.VersionRange.VERSION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python -DPython_REQUESTED_VERSION=3
+                                     -DPython_FIND_STRATEGY=VERSION
+    )
+
   add_test(NAME FindPython.MultiplePackages COMMAND
     ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
     --build-and-test
@@ -492,6 +573,29 @@ if(CMake_TEST_FindPython_IronPython)
     --build-options ${build_options} -DPython_REQUESTED_VERSION=2 -DPython_FIND_STRATEGY=VERSION
     --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
     )
+
+  add_test(NAME FindPython.IronPython2.VersionRange.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/IronPython2.VersionRange.LOCATION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python2 -DPython_REQUESTED_VERSION=2
+                                     -DPython2_FIND_IMPLEMENTATIONS=IronPython
+                                     -DPython2_FIND_STRATEGY=LOCATION
+    )
+  add_test(NAME FindPython.IronPython2.VersionRange.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VersionRange"
+    "${CMake_BINARY_DIR}/Tests/FindPython/IronPython2.VersionRange.VERSION"
+    ${build_generator_args}
+    --build-project TestVersionRange
+    --build-options ${build_options} -DPython=Python2 -DPython_REQUESTED_VERSION=2
+                                     -DPython2_FIND_IMPLEMENTATIONS=IronPython
+                                     -DPython2_FIND_STRATEGY=VERSION
+    )
 endif()
 
 if(CMake_TEST_FindPython_PyPy)
diff --git a/Tests/FindPython/VersionRange/CMakeLists.txt b/Tests/FindPython/VersionRange/CMakeLists.txt
new file mode 100644
index 0000000..0d946f5
--- /dev/null
+++ b/Tests/FindPython/VersionRange/CMakeLists.txt
@@ -0,0 +1,55 @@
+cmake_minimum_required (VERSION 3.18...3.19)
+
+project (TestVersionRange LANGUAGES NONE)
+
+
+find_package (${Python} ${Python_REQUESTED_VERSION} EXACT COMPONENTS Interpreter)
+if (NOT ${Python}_FOUND)
+  message (FATAL_ERROR "Failed to find ${Python} ${Python_REQUESTED_VERSION}")
+endif()
+
+if (Python_REQUESTED_VERSION VERSION_LESS 3.0)
+  set (IN_VERSION_RANGE 2.0...<3.0)
+  set (OUT_VERSION_RANGE 2.0...<${${Python}_VERSION})
+else()
+  set (IN_VERSION_RANGE 3.0...<4.0)
+  set (OUT_VERSION_RANGE 3.0...<${${Python}_VERSION})
+endif()
+
+function (FIND_PYTHON EXPECTED_VERSION)
+  unset (_${Python}_EXECUTABLE CACHE)
+  unset (_${Python}_LIBRARY_RELEASE CACHE)
+  unset (_${Python}_INCLUDE_DIR CACHE)
+  unset (${Python}_FOUND)
+
+  find_package (${ARGN})
+
+  if (EXPECTED_VERSION STREQUAL "NONE")
+    if (${Python}_FOUND)
+      message (SEND_ERROR "Unexpectedly found version:  ${${Python}_VERSION} for ${ARGN}")
+    endif()
+    return()
+  endif()
+
+  if (NOT ${Python}_FOUND)
+    message (SEND_ERROR "Not found: ${ARGN}")
+  elseif (NOT ${Python}_VERSION VERSION_EQUAL EXPECTED_VERSION)
+    message (SEND_ERROR "Wrong version: ${${Python}_VERSION} for ${ARGN}")
+  endif()
+endfunction()
+
+find_python (${${Python}_VERSION} ${Python} ${IN_VERSION_RANGE} COMPONENTS Interpreter)
+if (${Python}_FIND_IMPLEMENTATIONS STREQUAL "IronPython")
+  find_python (${${Python}_VERSION} ${Python} ${IN_VERSION_RANGE} COMPONENTS Compiler)
+else()
+  find_python (${${Python}_VERSION} ${Python} ${IN_VERSION_RANGE} COMPONENTS Development)
+endif()
+
+find_python ("NONE" ${Python} ${OUT_VERSION_RANGE} COMPONENTS Interpreter)
+if (${Python}_FIND_IMPLEMENTATIONS STREQUAL "IronPython")
+  find_python ("NONE" ${Python} ${OUT_VERSION_RANGE} COMPONENTS Compiler)
+else()
+  find_python ("NONE" ${Python} ${OUT_VERSION_RANGE} COMPONENTS Development)
+endif()
+
+find_python ("NONE" ${Python} 5...6 COMPONENTS Interpreter)
-- 
cgit v0.12