Skip the "resume after portage update" routine.
authorZac Medico <zmedico@gentoo.org>
Thu, 17 Nov 2011 23:10:13 +0000 (15:10 -0800)
committerZac Medico <zmedico@gentoo.org>
Thu, 17 Nov 2011 23:10:13 +0000 (15:10 -0800)
Instead, finish the whole job using a copy of the currently running
instance. This allows us to avoid the complexities of emerge --resume,
such as the differences in option handling between different portage
versions, as reported in bug #390819.

pym/_emerge/Scheduler.py
pym/_emerge/depgraph.py
pym/_emerge/resolver/output.py
pym/_emerge/resolver/output_helpers.py
pym/portage/dbapi/_MergeProcess.py
pym/portage/package/ebuild/config.py
pym/portage/package/ebuild/doebuild.py

index 3b53a372366ac7703e38b1d9826188d8f1d12f00..393eeb605970b7427bd42bf382a21132680a7368 100644 (file)
@@ -29,7 +29,8 @@ from portage._sets.base import InternalPackageSet
 from portage.util import ensure_dirs, writemsg, writemsg_level
 from portage.package.ebuild.digestcheck import digestcheck
 from portage.package.ebuild.digestgen import digestgen
-from portage.package.ebuild.doebuild import _check_temp_dir
+from portage.package.ebuild.doebuild import (_check_temp_dir,
+       _prepare_self_update)
 from portage.package.ebuild.prepare_build_dirs import prepare_build_dirs
 
 import _emerge
@@ -75,12 +76,9 @@ class Scheduler(PollScheduler):
                frozenset(["--pretend",
                "--fetchonly", "--fetch-all-uri"])
 
-       _opts_no_restart = frozenset(["--buildpkgonly",
+       _opts_no_self_reinstall = frozenset(["--buildpkgonly",
                "--fetchonly", "--fetch-all-uri", "--pretend"])
 
-       _bad_resume_opts = set(["--ask", "--changelog",
-               "--resume", "--skipfirst"])
-
        class _iface_class(SlotObject):
                __slots__ = ("fetch",
                        "output", "register", "schedule",
@@ -289,6 +287,38 @@ class Scheduler(PollScheduler):
                        self._running_portage = self._pkg(cpv, "installed",
                                self._running_root, installed=True)
 
+       def _handle_self_update(self):
+               """
+               If portage is updating itself, create temporary
+               copies of PORTAGE_BIN_PATH and PORTAGE_PYM_PATH in order
+               to avoid relying on the new versions which may be
+               incompatible. Register an atexit hook to clean up the
+               temporary directories. Pre-load elog modules here since
+               we won't be able to later if they get unmerged (happens
+               when namespace changes).
+               """
+
+               if self._opts_no_self_reinstall.intersection(self.myopts):
+                       return
+
+               for x in self._mergelist:
+                       if not isinstance(x, Package):
+                               continue
+                       if x.operation != "merge":
+                               continue
+                       if x.root != self._running_root.root:
+                               continue
+                       if not portage.dep.match_from_list(
+                               portage.const.PORTAGE_PACKAGE_ATOM, [x]):
+                               continue
+                       if self._running_portage is None or \
+                               self._running_portage.cpv != x.cpv or \
+                               '9999' in x.cpv or \
+                               'git' in x.inherited or \
+                               'git-2' in x.inherited:
+                               _prepare_self_update(self.settings)
+                       break
+
        def _terminate_tasks(self):
                self._status_display.quiet = True
                while self._running_tasks:
@@ -785,100 +815,6 @@ class Scheduler(PollScheduler):
 
                return prefetcher
 
-       def _is_restart_scheduled(self):
-               """
-               Check if the merge list contains a replacement
-               for the current running instance, that will result
-               in restart after merge.
-               @rtype: bool
-               @returns: True if a restart is scheduled, False otherwise.
-               """
-               if self._opts_no_restart.intersection(self.myopts):
-                       return False
-
-               mergelist = self._mergelist
-
-               for i, pkg in enumerate(mergelist):
-                       if self._is_restart_necessary(pkg) and \
-                               i != len(mergelist) - 1:
-                               return True
-
-               return False
-
-       def _is_restart_necessary(self, pkg):
-               """
-               @return: True if merging the given package
-                       requires restart, False otherwise.
-               """
-
-               # Figure out if we need a restart.
-               if pkg.root == self._running_root.root and \
-                       portage.match_from_list(
-                       portage.const.PORTAGE_PACKAGE_ATOM, [pkg]):
-                       if self._running_portage is None:
-                               return True
-                       elif pkg.cpv != self._running_portage.cpv or \
-                               '9999' in pkg.cpv or \
-                               'git' in pkg.inherited or \
-                               'git-2' in pkg.inherited:
-                               return True
-               return False
-
-       def _restart_if_necessary(self, pkg):
-               """
-               Use execv() to restart emerge. This happens
-               if portage upgrades itself and there are
-               remaining packages in the list.
-               """
-
-               if self._opts_no_restart.intersection(self.myopts):
-                       return
-
-               if not self._is_restart_necessary(pkg):
-                       return
-
-               if pkg == self._mergelist[-1]:
-                       return
-
-               self._main_loop_cleanup()
-
-               logger = self._logger
-               pkg_count = self._pkg_count
-               mtimedb = self._mtimedb
-               bad_resume_opts = self._bad_resume_opts
-
-               logger.log(" ::: completed emerge (%s of %s) %s to %s" % \
-                       (pkg_count.curval, pkg_count.maxval, pkg.cpv, pkg.root))
-
-               logger.log(" *** RESTARTING " + \
-                       "emerge via exec() after change of " + \
-                       "portage version.")
-
-               mtimedb["resume"]["mergelist"].remove(list(pkg))
-               mtimedb.commit()
-               portage.run_exitfuncs()
-               # Don't trust sys.argv[0] here because eselect-python may modify it.
-               emerge_binary = os.path.join(portage.const.PORTAGE_BIN_PATH, 'emerge')
-               mynewargv = [emerge_binary, "--resume"]
-               resume_opts = self.myopts.copy()
-               # For automatic resume, we need to prevent
-               # any of bad_resume_opts from leaking in
-               # via EMERGE_DEFAULT_OPTS.
-               resume_opts["--ignore-default-opts"] = True
-               for myopt, myarg in resume_opts.items():
-                       if myopt not in bad_resume_opts:
-                               if myarg is True:
-                                       mynewargv.append(myopt)
-                               elif isinstance(myarg, list):
-                                       # arguments like --exclude that use 'append' action
-                                       for x in myarg:
-                                               mynewargv.append("%s=%s" % (myopt, x))
-                               else:
-                                       mynewargv.append("%s=%s" % (myopt, myarg))
-               # priority only needs to be adjusted on the first run
-               os.environ["PORTAGE_NICENESS"] = "0"
-               os.execv(mynewargv[0], mynewargv)
-
        def _run_pkg_pretend(self):
                """
                Since pkg_pretend output may be important, this method sends all
@@ -1034,6 +970,8 @@ class Scheduler(PollScheduler):
                except self._unknown_internal_error:
                        return 1
 
+               self._handle_self_update()
+
                for root in self.trees:
                        root_config = self.trees[root]["root_config"]
 
@@ -1379,8 +1317,6 @@ class Scheduler(PollScheduler):
                if pkg.installed:
                        return
 
-               self._restart_if_necessary(pkg)
-
                # Call mtimedb.commit() after each merge so that
                # --resume still works after being interrupted
                # by reboot, sigkill or similar.
@@ -1585,10 +1521,7 @@ class Scheduler(PollScheduler):
 
        def _main_loop(self):
 
-               # Only allow 1 job max if a restart is scheduled
-               # due to portage update.
-               if self._is_restart_scheduled() or \
-                       self._opts_no_background.intersection(self.myopts):
+               if self._opts_no_background.intersection(self.myopts):
                        self._set_max_jobs(1)
 
                while self._schedule():
index fda335fcc9fdadb21d6761e00d2639920f76be9f..65fe1fda2f3894effee5ac000cbd5f654e140579 100644 (file)
@@ -100,8 +100,6 @@ class _frozen_depgraph_config(object):
                        self.edebug = 1
                self.spinner = spinner
                self._running_root = trees[trees._running_eroot]["root_config"]
-               self._opts_no_restart = frozenset(["--buildpkgonly",
-                       "--fetchonly", "--fetch-all-uri", "--pretend"])
                self.pkgsettings = {}
                self.trees = {}
                self._trees_orig = trees
index eed30190d56fee517193dcdc3e73be44d2cf76d4..6f1c76c43c166c9986ba32f0add6ec3e7ed09d4d 100644 (file)
@@ -865,27 +865,6 @@ class Display(object):
                                        continue
                                self.print_msg.append((myprint, self.verboseadd, self.repoadd))
 
-                               if not self.conf.tree_display \
-                                       and not self.conf.no_restart \
-                                       and pkg.root == self.conf.running_root.root \
-                                       and match_from_list(PORTAGE_PACKAGE_ATOM, [pkg]) \
-                                       and not self.conf.quiet:
-
-                                       if not self.vardb.cpv_exists(pkg.cpv) or \
-                                               '9999' in pkg.cpv or \
-                                               'git' in pkg.inherited or \
-                                               'git-2' in pkg.inherited:
-                                               if mylist_index < len(mylist) - 1:
-                                                       self.print_msg.append(
-                                                               colorize(
-                                                                       "WARN", "*** Portage will stop merging "
-                                                                       "at this point and reload itself,"
-                                                                       )
-                                                               )
-                                                       self.print_msg.append(
-                                                               colorize("WARN", "    then resume the merge.")
-                                                               )
-
                show_repos = repoadd_set and repoadd_set != set(["0"])
 
                # now finally print out the messages
index b3cdbc4c408cee56038b735abf5bbcd059591eba..dd26534f8c3e1381df0a115a9ef96b97212a2e2a 100644 (file)
@@ -198,7 +198,6 @@ class _DisplayConfig(object):
                self.print_use_string = self.verbosity != 1 or "--verbose" in frozen_config.myopts
                self.changelog = "--changelog" in frozen_config.myopts
                self.edebug = frozen_config.edebug
-               self.no_restart = frozen_config._opts_no_restart.intersection(frozen_config.myopts)
                self.unordered_display = "--unordered-display" in frozen_config.myopts
 
                mywidth = 130
index 34ed03157e30257c0c45bc74ec92fe3d0925a362..c9b628865895ebe458c2218dc884c2fa540758dd 100644 (file)
@@ -2,20 +2,14 @@
 # Distributed under the terms of the GNU General Public License v2
 
 import io
-import shutil
 import signal
-import tempfile
 import traceback
 
 import errno
 import fcntl
 import portage
 from portage import os, _unicode_decode
-from portage.const import PORTAGE_PACKAGE_ATOM
-from portage.dep import match_from_list
 import portage.elog.messages
-from portage.elog import _preload_elog_modules
-from portage.util import ensure_dirs
 from _emerge.PollConstants import PollConstants
 from _emerge.SpawnProcess import SpawnProcess
 
@@ -46,8 +40,6 @@ class MergeProcess(SpawnProcess):
                        settings.reset()
                        settings.setcpv(cpv, mydb=self.mydbapi)
 
-               if not self.unmerge:
-                       self._handle_self_reinstall()
                super(MergeProcess, self)._start()
 
        def _lock_vdb(self):
@@ -69,56 +61,6 @@ class MergeProcess(SpawnProcess):
                        self.vartree.dbapi.unlock()
                        self._locked_vdb = False
 
-       def _handle_self_reinstall(self):
-               """
-               If portage is reinstalling itself, create temporary
-               copies of PORTAGE_BIN_PATH and PORTAGE_PYM_PATH in order
-               to avoid relying on the new versions which may be
-               incompatible. Register an atexit hook to clean up the
-               temporary directories. Pre-load elog modules here since
-               we won't be able to later if they get unmerged (happens
-               when namespace changes).
-               """
-
-               settings = self.settings
-               cpv = settings.mycpv
-               reinstall_self = False
-               if self.settings["ROOT"] == "/" and \
-                       match_from_list(PORTAGE_PACKAGE_ATOM, [cpv]):
-                       inherited = frozenset(self.settings.get('INHERITED', '').split())
-                       if not self.vartree.dbapi.cpv_exists(cpv) or \
-                               '9999' in cpv or \
-                               'git' in inherited or \
-                               'git-2' in inherited:
-                               reinstall_self = True
-
-               if reinstall_self:
-                       # Load lazily referenced portage submodules into memory,
-                       # so imports won't fail during portage upgrade/downgrade.
-                       _preload_elog_modules(self.settings)
-                       portage.proxy.lazyimport._preload_portage_submodules()
-
-                       # Make the temp directory inside $PORTAGE_TMPDIR/portage, since
-                       # it's common for /tmp and /var/tmp to be mounted with the
-                       # "noexec" option (see bug #346899).
-                       build_prefix = os.path.join(settings["PORTAGE_TMPDIR"], "portage")
-                       ensure_dirs(build_prefix)
-                       base_path_tmp = tempfile.mkdtemp(
-                               "", "._portage_reinstall_.", build_prefix)
-                       portage.process.atexit_register(shutil.rmtree, base_path_tmp)
-                       dir_perms = 0o755
-                       for subdir in "bin", "pym":
-                               var_name = "PORTAGE_%s_PATH" % subdir.upper()
-                               var_orig = settings[var_name]
-                               var_new = os.path.join(base_path_tmp, subdir)
-                               settings[var_name] = var_new
-                               settings.backup_changes(var_name)
-                               shutil.copytree(var_orig, var_new, symlinks=True)
-                               os.chmod(var_new, dir_perms)
-                       portage._bin_path = settings['PORTAGE_BIN_PATH']
-                       portage._pym_path = settings['PORTAGE_PYM_PATH']
-                       os.chmod(base_path_tmp, dir_perms)
-
        def _elog_output_handler(self, fd, event):
                output = None
                if event & PollConstants.POLLIN:
index 765a4f77d4509ff84fc1b69b6985b5894590d78e..6d5de92a17290322c51c32611837585b81320e3b 100644 (file)
@@ -22,7 +22,7 @@ from portage import bsd_chflags, \
        load_mod, os, selinux, _unicode_decode
 from portage.const import CACHE_PATH, \
        DEPCACHE_PATH, INCREMENTALS, MAKE_CONF_FILE, \
-       MODULES_FILE_PATH, PORTAGE_BIN_PATH, PORTAGE_PYM_PATH, \
+       MODULES_FILE_PATH, \
        PRIVATE_PATH, PROFILE_PATH, USER_CONFIG_PATH, \
        USER_VIRTUALS_FILE
 from portage.const import _SANDBOX_COMPAT_LEVEL
@@ -722,11 +722,6 @@ class config(object):
                                        self["USERLAND"] = "GNU"
                                self.backup_changes("USERLAND")
 
-                       self["PORTAGE_BIN_PATH"] = PORTAGE_BIN_PATH
-                       self.backup_changes("PORTAGE_BIN_PATH")
-                       self["PORTAGE_PYM_PATH"] = PORTAGE_PYM_PATH
-                       self.backup_changes("PORTAGE_PYM_PATH")
-
                        for var in ("PORTAGE_INST_UID", "PORTAGE_INST_GID"):
                                try:
                                        self[var] = str(int(self.get(var, "0")))
@@ -2088,6 +2083,14 @@ class config(object):
                                        del x[mykey]
 
        def __getitem__(self,mykey):
+
+               # These ones point to temporary values when
+               # portage plans to update itself.
+               if mykey == "PORTAGE_BIN_PATH":
+                       return portage._bin_path
+               elif mykey == "PORTAGE_PYM_PATH":
+                       return portage._pym_path
+
                for d in self.lookuplist:
                        if mykey in d:
                                return d[mykey]
@@ -2133,6 +2136,8 @@ class config(object):
 
        def __iter__(self):
                keys = set()
+               keys.add("PORTAGE_BIN_PATH")
+               keys.add("PORTAGE_PYM_PATH")
                for d in self.lookuplist:
                        keys.update(d)
                return iter(keys)
index bdfcbcc350b5247a77ab443b138164cd55fda3df..49d3e899e48c1ca3232124c9f6612a7edd3ddfcb 100644 (file)
@@ -44,7 +44,7 @@ from portage.dep import Atom, check_required_use, \
 from portage.eapi import eapi_exports_KV, eapi_exports_merge_type, \
        eapi_exports_replace_vars, eapi_has_required_use, \
        eapi_has_src_prepare_and_src_configure, eapi_has_pkg_pretend
-from portage.elog import elog_process
+from portage.elog import elog_process, _preload_elog_modules
 from portage.elog.messages import eerror, eqawarn
 from portage.exception import DigestException, FileNotFound, \
        IncorrectParameter, InvalidDependString, PermissionDenied, \
@@ -1027,6 +1027,7 @@ def doebuild(myebuild, mydo, _unused=None, settings=None, debug=0, listonly=0,
                        # qmerge is a special phase that implies noclean.
                        if "noclean" not in mysettings.features:
                                mysettings.features.add("noclean")
+                       _handle_self_update(mysettings, vartree.dbapi)
                        #qmerge is specifically not supposed to do a runtime dep check
                        retval = merge(
                                mysettings["CATEGORY"], mysettings["PF"], mysettings["D"],
@@ -1043,6 +1044,7 @@ def doebuild(myebuild, mydo, _unused=None, settings=None, debug=0, listonly=0,
                                # so that it's only called once.
                                elog_process(mysettings.mycpv, mysettings)
                        if retval == os.EX_OK:
+                               _handle_self_update(mysettings, vartree.dbapi)
                                retval = merge(mysettings["CATEGORY"], mysettings["PF"],
                                        mysettings["D"], os.path.join(mysettings["PORTAGE_BUILDDIR"],
                                        "build-info"), myroot, mysettings,
@@ -2013,3 +2015,43 @@ def _merge_unicode_error(errors):
                lines.append("")
 
        return lines
+
+def _prepare_self_update(settings):
+       # Load lazily referenced portage submodules into memory,
+       # so imports won't fail during portage upgrade/downgrade.
+       _preload_elog_modules(settings)
+       portage.proxy.lazyimport._preload_portage_submodules()
+
+       # Make the temp directory inside $PORTAGE_TMPDIR/portage, since
+       # it's common for /tmp and /var/tmp to be mounted with the
+       # "noexec" option (see bug #346899).
+       build_prefix = os.path.join(settings["PORTAGE_TMPDIR"], "portage")
+       portage.util.ensure_dirs(build_prefix)
+       base_path_tmp = tempfile.mkdtemp(
+               "", "._portage_reinstall_.", build_prefix)
+       portage.process.atexit_register(shutil.rmtree, base_path_tmp)
+
+       orig_bin_path = portage._bin_path
+       portage._bin_path = os.path.join(base_path_tmp, "bin")
+       shutil.copytree(orig_bin_path, portage._bin_path, symlinks=True)
+
+       orig_pym_path = portage._pym_path
+       portage._pym_path = os.path.join(base_path_tmp, "pym")
+       shutil.copytree(orig_pym_path, portage._pym_path, symlinks=True)
+
+       for dir_path in (base_path_tmp, portage._bin_path, portage._pym_path):
+               os.chmod(dir_path, 0o755)
+
+def _handle_self_update(settings, vardb):
+       cpv = settings.mycpv
+       if settings["ROOT"] == "/" and \
+               portage.dep.match_from_list(
+               portage.const.PORTAGE_PACKAGE_ATOM, [cpv]):
+               inherited = frozenset(settings.get('INHERITED', '').split())
+               if not vardb.cpv_exists(cpv) or \
+                       '9999' in cpv or \
+                       'git' in inherited or \
+                       'git-2' in inherited:
+                       _prepare_self_update(settings)
+                       return True
+       return False