Files
entropy/client/solo/commands/install.py
2012-11-09 09:16:08 +01:00

635 lines
21 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 etpConst, const_convert_to_unicode
from entropy.misc import ParallelTask
from entropy.output import brown, purple, darkred, red, \
blue, darkblue, darkgreen, bold
from entropy.client.interfaces.package import Package
import entropy.tools
from solo.utils import enlightenatom
from solo.commands.descriptor import SoloCommandDescriptor
from solo.commands._manage import SoloManage
class SoloInstall(SoloManage):
"""
Main Solo Install command.
"""
NAME = "install"
ALIASES = ["i"]
ALLOW_UNPRIVILEGED = False
INTRODUCTION = """\
Install or update packages or package files.
"""
SEE_ALSO = "equo-remove(1), equo-config(1)"
def __init__(self, args):
SoloManage.__init__(self, args)
self._commands = {}
self._check_critical_updates = True
def _get_parser(self):
"""
Overridden from SoloCommand.
"""
_commands = {}
descriptor = SoloCommandDescriptor.obtain_descriptor(
SoloInstall.NAME)
parser = argparse.ArgumentParser(
description=descriptor.get_description(),
formatter_class=argparse.RawDescriptionHelpFormatter,
prog="%s %s" % (sys.argv[0], SoloInstall.NAME))
parser.set_defaults(func=self._install)
parser.add_argument(
"packages", nargs='+',
metavar="<package>", help=_("package name"))
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(
"--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 _install(self, entropy_client):
"""
Solo Install 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
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
packages = self._nsargs.packages
exit_st, _show_cfgupd = self._install_action(
entropy_client, deps, recursive,
pretend, ask, verbose, quiet, empty,
config_files, deep, fetch, bdeps,
relaxed, multifetch, packages)
if _show_cfgupd:
self._show_config_files_update(entropy_client)
return exit_st
@staticmethod
def _show_install_queue(entropy_client, inst_repo, run_queue,
removal_queue, ask, pretend, quiet, verbose):
"""
Show expanded installation queue to user.
"""
download_size = 0
unpack_size = 0
on_disk_used_size = 0
on_disk_freed_size = 0
pkgs_install = 0
pkgs_update = 0
pkgs_reinstall = 0
pkgs_downgrade = 0
pkgs_remove = len(removal_queue)
client_settings = entropy_client.ClientSettings()
splitdebug = client_settings['misc']['splitdebug']
if run_queue and ((ask or pretend) and not quiet):
inst_msg = _("These are the packages that would be installed")
entropy_client.output(
"%s:" % (blue(inst_msg),),
header=darkred(" @@ "))
for package_id, repository_id in run_queue:
repo = entropy_client.open_repository(repository_id)
atom = repo.retrieveAtom(package_id)
pkgver = repo.retrieveVersion(package_id)
pkgtag = repo.retrieveTag(package_id)
pkgrev = repo.retrieveRevision(package_id)
pkgslot = repo.retrieveSlot(package_id)
pkgfile = repo.retrieveDownloadURL(package_id)
on_disk_used_size += repo.retrieveOnDiskSize(package_id)
pkgsize = repo.retrieveSize(package_id)
extra_downloads = repo.retrieveExtraDownload(package_id)
for extra_download in extra_downloads:
if not splitdebug and (extra_download['type'] == "debug"):
continue
pkgsize += extra_download['size']
on_disk_used_size += extra_download['disksize']
unpack_size += int(pkgsize) * 2
fetch_path = Package.get_standard_fetch_disk_path(pkgfile)
if not os.path.exists(fetch_path):
download_size += int(pkgsize)
else:
try:
f_size = entropy.tools.get_file_size(fetch_path)
except OSError:
f_size = 0
download_size += pkgsize - f_size
installed_ver = '-1'
installed_tag = ''
installed_rev = 0
inst_repo_s = None
inst_pkg_id, inst_pkg_rc = inst_repo.atomMatch(
entropy.dep.dep_getkey(atom), matchSlot = pkgslot)
if inst_pkg_rc == 0:
installed_ver = inst_repo.retrieveVersion(
inst_pkg_id)
installed_tag = inst_repo.retrieveTag(
inst_pkg_id)
installed_rev = inst_repo.retrieveRevision(
inst_pkg_id)
inst_repo_s = \
inst_repo.getInstalledPackageRepository(
inst_pkg_id)
if inst_repo_s is None:
inst_repo_s = _("Not available")
on_disk_freed_size += inst_repo.retrieveOnDiskSize(
inst_pkg_id)
extra_downloads = inst_repo.retrieveExtraDownload(
inst_pkg_id)
for extra_download in extra_downloads:
on_disk_freed_size += extra_download['disksize']
# statistics generation complete
# if --quiet, we're done doing stuff
if quiet:
continue
inst_meta = (installed_ver, installed_tag, installed_rev,)
avail_meta = (pkgver, pkgtag, pkgrev,)
action = 0
repo_switch = False
if (repository_id != inst_repo_s) and \
(inst_repo_s is not None):
repo_switch = True
if repo_switch:
flags = darkred(" [")
else:
flags = " ["
if inst_repo_s is None:
inst_repo_s = _('Not available')
pkgcmp = entropy_client.get_package_action(
(package_id, repository_id))
if pkgcmp == 0:
pkgs_reinstall += 1
flags += red("R")
action = 1
elif pkgcmp == 1:
pkgs_install += 1
flags += darkgreen("N")
elif pkgcmp == 2:
pkgs_update += 1
if avail_meta == inst_meta:
flags += blue("U") + red("R")
else:
flags += blue("U")
action = 2
else:
pkgs_downgrade += 1
flags += darkblue("D")
action = -1
if repo_switch:
flags += darkred("] ")
else:
flags += "] "
if repo_switch:
repo_info = "[%s->%s] " % (
brown(inst_repo_s),
darkred(repository_id),)
else:
repo_info = "[%s] " % (
brown(repository_id),)
old_info = ""
if action != 0:
old_info = " [%s|%s" % (
blue(installed_ver),
darkred(const_convert_to_unicode(installed_rev)),)
old_tag = "]"
if installed_tag:
old_tag = "|%s%s" % (
darkred(installed_tag),
old_tag,)
old_info += old_tag
entropy_client.output(
"%s%s%s|%s%s" % (
flags,
repo_info,
enlightenatom(atom),
darkred(const_convert_to_unicode(pkgrev)),
old_info,),
header=darkred(" ##"))
delta_size = on_disk_used_size - on_disk_freed_size
needed_size = delta_size
if unpack_size > 0:
needed_size += unpack_size
if (ask or pretend or verbose) and removal_queue:
mytxt = "%s (%s):" % (
blue(_("These are the packages that would be removed")),
bold(_("conflicting/substituted")),
)
entropy_client.output(
mytxt, header=darkred(" @@ "))
for package_id in removal_queue:
atom = inst_repo.retrieveAtom(package_id)
on_disk_freed_size += inst_repo.retrieveOnDiskSize(
package_id)
extra_downloads = inst_repo.retrieveExtraDownload(
package_id)
for extra_download in extra_downloads:
on_disk_freed_size += extra_download['disksize']
installedfrom = inst_repo.getInstalledPackageRepository(
package_id)
if installedfrom is None:
installedfrom = _("Not available")
mytxt = "[%s] %s%s: %s%s %s" % (
purple("W"),
darkred("["),
brown(_("from")),
bold(installedfrom),
darkred("]"),
enlightenatom(atom))
entropy_client.output(mytxt, header=darkred(" ## "))
# if --quiet, there is nothing else to show
if quiet:
return
mytxt = "%s: %s" % (
blue(_("Packages needing to be installed/updated/downgraded")),
darkred(const_convert_to_unicode(len(run_queue))),)
entropy_client.output(mytxt, header=darkred(" @@ "))
mytxt = "%s: %s" % (
blue(_("Packages needing to be removed")),
darkred(const_convert_to_unicode(pkgs_remove)),)
entropy_client.output(mytxt, header=darkred(" @@ "))
if ask or verbose or pretend:
mytxt = "%s: %s" % (
darkgreen(_("Packages needing to be installed")),
darkgreen(const_convert_to_unicode(pkgs_install)),
)
entropy_client.output(
mytxt, header=darkred(" @@ "))
mytxt = "%s: %s" % (
brown(_("Packages needing to be reinstalled")),
brown(const_convert_to_unicode(pkgs_reinstall)),
)
entropy_client.output(
mytxt, header=darkred(" @@ "))
mytxt = "%s: %s" % (
blue(_("Packages needing to be updated")),
blue(const_convert_to_unicode(pkgs_update)),
)
entropy_client.output(
mytxt, header=darkred(" @@ "))
mytxt = "%s: %s" % (
darkred(_("Packages needing to be downgraded")),
darkred(const_convert_to_unicode(pkgs_downgrade)),
)
entropy_client.output(
mytxt, header=darkred(" @@ "))
if download_size > 0:
mysize = const_convert_to_unicode(
entropy.tools.bytes_into_human(download_size))
else:
mysize = const_convert_to_unicode("0b")
mytxt = "%s: %s" % (
blue(_("Download size")),
bold(mysize),
)
entropy_client.output(
mytxt, header=darkred(" @@ "))
if delta_size > 0:
mysizetxt = _("Used disk space")
else:
mysizetxt = _("Freed disk space")
delta_size = -delta_size
delta_human = entropy.tools.bytes_into_human(delta_size)
mytxt = "%s: %s" % (
blue(mysizetxt),
bold(delta_human),
)
entropy_client.output(mytxt, header=darkred(" @@ "))
if needed_size < 0:
needed_size = -needed_size
mytxt = "%s: %s %s" % (
blue(_("You need at least")),
bold(entropy.tools.bytes_into_human(needed_size)),
blue(_("of free space")),
)
entropy_client.output(
mytxt, header=darkred(" @@ "))
# check for disk space and print a warning
target_dir = etpConst['entropyunpackdir']
while not os.path.isdir(target_dir):
target_dir = os.path.dirname(target_dir)
size_match = entropy.tools.check_required_space(target_dir,
needed_size)
if not size_match:
mytxt = "%s: %s" % (
blue(_("You don't have enough space for "
"the installation. Free some space into")),
darkred(target_dir),)
entropy_client.output(
bold(_("Attention")),
header=darkred(" !!! "))
entropy_client.output(
bold(_("Attention")),
header=darkred(" !!! "))
entropy_client.output(
mytxt, header=darkred(" !!! "))
entropy_client.output(
bold(_("Attention")),
header=darkred(" !!! "))
entropy_client.output(
bold(_("Attention")),
header=darkred(" !!! "))
def _install_action(self, entropy_client, deps, recursive,
pretend, ask, verbose, quiet, empty,
config_files, deep, fetch, bdeps,
relaxed, multifetch, packages,
package_matches=None):
"""
Solo Install action implementation.
"""
inst_repo = entropy_client.installed_repository()
self._advise_repository_update(entropy_client)
if self._check_critical_updates:
self._advise_packages_update(entropy_client)
if package_matches is None:
packages = self._scan_packages(
entropy_client, packages)
if not packages:
entropy_client.output(
"%s." % (
darkred(_("No packages found")),),
level="error", importance=1)
return 1, False
else:
packages = package_matches
exit_st = self._show_packages_info(
entropy_client, packages, deps,
ask, pretend, verbose, quiet)
if exit_st != 0:
return 1, False
run_queue, removal_queue = self._generate_install_queue(
entropy_client, packages, deps, empty, deep, relaxed, bdeps,
recursive)
if (run_queue is None) or (removal_queue is None):
return 1, False
self._show_install_queue(
entropy_client, inst_repo, run_queue, removal_queue,
ask, pretend, quiet, verbose)
if ask:
rc = entropy_client.ask_question(
" %s" % (_("Would you like to continue ?"),))
if rc == _("No"):
return 1, False
if pretend:
return 0, True # yes, tell user
if self._interactive:
exit_st = self._accept_license(
entropy_client, inst_repo, run_queue)
if exit_st != 0:
return 1, False
ugc_thread = None
down_data = {}
exit_st = self._download_packages(
entropy_client, run_queue, down_data, multifetch,
True)
if exit_st == 0:
ugc_thread = ParallelTask(
self._signal_ugc, entropy_client, down_data)
ugc_thread.name = "UgcThread"
ugc_thread.start()
elif exit_st != 0:
return 1, False
# is --fetch on? then quit.
if fetch:
if ugc_thread is not None:
ugc_thread.join()
entropy_client.output(
"%s." % (
blue(_("Download complete")),),
header=darkred(" @@ "))
return 0, False
package_set = set(packages)
total = len(run_queue)
for count, pkg_match in enumerate(run_queue, 1):
metaopts = {
'removeconfig': config_files,
}
if pkg_match in package_set:
metaopts['install_source'] = \
etpConst['install_sources']['user']
else:
metaopts['install_source'] = \
etpConst['install_sources']['automatic_dependency']
package_id, repository_id = pkg_match
atom = entropy_client.open_repository(
repository_id).retrieveAtom(package_id)
pkg = None
try:
pkg = entropy_client.Package()
pkg.prepare(pkg_match, "install", metaopts)
xterm_header = "equo (%s) :: %d of %d ::" % (
_("install"), count, total)
entropy_client.output(
purple(atom),
count=(count, total),
header=darkgreen(" +++ ") + ">>> ")
exit_st = pkg.run(xterm_header=xterm_header)
if exit_st != 0:
if ugc_thread is not None:
ugc_thread.join()
return 1, True
finally:
if pkg is not None:
pkg.kill()
if ugc_thread is not None:
ugc_thread.join()
entropy_client.output(
"%s." % (
blue(_("Installation complete")),),
header=darkred(" @@ "))
return 0, True
SoloCommandDescriptor.register(
SoloCommandDescriptor(
SoloInstall,
SoloInstall.NAME,
_("install or update packages or package files"))
)