proj/portage:master commit in: lib/portage/package/ebuild/

"Matt Turner" <[email protected]>
Newsgroups gmane.linux.gentoo.cvs
Message-ID <1787000837.1b188f43eb40a3c59c306b0fbd0407435b6cd4a7.mattst88@gentoo>
commit:     1b188f43eb40a3c59c306b0fbd0407435b6cd4a7
Author:     Matt Turner <mattst88 <AT> gentoo <DOT> org>
AuthorDate: Sun Aug 16 21:39:44 2026 +0000
Commit:     Matt Turner <mattst88 <AT> gentoo <DOT> org>
CommitDate: Mon Aug 17 21:07:17 2026 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=1b188f43

doebuild: rebind loaded module paths for self update

_prepare_self_update() copies the running version of portage to a
temporary directory and prepends it to sys.path, so that a portage
update cannot pull modules out from under the running process.

sys.path only governs top-level imports. By the time the update runs,
the portage package is already imported, so an import of a submodule
such as portage.util.env_update is resolved via portage.util.__path__,
which still points at the installed copy. That copy disappears when the
new version installs somewhere else, as happens when PYTHON_TARGETS
changes, and the import then fails with:

    ModuleNotFoundError: No module named 'portage.util.env_update'

Only the multiprocessing forkserver and spawn start methods escaped
this, since their children import portage from scratch and pick up the
sys.path entry. The parent process, and anything forked from it, kept
the stale paths.

Rewrite __path__ of the already imported portage and _emerge packages
to point at the backup copy, so that lazily imported submodules come
from the same snapshot as the rest of the running process.

Bug: https://bugs.gentoo.org/976616
Suggested-by: Kerin Millar <kfm <AT> plushkava.net>
Signed-off-by: Matt Turner <mattst88 <AT> gentoo.org>

 lib/portage/package/ebuild/doebuild.py | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/lib/portage/package/ebuild/doebuild.py b/lib/portage/package/ebuild/doebuild.py
index 249b7003a..ab56e786b 100644
--- a/lib/portage/package/ebuild/doebuild.py
+++ b/lib/portage/package/ebuild/doebuild.py
@@ -3352,6 +3352,34 @@ def _merge_unicode_error(errors):
     return lines
 
 
+def _rebind_loaded_modules(orig_pym_path, new_pym_path):
+    """
+    Rewrite the __path__ of loaded portage packages to point at a
+    temporary backup copy of the running version of portage. Submodule
+    imports are resolved via the __path__ of the parent package rather
+    than via sys.path, so this is what allows a process which has
+    already imported portage to import anything else after the
+    installed copy is replaced or removed (bug 976616).
+    """
+
+    orig_prefix = orig_pym_path.rstrip(os.sep) + os.sep
+
+    def _remap(path):
+        # The __path__ entries are not necessarily resolved, unlike
+        # orig_pym_path.
+        resolved = os.path.realpath(path)
+        if resolved.startswith(orig_prefix):
+            return os.path.join(new_pym_path, resolved[len(orig_prefix) :])
+        return path
+
+    for name, module in list(sys.modules.items()):
+        if name.partition(".")[0] not in PORTAGE_PYM_PACKAGES:
+            continue
+        path = getattr(module, "__path__", None)
+        if path is not None:
+            module.__path__ = [_remap(x) for x in path]
+
+
 def _prepare_self_update(settings):
     """
     Call this when portage is updating itself, in order to create
@@ -3393,6 +3421,11 @@ def _prepare_self_update(settings):
     # multiprocessing forkserver and spawn start methods (bug 965976).
     sys.path.insert(0, portage._pym_path)
 
+    # The sys.path update above does nothing for this process, which
+    # has already imported portage, or for anything forked from it
+    # (bug 976616).
+    _rebind_loaded_modules(orig_pym_path, portage._pym_path)
+
     if multiprocessing.get_start_method() == "forkserver":
 
         def _get_forkserver_pid():
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.