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 find_best_match, uniqify
23 from gentoolkit.dbapi import PORTDB, VARDB
29 class Dependencies(CPV):
30 """Access a package's dependencies and reverse dependencies.
33 >>> from gentoolkit.dependencies import Dependencies
34 >>> portage = Dependencies('sys-apps/portage-2.1.6.13')
36 <Dependencies 'sys-apps/portage-2.1.6.13'>
37 >>> # All methods return gentoolkit.atom.Atom instances
38 ... portage.get_depend()
39 [<Atom '>=dev-lang/python-2.5'>, <Atom '<dev-lang/python-3.0'>, ...]
42 def __init__(self, cpv, op='', parser=None):
43 if isinstance(cpv, CPV):
49 self.atom = self.operator + str(self.cpv)
53 # Allow a custom parser function:
54 self.parser = parser if parser else self._parser
56 def __eq__(self, other):
57 if self.atom != other.atom:
62 def __ne__(self, other):
63 return not self == other
66 return hash((self.atom, self.depatom, tuple(self.use)))
69 return "<%s %r>" % (self.__class__.__name__, self.atom)
71 def environment(self, envvars):
72 """Returns predefined env vars DEPEND, SRC_URI, etc."""
74 # Try to use the Portage tree first, since emerge only uses the tree
75 # when calculating dependencies
77 result = PORTDB.aux_get(str(self.cpv), envvars)
79 result = VARDB.aux_get(str(self.cpv), envvars)
83 """Get the contents of DEPEND and parse it with self.parser."""
86 return self.parser(self.environment(('DEPEND',))[0])
87 except portage.exception.InvalidPackageName, err:
88 raise errors.GentoolkitInvalidCPV(err)
90 def get_pdepend(self):
91 """Get the contents of PDEPEND and parse it with self.parser."""
94 return self.parser(self.environment(('PDEPEND',))[0])
95 except portage.exception.InvalidPackageName, err:
96 raise errors.GentoolkitInvalidCPV(err)
98 def get_rdepend(self):
99 """Get the contents of RDEPEND and parse it with self.parser."""
102 return self.parser(self.environment(('RDEPEND',))[0])
103 except portage.exception.InvalidPackageName, err:
104 raise errors.GentoolkitInvalidCPV(err)
106 def get_all_depends(self):
107 """Get the contents of ?DEPEND and parse it with self.parser."""
109 env_vars = ('DEPEND', 'PDEPEND', 'RDEPEND')
111 return self.parser(' '.join(self.environment(env_vars)))
112 except portage.exception.InvalidPackageName, err:
113 raise errors.GentoolkitInvalidCPV(err)
119 # The rest of these are only used internally:
125 """Graph direct dependencies for self.
127 Optionally gather indirect dependencies.
130 @keyword max_depth: Maximum depth to recurse if.
131 <1 means no maximum depth
132 >0 means recurse only this depth;
133 @type printer_fn: callable
134 @keyword printer_fn: If None, no effect. If set, it will be applied to
137 @return: [(depth, pkg), ...]
147 deps = self.get_all_depends()
149 if dep.atom in depcache:
152 pkgdep = depcache[dep.atom]
154 pkgdep = find_best_match(dep.atom)
155 depcache[dep.atom] = pkgdep
156 if pkgdep and str(pkgdep.cpv) in seen:
158 if depth < max_depth or max_depth <= 0:
160 if printer_fn is not None:
161 printer_fn(depth, pkgdep, dep)
165 seen.add(str(pkgdep.cpv))
168 pkgdep.deps.graph_depends(
170 printer_fn=printer_fn,
171 # The rest of these are only used internally:
183 def graph_reverse_depends(
189 # The rest of these are only used internally:
195 """Graph direct reverse dependencies for self.
198 >>> from gentoolkit.dependencies import Dependencies
199 >>> ffmpeg = Dependencies('media-video/ffmpeg-0.5_p20373')
200 >>> # I only care about installed packages that depend on me:
201 ... from gentoolkit.helpers import get_installed_cpvs
202 >>> # I want to pass in a sorted list. We can pass strings or
203 ... # Package or Atom types, so I'll use Package to sort:
204 ... from gentoolkit.package import Package
205 >>> installed = sorted(Package(x) for x in get_installed_cpvs())
206 >>> deptree = ffmpeg.graph_reverse_depends(
207 ... only_direct=False, # Include indirect revdeps
208 ... pkgset=installed) # from installed pkgset
212 @type pkgset: iterable
213 @keyword pkgset: sorted pkg cpv strings or anything sublassing
214 L{gentoolkit.cpv.CPV} to use for calculate our revdep graph.
216 @keyword max_depth: Maximum depth to recurse if only_direct=False.
217 -1 means no maximum depth;
218 0 is the same as only_direct=True;
219 >0 means recurse only this many times;
220 @type only_direct: bool
221 @keyword only_direct: to recurse or not to recurse
222 @type printer_fn: callable
223 @keyword printer_fn: If None, no effect. If set, it will be applied to
224 each L{gentoolkit.atom.Atom} object as it is added to the results.
226 @return: L{gentoolkit.dependencies.Dependencies} objects
229 err = ("%s kwarg 'pkgset' must be set. "
230 "Can be list of cpv strings or any 'intersectable' object.")
231 raise errors.GentoolkitFatalError(err % (self.__class__.__name__,))
241 pkgset = tuple(Dependencies(x) for x in pkgset)
244 for pkgdep in pkgset:
246 all_depends = depcache[pkgdep]
248 all_depends = uniqify(pkgdep.get_all_depends())
249 depcache[pkgdep] = all_depends
251 dep_is_displayed = False
252 for dep in all_depends:
253 # TODO: Add ability to determine if dep is enabled by USE flag.
254 # Check portage.dep.use_reduce
255 if dep.intersects(self):
257 pkgdep.matching_dep = dep
258 if printer_fn is not None:
259 printer_fn(pkgdep, dep_is_displayed=dep_is_displayed)
260 result.append(pkgdep)
261 dep_is_displayed = True
263 # if --indirect specified, call ourselves again with the dep
264 # Do not call if we have already called ourselves.
266 dep_is_displayed and not only_direct and
267 str(pkgdep.cpv) not in seen and
268 (depth < max_depth or max_depth == -1)
271 seen.add(str(pkgdep.cpv))
273 pkgdep.graph_reverse_depends(
276 only_direct=only_direct,
277 printer_fn=printer_fn,
289 def _parser(self, deps, use_conditional=None, depth=0):
290 """?DEPEND file parser.
293 @return: L{gentoolkit.atom.Atom} objects
298 deps = paren_reduce(deps)
303 use_conditional = tok[:-1]
305 if isinstance(tok, list):
306 sub_r = self._parser(tok, use_conditional, depth=depth+1)
308 use_conditional = None
310 # FIXME: This is a quick fix for bug #299260.
311 # A better fix is to not discard blockers in the parser,
312 # but to check for atom.blocker in whatever equery/depends
313 # (in this case) and ignore them there.
314 # TODO: Test to see how much a performance impact ignoring
315 # blockers here rather than checking for atom.blocker has.
317 # We're not interested in blockers
320 if use_conditional is not None:
321 atom.use_conditional = use_conditional
326 # vim: set ts=4 sw=4 tw=0: