From cc02cc23e24ae6f8bf9df40c5c16655c34a88fc0 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Wed, 13 May 2026 20:45:34 +0100 Subject: [PATCH] Remove downstream site startup backport --- cpython-unix/build-cpython.sh | 6 - ...ch-site-reentrant-startup-files-3.15.patch | 158 ------------------ cpython-windows/build.py | 6 - ...ch-site-reentrant-startup-files-3.15.patch | 145 ---------------- 4 files changed, 315 deletions(-) delete mode 100644 cpython-unix/patch-site-reentrant-startup-files-3.15.patch delete mode 100644 cpython-windows/patch-site-reentrant-startup-files-3.15.patch diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 7deb00830..422d4847b 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -338,12 +338,6 @@ if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_15}" ]; then patch -p1 -i "${ROOT}/patch-testinternalcapi-interpreter-extern.patch" fi -# Apply https://github.com/python/cpython/pull/149516 for a 3.15.0b1 -# site startup regression. -if [ "${PYTHON_MAJMIN_VERSION}" = "3.15" ]; then - patch -p1 -i "${ROOT}/patch-site-reentrant-startup-files-3.15.patch" -fi - # Most bits look at CFLAGS. But setup.py only looks at CPPFLAGS. # So we need to set both. CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I${TOOLS_PATH}/deps/include -I${TOOLS_PATH}/deps/include/ncursesw" diff --git a/cpython-unix/patch-site-reentrant-startup-files-3.15.patch b/cpython-unix/patch-site-reentrant-startup-files-3.15.patch deleted file mode 100644 index 355467182..000000000 --- a/cpython-unix/patch-site-reentrant-startup-files-3.15.patch +++ /dev/null @@ -1,158 +0,0 @@ -From 8e4ad95833c302dc3ef1edf0b58eb9ef21f56889 Mon Sep 17 00:00:00 2001 -From: Zsolt Dollenstein -Date: Thu, 7 May 2026 13:11:52 -0700 -Subject: [PATCH] gh-149504: Fix reentrant site startup processing - -Copy and clear pending startup file data before executing import lines or .start entry points so recursive site.addsitedir() calls process a separate batch. ---- - Lib/site.py | 47 ++++++++++++++----- - Lib/test/test_site.py | 23 +++++++++ - ...6-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst | 2 + - 3 files changed, 59 insertions(+), 13 deletions(-) - create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst - -diff --git a/Lib/site.py b/Lib/site.py -index 52dd9648734c3e..766cbed460eac1 100644 ---- a/Lib/site.py -+++ b/Lib/site.py -@@ -163,6 +163,13 @@ def _init_pathinfo(): - _pending_importexecs = {} - - -+def _take_pending(mapping): -+ """Return the pending data and clear it before running startup code.""" -+ pending = mapping.copy() -+ mapping.clear() -+ return pending -+ -+ - def _read_pthstart_file(sitedir, name, suffix): - """Parse a .start or .pth file and return (lines, filename). - -@@ -280,11 +287,14 @@ def _read_start_file(sitedir, name): - entrypoints.append(line) - - --def _extend_syspath(): -+def _extend_syspath(pending_syspaths=None): - # We've already filtered out duplicates, either in the existing sys.path - # or in all the .pth files we've seen. We've also abspath/normpath'd all - # the entries, so all that's left to do is to ensure that the path exists. -- for filename, dirs in _pending_syspaths.items(): -+ if pending_syspaths is None: -+ pending_syspaths = _pending_syspaths -+ -+ for filename, dirs in pending_syspaths.items(): - for dir_ in dirs: - if os.path.exists(dir_): - _trace(f"Extending sys.path with {dir_} from {filename}") -@@ -295,16 +305,21 @@ def _extend_syspath(): - f"skipping sys.path append") - - --def _exec_imports(): -+def _exec_imports(pending_importexecs=None, pending_entrypoints=None): - # For all the `import` lines we've seen in .pth files, exec() them in - # order. However, if they come from a file with a matching .start, then - # we ignore these import lines. For the ones we do process, print a - # warning but only when -v was given. -- for filename, imports in _pending_importexecs.items(): -+ if pending_importexecs is None: -+ pending_importexecs = _pending_importexecs -+ if pending_entrypoints is None: -+ pending_entrypoints = _pending_entrypoints -+ -+ for filename, imports in pending_importexecs.items(): - name, dot, pth = filename.rpartition(".") - assert dot == "." and pth == "pth", f"Bad startup filename: {filename}" - -- if f"{name}.start" in _pending_entrypoints: -+ if f"{name}.start" in pending_entrypoints: - # Skip import lines in favor of entry points. - continue - -@@ -322,7 +337,7 @@ def _exec_imports(): - f"Error in import line from {filename}: {line}", exc) - - --def _execute_start_entrypoints(): -+def _execute_start_entrypoints(pending_entrypoints=None): - """Execute all accumulated .start file entry points. - - Called after all site-packages directories have been processed so that -@@ -330,7 +345,10 @@ def _execute_start_entrypoints(): - pkgutil.resolve_name(strict=True) which both validates the strict - pkg.mod:callable form and resolves the entry point in one step. - """ -- for filename, entrypoints in _pending_entrypoints.items(): -+ if pending_entrypoints is None: -+ pending_entrypoints = _pending_entrypoints -+ -+ for filename, entrypoints in pending_entrypoints.items(): - for entrypoint in entrypoints: - try: - _trace(f"Executing entry point: {entrypoint} from {filename}") -@@ -355,12 +373,15 @@ def _execute_start_entrypoints(): - - def process_startup_files(): - """Flush all pending sys.path and entry points.""" -- _extend_syspath() -- _exec_imports() -- _execute_start_entrypoints() -- _pending_syspaths.clear() -- _pending_importexecs.clear() -- _pending_entrypoints.clear() -+ # Startup code may call addsitedir(), so remove this batch from the -+ # globals before executing any import lines or entry points. -+ pending_syspaths = _take_pending(_pending_syspaths) -+ pending_importexecs = _take_pending(_pending_importexecs) -+ pending_entrypoints = _take_pending(_pending_entrypoints) -+ -+ _extend_syspath(pending_syspaths) -+ _exec_imports(pending_importexecs, pending_entrypoints) -+ _execute_start_entrypoints(pending_entrypoints) - - - def addpackage(sitedir, name, known_paths): -diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py -index ac69e2cbdbbe54..62fc8820f3b6f0 100644 ---- a/Lib/test/test_site.py -+++ b/Lib/test/test_site.py -@@ -1297,6 +1297,29 @@ def startup(): - import epmod - self.assertFalse(epmod.called) - -+ def test_exec_imports_allows_reentrant_addsitedir(self): -+ nested = os.path.join(self.sitedir, 'nested') -+ nestedlib = os.path.join(nested, 'nestedlib') -+ os.mkdir(nested) -+ os.mkdir(nestedlib) -+ with open(os.path.join(nested, 'nested.pth'), 'w', -+ encoding='utf-8') as f: -+ f.write("nestedlib\n") -+ with open(os.path.join(nestedlib, 'nestedmod.py'), 'w', -+ encoding='utf-8') as f: -+ f.write("value = 42\n") -+ self.addCleanup(sys.modules.pop, 'nestedmod', None) -+ -+ outer_pth = os.path.join(self.sitedir, 'outer.pth') -+ site._pending_importexecs[outer_pth] = [ -+ f"import site; site.addsitedir({nested!r}); import nestedmod" -+ ] -+ -+ site.process_startup_files() -+ -+ self.assertIn(nestedlib, sys.path) -+ self.assertEqual(sys.modules['nestedmod'].value, 42) -+ - # --- _extend_syspath tests --- - - def test_extend_syspath_existing_dir(self): -diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst -new file mode 100644 -index 00000000000000..77f72ea2247fe8 ---- /dev/null -+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst -@@ -0,0 +1,2 @@ -+Fix reentrant processing of site startup files when a ``.pth`` import line -+calls :func:`site.addsitedir`. diff --git a/cpython-windows/build.py b/cpython-windows/build.py index d7201f53f..070f99f08 100644 --- a/cpython-windows/build.py +++ b/cpython-windows/build.py @@ -1516,12 +1516,6 @@ def build_cpython( cpython_source_path = td / ("Python-%s" % python_version) pcbuild_path = cpython_source_path / "PCbuild" - if python_version.startswith("3.15."): - apply_source_patch( - cpython_source_path, - SUPPORT / "patch-site-reentrant-startup-files-3.15.patch", - ) - out_dir = td / "out" build_dir = out_dir / "python" / "build" diff --git a/cpython-windows/patch-site-reentrant-startup-files-3.15.patch b/cpython-windows/patch-site-reentrant-startup-files-3.15.patch deleted file mode 100644 index 4c63d6247..000000000 --- a/cpython-windows/patch-site-reentrant-startup-files-3.15.patch +++ /dev/null @@ -1,145 +0,0 @@ -diff --git a/Lib/site.py b/Lib/site.py -index 52dd9648734c3e..766cbed460eac1 100644 ---- a/Lib/site.py -+++ b/Lib/site.py -@@ -163,6 +163,13 @@ def _init_pathinfo(): - _pending_importexecs = {} - - -+def _take_pending(mapping): -+ """Return the pending data and clear it before running startup code.""" -+ pending = mapping.copy() -+ mapping.clear() -+ return pending -+ -+ - def _read_pthstart_file(sitedir, name, suffix): - """Parse a .start or .pth file and return (lines, filename). - -@@ -280,11 +287,14 @@ def _read_start_file(sitedir, name): - entrypoints.append(line) - - --def _extend_syspath(): -+def _extend_syspath(pending_syspaths=None): - # We've already filtered out duplicates, either in the existing sys.path - # or in all the .pth files we've seen. We've also abspath/normpath'd all - # the entries, so all that's left to do is to ensure that the path exists. -- for filename, dirs in _pending_syspaths.items(): -+ if pending_syspaths is None: -+ pending_syspaths = _pending_syspaths -+ -+ for filename, dirs in pending_syspaths.items(): - for dir_ in dirs: - if os.path.exists(dir_): - _trace(f"Extending sys.path with {dir_} from {filename}") -@@ -295,16 +305,21 @@ def _extend_syspath(): - f"skipping sys.path append") - - --def _exec_imports(): -+def _exec_imports(pending_importexecs=None, pending_entrypoints=None): - # For all the `import` lines we've seen in .pth files, exec() them in - # order. However, if they come from a file with a matching .start, then - # we ignore these import lines. For the ones we do process, print a - # warning but only when -v was given. -- for filename, imports in _pending_importexecs.items(): -+ if pending_importexecs is None: -+ pending_importexecs = _pending_importexecs -+ if pending_entrypoints is None: -+ pending_entrypoints = _pending_entrypoints -+ -+ for filename, imports in pending_importexecs.items(): - name, dot, pth = filename.rpartition(".") - assert dot == "." and pth == "pth", f"Bad startup filename: {filename}" - -- if f"{name}.start" in _pending_entrypoints: -+ if f"{name}.start" in pending_entrypoints: - # Skip import lines in favor of entry points. - continue - -@@ -322,7 +337,7 @@ def _exec_imports(): - f"Error in import line from {filename}: {line}", exc) - - --def _execute_start_entrypoints(): -+def _execute_start_entrypoints(pending_entrypoints=None): - """Execute all accumulated .start file entry points. - - Called after all site-packages directories have been processed so that -@@ -330,7 +345,10 @@ def _execute_start_entrypoints(): - pkgutil.resolve_name(strict=True) which both validates the strict - pkg.mod:callable form and resolves the entry point in one step. - """ -- for filename, entrypoints in _pending_entrypoints.items(): -+ if pending_entrypoints is None: -+ pending_entrypoints = _pending_entrypoints -+ -+ for filename, entrypoints in pending_entrypoints.items(): - for entrypoint in entrypoints: - try: - _trace(f"Executing entry point: {entrypoint} from {filename}") -@@ -355,12 +373,15 @@ def _execute_start_entrypoints(): - - def process_startup_files(): - """Flush all pending sys.path and entry points.""" -- _extend_syspath() -- _exec_imports() -- _execute_start_entrypoints() -- _pending_syspaths.clear() -- _pending_importexecs.clear() -- _pending_entrypoints.clear() -+ # Startup code may call addsitedir(), so remove this batch from the -+ # globals before executing any import lines or entry points. -+ pending_syspaths = _take_pending(_pending_syspaths) -+ pending_importexecs = _take_pending(_pending_importexecs) -+ pending_entrypoints = _take_pending(_pending_entrypoints) -+ -+ _extend_syspath(pending_syspaths) -+ _exec_imports(pending_importexecs, pending_entrypoints) -+ _execute_start_entrypoints(pending_entrypoints) - - - def addpackage(sitedir, name, known_paths): -diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py -index ac69e2cbdbbe54..62fc8820f3b6f0 100644 ---- a/Lib/test/test_site.py -+++ b/Lib/test/test_site.py -@@ -1297,6 +1297,29 @@ def startup(): - import epmod - self.assertFalse(epmod.called) - -+ def test_exec_imports_allows_reentrant_addsitedir(self): -+ nested = os.path.join(self.sitedir, 'nested') -+ nestedlib = os.path.join(nested, 'nestedlib') -+ os.mkdir(nested) -+ os.mkdir(nestedlib) -+ with open(os.path.join(nested, 'nested.pth'), 'w', -+ encoding='utf-8') as f: -+ f.write("nestedlib\n") -+ with open(os.path.join(nestedlib, 'nestedmod.py'), 'w', -+ encoding='utf-8') as f: -+ f.write("value = 42\n") -+ self.addCleanup(sys.modules.pop, 'nestedmod', None) -+ -+ outer_pth = os.path.join(self.sitedir, 'outer.pth') -+ site._pending_importexecs[outer_pth] = [ -+ f"import site; site.addsitedir({nested!r}); import nestedmod" -+ ] -+ -+ site.process_startup_files() -+ -+ self.assertIn(nestedlib, sys.path) -+ self.assertEqual(sys.modules['nestedmod'].value, 42) -+ - # --- _extend_syspath tests --- - - def test_extend_syspath_existing_dir(self): -diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst -new file mode 100644 -index 00000000000000..77f72ea2247fe8 ---- /dev/null -+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst -@@ -0,0 +1,2 @@ -+Fix reentrant processing of site startup files when a ``.pth`` import line -+calls :func:`site.addsitedir`.