1 # versions.py -- core Portage functionality
2 # Copyright 1998-2014 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 from __future__ import unicode_literals
8 'best', 'catpkgsplit', 'catsplit',
9 'cpv_getkey', 'cpv_getversion', 'cpv_sort_key', 'pkgcmp', 'pkgsplit',
17 if sys.hexversion < 0x3000000:
23 portage.proxy.lazyimport.lazyimport(globals(),
24 'portage.repository.config:_gen_valid_repo',
25 'portage.util:cmp_sort_key',
27 from portage import _unicode_decode
28 from portage.eapi import _get_eapi_attrs
29 from portage.exception import InvalidData
30 from portage.localization import _
32 _unknown_repo = "__unknown__"
36 # PMS 3.1.3: A slot name may contain any of the characters [A-Za-z0-9+_.-].
37 # It must not begin with a hyphen or a dot.
38 _slot = r'([\w+][\w+.-]*)'
40 # 2.1.1 A category name may contain any of the characters [A-Za-z0-9+_.-].
41 # It must not begin with a hyphen or a dot.
42 _cat = r'[\w+][\w+.-]*'
44 # 2.1.2 A package name may contain any of the characters [A-Za-z0-9+_-].
45 # It must not begin with a hyphen,
46 # and must not end in a hyphen followed by one or more digits.
48 "dots_disallowed_in_PN": r'[\w+][\w+-]*?',
49 "dots_allowed_in_PN": r'[\w+][\w+.-]*?',
52 _v = r'(cvs\.)?(\d+)((\.\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\d*)*)'
54 _vr = _v + '(-r(' + _rev + '))?'
57 "dots_disallowed_in_PN": '(' + _cat + '/' + _pkg['dots_disallowed_in_PN'] + '(-' + _vr + ')?)',
58 "dots_allowed_in_PN": '(' + _cat + '/' + _pkg['dots_allowed_in_PN'] + '(-' + _vr + ')?)',
61 "dots_disallowed_in_PN": '(' + _cp['dots_disallowed_in_PN'] + '-' + _vr + ')',
62 "dots_allowed_in_PN": '(' + _cp['dots_allowed_in_PN'] + '-' + _vr + ')',
65 "dots_disallowed_in_PN": '(?P<pn>' + _pkg['dots_disallowed_in_PN'] + '(?P<pn_inval>-' + _vr + ')?)' + '-(?P<ver>' + _v + ')(-r(?P<rev>' + _rev + '))?',
66 "dots_allowed_in_PN": '(?P<pn>' + _pkg['dots_allowed_in_PN'] + '(?P<pn_inval>-' + _vr + ')?)' + '-(?P<ver>' + _v + ')(-r(?P<rev>' + _rev + '))?',
69 ver_regexp = re.compile("^" + _vr + "$")
70 suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$")
71 suffix_value = {"pre": -2, "p": 0, "alpha": -4, "beta": -3, "rc": -1}
72 endversion_keys = ["pre", "p", "alpha", "beta", "rc"]
76 def _get_slot_re(eapi_attrs):
77 cache_key = eapi_attrs.slot_operator
78 slot_re = _slot_re_cache.get(cache_key)
79 if slot_re is not None:
82 if eapi_attrs.slot_operator:
83 slot_re = _slot + r'(/' + _slot + r')?'
87 slot_re = re.compile('^' + slot_re + '$', re.VERBOSE | re.UNICODE)
89 _slot_re_cache[cache_key] = slot_re
94 def _get_pv_re(eapi_attrs):
95 cache_key = eapi_attrs.dots_in_PN
96 pv_re = _pv_re_cache.get(cache_key)
100 if eapi_attrs.dots_in_PN:
101 pv_re = _pv['dots_allowed_in_PN']
103 pv_re = _pv['dots_disallowed_in_PN']
105 pv_re = re.compile(r'^' + pv_re + r'$', re.VERBOSE | re.UNICODE)
107 _pv_re_cache[cache_key] = pv_re
110 def ververify(myver, silent=1):
111 if ver_regexp.match(myver):
115 print(_("!!! syntax error in version: %s") % myver)
118 def vercmp(ver1, ver2, silent=1):
122 >>> from portage.versions import vercmp
123 >>> vercmp('1.0-r1','1.2-r3')
125 >>> vercmp('1.3','1.2-r3')
127 >>> vercmp('1.0_p3','1.0_p3')
130 @param pkg1: version to compare with (see ver_regexp in portage.versions.py)
131 @type pkg1: string (example: "2.1.2-r3")
132 @param pkg2: version to compare againts (see ver_regexp in portage.versions.py)
133 @type pkg2: string (example: "2.1.2_rc5")
134 @rtype: None or float
136 1. positive if ver1 is greater than ver2
137 2. negative if ver1 is less than ver2
138 3. 0 if ver1 equals ver2
139 4. None if ver1 or ver2 are invalid (see ver_regexp in portage.versions.py)
145 match1 = ver_regexp.match(ver1)
146 match2 = ver_regexp.match(ver2)
148 # checking that the versions are valid
149 if not match1 or not match1.groups():
151 print(_("!!! syntax error in version: %s") % ver1)
153 if not match2 or not match2.groups():
155 print(_("!!! syntax error in version: %s") % ver2)
158 # shortcut for cvs ebuilds (new style)
159 if match1.group(1) and not match2.group(1):
161 elif match2.group(1) and not match1.group(1):
164 # building lists of the version parts before the suffix
165 # first part is simple
166 list1 = [int(match1.group(2))]
167 list2 = [int(match2.group(2))]
169 # this part would greatly benefit from a fixed-length version pattern
170 if match1.group(3) or match2.group(3):
171 vlist1 = match1.group(3)[1:].split(".")
172 vlist2 = match2.group(3)[1:].split(".")
174 for i in range(0, max(len(vlist1), len(vlist2))):
175 # Implcit .0 is given a value of -1, so that 1.0.0 > 1.0, since it
176 # would be ambiguous if two versions that aren't literally equal
177 # are given the same value (in sorting, for example).
178 if len(vlist1) <= i or len(vlist1[i]) == 0:
180 list2.append(int(vlist2[i]))
181 elif len(vlist2) <= i or len(vlist2[i]) == 0:
182 list1.append(int(vlist1[i]))
184 # Let's make life easy and use integers unless we're forced to use floats
185 elif (vlist1[i][0] != "0" and vlist2[i][0] != "0"):
186 list1.append(int(vlist1[i]))
187 list2.append(int(vlist2[i]))
188 # now we have to use floats so 1.02 compares correctly against 1.1
190 # list1.append(float("0."+vlist1[i]))
191 # list2.append(float("0."+vlist2[i]))
192 # Since python floats have limited range, we multiply both
193 # floating point representations by a constant so that they are
194 # transformed into whole numbers. This allows the practically
195 # infinite range of a python int to be exploited. The
196 # multiplication is done by padding both literal strings with
197 # zeros as necessary to ensure equal length.
198 max_len = max(len(vlist1[i]), len(vlist2[i]))
199 list1.append(int(vlist1[i].ljust(max_len, "0")))
200 list2.append(int(vlist2[i].ljust(max_len, "0")))
202 # and now the final letter
203 # NOTE: Behavior changed in r2309 (between portage-2.0.x and portage-2.1).
204 # The new behavior is 12.2.5 > 12.2b which, depending on how you look at,
205 # may seem counter-intuitive. However, if you really think about it, it
206 # seems like it's probably safe to assume that this is the behavior that
207 # is intended by anyone who would use versions such as these.
208 if len(match1.group(5)):
209 list1.append(ord(match1.group(5)))
210 if len(match2.group(5)):
211 list2.append(ord(match2.group(5)))
213 for i in range(0, max(len(list1), len(list2))):
216 elif len(list2) <= i:
218 elif list1[i] != list2[i]:
221 rval = (a > b) - (a < b)
224 # main version is equal, so now compare the _suffix part
225 list1 = match1.group(6).split("_")[1:]
226 list2 = match2.group(6).split("_")[1:]
228 for i in range(0, max(len(list1), len(list2))):
229 # Implicit _p0 is given a value of -1, so that 1 < 1_p0
233 s1 = suffix_regexp.match(list1[i]).groups()
237 s2 = suffix_regexp.match(list2[i]).groups()
239 a = suffix_value[s1[0]]
240 b = suffix_value[s2[0]]
241 rval = (a > b) - (a < b)
244 # it's possible that the s(1|2)[1] == ''
245 # in such a case, fudge it.
254 rval = (r1 > r2) - (r1 < r2)
258 # the suffix part is equal to, so finally check the revision
260 r1 = int(match1.group(10))
264 r2 = int(match2.group(10))
267 rval = (r1 > r2) - (r1 < r2)
270 def pkgcmp(pkg1, pkg2):
272 Compare 2 package versions created in pkgsplit format.
275 >>> from portage.versions import *
276 >>> pkgcmp(pkgsplit('test-1.0-r1'),pkgsplit('test-1.2-r3'))
278 >>> pkgcmp(pkgsplit('test-1.3'),pkgsplit('test-1.2-r3'))
281 @param pkg1: package to compare with
282 @type pkg1: list (example: ['test', '1.0', 'r1'])
283 @param pkg2: package to compare againts
284 @type pkg2: list (example: ['test', '1.0', 'r1'])
285 @rtype: None or integer
287 1. None if package names are not the same
288 2. 1 if pkg1 is greater than pkg2
289 3. -1 if pkg1 is less than pkg2
290 4. 0 if pkg1 equals pkg2
292 if pkg1[0] != pkg2[0]:
294 return vercmp("-".join(pkg1[1:]), "-".join(pkg2[1:]))
296 def _pkgsplit(mypkg, eapi=None):
300 1. None if input is invalid.
301 2. (pn, ver, rev) if input is pv
303 m = _get_pv_re(_get_eapi_attrs(eapi)).match(mypkg)
307 if m.group('pn_inval') is not None:
308 # package name appears to have a version-like suffix
316 return (m.group('pn'), m.group('ver'), rev)
318 _cat_re = re.compile('^%s$' % _cat, re.UNICODE)
319 _missing_cat = 'null'
321 def catpkgsplit(mydata, silent=1, eapi=None):
323 Takes a Category/Package-Version-Rev and returns a list of each.
325 @param mydata: Data to split
327 @param silent: suppress error messages
328 @type silent: Boolean (integer)
331 1. If each exists, it returns [cat, pkgname, version, rev]
332 2. If cat is not specificed in mydata, cat will be "null"
333 3. if rev does not exist it will be '-r0'
336 return mydata.cpv_split
337 except AttributeError:
339 mysplit = mydata.split('/', 1)
343 p_split = _pkgsplit(mydata, eapi=eapi)
344 elif len(mysplit)==2:
346 if _cat_re.match(cat) is not None:
347 p_split = _pkgsplit(mysplit[1], eapi=eapi)
350 retval = (cat, p_split[0], p_split[1], p_split[2])
353 class _pkg_str(_unicode):
355 This class represents a cpv. It inherits from str (unicode in python2) and
356 has attributes that cache results for use by functions like catpkgsplit and
357 cpv_getkey which are called frequently (especially in match_from_list).
358 Instances are typically created in dbapi.cp_list() or the Atom contructor,
359 and propagate from there. Generally, code that pickles these objects will
360 manually convert them to a plain unicode object first.
363 def __new__(cls, cpv, metadata=None, settings=None, eapi=None,
364 repo=None, slot=None):
365 return _unicode.__new__(cls, cpv)
367 def __init__(self, cpv, metadata=None, settings=None, eapi=None,
368 repo=None, slot=None):
369 if not isinstance(cpv, _unicode):
370 # Avoid TypeError from _unicode.__init__ with PyPy.
371 cpv = _unicode_decode(cpv)
372 _unicode.__init__(cpv)
373 if metadata is not None:
374 self.__dict__['_metadata'] = metadata
375 slot = metadata.get('SLOT', slot)
376 repo = metadata.get('repository', repo)
377 eapi = metadata.get('EAPI', eapi)
378 if settings is not None:
379 self.__dict__['_settings'] = settings
381 self.__dict__['eapi'] = eapi
382 self.__dict__['cpv_split'] = catpkgsplit(cpv, eapi=eapi)
383 if self.cpv_split is None:
384 raise InvalidData(cpv)
385 self.__dict__['cp'] = self.cpv_split[0] + '/' + self.cpv_split[1]
386 if self.cpv_split[-1] == "r0" and cpv[-3:] != "-r0":
387 self.__dict__['version'] = "-".join(self.cpv_split[2:-1])
389 self.__dict__['version'] = "-".join(self.cpv_split[2:])
390 # for match_from_list introspection
391 self.__dict__['cpv'] = self
393 eapi_attrs = _get_eapi_attrs(eapi)
394 slot_match = _get_slot_re(eapi_attrs).match(slot)
395 if slot_match is None:
396 # Avoid an InvalidAtom exception when creating SLOT atoms
397 self.__dict__['slot'] = '0'
398 self.__dict__['sub_slot'] = '0'
399 self.__dict__['slot_invalid'] = slot
401 if eapi_attrs.slot_operator:
402 slot_split = slot.split("/")
403 self.__dict__['slot'] = slot_split[0]
404 if len(slot_split) > 1:
405 self.__dict__['sub_slot'] = slot_split[1]
407 self.__dict__['sub_slot'] = slot_split[0]
409 self.__dict__['slot'] = slot
410 self.__dict__['sub_slot'] = slot
413 repo = _gen_valid_repo(repo)
416 self.__dict__['repo'] = repo
418 def __setattr__(self, name, value):
419 raise AttributeError("_pkg_str instances are immutable",
420 self.__class__, name, value)
426 except AttributeError:
428 metadata = self._metadata
429 settings = self._settings
430 except AttributeError:
431 raise AttributeError('stable')
432 if not settings.local_config:
433 # Since repoman uses different config instances for
434 # different profiles, our local instance does not
435 # refer to the correct profile.
436 raise AssertionError('invalid context')
437 stable = settings._isStable(self)
438 self.__dict__['_stable'] = stable
441 def pkgsplit(mypkg, silent=1, eapi=None):
443 @param mypkg: either a pv or cpv
445 1. None if input is invalid.
446 2. (pn, ver, rev) if input is pv
447 3. (cp, ver, rev) if input is a cpv
449 catpsplit = catpkgsplit(mypkg, eapi=eapi)
450 if catpsplit is None:
452 cat, pn, ver, rev = catpsplit
453 if cat is _missing_cat and '/' not in mypkg:
454 return (pn, ver, rev)
456 return (cat + '/' + pn, ver, rev)
458 def cpv_getkey(mycpv, eapi=None):
459 """Calls catpkgsplit on a cpv and returns only the cp."""
462 except AttributeError:
464 mysplit = catpkgsplit(mycpv, eapi=eapi)
465 if mysplit is not None:
466 return mysplit[0] + '/' + mysplit[1]
468 warnings.warn("portage.versions.cpv_getkey() " + \
469 "called with invalid cpv: '%s'" % (mycpv,),
470 DeprecationWarning, stacklevel=2)
472 myslash = mycpv.split("/", 1)
473 mysplit = _pkgsplit(myslash[-1], eapi=eapi)
478 return myslash[0] + "/" + mysplit[0]
482 def cpv_getversion(mycpv, eapi=None):
483 """Returns the v (including revision) from an cpv."""
486 except AttributeError:
488 cp = cpv_getkey(mycpv, eapi=eapi)
491 return mycpv[len(cp+"-"):]
493 def cpv_sort_key(eapi=None):
495 Create an object for sorting cpvs, to be used as the 'key' parameter
496 in places like list.sort() or sorted(). This calls catpkgsplit() once for
497 each cpv and caches the result. If a given cpv is invalid or two cpvs
498 have different category/package names, then plain string (> and <)
501 @rtype: key object for sorting
502 @return: object for use as the 'key' parameter in places like
503 list.sort() or sorted()
508 def cmp_cpv(cpv1, cpv2):
510 split1 = split_cache.get(cpv1, False)
515 except AttributeError:
517 split1 = _pkg_str(cpv1, eapi=eapi)
520 split_cache[cpv1] = split1
522 split2 = split_cache.get(cpv2, False)
527 except AttributeError:
529 split2 = _pkg_str(cpv2, eapi=eapi)
532 split_cache[cpv2] = split2
534 if split1 is None or split2 is None or split1.cp != split2.cp:
535 return (cpv1 > cpv2) - (cpv1 < cpv2)
537 return vercmp(split1.version, split2.version)
539 return cmp_sort_key(cmp_cpv)
542 return mydep.split("/", 1)
544 def best(mymatches, eapi=None):
545 """Accepts None arguments; assumes matches are valid."""
548 if len(mymatches) == 1:
550 bestmatch = mymatches[0]
552 v2 = bestmatch.version
553 except AttributeError:
554 v2 = _pkg_str(bestmatch, eapi=eapi).version
555 for x in mymatches[1:]:
558 except AttributeError:
559 v1 = _pkg_str(x, eapi=eapi).version
560 if vercmp(v1, v2) > 0: