aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'pym/gentoolkit/dependencies.py')
-rw-r--r--pym/gentoolkit/dependencies.py608
1 files changed, 310 insertions, 298 deletions
diff --git a/pym/gentoolkit/dependencies.py b/pym/gentoolkit/dependencies.py
index 38676a2..f94b82e 100644
--- a/pym/gentoolkit/dependencies.py
+++ b/pym/gentoolkit/dependencies.py
@@ -4,8 +4,8 @@
"""Provides a class for easy calculating dependencies for a given CPV."""
-__docformat__ = 'epytext'
-__all__ = ('Dependencies',)
+__docformat__ = "epytext"
+__all__ = ("Dependencies",)
# =======
# Imports
@@ -23,302 +23,314 @@ from gentoolkit.query import Query
# Classes
# =======
+
class Dependencies(Query):
- """Access a package's dependencies and reverse dependencies.
-
- Example usage:
- >>> from gentoolkit.dependencies import Dependencies
- >>> portage = Dependencies('sys-apps/portage-9999')
- >>> portage
- <Dependencies 'sys-apps/portage-9999'>
- >>> # All methods return gentoolkit.atom.Atom instances
- ... portage.get_depend()
- ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
- [<Atom 'python3? =dev-lang/python-3*'>,
- <Atom '!python3? >=dev-lang/python-2.7'>, ...]
-
- """
- def __init__(self, query, parser=None):
- Query.__init__(self, query)
- 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 = portage.db[portage.root]["porttree"].dbapi.aux_get(self.cpv, envvars)
- except KeyError:
- try:
- result = portage.db[portage.root]["vartree"].dbapi.aux_get(self.cpv, envvars)
- except KeyError:
- return []
- return result
-
- def _get_depend(self, env_vars, raw=False):
- raw_depend = ' '.join(self.environment(env_vars))
- if raw:
- return raw_depend
- try:
- return self.parser(raw_depend)
- except portage.exception.InvalidPackageName as err:
- raise errors.GentoolkitInvalidCPV(err)
-
- def get_depend(self, **kwargs):
- """Get the contents of DEPEND and parse it with self.parser."""
- return self._get_depend(('DEPEND', ), **kwargs)
-
- def get_pdepend(self, **kwargs):
- """Get the contents of PDEPEND and parse it with self.parser."""
- return self._get_depend(('PDEPEND', ), **kwargs)
-
- def get_rdepend(self, **kwargs):
- """Get the contents of RDEPEND and parse it with self.parser."""
- return self._get_depend(('RDEPEND', ), **kwargs)
-
- def get_all_depends(self, **kwargs):
- """Get the contents of ?DEPEND and parse it with self.parser."""
- env_vars = ('DEPEND', 'PDEPEND', 'RDEPEND', 'BDEPEND')
- return self._get_depend(env_vars, **kwargs)
-
- def graph_depends(
- self,
- max_depth=1,
- printer_fn=None,
- # The rest of these are only used internally:
- depth=1,
- 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 = Query(dep.atom).find_best()
- depcache[dep.atom] = pkgdep
- if not pkgdep:
- continue
- elif pkgdep.cpv in seen:
- continue
- if depth <= max_depth or max_depth == 0:
- if printer_fn is not None:
- printer_fn(depth, pkgdep, dep)
- result.append((depth,pkgdep))
-
- seen.add(pkgdep.cpv)
- if depth < max_depth or max_depth == 0:
- # result is passed in and added to directly
- # so rdeps is disposable
- rdeps = pkgdep.deps.graph_depends( # noqa
- 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
- )
- return result
-
- 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-9999')
- >>> # 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(get_installed_cpvs())
- >>> deptree = ffmpeg.graph_reverse_depends(
- ... only_direct=False, # Include indirect revdeps
- ... pkgset=installed) # from installed pkgset
- >>> len(deptree)
- 24
-
- @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:
- raw_depends = pkgdep.get_all_depends(raw=True)
- if self.cp not in raw_depends:
- # fast path for obviously non-matching packages. This saves
- # us the work of instantiating a whole Atom() for *every*
- # dependency of *every* package in pkgset.
- continue
- 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
- # skip it if it's empty
- if tok and tok != '':
- atom = Atom(tok)
- if use_conditional is not None:
- atom.use_conditional = use_conditional
- result.append(atom)
- else:
- message = "dependencies.py: _parser() found an empty " +\
- "dep string token for: %s, deps= %s"
- raise errors.GentoolkitInvalidAtom(message %(self.cpv, deps))
-
- return result
+ """Access a package's dependencies and reverse dependencies.
+
+ Example usage:
+ >>> from gentoolkit.dependencies import Dependencies
+ >>> portage = Dependencies('sys-apps/portage-9999')
+ >>> portage
+ <Dependencies 'sys-apps/portage-9999'>
+ >>> # All methods return gentoolkit.atom.Atom instances
+ ... portage.get_depend()
+ ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ [<Atom 'python3? =dev-lang/python-3*'>,
+ <Atom '!python3? >=dev-lang/python-2.7'>, ...]
+
+ """
+
+ def __init__(self, query, parser=None):
+ Query.__init__(self, query)
+ 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 = portage.db[portage.root]["porttree"].dbapi.aux_get(
+ self.cpv, envvars
+ )
+ except KeyError:
+ try:
+ result = portage.db[portage.root]["vartree"].dbapi.aux_get(
+ self.cpv, envvars
+ )
+ except KeyError:
+ return []
+ return result
+
+ def _get_depend(self, env_vars, raw=False):
+ raw_depend = " ".join(self.environment(env_vars))
+ if raw:
+ return raw_depend
+ try:
+ return self.parser(raw_depend)
+ except portage.exception.InvalidPackageName as err:
+ raise errors.GentoolkitInvalidCPV(err)
+
+ def get_depend(self, **kwargs):
+ """Get the contents of DEPEND and parse it with self.parser."""
+ return self._get_depend(("DEPEND",), **kwargs)
+
+ def get_pdepend(self, **kwargs):
+ """Get the contents of PDEPEND and parse it with self.parser."""
+ return self._get_depend(("PDEPEND",), **kwargs)
+
+ def get_rdepend(self, **kwargs):
+ """Get the contents of RDEPEND and parse it with self.parser."""
+ return self._get_depend(("RDEPEND",), **kwargs)
+
+ def get_all_depends(self, **kwargs):
+ """Get the contents of ?DEPEND and parse it with self.parser."""
+ env_vars = ("DEPEND", "PDEPEND", "RDEPEND", "BDEPEND")
+ return self._get_depend(env_vars, **kwargs)
+
+ def graph_depends(
+ self,
+ max_depth=1,
+ printer_fn=None,
+ # The rest of these are only used internally:
+ depth=1,
+ 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 = Query(dep.atom).find_best()
+ depcache[dep.atom] = pkgdep
+ if not pkgdep:
+ continue
+ elif pkgdep.cpv in seen:
+ continue
+ if depth <= max_depth or max_depth == 0:
+ if printer_fn is not None:
+ printer_fn(depth, pkgdep, dep)
+ result.append((depth, pkgdep))
+
+ seen.add(pkgdep.cpv)
+ if depth < max_depth or max_depth == 0:
+ # result is passed in and added to directly
+ # so rdeps is disposable
+ rdeps = pkgdep.deps.graph_depends( # noqa
+ 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,
+ )
+ return result
+
+ 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-9999')
+ >>> # 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(get_installed_cpvs())
+ >>> deptree = ffmpeg.graph_reverse_depends(
+ ... only_direct=False, # Include indirect revdeps
+ ... pkgset=installed) # from installed pkgset
+ >>> len(deptree)
+ 24
+
+ @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:
+ raw_depends = pkgdep.get_all_depends(raw=True)
+ if self.cp not in raw_depends:
+ # fast path for obviously non-matching packages. This saves
+ # us the work of instantiating a whole Atom() for *every*
+ # dependency of *every* package in pkgset.
+ continue
+ 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
+ # skip it if it's empty
+ if tok and tok != "":
+ atom = Atom(tok)
+ if use_conditional is not None:
+ atom.use_conditional = use_conditional
+ result.append(atom)
+ else:
+ message = (
+ "dependencies.py: _parser() found an empty "
+ + "dep string token for: %s, deps= %s"
+ )
+ raise errors.GentoolkitInvalidAtom(message % (self.cpv, deps))
+
+ return result
+
# vim: set ts=4 sw=4 tw=0: