diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 55e442b20ff877..f113993494b0db 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -24,6 +24,22 @@ always available. Unless explicitly noted otherwise, all variables are read-only .. availability:: Unix. + .. versionchanged:: next + A :exc:`DeprecationWarning` will be emitted if the :data:`sys.abiflags` + member is accessed on Windows before Python 3.16. The :data:`!sys.abiflags` + member will be set to a meaningful value on Windows in Python 3.16. This + means the :data:`!sys.abiflags` member will always be available on all + platforms starting from Python 3.16. + + See the notes for :ref:`incoming change to sys.abiflags + ` on the *What's New in 3.14* + page for more details. + + .. TODO: When we're in Python 3.16: + - Add a **CAUTION** section about the differences of :data:`sys.abiflags` + between prior-3.14, 3.14-3.15, and 3.16+ on Windows. + - Move porting recommendations from whatsnew/3.14.rst. + .. function:: addaudithook(hook) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 894f011ec86a30..1b04b07650216b 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1693,6 +1693,66 @@ sys * Raise :exc:`DeprecationWarning` for :func:`sys._clear_type_cache`. This function was deprecated in Python 3.13 but it didn't raise a runtime warning. +* Schedule a change for availability of :data:`sys.abiflags` on Windows. A + :exc:`DeprecationWarning` will be emitted if the :data:`!sys.abiflags` member + is accessed on Windows before Python 3.16. + (Contributed by Xuehai Pan in :gh:`131717`.) + + +.. _whatsnew314-sys-abiflags-change: + +sys.abiflags +------------ + +A :exc:`DeprecationWarning` will be emitted if the :data:`sys.abiflags` member +is accessed on Windows before Python 3.16. +For example: + +.. code-block:: python + + >>> import sys + >>> getattr(sys, 'abiflags', None) # on Windows + :1: DeprecationWarning: sys.abiflags will be set to a meaningful value on all platforms ... + >>> hasattr(sys, 'abiflags') # on Windows + :1: DeprecationWarning: sys.abiflags will be set to a meaningful value on all platforms ... + False + +To suppress this warning, use the :mod:`warnings` module: + +.. code-block:: python + + import warnings + + with warnings.catch_warnings(): + # ignore DeprecationWarning on incoming sys.abiflags change on Windows before 3.16 + warnings.simplefilter('ignore', DeprecationWarning) + abiflags = getattr(sys, 'abiflags', '') + +Due to historical reasons, :data:`sys.abiflags` is not covered by +:pep:`3149` on Windows. Now we have multiple builds, such as the +:term:`free-threaded ` build, that provide different ABIs. +:data:`!sys.abiflags` is now required under many circumstances to determine +the ABI of the Python interpreter. + +The :data:`!sys.abiflags` member will be set to a meaningful value on +Windows in Python 3.16. This means the :data:`!sys.abiflags` member will +always be available on all platforms starting from Python 3.16. +Before this incoming change is made, :func:`sysconfig.get_config_vars` would be +useful. See also :ref:`Changes of **sysconfig** `. + +The following table shows how to migrate from the old code to the new code +without changing the behavior: + ++---------------------------------------+---------------------------------------------------------------------+ +| Code prior to Python 3.14 | Code prior to Python 3.16 with the same behavior | ++=======================================+=====================================================================+ +| ``sys.abiflags`` | ``sys.abiflags`` | ++---------------------------------------+---------------------------------------------------------------------+ +| ``getattr(sys, 'abiflags', default)`` | ``sys.abiflags if not sys.platform.startswith('win') else default`` | ++---------------------------------------+---------------------------------------------------------------------+ +| ``hasattr(sys, 'abiflags')`` | ``not sys.platform.startswith('win')`` | ++---------------------------------------+---------------------------------------------------------------------+ + sys.monitoring -------------- @@ -1701,6 +1761,8 @@ sys.monitoring :monitoring-event:`BRANCH_RIGHT`. The ``BRANCH`` event is deprecated. +.. _whatsnew314-sysconfig-abiflags: + sysconfig --------- diff --git a/Lib/site.py b/Lib/site.py index 5c38b1b17d5abd..70d062d089b1e2 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -318,11 +318,6 @@ def joinuser(*args): # Same to sysconfig.get_path('purelib', os.name+'_user') def _get_path(userbase): version = sys.version_info - if hasattr(sys, 'abiflags') and 't' in sys.abiflags: - abi_thread = 't' - else: - abi_thread = '' - implementation = _get_implementation() implementation_lower = implementation.lower() if os.name == 'nt': @@ -332,6 +327,7 @@ def _get_path(userbase): if sys.platform == 'darwin' and sys._framework: return f'{userbase}/lib/{implementation_lower}/site-packages' + abi_thread = 't' if 't' in sys.abiflags else '' return f'{userbase}/lib/python{version[0]}.{version[1]}{abi_thread}/site-packages' @@ -400,11 +396,8 @@ def getsitepackages(prefixes=None): implementation = _get_implementation().lower() ver = sys.version_info - if hasattr(sys, 'abiflags') and 't' in sys.abiflags: - abi_thread = 't' - else: - abi_thread = '' - if os.sep == '/': + if os.name != 'nt': + abi_thread = 't' if 't' in sys.abiflags else '' libdirs = [sys.platlibdir] if sys.platlibdir != "lib": libdirs.append("lib") diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 68890b45b82008..b1478378d2d236 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -314,6 +314,7 @@ def get_default_scheme(): def get_makefile_filename(): """Return the path of the Makefile.""" + import warnings # GH-127429: When cross-compiling, use the Makefile from the target, instead of the host Python. if cross_base := os.environ.get('_PYTHON_PROJECT_BASE'): @@ -322,7 +323,14 @@ def get_makefile_filename(): if _PYTHON_BUILD: return os.path.join(_PROJECT_BASE, "Makefile") - if hasattr(sys, 'abiflags'): + # TODO: Remove this in Python 3.16. + # This flag will always be True since Python 3.16. + with warnings.catch_warnings(): + # ignore DeprecationWarning on sys.abiflags change on Windows + warnings.filterwarnings('ignore', r'sys\.abiflags', category=DeprecationWarning) + has_abiflags = hasattr(sys, 'abiflags') + + if has_abiflags: config_dir_name = f'config-{_PY_VERSION_SHORT}{sys.abiflags}' else: config_dir_name = 'config' @@ -496,6 +504,8 @@ def get_path(name, scheme=get_default_scheme(), vars=None, expand=True): def _init_config_vars(): + import warnings + global _CONFIG_VARS _CONFIG_VARS = {} @@ -504,10 +514,12 @@ def _init_config_vars(): base_prefix = _BASE_PREFIX base_exec_prefix = _BASE_EXEC_PREFIX - try: - abiflags = sys.abiflags - except AttributeError: - abiflags = '' + # XXX: Remove this context manager in Python 3.16 + # sys.abiflags will always be available on all platforms since Python 3.16 + with warnings.catch_warnings(): + # ignore DeprecationWarning on sys.abiflags change on Windows + warnings.simplefilter('ignore', DeprecationWarning) + abiflags = getattr(sys, 'abiflags', '') if os.name == 'posix': _init_posix(_CONFIG_VARS) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 682815c3fdd6e0..320b494714a47f 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -47,7 +47,14 @@ def get_infos(self): def copy_attributes(info_add, obj, name_fmt, attributes, *, formatter=None): for attr in attributes: - value = getattr(obj, attr, None) + if attr == 'abiflags': + # TODO: Remove this special case handling in Python 3.16 + with warnings.catch_warnings(): + # ignore DeprecationWarning on sys.abiflags change on Windows + warnings.filterwarnings('ignore', r'sys\.abiflags', category=DeprecationWarning) + value = getattr(obj, attr, None) + else: + value = getattr(obj, attr, None) if value is None: continue name = name_fmt % attr diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index c74c3a3190947b..2258b2f31c3f00 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -890,6 +890,15 @@ def check_bolt_optimized(): return '--enable-bolt' in config_args +# TODO: Remove this in Python 3.16. +# This flag will always be True since Python 3.16. +with warnings.catch_warnings(): + # ignore DeprecationWarning on sys.abiflags change on Windows + warnings.filterwarnings('ignore', r'sys\.abiflags', category=DeprecationWarning) + # Equal to `not sys.platform.startswith('win')` prior to 3.16 + HAS_SYS_ABIFLAGS = hasattr(sys, 'abiflags') + + Py_GIL_DISABLED = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) def requires_gil_enabled(msg="needs the GIL enabled"): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 59ef5c993099f0..2da116aa0fbe7f 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -723,9 +723,34 @@ def test_attributes(self): self.assertIsInstance(sys.float_repr_style, str) self.assertIn(sys.float_repr_style, ('short', 'legacy')) if not sys.platform.startswith('win'): + self.assertEqual(os.name, 'posix') self.assertIsInstance(sys.abiflags, str) else: - self.assertFalse(hasattr(sys, 'abiflags')) + self.assertEqual(os.name, 'nt') + # TODO: Simpify the tests. + # sys.abiflags will be defined on Windows in Python 3.16. + absent = object() + with self.assertWarnsRegex( + DeprecationWarning, + r'sys\.abiflags will be set\b.*\bon all platforms', + ): + self.assertIs(getattr(sys, 'abiflags', absent), absent) + with self.assertWarnsRegex( + DeprecationWarning, + r'sys\.abiflags will be set\b.*\bon all platforms', + ): + self.assertFalse(hasattr(sys, 'abiflags')) + + # Emit a deprecated warning and also raise an AttributeError + with self.assertRaisesRegex( + AttributeError, + r"module 'sys' has no attribute 'abiflags'", + ): + with self.assertWarnsRegex( + DeprecationWarning, + r'sys\.abiflags will be set\b.*\bon all platforms', + ): + _ = sys.abiflags def test_thread_info(self): info = sys.thread_info @@ -1341,7 +1366,7 @@ def test_pystats(self): sys._stats_dump() @test.support.cpython_only - @unittest.skipUnless(hasattr(sys, 'abiflags'), 'need sys.abiflags') + @unittest.skipUnless(support.HAS_SYS_ABIFLAGS, 'need sys.abiflags') def test_disable_gil_abi(self): self.assertEqual('t' in sys.abiflags, support.Py_GIL_DISABLED) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-25-13-32-18.gh-issue-127405.o10vve.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-25-13-32-18.gh-issue-127405.o10vve.rst new file mode 100644 index 00000000000000..129b1861a98e68 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-25-13-32-18.gh-issue-127405.o10vve.rst @@ -0,0 +1 @@ +Emit a :exc:`DeprecationWarning` about a future change of :data:`sys.abiflags` availability on Windows. Patch by Xuehai Pan. diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 41b9a6b276a3b1..087daf6322c76c 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1,4 +1,3 @@ - /* System module */ /* @@ -36,7 +35,7 @@ Data members: #include "pycore_pystats.h" // _Py_PrintSpecializationStats() #include "pycore_structseq.h" // _PyStructSequence_InitBuiltinWithFlags() #include "pycore_sysmodule.h" // export _PySys_GetSizeOf() -#include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal() +#include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal(), _PyUnicode_EqualToASCIIString() #include "pydtrace.h" // PyDTrace_AUDIT() #include "osdefs.h" // DELIM @@ -75,6 +74,26 @@ module sys #include "clinic/sysmodule.c.h" +#ifndef ABIFLAGS + +// TODO: Remove this and related code in Python 3.16. Set the `ABIFLAGS` macro +// and `sys.abiflags` on Windows. +static inline int +_warn_incoming_sys_abiflags_change(void) +{ + return PyErr_WarnEx( + PyExc_DeprecationWarning, + "sys.abiflags will be set to a meaningful value on all platforms " + "in Python 3.16 instead of absent.\n\n" + "Please consider using `warnings.simplefilter()` with the " + "`warnings.catch_warnings()` context manager.\n" + "Or update the code with `if sys.platform.startswith('win')` " + "condition.", + /*stack_level=*/1); +} + +#endif + PyObject * _PySys_GetRequiredAttr(PyObject *name) { @@ -92,6 +111,14 @@ _PySys_GetRequiredAttr(PyObject *name) } PyObject *value; if (PyDict_GetItemRef(sysdict, name, &value) == 0) { +#ifndef ABIFLAGS + if (_PyUnicode_EqualToASCIIString(name, "abiflags")) { + if (_warn_incoming_sys_abiflags_change() < 0) { + Py_CLEAR(value); + return NULL; + } + } +#endif PyErr_Format(PyExc_RuntimeError, "lost sys.%U", name); } return value; @@ -108,6 +135,14 @@ _PySys_GetRequiredAttrString(const char *name) } PyObject *value; if (PyDict_GetItemStringRef(sysdict, name, &value) == 0) { +#ifndef ABIFLAGS + if (strcmp(name, "abiflags") == 0) { + if (_warn_incoming_sys_abiflags_change() < 0) { + Py_CLEAR(value); + return NULL; + } + } +#endif PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); } return value; @@ -129,7 +164,16 @@ _PySys_GetOptionalAttr(PyObject *name, PyObject **value) *value = NULL; return 0; } - return PyDict_GetItemRef(sysdict, name, value); + int ret = PyDict_GetItemRef(sysdict, name, value); +#ifndef ABIFLAGS + if (ret == 0 && _PyUnicode_EqualToASCIIString(name, "abiflags")) { + if (_warn_incoming_sys_abiflags_change() < 0) { + Py_CLEAR(*value); + return -1; + } + } +#endif + return ret; } int @@ -141,7 +185,16 @@ _PySys_GetOptionalAttrString(const char *name, PyObject **value) *value = NULL; return 0; } - return PyDict_GetItemStringRef(sysdict, name, value); + int ret = PyDict_GetItemStringRef(sysdict, name, value); +#ifndef ABIFLAGS + if (ret == 0 && strcmp(name, "abiflags") == 0) { + if (_warn_incoming_sys_abiflags_change() < 0) { + Py_CLEAR(*value); + return -1; + } + } +#endif + return ret; } PyObject * @@ -154,7 +207,7 @@ PySys_GetObject(const char *name) } PyObject *exc = _PyErr_GetRaisedException(tstate); PyObject *value; - (void) PyDict_GetItemStringRef(sysdict, name, &value); + int ret = PyDict_GetItemStringRef(sysdict, name, &value); /* XXX Suppress a new exception if it was raised and restore * the old one. */ if (_PyErr_Occurred(tstate)) { @@ -162,6 +215,16 @@ PySys_GetObject(const char *name) } _PyErr_SetRaisedException(tstate, exc); Py_XDECREF(value); // return a borrowed reference +#ifndef ABIFLAGS + if (ret == 0 && strcmp(name, "abiflags") == 0) { + if (_warn_incoming_sys_abiflags_change() < 0) { + Py_CLEAR(value); + return NULL; + } + } +#else + (void) ret; // ignore unused variable warning +#endif return value; } @@ -921,6 +984,33 @@ sys_exit_impl(PyObject *module, PyObject *status) } +// This function is used to warn about the future change of sys.abiflags +// from absent to a meaningful value on all platforms. +// It can be removed when the change is made. +static PyObject * +sys___getattr__(PyObject *module, PyObject *name) +{ + PyObject *value = NULL; + if (_PySys_GetOptionalAttr(name, &value) < 0) { + Py_XDECREF(value); + return NULL; + } + if (value == NULL) { + PyErr_Clear(); + PyErr_Format(PyExc_AttributeError, + "module 'sys' has no attribute '%U'", name); + } + return value; +} + +PyDoc_STRVAR(sysmodule__getattr___doc, +"__getattr__($module, name, /)\n" +"--\n" +"\n" +"Get a sys attribute by name.\n" +); + + static PyObject * get_utf8_unicode(void) { @@ -2776,6 +2866,8 @@ static PyMethodDef sys_methods[] = { SYS_EXC_INFO_METHODDEF SYS_EXCEPTHOOK_METHODDEF SYS_EXIT_METHODDEF + {"__getattr__", _PyCFunction_CAST(sys___getattr__), + METH_O, sysmodule__getattr___doc}, SYS_GETDEFAULTENCODING_METHODDEF SYS_GETDLOPENFLAGS_METHODDEF SYS_GETALLOCATEDBLOCKS_METHODDEF