Browse Source

FindPython: Add support for free threaded python

Fixes: #26016
Marc Chevrier 1 year ago
parent
commit
9a0dee7257

+ 5 - 0
Help/release/dev/FindPython-free_threaded_Python.rst

@@ -0,0 +1,5 @@
+FindPython-free_thread_Python
+-----------------------------
+
+* The :module:`FindPython`, :module:`FindPython3` modules gained the support
+  of the free threaded python version.

+ 25 - 14
Modules/FindPython.cmake

@@ -310,42 +310,53 @@ Hints
 
     This hint will be honored only when searched for ``Python`` version 3.
 
-  .. note::
+  The ``Python_FIND_ABI`` variable is a 4-tuple specifying, in that order,
+  ``pydebug`` (``d``), ``pymalloc`` (``m``), ``unicode`` (``u``) and
+  ``gil_disabled`` (``t``) flags.
 
-    If ``Python_FIND_ABI`` is not defined, any ABI will be searched.
+  .. versionadded:: 3.30
+    A fourth element, specifying the ``gil_disabled`` flag (i.e. free
+    threaded python), is added and is optional. If not specified, the value is
+    ``OFF``.
 
-  The ``Python_FIND_ABI`` variable is a 3-tuple specifying, in that order,
-  ``pydebug`` (``d``), ``pymalloc`` (``m``) and ``unicode`` (``u``) flags.
   Each element can be set to one of the following:
 
   * ``ON``: Corresponding flag is selected.
   * ``OFF``: Corresponding flag is not selected.
   * ``ANY``: The two possibilities (``ON`` and ``OFF``) will be searched.
 
-  From this 3-tuple, various ABIs will be searched starting from the most
-  specialized to the most general. Moreover, ``debug`` versions will be
-  searched **after** ``non-debug`` ones.
+  .. note::
+
+    If ``Python_FIND_ABI`` is not defined, any ABI will be searched.
+
+  From this 4-tuple, various ABIs will be searched starting from the most
+  specialized to the most general. Moreover, when ``ANY`` is specified for
+  ``pydebug`` and ``gil_disabled``, ``debug`` and ``free threaded`` versions
+  will be searched **after** ``non-debug`` and ``non-gil-disabled`` ones.
 
   For example, if we have::
 
-    set (Python_FIND_ABI "ON" "ANY" "ANY")
+    set (Python_FIND_ABI "ON" "ANY" "ANY" "ON")
 
   The following flags combinations will be appended, in that order, to the
-  artifact names: ``dmu``, ``dm``, ``du``, and ``d``.
+  artifact names: ``tdmu``, ``tdm``, ``tdu``, and ``td``.
 
   And to search any possible ABIs::
 
-    set (Python_FIND_ABI "ANY" "ANY" "ANY")
+    set (Python_FIND_ABI "ANY" "ANY" "ANY" "ANY")
 
   The following combinations, in that order, will be used: ``mu``, ``m``,
-  ``u``, ``<empty>``, ``dmu``, ``dm``, ``du`` and ``d``.
+  ``u``, ``<empty>``, ``dmu``, ``dm``, ``du``, ``d``, ``tmu``, ``tm``, ``tu``,
+  ``t``, ``tdmu``, ``tdm``, ``tdu``, and ``td``.
 
   .. note::
 
-    This hint is useful only on ``POSIX`` systems. So, on ``Windows`` systems,
+    This hint is useful only on ``POSIX`` systems except for the
+    ``gil_disabled`` flag. So, on ``Windows`` systems,
     when ``Python_FIND_ABI`` is defined, ``Python`` distributions from
-    `python.org <https://www.python.org/>`_ will be found only if value for
-    each flag is ``OFF`` or ``ANY``.
+    `python.org <https://www.python.org/>`_ will be found only if the value for
+    each flag is ``OFF`` or ``ANY`` except for the fourth one
+    (``gil_disabled``).
 
 ``Python_FIND_STRATEGY``
   .. versionadded:: 3.15

+ 115 - 68
Modules/FindPython/Support.cmake

@@ -226,17 +226,28 @@ endfunction()
 
 
 function (_PYTHON_GET_ABIFLAGS _PGABIFLAGS)
-  set (abiflags)
+  if (NOT DEFINED _${_PYTHON_PREFIX}_FIND_ABI)
+    set (${_PGABIFLAGS} "<none>" PARENT_SCOPE)
+    return()
+  endif()
+
+  set (abiflags "<none>")
   list (GET _${_PYTHON_PREFIX}_FIND_ABI 0 pydebug)
   list (GET _${_PYTHON_PREFIX}_FIND_ABI 1 pymalloc)
   list (GET _${_PYTHON_PREFIX}_FIND_ABI 2 unicode)
+  list (LENGTH _${_PYTHON_PREFIX}_FIND_ABI find_abi_length)
+  if (find_abi_length GREATER 3)
+    list (GET _${_PYTHON_PREFIX}_FIND_ABI 3 gil)
+  else()
+    set (gil "OFF")
+  endif()
 
   if (pymalloc STREQUAL "ANY" AND unicode STREQUAL "ANY")
-    set (abiflags "mu" "m" "u" "")
+    set (abiflags "mu" "m" "u" "<none>")
   elseif (pymalloc STREQUAL "ANY" AND unicode STREQUAL "ON")
     set (abiflags "mu" "u")
   elseif (pymalloc STREQUAL "ANY" AND unicode STREQUAL "OFF")
-    set (abiflags "m" "")
+    set (abiflags "m" "<none>")
   elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ANY")
     set (abiflags "mu" "m")
   elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ON")
@@ -244,7 +255,7 @@ function (_PYTHON_GET_ABIFLAGS _PGABIFLAGS)
   elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "OFF")
     set (abiflags "m")
   elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ANY")
-    set (abiflags "u" "")
+    set (abiflags "u" "<none>")
   elseif (pymalloc STREQUAL "OFF" AND unicode STREQUAL "ON")
     set (abiflags "u")
   endif()
@@ -261,10 +272,26 @@ function (_PYTHON_GET_ABIFLAGS _PGABIFLAGS)
       list (TRANSFORM flags PREPEND "d")
       list (APPEND abiflags "${flags}")
     else()
-      set (abiflags "" "d")
+      set (abiflags "<none>" "d")
     endif()
   endif()
 
+  if (gil STREQUAL "ON")
+    if (abiflags)
+      list (TRANSFORM abiflags PREPEND "t")
+    else()
+      set (abiflags "t")
+    endif()
+  elseif (gil STREQUAL "ANY")
+    if (abiflags)
+      set (flags "${abiflags}")
+      list (TRANSFORM flags PREPEND "t")
+      list (APPEND abiflags "${flags}")
+    else()
+      set (abiflags "<none>" "t")
+    endif()
+  endif()
+  list (TRANSFORM abiflags REPLACE "^(.+)<none>$" "\\1")
   set (${_PGABIFLAGS} "${abiflags}" PARENT_SCOPE)
 endfunction()
 
@@ -277,6 +304,7 @@ function (_PYTHON_GET_PATH_SUFFIXES _PYTHON_PGPS_PATH_SUFFIXES)
 
   if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS)
     set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}")
+    list (TRANSFORM abi REPLACE "^<none>$" "")
   else()
     set (abi "mu" "m" "u" "")
   endif()
@@ -368,19 +396,33 @@ function (_PYTHON_GET_NAMES _PYTHON_PGN_NAMES)
       endif()
       foreach (version IN LISTS _PGN_VERSION)
         if (_PGN_WIN32)
-          string (REPLACE "." "" version_no_dots ${version})
-
-          set (name python${version_no_dots})
+          if (_PGN_INTERPRETER)
+            set (name_version ${version})
+          else()
+            string (REPLACE "." "" name_version ${version})
+          endif()
+          if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS)
+            set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}")
+            list (TRANSFORM abi REPLACE "^<none>$" "")
+          else()
+            set (abi "")
+          endif()
+          if (abi)
+            set (abinames "${abi}")
+            list (TRANSFORM abinames PREPEND "python${name_version}")
+          else()
+            set (abinames "python${name_version}")
+          endif()
           if (_PGN_DEBUG)
-            string (APPEND name "_d")
+            list (TRANSFORM abinames APPEND "_d")
           endif()
-
-          list (APPEND names "${name}")
+          list (APPEND names ${abinames})
         endif()
 
         if (_PGN_POSIX)
           if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS)
             set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}")
+            list (TRANSFORM abi REPLACE "^<none>$" "")
           else()
             if (_PGN_INTERPRETER OR _PGN_CONFIG)
               set (abi "")
@@ -506,6 +548,9 @@ function (_PYTHON_GET_CONFIG_VAR _PYTHON_PGCV_VALUE NAME)
         endif()
       endif()
     endif()
+    if (NAME STREQUAL "ABIFLAGS" AND NOT _values)
+      set (_values "<none>")
+    endif()
   endif()
 
   if (_${_PYTHON_PREFIX}_EXECUTABLE AND NOT CMAKE_CROSSCOMPILING)
@@ -600,6 +645,20 @@ function (_PYTHON_GET_CONFIG_VAR _PYTHON_PGCV_VALUE NAME)
       if (WIN32 AND _${_PYTHON_PREFIX}_LIBRARY_DEBUG MATCHES "_d${CMAKE_IMPORT_LIBRARY_SUFFIX}$")
         set (_values "_d")
       endif()
+    elseif (NAME STREQUAL "ABIFLAGS" AND WIN32)
+      # config var ABIFLAGS does not exist, check GIL specific variable
+      execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
+                               "import sys; import sysconfig; sys.stdout.write(str(sysconfig.get_config_var('Py_GIL_DISABLED')))"
+                     RESULT_VARIABLE _result
+                     OUTPUT_VARIABLE _values
+                     ERROR_QUIET
+                     OUTPUT_STRIP_TRAILING_WHITESPACE)
+      if (result OR NOT _values EQUAL "1")
+        # assume ABI is not supported or GIL is set
+        set (_values "<none>")
+      else()
+        set (_values "t")
+      endif()
     else()
       set (config_flag "${NAME}")
       if (NAME STREQUAL "CONFIGDIR")
@@ -614,6 +673,9 @@ function (_PYTHON_GET_CONFIG_VAR _PYTHON_PGCV_VALUE NAME)
       if (_result)
         unset (_values)
       endif()
+      if (NAME STREQUAL "ABIFLAGS" AND NOT _values)
+        set (_values "<none>")
+      endif()
     endif()
   endif()
 
@@ -650,26 +712,27 @@ function (_PYTHON_GET_VERSION)
   unset (${_PGV_PREFIX}VERSION_PATCH PARENT_SCOPE)
   unset (${_PGV_PREFIX}ABI PARENT_SCOPE)
 
+  unset (abi)
+
   if (_PGV_LIBRARY)
     # retrieve version and abi from library name
     if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE)
       get_filename_component (library_name "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" NAME)
       # extract version from library name
-      if (library_name MATCHES "python([23])([0-9]+)")
+      if (library_name MATCHES "python([23])([0-9]+)([tdmu]*)")
         set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE)
         set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE)
         set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE)
-        set (${_PGV_PREFIX}ABI "" PARENT_SCOPE)
-      elseif (library_name MATCHES "python([23])\\.([0-9]+)([dmu]*)")
+        set (abi "${CMAKE_MATCH_3}")
+      elseif (library_name MATCHES "python([23])\\.([0-9]+)([tdmu]*)")
         set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE)
         set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE)
         set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE)
-        set (${_PGV_PREFIX}ABI "${CMAKE_MATCH_3}" PARENT_SCOPE)
+        set (abi "${CMAKE_MATCH_3}")
       elseif (library_name MATCHES "pypy([23])\\.([0-9]+)-c")
         set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE)
         set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE)
         set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE)
-        set (${_PGV_PREFIX}ABI "" PARENT_SCOPE)
       elseif (library_name MATCHES "pypy(3)?-c")
         set (version "${CMAKE_MATCH_1}")
         # try to pick-up a more precise version from the path
@@ -685,7 +748,6 @@ function (_PYTHON_GET_VERSION)
           set (${_PGV_PREFIX}VERSION_MAJOR "2" PARENT_SCOPE)
           set (${_PGV_PREFIX}VERSION "2" PARENT_SCOPE)
         endif()
-        set (${_PGV_PREFIX}ABI "" PARENT_SCOPE)
       endif()
     endif()
   elseif (_PGV_SABI_LIBRARY)
@@ -693,14 +755,13 @@ function (_PYTHON_GET_VERSION)
     if (_${_PYTHON_PREFIX}_SABI_LIBRARY_RELEASE)
       get_filename_component (library_name "${_${_PYTHON_PREFIX}_SABI_LIBRARY_RELEASE}" NAME)
       # extract version from library name
-      if (library_name MATCHES "python([23])([dmu]*)")
+      if (library_name MATCHES "python([23])([tdmu]*)")
         set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE)
         set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}" PARENT_SCOPE)
-        set (${_PGV_PREFIX}ABI "${CMAKE_MATCH_2}" PARENT_SCOPE)
+        set (abi "${CMAKE_MATCH_2}")
       elseif (library_name MATCHES "pypy([23])-c")
         set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE)
         set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}" PARENT_SCOPE)
-        set (${_PGV_PREFIX}ABI "" PARENT_SCOPE)
       elseif (library_name MATCHES "pypy-c")
         # try to pick-up a more precise version from the path
         get_filename_component (library_dir "${_${_PYTHON_PREFIX}_SABI_LIBRARY_RELEASE}" DIRECTORY)
@@ -708,7 +769,6 @@ function (_PYTHON_GET_VERSION)
           set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE)
           set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}" PARENT_SCOPE)
         endif()
-        set (${_PGV_PREFIX}ABI "" PARENT_SCOPE)
       endif()
     endif()
   else()
@@ -730,39 +790,44 @@ function (_PYTHON_GET_VERSION)
 
       # compute ABI flags
       if (version_major VERSION_GREATER "2")
-        file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/pyconfig.h" config REGEX "(Py_DEBUG|WITH_PYMALLOC|Py_UNICODE_SIZE|MS_WIN32)")
-        set (abi)
-        if (config MATCHES "#[ ]*define[ ]+MS_WIN32")
-          # ABI not used on Windows
-          set (abi "")
+        set (config_flags "Py_GIL_DISABLED|Py_DEBUG|Py_UNICODE_SIZE|MS_WIN32")
+        if ("${version_major}.${version_minor}" VERSION_LESS "3.8")
+          # pymalloc is not the default. Must be part of the name signature
+          string (APPEND config_flags "|WITH_PYMALLOC")
+        endif()
+        file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/pyconfig.h" config REGEX "(${config_flags})")
+        if (config MATCHES "(^|;)[ ]*#[ ]*define[ ]+MS_WIN32")
+          # same header is used regardless abi, so set is as <none>
+          set (abi "<none>")
         else()
           if (NOT config)
             # pyconfig.h can be a wrapper to a platform specific pyconfig.h
             # In this case, try to identify ABI from include directory
-            if (_${_PYTHON_PREFIX}_INCLUDE_DIR MATCHES "python${version_major}\\.${version_minor}+([dmu]*)")
+            if (_${_PYTHON_PREFIX}_INCLUDE_DIR MATCHES "python${version_major}\\.${version_minor}+([tdmu]*)")
               set (abi "${CMAKE_MATCH_1}")
-            else()
-              set (abi "")
             endif()
           else()
-            if (config MATCHES "#[ ]*define[ ]+Py_DEBUG[ ]+1")
+            if (config MATCHES "(^|;)[ ]*#[ ]*define[ ]+Py_GIL_DISABLED[ ]+1")
+              string (APPEND abi "t")
+            endif()
+            if (config MATCHES "(^|;)[ ]*#[ ]*define[ ]+Py_DEBUG[ ]+1")
               string (APPEND abi "d")
             endif()
-            if (config MATCHES "#[ ]*define[ ]+WITH_PYMALLOC[ ]+1")
+            if (config MATCHES "(^|;)[ ]*#[ ]*define[ ]+WITH_PYMALLOC[ ]+1")
               string (APPEND abi "m")
             endif()
-            if (config MATCHES "#[ ]*define[ ]+Py_UNICODE_SIZE[ ]+4")
+            if (config MATCHES "(^|;)[ ]*#[ ]*define[ ]+Py_UNICODE_SIZE[ ]+4")
               string (APPEND abi "u")
             endif()
           endif()
-          set (${_PGV_PREFIX}ABI "${abi}" PARENT_SCOPE)
         endif()
-      else()
-        # ABI not supported
-        set (${_PGV_PREFIX}ABI "" PARENT_SCOPE)
       endif()
     endif()
   endif()
+  if (NOT abi)
+    set (abi "<none>")
+  endif()
+  set (${_PGV_PREFIX}ABI "${abi}" PARENT_SCOPE)
 endfunction()
 
 function (_PYTHON_GET_LAUNCHER _PYTHON_PGL_NAME)
@@ -845,20 +910,11 @@ function (_PYTHON_VALIDATE_INTERPRETER)
     return()
   endif()
 
-  _python_get_launcher (launcher INTERPRETER)
+  _python_get_launcher (_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER INTERPRETER)
 
   # validate ABI compatibility
   if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI)
-    execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
-                             "import sys; sys.stdout.write(sys.abiflags)"
-                     RESULT_VARIABLE result
-                     OUTPUT_VARIABLE abi
-                     ERROR_QUIET
-                     OUTPUT_STRIP_TRAILING_WHITESPACE)
-    if (result)
-      # assume ABI is not supported
-      set (abi "")
-    endif()
+    _python_get_config_var (abi ABIFLAGS)
     if (NOT abi IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)
       # incompatible ABI
       set_property (CACHE _${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE PROPERTY VALUE "Wrong ABI for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"")
@@ -869,7 +925,7 @@ function (_PYTHON_VALIDATE_INTERPRETER)
 
   if (_PVI_IN_RANGE OR _PVI_VERSION)
     # retrieve full version
-    execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
+    execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_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
@@ -936,7 +992,7 @@ function (_PYTHON_VALIDATE_INTERPRETER)
     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
-      execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
+      execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
                                "import sys; sys.stdout.write(str(sys.version_info[0]))"
                        RESULT_VARIABLE result
                        OUTPUT_VARIABLE version
@@ -960,7 +1016,7 @@ function (_PYTHON_VALIDATE_INTERPRETER)
         OR "Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
       AND NOT CMAKE_CROSSCOMPILING)
     # In this case, interpreter must have same architecture as environment
-    execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
+    execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
                              "import sys, struct; sys.stdout.write(str(struct.calcsize(\"P\")))"
                      RESULT_VARIABLE result
                      OUTPUT_VARIABLE size
@@ -984,7 +1040,7 @@ function (_PYTHON_VALIDATE_INTERPRETER)
       else()
         set(target_arm FALSE)
       endif()
-      execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
+      execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
         "import sys, sysconfig; sys.stdout.write(sysconfig.get_platform())"
         RESULT_VARIABLE result
         OUTPUT_VARIABLE platform
@@ -1252,7 +1308,7 @@ function (_PYTHON_VALIDATE_INCLUDE_DIR)
   # retrieve version from header file
   _python_get_version (INCLUDE PREFIX inc_)
 
-  if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT inc_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)
+  if (NOT WIN32 AND DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT inc_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)
     # incompatible ABI
     set_property (CACHE _${_PYTHON_PREFIX}_Development_INCLUDE_DIR_REASON_FAILURE PROPERTY VALUE "Wrong ABI for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"")
     set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND")
@@ -1452,7 +1508,7 @@ endif()
 if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR VERSION_LESS "3")
   # ABI not supported
   unset (_${_PYTHON_PREFIX}_FIND_ABI)
-  set (_${_PYTHON_PREFIX}_ABIFLAGS "")
+  set (_${_PYTHON_PREFIX}_ABIFLAGS "<none>")
 else()
   unset (_${_PYTHON_PREFIX}_FIND_ABI)
   unset (_${_PYTHON_PREFIX}_ABIFLAGS)
@@ -1461,12 +1517,12 @@ else()
     string (TOUPPER "${${_PYTHON_PREFIX}_FIND_ABI}" _${_PYTHON_PREFIX}_FIND_ABI)
     list (TRANSFORM _${_PYTHON_PREFIX}_FIND_ABI REPLACE "^(TRUE|Y(ES)?|1)$" "ON")
     list (TRANSFORM _${_PYTHON_PREFIX}_FIND_ABI REPLACE "^(FALSE|N(O)?|0)$" "OFF")
-    if (NOT _${_PYTHON_PREFIX}_FIND_ABI MATCHES "^(ON|OFF|ANY);(ON|OFF|ANY);(ON|OFF|ANY)$")
+    if (NOT _${_PYTHON_PREFIX}_FIND_ABI MATCHES "^(ON|OFF|ANY);(ON|OFF|ANY);(ON|OFF|ANY)(;(ON|OFF|ANY))?$")
       message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_ABI}: invalid value for '${_PYTHON_PREFIX}_FIND_ABI'. Ignore it")
       unset (_${_PYTHON_PREFIX}_FIND_ABI)
     endif()
-    _python_get_abiflags (_${_PYTHON_PREFIX}_ABIFLAGS)
   endif()
+  _python_get_abiflags (_${_PYTHON_PREFIX}_ABIFLAGS)
 endif()
 unset (${_PYTHON_PREFIX}_SOABI)
 unset (${_PYTHON_PREFIX}_SOSABI)
@@ -2168,16 +2224,7 @@ if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
 
         # Use interpreter version and ABI for future searches to ensure consistency
         set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR})
-        execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
-                                 "import sys; sys.stdout.write(sys.abiflags)"
-                         RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT
-                         OUTPUT_VARIABLE _${_PYTHON_PREFIX}_ABIFLAGS
-                         ERROR_QUIET
-                         OUTPUT_STRIP_TRAILING_WHITESPACE)
-        if (_${_PYTHON_PREFIX}_RESULT)
-          # assunme ABI is not supported
-          set (_${_PYTHON_PREFIX}_ABIFLAGS "")
-        endif()
+        _python_get_config_var (_${_PYTHON_PREFIX}_ABIFLAGS ABIFLAGS)
       endif()
 
       if (${_PYTHON_PREFIX}_Interpreter_FOUND)
@@ -2835,7 +2882,7 @@ if (("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS
                            OUTPUT_STRIP_TRAILING_WHITESPACE)
           if (_${_PYTHON_PREFIX}_RESULT)
             # assume ABI is not supported
-            set (__${_PYTHON_PREFIX}_ABIFLAGS "")
+            set (__${_PYTHON_PREFIX}_ABIFLAGS "<none>")
           endif()
           if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT __${_PYTHON_PREFIX}_ABIFLAGS IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)
             # Wrong ABI
@@ -2930,7 +2977,7 @@ if (("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS
                            OUTPUT_STRIP_TRAILING_WHITESPACE)
           if (_${_PYTHON_PREFIX}_RESULT)
             # assume ABI is not supported
-            set (__${_PYTHON_PREFIX}_ABIFLAGS "")
+            set (__${_PYTHON_PREFIX}_ABIFLAGS "<none>")
           endif()
           if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT __${_PYTHON_PREFIX}_ABIFLAGS IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)
             # Wrong ABI
@@ -3723,7 +3770,7 @@ if (("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS
     endif()
     if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND
         (NOT _${_PYTHON_PREFIX}_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS
-          OR NOT _${_PYTHON_PREFIX}_INC_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS))
+          OR (NOT WIN32 AND NOT _${_PYTHON_PREFIX}_INC_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)))
       set (${_PYTHON_PREFIX}_Development.Module_FOUND FALSE)
       set (${_PYTHON_PREFIX}_Development.SABIModule_FOUND FALSE)
       set (${_PYTHON_PREFIX}_Development.Embed_FOUND FALSE)

+ 26 - 15
Modules/FindPython3.cmake

@@ -308,42 +308,53 @@ Hints
   This variable defines which ABIs, as defined in :pep:`3149`, should be
   searched.
 
-  .. note::
+  The ``Python3_FIND_ABI`` variable is a 4-tuple specifying, in that order,
+  ``pydebug`` (``d``), ``pymalloc`` (``m``), ``unicode`` (``u``) and
+  ``gil_disabled`` (``t``) flags.
 
-    If ``Python3_FIND_ABI`` is not defined, any ABI will be searched.
+  .. versionadded:: 3.30
+    A fourth element, specifying the ``gil_disabled`` flag (i.e. free
+    threaded python), is added and is optional. If not specified, the value is
+    ``OFF``.
 
-  The ``Python3_FIND_ABI`` variable is a 3-tuple specifying, in that order,
-  ``pydebug`` (``d``), ``pymalloc`` (``m``) and ``unicode`` (``u``) flags.
   Each element can be set to one of the following:
 
   * ``ON``: Corresponding flag is selected.
   * ``OFF``: Corresponding flag is not selected.
   * ``ANY``: The two possibilities (``ON`` and ``OFF``) will be searched.
 
-  From this 3-tuple, various ABIs will be searched starting from the most
-  specialized to the most general. Moreover, ``debug`` versions will be
-  searched **after** ``non-debug`` ones.
+  .. note::
+
+    If ``Python3_FIND_ABI`` is not defined, any ABI will be searched.
+
+  From this 4-tuple, various ABIs will be searched starting from the most
+  specialized to the most general. Moreover, when ``ANY`` is specified for
+  ``pydebug`` and ``gil_disabled``, ``debug`` and ``free threaded`` versions
+  will be searched **after** ``non-debug`` and ``non-gil-disabled`` ones.
 
   For example, if we have::
 
-    set (Python3_FIND_ABI "ON" "ANY" "ANY")
+    set (Python3_FIND_ABI "ON" "ANY" "ANY" "ON")
 
   The following flags combinations will be appended, in that order, to the
-  artifact names: ``dmu``, ``dm``, ``du``, and ``d``.
+  artifact names: ``tdmu``, ``tdm``, ``tdu``, and ``td``.
 
   And to search any possible ABIs::
 
-    set (Python3_FIND_ABI "ANY" "ANY" "ANY")
+    set (Python3_FIND_ABI "ANY" "ANY" "ANY" "ANY")
 
   The following combinations, in that order, will be used: ``mu``, ``m``,
-  ``u``, ``<empty>``, ``dmu``, ``dm``, ``du`` and ``d``.
+  ``u``, ``<empty>``, ``dmu``, ``dm``, ``du``, ``d``, ``tmu``, ``tm``, ``tu``,
+  ``t``, ``tdmu``, ``tdm``, ``tdu``, and ``td``.
 
   .. note::
 
-    This hint is useful only on ``POSIX`` systems. So, on ``Windows`` systems,
-    when ``Python3_FIND_ABI`` is defined, ``Python`` distributions from
-    `python.org <https://www.python.org/>`_ will be found only if value for
-    each flag is ``OFF`` or ``ANY``.
+    This hint is useful only on ``POSIX`` systems except for the
+    ``gil_disabled`` flag. So, on ``Windows`` systems,
+    when ``Python_FIND_ABI`` is defined, ``Python`` distributions from
+    `python.org <https://www.python.org/>`_ will be found only if the value for
+    each flag is ``OFF`` or ``ANY`` except for the fourth one
+    (``gil_disabled``).
 
 ``Python3_FIND_STRATEGY``
   .. versionadded:: 3.15

+ 5 - 0
Tests/FindPython/Python3/CMakeLists.txt

@@ -76,6 +76,11 @@ if (abi MATCHES "u")
 else()
   list (APPEND Python3_VALID_ABI "OFF")
 endif()
+if (abi MATCHES "t")
+  list (APPEND Python3_VALID_ABI "ON")
+else()
+  list (APPEND Python3_VALID_ABI "OFF")
+endif()
 # build an invalid pattern for ABI
 set (Python3_INVALID_ABI)
 foreach (abi IN LISTS Python3_VALID_ABI)