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, validate=False):
66 if isinstance(cpv, CPV):
67 self.__dict__.update(cpv.__dict__)
69 CPV.__init__(self, cpv, validate=validate)
71 if validate and not all(
72 hasattr(self, x) for x in ('category', 'version')
74 # CPV allows some things that Package must not
75 raise errors.GentoolkitInvalidPackage(self.cpv)
78 self._package_path = None
82 self._portdir_path = None
85 return "<%s %r>" % (self.__class__.__name__, self.cpv)
90 def __contains__(self, key):
91 return key in self.cpv
98 """Instantiate a L{gentoolkit.metadata.MetaData} object here."""
100 from gentoolkit.metadata import MetaData
102 if self._metadata is None:
103 metadata_path = os.path.join(
104 self.package_path(), 'metadata.xml'
107 self._metadata = MetaData(metadata_path)
110 if e.errno != errno.ENOENT:
114 return self._metadata
118 """Instantiate a L{portage.dbapi.vartree.dblink} object here."""
120 if self._dblink is None:
121 self._dblink = portage.dblink(
123 "%s-%s" % (self.name, self.fullversion),
132 """Instantiate a L{gentoolkit.dependencies.Dependencies} object here."""
134 from gentoolkit.dependencies import Dependencies
136 if self._deps is None:
137 self._deps = Dependencies(self.cpv)
141 def environment(self, envvars, prefer_vdb=True, fallback=True):
142 """Returns one or more of the predefined environment variables.
144 Available envvars are:
145 ----------------------
146 BINPKGMD5 COUNTER FEATURES LICENSE SRC_URI
147 CATEGORY CXXFLAGS HOMEPAGE PDEPEND USE
148 CBUILD DEFINED_PHASES INHERITED PF
149 CFLAGS DEPEND IUSE PROVIDE
150 CHOST DESCRIPTION KEYWORDS RDEPEND
151 CONTENTS EAPI LDFLAGS SLOT
154 >>> pkg = Package('sys-apps/portage-2.1.6.13')
155 >>> pkg.environment('USE')
156 'elibc_glibc kernel_linux userland_GNU x86'
157 >>> pkg.environment(('USE', 'IUSE'))
158 ['elibc_glibc kernel_linux userland_GNU x86',
159 'build doc epydoc selinux linguas_pl']
161 @type envvars: str or array
162 @param envvars: one or more of (DEPEND, SRC_URI, etc.)
163 @type prefer_vdb: bool
164 @keyword prefer_vdb: if True, look in the vardb before portdb, else
165 reverse order. Specifically KEYWORDS will get more recent
166 information by preferring portdb.
168 @keyword fallback: query only the preferred db if False
170 @return: str if envvars is str, list if envvars is array
171 @raise KeyError: if key is not found in requested db(s)
175 if isinstance(envvars, str):
180 result = VARDB.aux_get(self.cpv, envvars)
185 result = PORTDB.aux_get(self.cpv, envvars)
187 err = "aux_get returned unexpected results"
188 raise errors.GentoolkitFatalError(err)
191 result = PORTDB.aux_get(self.cpv, envvars)
196 result = VARDB.aux_get(self.cpv, envvars)
198 err = "aux_get returned unexpected results"
199 raise errors.GentoolkitFatalError(err)
206 """Return True if package exists in the Portage tree, else False"""
208 return bool(PORTDB.cpv_exists(self.cpv))
212 """Returns the value of the given key for this package (useful
213 for package.* files."""
218 result = settings[key]
223 def mask_status(self):
224 """Shortcut to L{portage.getmaskingstatus}.
227 @return: a list containing none or some of:
238 result = portage.getmaskingstatus(self.cpv,
242 # getmaskingstatus doesn't support packages without ebuilds in the
248 def mask_reason(self):
249 """Shortcut to L{portage.getmaskingreason}.
251 @rtype: None or tuple
252 @return: empty tuple if pkg not masked OR
253 ('mask reason', 'mask location')
257 result = portage.getmaskingreason(self.cpv,
260 return_location=True)
264 # getmaskingstatus doesn't support packages without ebuilds in the
270 def ebuild_path(self, in_vartree=False):
271 """Returns the complete path to the .ebuild file.
274 >>> pkg.ebuild_path()
275 '/usr/portage/sys-apps/portage/portage-2.1.6.13.ebuild'
276 >>> pkg.ebuild_path(in_vartree=True)
277 '/var/db/pkg/sys-apps/portage-2.1.6.13/portage-2.1.6.13.ebuild'
281 return VARDB.findname(self.cpv)
282 return PORTDB.findname(self.cpv)
284 def package_path(self, in_vartree=False):
285 """Return the path to where the ebuilds and other files reside."""
288 return self.dblink.getpath()
289 return os.sep.join(self.ebuild_path().split(os.sep)[:-1])
291 def repo_name(self, fallback=True):
292 """Determine the repository name.
295 @param fallback: if the repo_name file does not exist, return the
296 repository name from the path
298 @return: output of the repository metadata file, which stores the
299 repo_name variable, or try to get the name of the repo from
301 @raise GentoolkitFatalError: if fallback is False and repo_name is
302 not specified by the repository.
306 return self.environment('repository')
307 except errors.GentoolkitFatalError:
309 return self.package_path().split(os.sep)[-3]
313 """Returns the USE flags active at time of installation."""
315 return self.dblink.getstring("USE")
317 def parsed_contents(self):
318 """Returns the parsed CONTENTS file.
321 @return: {'/full/path/to/obj': ['type', 'timestamp', 'md5sum'], ...}
324 return self.dblink.getcontents()
327 """Estimates the installed size of the contents of this package.
330 @return: (size, number of files in total, number of uncounted files)
334 size = n_files = n_uncounted = 0
335 for f in self.parsed_contents():
341 # Remove hardlinks by checking for duplicate inodes. Bug #301026.
342 file_inode = st.st_ino
343 if file_inode in seen:
353 return (size, n_files, n_uncounted)
355 def is_installed(self):
356 """Returns True if this package is installed (merged)."""
358 return self.dblink.exists()
360 def is_overlay(self):
361 """Returns True if the package is in an overlay."""
363 ebuild, tree = PORTDB.findname2(self.cpv)
366 if self._portdir_path is None:
367 self._portdir_path = os.path.realpath(settings["PORTDIR"])
368 return (tree and tree != self._portdir_path)
371 """Returns True if this package is masked against installation.
373 @note: We blindly assume that the package actually exists on disk.
376 unmasked = PORTDB.xmatch("match-visible", self.cpv)
377 return self.cpv not in unmasked
380 class PackageFormatter(object):
381 """When applied to a L{gentoolkit.package.Package} object, determine the
382 location (Portage Tree vs. overlay), install status and masked status. That
383 information can then be easily formatted and displayed.
386 >>> from gentoolkit.helpers import find_packages
387 >>> from gentoolkit.package import PackageFormatter
388 >>> pkgs = [PackageFormatter(x) for x in find_packages('gcc')]
390 ... # Only print packages that are installed and from the Portage
392 ... if set('IP').issubset(pkg.location):
395 [IP-] [ ] sys-devel/gcc-4.3.2-r3 (4.3)
397 @type pkg: L{gentoolkit.package.Package}
398 @param pkg: package to format
399 @type do_format: bool
400 @param do_format: Whether to format the package name or not.
401 Essentially C{do_format} should be set to False when piping or when
402 quiet output is desired. If C{do_format} is False, only the location
403 attribute will be created to save time.
406 _tmpl_verbose = "[$location] [$mask] $cpv:$slot"
409 def __init__(self, pkg, do_format=True, custom_format=None):
411 self._do_format = do_format
413 self._location = None
414 if not custom_format:
416 custom_format = self._tmpl_verbose
418 custom_format = self._tmpl_quiet
419 self.tmpl = Template(custom_format)
420 self.format_vars = LazyItemsDict()
424 return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self))
427 if self._str is None:
428 self._str = self.tmpl.safe_substitute(self.format_vars)
433 if self._location is None:
434 self._location = self.format_package_location()
435 return self._location
439 """Package to format"""
443 def pkg(self, value):
444 if self._pkg == value:
447 self._location = None
449 fmt_vars = self.format_vars
450 self.format_vars.clear()
451 fmt_vars.addLazySingleton("location",
452 lambda: getattr(self, "location"))
453 fmt_vars.addLazySingleton("mask", self.format_mask)
454 fmt_vars.addLazySingleton("mask2", self.format_mask_status2)
455 fmt_vars.addLazySingleton("cpv", self.format_cpv)
456 fmt_vars.addLazySingleton("cp", self.format_cpv, "cp")
457 fmt_vars.addLazySingleton("category", self.format_cpv, "category")
458 fmt_vars.addLazySingleton("name", self.format_cpv, "name")
459 fmt_vars.addLazySingleton("version", self.format_cpv, "version")
460 fmt_vars.addLazySingleton("revision", self.format_cpv, "revision")
461 fmt_vars.addLazySingleton("fullversion", self.format_cpv,
463 fmt_vars.addLazySingleton("slot", self.format_slot)
464 fmt_vars.addLazySingleton("repo", self.pkg.repo_name)
466 def format_package_location(self):
467 """Get the install status (in /var/db/?) and origin (from an overlay
468 and the Portage tree?).
472 'I--' : Installed but ebuild doesn't exist on system anymore
473 '-P-' : Not installed and from the Portage tree
474 '--O' : Not installed and from an overlay
475 'IP-' : Installed and from the Portage tree
476 'I-O' : Installed and from an overlay
479 result = ['-', '-', '-']
481 if self.pkg.is_installed():
484 overlay = self.pkg.is_overlay()
492 return ''.join(result)
494 def format_mask_status(self):
495 """Get the mask status of a given package.
497 @rtype: tuple: (int, list)
498 @return: int = an index for this list:
499 [" ", " ~", " -", "M ", "M~", "M-", "??"]
504 4 = hard and keyword masked,
505 5 = hard and arch masked
506 6 = ebuild doesn't exist on system anymore
508 list = original output of portage.getmaskingstatus
512 masking_status = self.pkg.mask_status()
513 if masking_status is None:
516 if ("~%s keyword" % self.pkg.settings("ARCH")) in masking_status:
518 if "missing keyword" in masking_status:
520 if set(('profile', 'package.mask')).intersection(masking_status):
523 return (result, masking_status)
525 def format_mask_status2(self):
526 """Get the mask status of a given package.
528 mask = self.pkg.mask_status()
530 return pp.masking(mask)
532 arch = self.pkg.settings("ARCH")
533 keywords = self.pkg.environment('KEYWORDS')
534 mask = [determine_keyword(arch,
535 portage.settings["ACCEPT_KEYWORDS"],
537 return pp.masking(mask)
539 def format_mask(self):
540 maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-', '??']
541 maskmode = maskmodes[self.format_mask_status()[0]]
544 stable=not maskmode.strip(),
545 hard_masked=set(('M', '?', '-')).intersection(maskmode)
548 def format_cpv(self, attr=None):
552 value = getattr(self.pkg, attr)
558 def format_slot(self):
559 value = self.pkg.environment("SLOT")
561 return pp.slot(value)
566 # vim: set ts=4 sw=4 tw=79: