EAPI="4-python" and EAPI="5-progress": Add support for use.aliases and package.use...
[portage.git] / pym / _emerge / Package.py
index 0ac5b84c6c6f7b10410d73d9d5065a527e2e8b01..1c1840836984f6411646bc8bbcf42937ced69fdc 100644 (file)
@@ -1,19 +1,26 @@
-# Copyright 1999-2010 Gentoo Foundation
+# Copyright 1999-2012 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
-import re
 import sys
 from itertools import chain
 import portage
+from portage import _encodings, _unicode_decode, _unicode_encode
 from portage.cache.mappings import slot_dict_class
-from portage.dep import isvalidatom, use_reduce, \
-       paren_enclose, _slot_re
-from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
+from portage.const import EBUILD_PHASES
+from portage.dep import Atom, check_required_use, use_reduce, \
+       paren_enclose, _slot_separator, _repo_separator
+from portage.versions import _pkg_str, _unknown_repo
+from portage.eapi import _get_eapi_attrs, eapi_has_use_aliases
+from portage.exception import InvalidDependString
+from portage.localization import _
 from _emerge.Task import Task
 
 if sys.hexversion >= 0x3000000:
        basestring = str
        long = int
+       _unicode = str
+else:
+       _unicode = unicode
 
 class Package(Task):
 
@@ -22,46 +29,246 @@ class Package(Task):
                "installed", "metadata", "onlydeps", "operation",
                "root_config", "type_name",
                "category", "counter", "cp", "cpv_split",
-               "inherited", "invalid", "iuse", "masks", "mtime",
-               "pf", "pv_split", "root", "slot", "slot_atom", "visible",) + \
-       ("_raw_metadata", "_use",)
+               "inherited", "iuse", "mtime",
+               "pf", "root", "slot", "sub_slot", "slot_atom", "version") + \
+               ("_invalid", "_raw_metadata", "_masks", "_use",
+               "_validated_atoms", "_visible")
 
        metadata_keys = [
                "BUILD_TIME", "CHOST", "COUNTER", "DEPEND", "EAPI",
-               "INHERITED", "IUSE", "KEYWORDS",
+               "HDEPEND", "INHERITED", "IUSE", "KEYWORDS",
                "LICENSE", "PDEPEND", "PROVIDE", "RDEPEND",
                "repository", "PROPERTIES", "RESTRICT", "SLOT", "USE",
                "_mtime_", "DEFINED_PHASES", "REQUIRED_USE"]
 
+       _dep_keys = ('DEPEND', 'HDEPEND', 'PDEPEND', 'RDEPEND')
+       _buildtime_keys = ('DEPEND', 'HDEPEND')
+       _runtime_keys = ('PDEPEND', 'RDEPEND')
+       _use_conditional_misc_keys = ('LICENSE', 'PROPERTIES', 'RESTRICT')
+       UNKNOWN_REPO = _unknown_repo
+
        def __init__(self, **kwargs):
                Task.__init__(self, **kwargs)
+               # the SlotObject constructor assigns self.root_config from keyword args
+               # and is an instance of a '_emerge.RootConfig.RootConfig class
                self.root = self.root_config.root
                self._raw_metadata = _PackageMetadataWrapperBase(self.metadata)
                self.metadata = _PackageMetadataWrapper(self, self._raw_metadata)
                if not self.built:
                        self.metadata['CHOST'] = self.root_config.settings.get('CHOST', '')
-               self.cp = portage.cpv_getkey(self.cpv)
-               slot = self.slot
-               if _slot_re.match(slot) is None:
+               eapi_attrs = _get_eapi_attrs(self.metadata["EAPI"])
+               self.cpv = _pkg_str(self.cpv, metadata=self.metadata,
+                       settings=self.root_config.settings)
+               if hasattr(self.cpv, 'slot_invalid'):
                        self._invalid_metadata('SLOT.invalid',
-                               "SLOT: invalid value: '%s'" % slot)
-                       # Avoid an InvalidAtom exception when creating slot_atom.
-                       # This package instance will be masked due to empty SLOT.
-                       slot = '0'
-               if (self.iuse.enabled or self.iuse.disabled) and \
-                       not eapi_has_iuse_defaults(self.metadata["EAPI"]):
-                       self._invalid_metadata('IUSE.invalid',
-                               "IUSE contains defaults, but EAPI doesn't allow them")
-               if self.metadata.get("REQUIRED_USE") and \
-                       not eapi_has_required_use(self.metadata["EAPI"]):
-                       self._invalid_metadata('REQUIRED_USE.invalid',
-                               "REQUIRED_USE set, but EAPI doesn't allow it")
-               self.slot_atom = portage.dep.Atom("%s:%s" % (self.cp, slot))
+                               "SLOT: invalid value: '%s'" % self.metadata["SLOT"])
+               self.cpv_split = self.cpv.cpv_split
                self.category, self.pf = portage.catsplit(self.cpv)
-               self.cpv_split = portage.catpkgsplit(self.cpv)
-               self.pv_split = self.cpv_split[1:]
-               self.masks = self._masks()
-               self.visible = self._visible(self.masks)
+               self.cp = self.cpv.cp
+               self.version = self.cpv.version
+               self.slot = self.cpv.slot
+               self.sub_slot = self.cpv.sub_slot
+               self.slot_atom = Atom("%s%s%s" % (self.cp, _slot_separator, self.slot))
+               # sync metadata with validated repo (may be UNKNOWN_REPO)
+               self.metadata['repository'] = self.cpv.repo
+
+               if eapi_attrs.iuse_effective:
+                       implicit_match = self.root_config.settings._iuse_effective_match
+               else:
+                       implicit_match = self.root_config.settings._iuse_implicit_match
+               usealiases = self.root_config.settings._use_manager.getUseAliases(self)
+               self.iuse = self._iuse(self, self.metadata["IUSE"].split(), implicit_match,
+                       usealiases, self.metadata["EAPI"])
+
+               if (self.iuse.enabled or self.iuse.disabled) and \
+                       not eapi_attrs.iuse_defaults:
+                       if not self.installed:
+                               self._invalid_metadata('EAPI.incompatible',
+                                       "IUSE contains defaults, but EAPI doesn't allow them")
+               if self.inherited is None:
+                       self.inherited = frozenset()
+
+               if self.operation is None:
+                       if self.onlydeps or self.installed:
+                               self.operation = "nomerge"
+                       else:
+                               self.operation = "merge"
+
+               self._hash_key = Package._gen_hash_key(cpv=self.cpv,
+                       installed=self.installed, onlydeps=self.onlydeps,
+                       operation=self.operation, repo_name=self.cpv.repo,
+                       root_config=self.root_config,
+                       type_name=self.type_name)
+               self._hash_value = hash(self._hash_key)
+
+       # For consistency with _pkg_str
+       @property
+       def _metadata(self):
+               return self.metadata
+
+       # These are calculated on-demand, so that they are calculated
+       # after FakeVartree applies its metadata tweaks.
+       @property
+       def invalid(self):
+               if self._invalid is None:
+                       self._validate_deps()
+                       if self._invalid is None:
+                               self._invalid = False
+               return self._invalid
+
+       @property
+       def masks(self):
+               if self._masks is None:
+                       self._masks = self._eval_masks()
+               return self._masks
+
+       @property
+       def visible(self):
+               if self._visible is None:
+                       self._visible = self._eval_visiblity(self.masks)
+               return self._visible
+
+       @property
+       def validated_atoms(self):
+               """
+               Returns *all* validated atoms from the deps, regardless
+               of USE conditionals, with USE conditionals inside
+               atoms left unevaluated.
+               """
+               if self._validated_atoms is None:
+                       self._validate_deps()
+               return self._validated_atoms
+
+       @property
+       def stable(self):
+               return self.cpv.stable
+
+       @classmethod
+       def _gen_hash_key(cls, cpv=None, installed=None, onlydeps=None,
+               operation=None, repo_name=None, root_config=None,
+               type_name=None, **kwargs):
+
+               if operation is None:
+                       if installed or onlydeps:
+                               operation = "nomerge"
+                       else:
+                               operation = "merge"
+
+               root = None
+               if root_config is not None:
+                       root = root_config.root
+               else:
+                       raise TypeError("root_config argument is required")
+
+               # For installed (and binary) packages we don't care for the repo
+               # when it comes to hashing, because there can only be one cpv.
+               # So overwrite the repo_key with type_name.
+               if type_name is None:
+                       raise TypeError("type_name argument is required")
+               elif type_name == "ebuild":
+                       if repo_name is None:
+                               raise AssertionError(
+                                       "Package._gen_hash_key() " + \
+                                       "called without 'repo_name' argument")
+                       repo_key = repo_name
+               else:
+                       # For installed (and binary) packages we don't care for the repo
+                       # when it comes to hashing, because there can only be one cpv.
+                       # So overwrite the repo_key with type_name.
+                       repo_key = type_name
+
+               return (type_name, root, _unicode(cpv), operation, repo_key)
+
+       def _validate_deps(self):
+               """
+               Validate deps. This does not trigger USE calculation since that
+               is expensive for ebuilds and therefore we want to avoid doing
+               in unnecessarily (like for masked packages).
+               """
+               eapi = self.metadata['EAPI']
+               dep_eapi = eapi
+               dep_valid_flag = self.iuse.is_valid_flag
+               if self.installed:
+                       # Ignore EAPI.incompatible and conditionals missing
+                       # from IUSE for installed packages since these issues
+                       # aren't relevant now (re-evaluate when new EAPIs are
+                       # deployed).
+                       dep_eapi = None
+                       dep_valid_flag = None
+
+               validated_atoms = []
+               for k in self._dep_keys:
+                       v = self.metadata.get(k)
+                       if not v:
+                               continue
+                       try:
+                               atoms = use_reduce(v, eapi=dep_eapi,
+                                       matchall=True, is_valid_flag=dep_valid_flag,
+                                       token_class=Atom, flat=True)
+                       except InvalidDependString as e:
+                               self._metadata_exception(k, e)
+                       else:
+                               validated_atoms.extend(atoms)
+                               if not self.built:
+                                       for atom in atoms:
+                                               if not isinstance(atom, Atom):
+                                                       continue
+                                               if atom.slot_operator_built:
+                                                       e = InvalidDependString(
+                                                               _("Improper context for slot-operator "
+                                                               "\"built\" atom syntax: %s") %
+                                                               (atom.unevaluated_atom,))
+                                                       self._metadata_exception(k, e)
+
+               self._validated_atoms = tuple(set(atom for atom in
+                       validated_atoms if isinstance(atom, Atom)))
+
+               k = 'PROVIDE'
+               v = self.metadata.get(k)
+               if v:
+                       try:
+                               use_reduce(v, eapi=dep_eapi, matchall=True,
+                                       is_valid_flag=dep_valid_flag, token_class=Atom)
+                       except InvalidDependString as e:
+                               self._invalid_metadata("PROVIDE.syntax",
+                                       _unicode_decode("%s: %s") % (k, e))
+
+               for k in self._use_conditional_misc_keys:
+                       v = self.metadata.get(k)
+                       if not v:
+                               continue
+                       try:
+                               use_reduce(v, eapi=dep_eapi, matchall=True,
+                                       is_valid_flag=dep_valid_flag)
+                       except InvalidDependString as e:
+                               self._metadata_exception(k, e)
+
+               k = 'REQUIRED_USE'
+               v = self.metadata.get(k)
+               if v and not self.built:
+                       if not _get_eapi_attrs(eapi).required_use:
+                               self._invalid_metadata('EAPI.incompatible',
+                                       "REQUIRED_USE set, but EAPI='%s' doesn't allow it" % eapi)
+                       else:
+                               try:
+                                       check_required_use(v, (),
+                                               self.iuse.is_valid_flag, eapi=eapi)
+                               except InvalidDependString as e:
+                                       # Force unicode format string for python-2.x safety,
+                                       # ensuring that PortageException.__unicode__() is used
+                                       # when necessary.
+                                       self._invalid_metadata(k + ".syntax",
+                                               _unicode_decode("%s: %s") % (k, e))
+
+               k = 'SRC_URI'
+               v = self.metadata.get(k)
+               if v:
+                       try:
+                               use_reduce(v, is_src_uri=True, eapi=eapi, matchall=True,
+                                       is_valid_flag=self.iuse.is_valid_flag)
+                       except InvalidDependString as e:
+                               if not self.installed:
+                                       self._metadata_exception(k, e)
 
        def copy(self):
                return Package(built=self.built, cpv=self.cpv, depth=self.depth,
@@ -69,11 +276,11 @@ class Package(Task):
                        onlydeps=self.onlydeps, operation=self.operation,
                        root_config=self.root_config, type_name=self.type_name)
 
-       def _masks(self):
+       def _eval_masks(self):
                masks = {}
                settings = self.root_config.settings
 
-               if self.invalid is not None:
+               if self.invalid is not False:
                        masks['invalid'] = self.invalid
 
                if not settings._accept_chost(self.cpv, self.metadata):
@@ -95,7 +302,7 @@ class Package(Task):
                                self.cpv, self.metadata)
                        if missing_properties:
                                masks['PROPERTIES'] = missing_properties
-               except portage.exception.InvalidDependString:
+               except InvalidDependString:
                        # already recorded as 'invalid'
                        pass
 
@@ -103,34 +310,31 @@ class Package(Task):
                if mask_atom is not None:
                        masks['package.mask'] = mask_atom
 
-               system_mask = settings._getProfileMaskAtom(
-                       self.cpv, self.metadata)
-               if system_mask is not None:
-                       masks['profile.system'] = system_mask
-
                try:
                        missing_licenses = settings._getMissingLicenses(
                                self.cpv, self.metadata)
                        if missing_licenses:
                                masks['LICENSE'] = missing_licenses
-               except portage.exception.InvalidDependString:
+               except InvalidDependString:
                        # already recorded as 'invalid'
                        pass
 
                if not masks:
-                       masks = None
+                       masks = False
 
                return masks
 
-       def _visible(self, masks):
+       def _eval_visiblity(self, masks):
 
-               if masks is not None:
+               if masks is not False:
 
                        if 'EAPI.unsupported' in masks:
                                return False
 
+                       if 'invalid' in masks:
+                               return False
+
                        if not self.installed and ( \
-                               'invalid' in masks or \
                                'CHOST' in masks or \
                                'EAPI.deprecated' in masks or \
                                'KEYWORDS' in masks or \
@@ -138,27 +342,87 @@ class Package(Task):
                                return False
 
                        if 'package.mask' in masks or \
-                               'profile.system' in masks or \
                                'LICENSE' in masks:
                                return False
 
                return True
 
+       def get_keyword_mask(self):
+               """returns None, 'missing', or 'unstable'."""
+
+               missing = self.root_config.settings._getRawMissingKeywords(
+                               self.cpv, self.metadata)
+
+               if not missing:
+                       return None
+
+               if '**' in missing:
+                       return 'missing'
+
+               global_accept_keywords = frozenset(
+                       self.root_config.settings.get("ACCEPT_KEYWORDS", "").split())
+
+               for keyword in missing:
+                       if keyword.lstrip("~") in global_accept_keywords:
+                               return 'unstable'
+
+               return 'missing'
+
+       def isHardMasked(self):
+               """returns a bool if the cpv is in the list of
+               expanded pmaskdict[cp] available ebuilds"""
+               pmask = self.root_config.settings._getRawMaskAtom(
+                       self.cpv, self.metadata)
+               return pmask is not None
+
+       def _metadata_exception(self, k, e):
+
+               if k.endswith('DEPEND'):
+                       qacat = 'dependency.syntax'
+               else:
+                       qacat = k + ".syntax"
+
+               # For unicode safety with python-2.x we need to avoid
+               # using the string format operator with a non-unicode
+               # format string, since that will result in the
+               # PortageException.__str__() method being invoked,
+               # followed by unsafe decoding that may result in a
+               # UnicodeDecodeError. Therefore, use _unicode_decode()
+               # to ensure that format strings are unicode, so that
+               # PortageException.__unicode__() is used when necessary
+               # in python-2.x.
+               if not self.installed:
+                       categorized_error = False
+                       if e.errors:
+                               for error in e.errors:
+                                       if getattr(error, 'category', None) is None:
+                                               continue
+                                       categorized_error = True
+                                       self._invalid_metadata(error.category,
+                                               _unicode_decode("%s: %s") % (k, error))
+
+                       if not categorized_error:
+                               self._invalid_metadata(qacat,
+                                       _unicode_decode("%s: %s") % (k, e))
+               else:
+                       # For installed packages, show the path of the file
+                       # containing the invalid metadata, since the user may
+                       # want to fix the deps by hand.
+                       vardb = self.root_config.trees['vartree'].dbapi
+                       path = vardb.getpath(self.cpv, filename=k)
+                       self._invalid_metadata(qacat,
+                               _unicode_decode("%s: %s in '%s'") % (k, e, path))
+
        def _invalid_metadata(self, msg_type, msg):
-               if self.invalid is None:
-                       self.invalid = {}
-               msgs = self.invalid.get(msg_type)
+               if self._invalid is None:
+                       self._invalid = {}
+               msgs = self._invalid.get(msg_type)
                if msgs is None:
                        msgs = []
-                       self.invalid[msg_type] = msgs
+                       self._invalid[msg_type] = msgs
                msgs.append(msg)
 
        def __str__(self):
-               if self.operation is None:
-                       self.operation = "merge"
-                       if self.onlydeps or self.installed:
-                               self.operation = "nomerge"
-
                if self.operation == "merge":
                        if self.type_name == "binary":
                                cpv_color = "PKG_BINARY_MERGE"
@@ -170,120 +434,230 @@ class Package(Task):
                        cpv_color = "PKG_NOMERGE"
 
                s = "(%s, %s" \
-                       % (portage.output.colorize(cpv_color, self.cpv) , self.type_name)
+                       % (portage.output.colorize(cpv_color, self.cpv + _repo_separator + self.repo) , self.type_name)
 
                if self.type_name == "installed":
-                       if self.root != "/":
-                               s += " in '%s'" % self.root
+                       if self.root_config.settings['ROOT'] != "/":
+                               s += " in '%s'" % self.root_config.settings['ROOT']
                        if self.operation == "uninstall":
                                s += " scheduled for uninstall"
                else:
                        if self.operation == "merge":
                                s += " scheduled for merge"
-                               if self.root != "/":
-                                       s += " to '%s'" % self.root
+                               if self.root_config.settings['ROOT'] != "/":
+                                       s += " to '%s'" % self.root_config.settings['ROOT']
                s += ")"
                return s
 
+       if sys.hexversion < 0x3000000:
+
+               __unicode__ = __str__
+
+               def __str__(self):
+                       return _unicode_encode(self.__unicode__(),
+                               encoding=_encodings['content'])
+
        class _use_class(object):
 
-               __slots__ = ("__weakref__", "enabled")
+               __slots__ = ("enabled", "_expand", "_expand_hidden",
+                       "_force", "_pkg", "_mask")
+
+               # Share identical frozenset instances when available.
+               _frozensets = {}
+
+               def __init__(self, pkg, use_str):
+                       self._pkg = pkg
+                       self._expand = None
+                       self._expand_hidden = None
+                       self._force = None
+                       self._mask = None
+                       enabled_flags = use_str.split()
+                       if eapi_has_use_aliases(pkg.metadata["EAPI"]):
+                               for enabled_flag in enabled_flags:
+                                       enabled_flags.extend(pkg.iuse.alias_mapping.get(enabled_flag, []))
+                       self.enabled = frozenset(enabled_flags)
+                       if pkg.built:
+                               # Use IUSE to validate USE settings for built packages,
+                               # in case the package manager that built this package
+                               # failed to do that for some reason (or in case of
+                               # data corruption).
+                               missing_iuse = pkg.iuse.get_missing_iuse(self.enabled)
+                               if missing_iuse:
+                                       self.enabled = self.enabled.difference(missing_iuse)
+
+               def _init_force_mask(self):
+                       pkgsettings = self._pkg._get_pkgsettings()
+                       frozensets = self._frozensets
+                       s = frozenset(
+                               pkgsettings.get("USE_EXPAND", "").lower().split())
+                       self._expand = frozensets.setdefault(s, s)
+                       s = frozenset(
+                               pkgsettings.get("USE_EXPAND_HIDDEN", "").lower().split())
+                       self._expand_hidden = frozensets.setdefault(s, s)
+                       s = pkgsettings.useforce
+                       self._force = frozensets.setdefault(s, s)
+                       s = pkgsettings.usemask
+                       self._mask = frozensets.setdefault(s, s)
+
+               @property
+               def expand(self):
+                       if self._expand is None:
+                               self._init_force_mask()
+                       return self._expand
+
+               @property
+               def expand_hidden(self):
+                       if self._expand_hidden is None:
+                               self._init_force_mask()
+                       return self._expand_hidden
+
+               @property
+               def force(self):
+                       if self._force is None:
+                               self._init_force_mask()
+                       return self._force
+
+               @property
+               def mask(self):
+                       if self._mask is None:
+                               self._init_force_mask()
+                       return self._mask
+
+       @property
+       def repo(self):
+               return self.metadata['repository']
 
-               def __init__(self, use):
-                       self.enabled = frozenset(use)
+       @property
+       def repo_priority(self):
+               repo_info = self.root_config.settings.repositories.prepos.get(self.repo)
+               if repo_info is None:
+                       return None
+               return repo_info.priority
 
        @property
        def use(self):
                if self._use is None:
-                       self._use = self._use_class(self.metadata['USE'].split())
+                       self.metadata._init_use()
                return self._use
 
+       def _get_pkgsettings(self):
+               pkgsettings = self.root_config.trees[
+                       'porttree'].dbapi.doebuild_settings
+               pkgsettings.setcpv(self)
+               return pkgsettings
+
        class _iuse(object):
 
-               __slots__ = ("__weakref__", "all", "enabled", "disabled",
-                       "tokens") + ("_iuse_implicit_regex",)
+               __slots__ = ("__weakref__", "_iuse_implicit_match", "_pkg", "alias_mapping",
+                       "all", "all_aliases", "enabled", "disabled", "tokens")
 
-               def __init__(self, tokens, iuse_implicit_regex):
+               def __init__(self, pkg, tokens, iuse_implicit_match, aliases, eapi):
+                       self._pkg = pkg
                        self.tokens = tuple(tokens)
-                       self._iuse_implicit_regex = iuse_implicit_regex
+                       self._iuse_implicit_match = iuse_implicit_match
                        enabled = []
                        disabled = []
                        other = []
+                       enabled_aliases = []
+                       disabled_aliases = []
+                       other_aliases = []
+                       aliases_supported = eapi_has_use_aliases(eapi)
+                       self.alias_mapping = {}
                        for x in tokens:
                                prefix = x[:1]
                                if prefix == "+":
                                        enabled.append(x[1:])
+                                       if aliases_supported:
+                                               self.alias_mapping[x[1:]] = aliases.get(x[1:], [])
+                                               enabled_aliases.extend(self.alias_mapping[x[1:]])
                                elif prefix == "-":
                                        disabled.append(x[1:])
+                                       if aliases_supported:
+                                               self.alias_mapping[x[1:]] = aliases.get(x[1:], [])
+                                               disabled_aliases.extend(self.alias_mapping[x[1:]])
                                else:
                                        other.append(x)
-                       self.enabled = frozenset(enabled)
-                       self.disabled = frozenset(disabled)
+                                       if aliases_supported:
+                                               self.alias_mapping[x] = aliases.get(x, [])
+                                               other_aliases.extend(self.alias_mapping[x])
+                       self.enabled = frozenset(chain(enabled, enabled_aliases))
+                       self.disabled = frozenset(chain(disabled, disabled_aliases))
                        self.all = frozenset(chain(enabled, disabled, other))
+                       self.all_aliases = frozenset(chain(enabled_aliases, disabled_aliases, other_aliases))
 
                def is_valid_flag(self, flags):
                        """
-                       @returns: True if all flags are valid USE values which may
+                       @return: True if all flags are valid USE values which may
                                be specified in USE dependencies, False otherwise.
                        """
                        if isinstance(flags, basestring):
                                flags = [flags]
 
                        for flag in flags:
-                               if not flag in self.all and \
-                                       self._iuse_implicit_regex.match(flag) is None:
+                               if not flag in self.all and not flag in self.all_aliases and \
+                                       not self._iuse_implicit_match(flag):
                                        return False
                        return True
-               
+
                def get_missing_iuse(self, flags):
                        """
-                       @returns: A list of flags missing from IUSE.
+                       @return: A list of flags missing from IUSE.
                        """
                        if isinstance(flags, basestring):
                                flags = [flags]
                        missing_iuse = []
                        for flag in flags:
-                               if not flag in self.all and \
-                                       self._iuse_implicit_regex.match(flag) is None:
+                               if not flag in self.all and not flag in self.all_aliases and \
+                                       not self._iuse_implicit_match(flag):
                                        missing_iuse.append(flag)
                        return missing_iuse
 
-       def _get_hash_key(self):
-               hash_key = getattr(self, "_hash_key", None)
-               if hash_key is None:
-                       if self.operation is None:
-                               self.operation = "merge"
-                               if self.onlydeps or self.installed:
-                                       self.operation = "nomerge"
-                       self._hash_key = \
-                               (self.type_name, self.root, self.cpv, self.operation)
-               return self._hash_key
+               def get_real_flag(self, flag):
+                       if flag in self.all:
+                               return flag
+                       elif flag in self.all_aliases:
+                               for k, v in self.alias_mapping.items():
+                                       if flag in v:
+                                               return k
+                       else:
+                               raise ValueError("'%s' flag is not in IUSE and is not an alias of any flag in IUSE of '%s::%s'" %
+                                       (flag, self._pkg.cpv, self._pkg.repo))
+
+       def __len__(self):
+               return 4
+
+       def __iter__(self):
+               """
+               This is used to generate mtimedb resume mergelist entries, so we
+               limit it to 4 items for backward compatibility.
+               """
+               return iter(self._hash_key[:4])
 
        def __lt__(self, other):
                if other.cp != self.cp:
                        return False
-               if portage.pkgcmp(self.pv_split, other.pv_split) < 0:
+               if portage.vercmp(self.version, other.version) < 0:
                        return True
                return False
 
        def __le__(self, other):
                if other.cp != self.cp:
                        return False
-               if portage.pkgcmp(self.pv_split, other.pv_split) <= 0:
+               if portage.vercmp(self.version, other.version) <= 0:
                        return True
                return False
 
        def __gt__(self, other):
                if other.cp != self.cp:
                        return False
-               if portage.pkgcmp(self.pv_split, other.pv_split) > 0:
+               if portage.vercmp(self.version, other.version) > 0:
                        return True
                return False
 
        def __ge__(self, other):
                if other.cp != self.cp:
                        return False
-               if portage.pkgcmp(self.pv_split, other.pv_split) >= 0:
+               if portage.vercmp(self.version, other.version) >= 0:
                        return True
                return False
 
@@ -301,7 +675,7 @@ class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
 
        __slots__ = ("_pkg",)
        _wrapped_keys = frozenset(
-               ["COUNTER", "INHERITED", "IUSE", "SLOT", "USE", "_mtime_"])
+               ["COUNTER", "INHERITED", "USE", "_mtime_"])
        _use_conditional_keys = frozenset(
                ['LICENSE', 'PROPERTIES', 'PROVIDE', 'RESTRICT',])
 
@@ -314,13 +688,39 @@ class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
 
                self.update(metadata)
 
+       def _init_use(self):
+               if self._pkg.built:
+                       use_str = self['USE']
+                       self._pkg._use = self._pkg._use_class(
+                               self._pkg, use_str)
+               else:
+                       try:
+                               use_str = _PackageMetadataWrapperBase.__getitem__(self, 'USE')
+                       except KeyError:
+                               use_str = None
+                       calculated_use = False
+                       if not use_str:
+                               use_str = self._pkg._get_pkgsettings()["PORTAGE_USE"]
+                               calculated_use = True
+                       _PackageMetadataWrapperBase.__setitem__(self, 'USE', use_str)
+                       self._pkg._use = self._pkg._use_class(
+                               self._pkg, use_str)
+                       # Initialize these now, since USE access has just triggered
+                       # setcpv, and we want to cache the result of the force/mask
+                       # calculations that were done.
+                       if calculated_use:
+                               self._pkg._use._init_force_mask()
+
+               return use_str
+
        def __getitem__(self, k):
                v = _PackageMetadataWrapperBase.__getitem__(self, k)
                if k in self._use_conditional_keys:
                        if self._pkg.root_config.settings.local_config and '?' in v:
                                try:
-                                       v = paren_enclose(use_reduce(v, uselist=self._pkg.use.enabled))
-                               except portage.exception.InvalidDependString:
+                                       v = paren_enclose(use_reduce(v, uselist=self._pkg.use.enabled, \
+                                               is_valid_flag=self._pkg.iuse.is_valid_flag))
+                               except InvalidDependString:
                                        # This error should already have been registered via
                                        # self._pkg._invalid_metadata().
                                        pass
@@ -330,11 +730,7 @@ class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
                elif k == 'USE' and not self._pkg.built:
                        if not v:
                                # This is lazy because it's expensive.
-                               pkgsettings = self._pkg.root_config.trees[
-                                       'porttree'].dbapi.doebuild_settings
-                               pkgsettings.setcpv(self._pkg)
-                               v = pkgsettings["PORTAGE_USE"]
-                               _PackageMetadataWrapperBase.__setitem__(self, 'USE', v)
+                               v = self._init_use()
 
                return v
 
@@ -342,30 +738,12 @@ class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
                _PackageMetadataWrapperBase.__setitem__(self, k, v)
                if k in self._wrapped_keys:
                        getattr(self, "_set_" + k.lower())(k, v)
-               elif k in self._use_conditional_keys:
-                       try:
-                               reduced = use_reduce(v, matchall=1, flat=True)
-                       except portage.exception.InvalidDependString as e:
-                               self._pkg._invalid_metadata(k + ".syntax", "%s: %s" % (k, e))
-                       else:
-                               if reduced and k == 'PROVIDE':
-                                       for x in reduced:
-                                               if not isvalidatom(x):
-                                                       self._pkg._invalid_metadata(k + ".syntax",
-                                                               "%s: %s" % (k, x))
 
        def _set_inherited(self, k, v):
                if isinstance(v, basestring):
                        v = frozenset(v.split())
                self._pkg.inherited = v
 
-       def _set_iuse(self, k, v):
-               self._pkg.iuse = self._pkg._iuse(
-                       v.split(), self._pkg.root_config.settings._iuse_implicit_re)
-
-       def _set_slot(self, k, v):
-               self._pkg.slot = v
-
        def _set_counter(self, k, v):
                if isinstance(v, basestring):
                        try:
@@ -404,4 +782,13 @@ class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
 
        @property
        def defined_phases(self):
-               return self['DEFINED_PHASES'].split()
+               """
+               Returns tokens from DEFINED_PHASES metadata if it is defined,
+               otherwise returns a tuple containing all possible phases. This
+               makes it easy to do containment checks to see if it's safe to
+               skip execution of a given phase.
+               """
+               s = self['DEFINED_PHASES']
+               if s:
+                       return s.split()
+               return EBUILD_PHASES