# Copyright 1999-2012 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import functools import logging import portage from portage import os from portage.dbapi._MergeProcess import MergeProcess from portage.exception import UnsupportedAPIException from portage.util._async.AsyncTaskFuture import AsyncTaskFuture from _emerge.EbuildBuildDir import EbuildBuildDir from _emerge.emergelog import emergelog from _emerge.CompositeTask import CompositeTask from _emerge.unmerge import _unmerge_display class PackageUninstall(CompositeTask): """ Uninstall a package asynchronously in a subprocess. When both parallel-install and ebuild-locks FEATURES are enabled, it is essential for the ebuild-locks code to execute in a subprocess, since the portage.locks module does not behave as desired if we try to lock the same file multiple times concurrently from the same process for ebuild-locks phases such as pkg_setup, pkg_prerm, and pkg_postrm. """ __slots__ = ("world_atom", "ldpath_mtimes", "opts", "pkg", "settings", "_builddir_lock") def _start(self): vardb = self.pkg.root_config.trees["vartree"].dbapi dbdir = vardb.getpath(self.pkg.cpv) if not os.path.exists(dbdir): # Apparently the package got uninstalled # already, so we can safely return early. self.returncode = os.EX_OK self._async_wait() return self.settings.setcpv(self.pkg) cat, pf = portage.catsplit(self.pkg.cpv) myebuildpath = os.path.join(dbdir, pf + ".ebuild") try: portage.doebuild_environment(myebuildpath, "prerm", settings=self.settings, db=vardb) except UnsupportedAPIException: # This is safe to ignore since this function is # guaranteed to set PORTAGE_BUILDDIR even though # it raises UnsupportedAPIException. The error # will be logged when it prevents the pkg_prerm # and pkg_postrm phases from executing. pass self._builddir_lock = EbuildBuildDir( scheduler=self.scheduler, settings=self.settings) self._start_task( AsyncTaskFuture(future=self._builddir_lock.async_lock()), self._start_unmerge) def _start_unmerge(self, lock_task): self._assert_current(lock_task) if lock_task.cancelled: self._default_final_exit(lock_task) return lock_task.future.result() portage.prepare_build_dirs( settings=self.settings, cleanup=True) # Output only gets logged if it comes after prepare_build_dirs() # which initializes PORTAGE_LOG_FILE. retval, pkgmap = _unmerge_display(self.pkg.root_config, self.opts, "unmerge", [self.pkg.cpv], clean_delay=0, writemsg_level=self._writemsg_level) if retval != os.EX_OK: self._async_unlock_builddir(returncode=retval) return self._writemsg_level(">>> Unmerging %s...\n" % (self.pkg.cpv,), noiselevel=-1) self._emergelog("=== Unmerging... (%s)" % (self.pkg.cpv,)) cat, pf = portage.catsplit(self.pkg.cpv) unmerge_task = MergeProcess( mycat=cat, mypkg=pf, settings=self.settings, treetype="vartree", vartree=self.pkg.root_config.trees["vartree"], scheduler=self.scheduler, background=self.background, mydbapi=self.pkg.root_config.trees["vartree"].dbapi, prev_mtimes=self.ldpath_mtimes, logfile=self.settings.get("PORTAGE_LOG_FILE"), unmerge=True) self._start_task(unmerge_task, self._unmerge_exit) def _unmerge_exit(self, unmerge_task): if self._final_exit(unmerge_task) != os.EX_OK: self._emergelog(" !!! unmerge FAILURE: %s" % (self.pkg.cpv,)) else: self._emergelog(" >>> unmerge success: %s" % (self.pkg.cpv,)) self.world_atom(self.pkg) self._async_unlock_builddir(returncode=self.returncode) def _async_unlock_builddir(self, returncode=None): """ Release the lock asynchronously, and if a returncode parameter is given then set self.returncode and notify exit listeners. """ if returncode is not None: # The returncode will be set after unlock is complete. self.returncode = None self._start_task( AsyncTaskFuture(future=self._builddir_lock.async_unlock()), functools.partial(self._unlock_builddir_exit, returncode=returncode)) def _unlock_builddir_exit(self, unlock_task, returncode=None): self._assert_current(unlock_task) if unlock_task.cancelled and returncode is not None: self._default_final_exit(unlock_task) return # Normally, async_unlock should not raise an exception here. unlock_task.future.cancelled() or unlock_task.future.result() if returncode is not None: self.returncode = returncode self._async_wait() def _emergelog(self, msg): emergelog("notitles" not in self.settings.features, msg) def _writemsg_level(self, msg, level=0, noiselevel=0): log_path = self.settings.get("PORTAGE_LOG_FILE") background = self.background if log_path is None: if not (background and level < logging.WARNING): portage.util.writemsg_level(msg, level=level, noiselevel=noiselevel) else: self.scheduler.output(msg, log_path=log_path, level=level, noiselevel=noiselevel)