#!/usr/bin/python # -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab: # # Copyright (C) 2009 Mounir Lamouri (volkmar) # # Licensed under the GNU General Public License Version 2 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # packagekit imports from packagekit.backend import * from packagekit.progress import * from packagekit.package import PackagekitPackage # portage imports import portage import portage.versions import portage.dep import _emerge.actions import _emerge.stdout_spinner import _emerge.create_depgraph_params import _emerge.AtomArg # layman imports import layman.db import layman.config # misc imports import sys import signal import re from itertools import izip # NOTES: # # Package IDs description: # CAT/PN;PV;KEYWORD;[REPOSITORY|installed] # Last field must be "installed" if installed. Otherwise it's the repo name # # Naming convention: # cpv: category package version, the standard representation of what packagekit # names a package (an ebuild for portage) # TODO: # remove percentage(None) if percentage is used # protection against signal when installing/removing # Map Gentoo categories to the PackageKit group name space class PortagePackageGroups(dict): """ Portage Package categories group representation """ def __init__(self): dict.__init__(self) data = { 'accessibility': { 'name': "Accessibility", 'description': "Accessibility applications", 'categories': ['app-accessibility'], }, 'office': { 'name': "Office", 'description': "Applications used in office environments", 'categories': ['app-office', 'app-pda', 'app-mobilephone', 'app-cdr', 'app-antivirus', 'app-laptop', 'mail-', ], }, 'development': { 'name': "Development", 'description': "Applications or system libraries", 'categories': ['dev-', 'sys-devel'], }, 'system': { 'name': "System", 'description': "System applications or libraries", 'categories': ['sys-'], }, 'games': { 'name': "Games", 'description': "Games, enjoy your spare time", 'categories': ['games-'], }, 'gnome': { 'name': "GNOME Desktop", 'description': \ "Applications and libraries for the GNOME Desktop", 'categories': ['gnome-'], }, 'kde': { 'name': "KDE Desktop", 'description': \ "Applications and libraries for the KDE Desktop", 'categories': ['kde-'], }, 'xfce': { 'name': "XFCE Desktop", 'description': \ "Applications and libraries for the XFCE Desktop", 'categories': ['xfce-'], }, 'lxde': { 'name': "LXDE Desktop", 'description': \ "Applications and libraries for the LXDE Desktop", 'categories': ['lxde-'], }, 'multimedia': { 'name': "Multimedia", 'description': \ "Applications and libraries for Multimedia", 'categories': ['media-'], }, 'networking': { 'name': "Networking", 'description': \ "Applications and libraries for Networking", 'categories': ['net-', 'www-'], }, 'science': { 'name': "Science", 'description': \ "Scientific applications and libraries", 'categories': ['sci-'], }, 'security': { 'name': "Security", 'description': \ "Security orientend applications", 'categories': ['app-antivirus', 'net-analyzer', 'net-firewall'], }, 'x11': { 'name': "X11", 'description': \ "Applications and libraries for X11", 'categories': ['x11-'], }, } self.update(data) class PortageBridge(): ''' Bridge to portage/emerge settings and variabales to help using them and be sure they are always up-to-date. ''' def __init__(self): self.settings = None self.trees = None self.mtimedb = None self.vardb = None self.portdb = None self.root_config = None self.update() def update(self): self.settings, self.trees, self.mtimedb = \ _emerge.actions.load_emerge_config() self.vardb = self.trees[self.settings['ROOT']]['vartree'].dbapi self.portdb = self.trees[self.settings['ROOT']]['porttree'].dbapi self.root_config = self.trees[self.settings['ROOT']]['root_config'] # doing all the changes to settings self.settings.unlock() # we don't want interactive ebuilds self.settings["ACCEPT_PROPERTIES"] = "-interactive" self.settings.backup_changes("ACCEPT_PROPERTIES") # do not log with mod_echo (cleanly prevent some outputs) def filter_echo(x): return x != 'echo' elogs = self.settings["PORTAGE_ELOG_SYSTEM"].split() elogs = filter(filter_echo, elogs) self.settings["PORTAGE_ELOG_SYSTEM"] = ' '.join(elogs) self.settings.backup_changes("PORTAGE_ELOG_SYSTEM") # finally, regenerate settings and lock them again self.settings.regenerate() self.settings.lock() class PackageKitPortageMixin(object): def __init__(self): object.__init__(self) self.pvar = PortageBridge() # TODO: should be removed when using non-verbose function API # FIXME: avoid using /dev/null, dangerous (ro fs) self._dev_null = open('/dev/null', 'w') # TODO: atm, this stack keep tracks of elog messages self._elog_messages = [] self._error_message = "" self._error_phase = "" # TODO: should be removed when using non-verbose function API def _block_output(self): sys.stdout = self._dev_null sys.stderr = self._dev_null # TODO: should be removed when using non-verbose function API def _unblock_output(self): sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ def _is_repo_enabled(self, layman_db, repo_name): if repo_name in layman_db.overlays.keys(): return True return False def _get_search_list(self, keys_list): ''' Get a string composed of keys (separated with spaces). Returns a list of compiled regular expressions. ''' search_list = [] for k in keys_list: # not done entirely by pk-transaction k = re.escape(k) search_list.append(re.compile(k, re.IGNORECASE)) return search_list def _get_portage_categories(self): """ Return a list of available Portage categories """ return self.pvar.settings.categories def _get_portage_category_description(self, category): from xml.dom import minidom data = {} portdir = self.pvar.settings['PORTDIR'] myfile = os.path.join(portdir, category, "metadata.xml") if os.access(myfile, os.R_OK) and os.path.isfile(myfile): doc = minidom.parse(myfile) longdescs = doc.getElementsByTagName("longdescription") for longdesc in longdescs: data[longdesc.getAttribute("lang").strip()] = \ ' '.join([x.strip() for x in \ longdesc.firstChild.data.strip().split("\n")]) # Only return in plain English since Portage doesn't support i18n/l10n return data.get('en', "No description") def _get_portage_groups(self): """ Return an expanded version of PortagePackageGroups """ groups = PortagePackageGroups() categories = self._get_portage_categories() # expand categories for data in list(groups.values()): exp_cats = set() for g_cat in data['categories']: exp_cats.update([x for x in categories if x.startswith(g_cat)]) data['categories'] = sorted(exp_cats) return groups def _get_pk_group(self, cp): """ Return PackageKit group belonging to given Portage package. """ category = portage.versions.catsplit(cp)[0] group_data = [key for key, data in self._get_portage_groups().items() \ if category in data['categories']] try: generic_group_name = group_data.pop(0) except IndexError: return GROUP_UNKNOWN return PackageKitPortageBackend.GROUP_MAP[generic_group_name] def _get_portage_group(self, pk_group): """ Given a PackageKit group identifier, return Portage packages group. """ group_map = PackageKitPortageBackend.GROUP_MAP # reverse dict group_map_reverse = dict((y, x) for x, y in group_map.items()) return group_map_reverse.get(pk_group, 'unknown') def _get_ebuild_settings(self, cpv, metadata): """ Return values of given metadata keys for given Portage CPV. """ settings = portage.config(clone=self.pvar.settings) settings.setcpv(cpv, mydb=metadata) return settings def _is_installed(self, cpv): if self.pvar.vardb.cpv_exists(cpv): return True return False def _is_cpv_valid(self, cpv): if self._is_installed(cpv): # actually if is_installed return True that means cpv is in db return True elif self.pvar.portdb.cpv_exists(cpv): return True return False def _get_real_license_str(self, cpv, metadata): # use conditionals info (w/ USE) in LICENSE and remove || ebuild_settings = self._get_ebuild_settings(cpv, metadata) license = set(portage.flatten(portage.dep.use_reduce( portage.dep.paren_reduce(metadata["LICENSE"]), uselist=ebuild_settings.get("USE", "").split()))) license.discard('||') license = ' '.join(license) return license def _signal_config_update(self): result = list(portage.util.find_updated_config_files( self.pvar.settings['ROOT'], self.pvar.settings.get('CONFIG_PROTECT', '').split())) if result: message = "Some configuration files need updating." message += ";You should use Gentoo's tools to update them (dispatch-conf)" message += ";If you can't do that, ask your system administrator." self.message(MESSAGE_CONFIG_FILES_CHANGED, message) def _get_restricted_fetch_files(self, cpv, metadata): ''' This function checks files in SRC_URI and look if they are in DESTDIR. Missing files are returned. If there is no issue, None is returned. We don't care about digest but only about existance of files. NOTES: - we are assuming the package has RESTRICT='fetch' be sure to call this function only in this case. - we are not using fetch_check because it's not returning missing files so this function is a simplist fetch_check ''' missing_files = [] ebuild_settings = self._get_ebuild_settings(cpv, metadata) files = self.pvar.portdb.getFetchMap(cpv, ebuild_settings['USE'].split()) for f in files: file_path = os.path.join(ebuild_settings["DISTDIR"], f) if not os.access(file_path, os.F_OK): missing_files.append([file_path, files[f]]) if len(missing_files) > 0: return missing_files return None def _check_fetch_restrict(self, packages_list): for p in packages_list: if 'fetch' in p.metadata['RESTRICT']: files = self._get_restricted_fetch_files(p.cpv, p.metadata) if files: message = "Package %s can't download some files." % p.cpv message += ";Please, download manually the followonig file(s):" for x in files: message += ";- %s then copy it to %s" % (' '.join(x[1]), x[0]) self.error(ERROR_RESTRICTED_DOWNLOAD, message) def _elog_listener(self, settings, key, logentries, fulltext): ''' This is a listener for elog. It's called each time elog is emitting log messages (at end of process). We are not using settings and fulltext but they are used by other listeners so we have to keep them as arguments. ''' message = "Messages for package %s:;" % str(key) error_message = "" # building the message for phase in logentries: for entries in logentries[phase]: type = entries[0] messages = entries[1] # TODO: portage.elog.filtering is using upper() should we ? if type == 'LOG': message += ";Information messages:" elif type == 'WARN': message += ";Warning messages:" elif type == 'QA': message += ";QA messages:" elif type == 'ERROR': message += ";Error messages:" self._error_phase = phase else: continue for msg in messages: msg = msg.replace('\n', '') if type == 'ERROR': error_message += msg + ";" message += "; " + msg # add the message to the stack self._elog_messages.append(message) self._error_message = message def _send_merge_error(self, default): # EAPI-2 compliant (at least) # 'other' phase is ignored except this one, every phase should be there if self._error_phase in ("setup", "unpack", "prepare", "configure", "nofetch", "config", "info"): error_type = ERROR_PACKAGE_FAILED_TO_CONFIGURE elif self._error_phase in ("compile", "test"): error_type = ERROR_PACKAGE_FAILED_TO_BUILD elif self._error_phase in ("install", "preinst", "postinst", "package"): error_type = ERROR_PACKAGE_FAILED_TO_INSTALL elif self._error_phase in ("prerm", "postrm"): error_type = ERROR_PACKAGE_FAILED_TO_REMOVE else: error_type = default self.error(error_type, self._error_message) def _get_file_list(self, cpv): cat, pv = portage.versions.catsplit(cpv) db = portage.dblink(cat, pv, self.pvar.settings['ROOT'], self.pvar.settings, treetype="vartree", vartree=self.pvar.vardb) contents = db.getcontents() if not contents: return [] return db.getcontents().keys() def _cmp_cpv(self, cpv1, cpv2): ''' returns 1 if cpv1 > cpv2 returns 0 if cpv1 = cpv2 returns -1 if cpv1 < cpv2 ''' return portage.versions.pkgcmp(portage.versions.pkgsplit(cpv1), portage.versions.pkgsplit(cpv2)) def _get_newest_cpv(self, cpv_list, installed): newer = "" # get the first cpv following the installed rule for cpv in cpv_list: if self._is_installed(cpv) == installed: newer = cpv break if newer == "": return "" for cpv in cpv_list: if self._is_installed(cpv) == installed: if self._cmp_cpv(cpv, newer) == 1: newer = cpv return newer def _get_metadata(self, cpv, keys, in_dict = False, add_cache_keys = False): ''' This function returns required metadata. If in_dict is True, metadata is returned in a dict object. If add_cache_keys is True, cached keys are added to keys in parameter. ''' if self._is_installed(cpv): aux_get = self.pvar.vardb.aux_get if add_cache_keys: keys.extend(list(self.pvar.vardb._aux_cache_keys)) else: aux_get = self.pvar.portdb.aux_get if add_cache_keys: keys.extend(list(self.pvar.portdb._aux_cache_keys)) if in_dict: return dict(izip(keys, aux_get(cpv, keys))) else: return aux_get(cpv, keys) def _get_size(self, cpv): ''' Returns the installed size if the package is installed. Otherwise, the size of files needed to be downloaded. If some required files have been downloaded, only the remaining size will be considered. ''' size = 0 if self._is_installed(cpv): size = self._get_metadata(cpv, ["SIZE"])[0] if size == '': size = 0 else: size = int(size) else: self metadata = self._get_metadata(cpv, ["IUSE", "SLOT"], in_dict=True) package = _emerge.Package.Package( type_name="ebuild", built=False, installed=False, root_config=self.pvar.root_config, cpv=cpv, metadata=metadata) fetch_file = self.pvar.portdb.getfetchsizes(package[2], package.use.enabled) for f in fetch_file: size += fetch_file[f] return size def _get_cpv_slotted(self, cpv_list): cpv_dict = {} for cpv in cpv_list: slot = self._get_metadata(cpv, ["SLOT"])[0] if slot not in cpv_dict: cpv_dict[slot] = [cpv] else: cpv_dict[slot].append(cpv) return cpv_dict def _filter_free(self, cpv_list, fltlist): if not cpv_list: return cpv_list def _has_validLicense(cpv): metadata = self._get_metadata(cpv, ["LICENSE", "USE", "SLOT"], True) return not self.pvar.settings._getMissingLicenses(cpv, metadata) if FILTER_FREE in fltlist or FILTER_NOT_FREE in fltlist: free_licenses = "@FSF-APPROVED" if FILTER_FREE in fltlist: licenses = "-* " + free_licenses elif FILTER_NOT_FREE in fltlist: licenses = "* -" + free_licenses backup_license = self.pvar.settings["ACCEPT_LICENSE"] self.pvar.settings.unlock() self.pvar.settings["ACCEPT_LICENSE"] = licenses self.pvar.settings.backup_changes("ACCEPT_LICENSE") self.pvar.settings.regenerate() cpv_list = filter(_has_validLicense, cpv_list) self.pvar.settings["ACCEPT_LICENSE"] = backup_license self.pvar.settings.backup_changes("ACCEPT_LICENSE") self.pvar.settings.regenerate() self.pvar.settings.lock() return cpv_list def _filter_newest(self, cpv_list, fltlist): if len(cpv_list) == 0: return cpv_list if FILTER_NEWEST not in fltlist: return cpv_list if FILTER_INSTALLED in fltlist: # we have one package per slot, so it's the newest return cpv_list cpv_dict = self._get_cpv_slotted(cpv_list) # slots are sorted (dict), revert them to have newest slots first slots = cpv_dict.keys() slots.reverse() # empty cpv_list, cpv are now in cpv_dict and cpv_list gonna be repop cpv_list = [] for k in slots: # if not_intalled on, no need to check for newest installed if FILTER_NOT_INSTALLED not in fltlist: newest_installed = self._get_newest_cpv(cpv_dict[k], True) if newest_installed != "": cpv_list.append(newest_installed) newest_available = self._get_newest_cpv(cpv_dict[k], False) if newest_available != "": cpv_list.append(newest_available) return cpv_list def _get_all_cp(self, fltlist): # NOTES: # returns a list of cp # # FILTERS: # - installed: ok # - free: ok (should be done with cpv) # - newest: ok (should be finished with cpv) cp_list = [] if FILTER_INSTALLED in fltlist: cp_list = self.pvar.vardb.cp_all() elif FILTER_NOT_INSTALLED in fltlist: cp_list = self.pvar.portdb.cp_all() else: # need installed packages first cp_list = self.pvar.vardb.cp_all() for cp in self.pvar.portdb.cp_all(): if cp not in cp_list: cp_list.append(cp) return cp_list def _get_all_cpv(self, cp, fltlist, filter_newest=True): # NOTES: # returns a list of cpv # # FILTERS: # - installed: ok # - free: ok # - newest: ok cpv_list = [] # populate cpv_list taking care of installed filter if FILTER_INSTALLED in fltlist: cpv_list = self.pvar.vardb.match(cp) elif FILTER_NOT_INSTALLED in fltlist: for cpv in self.pvar.portdb.match(cp): if not self._is_installed(cpv): cpv_list.append(cpv) else: cpv_list = self.pvar.vardb.match(cp) for cpv in self.pvar.portdb.match(cp): if cpv not in cpv_list: cpv_list.append(cpv) # free filter cpv_list = self._filter_free(cpv_list, fltlist) # newest filter if filter_newest: cpv_list = self._filter_newest(cpv_list, fltlist) return cpv_list def _id_to_cpv(self, pkgid): ''' Transform the package id (packagekit) to a cpv (portage) ''' ret = split_package_id(pkgid) if len(ret) < 4: self.error(ERROR_PACKAGE_ID_INVALID, "The package id %s does not contain 4 fields" % pkgid) if '/' not in ret[0]: self.error(ERROR_PACKAGE_ID_INVALID, "The first field of the package id must contain a category") # remove slot info from version field version = ret[1].split(':')[0] return ret[0] + "-" + version def _cpv_to_id(self, cpv): ''' Transform the cpv (portage) to a package id (packagekit) ''' package, version, rev = portage.versions.pkgsplit(cpv) pkg_keywords, repo, slot = self._get_metadata(cpv, ["KEYWORDS", "repository", "SLOT"]) pkg_keywords = pkg_keywords.split() sys_keywords = self.pvar.settings["ACCEPT_KEYWORDS"].split() keywords = [] for x in sys_keywords: if x in pkg_keywords: keywords.append(x) # if no keywords, check in package.keywords if not keywords: key_dict = self.pvar.settings.pkeywordsdict.get( portage.dep.dep_getkey(cpv)) if key_dict: for _, keys in key_dict.iteritems(): for x in keys: keywords.append(x) if not keywords: keywords.append("no keywords") self.message(MESSAGE_UNKNOWN, "No keywords have been found for %s" % cpv) # don't want to see -r0 if rev != "r0": version = version + "-" + rev # add slot info if slot != 0 if slot != '0': version = version + ':' + slot # if installed, repo should be 'installed', packagekit rule if self._is_installed(cpv): repo = "installed" return get_package_id(package, version, ' '.join(keywords), repo) def _get_required_packages(self, cpv_input, recursive): ''' Get a list of cpv and recursive parameter. Returns the list of packages required for cpv list. ''' packages_list = [] myopts = {} myopts["--selective"] = True myopts["--deep"] = True myparams = _emerge.create_depgraph_params.create_depgraph_params( myopts, "remove") depgraph = _emerge.depgraph.depgraph(self.pvar.settings, self.pvar.trees, myopts, myparams, None) # TODO: atm, using FILTER_INSTALLED because it's quicker # and we don't want to manage non-installed packages for cp in self._get_all_cp([FILTER_INSTALLED]): for cpv in self._get_all_cpv(cp, [FILTER_INSTALLED]): depgraph._dynamic_config._dep_stack.append( _emerge.Dependency.Dependency( atom=portage.dep.Atom('=' + cpv), root=self.pvar.settings["ROOT"], parent=None)) if not depgraph._complete_graph(): self.error(ERROR_INTERNAL_ERROR, "Error when generating depgraph") return def _add_children_to_list(packages_list, node): for n in depgraph._dynamic_config.digraph.parent_nodes(node): if n not in packages_list \ and not isinstance(n, _emerge.SetArg.SetArg): packages_list.append(n) _add_children_to_list(packages_list, n) for node in depgraph._dynamic_config.digraph.__iter__(): if isinstance(node, _emerge.SetArg.SetArg): continue if node.cpv in cpv_input: if recursive: _add_children_to_list(packages_list, node) else: for n in \ depgraph._dynamic_config.digraph.parent_nodes(node): if not isinstance(n, _emerge.SetArg.SetArg): packages_list.append(n) # remove cpv_input that may be added to the list def filter_cpv_input(x): return x.cpv not in cpv_input return filter(filter_cpv_input, packages_list) class PackageKitPortageBackend(PackageKitPortageMixin, PackageKitPortageBase): # Portage <-> PackageKit groups map GROUP_MAP = { 'accessibility': GROUP_ACCESSIBILITY, 'development': GROUP_PROGRAMMING, 'games': GROUP_GAMES, 'gnome': GROUP_DESKTOP_GNOME, 'kde': GROUP_DESKTOP_KDE, 'lxde': GROUP_DESKTOP_OTHER, 'multimedia': GROUP_MULTIMEDIA, 'networking': GROUP_NETWORK, 'office': GROUP_OFFICE, 'science': GROUP_SCIENCE, 'system': GROUP_SYSTEM, 'security': GROUP_SECURITY, 'x11': GROUP_OTHER, 'xfce': GROUP_DESKTOP_XFCE, 'unknown': GROUP_UNKNOWN, } def __sigquit(self, signum, frame): raise SystemExit(1) def __init__(self, args): signal.signal(signal.SIGQUIT, self.__sigquit) PackageKitPortageMixin.__init__(self) PackageKitBaseBackend.__init__(self, args) def _package(self, cpv, info=None): desc = self._get_metadata(cpv, ["DESCRIPTION"])[0] if not info: if self._is_installed(cpv): info = INFO_INSTALLED else: info = INFO_AVAILABLE self.package(self._cpv_to_id(cpv), info, desc) def get_categories(self): self.status(STATUS_QUERY) self.allow_cancel(True) categories = self._get_portage_categories() if not categories: self.error(ERROR_GROUP_LIST_INVALID, "no package categories") return for name in categories: summary = self._get_portage_category_description(name) f_name = "/usr/share/pixmaps/portage/%s.png" % (name,) if os.path.isfile(f_name) and os.access(f_name, os.R_OK): icon = name else: icon = "image-missing" cat_id = name # same thing self.category("", cat_id, name, summary, icon) def get_depends(self, filters, pkgs, recursive): # TODO: use only myparams ? # TODO: improve error management / info # FILTERS: # - installed: ok # - free: ok # - newest: ignored because only one version of a package is installed self.status(STATUS_INFO) self.allow_cancel(True) self.percentage(None) fltlist = filters.split(';') cpv_input = [] cpv_list = [] for pkg in pkgs: cpv = self._id_to_cpv(pkg) if not self._is_cpv_valid(cpv): self.error(ERROR_PACKAGE_NOT_FOUND, "Package %s was not found" % pkg) continue cpv_input.append('=' + cpv) myopts = {} myopts["--selective"] = True myopts["--deep"] = True myparams = _emerge.create_depgraph_params.create_depgraph_params( myopts, "") depgraph = _emerge.depgraph.depgraph( self.pvar.settings, self.pvar.trees, myopts, myparams, None) retval, fav = depgraph.select_files(cpv_input) if not retval: self.error(ERROR_DEP_RESOLUTION_FAILED, "Wasn't able to get dependency graph") return def _add_children_to_list(cpv_list, node): for n in depgraph._dynamic_config.digraph.child_nodes(node): if n not in cpv_list: cpv_list.append(n) _add_children_to_list(cpv_list, n) for cpv in cpv_input: for r in depgraph._dynamic_config.digraph.root_nodes(): # TODO: remove things with @ as first char # TODO: or refuse SetArgs if not isinstance(r, _emerge.AtomArg.AtomArg): continue if r.atom == cpv: if recursive: _add_children_to_list(cpv_list, r) else: for n in \ depgraph._dynamic_config.digraph.child_nodes(r): for c in \ depgraph._dynamic_config.digraph.child_nodes(n): cpv_list.append(c) def _filter_uninstall(cpv): return cpv[3] != 'uninstall' def _filter_installed(cpv): return cpv[0] == 'installed' def _filter_not_installed(cpv): return cpv[0] != 'installed' # removing packages going to be uninstalled cpv_list = filter(_filter_uninstall, cpv_list) # install filter if FILTER_INSTALLED in fltlist: cpv_list = filter(_filter_installed, cpv_list) if FILTER_NOT_INSTALLED in fltlist: cpv_list = filter(_filter_not_installed, cpv_list) # now we can change cpv_list to a real cpv list tmp_list = cpv_list[:] cpv_list = [] for x in tmp_list: cpv_list.append(x[2]) del tmp_list # free filter cpv_list = self._filter_free(cpv_list, fltlist) for cpv in cpv_list: # prevent showing input packages if '=' + cpv not in cpv_input: self._package(cpv) def get_details(self, pkgs): self.status(STATUS_INFO) self.allow_cancel(True) self.percentage(0) nb_pkg = float(len(pkgs)) pkg_processed = 0.0 for pkg in pkgs: cpv = self._id_to_cpv(pkg) if not self._is_cpv_valid(cpv): self.error(ERROR_PACKAGE_NOT_FOUND, "Package %s was not found" % pkg) continue metadata = self._get_metadata(cpv, ["DESCRIPTION", "HOMEPAGE", "IUSE", "LICENSE", "SLOT"], in_dict=True) license = self._get_real_license_str(cpv, metadata) self.details(self._cpv_to_id(cpv), license, self._get_pk_group(cpv), metadata["DESCRIPTION"], metadata["HOMEPAGE"], self._get_size(cpv)) pkg_processed += 100.0 self.percentage(int(pkg_processed/nb_pkg)) self.percentage(100) def get_files(self, pkgs): self.status(STATUS_INFO) self.allow_cancel(True) self.percentage(0) nb_pkg = float(len(pkgs)) pkg_processed = 0.0 for pkg in pkgs: cpv = self._id_to_cpv(pkg) if not self._is_cpv_valid(cpv): self.error(ERROR_PACKAGE_NOT_FOUND, "Package %s was not found" % pkg) continue if not self._is_installed(cpv): self.error(ERROR_CANNOT_GET_FILELIST, "get-files is only available for installed packages") continue files = self._get_file_list(cpv) files = sorted(files) files = ";".join(files) self.files(pkg, files) pkg_processed += 100.0 self.percentage(int(pkg_processed/nb_pkg)) self.percentage(100) def get_packages(self, filters): self.status(STATUS_QUERY) self.allow_cancel(True) self.percentage(0) fltlist = filters.split(';') cp_list = self._get_all_cp(fltlist) nb_cp = float(len(cp_list)) cp_processed = 0.0 for cp in self._get_all_cp(fltlist): for cpv in self._get_all_cpv(cp, fltlist): self._package(cpv) cp_processed += 100.0 self.percentage(int(cp_processed/nb_cp)) self.percentage(100) def get_repo_list(self, filters): # NOTES: # use layman API # returns only official and supported repositories # and creates a dummy repo for portage tree self.status(STATUS_INFO) self.allow_cancel(True) self.percentage(None) fltlist = filters.split(';') # get installed and available dbs installed_layman_db = layman.db.DB(layman.config.Config()) available_layman_db = layman.db.RemoteDB(layman.config.Config()) # 'gentoo' is a dummy repo self.repo_detail('gentoo', 'Gentoo Portage tree', True) if FILTER_NOT_DEVELOPMENT not in fltlist: for o in available_layman_db.overlays.keys(): if available_layman_db.overlays[o].is_official() \ and available_layman_db.overlays[o].is_supported(): self.repo_detail(o, o, self._is_repo_enabled(installed_layman_db, o)) def get_requires(self, filters, pkgs, recursive): # TODO: manage non-installed package # FILTERS: # - installed: error atm, see previous TODO # - free: ok # - newest: ignored because only one version of a package is installed self.status(STATUS_RUNNING) self.allow_cancel(True) self.percentage(None) fltlist = filters.split(';') cpv_input = [] cpv_list = [] if FILTER_NOT_INSTALLED in fltlist: self.error(ERROR_CANNOT_GET_REQUIRES, "get-requires returns only installed packages at the moment") return for pkg in pkgs: cpv = self._id_to_cpv(pkg) if not self._is_cpv_valid(cpv): self.error(ERROR_PACKAGE_NOT_FOUND, "Package %s was not found" % pkg) continue if not self._is_installed(cpv): self.error(ERROR_CANNOT_GET_REQUIRES, "get-requires is only available for installed packages at the moment") continue cpv_input.append(cpv) packages_list = self._get_required_packages(cpv_input, recursive) # now we can populate cpv_list cpv_list = [] for p in packages_list: cpv_list.append(p.cpv) del packages_list # free filter cpv_list = self._filter_free(cpv_list, fltlist) for cpv in cpv_list: # prevent showing input packages if '=' + cpv not in cpv_input: self._package(cpv) def get_update_detail(self, pkgs): # TODO: a lot of informations are missing self.status(STATUS_INFO) self.allow_cancel(True) self.percentage(None) for pkg in pkgs: updates = [] obsoletes = "" vendor_url = "" bugzilla_url = "" cve_url = "" cpv = self._id_to_cpv(pkg) if not self.pvar.portdb.cpv_exists(cpv): self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, "could not find %s" % pkg) for cpv in self.pvar.vardb.match(portage.versions.pkgsplit(cpv)[0]): updates.append(cpv) updates = "&".join(updates) # temporarily set vendor_url = homepage homepage = self._get_metadata(cpv, ["HOMEPAGE"])[0] vendor_url = homepage issued = "" updated = "" self.update_detail(pkg, updates, obsoletes, vendor_url, bugzilla_url, cve_url, "none", "No update text", "No ChangeLog", UPDATE_STATE_STABLE, issued, updated) def get_updates(self, filters): # NOTES: # because of a lot of things related to Gentoo, # only world and system packages are can be listed as updates # _except_ for security updates # UPDATE TYPES: # - blocked: wait for feedbacks # - low: TODO: --newuse # - normal: default # - important: none atm # - security: from @security # FILTERS: # - installed: try to update non-installed packages and call me ;) # - free: ok # - newest: ok self.status(STATUS_INFO) self.allow_cancel(True) self.percentage(None) fltlist = filters.split(';') update_candidates = [] cpv_updates = {} cpv_downgra = {} # get system and world packages for s in ["system", "world"]: set = portage.sets.base.InternalPackageSet( initial_atoms=self.pvar.root_config.setconfig.getSetAtoms(s)) for atom in set: update_candidates.append(atom.cp) # check if a candidate can be updated for cp in update_candidates: cpv_list_inst = self.pvar.vardb.match(cp) cpv_list_avai = self.pvar.portdb.match(cp) cpv_dict_inst = self._get_cpv_slotted(cpv_list_inst) cpv_dict_avai = self._get_cpv_slotted(cpv_list_avai) dict_upda = {} dict_down = {} # candidate slots are installed slots slots = cpv_dict_inst.keys() slots.reverse() for s in slots: cpv_list_updates = [] cpv_inst = cpv_dict_inst[s][0] # only one install per slot # the slot can be outdated (not in the tree) if s not in cpv_dict_avai: break tmp_list_avai = cpv_dict_avai[s] tmp_list_avai.reverse() for cpv in tmp_list_avai: if self._cmp_cpv(cpv_inst, cpv) == -1: cpv_list_updates.append(cpv) else: # because the list is sorted break # no update for this slot if len(cpv_list_updates) == 0: if [cpv_inst] == self.pvar.portdb.visible([cpv_inst]): break # really no update else: # that's actually a downgrade or even worst if len(tmp_list_avai) == 0: break # this package is not known in the tree... else: dict_down[s] = [tmp_list_avai.pop()] cpv_list_updates = self._filter_free(cpv_list_updates, fltlist) if len(cpv_list_updates) == 0: break if FILTER_NEWEST in fltlist: best_cpv = portage.versions.best(cpv_list_updates) cpv_list_updates = [best_cpv] dict_upda[s] = cpv_list_updates if len(dict_upda) != 0: cpv_updates[cp] = dict_upda if len(dict_down) != 0: cpv_downgra[cp] = dict_down # get security updates for atom in portage.sets.base.InternalPackageSet( initial_atoms=self.pvar.root_config.setconfig.getSetAtoms("security")): # send update message and remove atom from cpv_updates if atom.cp in cpv_updates: slot = self._get_metadata(atom.cpv, ["SLOT"])[0] if slot in cpv_updates[atom.cp]: tmp_cpv_list = cpv_updates[atom.cp][slot][:] for cpv in tmp_cpv_list: if self._cmp_cpv(cpv, atom.cpv) >= 0: # cpv is a security update and removed from list cpv_updates[atom.cp][slot].remove(cpv) self._package(cpv, INFO_SECURITY) else: # update also non-world and non-system packages if security self._package(atom.cpv, INFO_SECURITY) # downgrades for cp in cpv_downgra: for slot in cpv_downgra[cp]: for cpv in cpv_downgra[cp][slot]: self._package(cpv, INFO_IMPORTANT) # normal updates for cp in cpv_updates: for slot in cpv_updates[cp]: for cpv in cpv_updates[cp][slot]: self._package(cpv, INFO_NORMAL) def simulate_install_packages(self, pkgs): return self._install_packages(False, pkgs, simulate=True) def install_packages(self, only_trusted, pkgs): return self._install_packages(False, pkgs) def _install_packages(self, only_trusted, pkgs, simulate=False): # NOTES: # can't install an already installed packages # even if it happens to be needed in Gentoo but probably not this API # TODO: every merged pkg should emit self.package() # see around _emerge.Scheduler.Scheduler self.status(STATUS_RUNNING) self.allow_cancel(False) self.percentage(None) cpv_list = [] for pkg in pkgs: cpv = self._id_to_cpv(pkg) if not self._is_cpv_valid(cpv): self.error(ERROR_PACKAGE_NOT_FOUND, "Package %s was not found" % pkg) continue if self._is_installed(cpv): self.error(ERROR_PACKAGE_ALREADY_INSTALLED, "Package %s is already installed" % pkg) continue cpv_list.append('=' + cpv) # only_trusted isn't supported # but better to show it after important errors if only_trusted: self.error(ERROR_MISSING_GPG_SIGNATURE, "Portage backend does not support GPG signature") return # creating installation depgraph myopts = {} favorites = [] myparams = _emerge.create_depgraph_params.create_depgraph_params( myopts, "") self.status(STATUS_DEP_RESOLVE) depgraph = _emerge.depgraph.depgraph(self.pvar.settings, self.pvar.trees, myopts, myparams, None) retval, favorites = depgraph.select_files(cpv_list) if not retval: self.error(ERROR_DEP_RESOLUTION_FAILED, "Wasn't able to get dependency graph") return # check fetch restrict, can stop the function via error signal self._check_fetch_restrict(depgraph.altlist()) self.status(STATUS_INSTALL) if simulate: return # get elog messages portage.elog.add_listener(self._elog_listener) try: self._block_output() # compiling/installing mergetask = _emerge.Scheduler.Scheduler(self.pvar.settings, self.pvar.trees, self.pvar.mtimedb, myopts, None, depgraph.altlist(), favorites, depgraph.schedulerGraph()) rval = mergetask.merge() finally: self._unblock_output() # when an error is found print error messages if rval != os.EX_OK: self._send_merge_error(ERROR_PACKAGE_FAILED_TO_INSTALL) # show elog messages and clean portage.elog.remove_listener(self._elog_listener) for msg in self._elog_messages: # TODO: use specific message ? self.message(MESSAGE_UNKNOWN, msg) self._elog_messages = [] self._signal_config_update() def refresh_cache(self, force): # NOTES: can't manage progress even if it could be better # TODO: do not wait for exception, check timestamp # TODO: message if overlay repo has changed (layman) self.status(STATUS_REFRESH_CACHE) self.allow_cancel(False) self.percentage(None) myopts = {'--quiet': True} # get installed and available dbs installed_layman_db = layman.db.DB(layman.config.Config()) if force: timestamp_path = os.path.join( self.pvar.settings["PORTDIR"], "metadata", "timestamp.chk") if os.access(timestamp_path, os.F_OK): os.remove(timestamp_path) try: self._block_output() for o in installed_layman_db.overlays.keys(): installed_layman_db.sync(o, quiet=True) _emerge.actions.action_sync(self.pvar.settings, self.pvar.trees, self.pvar.mtimedb, myopts, "") except: self.error(ERROR_INTERNAL_ERROR, traceback.format_exc()) finally: self._unblock_output() def remove_packages(self, allowdep, autoremove, pkgs): return self._remove_packages(allowdep, autoremove, pkgs) def simulate_remove_packages(self, pkgs): return self._remove_packages(True, False, pkgs, simulate=True) def _remove_packages(self, allowdep, autoremove, pkgs, simulate=False): # TODO: every to-be-removed pkg should emit self.package() # see around _emerge.Scheduler.Scheduler self.status(STATUS_RUNNING) self.allow_cancel(False) self.percentage(None) cpv_list = [] packages = [] required_packages = [] system_packages = [] # get system packages set = portage.sets.base.InternalPackageSet( initial_atoms=self.pvar.root_config.setconfig.getSetAtoms("system")) for atom in set: system_packages.append(atom.cp) # create cpv_list for pkg in pkgs: cpv = self._id_to_cpv(pkg) if not self._is_cpv_valid(cpv): self.error(ERROR_PACKAGE_NOT_FOUND, "Package %s was not found" % pkg) continue if not self._is_installed(cpv): self.error(ERROR_PACKAGE_NOT_INSTALLED, "Package %s is not installed" % pkg) continue # stop removal if a package is in the system set if portage.versions.pkgsplit(cpv)[0] in system_packages: self.error(ERROR_CANNOT_REMOVE_SYSTEM_PACKAGE, "Package %s is a system package. If you really want to remove it, please use portage" % pkg) continue cpv_list.append(cpv) # backend do not implement autoremove if autoremove: self.message(MESSAGE_AUTOREMOVE_IGNORED, "Portage backend do not implement autoremove option") # get packages needing candidates for removal required_packages = self._get_required_packages(cpv_list, recursive=True) # if there are required packages, allowdep must be on if required_packages and not allowdep: self.error(ERROR_DEP_RESOLUTION_FAILED, "Could not perform remove operation has packages are needed by other packages") return # first, we add required packages for p in required_packages: package = _emerge.Package.Package( type_name=p.type_name, built=p.built, installed=p.installed, root_config=p.root_config, cpv=p.cpv, metadata=p.metadata, operation='uninstall') packages.append(package) # and now, packages we want really to remove for cpv in cpv_list: metadata = self._get_metadata(cpv, [], in_dict=True, add_cache_keys=True) package = _emerge.Package.Package( type_name="ebuild", built=True, installed=True, root_config=self.pvar.root_config, cpv=cpv, metadata=metadata, operation="uninstall") packages.append(package) if simulate: return # need to define favorites to remove packages from world set favorites = [] for p in packages: favorites.append('=' + p.cpv) # get elog messages portage.elog.add_listener(self._elog_listener) # now, we can remove try: self._block_output() mergetask = _emerge.Scheduler.Scheduler(self.pvar.settings, self.pvar.trees, self.pvar.mtimedb, mergelist=packages, myopts={}, spinner=None, favorites=favorites, digraph=None) rval = mergetask.merge() finally: self._unblock_output() # when an error is found print error messages if rval != os.EX_OK: self._send_merge_error(ERROR_PACKAGE_FAILED_TO_REMOVE) # show elog messages and clean portage.elog.remove_listener(self._elog_listener) for msg in self._elog_messages: # TODO: use specific message ? self.message(MESSAGE_UNKNOWN, msg) self._elog_messages = [] def repo_enable(self, repoid, enable): # NOTES: use layman API >= 1.2.3 self.status(STATUS_INFO) self.allow_cancel(True) self.percentage(None) # special case: trying to work with gentoo repo if repoid == 'gentoo': if not enable: self.error(ERROR_CANNOT_DISABLE_REPOSITORY, "gentoo repository can't be disabled") return # get installed and available dbs installed_layman_db = layman.db.DB(layman.config.Config()) available_layman_db = layman.db.RemoteDB(layman.config.Config()) # check now for repoid so we don't have to do it after if not repoid in available_layman_db.overlays.keys(): self.error(ERROR_REPO_NOT_FOUND, "Repository %s was not found" % repoid) return # disabling (removing) a db # if repository already disabled, ignoring if not enable and self._is_repo_enabled(installed_layman_db, repoid): try: installed_layman_db.delete(installed_layman_db.select(repoid)) except Exception, e: self.error(ERROR_INTERNAL_ERROR, "Failed to disable repository "+repoid+" : "+str(e)) return # enabling (adding) a db # if repository already enabled, ignoring if enable and not self._is_repo_enabled(installed_layman_db, repoid): try: # TODO: clean the trick to prevent outputs from layman self._block_output() installed_layman_db.add(available_layman_db.select(repoid), quiet=True) self._unblock_output() except Exception, e: self._unblock_output() self.error(ERROR_INTERNAL_ERROR, "Failed to enable repository "+repoid+" : "+str(e)) return def resolve(self, filters, pkgs): self.status(STATUS_QUERY) self.allow_cancel(True) self.percentage(0) fltlist = filters.split(';') cp_list = self._get_all_cp(fltlist) nb_cp = float(len(cp_list)) cp_processed = 0.0 reg_expr = [] for pkg in pkgs: reg_expr.append("^" + re.escape(pkg) + "$") reg_expr = "|".join(reg_expr) # specifications says "be case sensitive" s = re.compile(reg_expr) for cp in cp_list: if s.match(cp): for cpv in self._get_all_cpv(cp, fltlist): self._package(cpv) cp_processed += 100.0 self.percentage(int(cp_processed/nb_cp)) self.percentage(100) def search_details(self, filters, keys): # NOTES: very bad performance self.status(STATUS_QUERY) self.allow_cancel(True) self.percentage(0) fltlist = filters.split(';') cp_list = self._get_all_cp(fltlist) nb_cp = float(len(cp_list)) cp_processed = 0.0 search_list = self._get_search_list(keys) for cp in cp_list: # unfortunatelly, everything is related to cpv, not cp # can't filter cp cpv_list = [] # newest filter can't be executed now # because some cpv are going to be filtered by search conditions # and newest filter could be alterated for cpv in self._get_all_cpv(cp, fltlist, filter_newest=False): match = True metadata = self._get_metadata(cpv, ["DESCRIPTION", "HOMEPAGE", "IUSE", "LICENSE", "repository", "SLOT"], in_dict=True) # update LICENSE to correspond to system settings metadata["LICENSE"] = self._get_real_license_str(cpv, metadata) for s in search_list: found = False for x in metadata: if s.search(metadata[x]): found = True break if not found: match = False break if match: cpv_list.append(cpv) # newest filter cpv_list = self._filter_newest(cpv_list, fltlist) for cpv in cpv_list: self._package(cpv) cp_processed += 100.0 self.percentage(int(cp_processed/nb_cp)) self.percentage(100) def search_file(self, filters, key): # FILTERS: # - ~installed is not accepted (error) # - free: ok # - newest: as only installed, by himself self.status(STATUS_QUERY) self.allow_cancel(True) self.percentage(0) fltlist = filters.split(';') if FILTER_NOT_INSTALLED in fltlist: self.error(ERROR_CANNOT_GET_FILELIST, "search-file isn't available with ~installed filter") return cpv_list = self.pvar.vardb.cpv_all() nb_cpv = 0.0 cpv_processed = 0.0 is_full_path = True if key[0] != "/": is_full_path = False key = re.escape(key) searchre = re.compile("/" + key + "$", re.IGNORECASE) # free filter cpv_list = self._filter_free(cpv_list, fltlist) nb_cpv = float(len(cpv_list)) for cpv in cpv_list: for f in self._get_file_list(cpv): if (is_full_path and key == f) \ or (not is_full_path and searchre.search(f)): self._package(cpv) break cpv_processed += 100.0 self.percentage(int(cpv_processed/nb_cpv)) self.percentage(100) def search_group(self, filters, groups): # TODO: filter unknown groups before searching ? (optimization) self.status(STATUS_QUERY) self.allow_cancel(True) self.percentage(0) fltlist = filters.split(';') cp_list = self._get_all_cp(fltlist) nb_cp = float(len(cp_list)) cp_processed = 0.0 for cp in cp_list: for group in groups: if self._get_pk_group(cp) == group: for cpv in self._get_all_cpv(cp, fltlist): self._package(cpv) cp_processed += 100.0 self.percentage(int(cp_processed/nb_cp)) self.percentage(100) def search_name(self, filters, keys_list): # searching for all keys in package name # also filtering by categories if categery is specified in a key # keys contain more than one category name, no results can be found self.status(STATUS_QUERY) self.allow_cancel(True) self.percentage(0) categories = [] for k in keys_list[:]: if "/" in k: cat, cp = portage.versions.catsplit(k) categories.append(cat) keys_list[keys_list.index(k)] = cp category_filter = None if len(categories) > 1: # nothing will be found because we have two cat/pkg # with a AND operator search return elif len(categories) == 1: category_filter = categories[0] # do not use self._get_search_list because of this category feature search_list = [] for k in keys_list: # not done entirely by pk-transaction k = re.escape(k) search_list.append(re.compile(k, re.IGNORECASE)) fltlist = filters.split(';') cp_list = self._get_all_cp(fltlist) nb_cp = float(len(cp_list)) cp_processed = 0.0 for cp in cp_list: if category_filter: cat, pkg_name = portage.versions.catsplit(cp) if cat != category_filter: continue else: pkg_name = portage.versions.catsplit(cp)[1] found = True # pkg name has to correspond to _every_ keys for s in search_list: if not s.search(pkg_name): found = False break if found: for cpv in self._get_all_cpv(cp, fltlist): self._package(cpv) cp_processed += 100.0 self.percentage(int(cp_processed/nb_cp)) self.percentage(100) def update_packages(self, only_trusted, pkgs): return self._update_packages(only_trusted, pkgs) def simulate_update_packages(self, pkgs): return self._update_packages(False, pkgs, simulate=True) def _update_packages(self, only_trusted, pkgs, simulate=False): # TODO: manage errors # TODO: manage config file updates # TODO: every updated pkg should emit self.package() # see around _emerge.Scheduler.Scheduler self.status(STATUS_RUNNING) self.allow_cancel(False) self.percentage(None) cpv_list = [] for pkg in pkgs: cpv = self._id_to_cpv(pkg) if not self._is_cpv_valid(cpv): self.error(ERROR_UPDATE_NOT_FOUND, "Package %s was not found" % pkg) continue cpv_list.append('=' + cpv) # only_trusted isn't supported # but better to show it after important errors if only_trusted: self.error(ERROR_MISSING_GPG_SIGNATURE, "Portage backend does not support GPG signature") return # creating update depgraph myopts = {} favorites = [] myparams = _emerge.create_depgraph_params.create_depgraph_params( myopts, "") self.status(STATUS_DEP_RESOLVE) depgraph = _emerge.depgraph.depgraph(self.pvar.settings, self.pvar.trees, myopts, myparams, None) retval, favorites = depgraph.select_files(cpv_list) if not retval: self.error(ERROR_DEP_RESOLUTION_FAILED, "Wasn't able to get dependency graph") return # check fetch restrict, can stop the function via error signal self._check_fetch_restrict(depgraph.altlist()) self.status(STATUS_INSTALL) if simulate: return # get elog messages portage.elog.add_listener(self._elog_listener) try: self._block_output() # compiling/installing mergetask = _emerge.Scheduler.Scheduler(self.pvar.settings, self.pvar.trees, self.pvar.mtimedb, myopts, None, depgraph.altlist(), favorites, depgraph.schedulerGraph()) rval = mergetask.merge() finally: self._unblock_output() # when an error is found print error messages if rval != os.EX_OK: self._send_merge_error(ERROR_PACKAGE_FAILED_TO_INSTALL) # show elog messages and clean portage.elog.remove_listener(self._elog_listener) for msg in self._elog_messages: # TODO: use specific message ? self.message(MESSAGE_UNKNOWN, msg) self._elog_messages = [] self._signal_config_update() def update_system(self, only_trusted): self.status(STATUS_RUNNING) self.allow_cancel(False) self.percentage(None) if only_trusted: self.error(ERROR_MISSING_GPG_SIGNATURE, "Portage backend does not support GPG signature") return myopts = {} myopts["--deep"] = True myopts["--newuse"] = True myopts["--update"] = True myparams = _emerge.create_depgraph_params.create_depgraph_params( myopts, "") self.status(STATUS_DEP_RESOLVE) # creating list of ebuilds needed for the system update # using backtrack_depgraph to prevent errors retval, depgraph, _ = _emerge.depgraph.backtrack_depgraph( self.pvar.settings, self.pvar.trees, myopts, myparams, "", ["@system", "@world"], None) if not retval: self.error(ERROR_INTERNAL_ERROR, "Wasn't able to get dependency graph") return # check fetch restrict, can stop the function via error signal self._check_fetch_restrict(depgraph.altlist()) self.status(STATUS_INSTALL) # get elog messages portage.elog.add_listener(self._elog_listener) try: self._block_output() # compiling/installing mergetask = _emerge.Scheduler.Scheduler(self.pvar.settings, self.pvar.trees, self.pvar.mtimedb, myopts, None, depgraph.altlist(), None, depgraph.schedulerGraph()) rval = mergetask.merge() finally: self._unblock_output() # when an error is found print error messages if rval != os.EX_OK: self._send_merge_error(ERROR_PACKAGE_FAILED_TO_INSTALL) # show elog messages and clean portage.elog.remove_listener(self._elog_listener) for msg in self._elog_messages: # TODO: use specific message ? self.message(MESSAGE_UNKNOWN, msg) self._elog_messages = [] self._signal_config_update() def main(): backend = PackageKitPortageBackend("") backend.dispatcher(sys.argv[1:]) if __name__ == "__main__": main()