From 25978c7a00324bb4007224b562d3f29a09dc497f Mon Sep 17 00:00:00 2001
From: Marc Chevrier <marc.chevrier@gmail.com>
Date: Tue, 5 May 2020 15:58:26 +0200
Subject: [PATCH] FindPython: ensure any specified version is correctly handled

Fixes: #20674
---
 Modules/FindPython/Support.cmake             | 116 ++++++++++++-------
 Tests/FindPython/CMakeLists.txt              |  93 +++++++++++++++
 Tests/FindPython/ExactVersion/CMakeLists.txt |  56 +++++++++
 3 files changed, 222 insertions(+), 43 deletions(-)
 create mode 100644 Tests/FindPython/ExactVersion/CMakeLists.txt

diff --git a/Modules/FindPython/Support.cmake b/Modules/FindPython/Support.cmake
index a00b21cce5..5a08b4bbcc 100644
--- a/Modules/FindPython/Support.cmake
+++ b/Modules/FindPython/Support.cmake
@@ -434,7 +434,7 @@ function (_PYTHON_GET_VERSION)
       list (GET versions 1 version_minor)
       list (GET versions 2 version_patch)
 
-      set (${_PGV_PREFIX}VERSION "${version_major}.${version_minor}" PARENT_SCOPE)
+      set (${_PGV_PREFIX}VERSION "${version_major}.${version_minor}.${version_patch}" PARENT_SCOPE)
       set (${_PGV_PREFIX}VERSION_MAJOR ${version_major} PARENT_SCOPE)
       set (${_PGV_PREFIX}VERSION_MINOR ${version_minor} PARENT_SCOPE)
       set (${_PGV_PREFIX}VERSION_PATCH ${version_patch} PARENT_SCOPE)
@@ -508,36 +508,46 @@ function (_PYTHON_VALIDATE_INTERPRETER)
 
   get_filename_component (python_name "${_${_PYTHON_PREFIX}_EXECUTABLE}" NAME)
 
-  if (expected_version AND NOT python_name STREQUAL "python${expected_version}${abi}${CMAKE_EXECUTABLE_SUFFIX}")
-    # executable found must have a specific version
-    execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
-                             "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:2]]))"
-                     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")
-    else()
-      if (_PVI_EXACT AND NOT version VERSION_EQUAL expected_version)
-        # interpreter has wrong version
-        set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE)
+  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
+      string (LENGTH "${dots}." count)
+      if (count GREATER 3)
+        set (count 3)
+      endif()
+
+      # executable found must have a specific version
+      execute_process (COMMAND "${_${_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)
         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)
+        if (_PVI_EXACT AND NOT version VERSION_EQUAL expected_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")
+        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()
         endif()
       endif()
-    endif()
-    if (NOT _${_PYTHON_PREFIX}_EXECUTABLE)
-      return()
+      if (NOT _${_PYTHON_PREFIX}_EXECUTABLE)
+        return()
+      endif()
     endif()
   else()
     if (NOT python_name STREQUAL "python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}${CMAKE_EXECUTABLE_SUFFIX}")
@@ -613,7 +623,14 @@ function (_PYTHON_VALIDATE_COMPILER)
     # check only major version
     file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write(str(sys.version_info[0]))")
   else()
-    file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:2]]))\n")
+    # 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()
   execute_process (COMMAND "${_${_PYTHON_PREFIX}_COMPILER}" /target:exe /embed "${working_dir}/version.py"
                    WORKING_DIRECTORY "${working_dir}"
@@ -671,7 +688,9 @@ function (_PYTHON_VALIDATE_LIBRARY)
     set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND")
   else()
     if (expected_version)
-      if ((_PVL_EXACT AND NOT lib_VERSION VERSION_EQUAL expected_version) OR (lib_VERSION VERSION_LESS 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")
@@ -791,21 +810,22 @@ endif()
 foreach (_${_PYTHON_PREFIX}_COMPONENT IN ITEMS Interpreter Compiler Development NumPy)
   set (${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_FOUND FALSE)
 endforeach()
-unset (_${_PYTHON_PREFIX}_FIND_VERSIONS)
 
 # Set versions to search
 ## default: search any version
 set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSIONS})
+unset (_${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
 
-if (${_PYTHON_PREFIX}_FIND_VERSION_COUNT GREATER 1)
+if (${_PYTHON_PREFIX}_FIND_VERSION_COUNT)
   if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT)
-    set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR})
+    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)
-        list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION})
+      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()
@@ -1033,7 +1053,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_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1054,7 +1074,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_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1070,7 +1090,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION})
+          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1085,7 +1105,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                       PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}
                       NO_SYSTEM_ENVIRONMENT_PATH
                       NO_CMAKE_SYSTEM_PATH)
-        _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION})
+        _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
         if (_${_PYTHON_PREFIX}_EXECUTABLE)
           break()
         endif()
@@ -1095,7 +1115,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                             ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES}
                       NAMES_PER_DIR
                       PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES})
-        _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION})
+        _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
         if (_${_PYTHON_PREFIX}_EXECUTABLE)
           break()
         endif()
@@ -1108,7 +1128,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}
                         PATH_SUFFIXES bin
                         NO_DEFAULT_PATH)
-          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION})
+          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1122,7 +1142,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}
                         PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}
                         NO_DEFAULT_PATH)
-          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION})
+          _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
           if (_${_PYTHON_PREFIX}_EXECUTABLE)
             break()
           endif()
@@ -1472,7 +1492,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                         PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}
                         NO_SYSTEM_ENVIRONMENT_PATH
                         NO_CMAKE_SYSTEM_PATH)
-          _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION})
+          _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
           if (_${_PYTHON_PREFIX}_COMPILER)
             break()
           endif()
@@ -1484,7 +1504,7 @@ if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
                       PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}
                       NO_SYSTEM_ENVIRONMENT_PATH
                       NO_CMAKE_SYSTEM_PATH)
-        _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION})
+        _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT})
         if (_${_PYTHON_PREFIX}_COMPILER)
           break()
         endif()
@@ -2224,8 +2244,18 @@ if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS
     # retrieve version from header file
     _python_get_version (INCLUDE PREFIX _${_PYTHON_PREFIX}_INC_)
 
-    # update versioning
-    if (_${_PYTHON_PREFIX}_INC_VERSION VERSION_EQUAL _${_PYTHON_PREFIX}_VERSION)
+    if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE)
+      if ("${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}"
+          VERSION_EQUAL _${_PYTHON_PREFIX}_VERSION)
+        # update versioning
+        set (_${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_INC_VERSION})
+        set (_${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_INC_VERSION_PATCH})
+      endif()
+    else()
+      # library is not defined, rely on header for version
+      set (_${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_INC_VERSION})
+      set (_${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR})
+      set (_${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_INC_VERSION_MINOR})
       set (_${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_INC_VERSION_PATCH})
     endif()
   endif()
diff --git a/Tests/FindPython/CMakeLists.txt b/Tests/FindPython/CMakeLists.txt
index 5c954e222f..9b89832272 100644
--- a/Tests/FindPython/CMakeLists.txt
+++ b/Tests/FindPython/CMakeLists.txt
@@ -128,6 +128,99 @@ if(CMake_TEST_FindPython)
     --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
     )
 
+  add_test(NAME FindPython.Python2.ExactVersion.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/ExactVersion"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python2.ExactVersion.LOCATION"
+    ${build_generator_args}
+    --build-project TestExactVersion
+    --build-options ${build_options} -DPython_MAJOR_VERSION=2
+                                     -DPython_REQUESTED_VERSION=2.1.2
+                                     -DPython2_FIND_STRATEGY=LOCATION
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+  add_test(NAME FindPython.Python2.ExactVersion.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/ExactVersion"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python2.ExactVersion.VERSION"
+    ${build_generator_args}
+    --build-project TestExactVersion
+    --build-options ${build_options} -DPython_MAJOR_VERSION=2
+                                     -DPython_REQUESTED_VERSION=2.1.2
+                                     -DPython2_FIND_STRATEGY=VERSION
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+  add_test(NAME FindPython.Python3.ExactVersion.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/ExactVersion"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python3.ExactVersion.LOCATION"
+    ${build_generator_args}
+    --build-project TestExactVersion
+    --build-options ${build_options} -DPython_MAJOR_VERSION=3
+                                     -DPython_REQUESTED_VERSION=3.1.2
+                                     -DPython3_FIND_STRATEGY=LOCATION
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+  add_test(NAME FindPython.Python3.ExactVersion.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/ExactVersion"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python3.ExactVersion.VERSION"
+    ${build_generator_args}
+    --build-project TestExactVersion
+    --build-options ${build_options} -DPython_MAJOR_VERSION=3
+                                     -DPython_REQUESTED_VERSION=3.1.2
+                                     -DPython3_FIND_STRATEGY=VERSION
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+  add_test(NAME FindPython.Python.V2.ExactVersion.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/ExactVersion"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python.V2.ExactVersion.LOCATION"
+    ${build_generator_args}
+    --build-project TestExactVersion
+    --build-options ${build_options} -DPython_REQUESTED_VERSION=2.1.2
+                                     -DPython_FIND_STRATEGY=LOCATION
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+  add_test(NAME FindPython.Python.V2.ExactVersion.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/ExactVersion"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python.V2.ExactVersion.VERSION"
+    ${build_generator_args}
+    --build-project TestExactVersion
+    --build-options ${build_options} -DPython_REQUESTED_VERSION=2.1.2
+                                     -DPython_FIND_STRATEGY=VERSION
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+  add_test(NAME FindPython.Python.V3.ExactVersion.LOCATION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/ExactVersion"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python.V3.ExactVersion.LOCATION"
+    ${build_generator_args}
+    --build-project TestExactVersion
+    --build-options ${build_options} -DPython_REQUESTED_VERSION=3.1.2
+                                     -DPython_FIND_STRATEGY=LOCATION
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+  add_test(NAME FindPython.Python.V3.ExactVersion.VERSION COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/ExactVersion"
+    "${CMake_BINARY_DIR}/Tests/FindPython/Python.V3.ExactVersion.VERSION"
+    ${build_generator_args}
+    --build-project TestExactVersion
+    --build-options ${build_options} -DPython_REQUESTED_VERSION=3.1.2
+                                     -DPython_FIND_STRATEGY=VERSION
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+
   add_test(NAME FindPython.MultiplePackages COMMAND
     ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
     --build-and-test
diff --git a/Tests/FindPython/ExactVersion/CMakeLists.txt b/Tests/FindPython/ExactVersion/CMakeLists.txt
new file mode 100644
index 0000000000..e09f73a9d3
--- /dev/null
+++ b/Tests/FindPython/ExactVersion/CMakeLists.txt
@@ -0,0 +1,56 @@
+cmake_minimum_required(VERSION 3.1)
+
+project(TestExactVersion C)
+
+find_package(Python${Python_MAJOR_VERSION} ${Python_REQUESTED_VERSION} COMPONENTS Interpreter Development)
+if (NOT Python${Python_MAJOR_VERSION}_FOUND)
+  message (FATAL_ERROR "Fail to found Python ${Python_REQUESTED_VERSION}")
+endif()
+if (NOT Python${Python_MAJOR_VERSION}_Interpreter_FOUND)
+  message (FATAL_ERROR "Fail to found Python ${Python_REQUESTED_VERSION} Interpreter")
+endif()
+if (NOT Python${Python_MAJOR_VERSION}_Development_FOUND)
+  message (FATAL_ERROR "Fail to found Python ${Python_REQUESTED_VERSION} Development")
+endif()
+
+if(NOT TARGET Python${Python_MAJOR_VERSION}::Interpreter)
+  message(SEND_ERROR "Python${Python_MAJOR_VERSION}::Interpreter not found")
+endif()
+
+if(NOT TARGET Python${Python_MAJOR_VERSION}::Python)
+  message(SEND_ERROR "Python${Python_MAJOR_VERSION}::Python not found")
+endif()
+if(NOT TARGET Python${Python_MAJOR_VERSION}::Module)
+  message(SEND_ERROR "Python${Python_MAJOR_VERSION}::Module not found")
+endif()
+
+
+# reset artifacts and second search with exact version already founded
+unset (Python${Python_MAJOR_VERSION}_EXECUTABLE)
+unset (_Python${Python_MAJOR_VERSION}_EXECUTABLE CACHE)
+
+unset (_Python${Python_MAJOR_VERSION}_LIBRARY_RELEASE CACHE)
+unset (_Python${Python_MAJOR_VERSION}_INCLUDE_DIR CACHE)
+
+set (Python_REQUESTED_VERSION ${Python${Python_MAJOR_VERSION}_VERSION})
+find_package(Python${Python_MAJOR_VERSION} ${Python_REQUESTED_VERSION} EXACT COMPONENTS Interpreter Development)
+if (NOT Python${Python_MAJOR_VERSION}_FOUND)
+  message (FATAL_ERROR "Fail to found Python ${Python_REQUESTED_VERSION}")
+endif()
+if (NOT Python${Python_MAJOR_VERSION}_Interpreter_FOUND)
+  message (FATAL_ERROR "Fail to found Python ${Python_REQUESTED_VERSION} Interpreter")
+endif()
+if (NOT Python${Python_MAJOR_VERSION}_Development_FOUND)
+  message (FATAL_ERROR "Fail to found Python ${Python_REQUESTED_VERSION} Development")
+endif()
+
+if(NOT TARGET Python${Python_MAJOR_VERSION}::Interpreter)
+  message(SEND_ERROR "Python${Python_MAJOR_VERSION}::Interpreter not found")
+endif()
+
+if(NOT TARGET Python${Python_MAJOR_VERSION}::Python)
+  message(SEND_ERROR "Python${Python_MAJOR_VERSION}::Python not found")
+endif()
+if(NOT TARGET Python${Python_MAJOR_VERSION}::Module)
+  message(SEND_ERROR "Python${Python_MAJOR_VERSION}::Module not found")
+endif()
-- 
GitLab