MergeListItem: fix "unknown repo" display
[portage.git] / pym / _emerge / Package.py
1 # Copyright 1999-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 import sys
5 from itertools import chain
6 import portage
7 from portage import _encodings, _unicode_decode, _unicode_encode
8 from portage.cache.mappings import slot_dict_class
9 from portage.const import EBUILD_PHASES
10 from portage.dep import Atom, check_required_use, use_reduce, \
11         paren_enclose, _slot_re, _slot_separator, _repo_separator
12 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
13 from portage.exception import InvalidDependString
14 from portage.repository.config import _gen_valid_repo
15 from _emerge.Task import Task
16
17 if sys.hexversion >= 0x3000000:
18         basestring = str
19         long = int
20
21 class Package(Task):
22
23         __hash__ = Task.__hash__
24         __slots__ = ("built", "cpv", "depth",
25                 "installed", "metadata", "onlydeps", "operation",
26                 "root_config", "type_name",
27                 "category", "counter", "cp", "cpv_split",
28                 "inherited", "invalid", "iuse", "masks", "mtime",
29                 "pf", "pv_split", "root", "slot", "slot_atom", "visible",) + \
30         ("_raw_metadata", "_use",)
31
32         metadata_keys = [
33                 "BUILD_TIME", "CHOST", "COUNTER", "DEPEND", "EAPI",
34                 "INHERITED", "IUSE", "KEYWORDS",
35                 "LICENSE", "PDEPEND", "PROVIDE", "RDEPEND",
36                 "repository", "PROPERTIES", "RESTRICT", "SLOT", "USE",
37                 "_mtime_", "DEFINED_PHASES", "REQUIRED_USE"]
38
39         _dep_keys = ('DEPEND', 'PDEPEND', 'RDEPEND',)
40         _use_conditional_misc_keys = ('LICENSE', 'PROPERTIES', 'RESTRICT')
41         UNKNOWN_REPO = "__unknown__"
42
43         def __init__(self, **kwargs):
44                 Task.__init__(self, **kwargs)
45                 # the SlotObject constructor assigns self.root_config from keyword args
46                 # and is an instance of a '_emerge.RootConfig.RootConfig class
47                 self.root = self.root_config.root
48                 self._raw_metadata = _PackageMetadataWrapperBase(self.metadata)
49                 self.metadata = _PackageMetadataWrapper(self, self._raw_metadata)
50                 if not self.built:
51                         self.metadata['CHOST'] = self.root_config.settings.get('CHOST', '')
52                 self.cp = portage.cpv_getkey(self.cpv)
53                 slot = self.slot
54                 if _slot_re.match(slot) is None:
55                         self._invalid_metadata('SLOT.invalid',
56                                 "SLOT: invalid value: '%s'" % slot)
57                         # Avoid an InvalidAtom exception when creating slot_atom.
58                         # This package instance will be masked due to empty SLOT.
59                         slot = '0'
60                 if (self.iuse.enabled or self.iuse.disabled) and \
61                         not eapi_has_iuse_defaults(self.metadata["EAPI"]):
62                         if not self.installed:
63                                 self._invalid_metadata('EAPI.incompatible',
64                                         "IUSE contains defaults, but EAPI doesn't allow them")
65                 self.slot_atom = portage.dep.Atom("%s%s%s" % (self.cp, _slot_separator, slot))
66                 self.category, self.pf = portage.catsplit(self.cpv)
67                 self.cpv_split = portage.catpkgsplit(self.cpv)
68                 self.pv_split = self.cpv_split[1:]
69                 if self.inherited is None:
70                         self.inherited = frozenset()
71                 repo = _gen_valid_repo(self.metadata.get('repository', ''))
72                 if not repo:
73                         repo = self.UNKNOWN_REPO
74                 self.metadata['repository'] = repo
75
76                 self._validate_deps()
77                 self.masks = self._masks()
78                 self.visible = self._visible(self.masks)
79                 if self.operation is None:
80                         if self.onlydeps or self.installed:
81                                 self.operation = "nomerge"
82                         else:
83                                 self.operation = "merge"
84
85                 self._hash_key = Package._gen_hash_key(cpv=self.cpv,
86                         installed=self.installed, onlydeps=self.onlydeps,
87                         operation=self.operation, repo_name=repo,
88                         root_config=self.root_config,
89                         type_name=self.type_name)
90                 self._hash_value = hash(self._hash_key)
91
92         @classmethod
93         def _gen_hash_key(cls, cpv=None, installed=None, onlydeps=None,
94                 operation=None, repo_name=None, root_config=None,
95                 type_name=None, **kwargs):
96
97                 if operation is None:
98                         if installed or onlydeps:
99                                 operation = "nomerge"
100                         else:
101                                 operation = "merge"
102
103                 root = None
104                 if root_config is not None:
105                         root = root_config.root
106                 else:
107                         raise TypeError("root_config argument is required")
108
109                 # For installed (and binary) packages we don't care for the repo
110                 # when it comes to hashing, because there can only be one cpv.
111                 # So overwrite the repo_key with type_name.
112                 if type_name is None:
113                         raise TypeError("type_name argument is required")
114                 elif type_name == "ebuild":
115                         if repo_name is None:
116                                 raise AssertionError(
117                                         "Package._gen_hash_key() " + \
118                                         "called without 'repo_name' argument")
119                         repo_key = repo_name
120                 else:
121                         # For installed (and binary) packages we don't care for the repo
122                         # when it comes to hashing, because there can only be one cpv.
123                         # So overwrite the repo_key with type_name.
124                         repo_key = type_name
125
126                 return (type_name, root, cpv, operation, repo_key)
127
128         def _validate_deps(self):
129                 """
130                 Validate deps. This does not trigger USE calculation since that
131                 is expensive for ebuilds and therefore we want to avoid doing
132                 in unnecessarily (like for masked packages).
133                 """
134                 eapi = self.metadata['EAPI']
135                 dep_eapi = eapi
136                 dep_valid_flag = self.iuse.is_valid_flag
137                 if self.installed:
138                         # Ignore EAPI.incompatible and conditionals missing
139                         # from IUSE for installed packages since these issues
140                         # aren't relevant now (re-evaluate when new EAPIs are
141                         # deployed).
142                         dep_eapi = None
143                         dep_valid_flag = None
144
145                 for k in self._dep_keys:
146                         v = self.metadata.get(k)
147                         if not v:
148                                 continue
149                         try:
150                                 use_reduce(v, eapi=dep_eapi, matchall=True,
151                                         is_valid_flag=dep_valid_flag, token_class=Atom)
152                         except InvalidDependString as e:
153                                 self._metadata_exception(k, e)
154
155                 k = 'PROVIDE'
156                 v = self.metadata.get(k)
157                 if v:
158                         try:
159                                 use_reduce(v, eapi=dep_eapi, matchall=True,
160                                         is_valid_flag=dep_valid_flag, token_class=Atom)
161                         except InvalidDependString as e:
162                                 self._invalid_metadata("PROVIDE.syntax",
163                                         _unicode_decode("%s: %s") % (k, e))
164
165                 for k in self._use_conditional_misc_keys:
166                         v = self.metadata.get(k)
167                         if not v:
168                                 continue
169                         try:
170                                 use_reduce(v, eapi=dep_eapi, matchall=True,
171                                         is_valid_flag=dep_valid_flag)
172                         except InvalidDependString as e:
173                                 self._metadata_exception(k, e)
174
175                 k = 'REQUIRED_USE'
176                 v = self.metadata.get(k)
177                 if v:
178                         if not eapi_has_required_use(eapi):
179                                 self._invalid_metadata('EAPI.incompatible',
180                                         "REQUIRED_USE set, but EAPI='%s' doesn't allow it" % eapi)
181                         else:
182                                 try:
183                                         check_required_use(v, (),
184                                                 self.iuse.is_valid_flag)
185                                 except InvalidDependString as e:
186                                         # Force unicode format string for python-2.x safety,
187                                         # ensuring that PortageException.__unicode__() is used
188                                         # when necessary.
189                                         self._invalid_metadata(k + ".syntax",
190                                                 _unicode_decode("%s: %s") % (k, e))
191
192                 k = 'SRC_URI'
193                 v = self.metadata.get(k)
194                 if v:
195                         try:
196                                 use_reduce(v, is_src_uri=True, eapi=eapi, matchall=True,
197                                         is_valid_flag=self.iuse.is_valid_flag)
198                         except InvalidDependString as e:
199                                 if not self.installed:
200                                         self._metadata_exception(k, e)
201
202         def copy(self):
203                 return Package(built=self.built, cpv=self.cpv, depth=self.depth,
204                         installed=self.installed, metadata=self._raw_metadata,
205                         onlydeps=self.onlydeps, operation=self.operation,
206                         root_config=self.root_config, type_name=self.type_name)
207
208         def _masks(self):
209                 masks = {}
210                 settings = self.root_config.settings
211
212                 if self.invalid is not None:
213                         masks['invalid'] = self.invalid
214
215                 if not settings._accept_chost(self.cpv, self.metadata):
216                         masks['CHOST'] = self.metadata['CHOST']
217
218                 eapi = self.metadata["EAPI"]
219                 if not portage.eapi_is_supported(eapi):
220                         masks['EAPI.unsupported'] = eapi
221                 if portage._eapi_is_deprecated(eapi):
222                         masks['EAPI.deprecated'] = eapi
223
224                 missing_keywords = settings._getMissingKeywords(
225                         self.cpv, self.metadata)
226                 if missing_keywords:
227                         masks['KEYWORDS'] = missing_keywords
228
229                 try:
230                         missing_properties = settings._getMissingProperties(
231                                 self.cpv, self.metadata)
232                         if missing_properties:
233                                 masks['PROPERTIES'] = missing_properties
234                 except InvalidDependString:
235                         # already recorded as 'invalid'
236                         pass
237
238                 mask_atom = settings._getMaskAtom(self.cpv, self.metadata)
239                 if mask_atom is not None:
240                         masks['package.mask'] = mask_atom
241
242                 system_mask = settings._getProfileMaskAtom(
243                         self.cpv, self.metadata)
244                 if system_mask is not None:
245                         masks['profile.system'] = system_mask
246
247                 try:
248                         missing_licenses = settings._getMissingLicenses(
249                                 self.cpv, self.metadata)
250                         if missing_licenses:
251                                 masks['LICENSE'] = missing_licenses
252                 except InvalidDependString:
253                         # already recorded as 'invalid'
254                         pass
255
256                 if not masks:
257                         masks = None
258
259                 return masks
260
261         def _visible(self, masks):
262
263                 if masks is not None:
264
265                         if 'EAPI.unsupported' in masks:
266                                 return False
267
268                         if 'invalid' in masks:
269                                 return False
270
271                         if not self.installed and ( \
272                                 'CHOST' in masks or \
273                                 'EAPI.deprecated' in masks or \
274                                 'KEYWORDS' in masks or \
275                                 'PROPERTIES' in masks):
276                                 return False
277
278                         if 'package.mask' in masks or \
279                                 'profile.system' in masks or \
280                                 'LICENSE' in masks:
281                                 return False
282
283                 return True
284
285         def get_keyword_mask(self):
286                 """returns None, 'missing', or 'unstable'."""
287
288                 missing = self.root_config.settings._getRawMissingKeywords(
289                                 self.cpv, self.metadata)
290
291                 if not missing:
292                         return None
293
294                 if '**' in missing:
295                         return 'missing'
296
297                 global_accept_keywords = frozenset(
298                         self.root_config.settings.get("ACCEPT_KEYWORDS", "").split())
299
300                 for keyword in missing:
301                         if keyword.lstrip("~") in global_accept_keywords:
302                                 return 'unstable'
303
304                 return 'missing'
305
306         def isHardMasked(self):
307                 """returns a bool if the cpv is in the list of
308                 expanded pmaskdict[cp] available ebuilds"""
309                 pmask = self.root_config.settings._getRawMaskAtom(
310                         self.cpv, self.metadata)
311                 return pmask is not None
312
313         def _metadata_exception(self, k, e):
314
315                 # For unicode safety with python-2.x we need to avoid
316                 # using the string format operator with a non-unicode
317                 # format string, since that will result in the
318                 # PortageException.__str__() method being invoked,
319                 # followed by unsafe decoding that may result in a
320                 # UnicodeDecodeError. Therefore, use _unicode_decode()
321                 # to ensure that format strings are unicode, so that
322                 # PortageException.__unicode__() is used when necessary
323                 # in python-2.x.
324                 if not self.installed:
325                         categorized_error = False
326                         if e.errors:
327                                 for error in e.errors:
328                                         if getattr(error, 'category', None) is None:
329                                                 continue
330                                         categorized_error = True
331                                         self._invalid_metadata(error.category,
332                                                 _unicode_decode("%s: %s") % (k, error))
333
334                         if not categorized_error:
335                                 self._invalid_metadata(k + ".syntax",
336                                         _unicode_decode("%s: %s") % (k, e))
337                 else:
338                         # For installed packages, show the path of the file
339                         # containing the invalid metadata, since the user may
340                         # want to fix the deps by hand.
341                         vardb = self.root_config.trees['vartree'].dbapi
342                         path = vardb.getpath(self.cpv, filename=k)
343                         self._invalid_metadata(k + ".syntax",
344                                 _unicode_decode("%s: %s in '%s'") % (k, e, path))
345
346         def _invalid_metadata(self, msg_type, msg):
347                 if self.invalid is None:
348                         self.invalid = {}
349                 msgs = self.invalid.get(msg_type)
350                 if msgs is None:
351                         msgs = []
352                         self.invalid[msg_type] = msgs
353                 msgs.append(msg)
354
355         def __str__(self):
356                 if self.operation == "merge":
357                         if self.type_name == "binary":
358                                 cpv_color = "PKG_BINARY_MERGE"
359                         else:
360                                 cpv_color = "PKG_MERGE"
361                 elif self.operation == "uninstall":
362                         cpv_color = "PKG_UNINSTALL"
363                 else:
364                         cpv_color = "PKG_NOMERGE"
365
366                 s = "(%s, %s" \
367                         % (portage.output.colorize(cpv_color, self.cpv + _repo_separator + self.repo) , self.type_name)
368
369                 if self.type_name == "installed":
370                         if self.root != "/":
371                                 s += " in '%s'" % self.root
372                         if self.operation == "uninstall":
373                                 s += " scheduled for uninstall"
374                 else:
375                         if self.operation == "merge":
376                                 s += " scheduled for merge"
377                                 if self.root != "/":
378                                         s += " to '%s'" % self.root
379                 s += ")"
380                 return s
381
382         if sys.hexversion < 0x3000000:
383
384                 __unicode__ = __str__
385
386                 def __str__(self):
387                         return _unicode_encode(self.__unicode__(),
388                                 encoding=_encodings['content'])
389
390         class _use_class(object):
391
392                 __slots__ = ("enabled", "_force", "_pkg", "_mask")
393
394                 def __init__(self, pkg, use_str):
395                         self._pkg = pkg
396                         self._force = None
397                         self._mask = None
398                         self.enabled = frozenset(use_str.split())
399                         if pkg.built:
400                                 # Use IUSE to validate USE settings for built packages,
401                                 # in case the package manager that built this package
402                                 # failed to do that for some reason (or in case of
403                                 # data corruption).
404                                 missing_iuse = pkg.iuse.get_missing_iuse(self.enabled)
405                                 if missing_iuse:
406                                         self.enabled = self.enabled.difference(missing_iuse)
407
408                 def _init_force_mask(self):
409                         pkgsettings = self._pkg._get_pkgsettings()
410                         self._force = pkgsettings.useforce
411                         self._mask = pkgsettings.usemask
412
413                 @property
414                 def force(self):
415                         if self._force is None:
416                                 self._init_force_mask()
417                         return self._force
418
419                 @property
420                 def mask(self):
421                         if self._mask is None:
422                                 self._init_force_mask()
423                         return self._mask
424
425         @property
426         def repo(self):
427                 return self.metadata['repository']
428
429         @property
430         def repo_priority(self):
431                 repo_info = self.root_config.settings.repositories.prepos.get(self.repo)
432                 if repo_info is None:
433                         return None
434                 return repo_info.priority
435
436         @property
437         def use(self):
438                 if self._use is None:
439                         self.metadata._init_use()
440                 return self._use
441
442         def _get_pkgsettings(self):
443                 pkgsettings = self.root_config.trees[
444                         'porttree'].dbapi.doebuild_settings
445                 pkgsettings.setcpv(self)
446                 return pkgsettings
447
448         class _iuse(object):
449
450                 __slots__ = ("__weakref__", "all", "enabled", "disabled",
451                         "tokens") + ("_iuse_implicit_match",)
452
453                 def __init__(self, tokens, iuse_implicit_match):
454                         self.tokens = tuple(tokens)
455                         self._iuse_implicit_match = iuse_implicit_match
456                         enabled = []
457                         disabled = []
458                         other = []
459                         for x in tokens:
460                                 prefix = x[:1]
461                                 if prefix == "+":
462                                         enabled.append(x[1:])
463                                 elif prefix == "-":
464                                         disabled.append(x[1:])
465                                 else:
466                                         other.append(x)
467                         self.enabled = frozenset(enabled)
468                         self.disabled = frozenset(disabled)
469                         self.all = frozenset(chain(enabled, disabled, other))
470
471                 def is_valid_flag(self, flags):
472                         """
473                         @returns: True if all flags are valid USE values which may
474                                 be specified in USE dependencies, False otherwise.
475                         """
476                         if isinstance(flags, basestring):
477                                 flags = [flags]
478
479                         for flag in flags:
480                                 if not flag in self.all and \
481                                         not self._iuse_implicit_match(flag):
482                                         return False
483                         return True
484
485                 def get_missing_iuse(self, flags):
486                         """
487                         @returns: A list of flags missing from IUSE.
488                         """
489                         if isinstance(flags, basestring):
490                                 flags = [flags]
491                         missing_iuse = []
492                         for flag in flags:
493                                 if not flag in self.all and \
494                                         not self._iuse_implicit_match(flag):
495                                         missing_iuse.append(flag)
496                         return missing_iuse
497
498         def __len__(self):
499                 return 4
500
501         def __iter__(self):
502                 """
503                 This is used to generate mtimedb resume mergelist entries, so we
504                 limit it to 4 items for backward compatibility.
505                 """
506                 return iter(self._hash_key[:4])
507
508         def __lt__(self, other):
509                 if other.cp != self.cp:
510                         return False
511                 if portage.pkgcmp(self.pv_split, other.pv_split) < 0:
512                         return True
513                 return False
514
515         def __le__(self, other):
516                 if other.cp != self.cp:
517                         return False
518                 if portage.pkgcmp(self.pv_split, other.pv_split) <= 0:
519                         return True
520                 return False
521
522         def __gt__(self, other):
523                 if other.cp != self.cp:
524                         return False
525                 if portage.pkgcmp(self.pv_split, other.pv_split) > 0:
526                         return True
527                 return False
528
529         def __ge__(self, other):
530                 if other.cp != self.cp:
531                         return False
532                 if portage.pkgcmp(self.pv_split, other.pv_split) >= 0:
533                         return True
534                 return False
535
536 _all_metadata_keys = set(x for x in portage.auxdbkeys \
537         if not x.startswith("UNUSED_"))
538 _all_metadata_keys.update(Package.metadata_keys)
539 _all_metadata_keys = frozenset(_all_metadata_keys)
540
541 _PackageMetadataWrapperBase = slot_dict_class(_all_metadata_keys)
542
543 class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
544         """
545         Detect metadata updates and synchronize Package attributes.
546         """
547
548         __slots__ = ("_pkg",)
549         _wrapped_keys = frozenset(
550                 ["COUNTER", "INHERITED", "IUSE", "SLOT", "USE", "_mtime_"])
551         _use_conditional_keys = frozenset(
552                 ['LICENSE', 'PROPERTIES', 'PROVIDE', 'RESTRICT',])
553
554         def __init__(self, pkg, metadata):
555                 _PackageMetadataWrapperBase.__init__(self)
556                 self._pkg = pkg
557                 if not pkg.built:
558                         # USE is lazy, but we want it to show up in self.keys().
559                         _PackageMetadataWrapperBase.__setitem__(self, 'USE', '')
560
561                 self.update(metadata)
562
563         def _init_use(self):
564                 if self._pkg.built:
565                         use_str = self['USE']
566                         self._pkg._use = self._pkg._use_class(
567                                 self._pkg, use_str)
568                 else:
569                         try:
570                                 use_str = _PackageMetadataWrapperBase.__getitem__(self, 'USE')
571                         except KeyError:
572                                 use_str = None
573                         calculated_use = False
574                         if not use_str:
575                                 use_str = self._pkg._get_pkgsettings()["PORTAGE_USE"]
576                                 calculated_use = True
577                         _PackageMetadataWrapperBase.__setitem__(self, 'USE', use_str)
578                         self._pkg._use = self._pkg._use_class(
579                                 self._pkg, use_str)
580                         # Initialize these now, since USE access has just triggered
581                         # setcpv, and we want to cache the result of the force/mask
582                         # calculations that were done.
583                         if calculated_use:
584                                 self._pkg._use._init_force_mask()
585
586                 return use_str
587
588         def __getitem__(self, k):
589                 v = _PackageMetadataWrapperBase.__getitem__(self, k)
590                 if k in self._use_conditional_keys:
591                         if self._pkg.root_config.settings.local_config and '?' in v:
592                                 try:
593                                         v = paren_enclose(use_reduce(v, uselist=self._pkg.use.enabled, \
594                                                 is_valid_flag=self._pkg.iuse.is_valid_flag))
595                                 except InvalidDependString:
596                                         # This error should already have been registered via
597                                         # self._pkg._invalid_metadata().
598                                         pass
599                                 else:
600                                         self[k] = v
601
602                 elif k == 'USE' and not self._pkg.built:
603                         if not v:
604                                 # This is lazy because it's expensive.
605                                 v = self._init_use()
606
607                 return v
608
609         def __setitem__(self, k, v):
610                 _PackageMetadataWrapperBase.__setitem__(self, k, v)
611                 if k in self._wrapped_keys:
612                         getattr(self, "_set_" + k.lower())(k, v)
613
614         def _set_inherited(self, k, v):
615                 if isinstance(v, basestring):
616                         v = frozenset(v.split())
617                 self._pkg.inherited = v
618
619         def _set_iuse(self, k, v):
620                 self._pkg.iuse = self._pkg._iuse(
621                         v.split(), self._pkg.root_config.settings._iuse_implicit_match)
622
623         def _set_slot(self, k, v):
624                 self._pkg.slot = v
625
626         def _set_counter(self, k, v):
627                 if isinstance(v, basestring):
628                         try:
629                                 v = long(v.strip())
630                         except ValueError:
631                                 v = 0
632                 self._pkg.counter = v
633
634         def _set_use(self, k, v):
635                 # Force regeneration of _use attribute
636                 self._pkg._use = None
637                 # Use raw metadata to restore USE conditional values
638                 # to unevaluated state
639                 raw_metadata = self._pkg._raw_metadata
640                 for x in self._use_conditional_keys:
641                         try:
642                                 self[x] = raw_metadata[x]
643                         except KeyError:
644                                 pass
645
646         def _set__mtime_(self, k, v):
647                 if isinstance(v, basestring):
648                         try:
649                                 v = long(v.strip())
650                         except ValueError:
651                                 v = 0
652                 self._pkg.mtime = v
653
654         @property
655         def properties(self):
656                 return self['PROPERTIES'].split()
657
658         @property
659         def restrict(self):
660                 return self['RESTRICT'].split()
661
662         @property
663         def defined_phases(self):
664                 """
665                 Returns tokens from DEFINED_PHASES metadata if it is defined,
666                 otherwise returns a tuple containing all possible phases. This
667                 makes it easy to do containment checks to see if it's safe to
668                 skip execution of a given phase.
669                 """
670                 s = self['DEFINED_PHASES']
671                 if s:
672                         return s.split()
673                 return EBUILD_PHASES