aboutsummaryrefslogtreecommitdiff
blob: 27cf77c85b99d508160581602ad63d1810535f39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#!/usr/bin/python
#
# Copyright(c) 2009, Gentoo Foundation
#
# Licensed under the GNU General Public License, v2

"""Subclasses portage.dep.Atom to provide methods on a Gentoo atom string."""

__all__ = ("Atom",)

# =======
# Imports
# =======

import weakref

import portage

from gentoolkit.cpv import CPV
from gentoolkit.versionmatch import VersionMatch
from gentoolkit import errors

# =======
# Classes
# =======


class Atom(portage.dep.Atom, CPV):
    """Portage's Atom class with improvements from pkgcore.

    portage.dep.Atom provides the following instance variables:

    @type operator: str
    @ivar operator: one of ('=', '=*', '<', '>', '<=', '>=', '~', None)
    @type cp: str
    @ivar cp: cat/pkg
    @type cpv: str
    @ivar cpv: cat/pkg-ver (if ver)
    @type slot: str or None (modified to tuple if not None)
    @ivar slot: slot passed in as cpv:#
    """

    # Necessary for Portage versions < 2.1.7
    _atoms = weakref.WeakValueDictionary()

    def __init__(self, atom):
        self.atom = atom
        self.operator = self.blocker = self.use = self.slot = None

        try:
            portage.dep.Atom.__init__(self, atom)
        except portage.exception.InvalidAtom:
            raise errors.GentoolkitInvalidAtom(atom)

        # Make operator compatible with intersects
        if self.operator is None:
            self.operator = ""

        CPV.__init__(self, self.cpv)

        # use_conditional is USE flag condition for this Atom to be required:
        # For: !build? ( >=sys-apps/sed-4.0.5 ), use_conditional = '!build'
        self.use_conditional = None

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

        if self.operator != other.operator:
            return False

        if not CPV.__eq__(self, other):
            return False

        if bool(self.blocker) != bool(other.blocker):
            return False

        if self.blocker and other.blocker:
            if self.blocker.overlap.forbid != other.blocker.overlap.forbid:
                return False

        if self.use_conditional != other.use_conditional:
            return False

        # Don't believe Portage has something like this
        # c = cmp(self.negate_vers, other.negate_vers)
        # if c:
        #   return c

        if self.slot != other.slot:
            return False

        this_use = None
        if self.use is not None:
            this_use = sorted(self.use.tokens)
        that_use = None
        if other.use is not None:
            that_use = sorted(other.use.tokens)
        if this_use != that_use:
            return False

        # Not supported by Portage Atom yet
        # return cmp(self.repo_name, other.repo_name)
        return True

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

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

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

        if self.operator != other.operator:
            return self.operator < other.operator

        if not CPV.__eq__(self, other):
            return CPV.__lt__(self, other)

        if bool(self.blocker) != bool(other.blocker):
            # We want non blockers, then blockers, so only return True
            # if self.blocker is True and other.blocker is False.
            return bool(self.blocker) > bool(other.blocker)

        if self.blocker and other.blocker:
            if self.blocker.overlap.forbid != other.blocker.overlap.forbid:
                # we want !! prior to !
                return self.blocker.overlap.forbid < other.blocker.overlap.forbid

        # Don't believe Portage has something like this
        # c = cmp(self.negate_vers, other.negate_vers)
        # if c:
        #   return c

        if self.slot != other.slot:
            if self.slot is None:
                return False
            elif other.slot is None:
                return True
            return self.slot < other.slot

        this_use = []
        if self.use is not None:
            this_use = sorted(self.use.tokens)
        that_use = []
        if other.use is not None:
            that_use = sorted(other.use.tokens)
        if this_use != that_use:
            return this_use < that_use

        # Not supported by Portage Atom yet
        # return cmp(self.repo_name, other.repo_name)

        return False

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

        return not self <= other

    def __le__(self, other):
        if not isinstance(other, self.__class__):
            raise TypeError(
                f"other isn't of {self.__class__} type, is {other.__class__}"
            )
        return self < other or self == other

    def __ge__(self, other):
        if not isinstance(other, self.__class__):
            raise TypeError(
                f"other isn't of {self.__class__} type, is {other.__class__}"
            )
        return self > other or self == other

    def __repr__(self):
        uc = self.use_conditional
        uc = "%s? " % uc if uc is not None else ""
        return "<{} {!r}>".format(self.__class__.__name__, f"{uc}{self.atom}")

    def __setattr__(self, name, value):
        object.__setattr__(self, name, value)

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

        Lifted from pkgcore.

        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"

        @type other: L{gentoolkit.atom.Atom} or
                L{gentoolkit.versionmatch.VersionMatch}
        @param other: other package to compare
        @see: L{pkgcore.ebuild.atom}
        """
        # Our "cp" (cat/pkg) must match exactly:
        if self.cp != other.cp:
            # Check to see if one is name only:
            # We don't bother checking if self.category is None: it can't be
            # because we're an Atom subclass and that would be invalid.
            return not other.category and self.name == other.name

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

        if self.repo is not None and other.repo is not None and self.repo != other.repo:
            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:
        this_use = getattr(self, "use", None)
        that_use = getattr(other, "use", None)
        if this_use and that_use:
            # Set of flags we do not have in common:
            flags = set(this_use.tokens) ^ set(that_use.tokens)
            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.operator or not other.operator:
            return True

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

        # If one of us is an exact match we intersect if the other matches it:
        if self.operator == "=":
            if other.operator == "=*":
                return self.fullversion.startswith(other.fullversion)
            return VersionMatch(other, op=other.operator).match(self)
        if other.operator == "=":
            if self.operator == "=*":
                return other.fullversion.startswith(self.fullversion)
            return VersionMatch(self, op=self.operator).match(other)

        # If we are both ~ matches we match if we are identical:
        if self.operator == other.operator == "~":
            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.operator == other.operator == "=*":
            return self.fullversion.startswith(
                other.fullversion
            ) or other.fullversion.startswith(self.fullversion)

        # 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.operator == "=*" and other.operator == "~":
            return other.fullversion.startswith(self.version)
        if other.operator == "=*" and self.operator == "~":
            return self.fullversion.startswith(other.version)

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

        if "<" in other.operator or ">" in other.operator:
            # 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 VersionMatch(other, op=other.operator).match(
                ranged
            ) and VersionMatch(ranged, op=ranged.operator).match(other)

        if other.operator == "~":
            # Other definitely matches its own version. If ranged also
            # does we're done:
            if VersionMatch(ranged, op=ranged.operator).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.operator in (">", ">=") and VersionMatch(
                other, op=other.operator
            ).match(ranged)

        if other.operator == "=*":
            # a glob match definitely matches its own version, so if
            # ranged does too we're done:
            if VersionMatch(ranged, op=ranged.operator).match(other):
                return True
            if "<" in ranged.operator:
                # If other.revision is not defined then other does not
                # match anything smaller than its own fullversion:
                if other.revision:
                    return False

                # If other.revision is defined then we can always
                # construct a package smaller than other.fullversion by
                # tagging e.g. an _alpha1 on.
                return ranged.fullversion.startswith(other.version)
            else:
                # Remaining cases where this intersects: there is a
                # package greater than ranged.fullversion and
                # other.fullversion that they both match.
                return ranged.fullversion.startswith(other.version)

        # Handled all possible ops.
        raise NotImplementedError(
            "Someone added an operator without adding it to intersects"
        )

    def get_depstr(self):
        """Returns a string representation of the original dep"""
        uc = self.use_conditional
        uc = "%s? " % uc if uc is not None else ""
        return f"{uc}{self.atom}"


# vim: set ts=4 sw=4 tw=79: