From 2b3ea68cebb99928bcf01263b531239e313a207c Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 25 Jun 2026 19:58:28 +0800 Subject: [PATCH 1/9] gh-51067: Refactor duplicated logic in `zipfile` to `_read_local_file_header` helper (#152139) --- Lib/zipfile/__init__.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 084a47518a935cc..418933a2e8d9e87 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -418,6 +418,15 @@ def _sanitize_filename(filename): filename = filename.replace(os.altsep, "/") return filename +def _read_local_file_header(fp): + fheader = fp.read(sizeFileHeader) + if len(fheader) != sizeFileHeader: + raise BadZipFile("Truncated file header") + fheader = struct.unpack(structFileHeader, fheader) + if fheader[_FH_SIGNATURE] != stringFileHeader: + raise BadZipFile("Bad magic number for file header") + return fheader + class ZipInfo: """Class with attributes describing each file in the ZIP archive.""" @@ -1648,7 +1657,7 @@ def _validate_local_file_entry_sequence(self, fp, start_offset, end_offset, chec def _validate_local_file_entry(self, fp, offset, end_offset): fp.seek(offset) try: - fheader = self._read_local_file_header(fp) + fheader = _read_local_file_header(fp) except BadZipFile: return None @@ -1714,15 +1723,6 @@ def _validate_local_file_entry(self, fp, offset, end_offset): return entry_size - def _read_local_file_header(self, fp): - fheader = fp.read(sizeFileHeader) - if len(fheader) != sizeFileHeader: - raise BadZipFile("Truncated file header") - fheader = struct.unpack(structFileHeader, fheader) - if fheader[_FH_SIGNATURE] != stringFileHeader: - raise BadZipFile("Bad magic number for file header") - return fheader - def _scan_data_descriptor(self, fp, offset, end_offset, zip64): dd_fmt = ' Date: Thu, 25 Jun 2026 14:02:53 +0200 Subject: [PATCH 2/9] gh-151929: Get machine ID and uptime on Windows in pythoninfo (#152146) * Replace "linux." prefix with "system." in pythoninfo. * Add _winapi.GetTickCount64() function. --- Lib/test/pythoninfo.py | 101 +++++++++++++++++++++++++++---------- Modules/_winapi.c | 16 ++++++ Modules/clinic/_winapi.c.h | 20 +++++++- 3 files changed, 110 insertions(+), 27 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 067e218f797364c..7f3d566e988d804 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -8,6 +8,9 @@ import warnings +MS_WINDOWS = (sys.platform == "win32") + + def normalize_text(text): if text is None: return None @@ -906,8 +909,30 @@ def collect_subprocess(info_add): copy_attributes(info_add, subprocess, 'subprocess.%s', ('_USE_POSIX_SPAWN',)) +def winreg_query(path): + try: + import winreg + except ImportError: + return None + + key, path = path.split('\\', 1) + sub_key, value = path.rsplit('\\', 1) + if key == "HKEY_LOCAL_MACHINE": + key = winreg.HKEY_LOCAL_MACHINE + else: + raise ValueError(f"unknown key {key!r}") + + try: + access = winreg.KEY_READ | winreg.KEY_WOW64_64KEY + with winreg.OpenKey(key, sub_key, access=access) as key_handle: + result, _ = winreg.QueryValueEx(key_handle, value) + return result + except OSError: + return None + + def collect_windows(info_add): - if sys.platform != "win32": + if not MS_WINDOWS: # Code specific to Windows return @@ -999,19 +1024,10 @@ def collect_windows(info_add): info_add('windows.ver', line) # windows.developer_mode: get AllowDevelopmentWithoutDevLicense registry - import winreg - try: - key = winreg.OpenKey( - winreg.HKEY_LOCAL_MACHINE, - r"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock") - subkey = "AllowDevelopmentWithoutDevLicense" - try: - value, value_type = winreg.QueryValueEx(key, subkey) - finally: - winreg.CloseKey(key) - except OSError: - pass - else: + value = winreg_query(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows" + r"\CurrentVersion\AppModelUnlock" + r"\AllowDevelopmentWithoutDevLicense") + if value is not None: info_add('windows.developer_mode', "enabled" if value else "disabled") @@ -1044,20 +1060,22 @@ def collect_libregrtest_utils(info_add): info_add('libregrtests.build_info', ' '.join(utils.get_build_info())) -def linux_get_uptime(): - # Use CLOCK_BOOTTIME if available +def uptime_boottime(): + # Use CLOCK_BOOTTIME import time try: return time.clock_gettime(time.CLOCK_BOOTTIME) except (AttributeError, OSError): - pass + return None + - # Otherwise, parse the first member of /proc/uptime - uptime = read_first_line("/proc/uptime") - if not uptime: +def uptime_linux(): + # Parse the first member of /proc/uptime + line = read_first_line("/proc/uptime") + if not line: return try: - parts = uptime.split() + parts = line.split() if not parts: return return float(parts[0]) @@ -1065,17 +1083,48 @@ def linux_get_uptime(): return +def uptime_windows(): + try: + import _winapi + except ImportError: + return None + else: + return _winapi.GetTickCount64() / 1000. + + +def get_uptime(): + for func in (uptime_boottime, uptime_linux, uptime_windows): + uptime = func() + if uptime is not None: + return uptime + return None + + +def get_machine_id(): + if MS_WINDOWS: + machine_guid = winreg_query(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft" + r"\Cryptography\MachineGuid") + if machine_guid: + return machine_guid + + machine_id = read_first_line("/etc/machine-id") + if machine_id: + return machine_id + + return None + + def collect_linux(info_add): boot_id = read_first_line("/proc/sys/kernel/random/boot_id") if boot_id: - info_add('linux.boot_id', boot_id) + info_add('system.boot_id', boot_id) # https://www.freedesktop.org/software/systemd/man/latest/machine-id.html - machine_id = read_first_line("/etc/machine-id") + machine_id = get_machine_id() if machine_id: - info_add('linux.machine_id', machine_id) + info_add('system.machine_id', machine_id) - uptime = linux_get_uptime() + uptime = get_uptime() if uptime is not None: # truncate microseconds uptime = int(uptime) @@ -1084,7 +1133,7 @@ def collect_linux(info_add): uptime = str(datetime.timedelta(seconds=uptime)) except ImportError: uptime = f'{uptime} sec' - info_add('linux.uptime', uptime) + info_add('system.uptime', uptime) def collect_info(info): diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 5bbb02fe414bfa7..535784adedb24d8 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -3149,6 +3149,21 @@ _winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle) } +/*[clinic input] +_winapi.GetTickCount64 + +Number of milliseconds that have elapsed since the system was started. +[clinic start generated code]*/ + +static PyObject * +_winapi_GetTickCount64_impl(PyObject *module) +/*[clinic end generated code: output=cb33c0568f0b3ed1 input=77ed6539ac7d6590]*/ +{ + ULONGLONG ticks = GetTickCount64(); + return PyLong_FromUnsignedLongLong(ticks); +} + + static PyMethodDef winapi_functions[] = { _WINAPI_CLOSEHANDLE_METHODDEF _WINAPI_CONNECTNAMEDPIPE_METHODDEF @@ -3200,6 +3215,7 @@ static PyMethodDef winapi_functions[] = { _WINAPI_NEEDCURRENTDIRECTORYFOREXEPATH_METHODDEF _WINAPI_COPYFILE2_METHODDEF _WINAPI_GETPROCESSMEMORYINFO_METHODDEF + _WINAPI_GETTICKCOUNT64_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index dd9dbffaa9ac23c..031a0783aef60bb 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -2358,7 +2358,25 @@ _winapi_GetProcessMemoryInfo(PyObject *module, PyObject *arg) return return_value; } +PyDoc_STRVAR(_winapi_GetTickCount64__doc__, +"GetTickCount64($module, /)\n" +"--\n" +"\n" +"Number of milliseconds that have elapsed since the system was started."); + +#define _WINAPI_GETTICKCOUNT64_METHODDEF \ + {"GetTickCount64", (PyCFunction)_winapi_GetTickCount64, METH_NOARGS, _winapi_GetTickCount64__doc__}, + +static PyObject * +_winapi_GetTickCount64_impl(PyObject *module); + +static PyObject * +_winapi_GetTickCount64(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _winapi_GetTickCount64_impl(module); +} + #ifndef _WINAPI_GETSHORTPATHNAME_METHODDEF #define _WINAPI_GETSHORTPATHNAME_METHODDEF #endif /* !defined(_WINAPI_GETSHORTPATHNAME_METHODDEF) */ -/*[clinic end generated code: output=07dfd4bbacaed4a8 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=713a8ce97185b017 input=a9049054013a1b77]*/ From deeae2ac07a5aaa6fc1025a048592afe3516e57e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 25 Jun 2026 15:05:46 +0300 Subject: [PATCH 3/9] gh-127802: Schedule removal of legacy tkinter variable trace methods in 3.17 (GH-152012) The tkinter.Variable methods trace_variable(), trace(), trace_vdelete() and trace_vinfo(), deprecated since Python 3.14, are now scheduled for removal in Python 3.17. Co-authored-by: Claude Opus 4.8 --- Doc/deprecations/pending-removal-in-3.17.rst | 10 ++++++++ Lib/tkinter/__init__.py | 24 ++++++++++++------- ...-06-23-17-14-04.gh-issue-127802.nGSxo6.rst | 3 +++ 3 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-23-17-14-04.gh-issue-127802.nGSxo6.rst diff --git a/Doc/deprecations/pending-removal-in-3.17.rst b/Doc/deprecations/pending-removal-in-3.17.rst index 8ee7f335cc95148..d6e4e81afbbd639 100644 --- a/Doc/deprecations/pending-removal-in-3.17.rst +++ b/Doc/deprecations/pending-removal-in-3.17.rst @@ -68,3 +68,13 @@ Pending removal in Python 3.17 See :pep:`PEP 688 <688#current-options>` for more details. (Contributed by Shantanu Jain in :gh:`91896`.) + +* :mod:`tkinter`: + + - The :class:`!tkinter.Variable` methods :meth:`!trace_variable`, + :meth:`!trace` (an alias of :meth:`!trace_variable`), + :meth:`!trace_vdelete` and :meth:`!trace_vinfo`, deprecated since + Python 3.14, are scheduled for removal in Python 3.17. + Use :meth:`!trace_add`, :meth:`!trace_remove` and :meth:`!trace_info` + instead. + (Contributed by Serhiy Storchaka in :gh:`120220`.) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 122f4da25de5a2a..2e1230dc449f96e 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -505,12 +505,14 @@ def trace_variable(self, mode, callback): Return the name of the callback. This deprecated method wraps a deprecated Tcl method removed - in Tcl 9.0. Use trace_add() instead. + in Tcl 9.0 and will be removed in Python 3.17. Use trace_add() + instead. """ import warnings warnings.warn( - "trace_variable() is deprecated and not supported with Tcl 9; " - "use trace_add() instead.", + "trace_variable() is deprecated and will be removed in Python " + "3.17; use trace_add() instead. It is not supported with " + "Tcl 9.", DeprecationWarning, stacklevel=2) cbname = self._register(callback) self._tk.call("trace", "variable", self._name, mode, cbname) @@ -525,12 +527,14 @@ def trace_vdelete(self, mode, cbname): CBNAME is the name of the callback returned from trace_variable or trace. This deprecated method wraps a deprecated Tcl method removed - in Tcl 9.0. Use trace_remove() instead. + in Tcl 9.0 and will be removed in Python 3.17. Use trace_remove() + instead. """ import warnings warnings.warn( - "trace_vdelete() is deprecated and not supported with Tcl 9; " - "use trace_remove() instead.", + "trace_vdelete() is deprecated and will be removed in Python " + "3.17; use trace_remove() instead. It is not supported with " + "Tcl 9.", DeprecationWarning, stacklevel=2) self._tk.call("trace", "vdelete", self._name, mode, cbname) cbname = self._tk.splitlist(cbname)[0] @@ -548,12 +552,14 @@ def trace_vinfo(self): """Return all trace callback information. This deprecated method wraps a deprecated Tcl method removed - in Tcl 9.0. Use trace_info() instead. + in Tcl 9.0 and will be removed in Python 3.17. Use trace_info() + instead. """ import warnings warnings.warn( - "trace_vinfo() is deprecated and not supported with Tcl 9; " - "use trace_info() instead.", + "trace_vinfo() is deprecated and will be removed in Python " + "3.17; use trace_info() instead. It is not supported with " + "Tcl 9.", DeprecationWarning, stacklevel=2) return [self._tk.splitlist(x) for x in self._tk.splitlist( self._tk.call("trace", "vinfo", self._name))] diff --git a/Misc/NEWS.d/next/Library/2026-06-23-17-14-04.gh-issue-127802.nGSxo6.rst b/Misc/NEWS.d/next/Library/2026-06-23-17-14-04.gh-issue-127802.nGSxo6.rst new file mode 100644 index 000000000000000..9484c9dbd23b2fa --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-23-17-14-04.gh-issue-127802.nGSxo6.rst @@ -0,0 +1,3 @@ +The deprecated :class:`tkinter.Variable` methods :meth:`!trace_variable`, +:meth:`!trace`, :meth:`!trace_vdelete` and :meth:`!trace_vinfo` are now +scheduled for removal in Python 3.17. From b6d89edc4a13a71734ae1f1e386122bf62d68ed8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 25 Jun 2026 15:11:17 +0200 Subject: [PATCH 4/9] gh-151929: Get uptime on BSD/macOS in pythoninfo (#152189) * Check sysctlbyname() function and sys/sysctl.h header in configure. * Add _testcapi.uptime_bsd() function. --- Lib/test/pythoninfo.py | 27 ++++++++++++++++++++++----- Modules/_testcapimodule.c | 31 +++++++++++++++++++++++++++++++ configure | 12 ++++++++++++ configure.ac | 5 +++-- pyconfig.h.in | 6 ++++++ 5 files changed, 74 insertions(+), 7 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 7f3d566e988d804..a2767f3975bed25 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1083,6 +1083,18 @@ def uptime_linux(): return +def uptime_bsd(): + # Get sysctlbyname("kern.boottime") + try: + import _testcapi + except ImportError: + return None + try: + return _testcapi.uptime_bsd() + except (AttributeError, OSError): + return None + + def uptime_windows(): try: import _winapi @@ -1093,7 +1105,7 @@ def uptime_windows(): def get_uptime(): - for func in (uptime_boottime, uptime_linux, uptime_windows): + for func in (uptime_boottime, uptime_linux, uptime_bsd, uptime_windows): uptime = func() if uptime is not None: return uptime @@ -1107,9 +1119,15 @@ def get_machine_id(): if machine_guid: return machine_guid - machine_id = read_first_line("/etc/machine-id") - if machine_id: - return machine_id + for filename in ( + # https://www.freedesktop.org/software/systemd/man/latest/machine-id.html + "/etc/machine-id", + # BSD + "/etc/hostid", + ): + machine_id = read_first_line(filename) + if machine_id: + return machine_id return None @@ -1119,7 +1137,6 @@ def collect_linux(info_add): if boot_id: info_add('system.boot_id', boot_id) - # https://www.freedesktop.org/software/systemd/man/latest/machine-id.html machine_id = get_machine_id() if machine_id: info_add('system.machine_id', machine_id) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 9c90d1fc36f398e..799390c27053287 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -22,6 +22,9 @@ #ifdef HAVE_SYS_WAIT_H # include // W_STOPCODE #endif +#ifdef HAVE_SYS_SYSCTL_H +# include // sysctlbyname() +#endif #ifdef bool # error "The public headers should not include , see gh-48924" @@ -2970,6 +2973,31 @@ test_soft_deprecated_macros(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args) Py_RETURN_NONE; } + +#ifdef HAVE_SYSCTLBYNAME +static PyObject* +uptime_bsd(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + struct timeval tv; + size_t size = sizeof(tv); + int res = sysctlbyname("kern.boottime", &tv, &size, NULL, 0); + if (res != 0) { + return PyErr_SetFromErrno(PyExc_OSError); + } + double boottime = (double)tv.tv_sec + tv.tv_usec * 1e-6; + + PyTime_t now_t; + if (PyTime_Time(&now_t) < 0) { + return NULL; + } + double now = PyTime_AsSecondsDouble(now_t); + + double uptime = now - boottime; + return PyFloat_FromDouble(uptime); +} +#endif + + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -3076,6 +3104,9 @@ static PyMethodDef TestMethods[] = { {"test_thread_state_ensure_detachment", test_thread_state_ensure_detachment, METH_NOARGS}, {"test_thread_state_ensure_detached_gilstate", test_thread_state_ensure_detached_gilstate, METH_NOARGS}, {"test_thread_state_release_with_destructor", test_thread_state_release_with_destructor, METH_NOARGS}, +#ifdef HAVE_SYSCTLBYNAME + {"uptime_bsd", uptime_bsd, METH_NOARGS}, +#endif {NULL, NULL} /* sentinel */ }; diff --git a/configure b/configure index a978b613514f124..e96b87989793a8f 100755 --- a/configure +++ b/configure @@ -12104,6 +12104,12 @@ if test "x$ac_cv_header_sys_syscall_h" = xyes then : printf "%s\n" "#define HAVE_SYS_SYSCALL_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "sys/sysctl.h" "ac_cv_header_sys_sysctl_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_sysctl_h" = xyes +then : + printf "%s\n" "#define HAVE_SYS_SYSCTL_H 1" >>confdefs.h + fi ac_fn_c_check_header_compile "$LINENO" "sys/sysmacros.h" "ac_cv_header_sys_sysmacros_h" "$ac_includes_default" if test "x$ac_cv_header_sys_sysmacros_h" = xyes @@ -21049,6 +21055,12 @@ if test "x$ac_cv_func_sysconf" = xyes then : printf "%s\n" "#define HAVE_SYSCONF 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "sysctlbyname" "ac_cv_func_sysctlbyname" +if test "x$ac_cv_func_sysctlbyname" = xyes +then : + printf "%s\n" "#define HAVE_SYSCTLBYNAME 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "tcgetpgrp" "ac_cv_func_tcgetpgrp" if test "x$ac_cv_func_tcgetpgrp" = xyes diff --git a/configure.ac b/configure.ac index a2729c22386a951..cd1883f0195c47e 100644 --- a/configure.ac +++ b/configure.ac @@ -3163,7 +3163,8 @@ AC_CHECK_HEADERS([ \ sys/endian.h sys/epoll.h sys/event.h sys/eventfd.h sys/file.h sys/ioctl.h sys/kern_control.h \ sys/loadavg.h sys/lock.h sys/memfd.h sys/mkdev.h sys/mman.h sys/modem.h sys/param.h sys/pidfd.h sys/poll.h \ sys/random.h sys/resource.h sys/select.h sys/sendfile.h sys/socket.h sys/soundcard.h sys/stat.h \ - sys/statvfs.h sys/sys_domain.h sys/syscall.h sys/sysmacros.h sys/termio.h sys/time.h sys/times.h sys/timerfd.h \ + sys/statvfs.h sys/sys_domain.h sys/syscall.h sys/sysctl.h \ + sys/sysmacros.h sys/termio.h sys/time.h sys/times.h sys/timerfd.h \ sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h sys/xattr.h sysexits.h syslog.h \ termios.h util.h utime.h utmp.h \ ]) @@ -5431,7 +5432,7 @@ AC_CHECK_FUNCS([ \ setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ - sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ + sysconf sysctlbyname tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ tmpnam tmpnam_r truncate ttyname_r umask uname unlinkat unlockpt utimensat utimes vfork \ wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ ]) diff --git a/pyconfig.h.in b/pyconfig.h.in index a84b74299e159b1..a05cd8ecc91e197 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1417,6 +1417,9 @@ /* Define to 1 if you have the 'sysconf' function. */ #undef HAVE_SYSCONF +/* Define to 1 if you have the 'sysctlbyname' function. */ +#undef HAVE_SYSCTLBYNAME + /* Define to 1 if you have the header file. */ #undef HAVE_SYSEXITS_H @@ -1521,6 +1524,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SYSCALL_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SYSCTL_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SYSMACROS_H From 6a82832a0bd12ff6154fcfcccc3b7b5644eab0e8 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:30:49 +0300 Subject: [PATCH 5/9] gh-152168: Don't skip `test_bigmem` if `_testcapi` is missing (#152171) --- Lib/test/test_bigmem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_bigmem.py b/Lib/test/test_bigmem.py index 6d0879ad65d53c6..7f53379a952dfc1 100644 --- a/Lib/test/test_bigmem.py +++ b/Lib/test/test_bigmem.py @@ -10,7 +10,6 @@ from test import support from test.support import bigmemtest, _1G, _2G, _4G, import_helper -_testcapi = import_helper.import_module('_testcapi') import unittest import operator @@ -1264,6 +1263,7 @@ class ImmortalityTest(unittest.TestCase): def test_stickiness(self, size): """Check that immortality is "sticky", so that once an object is immortal it remains so.""" + _testcapi = import_helper.import_module('_testcapi') if size < _2G: # Not enough memory to cause immortality on overflow return From 11c241e1a8a71b5f25a304b1f428467b746f5d43 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:33:36 +0300 Subject: [PATCH 6/9] Don't require `_testcapi` for `test_code` (#152185) Co-authored-by: Stan Ulbrych --- Lib/test/test_code.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 3588872ed23ac49..657e70a92f09b9d 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -215,7 +215,6 @@ from test.support import threading_helper, import_helper from test.support.bytecode_helper import instructions_with_positions from opcode import opmap, opname -from _testcapi import code_offset_to_line try: import _testinternalcapi except ModuleNotFoundError: @@ -1491,6 +1490,8 @@ async def async_func(): rc, out, err = assert_python_ok('-OO', '-c', code) def test_co_branches(self): + _testcapi = import_helper.import_module("_testcapi") + code_offset_to_line = _testcapi.code_offset_to_line def get_line_branches(func): code = func.__code__ From 56ae0b8e4f78c612f7b3095cd1c936e54ee0db5f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:06:22 +0200 Subject: [PATCH 7/9] gh-148825: Fix build error if specialization is disabled (#148826) --- .../2026-04-21-15-07-03.gh-issue-148825.AbJzmZ.rst | 1 + Python/specialize.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-15-07-03.gh-issue-148825.AbJzmZ.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-15-07-03.gh-issue-148825.AbJzmZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-15-07-03.gh-issue-148825.AbJzmZ.rst new file mode 100644 index 000000000000000..5cf727b5d91fad1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-15-07-03.gh-issue-148825.AbJzmZ.rst @@ -0,0 +1 @@ +Fix build error if specialization is disabled. diff --git a/Python/specialize.c b/Python/specialize.c index 04aeb4a76d4046a..eca0c4ac2177699 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -113,7 +113,8 @@ _PyCode_Quicken(_Py_CODEUNIT *instructions, Py_ssize_t size, int enable_counters } #else for (Py_ssize_t i = 0; i < size-1; i++) { - if (instructions[i].op.code == GET_ITER) { + int opcode = instructions[i].op.code; + if (opcode == GET_ITER) { fixup_getiter(&instructions[i], flags); } i += _PyOpcode_Caches[opcode]; From 7676427cd875a4b7b3d1ad8521b0de151b9e1e63 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 25 Jun 2026 18:32:37 +0200 Subject: [PATCH 8/9] gh-151929: Add pythoninfo-build command to Platforms/emscripten (#152210) * Add also "pythoninfo-host" command. * Add pythoninfo to the "build" command. --- .github/workflows/reusable-emscripten.yml | 10 +++--- Platforms/emscripten/__main__.py | 44 ++++++++++++++++++----- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/.github/workflows/reusable-emscripten.yml b/.github/workflows/reusable-emscripten.yml index 69a780a9aebc25e..38e6dcceb8f47ca 100644 --- a/.github/workflows/reusable-emscripten.yml +++ b/.github/workflows/reusable-emscripten.yml @@ -63,15 +63,17 @@ jobs: run: python3 Platforms/emscripten configure-build-python -- --config-cache --with-pydebug - name: "Make build Python" run: python3 Platforms/emscripten make-build-python + - name: "Display build info of the build Python" + run: python3 Platforms/emscripten pythoninfo-build - name: "Make dependencies" run: >- python3 Platforms/emscripten make-dependencies ${{ steps.emsdk-cache.outputs.cache-hit == 'true' && '--check-up-to-date' || '' }} - - name: "Configure host Python" + - name: "Configure host/Emscripten Python" run: python3 Platforms/emscripten configure-host --host-runner node -- --config-cache - - name: "Make host Python" + - name: "Make host/Emscripten Python" run: python3 Platforms/emscripten make-host - - name: "Display build info" - run: python3 Platforms/emscripten run --pythoninfo + - name: "Display build info of the host/Emscripten Python" + run: python3 Platforms/emscripten pythoninfo-host - name: "Test" run: python3 Platforms/emscripten run --test diff --git a/Platforms/emscripten/__main__.py b/Platforms/emscripten/__main__.py index c2fb1c4c36e6087..84a1589b81f6a20 100644 --- a/Platforms/emscripten/__main__.py +++ b/Platforms/emscripten/__main__.py @@ -290,6 +290,12 @@ def make_build_python(context, working_dir): print(f"๐ŸŽ‰ {binary} {version}") +@subdir("native_build_dir") +def pythoninfo_build_python(context, working_dir): + """Display build info of the build Python.""" + call(["make", "pythoninfo"], quiet=context.quiet) + + def check_shasum(file: str, expected_shasum: str): with open(file, "rb") as f: digest = hashlib.file_digest(f, "sha256") @@ -580,7 +586,7 @@ def make_emscripten_python(context, working_dir): subprocess.check_call([exec_script, "--version"]) -def run_emscripten_python(context): +def run_emscripten_python(context, args=None): """Run the built emscripten Python.""" host_dir = context.build_paths["host_dir"] exec_script = host_dir / "python.sh" @@ -588,19 +594,25 @@ def run_emscripten_python(context): print("Emscripten not built", file=sys.stderr) sys.exit(1) - args = context.args - # Strip the "--" separator if present - if args and args[0] == "--": - args = args[1:] + if args is None: + args = context.args + # Strip the "--" separator if present + if args and args[0] == "--": + args = args[1:] - if context.test: - args = load_config_toml()["test-args"] + args - elif context.pythoninfo: - args = load_config_toml()["pythoninfo-args"] + args + if context.test: + args = load_config_toml()["test-args"] + args + elif context.pythoninfo: + args = load_config_toml()["pythoninfo-args"] + args os.execv(str(exec_script), [str(exec_script), *args]) +def pythoninfo_emscripten_python(context): + """Display build info of the host/Emscripten Python.""" + run_emscripten_python(context, ["-m", "test.pythoninfo"]) + + def build_target(context): """Build one or more targets.""" steps = [] @@ -608,6 +620,7 @@ def build_target(context): steps.extend([ configure_build_python, make_build_python, + pythoninfo_build_python, ]) if context.target in {"host", "all"}: steps.extend([ @@ -615,6 +628,7 @@ def build_target(context): make_mpdec, configure_emscripten_python, make_emscripten_python, + pythoninfo_emscripten_python, ]) for step in steps: @@ -707,6 +721,10 @@ def main(): "make-build-python", help="Run `make` for the build Python" ) + pythoninfo_build = subcommands.add_parser( + "pythoninfo-build", help="Display build info of the build Python" + ) + configure_host = subcommands.add_parser( "configure-host", help=( @@ -719,6 +737,10 @@ def main(): "make-host", help="Run `make` for the host/emscripten" ) + pythoninfo_host = subcommands.add_parser( + "pythoninfo-host", help="Display build info of the host/Emscripten Python" + ) + run = subcommands.add_parser( "run", help="Run the built emscripten Python", @@ -770,8 +792,10 @@ def main(): make_mpdec_cmd, make_dependencies_cmd, make_build, + pythoninfo_build, configure_host, make_host, + pythoninfo_host, clean, ): subcommand.add_argument( @@ -840,8 +864,10 @@ def main(): "make-dependencies": make_dependencies, "configure-build-python": configure_build_python, "make-build-python": make_build_python, + "pythoninfo-build": pythoninfo_build_python, "configure-host": configure_emscripten_python, "make-host": make_emscripten_python, + "pythoninfo-host": pythoninfo_emscripten_python, "build": build_target, "run": run_emscripten_python, "clean": clean_contents, From 7c8163719cd23d41daeaed0b243be45de3e82e05 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 25 Jun 2026 19:15:45 +0200 Subject: [PATCH 9/9] gh-151929: Add pythoninfo commands to Platforms/WASI (#152136) The "build" command now also runs "pythoninfo-build" and "pythoninfo-host" commands. If no subcommand is provided, display the help. GitHub Action "WASI": * Add "pythoninfo-build" and "pythoninfo-host" commands. * Remove unused and outdated CROSS_BUILD_PYTHON environment variable. --- .github/workflows/reusable-wasi.yml | 11 ++++++----- Platforms/WASI/__main__.py | 27 ++++++++++++++++++++++++++- Platforms/WASI/_build.py | 12 ++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/.github/workflows/reusable-wasi.yml b/.github/workflows/reusable-wasi.yml index 48fb70cbff80095..4b4888c38444092 100644 --- a/.github/workflows/reusable-wasi.yml +++ b/.github/workflows/reusable-wasi.yml @@ -16,7 +16,6 @@ jobs: timeout-minutes: 60 env: WASMTIME_VERSION: 38.0.3 - CROSS_BUILD_PYTHON: cross-build/build CROSS_BUILD_WASI: cross-build/wasm32-wasip1 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -54,14 +53,16 @@ jobs: run: python3 Platforms/WASI configure-build-python -- --config-cache --with-pydebug - name: "Make build Python" run: python3 Platforms/WASI make-build-python - - name: "Configure host" + - name: "Display build info of the build Python" + run: python3 Platforms/WASI pythoninfo-build + - name: "Configure host/WASI Python" # `--with-pydebug` inferred from configure-build-python run: python3 Platforms/WASI configure-host -- --config-cache env: WASI_SDK_PATH: ${{ steps.install-wasi-sdk.outputs.wasi-sdk-path }} - - name: "Make host" + - name: "Make host/WASI Python" run: python3 Platforms/WASI make-host - - name: "Display build info" - run: make --directory "${CROSS_BUILD_WASI}" pythoninfo + - name: "Display build info of the host/WASI Python" + run: python3 Platforms/WASI pythoninfo-host - name: "Test" run: make --directory "${CROSS_BUILD_WASI}" test diff --git a/Platforms/WASI/__main__.py b/Platforms/WASI/__main__.py index b8513a004f18e5f..880058bad660be9 100644 --- a/Platforms/WASI/__main__.py +++ b/Platforms/WASI/__main__.py @@ -40,6 +40,9 @@ def main(): build_python = subcommands.add_parser( "build-python", help="Build the build Python" ) + pythoninfo_build = subcommands.add_parser( + "pythoninfo-build", help="Display build info of the build Python" + ) configure_host = subcommands.add_parser( "configure-host", help="Run `configure` for the " @@ -53,6 +56,9 @@ def main(): build_host = subcommands.add_parser( "build-host", help="Build the host/WASI Python" ) + pythoninfo_host = subcommands.add_parser( + "pythoninfo-host", help="Display build info of the host/WASI Python" + ) subcommands.add_parser( "clean", help="Delete files and directories created by this script" ) @@ -61,8 +67,10 @@ def main(): configure_build, make_build, build_python, + pythoninfo_build, configure_host, make_host, + pythoninfo_host, build_host, ): subcommand.add_argument( @@ -118,7 +126,13 @@ def main(): help="Command template for running the WASI host; defaults to " f"`{default_host_runner}`", ) - for subcommand in build, configure_host, make_host, build_host: + for subcommand in ( + build, + configure_host, + make_host, + build_host, + pythoninfo_host, + ): subcommand.add_argument( "--host-triple", action="store", @@ -137,6 +151,8 @@ def main(): case "build-python": _build.configure_build_python(context) _build.make_build_python(context) + case "pythoninfo-build": + _build.pythoninfo_build_python(context) case "configure-host": _build.configure_wasi_python(context) case "make-host": @@ -144,13 +160,22 @@ def main(): case "build-host": _build.configure_wasi_python(context) _build.make_wasi_python(context) + case "pythoninfo-host": + _build.pythoninfo_wasi_python(context) case "build": + # Configure and build the build Python _build.configure_build_python(context) _build.make_build_python(context) + _build.pythoninfo_build_python(context) + + # Configure and build the host/WASI Python _build.configure_wasi_python(context) _build.make_wasi_python(context) + _build.pythoninfo_wasi_python(context) case "clean": _build.clean_contents(context) + case None: + parser.print_help() case _: raise ValueError(f"Unknown subcommand {context.subcommand!r}") diff --git a/Platforms/WASI/_build.py b/Platforms/WASI/_build.py index c1a91a9c833b8e8..792f8edd9943138 100644 --- a/Platforms/WASI/_build.py +++ b/Platforms/WASI/_build.py @@ -418,3 +418,15 @@ def clean_contents(context): if LOCAL_SETUP.exists(): if LOCAL_SETUP.read_bytes() == LOCAL_SETUP_MARKER: log("๐Ÿงน", f"Deleting generated {LOCAL_SETUP} ...") + + +@subdir(BUILD_DIR) +def pythoninfo_build_python(context, working_dir): + """Display build info of the build Python.""" + call(["make", "pythoninfo"], context=context) + + +@subdir(lambda context: CROSS_BUILD_DIR / host_triple(context)) +def pythoninfo_wasi_python(context, working_dir): + """Display build info of the host/WASI Python.""" + call(["make", "pythoninfo"], context=context)