Files
entropy/client/solo/commands/upgrade.py
T
Fabio Erculiani c16a305102 [equo] do not automatically remove unmaintained packages, unless --purge is passed
This addresses the case in where the user may not have run equo with --ask
and unmaintained packages get removed without user consent.
2013-03-12 14:09:10 +00:00

384 lines
13 KiB
Python

# -*- coding: utf-8 -*-
"""
@author: Fabio Erculiani <lxnay@sabayon.org>
@contact: lxnay@sabayon.org
@copyright: Fabio Erculiani
@license: GPL-2
B{Entropy Command Line Client}.
"""
import argparse
import os
import sys
from entropy.i18n import _
from entropy.const import const_convert_to_unicode
from entropy.output import darkred, darkgreen, blue, bold, teal, purple
import entropy.tools
from solo.commands.descriptor import SoloCommandDescriptor
from solo.commands.install import SoloInstall
from solo.commands.remove import SoloRemove
class SoloUpgrade(SoloInstall, SoloRemove):
"""
Main Solo Upgrade command.
"""
NAME = "upgrade"
ALIASES = ["u"]
ALLOW_UNPRIVILEGED = False
INTRODUCTION = """\
Upgrade the system.
"""
SEE_ALSO = "equo-install(1)"
def __init__(self, args):
SoloInstall.__init__(self, args)
SoloRemove.__init__(self, args)
self._commands = {}
self._check_critical_updates = False
def _get_parser(self):
"""
Overridden from SoloCommand.
"""
_commands = {}
descriptor = SoloCommandDescriptor.obtain_descriptor(
SoloUpgrade.NAME)
parser = argparse.ArgumentParser(
description=descriptor.get_description(),
formatter_class=argparse.RawDescriptionHelpFormatter,
prog="%s %s" % (sys.argv[0], SoloUpgrade.NAME))
parser.set_defaults(func=self._upgrade)
mg_group = parser.add_mutually_exclusive_group()
mg_group.add_argument(
"--ask", "-a", action="store_true",
default=False,
help=_("ask before making any changes"))
_commands["--ask"] = {}
_commands["-a"] = {}
mg_group.add_argument(
"--pretend", "-p", action="store_true",
default=False,
help=_("show what would be done"))
_commands["--pretend"] = {}
_commands["-p"] = {}
parser.add_argument(
"--verbose", "-v", action="store_true",
default=False,
help=_("verbose output"))
_commands["--verbose"] = {}
_commands["-v"] = {}
parser.add_argument(
"--quiet", "-q", action="store_true",
default=False,
help=_("quiet output"))
_commands["--quiet"] = {}
_commands["-q"] = {}
parser.add_argument(
"--fetch", action="store_true",
default=False,
help=_("just download packages"))
_commands["--fetch"] = {}
parser.add_argument(
"--bdeps", action="store_true",
default=False,
help=_("include build-time dependencies"))
_commands["--bdeps"] = {}
parser.add_argument(
"--nodeps", action="store_true",
default=False,
help=_("exclude package dependencies"))
_commands["--nodeps"] = {}
parser.add_argument(
"--norecursive", action="store_true",
default=False,
help=_("do not calculate dependencies recursively"))
_commands["--norecursive"] = {}
parser.add_argument(
"--deep", action="store_true",
default=False,
help=_("include dependencies no longer needed"))
_commands["--deep"] = {}
parser.add_argument(
"--empty", action="store_true",
default=False,
help=_("when used with --deep, include virtual packages"))
_commands["--empty"] = {}
parser.add_argument(
"--purge", action="store_true",
default=False,
help=_("remove unmaintained packages, if any. This will respect "
"--ask, --pretend and other switches."))
_commands["--purge"] = {}
parser.add_argument(
"--configfiles", action="store_true",
default=False,
help=_("remove package configuration files no longer needed"))
_commands["--configfiles"] = {}
parser.add_argument(
"--relaxed", action="store_true",
default=False,
help=_("relax dependencies constraints during calculation"))
_commands["--relaxed"] = {}
parser.add_argument(
"--multifetch",
type=int, default=1,
choices=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
help=_("download multiple packages in parallel (max 10)"))
_commands["--multifetch"] = {}
self._commands = _commands
return parser
def bashcomp(self, last_arg):
"""
Overridden from SoloCommand.
"""
self._get_parser() # this will generate self._commands
return self._hierarchical_bashcomp(last_arg, [], self._commands)
def _upgrade(self, entropy_client):
"""
Solo Upgrade command.
"""
deps = not self._nsargs.nodeps
recursive = not self._nsargs.norecursive
pretend = self._nsargs.pretend
ask = self._nsargs.ask
verbose = self._nsargs.verbose
quiet = self._nsargs.quiet
empty = self._nsargs.empty
purge = self._nsargs.purge
config_files = self._nsargs.configfiles
deep = self._nsargs.deep
fetch = self._nsargs.fetch
bdeps = self._nsargs.bdeps
relaxed = self._nsargs.relaxed
multifetch = self._nsargs.multifetch
exit_st, _show_cfgupd = self._upgrade_action(
entropy_client, deps, recursive,
pretend, ask, verbose, quiet, empty, purge,
config_files, deep, fetch, bdeps,
relaxed, multifetch)
if _show_cfgupd:
self._show_config_files_update(entropy_client)
return exit_st
def _upgrade_action(self, entropy_client, deps, recursive,
pretend, ask, verbose, quiet, empty,
purge, config_files, deep, fetch, bdeps,
relaxed, multifetch):
"""
Solo Upgrade action implementation.
"""
entropy_client.output(
"%s: " % (blue(_("Calculating System Updates")),),
darkred(" @@ "))
with entropy_client.Cacher():
outcome = entropy_client.calculate_updates(empty=empty)
update, remove = outcome['update'], outcome['remove']
fine, critical_f = outcome['fine'], outcome['critical_found']
# if critical updates have been found, relaxed is enforced
# as per specifications.
if critical_f:
relaxed = True
if verbose or pretend:
entropy_client.output(
"%s => %s" % (
bold(const_convert_to_unicode(len(update))),
darkgreen(_("Packages matching update")),
),
header=darkred(" @@ "))
entropy_client.output(
"%s => %s" % (
bold(const_convert_to_unicode(len(remove))),
darkred(_("Packages matching not available")),
),
header=darkred(" @@ "))
entropy_client.output(
"%s => %s" % (
bold(const_convert_to_unicode(len(fine))),
blue(_("Packages matching already up to date")),
),
header=darkred(" @@ "))
# disable collisions protection, better
client_settings = entropy_client.ClientSettings()
misc_settings = client_settings['misc']
old_cprotect = misc_settings['collisionprotect']
exit_st = 0
try:
misc_settings['collisionprotect'] = 1
if update:
exit_st, _show_cfgupd = self._install_action(
entropy_client, deps, recursive,
pretend, ask, verbose, quiet, empty,
config_files, deep, fetch, bdeps, False,
relaxed, multifetch, [],
package_matches=update)
if exit_st != 0:
return exit_st, _show_cfgupd
else:
entropy_client.output(
"%s." % (
blue(_("Nothing to update")),),
header=darkred(" @@ "))
finally:
misc_settings['collisionprotect'] = old_cprotect
if not fetch:
manual_removal, remove = \
entropy_client.calculate_orphaned_packages()
remove.sort()
manual_removal.sort()
if manual_removal or remove:
entropy_client.output(
"%s." % (
blue(_("On the system there are "
"packages that are not available "
"anymore in the online repositories")),),
header=darkred(" @@ "))
entropy_client.output(
blue(_("Even if they are usually harmless, "
"it is suggested (after proper verification) "
"to remove them.")),
header=darkred(" @@ "))
if manual_removal:
self._show_removal_info(
entropy_client, manual_removal, manual=True)
if remove:
self._show_removal_info(entropy_client, remove)
if not purge:
entropy_client.output(
blue(_("To automatically remove them, please run "
"equo with --purge.")),
header=darkred(" @@ "))
if remove and purge and not fetch:
do_run = True
rc = 1
if not pretend:
if self._interactive:
rm_options = [_("Yes"), _("No"), _("Selective")]
def fake_callback(s):
return s
input_params = [('answer',
('combo', (_('Repository'), rm_options),),
fake_callback, False)]
data = entropy_client.input_box(
_('Would you like to remove them?'),
input_params
)
if data is None:
return 1, False
rc = data.get('answer', 2)[0]
if rc == 2: # no
do_run = False
elif rc == 3: # selective
new_remove = []
c_repo = entropy_client.installed_repository()
for package_id in remove:
c_atom = c_repo.retrieveAtom(package_id)
if c_atom is None:
continue
c_atom = purple(c_atom)
r_rc = entropy_client.ask_question("[%s] %s" % (
c_atom, _("Remove this?"),))
if r_rc == _("Yes"):
new_remove.append(package_id)
remove = new_remove
if do_run and remove:
# use pretend
exit_st, _show_cfgupd = self._remove_action(
entropy_client, pretend, ask,
deps, deep, empty, recursive,
False, True, [], package_ids=remove)
if exit_st != 0:
return exit_st, _show_cfgupd
else:
entropy_client.output(
"%s." % (blue(_("Nothing to remove")),),
header=darkred(" @@ "))
# run post-branch upgrade hooks, if needed
if not pretend:
# this triggers post-branch upgrade function inside
# Entropy Client SystemSettings plugin
entropy_client.Settings().clear()
if update and not pretend and not fetch:
# if updates have been installed, check if there are more
# to come (perhaps critical updates were installed)
self._upgrade_respawn(entropy_client)
return exit_st, True
def _upgrade_respawn(self, entropy_client):
"""
Respawn the upgrade activity if required.
"""
# It might be an Entropy bug and Entropy was proritized in the
# install queue, ignoring the rest of available packages.
# So, respawning myself again using execvp() should be a much
# better idea.
outcome = entropy_client.calculate_updates()
if outcome['update']:
entropy_client.output(
"%s." % (
purple(_("There are more updates to install, "
"reloading Entropy")),),
header=teal(" @@ "))
# then spawn a new process
entropy_client.shutdown()
# hack to tell the resurrected equo to block on
# locking acquisition
os.environ['__EQUO_LOCKS_BLOCKING__'] = "1"
# we will acquire them again in blocking mode, cross
# fingers
entropy.tools.release_entropy_locks(entropy_client)
os.execvp("equo", sys.argv)
SoloCommandDescriptor.register(
SoloCommandDescriptor(
SoloUpgrade,
SoloUpgrade.NAME,
_("upgrade the system"))
)