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', '$cp', '$cpv', '$category', '$name', '$version', '$revision',
39 '$fullversion', '$slot', '$repo'
46 from string import Template
49 from portage import os, settings
50 from portage.util import LazyItemsDict
52 import gentoolkit.pprinter as pp
53 from gentoolkit import errors
54 from gentoolkit.cpv import CPV
55 from gentoolkit.dbapi import PORTDB, VARDB
56 from gentoolkit.keyword import determine_keyword
63 """Exposes the state of a given CPV."""
65 def __init__(self, cpv):
66 if isinstance(cpv, CPV):
67 self.__dict__.update(cpv.__dict__)
69 CPV.__init__(self, cpv)
72 if not all(hasattr(self, x) for x in ('category', 'version')):
73 # CPV allows some things that Package must not
74 raise errors.GentoolkitInvalidPackage(self.cpv)
77 self._package_path = None
81 self._portdir_path = None
84 return "<%s %r>" % (self.__class__.__name__, self.cpv)
89 def __contains__(self, key):
90 return key in self.cpv
97 """Instantiate a L{gentoolkit.metadata.MetaData} object here."""
99 from gentoolkit.metadata import MetaData
101 if self._metadata is None:
102 metadata_path = os.path.join(
103 self.package_path(), 'metadata.xml'
105 self._metadata = MetaData(metadata_path)
107 return self._metadata
111 """Instantiate a L{portage.dbapi.vartree.dblink} object here."""
113 if self._dblink is None:
114 self._dblink = portage.dblink(
116 "%s-%s" % (self.name, self.fullversion),
125 """Instantiate a L{gentoolkit.dependencies.Dependencies} object here."""
127 from gentoolkit.dependencies import Dependencies
129 if self._deps is None:
130 self._deps = Dependencies(self.cpv)
134 def environment(self, envvars, prefer_vdb=True, fallback=True):
135 """Returns one or more of the predefined environment variables.
137 Available envvars are:
138 ----------------------
139 BINPKGMD5 COUNTER FEATURES LICENSE SRC_URI
140 CATEGORY CXXFLAGS HOMEPAGE PDEPEND USE
141 CBUILD DEFINED_PHASES INHERITED PF
142 CFLAGS DEPEND IUSE PROVIDE
143 CHOST DESCRIPTION KEYWORDS RDEPEND
144 CONTENTS EAPI LDFLAGS SLOT
147 >>> pkg = Package('sys-apps/portage-2.1.6.13')
148 >>> pkg.environment('USE')
149 'elibc_glibc kernel_linux userland_GNU x86'
150 >>> pkg.environment(('USE', 'IUSE'))
151 ['elibc_glibc kernel_linux userland_GNU x86',
152 'build doc epydoc selinux linguas_pl']
154 @type envvars: str or array
155 @param envvars: one or more of (DEPEND, SRC_URI, etc.)
156 @type prefer_vdb: bool
157 @keyword prefer_vdb: if True, look in the vardb before portdb, else
158 reverse order. Specifically KEYWORDS will get more recent
159 information by preferring portdb.
161 @keyword fallback: query only the preferred db if False
163 @return: str if envvars is str, list if envvars is array
164 @raise KeyError: if key is not found in requested db(s)
168 if isinstance(envvars, str):
173 result = VARDB.aux_get(self.cpv, envvars)
178 result = PORTDB.aux_get(self.cpv, envvars)
180 err = "aux_get returned unexpected results"
181 raise errors.GentoolkitFatalError(err)
184 result = PORTDB.aux_get(self.cpv, envvars)
189 result = VARDB.aux_get(self.cpv, envvars)
191 err = "aux_get returned unexpected results"
192 raise errors.GentoolkitFatalError(err)
199 """Return True if package exists in the Portage tree, else False"""
201 return bool(PORTDB.cpv_exists(self.cpv))
205 """Returns the value of the given key for this package (useful
206 for package.* files."""
211 result = settings[key]
216 def mask_status(self):
217 """Shortcut to L{portage.getmaskingstatus}.
220 @return: a list containing none or some of:
231 result = portage.getmaskingstatus(self.cpv,
235 # getmaskingstatus doesn't support packages without ebuilds in the
241 def mask_reason(self):
242 """Shortcut to L{portage.getmaskingreason}.
244 @rtype: None or tuple
245 @return: empty tuple if pkg not masked OR
246 ('mask reason', 'mask location')
250 result = portage.getmaskingreason(self.cpv,
253 return_location=True)
257 # getmaskingstatus doesn't support packages without ebuilds in the
263 def ebuild_path(self, in_vartree=False):
264 """Returns the complete path to the .ebuild file.
267 >>> pkg.ebuild_path()
268 '/usr/portage/sys-apps/portage/portage-2.1.6.13.ebuild'
269 >>> pkg.ebuild_path(in_vartree=True)
270 '/var/db/pkg/sys-apps/portage-2.1.6.13/portage-2.1.6.13.ebuild'
274 return VARDB.findname(self.cpv)
275 return PORTDB.findname(self.cpv)
277 def package_path(self, in_vartree=False):
278 """Return the path to where the ebuilds and other files reside."""
281 return self.dblink.getpath()
282 return os.sep.join(self.ebuild_path().split(os.sep)[:-1])
284 def repo_name(self, fallback=True):
285 """Determine the repository name.
288 @param fallback: if the repo_name file does not exist, return the
289 repository name from the path
291 @return: output of the repository metadata file, which stores the
292 repo_name variable, or try to get the name of the repo from
294 @raise GentoolkitFatalError: if fallback is False and repo_name is
295 not specified by the repository.
299 return self.environment('repository')
300 except errors.GentoolkitFatalError:
302 return self.package_path().split(os.sep)[-3]
306 """Returns the USE flags active at time of installation."""
308 return self.dblink.getstring("USE")
310 def parsed_contents(self):
311 """Returns the parsed CONTENTS file.
314 @return: {'/full/path/to/obj': ['type', 'timestamp', 'md5sum'], ...}
317 return self.dblink.getcontents()
320 """Estimates the installed size of the contents of this package.
323 @return: (size, number of files in total, number of uncounted files)
327 size = n_files = n_uncounted = 0
328 for f in self.parsed_contents():
334 # Remove hardlinks by checking for duplicate inodes. Bug #301026.
335 file_inode = st.st_ino
336 if file_inode in seen:
346 return (size, n_files, n_uncounted)
348 def is_installed(self):
349 """Returns True if this package is installed (merged)."""
351 return self.dblink.exists()
353 def is_overlay(self):
354 """Returns True if the package is in an overlay."""
356 ebuild, tree = PORTDB.findname2(self.cpv)
359 if self._portdir_path is None:
360 self._portdir_path = os.path.realpath(settings["PORTDIR"])
361 return (tree and tree != self._portdir_path)
364 """Returns True if this package is masked against installation.
366 @note: We blindly assume that the package actually exists on disk.
369 unmasked = PORTDB.xmatch("match-visible", self.cpv)
370 return self.cpv not in unmasked
373 class PackageFormatter(object):
374 """When applied to a L{gentoolkit.package.Package} object, determine the
375 location (Portage Tree vs. overlay), install status and masked status. That
376 information can then be easily formatted and displayed.
379 >>> from gentoolkit.helpers import find_packages
380 >>> from gentoolkit.package import PackageFormatter
381 >>> pkgs = [PackageFormatter(x) for x in find_packages('gcc')]
383 ... # Only print packages that are installed and from the Portage
385 ... if set('IP').issubset(pkg.location):
388 [IP-] [ ] sys-devel/gcc-4.3.2-r3 (4.3)
390 @type pkg: L{gentoolkit.package.Package}
391 @param pkg: package to format
392 @type do_format: bool
393 @param do_format: Whether to format the package name or not.
394 Essentially C{do_format} should be set to False when piping or when
395 quiet output is desired. If C{do_format} is False, only the location
396 attribute will be created to save time.
399 _tmpl_verbose = "[$location] [$mask] $cpv:$slot"
400 _tmpl_quiet = "$cpv:$slot"
402 def __init__(self, pkg, do_format=True, custom_format=None, fill_sizes = None):
404 self.do_format = do_format
406 self._location = None
407 if not custom_format:
409 custom_format = self._tmpl_verbose
411 custom_format = self._tmpl_quiet
412 self.tmpl = Template(custom_format)
413 self.format_vars = LazyItemsDict()
416 self.fill_sizes = fill_sizes
426 return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self))
429 if self._str is None:
430 self._str = self.tmpl.safe_substitute(self.format_vars)
435 if self._location is None:
436 self._location = self.format_package_location()
437 return self._location
441 """Package to format"""
445 def pkg(self, value):
446 if self._pkg == value:
449 self._location = None
451 fmt_vars = self.format_vars
452 self.format_vars.clear()
453 fmt_vars.addLazySingleton("location",
454 lambda: getattr(self, "location"))
455 fmt_vars.addLazySingleton("mask", self.format_mask)
456 fmt_vars.addLazySingleton("mask2", self.format_mask_status2)
457 fmt_vars.addLazySingleton("cpv", self.format_cpv)
458 fmt_vars.addLazySingleton("cpv_fill", self.format_cpv, fill=True)
459 fmt_vars.addLazySingleton("cp", self.format_cpv, "cp")
460 fmt_vars.addLazySingleton("category", self.format_cpv, "category")
461 fmt_vars.addLazySingleton("name", self.format_cpv, "name")
462 fmt_vars.addLazySingleton("version", self.format_cpv, "version")
463 fmt_vars.addLazySingleton("revision", self.format_cpv, "revision")
464 fmt_vars.addLazySingleton("fullversion", self.format_cpv,
466 fmt_vars.addLazySingleton("slot", self.format_slot)
467 fmt_vars.addLazySingleton("repo", self.pkg.repo_name)
469 def format_package_location(self):
470 """Get the install status (in /var/db/?) and origin (from an overlay
471 and the Portage tree?).
475 'I--' : Installed but ebuild doesn't exist on system anymore
476 '-P-' : Not installed and from the Portage tree
477 '--O' : Not installed and from an overlay
478 'IP-' : Installed and from the Portage tree
479 'I-O' : Installed and from an overlay
482 result = ['-', '-', '-']
484 if self.pkg.is_installed():
487 overlay = self.pkg.is_overlay()
495 return ''.join(result)
497 def format_mask_status(self):
498 """Get the mask status of a given package.
500 @rtype: tuple: (int, list)
501 @return: int = an index for this list:
502 [" ", " ~", " -", "M ", "M~", "M-", "??"]
507 4 = hard and keyword masked,
508 5 = hard and arch masked
509 6 = ebuild doesn't exist on system anymore
511 list = original output of portage.getmaskingstatus
515 masking_status = self.pkg.mask_status()
516 if masking_status is None:
519 if ("~%s keyword" % self.pkg.settings("ARCH")) in masking_status:
521 if "missing keyword" in masking_status:
523 if set(('profile', 'package.mask')).intersection(masking_status):
526 return (result, masking_status)
528 def format_mask_status2(self):
529 """Get the mask status of a given package.
531 mask = self.pkg.mask_status()
533 return pp.masking(mask)
535 arch = self.pkg.settings("ARCH")
536 keywords = self.pkg.environment('KEYWORDS')
537 mask = [determine_keyword(arch,
538 portage.settings["ACCEPT_KEYWORDS"],
540 return pp.masking(mask)
542 def format_mask(self):
543 maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-', '??']
544 maskmode = maskmodes[self.format_mask_status()[0]]
547 stable=not maskmode.strip(),
548 hard_masked=set(('M', '?', '-')).intersection(maskmode)
551 def format_cpv(self, attr = None, fill=False):
555 value = getattr(self.pkg, attr)
558 trail = '.'*(self.fill_sizes['cpv']-len(value))
559 return pp.cpv(value) + trail
565 def format_slot(self):
566 value = self.pkg.environment("SLOT")
568 return pp.slot(value)
573 # vim: set ts=4 sw=4 tw=79: