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.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
57 from gentoolkit.flag import get_flags
58 from gentoolkit.eprefix import EPREFIX
65 default_settings = portage.config(local_config=True, eprefix=EPREFIX)
66 default_settings.lock()
67 nolocal_settings = portage.config(local_config=False, eprefix=EPREFIX)
68 nolocal_settings.lock()
70 default_settings = portage.config(local_config=True)
71 default_settings.lock()
72 nolocal_settings = portage.config(local_config=False)
73 nolocal_settings.lock()
80 """Exposes the state of a given CPV."""
82 def __init__(self, cpv, validate=False, local_config=True):
83 if isinstance(cpv, CPV):
84 self.__dict__.update(cpv.__dict__)
86 CPV.__init__(self, cpv, validate=validate)
88 if validate and not all(
89 hasattr(self, x) for x in ('category', 'version')
91 # CPV allows some things that Package must not
92 raise errors.GentoolkitInvalidPackage(self.cpv)
95 self._settings = default_settings
97 self._settings = nolocal_settings
100 self._package_path = None
102 self._metadata = None
104 self._portdir_path = None
107 return "<%s %r>" % (self.__class__.__name__, self.cpv)
110 return hash(self.cpv)
112 def __contains__(self, key):
113 return key in self.cpv
120 """Instantiate a L{gentoolkit.metadata.MetaData} object here."""
122 from gentoolkit.metadata import MetaData
124 if self._metadata is None:
125 metadata_path = os.path.join(
126 self.package_path(), 'metadata.xml'
129 self._metadata = MetaData(metadata_path)
132 if e.errno != errno.ENOENT:
136 return self._metadata
140 """Instantiate a L{portage.dbapi.vartree.dblink} object here."""
142 if self._dblink is None:
143 self._dblink = portage.dblink(
145 "%s-%s" % (self.name, self.fullversion),
146 self._settings["ROOT"],
154 """Instantiate a L{gentoolkit.dependencies.Dependencies} object here."""
156 from gentoolkit.dependencies import Dependencies
158 if self._deps is None:
159 self._deps = Dependencies(self.cpv)
163 def environment(self, envvars, prefer_vdb=True, fallback=True):
164 """Returns one or more of the predefined environment variables.
166 Some available envvars are:
167 ----------------------
168 BINPKGMD5 COUNTER FEATURES LICENSE SRC_URI
169 CATEGORY CXXFLAGS HOMEPAGE PDEPEND USE
170 CBUILD DEFINED_PHASES INHERITED PF
171 CFLAGS DEPEND IUSE PROVIDE
172 CHOST DESCRIPTION KEYWORDS RDEPEND
173 CONTENTS EAPI LDFLAGS SLOT
176 >>> pkg = Package('sys-apps/portage-2.1.6.13')
177 >>> pkg.environment('USE')
178 'elibc_glibc kernel_linux userland_GNU x86'
179 >>> pkg.environment(('USE', 'IUSE'))
180 ['elibc_glibc kernel_linux userland_GNU x86',
181 'build doc epydoc selinux linguas_pl']
183 @type envvars: str or array
184 @param envvars: one or more of (DEPEND, SRC_URI, etc.)
185 @type prefer_vdb: bool
186 @keyword prefer_vdb: if True, look in the vardb before portdb, else
187 reverse order. Specifically KEYWORDS will get more recent
188 information by preferring portdb.
190 @keyword fallback: query only the preferred db if False
192 @return: str if envvars is str, list if envvars is array
193 @raise KeyError: if key is not found in requested db(s)
197 if isinstance(envvars, str):
202 result = VARDB.aux_get(self.cpv, envvars)
207 result = PORTDB.aux_get(self.cpv, envvars)
209 err = "aux_get returned unexpected results"
210 raise errors.GentoolkitFatalError(err)
213 result = PORTDB.aux_get(self.cpv, envvars)
218 result = VARDB.aux_get(self.cpv, envvars)
220 err = "aux_get returned unexpected results"
221 raise errors.GentoolkitFatalError(err)
228 """Return True if package exists in the Portage tree, else False"""
230 return bool(PORTDB.cpv_exists(self.cpv))
232 def settings(self, key):
233 """Returns the value of the given key for this package (useful
234 for package.* files."""
236 if self._settings.locked:
237 self._settings.unlock()
239 result = self._settings[key]
241 self._settings.lock()
244 def mask_status(self):
245 """Shortcut to L{portage.getmaskingstatus}.
248 @return: a list containing none or some of:
256 if self._settings.locked:
257 self._settings.unlock()
259 result = portage.getmaskingstatus(self.cpv,
260 settings=self._settings,
263 # getmaskingstatus doesn't support packages without ebuilds in the
269 def mask_reason(self):
270 """Shortcut to L{portage.getmaskingreason}.
272 @rtype: None or tuple
273 @return: empty tuple if pkg not masked OR
274 ('mask reason', 'mask location')
278 result = portage.getmaskingreason(self.cpv,
279 settings=self._settings,
281 return_location=True)
285 # getmaskingstatus doesn't support packages without ebuilds in the
291 def ebuild_path(self, in_vartree=False):
292 """Returns the complete path to the .ebuild file.
295 >>> pkg.ebuild_path()
296 '/usr/portage/sys-apps/portage/portage-2.1.6.13.ebuild'
297 >>> pkg.ebuild_path(in_vartree=True)
298 '/var/db/pkg/sys-apps/portage-2.1.6.13/portage-2.1.6.13.ebuild'
302 return VARDB.findname(self.cpv)
303 return PORTDB.findname(self.cpv)
305 def package_path(self, in_vartree=False):
306 """Return the path to where the ebuilds and other files reside."""
309 return self.dblink.getpath()
310 return os.sep.join(self.ebuild_path().split(os.sep)[:-1])
312 def repo_name(self, fallback=True):
313 """Determine the repository name.
316 @param fallback: if the repo_name file does not exist, return the
317 repository name from the path
319 @return: output of the repository metadata file, which stores the
320 repo_name variable, or try to get the name of the repo from
322 @raise GentoolkitFatalError: if fallback is False and repo_name is
323 not specified by the repository.
327 return self.environment('repository')
328 except errors.GentoolkitFatalError:
330 return self.package_path().split(os.sep)[-3]
334 """Returns the USE flags active at time of installation."""
336 return self.dblink.getstring("USE")
338 def use_status(self):
339 """Returns the USE flags active for installation."""
341 iuse, final_flags = get_flags(self.cpv, final_setting=True)
344 def parsed_contents(self):
345 """Returns the parsed CONTENTS file.
348 @return: {'/full/path/to/obj': ['type', 'timestamp', 'md5sum'], ...}
351 return self.dblink.getcontents()
354 """Estimates the installed size of the contents of this package.
357 @return: (size, number of files in total, number of uncounted files)
361 size = n_files = n_uncounted = 0
362 for f in self.parsed_contents():
368 # Remove hardlinks by checking for duplicate inodes. Bug #301026.
369 file_inode = st.st_ino
370 if file_inode in seen:
380 return (size, n_files, n_uncounted)
382 def is_installed(self):
383 """Returns True if this package is installed (merged)."""
385 return self.dblink.exists()
387 def is_overlay(self):
388 """Returns True if the package is in an overlay."""
390 ebuild, tree = PORTDB.findname2(self.cpv)
393 if self._portdir_path is None:
394 self._portdir_path = os.path.realpath(self._settings["PORTDIR"])
395 return (tree and tree != self._portdir_path)
398 """Returns True if this package is masked against installation.
400 @note: We blindly assume that the package actually exists on disk.
403 unmasked = PORTDB.xmatch("match-visible", self.cpv)
404 return self.cpv not in unmasked
407 class PackageFormatter(object):
408 """When applied to a L{gentoolkit.package.Package} object, determine the
409 location (Portage Tree vs. overlay), install status and masked status. That
410 information can then be easily formatted and displayed.
413 >>> from gentoolkit.helpers import find_packages
414 >>> from gentoolkit.package import PackageFormatter
415 >>> pkgs = [PackageFormatter(x) for x in find_packages('gcc')]
417 ... # Only print packages that are installed and from the Portage
419 ... if set('IP').issubset(pkg.location):
422 [IP-] [ ] sys-devel/gcc-4.3.2-r3 (4.3)
424 @type pkg: L{gentoolkit.package.Package}
425 @param pkg: package to format
426 @type do_format: bool
427 @param do_format: Whether to format the package name or not.
428 Essentially C{do_format} should be set to False when piping or when
429 quiet output is desired. If C{do_format} is False, only the location
430 attribute will be created to save time.
433 _tmpl_verbose = "[$location] [$mask] $cpv:$slot"
436 def __init__(self, pkg, do_format=True, custom_format=None):
438 self._do_format = do_format
440 self._location = None
441 if not custom_format:
443 custom_format = self._tmpl_verbose
445 custom_format = self._tmpl_quiet
446 self.tmpl = Template(custom_format)
447 self.format_vars = LazyItemsDict()
451 return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self))
454 if self._str is None:
455 self._str = self.tmpl.safe_substitute(self.format_vars)
460 if self._location is None:
461 self._location = self.format_package_location()
462 return self._location
466 """Package to format"""
470 def pkg(self, value):
471 if self._pkg == value:
474 self._location = None
476 fmt_vars = self.format_vars
477 self.format_vars.clear()
478 fmt_vars.addLazySingleton("location",
479 lambda: getattr(self, "location"))
480 fmt_vars.addLazySingleton("mask", self.format_mask)
481 fmt_vars.addLazySingleton("mask2", self.format_mask_status2)
482 fmt_vars.addLazySingleton("cpv", self.format_cpv)
483 fmt_vars.addLazySingleton("cp", self.format_cpv, "cp")
484 fmt_vars.addLazySingleton("category", self.format_cpv, "category")
485 fmt_vars.addLazySingleton("name", self.format_cpv, "name")
486 fmt_vars.addLazySingleton("version", self.format_cpv, "version")
487 fmt_vars.addLazySingleton("revision", self.format_cpv, "revision")
488 fmt_vars.addLazySingleton("fullversion", self.format_cpv,
490 fmt_vars.addLazySingleton("slot", self.format_slot)
491 fmt_vars.addLazySingleton("repo", self.pkg.repo_name)
492 fmt_vars.addLazySingleton("keywords", self.format_keywords)
494 def format_package_location(self):
495 """Get the install status (in /var/db/?) and origin (from an overlay
496 and the Portage tree?).
500 'I--' : Installed but ebuild doesn't exist on system anymore
501 '-P-' : Not installed and from the Portage tree
502 '--O' : Not installed and from an overlay
503 'IP-' : Installed and from the Portage tree
504 'I-O' : Installed and from an overlay
507 result = ['-', '-', '-']
509 if self.pkg.is_installed():
512 overlay = self.pkg.is_overlay()
520 return ''.join(result)
522 def format_mask_status(self):
523 """Get the mask status of a given package.
525 @rtype: tuple: (int, list)
526 @return: int = an index for this list:
527 [" ", " ~", " -", "M ", "M~", "M-", "??"]
532 4 = hard and keyword masked,
533 5 = hard and arch masked
534 6 = ebuild doesn't exist on system anymore
536 list = original output of portage.getmaskingstatus
540 masking_status = self.pkg.mask_status()
541 if masking_status is None:
544 if ("~%s keyword" % self.pkg.settings("ARCH")) in masking_status:
546 if "missing keyword" in masking_status:
548 if set(('profile', 'package.mask')).intersection(masking_status):
551 return (result, masking_status)
553 def format_mask_status2(self):
554 """Get the mask status of a given package.
556 mask = self.pkg.mask_status()
558 return pp.masking(mask)
560 arch = self.pkg.settings("ARCH")
561 keywords = self.pkg.environment('KEYWORDS')
562 mask = [determine_keyword(arch,
563 portage.settings["ACCEPT_KEYWORDS"],
565 return pp.masking(mask)
567 def format_mask(self):
568 maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-', '??']
569 maskmode = maskmodes[self.format_mask_status()[0]]
572 stable=not maskmode.strip(),
573 hard_masked=set(('M', '?', '-')).intersection(maskmode)
576 def format_cpv(self, attr=None):
580 value = getattr(self.pkg, attr)
586 def format_slot(self):
587 value = self.pkg.environment("SLOT")
589 return pp.slot(value)
593 def format_keywords(self):
594 value = self.pkg.environment("KEYWORDS")
596 return pp.keyword(value)
601 # vim: set ts=4 sw=4 tw=79: