浏览代码

FindPython*: Add capability to control virtual env handling.

Fixes: #19097
Marc Chevrier 6 年之前
父节点
当前提交
8a2f62cc18

+ 5 - 0
Help/release/dev/FindPython-virtual-env.rst

@@ -0,0 +1,5 @@
+FindPython-virtual-env
+----------------------
+
+* Modules :module:`FindPython3`, :module:`FindPython2` and :module:`FindPython`
+  gain capability to control how virtual environments are handled.

+ 17 - 5
Modules/FindPython.cmake

@@ -145,18 +145,30 @@ Hints
   * ``NEVER``: Never try to use registry.
 
 ``CMAKE_FIND_FRAMEWORK``
-  On OS X the :variable:`CMAKE_FIND_FRAMEWORK` variable determine the order of
+  On macOS the :variable:`CMAKE_FIND_FRAMEWORK` variable determine the order of
   preference between Apple-style and unix-style package components.
 
   .. note::
 
     Value ``ONLY`` is not supported so ``FIRST`` will be used instead.
 
-.. note::
+``Python_FIND_VIRTUALENV``
+  This variable defines the handling of virtual environments. It is meaningfull
+  only when a virtual environment is active (i.e. the ``activate`` script has
+  been evaluated). In this case, it takes precedence over
+  ``Python_FIND_REGISTRY`` and ``CMAKE_FIND_FRAMEWORK`` variables.
+  The ``Python_FIND_VIRTUALENV`` variable can be set to empty or one of the
+  following:
 
-  If a Python virtual environment is configured, set variable
-  ``Python_FIND_REGISTRY`` (Windows) or ``CMAKE_FIND_FRAMEWORK`` (macOS) with
-  value ``LAST`` or ``NEVER`` to select it preferably.
+  * ``FIRST``: The virtual environment is used before any other standard
+    paths to look-up for the interpreter. This is the default.
+  * ``ONLY``: Only the virtual environment is used to look-up for the
+    interpreter.
+  * ``STANDARD``: The virtual environment is not used to look-up for the
+    interpreter. In this case, variable ``Python_FIND_REGISTRY`` (Windows)
+    or ``CMAKE_FIND_FRAMEWORK`` (macOS) can be set with value ``LAST`` or
+    ``NEVER`` to select preferably the interpreter from the virtual
+    environment.
 
 Commands
 ^^^^^^^^

+ 60 - 5
Modules/FindPython/Support.cmake

@@ -298,6 +298,22 @@ else()
   set (_${_PYTHON_PREFIX}_FIND_REGISTRY "FIRST")
 endif()
 
+# virtual environments handling
+if (DEFINED ENV{VIRTUAL_ENV})
+  if (DEFINED ${_PYTHON_PREFIX}_FIND_VIRTUALENV)
+    if (NOT ${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY|STANDARD)$")
+      message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_VIRTUALENV}: invalid value for '${_PYTHON_PREFIX}_FIND_VIRTUALENV'. 'FIRST', 'ONLY' or 'IGNORE' expected.")
+      set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV "FIRST")
+    else()
+      set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV ${${_PYTHON_PREFIX}_FIND_VIRTUALENV})
+    endif()
+  else()
+    set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV FIRST)
+  endif()
+else()
+  set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STANDARD)
+endif()
+
 
 unset (_${_PYTHON_PREFIX}_REQUIRED_VARS)
 unset (_${_PYTHON_PREFIX}_CACHED_VARS)
@@ -318,6 +334,30 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
 
     _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION})
 
+    # Virtual environments handling
+    if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$")
+      find_program (${_PYTHON_PREFIX}_EXECUTABLE
+                    NAMES python${_${_PYTHON_PREFIX}_VERSION}
+                          python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}
+                          python
+                    NAMES_PER_DIR
+                    HINTS ${_${_PYTHON_PREFIX}_HINTS}
+                    PATHS ENV VIRTUAL_ENV
+                    PATH_SUFFIXES bin Scripts
+                    NO_CMAKE_PATH
+                    NO_CMAKE_ENVIRONMENT_PATH
+                    NO_SYSTEM_ENVIRONMENT_PATH
+                    NO_CMAKE_SYSTEM_PATH)
+
+      _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION})
+      if (${_PYTHON_PREFIX}_EXECUTABLE)
+        break()
+      endif()
+      if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY")
+        continue()
+      endif()
+    endif()
+
     # Apple frameworks handling
     if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST")
       find_program (${_PYTHON_PREFIX}_EXECUTABLE
@@ -423,7 +463,8 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
     endif()
   endforeach()
 
-  if (NOT ${_PYTHON_PREFIX}_EXECUTABLE)
+  if (NOT ${_PYTHON_PREFIX}_EXECUTABLE AND
+      NOT _${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY")
     # No specific version found. Retry with generic names
     # try using HINTS
     find_program (${_PYTHON_PREFIX}_EXECUTABLE
@@ -685,18 +726,32 @@ if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS
   # if python interpreter is found, use its location and version to ensure consistency
   # between interpreter and development environment
   unset (_${_PYTHON_PREFIX}_PREFIX)
+  unset (_${_PYTHON_PREFIX}_EXEC_PREFIX)
+  unset (_${_PYTHON_PREFIX}_BASE_EXEC_PREFIX)
   if (${_PYTHON_PREFIX}_Interpreter_FOUND)
     execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c
-                             "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.PREFIX)"
+                             "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.EXEC_PREFIX)"
                      RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT
-                     OUTPUT_VARIABLE _${_PYTHON_PREFIX}_PREFIX
+                     OUTPUT_VARIABLE _${_PYTHON_PREFIX}_EXEC_PREFIX
                      ERROR_QUIET
                      OUTPUT_STRIP_TRAILING_WHITESPACE)
     if (_${_PYTHON_PREFIX}_RESULT)
-      unset (_${_PYTHON_PREFIX}_PREFIX)
+      unset (_${_PYTHON_PREFIX}_EXEC_PREFIX)
+    endif()
+
+    if (NOT ${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "STANDARD")
+      execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c
+                               "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.BASE_EXEC_PREFIX)"
+                       RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT
+                       OUTPUT_VARIABLE _${_PYTHON_PREFIX}_BASE_EXEC_PREFIX
+                       ERROR_QUIET
+                       OUTPUT_STRIP_TRAILING_WHITESPACE)
+      if (_${_PYTHON_PREFIX}_RESULT)
+        unset (_${_PYTHON_PREFIX}_BASE_EXEC_PREFIX)
+      endif()
     endif()
   endif()
-  set (_${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_PREFIX}" "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR)
+  set (_${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_EXEC_PREFIX}" "${_${_PYTHON_PREFIX}_BASE_EXEC_PREFIX}" "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR)
 
   foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS)
     string (REPLACE "." "" _${_PYTHON_PREFIX}_VERSION_NO_DOTS ${_${_PYTHON_PREFIX}_VERSION})

+ 16 - 4
Modules/FindPython2.cmake

@@ -153,11 +153,23 @@ Hints
 
     Value ``ONLY`` is not supported so ``FIRST`` will be used instead.
 
-.. note::
+``Python2_FIND_VIRTUALENV``
+  This variable defines the handling of virtual environments. It is meaningfull
+  only when a virtual environment is active (i.e. the ``activate`` script has
+  been evaluated). In this case, it takes precedence over
+  ``Python2_FIND_REGISTRY`` and ``CMAKE_FIND_FRAMEWORK`` variables.
+  The ``Python2_FIND_VIRTUALENV`` variable can be set to empty or one of the
+  following:
 
-  If a Python virtual environment is configured, set variable
-  ``Python_FIND_REGISTRY`` (Windows) or ``CMAKE_FIND_FRAMEWORK`` (macOS) with
-  value ``LAST`` or ``NEVER`` to select it preferably.
+  * ``FIRST``: The virtual environment is used before any other standard
+    paths to look-up for the interpreter. This is the default.
+  * ``ONLY``: Only the virtual environment is used to look-up for the
+    interpreter.
+  * ``STANDARD``: The virtual environment is not used to look-up for the
+    interpreter. In this case, variable ``Python2_FIND_REGISTRY`` (Windows)
+    or ``CMAKE_FIND_FRAMEWORK`` (macOS) can be set with value ``LAST`` or
+    ``NEVER`` to select preferably the interpreter from the virtual
+    environment.
 
 Commands
 ^^^^^^^^

+ 18 - 6
Modules/FindPython3.cmake

@@ -137,7 +137,7 @@ Hints
 ``Python3_FIND_REGISTRY``
   On Windows the ``Python3_FIND_REGISTRY`` variable determine the order
   of preference between registry and environment variables.
-  the ``Python3_FIND_REGISTRY`` variable can be set to empty or one of the
+  The ``Python3_FIND_REGISTRY`` variable can be set to empty or one of the
   following:
 
   * ``FIRST``: Try to use registry before environment variables.
@@ -146,18 +146,30 @@ Hints
   * ``NEVER``: Never try to use registry.
 
 ``CMAKE_FIND_FRAMEWORK``
-  On OS X the :variable:`CMAKE_FIND_FRAMEWORK` variable determine the order of
+  On macOS the :variable:`CMAKE_FIND_FRAMEWORK` variable determine the order of
   preference between Apple-style and unix-style package components.
 
   .. note::
 
     Value ``ONLY`` is not supported so ``FIRST`` will be used instead.
 
-.. note::
+``Python3_FIND_VIRTUALENV``
+  This variable defines the handling of virtual environments. It is meaningfull
+  only when a virtual environment is active (i.e. the ``activate`` script has
+  been evaluated). In this case, it takes precedence over
+  ``Python3_FIND_REGISTRY`` and ``CMAKE_FIND_FRAMEWORK`` variables.
+  The ``Python3_FIND_VIRTUALENV`` variable can be set to empty or one of the
+  following:
 
-  If a Python virtual environment is configured, set variable
-  ``Python_FIND_REGISTRY`` (Windows) or ``CMAKE_FIND_FRAMEWORK`` (macOS) with
-  value ``LAST`` or ``NEVER`` to select it preferably.
+  * ``FIRST``: The virtual environment is used before any other standard
+    paths to look-up for the interpreter. This is the default.
+  * ``ONLY``: Only the virtual environment is used to look-up for the
+    interpreter.
+  * ``STANDARD``: The virtual environment is not used to look-up for the
+    interpreter. In this case, variable ``Python3_FIND_REGISTRY`` (Windows)
+    or ``CMAKE_FIND_FRAMEWORK`` (macOS) can be set with value ``LAST`` or
+    ``NEVER`` to select preferably the interpreter from the virtual
+    environment.
 
 Commands
 ^^^^^^^^

+ 11 - 0
Tests/FindPython/CMakeLists.txt

@@ -68,6 +68,17 @@ if(CMake_TEST_FindPython)
     --build-options ${build_options}
     --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
     )
+
+  add_test(NAME FindPython.VirtualEnv COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindPython/VirtualEnv"
+    "${CMake_BINARY_DIR}/Tests/FindPython/VirtualEnv"
+    ${build_generator_args}
+    --build-project TestVirtualEnv
+    --build-options ${build_options}
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
 endif()
 
 if(CMake_TEST_FindPython_NumPy)

+ 42 - 0
Tests/FindPython/VirtualEnv/CMakeLists.txt

@@ -0,0 +1,42 @@
+cmake_minimum_required(VERSION 3.1)
+
+project(TestVirtualEnv LANGUAGES NONE)
+
+include(CTest)
+
+find_package(Python3 REQUIRED COMPONENTS Interpreter)
+if (NOT Python3_FOUND)
+  message (FATAL_ERROR "Fail to found Python 3")
+endif()
+
+set (Python3_VIRTUAL_ENV "${CMAKE_CURRENT_BINARY_DIR}/py3venv")
+
+execute_process (COMMAND "${Python3_EXECUTABLE}" -m venv "${Python3_VIRTUAL_ENV}"
+                 RESULT_VARIABLE result
+                 OUTPUT_VARIABLE outputs
+                 ERROR_VARIABLE outputs)
+if (result)
+  message (FATAL_ERROR "Fail to create virtual environment: ${outputs}")
+endif()
+
+add_test(NAME FindPython3.VirtualEnvDefault
+         COMMAND "${CMAKE_COMMAND}" -E env --unset=PYTHONHOME
+                                           "VIRTUAL_ENV=${Python3_VIRTUAL_ENV}"
+                 "${CMAKE_COMMAND}" "-DPYTHON3_VIRTUAL_ENV=${Python3_VIRTUAL_ENV}"
+                 -P "${CMAKE_CURRENT_LIST_DIR}/VirtualEnvDefault.cmake")
+
+add_test(NAME FindPython3.VirtualEnvOnly
+         COMMAND "${CMAKE_COMMAND}" -E env --unset=PYTHONHOME
+                                           "VIRTUAL_ENV=${Python3_VIRTUAL_ENV}"
+                 "${CMAKE_COMMAND}" "-DPYTHON3_VIRTUAL_ENV=${Python3_VIRTUAL_ENV}"
+                 -P "${CMAKE_CURRENT_LIST_DIR}/VirtualEnvOnly.cmake")
+add_test(NAME FindPython3.UnsetVirtualEnvOnly
+         COMMAND "${CMAKE_COMMAND}" -E env --unset=PYTHONHOME
+                                           --unset=VIRTUAL_ENV
+                 "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_LIST_DIR}/VirtualEnvOnly.cmake")
+
+add_test(NAME FindPython3.VirtualEnvStandard
+         COMMAND "${CMAKE_COMMAND}" -E env --unset=PYTHONHOME
+                                           "VIRTUAL_ENV=${Python3_VIRTUAL_ENV}"
+                 "${CMAKE_COMMAND}" "-DPYTHON3_VIRTUAL_ENV=${Python3_VIRTUAL_ENV}"
+                 -P "${CMAKE_CURRENT_LIST_DIR}/VirtualEnvStandard.cmake")

+ 6 - 0
Tests/FindPython/VirtualEnv/VirtualEnvDefault.cmake

@@ -0,0 +1,6 @@
+
+find_package (Python3 REQUIRED)
+
+if (NOT Python3_EXECUTABLE MATCHES "^${PYTHON3_VIRTUAL_ENV}/.+")
+  message (FATAL_ERROR "Fail to use virtual environment")
+endif()

+ 16 - 0
Tests/FindPython/VirtualEnv/VirtualEnvOnly.cmake

@@ -0,0 +1,16 @@
+
+#
+# Virtual environment is defined for python3
+# Trying to find a python2 using only virtual environment
+# It is expecting to fail if a virtual environment is active and to success otherwise.
+#
+set (Python2_FIND_VIRTUALENV ONLY)
+find_package (Python2 QUIET)
+
+if (PYTHON3_VIRTUAL_ENV AND Python2_FOUND)
+  message (FATAL_ERROR "Python2 unexpectedly found.")
+endif()
+
+if (NOT PYTHON3_VIRTUAL_ENV AND NOT Python2_FOUND)
+  message (FATAL_ERROR "Fail to find Python2.")
+endif()

+ 7 - 0
Tests/FindPython/VirtualEnv/VirtualEnvStandard.cmake

@@ -0,0 +1,7 @@
+
+set (Python3_FIND_VIRTUALENV STANDARD)
+find_package (Python3 REQUIRED)
+
+if (Python3_EXECUTABLE MATCHES "^${PYTHON3_VIRTUAL_ENV}/.+")
+  message (FATAL_ERROR "Python3 virtual env unexpectedly found.")
+endif()