1 # Copyright(c) 2009, Gentoo Foundation
3 # Licensed under the GNU General Public License, v2
7 """Provides a class for easy calculating dependencies for a given CPV."""
9 __docformat__ = 'epytext'
10 __all__ = ('Dependencies',)
17 from portage.dep import paren_reduce
19 from gentoolkit import errors
20 from gentoolkit.atom import Atom
21 from gentoolkit.cpv import CPV
22 from gentoolkit.helpers import uniqify_atoms
23 from gentoolkit.dbapi import PORTDB, VARDB
24 from gentoolkit.query import Query
30 class Dependencies(Query):
31 """Access a package's dependencies and reverse dependencies.
34 >>> from gentoolkit.dependencies import Dependencies
35 >>> portage = Dependencies('sys-apps/portage-2.1.6.13')
37 <Dependencies 'sys-apps/portage-2.1.6.13'>
38 >>> # All methods return gentoolkit.atom.Atom instances
39 ... portage.get_depend()
40 [<Atom '>=dev-lang/python-2.5'>, <Atom '<dev-lang/python-3.0'>, ...]
43 def __init__(self, query, parser=None):
44 Query.__init__(self, query)
48 # Allow a custom parser function:
49 self.parser = parser if parser else self._parser
51 def __eq__(self, other):
52 if self.atom != other.atom:
57 def __ne__(self, other):
58 return not self == other
61 return hash((self.atom, self.depatom, tuple(self.use)))
64 return "<%s %r>" % (self.__class__.__name__, self.atom)
66 def environment(self, envvars):
67 """Returns predefined env vars DEPEND, SRC_URI, etc."""
69 # Try to use the Portage tree first, since emerge only uses the tree
70 # when calculating dependencies
72 result = PORTDB.aux_get(self.cpv, envvars)
75 result = VARDB.aux_get(self.cpv, envvars)
81 """Get the contents of DEPEND and parse it with self.parser."""
84 return self.parser(self.environment(('DEPEND',))[0])
85 except portage.exception.InvalidPackageName as err:
86 raise errors.GentoolkitInvalidCPV(err)
88 def get_pdepend(self):
89 """Get the contents of PDEPEND and parse it with self.parser."""
92 return self.parser(self.environment(('PDEPEND',))[0])
93 except portage.exception.InvalidPackageName as err:
94 raise errors.GentoolkitInvalidCPV(err)
96 def get_rdepend(self):
97 """Get the contents of RDEPEND and parse it with self.parser."""
100 return self.parser(self.environment(('RDEPEND',))[0])
101 except portage.exception.InvalidPackageName as err:
102 raise errors.GentoolkitInvalidCPV(err)
104 def get_all_depends(self):
105 """Get the contents of ?DEPEND and parse it with self.parser."""
107 env_vars = ('DEPEND', 'PDEPEND', 'RDEPEND')
109 return self.parser(' '.join(self.environment(env_vars)))
110 except portage.exception.InvalidPackageName as err:
111 raise errors.GentoolkitInvalidCPV(err)
117 # The rest of these are only used internally:
123 """Graph direct dependencies for self.
125 Optionally gather indirect dependencies.
128 @keyword max_depth: Maximum depth to recurse if.
129 <1 means no maximum depth
130 >0 means recurse only this depth;
131 @type printer_fn: callable
132 @keyword printer_fn: If None, no effect. If set, it will be applied to
135 @return: [(depth, pkg), ...]
145 deps = self.get_all_depends()
147 if dep.atom in depcache:
150 pkgdep = depcache[dep.atom]
152 pkgdep = Query(dep.atom).find_best()
153 depcache[dep.atom] = pkgdep
154 if pkgdep and pkgdep.cpv in seen:
156 if depth < max_depth or max_depth <= 0:
158 if printer_fn is not None:
159 printer_fn(depth, pkgdep, dep)
166 pkgdep.deps.graph_depends(
168 printer_fn=printer_fn,
169 # The rest of these are only used internally:
181 def graph_reverse_depends(
187 # The rest of these are only used internally:
193 """Graph direct reverse dependencies for self.
196 >>> from gentoolkit.dependencies import Dependencies
197 >>> ffmpeg = Dependencies('media-video/ffmpeg-0.5_p20373')
198 >>> # I only care about installed packages that depend on me:
199 ... from gentoolkit.helpers import get_installed_cpvs
200 >>> # I want to pass in a sorted list. We can pass strings or
201 ... # Package or Atom types, so I'll use Package to sort:
202 ... from gentoolkit.package import Package
203 >>> installed = sorted(Package(x) for x in get_installed_cpvs())
204 >>> deptree = ffmpeg.graph_reverse_depends(
205 ... only_direct=False, # Include indirect revdeps
206 ... pkgset=installed) # from installed pkgset
210 @type pkgset: iterable
211 @keyword pkgset: sorted pkg cpv strings or anything sublassing
212 L{gentoolkit.cpv.CPV} to use for calculate our revdep graph.
214 @keyword max_depth: Maximum depth to recurse if only_direct=False.
215 -1 means no maximum depth;
216 0 is the same as only_direct=True;
217 >0 means recurse only this many times;
218 @type only_direct: bool
219 @keyword only_direct: to recurse or not to recurse
220 @type printer_fn: callable
221 @keyword printer_fn: If None, no effect. If set, it will be applied to
222 each L{gentoolkit.atom.Atom} object as it is added to the results.
224 @return: L{gentoolkit.dependencies.Dependencies} objects
227 err = ("%s kwarg 'pkgset' must be set. "
228 "Can be list of cpv strings or any 'intersectable' object.")
229 raise errors.GentoolkitFatalError(err % (self.__class__.__name__,))
239 pkgset = tuple(Dependencies(x) for x in pkgset)
242 for pkgdep in pkgset:
244 all_depends = depcache[pkgdep]
246 all_depends = uniqify_atoms(pkgdep.get_all_depends())
247 depcache[pkgdep] = all_depends
249 dep_is_displayed = False
250 for dep in all_depends:
251 # TODO: Add ability to determine if dep is enabled by USE flag.
252 # Check portage.dep.use_reduce
253 if dep.intersects(self):
255 pkgdep.matching_dep = dep
256 if printer_fn is not None:
257 printer_fn(pkgdep, dep_is_displayed=dep_is_displayed)
258 result.append(pkgdep)
259 dep_is_displayed = True
261 # if --indirect specified, call ourselves again with the dep
262 # Do not call if we have already called ourselves.
264 dep_is_displayed and not only_direct and
265 pkgdep.cpv not in seen and
266 (depth < max_depth or max_depth == -1)
271 pkgdep.graph_reverse_depends(
274 only_direct=only_direct,
275 printer_fn=printer_fn,
287 def _parser(self, deps, use_conditional=None, depth=0):
288 """?DEPEND file parser.
291 @return: L{gentoolkit.atom.Atom} objects
296 deps = paren_reduce(deps)
301 use_conditional = tok[:-1]
303 if isinstance(tok, list):
304 sub_r = self._parser(tok, use_conditional, depth=depth+1)
306 use_conditional = None
308 # FIXME: This is a quick fix for bug #299260.
309 # A better fix is to not discard blockers in the parser,
310 # but to check for atom.blocker in whatever equery/depends
311 # (in this case) and ignore them there.
312 # TODO: Test to see how much a performance impact ignoring
313 # blockers here rather than checking for atom.blocker has.
315 # We're not interested in blockers
317 # skip it if it's empty
318 if tok and tok != '':
320 if use_conditional is not None:
321 atom.use_conditional = use_conditional
324 message = "dependencies.py: _parser() found an empty " +\
325 "dep string token for: %s, deps= %s"
326 raise errors.GentoolkitInvalidAtom(message %(self.cpv, deps))
330 # vim: set ts=4 sw=4 tw=0: