aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'gentoolkit/pym/gentoolkit/dependencies.py')
-rw-r--r--gentoolkit/pym/gentoolkit/dependencies.py326
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: