3 # Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org>
4 # Copyright(c) 2004-2010, Gentoo Foundation
6 # Licensed under the GNU General Public License, v2
10 """Provides an interface to package information stored by package managers.
12 The Package class is the heart of much of Gentoolkit. Given a CPV
13 (category/package-version) string, it can reveal the package's status in the
14 tree and VARDB (/var/db/), provide rich comparison and sorting, and expose
15 important parts of Portage's back-end.
18 >>> portage = Package('sys-apps/portage-2.1.6.13')
19 >>> portage.ebuild_path()
20 '/usr/portage/sys-apps/portage/portage-2.1.6.13.ebuild'
21 >>> portage.is_masked()
23 >>> portage.is_installed()
38 '$location', '$mask', '$mask2', '$cp', '$cpv', '$category', '$name',
39 '$version', '$revision', '$fullversion', '$slot', '$repo', '$keywords'
47 from string import Template
50 from portage import settings
51 from portage.util import LazyItemsDict
53 import gentoolkit.pprinter as pp
54 from gentoolkit import errors
55 from gentoolkit.cpv import CPV
56 from gentoolkit.dbapi import PORTDB, VARDB
57 from gentoolkit.keyword import determine_keyword
58 from gentoolkit.flag import get_flags
65 """Exposes the state of a given CPV."""
67 def __init__(self, cpv, validate=False):
68 if isinstance(cpv, CPV):
69 self.__dict__.update(cpv.__dict__)
71 CPV.__init__(self, cpv, validate=validate)
73 if validate and not all(
74 hasattr(self, x) for x in ('category', 'version')
76 # CPV allows some things that Package must not
77 raise errors.GentoolkitInvalidPackage(self.cpv)
80 self._package_path = None
84 self._portdir_path = None
87 return "<%s %r>" % (self.__class__.__name__, self.cpv)
92 def __contains__(self, key):
93 return key in self.cpv
100 """Instantiate a L{gentoolkit.metadata.MetaData} object here."""
102 from gentoolkit.metadata import MetaData
104 if self._metadata is None:
105 metadata_path = os.path.join(
106 self.package_path(), 'metadata.xml'
109 self._metadata = MetaData(metadata_path)
112 if e.errno != errno.ENOENT:
116 return self._metadata
120 """Instantiate a L{portage.dbapi.vartree.dblink} object here."""
122 if self._dblink is None:
123 self._dblink = portage.dblink(
125 "%s-%s" % (self.name, self.fullversion),
134 """Instantiate a L{gentoolkit.dependencies.Dependencies} object here."""
136 from gentoolkit.dependencies import Dependencies
138 if self._deps is None:
139 self._deps = Dependencies(self.cpv)
143 def environment(self, envvars, prefer_vdb=True, fallback=True):
144 """Returns one or more of the predefined environment variables.
146 Some available envvars are:
147 ----------------------
148 BINPKGMD5 COUNTER FEATURES LICENSE SRC_URI
149 CATEGORY CXXFLAGS HOMEPAGE PDEPEND USE
150 CBUILD DEFINED_PHASES INHERITED PF
151 CFLAGS DEPEND IUSE PROVIDE
152 CHOST DESCRIPTION KEYWORDS RDEPEND
153 CONTENTS EAPI LDFLAGS SLOT
156 >>> pkg = Package('sys-apps/portage-2.1.6.13')
157 >>> pkg.environment('USE')
158 'elibc_glibc kernel_linux userland_GNU x86'
159 >>> pkg.environment(('USE', 'IUSE'))
160 ['elibc_glibc kernel_linux userland_GNU x86',
161 'build doc epydoc selinux linguas_pl']
163 @type envvars: str or array
164 @param envvars: one or more of (DEPEND, SRC_URI, etc.)
165 @type prefer_vdb: bool
166 @keyword prefer_vdb: if True, look in the vardb before portdb, else
167 reverse order. Specifically KEYWORDS will get more recent
168 information by preferring portdb.
170 @keyword fallback: query only the preferred db if False
172 @return: str if envvars is str, list if envvars is array
173 @raise KeyError: if key is not found in requested db(s)
177 if isinstance(envvars, str):
182 result = VARDB.aux_get(self.cpv, envvars)
187 result = PORTDB.aux_get(self.cpv, envvars)
189 err = "aux_get returned unexpected results"
190 raise errors.GentoolkitFatalError(err)
193 result = PORTDB.aux_get(self.cpv, envvars)
198 result = VARDB.aux_get(self.cpv, envvars)
200 err = "aux_get returned unexpected results"
201 raise errors.GentoolkitFatalError(err)
208 """Return True if package exists in the Portage tree, else False"""
210 return bool(PORTDB.cpv_exists(self.cpv))
214 """Returns the value of the given key for this package (useful
215 for package.* files."""
220 result = settings[key]
225 def mask_status(self):
226 """Shortcut to L{portage.getmaskingstatus}.
229 @return: a list containing none or some of:
240 result = portage.getmaskingstatus(self.cpv,
244 # getmaskingstatus doesn't support packages without ebuilds in the
250 def mask_reason(self):
251 """Shortcut to L{portage.getmaskingreason}.
253 @rtype: None or tuple
254 @return: empty tuple if pkg not masked OR
255 ('mask reason', 'mask location')
259 result = portage.getmaskingreason(self.cpv,
262 return_location=True)
266 # getmaskingstatus doesn't support packages without ebuilds in the
272 def ebuild_path(self, in_vartree=False):
273 """Returns the complete path to the .ebuild file.
276 >>> pkg.ebuild_path()
277 '/usr/portage/sys-apps/portage/portage-2.1.6.13.ebuild'
278 >>> pkg.ebuild_path(in_vartree=True)
279 '/var/db/pkg/sys-apps/portage-2.1.6.13/portage-2.1.6.13.ebuild'
283 return VARDB.findname(self.cpv)
284 return PORTDB.findname(self.cpv)
286 def package_path(self, in_vartree=False):
287 """Return the path to where the ebuilds and other files reside."""
290 return self.dblink.getpath()
291 return os.sep.join(self.ebuild_path().split(os.sep)[:-1])
293 def repo_name(self, fallback=True):
294 """Determine the repository name.
297 @param fallback: if the repo_name file does not exist, return the
298 repository name from the path
300 @return: output of the repository metadata file, which stores the
301 repo_name variable, or try to get the name of the repo from
303 @raise GentoolkitFatalError: if fallback is False and repo_name is
304 not specified by the repository.
308 return self.environment('repository')
309 except errors.GentoolkitFatalError:
311 return self.package_path().split(os.sep)[-3]
315 """Returns the USE flags active at time of installation."""
317 return self.dblink.getstring("USE")
319 def use_status(self):
320 """Returns the USE flags active for installation."""
322 iuse, final_flags = get_flags(self.cpv, final_setting=True)
325 def parsed_contents(self):
326 """Returns the parsed CONTENTS file.
329 @return: {'/full/path/to/obj': ['type', 'timestamp', 'md5sum'], ...}
332 return self.dblink.getcontents()
335 """Estimates the installed size of the contents of this package.
338 @return: (size, number of files in total, number of uncounted files)
342 size = n_files = n_uncounted = 0
343 for f in self.parsed_contents():
349 # Remove hardlinks by checking for duplicate inodes. Bug #301026.
350 file_inode = st.st_ino
351 if file_inode in seen:
361 return (size, n_files, n_uncounted)
363 def is_installed(self):
364 """Returns True if this package is installed (merged)."""
366 return self.dblink.exists()
368 def is_overlay(self):
369 """Returns True if the package is in an overlay."""
371 ebuild, tree = PORTDB.findname2(self.cpv)
374 if self._portdir_path is None:
375 self._portdir_path = os.path.realpath(settings["PORTDIR"])
376 return (tree and tree != self._portdir_path)
379 """Returns True if this package is masked against installation.
381 @note: We blindly assume that the package actually exists on disk.
384 unmasked = PORTDB.xmatch("match-visible", self.cpv)
385 return self.cpv not in unmasked
388 class PackageFormatter(object):
389 """When applied to a L{gentoolkit.package.Package} object, determine the
390 location (Portage Tree vs. overlay), install status and masked status. That
391 information can then be easily formatted and displayed.
394 >>> from gentoolkit.helpers import find_packages
395 >>> from gentoolkit.package import PackageFormatter
396 >>> pkgs = [PackageFormatter(x) for x in find_packages('gcc')]
398 ... # Only print packages that are installed and from the Portage
400 ... if set('IP').issubset(pkg.location):
403 [IP-] [ ] sys-devel/gcc-4.3.2-r3 (4.3)
405 @type pkg: L{gentoolkit.package.Package}
406 @param pkg: package to format
407 @type do_format: bool
408 @param do_format: Whether to format the package name or not.
409 Essentially C{do_format} should be set to False when piping or when
410 quiet output is desired. If C{do_format} is False, only the location
411 attribute will be created to save time.
414 _tmpl_verbose = "[$location] [$mask] $cpv:$slot"
417 def __init__(self, pkg, do_format=True, custom_format=None):
419 self._do_format = do_format
421 self._location = None
422 if not custom_format:
424 custom_format = self._tmpl_verbose
426 custom_format = self._tmpl_quiet
427 self.tmpl = Template(custom_format)
428 self.format_vars = LazyItemsDict()
432 return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self))
435 if self._str is None:
436 self._str = self.tmpl.safe_substitute(self.format_vars)
441 if self._location is None:
442 self._location = self.format_package_location()
443 return self._location
447 """Package to format"""
451 def pkg(self, value):
452 if self._pkg == value:
455 self._location = None
457 fmt_vars = self.format_vars
458 self.format_vars.clear()
459 fmt_vars.addLazySingleton("location",
460 lambda: getattr(self, "location"))
461 fmt_vars.addLazySingleton("mask", self.format_mask)
462 fmt_vars.addLazySingleton("mask2", self.format_mask_status2)
463 fmt_vars.addLazySingleton("cpv", self.format_cpv)
464 fmt_vars.addLazySingleton("cp", self.format_cpv, "cp")
465 fmt_vars.addLazySingleton("category", self.format_cpv, "category")
466 fmt_vars.addLazySingleton("name", self.format_cpv, "name")
467 fmt_vars.addLazySingleton("version", self.format_cpv, "version")
468 fmt_vars.addLazySingleton("revision", self.format_cpv, "revision")
469 fmt_vars.addLazySingleton("fullversion", self.format_cpv,
471 fmt_vars.addLazySingleton("slot", self.format_slot)
472 fmt_vars.addLazySingleton("repo", self.pkg.repo_name)
473 fmt_vars.addLazySingleton("keywords", self.format_keywords)
475 def format_package_location(self):
476 """Get the install status (in /var/db/?) and origin (from an overlay
477 and the Portage tree?).
481 'I--' : Installed but ebuild doesn't exist on system anymore
482 '-P-' : Not installed and from the Portage tree
483 '--O' : Not installed and from an overlay
484 'IP-' : Installed and from the Portage tree
485 'I-O' : Installed and from an overlay
488 result = ['-', '-', '-']
490 if self.pkg.is_installed():
493 overlay = self.pkg.is_overlay()
501 return ''.join(result)
503 def format_mask_status(self):
504 """Get the mask status of a given package.
506 @rtype: tuple: (int, list)
507 @return: int = an index for this list:
508 [" ", " ~", " -", "M ", "M~", "M-", "??"]
513 4 = hard and keyword masked,
514 5 = hard and arch masked
515 6 = ebuild doesn't exist on system anymore
517 list = original output of portage.getmaskingstatus
521 masking_status = self.pkg.mask_status()
522 if masking_status is None:
525 if ("~%s keyword" % self.pkg.settings("ARCH")) in masking_status:
527 if "missing keyword" in masking_status:
529 if set(('profile', 'package.mask')).intersection(masking_status):
532 return (result, masking_status)
534 def format_mask_status2(self):
535 """Get the mask status of a given package.
537 mask = self.pkg.mask_status()
539 return pp.masking(mask)
541 arch = self.pkg.settings("ARCH")
542 keywords = self.pkg.environment('KEYWORDS')
543 mask = [determine_keyword(arch,
544 portage.settings["ACCEPT_KEYWORDS"],
546 return pp.masking(mask)
548 def format_mask(self):
549 maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-', '??']
550 maskmode = maskmodes[self.format_mask_status()[0]]
553 stable=not maskmode.strip(),
554 hard_masked=set(('M', '?', '-')).intersection(maskmode)
557 def format_cpv(self, attr=None):
561 value = getattr(self.pkg, attr)
567 def format_slot(self):
568 value = self.pkg.environment("SLOT")
570 return pp.slot(value)
574 def format_keywords(self):
575 value = self.pkg.environment("KEYWORDS")
577 return pp.keyword(value)
582 # vim: set ts=4 sw=4 tw=79: