3 # Copyright(c) 2009, Gentoo Foundation
5 # Licensed under the GNU General Public License, v2
9 """Subclasses portage.dep.Atom to provide methods on a Gentoo atom string."""
21 from gentoolkit.cpv import CPV
22 from gentoolkit.versionmatch import VersionMatch
23 from gentoolkit import errors
29 class Atom(portage.dep.Atom, CPV):
30 """Portage's Atom class with improvements from pkgcore.
32 portage.dep.Atom provides the following instance variables:
35 @ivar operator: one of ('=', '=*', '<', '>', '<=', '>=', '~', None)
39 @ivar cpv: cat/pkg-ver (if ver)
40 @type slot: str or None (modified to tuple if not None)
41 @ivar slot: slot passed in as cpv:#
44 # Necessary for Portage versions < 2.1.7
45 _atoms = weakref.WeakValueDictionary()
47 def __init__(self, atom):
49 self.operator = self.blocker = self.use = self.slot = None
52 portage.dep.Atom.__init__(self, atom)
53 except portage.exception.InvalidAtom:
54 raise errors.GentoolkitInvalidAtom(atom)
56 # Make operator compatible with intersects
57 if self.operator is None:
60 CPV.__init__(self, self.cpv)
62 # use_conditional is USE flag condition for this Atom to be required:
63 # For: !build? ( >=sys-apps/sed-4.0.5 ), use_conditional = '!build'
64 self.use_conditional = None
66 def __eq__(self, other):
67 if not isinstance(other, self.__class__):
68 err = "other isn't of %s type, is %s"
69 raise TypeError(err % (self.__class__, other.__class__))
71 if self.operator != other.operator:
74 if not CPV.__eq__(self, other):
77 if bool(self.blocker) != bool(other.blocker):
80 if self.blocker and other.blocker:
81 if self.blocker.overlap.forbid != other.blocker.overlap.forbid:
84 # Don't believe Portage has something like this
85 #c = cmp(self.negate_vers, other.negate_vers)
89 if self.slot != other.slot:
93 if self.use is not None:
94 this_use = sorted(self.use.tokens)
96 if other.use is not None:
97 that_use = sorted(other.use.tokens)
98 if this_use != that_use:
101 # Not supported by Portage Atom yet
102 #return cmp(self.repo_name, other.repo_name)
106 return hash(self.atom)
108 def __ne__(self, other):
109 return not self == other
111 def __lt__(self, other):
112 if not isinstance(other, self.__class__):
113 err = "other isn't of %s type, is %s"
114 raise TypeError(err % (self.__class__, other.__class__))
116 if self.operator != other.operator:
117 return self.operator < other.operator
119 if not CPV.__eq__(self, other):
120 return CPV.__lt__(self, other)
122 if bool(self.blocker) != bool(other.blocker):
123 # We want non blockers, then blockers, so only return True
124 # if self.blocker is True and other.blocker is False.
125 return bool(self.blocker) > bool(other.blocker)
127 if self.blocker and other.blocker:
128 if self.blocker.overlap.forbid != other.blocker.overlap.forbid:
129 # we want !! prior to !
130 return (self.blocker.overlap.forbid <
131 other.blocker.overlap.forbid)
133 # Don't believe Portage has something like this
134 #c = cmp(self.negate_vers, other.negate_vers)
138 if self.slot != other.slot:
139 if self.slot is None:
141 elif other.slot is None:
143 return self.slot < other.slot
146 if self.use is not None:
147 this_use = sorted(self.use.tokens)
149 if other.use is not None:
150 that_use = sorted(other.use.tokens)
151 if this_use != that_use:
152 return this_use < that_use
154 # Not supported by Portage Atom yet
155 #return cmp(self.repo_name, other.repo_name)
159 def __gt__(self, other):
160 if not isinstance(other, self.__class__):
161 err = "other isn't of %s type, is %s"
162 raise TypeError(err % (self.__class__, other.__class__))
164 return not self <= other
166 def __le__(self, other):
167 if not isinstance(other, self.__class__):
168 raise TypeError("other isn't of %s type, is %s" % (
169 self.__class__, other.__class__)
171 return self < other or self == other
173 def __ge__(self, other):
174 if not isinstance(other, self.__class__):
175 raise TypeError("other isn't of %s type, is %s" % (
176 self.__class__, other.__class__)
178 return self > other or self == other
181 uc = self.use_conditional
182 uc = "%s? " % uc if uc is not None else ''
183 return "<%s %r>" % (self.__class__.__name__, "%s%s" % (uc, self.atom))
185 def __setattr__(self, name, value):
186 object.__setattr__(self, name, value)
188 #R0911:121:Atom.intersects: Too many return statements (20/6)
189 #R0912:121:Atom.intersects: Too many branches (23/12)
190 # pylint: disable-msg=R0911,R0912
191 def intersects(self, other):
192 """Check if a passed in package atom "intersects" this atom.
196 Two atoms "intersect" if a package can be constructed that
198 - if you query for just "dev-lang/python" it "intersects" both
199 "dev-lang/python" and ">=dev-lang/python-2.4"
200 - if you query for "=dev-lang/python-2.4" it "intersects"
201 ">=dev-lang/python-2.4" and "dev-lang/python" but not
202 "<dev-lang/python-2.3"
204 @type other: L{gentoolkit.atom.Atom} or
205 L{gentoolkit.versionmatch.VersionMatch}
206 @param other: other package to compare
207 @see: L{pkgcore.ebuild.atom}
209 # Our "cp" (cat/pkg) must match exactly:
210 if self.cp != other.cp:
211 # Check to see if one is name only:
212 # Avoid slow partitioning if we're definitely not matching
213 # (yes, this is hackish, but it's faster):
214 if self.cp[-1:] != other.cp[-1:]:
217 if ((not self.category and self.name == other.name) or
218 (not other.category and other.name == self.name)):
222 # Slot dep only matters if we both have one. If we do they
224 this_slot = getattr(self, 'slot', None)
225 that_slot = getattr(other, 'slot', None)
226 if (this_slot is not None and that_slot is not None and
227 this_slot != that_slot):
230 # TODO: Uncomment when Portage's Atom supports repo
231 #if (self.repo_name is not None and other.repo_name is not None and
232 # self.repo_name != other.repo_name):
235 # Use deps are similar: if one of us forces a flag on and the
236 # other forces it off we do not intersect. If only one of us
237 # cares about a flag it is irrelevant.
239 # Skip the (very common) case of one of us not having use deps:
240 this_use = getattr(self, 'use', None)
241 that_use = getattr(other, 'use', None)
242 if this_use and that_use:
243 # Set of flags we do not have in common:
244 flags = set(this_use.tokens) ^ set(that_use.tokens)
246 # If this is unset and we also have the set version we fail:
247 if flag[0] == '-' and flag[1:] in flags:
250 # Remaining thing to check is version restrictions. Get the
251 # ones we can check without actual version comparisons out of
254 # If one of us is unversioned we intersect:
255 if not self.operator or not other.operator:
258 # If we are both "unbounded" in the same direction we intersect:
259 if (('<' in self.operator and '<' in other.operator) or
260 ('>' in self.operator and '>' in other.operator)):
263 # If one of us is an exact match we intersect if the other matches it:
264 if self.operator == '=':
265 if other.operator == '=*':
266 return self.fullversion.startswith(other.fullversion)
267 return VersionMatch(other, op=other.operator).match(self)
268 if other.operator == '=':
269 if self.operator == '=*':
270 return other.fullversion.startswith(self.fullversion)
271 return VersionMatch(self, op=self.operator).match(other)
273 # If we are both ~ matches we match if we are identical:
274 if self.operator == other.operator == '~':
275 return (self.version == other.version and
276 self.revision == other.revision)
278 # If we are both glob matches we match if one of us matches the other.
279 if self.operator == other.operator == '=*':
280 return (self.fullversion.startswith(other.fullversion) or
281 other.fullversion.startswith(self.fullversion))
283 # If one of us is a glob match and the other a ~ we match if the glob
284 # matches the ~ (ignoring a revision on the glob):
285 if self.operator == '=*' and other.operator == '~':
286 return other.fullversion.startswith(self.version)
287 if other.operator == '=*' and self.operator == '~':
288 return self.fullversion.startswith(other.version)
290 # If we get here at least one of us is a <, <=, > or >=:
291 if self.operator in ('<', '<=', '>', '>='):
293 # E0601: Using variable 'ranged' before assignment
294 # pylint: disable-msg=E0601
295 ranged, ranged.operator = self, self.operator
297 ranged, ranged.operator = other, other.operator
298 other, other.operator = self, self.operator
300 if '<' in other.operator or '>' in other.operator:
301 # We are both ranged, and in the opposite "direction" (or
302 # we would have matched above). We intersect if we both
303 # match the other's endpoint (just checking one endpoint
304 # is not enough, it would give a false positive on <=2 vs >2)
306 VersionMatch(other, op=other.operator).match(ranged) and
307 VersionMatch(ranged, op=ranged.operator).match(other)
310 if other.operator == '~':
311 # Other definitely matches its own version. If ranged also
313 if VersionMatch(ranged, op=ranged.operator).match(other):
315 # The only other case where we intersect is if ranged is a
316 # > or >= on other's version and a nonzero revision. In
317 # that case other will match ranged. Be careful not to
318 # give a false positive for ~2 vs <2 here:
319 return (ranged.operator in ('>', '>=') and
320 VersionMatch(other, op=other.operator).match(ranged))
322 if other.operator == '=*':
323 # a glob match definitely matches its own version, so if
324 # ranged does too we're done:
325 if VersionMatch(ranged, op=ranged.operator).match(other):
327 if '<' in ranged.operator:
328 # If other.revision is not defined then other does not
329 # match anything smaller than its own fullversion:
333 # If other.revision is defined then we can always
334 # construct a package smaller than other.fullversion by
335 # tagging e.g. an _alpha1 on.
336 return ranged.fullversion.startswith(other.version)
338 # Remaining cases where this intersects: there is a
339 # package greater than ranged.fullversion and
340 # other.fullversion that they both match.
341 return ranged.fullversion.startswith(other.version)
343 # Handled all possible ops.
344 raise NotImplementedError(
345 'Someone added an operator without adding it to intersects')
347 def get_depstr(self):
348 """Returns a string representation of the original dep
350 uc = self.use_conditional
351 uc = "%s? " % uc if uc is not None else ''
352 return "%s%s" % (uc, self.atom)
354 # vim: set ts=4 sw=4 tw=79: