EAPI="4-python" and EAPI="5-progress": Add support for use.aliases and package.use...
[portage.git] / pym / _emerge / Package.py
index f87d5931289df0eff67c1f47af5816c620452cbf..1c1840836984f6411646bc8bbcf42937ced69fdc 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright 1999-2011 Gentoo Foundation
+# Copyright 1999-2012 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
 import sys
@@ -8,15 +8,19 @@ from portage import _encodings, _unicode_decode, _unicode_encode
 from portage.cache.mappings import slot_dict_class
 from portage.const import EBUILD_PHASES
 from portage.dep import Atom, check_required_use, use_reduce, \
-       paren_enclose, _slot_re, _slot_separator, _repo_separator
-from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
+       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.repository.config import _gen_valid_repo
+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):
 
@@ -25,20 +29,23 @@ 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', 'PDEPEND', 'RDEPEND',)
+       _dep_keys = ('DEPEND', 'HDEPEND', 'PDEPEND', 'RDEPEND')
+       _buildtime_keys = ('DEPEND', 'HDEPEND')
+       _runtime_keys = ('PDEPEND', 'RDEPEND')
        _use_conditional_misc_keys = ('LICENSE', 'PROPERTIES', 'RESTRICT')
-       UNKNOWN_REPO = "__unknown__"
+       UNKNOWN_REPO = _unknown_repo
 
        def __init__(self, **kwargs):
                Task.__init__(self, **kwargs)
@@ -49,33 +56,38 @@ class Package(Task):
                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'
+                               "SLOT: invalid value: '%s'" % self.metadata["SLOT"])
+               self.cpv_split = self.cpv.cpv_split
+               self.category, self.pf = portage.catsplit(self.cpv)
+               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_has_iuse_defaults(self.metadata["EAPI"]):
+                       not eapi_attrs.iuse_defaults:
                        if not self.installed:
                                self._invalid_metadata('EAPI.incompatible',
                                        "IUSE contains defaults, but EAPI doesn't allow them")
-               self.slot_atom = portage.dep.Atom("%s%s%s" % (self.cp, _slot_separator, slot))
-               self.category, self.pf = portage.catsplit(self.cpv)
-               self.cpv_split = portage.catpkgsplit(self.cpv)
-               self.pv_split = self.cpv_split[1:]
                if self.inherited is None:
                        self.inherited = frozenset()
-               repo = _gen_valid_repo(self.metadata.get('repository', ''))
-               if not repo:
-                       repo = self.UNKNOWN_REPO
-               self.metadata['repository'] = repo
-
-               self._validate_deps()
-               self.masks = self._masks()
-               self.visible = self._visible(self.masks)
+
                if self.operation is None:
                        if self.onlydeps or self.installed:
                                self.operation = "nomerge"
@@ -84,11 +96,53 @@ class Package(Task):
 
                self._hash_key = Package._gen_hash_key(cpv=self.cpv,
                        installed=self.installed, onlydeps=self.onlydeps,
-                       operation=self.operation, repo_name=repo,
+                       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,
@@ -123,7 +177,7 @@ class Package(Task):
                        # So overwrite the repo_key with type_name.
                        repo_key = type_name
 
-               return (type_name, root, cpv, operation, repo_key)
+               return (type_name, root, _unicode(cpv), operation, repo_key)
 
        def _validate_deps(self):
                """
@@ -142,15 +196,32 @@ class Package(Task):
                        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:
-                               use_reduce(v, eapi=dep_eapi, matchall=True,
-                                       is_valid_flag=dep_valid_flag, token_class=Atom)
+                               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)
@@ -174,14 +245,14 @@ class Package(Task):
 
                k = 'REQUIRED_USE'
                v = self.metadata.get(k)
-               if v:
-                       if not eapi_has_required_use(eapi):
+               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)
+                                               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
@@ -205,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):
@@ -249,13 +320,13 @@ class Package(Task):
                        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
@@ -306,6 +377,11 @@ class Package(Task):
 
        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
@@ -326,7 +402,7 @@ class Package(Task):
                                                _unicode_decode("%s: %s") % (k, error))
 
                        if not categorized_error:
-                               self._invalid_metadata(k + ".syntax",
+                               self._invalid_metadata(qacat,
                                        _unicode_decode("%s: %s") % (k, e))
                else:
                        # For installed packages, show the path of the file
@@ -334,16 +410,16 @@ class Package(Task):
                        # want to fix the deps by hand.
                        vardb = self.root_config.trees['vartree'].dbapi
                        path = vardb.getpath(self.cpv, filename=k)
-                       self._invalid_metadata(k + ".syntax",
+                       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):
@@ -395,7 +471,11 @@ class Package(Task):
                        self._expand_hidden = None
                        self._force = None
                        self._mask = None
-                       self.enabled = frozenset(use_str.split())
+                       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
@@ -468,54 +548,81 @@ class Package(Task):
 
        class _iuse(object):
 
-               __slots__ = ("__weakref__", "all", "enabled", "disabled",
-                       "tokens") + ("_iuse_implicit_match",)
+               __slots__ = ("__weakref__", "_iuse_implicit_match", "_pkg", "alias_mapping",
+                       "all", "all_aliases", "enabled", "disabled", "tokens")
 
-               def __init__(self, tokens, iuse_implicit_match):
+               def __init__(self, pkg, tokens, iuse_implicit_match, aliases, eapi):
+                       self._pkg = pkg
                        self.tokens = tuple(tokens)
                        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 \
+                               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 \
+                               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_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
 
@@ -529,28 +636,28 @@ class Package(Task):
        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
 
@@ -568,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',])
 
@@ -637,13 +744,6 @@ class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
                        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_match)
-
-       def _set_slot(self, k, v):
-               self._pkg.slot = v
-
        def _set_counter(self, k, v):
                if isinstance(v, basestring):
                        try: