1 # Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org>
2 # Copyright(c) 2004-2010, Gentoo Foundation
4 # Licensed under the GNU General Public License, v2
8 """Provides an interface to package information stored by package managers.
10 The Package class is the heart of much of Gentoolkit. Given a CPV
11 (category/package-version) string, it can reveal the package's status in the
12 tree and VARDB (/var/db/), provide rich comparison and sorting, and expose
13 important parts of Portage's back-end.
16 >>> portage = Package('sys-apps/portage-9999')
17 >>> portage.ebuild_path()
18 '/usr/portage/sys-apps/portage/portage-9999.ebuild'
19 >>> portage.is_masked()
21 >>> portage.is_installed()
36 '$location', '$mask', '$mask2', '$cp', '$cpv', '$category', '$name',
37 '$version', '$revision', '$fullversion', '$slot', '$repo', '$keywords'
45 from string import Template
48 from portage.util import LazyItemsDict
50 import gentoolkit.pprinter as pp
51 from gentoolkit import errors
52 from gentoolkit.cpv import CPV
53 from gentoolkit.keyword import determine_keyword
54 from gentoolkit.flag import get_flags
55 from gentoolkit.eprefix import EPREFIX
62 default_settings = portage.config(local_config=True, eprefix=EPREFIX)
63 default_settings.lock()
64 nolocal_settings = portage.config(local_config=False, eprefix=EPREFIX)
65 nolocal_settings.lock()
67 default_settings = portage.config(local_config=True)
68 default_settings.lock()
69 nolocal_settings = portage.config(local_config=False)
70 nolocal_settings.lock()
77 """Exposes the state of a given CPV."""
79 def __init__(self, cpv, validate=False, local_config=True):
80 if isinstance(cpv, CPV):
81 self.__dict__.update(cpv.__dict__)
83 CPV.__init__(self, cpv, validate=validate)
85 if validate and not all(
86 hasattr(self, x) for x in ('category', 'version')
88 # CPV allows some things that Package must not
89 raise errors.GentoolkitInvalidPackage(self.cpv)
92 self._settings = default_settings
94 self._settings = nolocal_settings
97 self._package_path = None
101 self._portdir_path = None
104 return "<%s %r>" % (self.__class__.__name__, self.cpv)
107 return hash(self.cpv)
109 def __contains__(self, key):
110 return key in self.cpv
117 """Instantiate a L{gentoolkit.metadata.MetaData} object here."""
119 from gentoolkit.metadata import MetaData
121 if self._metadata is None:
122 metadata_path = os.path.join(
123 self.package_path(), 'metadata.xml'
126 self._metadata = MetaData(metadata_path)
127 except IOError as error:
129 if error.errno != errno.ENOENT:
133 return self._metadata
137 """Instantiate a L{portage.dbapi.vartree.dblink} object here."""
139 if self._dblink is None:
140 self._dblink = portage.dblink(
142 "%s-%s" % (self.name, self.fullversion),
143 self._settings["ROOT"],
151 """Instantiate a L{gentoolkit.dependencies.Dependencies} object here."""
153 from gentoolkit.dependencies import Dependencies
155 if self._deps is None:
156 self._deps = Dependencies(self.cpv)
160 def environment(self, envvars, prefer_vdb=True, fallback=True):
161 """Returns one or more of the predefined environment variables.
163 Some available envvars are:
164 ----------------------
165 BINPKGMD5 COUNTER FEATURES LICENSE SRC_URI
166 CATEGORY CXXFLAGS HOMEPAGE PDEPEND USE
167 CBUILD DEFINED_PHASES INHERITED PF
168 CFLAGS DEPEND IUSE PROVIDE
169 CHOST DESCRIPTION KEYWORDS RDEPEND
170 CONTENTS EAPI LDFLAGS SLOT
173 >>> pkg = Package('sys-apps/portage-9999')
174 >>> pkg.environment('USE')
176 >>> pkg.environment(('USE', 'IUSE'))
177 ... # doctest: +NORMALIZE_WHITESPACE
178 ['', 'build doc epydoc +ipc pypy1_9 python2 python3
181 @type envvars: str or array
182 @param envvars: one or more of (DEPEND, SRC_URI, etc.)
183 @type prefer_vdb: bool
184 @keyword prefer_vdb: if True, look in the vardb before portdb, else
185 reverse order. Specifically KEYWORDS will get more recent
186 information by preferring portdb.
188 @keyword fallback: query only the preferred db if False
190 @return: str if envvars is str, list if envvars is array
191 @raise KeyError: if key is not found in requested db(s)
195 if isinstance(envvars, str):
200 result = portage.db[portage.root][
201 'vartree'].dbapi.aux_get(
207 result = portage.db[portage.root][
208 'porttree'].dbapi.aux_get(
211 raise errors.GentoolkitFatalError(
212 'aux_get returned unexpected '
216 result = portage.db[portage.root][
217 'porttree'].dbapi.aux_get(
223 result = portage.db[portage.root][
224 'vartree'].dbapi.aux_get(
227 raise errors.GentoolkitFatalError(
228 'aux_get returned unexpected '
236 """Return True if package exists in the Portage tree, else False"""
238 return bool(portage.db[portage.root]["porttree"].dbapi.cpv_exists(self.cpv))
240 def settings(self, key):
241 """Returns the value of the given key for this package (useful
242 for package.* files."""
244 if self._settings.locked:
245 self._settings.unlock()
247 result = self._settings[key]
249 self._settings.lock()
252 def mask_status(self):
253 """Shortcut to L{portage.getmaskingstatus}.
256 @return: a list containing none or some of:
264 if self._settings.locked:
265 self._settings.unlock()
267 result = portage.getmaskingstatus(self.cpv,
268 settings=self._settings,
269 portdb=portage.db[portage.root]["porttree"].dbapi)
271 # getmaskingstatus doesn't support packages without ebuilds in the
277 def mask_reason(self):
278 """Shortcut to L{portage.getmaskingreason}.
280 @rtype: None or tuple
281 @return: empty tuple if pkg not masked OR
282 ('mask reason', 'mask location')
286 result = portage.getmaskingreason(self.cpv,
287 settings=self._settings,
288 portdb=portage.db[portage.root]["porttree"].dbapi,
289 return_location=True)
293 # getmaskingstatus doesn't support packages without ebuilds in the
299 def ebuild_path(self, in_vartree=False):
300 """Returns the complete path to the .ebuild file.
303 >>> pkg = Package('sys-apps/portage-9999')
304 >>> pkg.ebuild_path()
305 '/usr/portage/sys-apps/portage/portage-9999.ebuild'
306 >>> pkg.ebuild_path(in_vartree=True)
307 '/var/db/pkg/sys-apps/portage-9999/portage-9999.ebuild'
311 return portage.db[portage.root]["vartree"].dbapi.findname(self.cpv)
312 return portage.db[portage.root]["porttree"].dbapi.findname(self.cpv)
314 def package_path(self, in_vartree=False):
315 """Return the path to where the ebuilds and other files reside."""
318 return self.dblink.getpath()
319 return os.sep.join(self.ebuild_path().split(os.sep)[:-1])
321 def repo_name(self, fallback=True):
322 """Determine the repository name.
325 @param fallback: if the repo_name file does not exist, return the
326 repository name from the path
328 @return: output of the repository metadata file, which stores the
329 repo_name variable, or try to get the name of the repo from
331 @raise GentoolkitFatalError: if fallback is False and repo_name is
332 not specified by the repository.
336 return self.environment('repository')
337 except errors.GentoolkitFatalError:
339 return self.package_path().split(os.sep)[-3]
343 """Returns the USE flags active at time of installation."""
345 return self.dblink.getstring("USE")
347 def use_status(self):
348 """Returns the USE flags active for installation."""
350 iuse, final_flags = get_flags(self.cpv, final_setting=True)
353 def parsed_contents(self):
354 """Returns the parsed CONTENTS file.
357 @return: {'/full/path/to/obj': ['type', 'timestamp', 'md5sum'], ...}
360 return self.dblink.getcontents()
363 """Estimates the installed size of the contents of this package.
366 @return: (size, number of files in total, number of uncounted files)
370 size = n_files = n_uncounted = 0
371 for path in self.parsed_contents():
377 # Remove hardlinks by checking for duplicate inodes. Bug #301026.
378 file_inode = st.st_ino
379 if file_inode in seen:
389 return (size, n_files, n_uncounted)
391 def is_installed(self):
392 """Returns True if this package is installed (merged)."""
394 return self.dblink.exists()
396 def is_overlay(self):
397 """Returns True if the package is in an overlay."""
399 ebuild, tree = portage.db[portage.root]["porttree"].dbapi.findname2(self.cpv)
402 if self._portdir_path is None:
403 self._portdir_path = os.path.realpath(self._settings["PORTDIR"])
404 return (tree and tree != self._portdir_path)
407 """Returns True if this package is masked against installation.
409 @note: We blindly assume that the package actually exists on disk.
412 unmasked = portage.db[portage.root]['porttree'].dbapi.xmatch(
413 'match-visible', self.cpv)
414 return self.cpv not in unmasked
417 class PackageFormatter(object):
418 """When applied to a L{gentoolkit.package.Package} object, determine the
419 location (Portage Tree vs. overlay), install status and masked status. That
420 information can then be easily formatted and displayed.
423 >>> from gentoolkit.query import Query
424 >>> from gentoolkit.package import PackageFormatter
425 >>> import portage.output
427 >>> pkgs = [PackageFormatter(x) for x in q.find()]
428 >>> havecolor = portage.output.havecolor
429 >>> portage.output.havecolor = False
431 ... # Only print packages that are installed and from the Portage
433 ... if set('IP').issubset(pkg.location):
436 [IP-] [ ] sys-devel/gcc-4.5.4:4.5
437 >>> portage.output.havecolor = havecolor
439 @type pkg: L{gentoolkit.package.Package}
440 @param pkg: package to format
441 @type do_format: bool
442 @param do_format: Whether to format the package name or not.
443 Essentially C{do_format} should be set to False when piping or when
444 quiet output is desired. If C{do_format} is False, only the location
445 attribute will be created to save time.
448 _tmpl_verbose = "[$location] [$mask] $cpv:$slot"
451 def __init__(self, pkg, do_format=True, custom_format=None):
453 self._do_format = do_format
455 self._location = None
456 if not custom_format:
458 custom_format = self._tmpl_verbose
460 custom_format = self._tmpl_quiet
461 self.tmpl = Template(custom_format)
462 self.format_vars = LazyItemsDict()
466 return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self))
469 if self._str is None:
470 self._str = self.tmpl.safe_substitute(self.format_vars)
475 if self._location is None:
476 self._location = self.format_package_location()
477 return self._location
481 """Package to format"""
485 def pkg(self, value):
486 if self._pkg == value:
489 self._location = None
491 fmt_vars = self.format_vars
492 self.format_vars.clear()
493 fmt_vars.addLazySingleton("location",
494 lambda: getattr(self, "location"))
495 fmt_vars.addLazySingleton("mask", self.format_mask)
496 fmt_vars.addLazySingleton("mask2", self.format_mask_status2)
497 fmt_vars.addLazySingleton("cpv", self.format_cpv)
498 fmt_vars.addLazySingleton("cp", self.format_cpv, "cp")
499 fmt_vars.addLazySingleton("category", self.format_cpv, "category")
500 fmt_vars.addLazySingleton("name", self.format_cpv, "name")
501 fmt_vars.addLazySingleton("version", self.format_cpv, "version")
502 fmt_vars.addLazySingleton("revision", self.format_cpv, "revision")
503 fmt_vars.addLazySingleton("fullversion", self.format_cpv,
505 fmt_vars.addLazySingleton("slot", self.format_slot)
506 fmt_vars.addLazySingleton("repo", self.pkg.repo_name)
507 fmt_vars.addLazySingleton("keywords", self.format_keywords)
509 def format_package_location(self):
510 """Get the install status (in /var/db/?) and origin (from an overlay
511 and the Portage tree?).
515 'I--' : Installed but ebuild doesn't exist on system anymore
516 '-P-' : Not installed and from the Portage tree
517 '--O' : Not installed and from an overlay
518 'IP-' : Installed and from the Portage tree
519 'I-O' : Installed and from an overlay
522 result = ['-', '-', '-']
524 if self.pkg.is_installed():
527 overlay = self.pkg.is_overlay()
535 return ''.join(result)
537 def format_mask_status(self):
538 """Get the mask status of a given package.
540 @rtype: tuple: (int, list)
541 @return: int = an index for this list:
542 [" ", " ~", " -", "M ", "M~", "M-", "??"]
547 4 = hard and keyword masked,
548 5 = hard and arch masked
549 6 = ebuild doesn't exist on system anymore
551 list = original output of portage.getmaskingstatus
555 masking_status = self.pkg.mask_status()
556 if masking_status is None:
559 if ("~%s keyword" % self.pkg.settings("ARCH")) in masking_status:
561 if "missing keyword" in masking_status:
563 if set(('profile', 'package.mask')).intersection(masking_status):
566 return (result, masking_status)
568 def format_mask_status2(self):
569 """Get the mask status of a given package.
571 mask = self.pkg.mask_status()
573 return pp.masking(mask)
575 arch = self.pkg.settings("ARCH")
576 keywords = self.pkg.environment('KEYWORDS')
577 mask = [determine_keyword(arch,
578 portage.settings["ACCEPT_KEYWORDS"],
580 return pp.masking(mask)
582 def format_mask(self):
583 maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-', '??']
584 maskmode = maskmodes[self.format_mask_status()[0]]
587 stable=not maskmode.strip(),
588 hard_masked=set(('M', '?', '-')).intersection(maskmode)
591 def format_cpv(self, attr=None):
595 value = getattr(self.pkg, attr)
601 def format_slot(self):
602 value = self.pkg.environment("SLOT")
604 return pp.slot(value)
608 def format_keywords(self):
609 value = self.pkg.environment("KEYWORDS")
611 return pp.keyword(value)
616 # vim: set ts=4 sw=4 tw=79: