Ejemplo n.º 1
0
class wrapper(base):

    __slots__ = ("_raw_pkg", )

    klass.inject_richcmp_methods_from_cmp(locals())

    def operations(self, domain, **kwds):
        return self._raw_pkg._operations(domain, self, **kwds)

    def __init__(self, raw_pkg):
        object.__setattr__(self, "_raw_pkg", raw_pkg)

    def __cmp__(self, other):
        if isinstance(other, wrapper):
            return cmp(self._raw_pkg, other._raw_pkg)
        return cmp(self._raw_pkg, other)

    def __eq__(self, other):
        if isinstance(other, wrapper):
            return cmp(self._raw_pkg, other._raw_pkg) == 0
        return cmp(self._raw_pkg, other) == 0

    def __ne__(self, other):
        return not self == other

    __getattr__ = klass.GetAttrProxy("_raw_pkg")

    built = klass.alias_attr("_raw_pkg.built")
    versioned_atom = klass.alias_attr("_raw_pkg.versioned_atom")
    unversioned_atom = klass.alias_attr("_raw_pkg.unversioned_atom")
    is_supported = klass.alias_attr('_raw_pkg.is_supported')

    def __hash__(self):
        return hash(self._raw_pkg)
Ejemplo n.º 2
0
Archivo: cpv.py Proyecto: chutz/pkgcore
    class CPV(base.base, base_cls):
        """
        base ebuild package class

        :ivar category: str category
        :ivar package: str package
        :ivar key: strkey (cat/pkg)
        :ivar version: str version
        :ivar revision: int revision
        :ivar versioned_atom: atom matching this exact version
        :ivar unversioned_atom: atom matching all versions of this package
        :cvar _get_attr: mapping of attr:callable to generate attributes on the fly
        """

        __slots__ = ()

        inject_richcmp_methods_from_cmp(locals())

        #       __metaclass__ = WeakInstMeta

        #       __inst_caching__ = True

        def __repr__(self):
            return '<%s cpvstr=%s @%#8x>' % (self.__class__.__name__,
                                             getattr(self, 'cpvstr',
                                                     None), id(self))

        # for some insane reason, py3k doesn't pick up the __hash__... add it
        # manually.
        __hash__ = base_cls.__hash__

        @property
        def versioned_atom(self):
            return atom.atom("=%s" % self.cpvstr)

        @property
        def unversioned_atom(self):
            return atom.atom(self.key)

        @classmethod
        def versioned(cls, *args):
            return cls(versioned=True, *args)

        @classmethod
        def unversioned(cls, *args):
            return cls(versioned=False, *args)

        def __reduce__(self):
            return (self.__class__, (self.cpvstr, ), None, None, None)
Ejemplo n.º 3
0
class FakePkg(base):

    # XXX why isn't this using existing classes?
    __slotting_intentionally_disabled__ = True

    def __init__(self, pkg, ver, data):
        base.__init__(self)
        self.pkg = pkg
        self.ver = ver
        self._get_attr = {k: partial(passthru, v) for k, v in data.iteritems()}

    # disable protection.  don't want it here
    __setattr__ = object.__setattr__
    __getattr__ = dynamic_getattr_dict

    def __cmp__(self, other):
        return cmp(self.ver, other.ver)

    inject_richcmp_methods_from_cmp(locals())
Ejemplo n.º 4
0
class atom(boolean.AndRestriction):
    """Currently implements gentoo ebuild atom parsing.

    Should be converted into an agnostic dependency base.
    """

    # note we don't need _hash
    __slots__ = ("blocks", "blocks_strongly", "op", "cpvstr", "negate_vers",
                 "use", "slot_operator", "slot", "subslot", "category",
                 "version", "revision", "fullver", "package", "key", "repo_id",
                 "_hash")

    type = packages.package_type

    negate = False

    _evaluate_collapse = True

    __attr_comparison__ = ("cpvstr", "op", "blocks", "negate_vers", "use",
                           "slot", "subslot", "slot_operator", "repo_id")

    inject_richcmp_methods_from_cmp(locals())
    # hack; combine these 2 metaclasses at some point...
    locals().pop("__eq__", None)
    locals().pop("__ne__", None)
    __metaclass__ = generic_equality
    __inst_caching__ = True

    locals().update(atom_overrides.iteritems())

    # overrided in child class if it's supported
    evaluate_depset = None

    @property
    def blocks_temp_ignorable(self):
        return not self.blocks_strongly

    weak_blocker = alias_attr("blocks_temp_ignorable")

    def __repr__(self):
        if self.op == '=*':
            atom = "=%s*" % self.cpvstr
        else:
            atom = self.op + self.cpvstr
        if self.blocks:
            atom = '!' + atom
        if self.blocks:
            if self.blocks_strongly:
                atom = '!!' + atom
            else:
                atom = '!' + atom
        attrs = [atom]
        if self.use:
            attrs.append('use=%r' % (self.use, ))
        if self.slot is not None:
            attrs.append('slot=%r' % (self.slot, ))
        if self.subslot is not None:
            attrs.append('subslot=%r' % (self.subslot, ))
        if self.repo_id is not None:
            attrs.append('repo_id=%r' % (self.repo_id, ))
        return '<%s %s @#%x>' % (self.__class__.__name__, ' '.join(attrs),
                                 id(self))

    def __reduce__(self):
        return (atom, (str(self), self.negate_vers))

    def iter_dnf_solutions(self, full_solution_expansion=False):
        if full_solution_expansion:
            return boolean.AndRestriction.iter_dnf_solutions(self, True)
        return iter([[self]])

    def iter_cnf_solutions(self, full_solution_expansion=False):
        if full_solution_expansion:
            return boolean.AndRestriction.iter_cnf_solutions(self, True)
        return iter([[self]])

    def cnf_solutions(self, full_solution_expansion=False):
        if full_solution_expansion:
            return boolean.AndRestriction.cnf_solutions(self, True)
        return [[self]]

    @property
    def is_simple(self):
        return len(self.restrictions) == 2

    def __str__(self):
        if self.op == '=*':
            s = "=%s*" % self.cpvstr
        else:
            s = self.op + self.cpvstr
        if self.blocks:
            if self.blocks_strongly:
                s = '!!' + s
            else:
                s = '!' + s
        if self.slot:
            s += ":%s" % self.slot
            if self.subslot and self.slot_operator == "=":
                s += "/%s=" % self.subslot
        if self.repo_id:
            s += "::%s" % self.repo_id
        if self.use:
            s += "[%s]" % ",".join(self.use)
        return s

    __hash__ = reflective_hash('_hash')

    def __iter__(self):
        return iter(self.restrictions)

    def __getitem__(self, index):
        return self.restrictions[index]

    def __cmp__(self, other):
        if not isinstance(other, self.__class__):
            raise TypeError("other isn't of %s type, is %s" %
                            (self.__class__, other.__class__))

        c = cmp(self.category, other.category)
        if c:
            return c

        c = cmp(self.package, other.package)
        if c:
            return c

        c = cmp(self.op, other.op)
        if c:
            return c

        c = cpv.ver_cmp(self.version, self.revision, other.version,
                        other.revision)
        if c:
            return c

        c = cmp(self.blocks, other.blocks)
        if c:
            # invert it; cmp(True, False) == 1
            # want non blockers then blockers.
            return -c

        c = cmp(self.blocks_strongly, other.blocks_strongly)
        if c:
            # want !! prior to !
            return c

        c = cmp(self.negate_vers, other.negate_vers)
        if c:
            return c

        def f(v):
            return '' if v is None else v

        c = cmp(f(self.slot), f(other.slot))
        if c:
            return c

        c = cmp(self.use, other.use)
        if c:
            return c

        return cmp(self.repo_id, other.repo_id)

    def intersects(self, other):
        """Check if a passed in atom "intersects" this restriction's atom.

        Two atoms "intersect" if a package can be constructed that
        matches both:

        - if you query for just "dev-lang/python" it "intersects" both
          "dev-lang/python" and ">=dev-lang/python-2.4"
        - if you query for "=dev-lang/python-2.4" it "intersects"
          ">=dev-lang/python-2.4" and "dev-lang/python" but not
          "<dev-lang/python-2.3"

        USE and slot deps are also taken into account.

        The block/nonblock state of the atom is ignored.
        """
        # Our "key" (cat/pkg) must match exactly:
        if self.key != other.key:
            return False
        # Slot dep only matters if we both have one. If we do they
        # must be identical:
        if (self.slot is not None and other.slot is not None
                and self.slot != other.slot):
            return False

        if (self.repo_id is not None and other.repo_id is not None
                and self.repo_id != other.repo_id):
            return False

        # Use deps are similar: if one of us forces a flag on and the
        # other forces it off we do not intersect. If only one of us
        # cares about a flag it is irrelevant.

        # Skip the (very common) case of one of us not having use deps:
        if self.use and other.use:
            # Set of flags we do not have in common:
            flags = set(self.use) ^ set(other.use)
            for flag in flags:
                # If this is unset and we also have the set version we fail:
                if flag[0] == '-' and flag[1:] in flags:
                    return False

        # Remaining thing to check is version restrictions. Get the
        # ones we can check without actual version comparisons out of
        # the way first.

        # If one of us is unversioned we intersect:
        if not self.op or not other.op:
            return True

        # If we are both "unbounded" in the same direction we intersect:
        if (('<' in self.op and '<' in other.op)
                or ('>' in self.op and '>' in other.op)):
            return True

        # Trick used here: just use the atoms as sufficiently
        # package-like object to pass to these functions (all that is
        # needed is a version and revision attr).

        # If one of us is an exact match we intersect if the other matches it:
        if self.op == '=':
            if other.op == '=*':
                return self.fullver.startswith(other.fullver)
            return restricts.VersionMatch(other.op, other.version,
                                          other.revision).match(self)
        if other.op == '=':
            if self.op == '=*':
                return other.fullver.startswith(self.fullver)
            return restricts.VersionMatch(self.op, self.version,
                                          self.revision).match(other)

        # If we are both ~ matches we match if we are identical:
        if self.op == other.op == '~':
            return (self.version == other.version
                    and self.revision == other.revision)

        # If we are both glob matches we match if one of us matches the other.
        if self.op == other.op == '=*':
            return (self.fullver.startswith(other.fullver)
                    or other.fullver.startswith(self.fullver))

        # If one of us is a glob match and the other a ~ we match if the glob
        # matches the ~ (ignoring a revision on the glob):
        if self.op == '=*' and other.op == '~':
            return other.fullver.startswith(self.version)
        if other.op == '=*' and self.op == '~':
            return self.fullver.startswith(other.version)

        # If we get here at least one of us is a <, <=, > or >=:
        if self.op in ('<', '<=', '>', '>='):
            ranged, other = self, other
        else:
            ranged, other = other, self

        if '<' in other.op or '>' in other.op:
            # We are both ranged, and in the opposite "direction" (or
            # we would have matched above). We intersect if we both
            # match the other's endpoint (just checking one endpoint
            # is not enough, it would give a false positive on <=2 vs >2)
            return (restricts.VersionMatch(other.op, other.version,
                                           other.revision).match(ranged)
                    and restricts.VersionMatch(ranged.op, ranged.version,
                                               ranged.revision).match(other))

        if other.op == '~':
            # Other definitely matches its own version. If ranged also
            # does we're done:
            if restricts.VersionMatch(ranged.op, ranged.version,
                                      ranged.revision).match(other):
                return True
            # The only other case where we intersect is if ranged is a
            # > or >= on other's version and a nonzero revision. In
            # that case other will match ranged. Be careful not to
            # give a false positive for ~2 vs <2 here:
            return ranged.op in ('>', '>=') and restricts.VersionMatch(
                other.op, other.version, other.revision).match(ranged)

        if other.op == '=*':
            # The fun one, since glob matches do not correspond to a
            # single contiguous region of versions.

            # a glob match definitely matches its own version, so if
            # ranged does too we're done:
            if restricts.VersionMatch(ranged.op, ranged.version,
                                      ranged.revision).match(other):
                return True
            if '<' in ranged.op:
                # Remaining cases where this intersects: there is a
                # package smaller than ranged.fullver and
                # other.fullver that they both match.

                # If other.revision is not None then other does not
                # match anything smaller than its own fullver:
                if other.revision is not None:
                    return False

                # If other.revision is None then we can always
                # construct a package smaller than other.fullver by
                # tagging e.g. an _alpha1 on, since
                # cat/pkg_beta2_alpha1_alpha1 is a valid version.
                # (Yes, really. Try it if you don't believe me.)
                # If and only if other also matches ranged then
                # ranged will also match one of those smaller packages.
                # XXX (I think, need to try harder to verify this.)
                return ranged.fullver.startswith(other.version)
            else:
                # Remaining cases where this intersects: there is a
                # package greater than ranged.fullver and
                # other.fullver that they both match.

                # We can always construct a package greater than
                # other.fullver by adding a digit to it.
                # If and only if other also matches ranged then
                # ranged will match such a larger package
                # XXX (I think, need to try harder to verify this.)
                return ranged.fullver.startswith(other.version)

        # Handled all possible ops.
        raise NotImplementedError(
            'Someone added an op to atom without adding it to intersects')

    def evaluate_conditionals(self,
                              parent_cls,
                              parent_seq,
                              enabled,
                              tristate=None):
        parent_seq.append(self)
Ejemplo n.º 5
0
class fsBase:
    """base class, all extensions must derive from this class"""
    __slots__ = ("location", "mtime", "mode", "uid", "gid")
    __attrs__ = __slots__
    __default_attrs__ = {}

    locals().update(
        (x.replace("is", "is_"), False) for x in __all__
        if x.startswith("is") and x.islower() and not x.endswith("fs_obj"))

    klass.inject_richcmp_methods_from_cmp(locals())
    klass.inject_immutable_instance(locals())

    def __init__(self, location, strict=True, **d):

        d["location"] = normpath(location)

        s = object.__setattr__
        if strict:
            for k in self.__attrs__:
                s(self, k, d[k])
        else:
            for k, v in d.items():
                s(self, k, v)

    gen_doc_additions(__init__, __attrs__)

    def change_attributes(self, **kwds):
        d = {x: getattr(self, x) for x in self.__attrs__ if hasattr(self, x)}
        d.update(kwds)
        # split location out
        location = d.pop("location")
        if not location.startswith(path_seperator):
            location = abspath(location)
        d["strict"] = False
        return self.__class__(location, **d)

    def __getattr__(self, attr):
        # we would only get called if it doesn't exist.
        if attr not in self.__attrs__:
            raise AttributeError(self, attr)
        obj = self.__default_attrs__.get(attr)
        if not callable(obj):
            return obj
        return obj(self)

    def __hash__(self):
        return hash(self.location)

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        return self.location == other.location

    def __ne__(self, other):
        return not self == other

    def realpath(self, cache=None):
        """calculate the abspath/canonicalized path for this entry, returning
        a new instance if the path differs.

        :keyword cache: Either None (no cache), or a data object of path->
          resolved.  Currently unused, but left in for forwards compatibility
        """
        new_path = realpath(self.location)
        if new_path == self.location:
            return self
        return self.change_attributes(location=new_path)

    @property
    def basename(self):
        return basename(self.location)

    @property
    def dirname(self):
        return dirname(self.location)

    def fnmatch(self, pattern):
        return fnmatch.fnmatch(self.location, pattern)

    def __cmp__(self, other):
        return cmp(self.location, other.location)

    def __str__(self):
        return self.location
Ejemplo n.º 6
0
class atom(boolean.AndRestriction, metaclass=klass.generic_equality):
    """Currently implements gentoo ebuild atom parsing.

    Should be converted into an agnostic dependency base.
    """

    # note we don't need _hash
    __slots__ = (
        "blocks",
        "blocks_strongly",
        "op",
        "cpvstr",
        "negate_vers",
        "use",
        "slot_operator",
        "slot",
        "subslot",
        "repo_id",
        "_hash",
        "_cpv",
        "_restrictions",
    )

    type = restriction.package_type

    negate = False

    _evaluate_collapse = True

    __attr_comparison__ = ("cpvstr", "op", "blocks", "negate_vers", "use",
                           "slot", "subslot", "slot_operator", "repo_id")

    klass.inject_richcmp_methods_from_cmp(locals())
    # hack; combine these 2 metaclasses at some point...
    locals().pop("__eq__", None)
    locals().pop("__ne__", None)
    __inst_caching__ = True

    # overrided in child class if it's supported
    evaluate_depset = None

    def __init__(self, atom, negate_vers=False, eapi='-1'):
        """
        :param atom: string, see gentoo ebuild atom syntax
        :keyword negate_vers: boolean controlling whether the version should be
            inverted for restriction matching
        :keyword eapi: string/int controlling what eapi to enforce for this atom
        """
        if not atom:
            raise errors.MalformedAtom(atom)

        sf = object.__setattr__
        orig_atom = atom
        override_kls = False
        use_start = atom.find("[")
        slot_start = atom.find(":")

        if use_start != -1:
            # use dep
            use_end = atom.find("]", use_start)
            if use_end == -1:
                raise errors.MalformedAtom(orig_atom,
                                           "use restriction isn't completed")
            elif use_end != len(atom) - 1:
                raise errors.MalformedAtom(orig_atom,
                                           "trailing garbage after use dep")
            sf(self, "use",
               tuple(sorted(atom[use_start + 1:use_end].split(','))))
            for x in self.use:
                # stripped purely for validation reasons
                try:
                    if x[-1] in "=?":
                        override_kls = True
                        x = x[:-1]
                        if x[0] == '!':
                            x = x[1:]
                        if x[0] == '-':
                            raise errors.MalformedAtom(
                                orig_atom, f"malformed use flag: {x}")
                    elif x[0] == '-':
                        x = x[1:]

                    if x[-1] == ')' and eapi not in ('0', '1', '2', '3'):
                        # use defaults.
                        if x[-3:] in ("(+)", "(-)"):
                            x = x[:-3]

                    if not x:
                        raise errors.MalformedAtom(orig_atom,
                                                   'empty use dep detected')
                    if not valid_use_flag.match(x):
                        raise errors.MalformedAtom(orig_atom,
                                                   f'invalid USE flag: {x!r}')
                except IndexError:
                    raise errors.MalformedAtom(orig_atom,
                                               'empty use dep detected')
            if override_kls:
                sf(self, '__class__', transitive_use_atom)
            atom = atom[0:use_start] + atom[use_end + 1:]
        else:
            sf(self, "use", None)
        if slot_start != -1:
            i2 = atom.find("::", slot_start)
            if i2 != -1:
                repo_id = atom[i2 + 2:]
                if not repo_id:
                    raise errors.MalformedAtom(orig_atom,
                                               "repo_id must not be empty")
                elif repo_id[0] in '-':
                    raise errors.MalformedAtom(
                        orig_atom,
                        f"invalid first char of repo_id '{repo_id}' (must not begin with a hyphen)"
                    )
                elif not valid_repo_chars.issuperset(repo_id):
                    raise errors.MalformedAtom(
                        orig_atom,
                        f"repo_id may contain only [a-Z0-9_-/], found {repo_id!r}"
                    )
                atom = atom[:i2]
                sf(self, "repo_id", repo_id)
            else:
                sf(self, "repo_id", None)
            # slot dep.
            slot = atom[slot_start + 1:]
            slot_operator = subslot = None
            if not slot:
                # if the slot char came in only due to repo_id, force slots to None
                if i2 == -1:
                    raise errors.MalformedAtom(
                        orig_atom, "Empty slot targets aren't allowed")
                slot = None
            else:
                slots = (slot, )
                if eapi not in ('0', '1', '2', '3', '4'):
                    if slot[0:1] in ("*", "="):
                        if len(slot) > 1:
                            raise errors.MalformedAtom(
                                orig_atom,
                                "Slot operators '*' and '=' do not take slot targets"
                            )
                        slot_operator = slot
                        slot, slots = None, ()
                    else:
                        if slot.endswith('='):
                            slot_operator = '='
                            slot = slot[:-1]
                        slots = slot.split('/', 1)
                elif eapi == '0':
                    raise errors.MalformedAtom(
                        orig_atom,
                        "slot dependencies aren't allowed in EAPI 0")

                for chunk in slots:
                    if not chunk:
                        raise errors.MalformedAtom(
                            orig_atom, "Empty slot targets aren't allowed")

                    if chunk[0] in '-.':
                        raise errors.MalformedAtom(
                            orig_atom,
                            "Slot targets must not start with a hypen or dot: {chunk!r}"
                        )
                    elif not valid_slot_chars.issuperset(chunk):
                        invalid_chars = ', '.join(
                            map(
                                repr,
                                sorted(
                                    set(chunk).difference(valid_slot_chars))))
                        raise errors.MalformedAtom(
                            orig_atom,
                            f"Invalid character(s) in slot target: {invalid_chars}"
                        )

                if len(slots) == 2:
                    slot, subslot = slots

            sf(self, "slot_operator", slot_operator)
            sf(self, "slot", slot)
            sf(self, "subslot", subslot)
            atom = atom[:slot_start]
        else:
            sf(self, "slot_operator", None)
            sf(self, "slot", None)
            sf(self, "subslot", None)
            sf(self, "repo_id", None)

        sf(self, "blocks", atom[0] == "!")
        if self.blocks:
            atom = atom[1:]
            # hackish/slow, but lstrip doesn't take a 'prune this many' arg
            # open to alternatives
            if eapi not in ('0', '1') and atom.startswith("!"):
                atom = atom[1:]
                sf(self, "blocks_strongly", True)
            else:
                sf(self, "blocks_strongly", False)
        else:
            sf(self, "blocks_strongly", False)

        if atom[0] in ('<', '>'):
            if atom[1] == '=':
                sf(self, 'op', atom[:2])
                atom = atom[2:]
            else:
                sf(self, 'op', atom[0])
                atom = atom[1:]
        elif atom[0] == '=':
            if atom[-1] == '*':
                sf(self, 'op', '=*')
                atom = atom[1:-1]
            else:
                atom = atom[1:]
                sf(self, 'op', '=')
        elif atom[0] == '~':
            sf(self, 'op', '~')
            atom = atom[1:]
        else:
            sf(self, 'op', '')
        sf(self, 'cpvstr', atom)

        if eapi == '0':
            for x in ('use', 'slot'):
                if getattr(self, x) is not None:
                    raise errors.MalformedAtom(
                        orig_atom, f"{x} atoms aren't supported for EAPI 0")
        elif eapi == '1':
            if self.use is not None:
                raise errors.MalformedAtom(
                    orig_atom, "use atoms aren't supported for EAPI < 2")
        if eapi != '-1':
            if self.repo_id is not None:
                raise errors.MalformedAtom(
                    orig_atom,
                    f"repo_id atoms aren't supported for EAPI {eapi}")
        if use_start != -1 and slot_start != -1 and use_start < slot_start:
            raise errors.MalformedAtom(orig_atom,
                                       "slot restriction must proceed use")
        try:
            sf(self, "_cpv", cpv.CPV(self.cpvstr, versioned=bool(self.op)))
        except errors.InvalidCPV as e:
            raise errors.MalformedAtom(orig_atom) from e

        if self.op:
            if self.version is None:
                raise errors.MalformedAtom(orig_atom,
                                           "operator requires a version")
            elif self.op == '~' and self.revision:
                raise errors.MalformedAtom(
                    orig_atom,
                    "~ revision operater cannot be combined with a revision")
        elif self.version is not None:
            raise errors.MalformedAtom(orig_atom,
                                       'versioned atom requires an operator')
        sf(self, "_hash", hash(orig_atom))
        sf(self, "negate_vers", negate_vers)

    __getattr__ = klass.GetAttrProxy("_cpv")
    __dir__ = klass.DirProxy("_cpv")

    @property
    def blocks_temp_ignorable(self):
        return not self.blocks_strongly

    weak_blocker = klass.alias_attr("blocks_temp_ignorable")

    def __repr__(self):
        if self.op == '=*':
            atom = f"={self.cpvstr}*"
        else:
            atom = self.op + self.cpvstr
        if self.blocks:
            atom = '!' + atom
        if self.blocks:
            if self.blocks_strongly:
                atom = '!!' + atom
            else:
                atom = '!' + atom
        attrs = [atom]
        if self.use:
            attrs.append(f'use={self.use!r}')
        if self.slot is not None:
            attrs.append(f'slot={self.slot!r}')
        if self.subslot is not None:
            attrs.append(f'subslot={self.subslot!r}')
        if self.repo_id is not None:
            attrs.append(f'repo_id={self.repo_id!r}')
        return '<%s %s @#%x>' % (self.__class__.__name__, ' '.join(attrs),
                                 id(self))

    def __reduce__(self):
        return (atom, (str(self), self.negate_vers))

    def iter_dnf_solutions(self, full_solution_expansion=False):
        if full_solution_expansion:
            return boolean.AndRestriction.iter_dnf_solutions(self, True)
        return iter([[self]])

    def iter_cnf_solutions(self, full_solution_expansion=False):
        if full_solution_expansion:
            return boolean.AndRestriction.iter_cnf_solutions(self, True)
        return iter([[self]])

    def cnf_solutions(self, full_solution_expansion=False):
        if full_solution_expansion:
            return boolean.AndRestriction.cnf_solutions(self, True)
        return [[self]]

    @property
    def is_simple(self):
        return len(self.restrictions) == 2

    @klass.jit_attr
    def restrictions(self):
        # ordering here matters; against 24702 ebuilds for
        # a non matchable atom with package as the first restriction
        # 10 loops, best of 3: 206 msec per loop
        # with category as the first(the obvious ordering)
        # 10 loops, best of 3: 209 msec per loop
        # why?  because category is more likely to collide;
        # at the time of this profiling, there were 151 categories.
        # over 11k packages however.
        r = [
            restricts.PackageDep(self.package),
            restricts.CategoryDep(self.category)
        ]

        if self.repo_id is not None:
            r.insert(0, restricts.RepositoryDep(self.repo_id))

        if self.fullver is not None:
            if self.op == '=*':
                r.append(
                    packages.PackageRestriction(
                        "fullver", values.StrGlobMatch(self.fullver)))
            else:
                r.append(
                    restricts.VersionMatch(self.op,
                                           self.version,
                                           self.revision,
                                           negate=self.negate_vers))

        if self.slot is not None:
            r.append(restricts.SlotDep(self.slot))
            if self.subslot is not None:
                r.append(restricts.SubSlotDep(self.subslot))

        if self.use is not None:
            r.extend(restricts._parse_nontransitive_use(self.use))

        return tuple(r)

    def __str__(self):
        if self.op == '=*':
            s = f"={self.cpvstr}*"
        else:
            s = self.op + self.cpvstr
        if self.blocks:
            if self.blocks_strongly:
                s = '!!' + s
            else:
                s = '!' + s
        if self.slot:
            s += f":{self.slot}"
            if self.subslot:
                s += f"/{self.subslot}"
            if self.slot_operator == "=":
                s += self.slot_operator
        elif self.slot_operator:
            s += f":{self.slot_operator}"
        if self.repo_id:
            s += f"::{self.repo_id}"
        if self.use:
            use = ','.join(self.use)
            s += f"[{use}]"
        return s

    __hash__ = klass.reflective_hash('_hash')

    def __iter__(self):
        return iter(self.restrictions)

    def __getitem__(self, index):
        return self.restrictions[index]

    def __cmp__(self, other):
        if not isinstance(other, atom):
            raise TypeError(
                f"other isn't of {atom!r} type, is {other.__class__}")

        c = cmp(self.category, other.category)
        if c:
            return c

        c = cmp(self.package, other.package)
        if c:
            return c

        c = cmp(self.op, other.op)
        if c:
            return c

        c = cpv.ver_cmp(self.version, self.revision, other.version,
                        other.revision)
        if c:
            return c

        c = cmp(self.blocks, other.blocks)
        if c:
            # invert it; cmp(True, False) == 1
            # want non blockers then blockers.
            return -c

        c = cmp(self.blocks_strongly, other.blocks_strongly)
        if c:
            # want !! prior to !
            return c

        c = cmp(self.negate_vers, other.negate_vers)
        if c:
            return c

        def f(v):
            return '' if v is None else v

        c = cmp(f(self.slot), f(other.slot))
        if c:
            return c

        c = cmp(self.use, other.use)
        if c:
            return c

        return cmp(self.repo_id, other.repo_id)

    no_usedeps = klass.alias_attr("get_atom_without_use_deps")

    @property
    def get_atom_without_use_deps(self):
        """Return atom object stripped of USE dependencies."""
        if not self.use:
            return self
        if self.op == '=*':
            s = f'={self.cpvstr}*'
        else:
            s = self.op + self.cpvstr
        if self.blocks:
            s = '!' + s
            if not self.blocks_temp_ignorable:
                s = '!' + s
        if self.slot:
            s += f':{self.slot}'
        return atom(s)

    def intersects(self, other):
        """Check if a passed in atom "intersects" this restriction's atom.

        Two atoms "intersect" if a package can be constructed that
        matches both:

        - if you query for just "dev-lang/python" it "intersects" both
          "dev-lang/python" and ">=dev-lang/python-2.4"
        - if you query for "=dev-lang/python-2.4" it "intersects"
          ">=dev-lang/python-2.4" and "dev-lang/python" but not
          "<dev-lang/python-2.3"

        USE and slot deps are also taken into account.

        The block/nonblock state of the atom is ignored.
        """
        # Our "key" (cat/pkg) must match exactly:
        if self.key != other.key:
            return False

        # Slot dep only matters if we both have one. If we do they
        # must be identical:
        if (self.slot is not None and other.slot is not None
                and self.slot != other.slot):
            return False

        # Subslot dep only matters if we both have one. If we do they
        # must be identical:
        if (self.subslot is not None and other.subslot is not None
                and self.subslot != other.subslot):
            return False

        if (self.repo_id is not None and other.repo_id is not None
                and self.repo_id != other.repo_id):
            return False

        # Use deps are similar: if one of us forces a flag on and the
        # other forces it off we do not intersect. If only one of us
        # cares about a flag it is irrelevant.

        # Skip the (very common) case of one of us not having use deps:
        if self.use and other.use:
            # Set of flags we do not have in common:
            flags = set(self.use) ^ set(other.use)
            for flag in flags:
                # If this is unset and we also have the set version we fail:
                if flag[0] == '-' and flag[1:] in flags:
                    return False

        # Remaining thing to check is version restrictions. Get the
        # ones we can check without actual version comparisons out of
        # the way first.

        # If one of us is unversioned we intersect:
        if not self.op or not other.op:
            return True

        # If we are both "unbounded" in the same direction we intersect:
        if (('<' in self.op and '<' in other.op)
                or ('>' in self.op and '>' in other.op)):
            return True

        # Trick used here: just use the atoms as sufficiently
        # package-like object to pass to these functions (all that is
        # needed is a version and revision attr).

        # If one of us is an exact match we intersect if the other matches it:
        if self.op == '=':
            if other.op == '=*':
                return self.fullver.startswith(other.fullver)
            return restricts.VersionMatch(other.op, other.version,
                                          other.revision).match(self)
        if other.op == '=':
            if self.op == '=*':
                return other.fullver.startswith(self.fullver)
            return restricts.VersionMatch(self.op, self.version,
                                          self.revision).match(other)

        # If we are both ~ matches we match if we are identical:
        if self.op == other.op == '~':
            return (self.version == other.version
                    and self.revision == other.revision)

        # If we are both glob matches we match if one of us matches the other.
        if self.op == other.op == '=*':
            return (self.fullver.startswith(other.fullver)
                    or other.fullver.startswith(self.fullver))

        # If one of us is a glob match and the other a ~ we match if the glob
        # matches the ~ (ignoring a revision on the glob):
        if self.op == '=*' and other.op == '~':
            return other.fullver.startswith(self.version)
        if other.op == '=*' and self.op == '~':
            return self.fullver.startswith(other.version)

        # If we get here at least one of us is a <, <=, > or >=:
        if self.op in ('<', '<=', '>', '>='):
            ranged, other = self, other
        else:
            ranged, other = other, self

        if '<' in other.op or '>' in other.op:
            # We are both ranged, and in the opposite "direction" (or
            # we would have matched above). We intersect if we both
            # match the other's endpoint (just checking one endpoint
            # is not enough, it would give a false positive on <=2 vs >2)
            return (restricts.VersionMatch(other.op, other.version,
                                           other.revision).match(ranged)
                    and restricts.VersionMatch(ranged.op, ranged.version,
                                               ranged.revision).match(other))

        if other.op == '~':
            # Other definitely matches its own version. If ranged also
            # does we're done:
            if restricts.VersionMatch(ranged.op, ranged.version,
                                      ranged.revision).match(other):
                return True
            # The only other case where we intersect is if ranged is a
            # > or >= on other's version and a nonzero revision. In
            # that case other will match ranged. Be careful not to
            # give a false positive for ~2 vs <2 here:
            return ranged.op in ('>', '>=') and restricts.VersionMatch(
                other.op, other.version, other.revision).match(ranged)

        if other.op == '=*':
            # The fun one, since glob matches do not correspond to a
            # single contiguous region of versions.

            # a glob match definitely matches its own version, so if
            # ranged does too we're done:
            if restricts.VersionMatch(ranged.op, ranged.version,
                                      ranged.revision).match(other):
                return True
            if '<' in ranged.op:
                # Remaining cases where this intersects: there is a
                # package smaller than ranged.fullver and
                # other.fullver that they both match.

                # If other.revision is not None or 0 then other does not match
                # anything smaller than its own fullver:
                if other.revision:
                    return False

                # If other.revision is None or 0 then we can always
                # construct a package smaller than other.fullver by
                # tagging e.g. an _alpha1 on, since
                # cat/pkg_beta2_alpha1_alpha1 is a valid version.
                # (Yes, really. Try it if you don't believe me.)
                # If and only if other also matches ranged then
                # ranged will also match one of those smaller packages.
                # XXX (I think, need to try harder to verify this.)
                return ranged.fullver.startswith(other.version)
            else:
                # Remaining cases where this intersects: there is a
                # package greater than ranged.fullver and
                # other.fullver that they both match.

                # We can always construct a package greater than
                # other.fullver by adding a digit to it.
                # If and only if other also matches ranged then
                # ranged will match such a larger package
                # XXX (I think, need to try harder to verify this.)
                return ranged.fullver.startswith(other.version)

        # Handled all possible ops.
        raise NotImplementedError(
            'Someone added an op to atom without adding it to intersects')

    def evaluate_conditionals(self,
                              parent_cls,
                              parent_seq,
                              enabled,
                              tristate=None):
        parent_seq.append(self)
Ejemplo n.º 7
0
class CPV(base.base):
    """base ebuild package class

    :ivar category: str category
    :ivar package: str package
    :ivar key: strkey (cat/pkg)
    :ivar version: str version
    :ivar revision: str revision
    :ivar versioned_atom: atom matching this exact version
    :ivar unversioned_atom: atom matching all versions of this package
    :cvar _get_attr: mapping of attr:callable to generate attributes on the fly
    """

    __slots__ = ("cpvstr", "key", "category", "package", "version", "revision",
                 "fullver")
    inject_richcmp_methods_from_cmp(locals())

    def __init__(self, *args, versioned=None):
        """
        Can be called with one string or with three string args.

        If called with one arg that is the cpv string. (See :obj:`parser`
        for allowed syntax).

        If called with three args they are the category, package and
        version components of the cpv string respectively.
        """
        for x in args:
            if not isinstance(x, str):
                raise TypeError(f"all args must be strings, got {args!r}")

        l = len(args)
        if l == 1:
            cpvstr = args[0]
            if versioned is None:
                raise TypeError(
                    f"single argument invocation requires versioned kwarg; {cpvstr!r}"
                )
        elif l == 2:
            cpvstr = f"{args[0]}/{args[1]}"
            versioned = False
        elif l == 3:
            cpvstr = f"{args[0]}/{args[1]}-{args[2]}"
            versioned = True
        else:
            raise TypeError(
                f"CPV takes 1 arg (cpvstr), 2 (cat, pkg), or 3 (cat, pkg, ver): got {args!r}"
            )

        try:
            category, pkgver = cpvstr.rsplit("/", 1)
        except ValueError:
            # occurs if the rsplit yields only one item
            raise InvalidCPV(cpvstr, 'no package or version components')
        if not isvalid_cat_re.match(category):
            raise InvalidCPV(cpvstr, 'invalid category name')
        sf = object.__setattr__
        sf(self, 'category', category)
        sf(self, 'cpvstr', cpvstr)
        pkg_chunks = pkgver.split("-")
        lpkg_chunks = len(pkg_chunks)
        if versioned:
            if lpkg_chunks == 1:
                raise InvalidCPV(cpvstr, 'missing package version')
            if isvalid_rev(pkg_chunks[-1]):
                if lpkg_chunks < 3:
                    # needs at least ('pkg', 'ver', 'rev')
                    raise InvalidCPV(
                        cpvstr,
                        'missing package name, version, and/or revision')
                rev = _Revision(pkg_chunks.pop(-1)[1:])
                if rev == 0:
                    # reset stored cpvstr to drop -r0+
                    sf(self, 'cpvstr', f"{category}/{'-'.join(pkg_chunks)}")
                elif rev[0] == '0':
                    # reset stored cpvstr to drop leading zeroes from revision
                    sf(self, 'cpvstr',
                       f"{category}/{'-'.join(pkg_chunks)}-r{int(rev)}")
                sf(self, 'revision', rev)
            else:
                sf(self, 'revision', _Revision(''))

            if not isvalid_version_re.match(pkg_chunks[-1]):
                raise InvalidCPV(cpvstr, f"invalid version '{pkg_chunks[-1]}'")
            sf(self, 'version', pkg_chunks.pop(-1))
            if self.revision:
                sf(self, 'fullver', f"{self.version}-r{self.revision}")
            else:
                sf(self, 'fullver', self.version)

            if not isvalid_pkg_name(pkg_chunks):
                raise InvalidCPV(cpvstr, 'invalid package name')
            sf(self, 'package', '-'.join(pkg_chunks))
            sf(self, 'key', f"{category}/{self.package}")
        else:
            if not isvalid_pkg_name(pkg_chunks):
                raise InvalidCPV(cpvstr, 'invalid package name')
            sf(self, 'revision', None)
            sf(self, 'fullver', None)
            sf(self, 'version', None)
            sf(self, 'key', cpvstr)
            sf(self, 'package', '-'.join(pkg_chunks))

    def __hash__(self):
        return hash(self.cpvstr)

    def __repr__(self):
        return '<%s cpvstr=%s @%#8x>' % (
            self.__class__.__name__, getattr(self, 'cpvstr', None), id(self))

    def __str__(self):
        return getattr(self, 'cpvstr', 'None')

    def __cmp__(self, other):
        try:
            if self.cpvstr == other.cpvstr:
                return 0

            if (self.category and other.category
                    and self.category != other.category):
                return cmp(self.category, other.category)

            if self.package and other.package and self.package != other.package:
                return cmp(self.package, other.package)

            # note I chucked out valueerror, none checks on versions
            # passed in. I suck, I know.
            # ~harring
            # fails in doing comparison of unversioned atoms against
            # versioned atoms
            return ver_cmp(self.version, self.revision, other.version,
                           other.revision)
        except AttributeError:
            return 1

    @property
    def versioned_atom(self):
        if self.version is not None:
            return atom.atom(f"={self.cpvstr}")
        return self.unversioned_atom

    @property
    def unversioned_atom(self):
        return atom.atom(self.key)

    @classmethod
    def versioned(cls, *args):
        return cls(versioned=True, *args)

    @classmethod
    def unversioned(cls, *args):
        return cls(versioned=False, *args)