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
23 from gentoolkit.query import Query
29 class Dependencies(Query):
30 """Access a package's dependencies and reverse dependencies.
33 >>> from gentoolkit.dependencies import Dependencies
34 >>> portage = Dependencies('sys-apps/portage-9999')
36 <Dependencies 'sys-apps/portage-9999'>
37 >>> # All methods return gentoolkit.atom.Atom instances
38 ... portage.get_depend()
39 ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
40 [<Atom 'python3? =dev-lang/python-3*'>,
41 <Atom '!python3? >=dev-lang/python-2.7'>, ...]
44 def __init__(self, query, parser=None):
45 Query.__init__(self, query)
49 # Allow a custom parser function:
50 self.parser = parser if parser else self._parser
52 def __eq__(self, other):
53 if self.atom != other.atom:
58 def __ne__(self, other):
59 return not self == other
62 return hash((self.atom, self.depatom, tuple(self.use)))
65 return "<%s %r>" % (self.__class__.__name__, self.atom)
67 def environment(self, envvars):
68 """Returns predefined env vars DEPEND, SRC_URI, etc."""
70 # Try to use the Portage tree first, since emerge only uses the tree
71 # when calculating dependencies
73 result = portage.db[portage.root]["porttree"].dbapi.aux_get(self.cpv, envvars)
76 result = portage.db[portage.root]["vartree"].dbapi.aux_get(self.cpv, envvars)
82 """Get the contents of DEPEND and parse it with self.parser."""
85 return self.parser(self.environment(('DEPEND',))[0])
86 except portage.exception.InvalidPackageName as err:
87 raise errors.GentoolkitInvalidCPV(err)
89 def get_pdepend(self):
90 """Get the contents of PDEPEND and parse it with self.parser."""
93 return self.parser(self.environment(('PDEPEND',))[0])
94 except portage.exception.InvalidPackageName as err:
95 raise errors.GentoolkitInvalidCPV(err)
97 def get_rdepend(self):
98 """Get the contents of RDEPEND and parse it with self.parser."""
101 return self.parser(self.environment(('RDEPEND',))[0])
102 except portage.exception.InvalidPackageName as err:
103 raise errors.GentoolkitInvalidCPV(err)
105 def get_all_depends(self):
106 """Get the contents of ?DEPEND and parse it with self.parser."""
108 env_vars = ('DEPEND', 'PDEPEND', 'RDEPEND')
110 return self.parser(' '.join(self.environment(env_vars)))
111 except portage.exception.InvalidPackageName as err:
112 raise errors.GentoolkitInvalidCPV(err)
118 # The rest of these are only used internally:
124 """Graph direct dependencies for self.
126 Optionally gather indirect dependencies.
129 @keyword max_depth: Maximum depth to recurse if.
130 <1 means no maximum depth
131 >0 means recurse only this depth;
132 @type printer_fn: callable
133 @keyword printer_fn: If None, no effect. If set, it will be applied to
136 @return: [(depth, pkg), ...]
146 deps = self.get_all_depends()
148 if dep.atom in depcache:
151 pkgdep = depcache[dep.atom]
153 pkgdep = Query(dep.atom).find_best()
154 depcache[dep.atom] = pkgdep
157 elif pkgdep.cpv in seen:
159 if depth <= max_depth or max_depth == 0:
160 if printer_fn is not None:
161 printer_fn(depth, pkgdep, dep)
162 result.append((depth,pkgdep))
165 if depth < max_depth or max_depth == 0:
166 # result is passed in and added to directly
167 # so rdeps is disposable
168 rdeps = pkgdep.deps.graph_depends(
170 printer_fn=printer_fn,
171 # The rest of these are only used internally:
179 def graph_reverse_depends(
185 # The rest of these are only used internally:
191 """Graph direct reverse dependencies for self.
194 >>> from gentoolkit.dependencies import Dependencies
195 >>> ffmpeg = Dependencies('media-video/ffmpeg-0.5_p20373')
196 >>> # I only care about installed packages that depend on me:
197 ... from gentoolkit.helpers import get_installed_cpvs
198 >>> # I want to pass in a sorted list. We can pass strings or
199 ... # Package or Atom types, so I'll use Package to sort:
200 ... from gentoolkit.package import Package
201 >>> installed = sorted(Package(x) for x in get_installed_cpvs())
202 >>> deptree = ffmpeg.graph_reverse_depends(
203 ... only_direct=False, # Include indirect revdeps
204 ... pkgset=installed) # from installed pkgset
208 @type pkgset: iterable
209 @keyword pkgset: sorted pkg cpv strings or anything sublassing
210 L{gentoolkit.cpv.CPV} to use for calculate our revdep graph.
212 @keyword max_depth: Maximum depth to recurse if only_direct=False.
213 -1 means no maximum depth;
214 0 is the same as only_direct=True;
215 >0 means recurse only this many times;
216 @type only_direct: bool
217 @keyword only_direct: to recurse or not to recurse
218 @type printer_fn: callable
219 @keyword printer_fn: If None, no effect. If set, it will be applied to
220 each L{gentoolkit.atom.Atom} object as it is added to the results.
222 @return: L{gentoolkit.dependencies.Dependencies} objects
225 err = ("%s kwarg 'pkgset' must be set. "
226 "Can be list of cpv strings or any 'intersectable' object.")
227 raise errors.GentoolkitFatalError(err % (self.__class__.__name__,))
237 pkgset = tuple(Dependencies(x) for x in pkgset)
240 for pkgdep in pkgset:
242 all_depends = depcache[pkgdep]
244 all_depends = uniqify(pkgdep.get_all_depends())
245 depcache[pkgdep] = all_depends
247 dep_is_displayed = False
248 for dep in all_depends:
249 # TODO: Add ability to determine if dep is enabled by USE flag.
250 # Check portage.dep.use_reduce
251 if dep.intersects(self):
253 pkgdep.matching_dep = dep
254 if printer_fn is not None:
255 printer_fn(pkgdep, dep_is_displayed=dep_is_displayed)
256 result.append(pkgdep)
257 dep_is_displayed = True
259 # if --indirect specified, call ourselves again with the dep
260 # Do not call if we have already called ourselves.
262 dep_is_displayed and not only_direct and
263 pkgdep.cpv not in seen and
264 (depth < max_depth or max_depth == -1)
269 pkgdep.graph_reverse_depends(
272 only_direct=only_direct,
273 printer_fn=printer_fn,
285 def _parser(self, deps, use_conditional=None, depth=0):
286 """?DEPEND file parser.
289 @return: L{gentoolkit.atom.Atom} objects
294 deps = paren_reduce(deps)
299 use_conditional = tok[:-1]
301 if isinstance(tok, list):
302 sub_r = self._parser(tok, use_conditional, depth=depth+1)
304 use_conditional = None
306 # FIXME: This is a quick fix for bug #299260.
307 # A better fix is to not discard blockers in the parser,
308 # but to check for atom.blocker in whatever equery/depends
309 # (in this case) and ignore them there.
310 # TODO: Test to see how much a performance impact ignoring
311 # blockers here rather than checking for atom.blocker has.
313 # We're not interested in blockers
315 # skip it if it's empty
316 if tok and tok != '':
318 if use_conditional is not None:
319 atom.use_conditional = use_conditional
322 message = "dependencies.py: _parser() found an empty " +\
323 "dep string token for: %s, deps= %s"
324 raise errors.GentoolkitInvalidAtom(message %(self.cpv, deps))
328 # vim: set ts=4 sw=4 tw=0: