diff options
Diffstat (limited to 'gentoolkit/pym/gentoolkit/dependencies.py')
-rw-r--r-- | gentoolkit/pym/gentoolkit/dependencies.py | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/gentoolkit/pym/gentoolkit/dependencies.py b/gentoolkit/pym/gentoolkit/dependencies.py new file mode 100644 index 0000000..2d5e28b --- /dev/null +++ b/gentoolkit/pym/gentoolkit/dependencies.py @@ -0,0 +1,326 @@ +# Copyright 2009-2010 Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""Provides a class for easy calculating dependencies for a given CPV.""" + +__docformat__ = 'epytext' +__all__ = ('Dependencies',) + +# ======= +# Imports +# ======= + +import portage +from portage.dep import paren_reduce + +from gentoolkit import errors +from gentoolkit.atom import Atom +from gentoolkit.cpv import CPV +from gentoolkit.helpers import find_best_match, uniqify +from gentoolkit.dbapi import PORTDB, VARDB + +# ======= +# Classes +# ======= + +class Dependencies(CPV): + """Access a package's dependencies and reverse dependencies. + + Example usage: + >>> from gentoolkit.dependencies import Dependencies + >>> portage = Dependencies('sys-apps/portage-2.1.6.13') + >>> portage + <Dependencies 'sys-apps/portage-2.1.6.13'> + >>> # All methods return gentoolkit.atom.Atom instances + ... portage.get_depend() + [<Atom '>=dev-lang/python-2.5'>, <Atom '<dev-lang/python-3.0'>, ...] + + """ + def __init__(self, cpv, op='', parser=None): + if isinstance(cpv, CPV): + self.__dict__.update(cpv.__dict__) + else: + CPV.__init__(self, cpv) + + self.operator = op + self.atom = self.operator + self.cpv + self.use = [] + self.depatom = str() + + # Allow a custom parser function: + self.parser = parser if parser else self._parser + + def __eq__(self, other): + if self.atom != other.atom: + return False + else: + return True + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.atom, self.depatom, tuple(self.use))) + + def __repr__(self): + return "<%s %r>" % (self.__class__.__name__, self.atom) + + def environment(self, envvars): + """Returns predefined env vars DEPEND, SRC_URI, etc.""" + + # Try to use the Portage tree first, since emerge only uses the tree + # when calculating dependencies + try: + result = PORTDB.aux_get(self.cpv, envvars) + except KeyError: + result = VARDB.aux_get(self.cpv, envvars) + return result + + def get_depend(self): + """Get the contents of DEPEND and parse it with self.parser.""" + + try: + return self.parser(self.environment(('DEPEND',))[0]) + except portage.exception.InvalidPackageName, err: + raise errors.GentoolkitInvalidCPV(err) + + def get_pdepend(self): + """Get the contents of PDEPEND and parse it with self.parser.""" + + try: + return self.parser(self.environment(('PDEPEND',))[0]) + except portage.exception.InvalidPackageName, err: + raise errors.GentoolkitInvalidCPV(err) + + def get_rdepend(self): + """Get the contents of RDEPEND and parse it with self.parser.""" + + try: + return self.parser(self.environment(('RDEPEND',))[0]) + except portage.exception.InvalidPackageName, err: + raise errors.GentoolkitInvalidCPV(err) + + def get_all_depends(self): + """Get the contents of ?DEPEND and parse it with self.parser.""" + + env_vars = ('DEPEND', 'PDEPEND', 'RDEPEND') + try: + return self.parser(' '.join(self.environment(env_vars))) + except portage.exception.InvalidPackageName, err: + raise errors.GentoolkitInvalidCPV(err) + + def graph_depends( + self, + max_depth=1, + printer_fn=None, + # The rest of these are only used internally: + depth=0, + seen=None, + depcache=None, + result=None + ): + """Graph direct dependencies for self. + + Optionally gather indirect dependencies. + + @type max_depth: int + @keyword max_depth: Maximum depth to recurse if. + <1 means no maximum depth + >0 means recurse only this depth; + @type printer_fn: callable + @keyword printer_fn: If None, no effect. If set, it will be applied to + each result. + @rtype: list + @return: [(depth, pkg), ...] + """ + if seen is None: + seen = set() + if depcache is None: + depcache = dict() + if result is None: + result = list() + + pkgdep = None + deps = self.get_all_depends() + for dep in deps: + if dep.atom in depcache: + continue + try: + pkgdep = depcache[dep.atom] + except KeyError: + pkgdep = find_best_match(dep.atom) + depcache[dep.atom] = pkgdep + if pkgdep and pkgdep.cpv in seen: + continue + if depth < max_depth or max_depth <= 0: + + if printer_fn is not None: + printer_fn(depth, pkgdep, dep) + if not pkgdep: + continue + + seen.add(pkgdep.cpv) + result.append(( + depth, + pkgdep.deps.graph_depends( + max_depth=max_depth, + printer_fn=printer_fn, + # The rest of these are only used internally: + depth=depth+1, + seen=seen, + depcache=depcache, + result=result + ) + )) + + if depth == 0: + return result + return pkgdep + + def graph_reverse_depends( + self, + pkgset=None, + max_depth=-1, + only_direct=True, + printer_fn=None, + # The rest of these are only used internally: + depth=0, + depcache=None, + seen=None, + result=None + ): + """Graph direct reverse dependencies for self. + + Example usage: + >>> from gentoolkit.dependencies import Dependencies + >>> ffmpeg = Dependencies('media-video/ffmpeg-0.5_p20373') + >>> # I only care about installed packages that depend on me: + ... from gentoolkit.helpers import get_installed_cpvs + >>> # I want to pass in a sorted list. We can pass strings or + ... # Package or Atom types, so I'll use Package to sort: + ... from gentoolkit.package import Package + >>> installed = sorted(Package(x) for x in get_installed_cpvs()) + >>> deptree = ffmpeg.graph_reverse_depends( + ... only_direct=False, # Include indirect revdeps + ... pkgset=installed) # from installed pkgset + >>> len(deptree) + 44 + + @type pkgset: iterable + @keyword pkgset: sorted pkg cpv strings or anything sublassing + L{gentoolkit.cpv.CPV} to use for calculate our revdep graph. + @type max_depth: int + @keyword max_depth: Maximum depth to recurse if only_direct=False. + -1 means no maximum depth; + 0 is the same as only_direct=True; + >0 means recurse only this many times; + @type only_direct: bool + @keyword only_direct: to recurse or not to recurse + @type printer_fn: callable + @keyword printer_fn: If None, no effect. If set, it will be applied to + each L{gentoolkit.atom.Atom} object as it is added to the results. + @rtype: list + @return: L{gentoolkit.dependencies.Dependencies} objects + """ + if not pkgset: + err = ("%s kwarg 'pkgset' must be set. " + "Can be list of cpv strings or any 'intersectable' object.") + raise errors.GentoolkitFatalError(err % (self.__class__.__name__,)) + + if depcache is None: + depcache = dict() + if seen is None: + seen = set() + if result is None: + result = list() + + if depth == 0: + pkgset = tuple(Dependencies(x) for x in pkgset) + + pkgdep = None + for pkgdep in pkgset: + try: + all_depends = depcache[pkgdep] + except KeyError: + all_depends = uniqify(pkgdep.get_all_depends()) + depcache[pkgdep] = all_depends + + dep_is_displayed = False + for dep in all_depends: + # TODO: Add ability to determine if dep is enabled by USE flag. + # Check portage.dep.use_reduce + if dep.intersects(self): + pkgdep.depth = depth + pkgdep.matching_dep = dep + if printer_fn is not None: + printer_fn(pkgdep, dep_is_displayed=dep_is_displayed) + result.append(pkgdep) + dep_is_displayed = True + + # if --indirect specified, call ourselves again with the dep + # Do not call if we have already called ourselves. + if ( + dep_is_displayed and not only_direct and + pkgdep.cpv not in seen and + (depth < max_depth or max_depth == -1) + ): + + seen.add(pkgdep.cpv) + result.append( + pkgdep.graph_reverse_depends( + pkgset=pkgset, + max_depth=max_depth, + only_direct=only_direct, + printer_fn=printer_fn, + depth=depth+1, + depcache=depcache, + seen=seen, + result=result + ) + ) + + if depth == 0: + return result + return pkgdep + + def _parser(self, deps, use_conditional=None, depth=0): + """?DEPEND file parser. + + @rtype: list + @return: L{gentoolkit.atom.Atom} objects + """ + result = [] + + if depth == 0: + deps = paren_reduce(deps) + for tok in deps: + if tok == '||': + continue + if tok[-1] == '?': + use_conditional = tok[:-1] + continue + if isinstance(tok, list): + sub_r = self._parser(tok, use_conditional, depth=depth+1) + result.extend(sub_r) + use_conditional = None + continue + # FIXME: This is a quick fix for bug #299260. + # A better fix is to not discard blockers in the parser, + # but to check for atom.blocker in whatever equery/depends + # (in this case) and ignore them there. + # TODO: Test to see how much a performance impact ignoring + # blockers here rather than checking for atom.blocker has. + if tok[0] == '!': + # We're not interested in blockers + continue + atom = Atom(tok) + if use_conditional is not None: + atom.use_conditional = use_conditional + result.append(atom) + + return result + +# vim: set ts=4 sw=4 tw=0: |