portage.versions.ververify(): Return an instance of bool (which is a subclass of...
[portage.git] / pym / portage / versions.py
1 # versions.py -- core Portage functionality
2 # Copyright 1998-2014 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4
5 from __future__ import unicode_literals
6
7 __all__ = [
8         'best', 'catpkgsplit', 'catsplit',
9         'cpv_getkey', 'cpv_getversion', 'cpv_sort_key', 'pkgcmp',  'pkgsplit',
10         'ververify', 'vercmp'
11 ]
12
13 import re
14 import sys
15 import warnings
16
17 if sys.hexversion < 0x3000000:
18         _unicode = unicode
19 else:
20         _unicode = str
21
22 import portage
23 portage.proxy.lazyimport.lazyimport(globals(),
24         'portage.repository.config:_gen_valid_repo',
25         'portage.util:cmp_sort_key',
26 )
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 _
31
32 _unknown_repo = "__unknown__"
33
34 # \w is [a-zA-Z0-9_]
35
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+.-]*)'
39
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+.-]*'
43
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.
47 _pkg = {
48         "dots_disallowed_in_PN": r'[\w+][\w+-]*?',
49         "dots_allowed_in_PN":    r'[\w+][\w+.-]*?',
50 }
51
52 _v = r'(cvs\.)?(\d+)((\.\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\d*)*)'
53 _rev = r'\d+'
54 _vr = _v + '(-r(' + _rev + '))?'
55
56 _cp = {
57         "dots_disallowed_in_PN": '(' + _cat + '/' + _pkg['dots_disallowed_in_PN'] + '(-' + _vr + ')?)',
58         "dots_allowed_in_PN":    '(' + _cat + '/' + _pkg['dots_allowed_in_PN']    + '(-' + _vr + ')?)',
59 }
60 _cpv = {
61         "dots_disallowed_in_PN": '(' + _cp['dots_disallowed_in_PN'] + '-' + _vr + ')',
62         "dots_allowed_in_PN":    '(' + _cp['dots_allowed_in_PN']    + '-' + _vr + ')',
63 }
64 _pv = {
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 + '))?',
67 }
68
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"]
73
74 _slot_re_cache = {}
75
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:
80                 return slot_re
81
82         if eapi_attrs.slot_operator:
83                 slot_re = _slot + r'(/' + _slot + r')?'
84         else:
85                 slot_re = _slot
86
87         slot_re = re.compile('^' + slot_re + '$', re.VERBOSE | re.UNICODE)
88
89         _slot_re_cache[cache_key] = slot_re
90         return slot_re
91
92 _pv_re_cache = {}
93
94 def _get_pv_re(eapi_attrs):
95         cache_key = eapi_attrs.dots_in_PN
96         pv_re = _pv_re_cache.get(cache_key)
97         if pv_re is not None:
98                 return pv_re
99
100         if eapi_attrs.dots_in_PN:
101                 pv_re = _pv['dots_allowed_in_PN']
102         else:
103                 pv_re = _pv['dots_disallowed_in_PN']
104
105         pv_re = re.compile(r'^' + pv_re + r'$', re.VERBOSE | re.UNICODE)
106
107         _pv_re_cache[cache_key] = pv_re
108         return pv_re
109
110 def ververify(myver, silent=1):
111         if ver_regexp.match(myver):
112                 return True
113         else:
114                 if not silent:
115                         print(_("!!! syntax error in version: %s") % myver)
116                 return False
117
118 def vercmp(ver1, ver2, silent=1):
119         """
120         Compare two versions
121         Example usage:
122                 >>> from portage.versions import vercmp
123                 >>> vercmp('1.0-r1','1.2-r3')
124                 negative number
125                 >>> vercmp('1.3','1.2-r3')
126                 positive number
127                 >>> vercmp('1.0_p3','1.0_p3')
128                 0
129         
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
135         @return:
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)
140         """
141
142         if ver1 == ver2:
143                 return 0
144
145         match1 = ver_regexp.match(ver1)
146         match2 = ver_regexp.match(ver2)
147         
148         # checking that the versions are valid
149         if not match1 or not match1.groups():
150                 if not silent:
151                         print(_("!!! syntax error in version: %s") % ver1)
152                 return None
153         if not match2 or not match2.groups():
154                 if not silent:
155                         print(_("!!! syntax error in version: %s") % ver2)
156                 return None
157
158         # shortcut for cvs ebuilds (new style)
159         if match1.group(1) and not match2.group(1):
160                 return 1
161         elif match2.group(1) and not match1.group(1):
162                 return -1
163         
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))]
168
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(".")
173
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:
179                                 list1.append(-1)
180                                 list2.append(int(vlist2[i]))
181                         elif len(vlist2) <= i or len(vlist2[i]) == 0:
182                                 list1.append(int(vlist1[i]))
183                                 list2.append(-1)
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
189                         else:
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")))
201
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)))
212
213         for i in range(0, max(len(list1), len(list2))):
214                 if len(list1) <= i:
215                         return -1
216                 elif len(list2) <= i:
217                         return 1
218                 elif list1[i] != list2[i]:
219                         a = list1[i]
220                         b = list2[i]
221                         rval = (a > b) - (a < b)
222                         return rval
223
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:]
227         
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
230                 if len(list1) <= i:
231                         s1 = ("p","-1")
232                 else:
233                         s1 = suffix_regexp.match(list1[i]).groups()
234                 if len(list2) <= i:
235                         s2 = ("p","-1")
236                 else:
237                         s2 = suffix_regexp.match(list2[i]).groups()
238                 if s1[0] != s2[0]:
239                         a = suffix_value[s1[0]]
240                         b = suffix_value[s2[0]]
241                         rval = (a > b) - (a < b)
242                         return rval
243                 if s1[1] != s2[1]:
244                         # it's possible that the s(1|2)[1] == ''
245                         # in such a case, fudge it.
246                         try:
247                                 r1 = int(s1[1])
248                         except ValueError:
249                                 r1 = 0
250                         try:
251                                 r2 = int(s2[1])
252                         except ValueError:
253                                 r2 = 0
254                         rval = (r1 > r2) - (r1 < r2)
255                         if rval:
256                                 return rval
257
258         # the suffix part is equal to, so finally check the revision
259         if match1.group(10):
260                 r1 = int(match1.group(10))
261         else:
262                 r1 = 0
263         if match2.group(10):
264                 r2 = int(match2.group(10))
265         else:
266                 r2 = 0
267         rval = (r1 > r2) - (r1 < r2)
268         return rval
269         
270 def pkgcmp(pkg1, pkg2):
271         """
272         Compare 2 package versions created in pkgsplit format.
273
274         Example usage:
275                 >>> from portage.versions import *
276                 >>> pkgcmp(pkgsplit('test-1.0-r1'),pkgsplit('test-1.2-r3'))
277                 -1
278                 >>> pkgcmp(pkgsplit('test-1.3'),pkgsplit('test-1.2-r3'))
279                 1
280
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
286         @return: 
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
291         """
292         if pkg1[0] != pkg2[0]:
293                 return None
294         return vercmp("-".join(pkg1[1:]), "-".join(pkg2[1:]))
295
296 def _pkgsplit(mypkg, eapi=None):
297         """
298         @param mypkg: pv
299         @return:
300         1. None if input is invalid.
301         2. (pn, ver, rev) if input is pv
302         """
303         m = _get_pv_re(_get_eapi_attrs(eapi)).match(mypkg)
304         if m is None:
305                 return None
306
307         if m.group('pn_inval') is not None:
308                 # package name appears to have a version-like suffix
309                 return None
310
311         rev = m.group('rev')
312         if rev is None:
313                 rev = '0'
314         rev = 'r' + rev
315
316         return  (m.group('pn'), m.group('ver'), rev) 
317
318 _cat_re = re.compile('^%s$' % _cat, re.UNICODE)
319 _missing_cat = 'null'
320
321 def catpkgsplit(mydata, silent=1, eapi=None):
322         """
323         Takes a Category/Package-Version-Rev and returns a list of each.
324         
325         @param mydata: Data to split
326         @type mydata: string 
327         @param silent: suppress error messages
328         @type silent: Boolean (integer)
329         @rype: list
330         @return:
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'
334         """
335         try:
336                 return mydata.cpv_split
337         except AttributeError:
338                 pass
339         mysplit = mydata.split('/', 1)
340         p_split=None
341         if len(mysplit)==1:
342                 cat = _missing_cat
343                 p_split = _pkgsplit(mydata, eapi=eapi)
344         elif len(mysplit)==2:
345                 cat = mysplit[0]
346                 if _cat_re.match(cat) is not None:
347                         p_split = _pkgsplit(mysplit[1], eapi=eapi)
348         if not p_split:
349                 return None
350         retval = (cat, p_split[0], p_split[1], p_split[2])
351         return retval
352
353 class _pkg_str(_unicode):
354         """
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.
361         """
362
363         def __new__(cls, cpv, metadata=None, settings=None, eapi=None,
364                 repo=None, slot=None):
365                 return _unicode.__new__(cls, cpv)
366
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
380                 if eapi is not None:
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])
388                 else:
389                         self.__dict__['version'] = "-".join(self.cpv_split[2:])
390                 # for match_from_list introspection
391                 self.__dict__['cpv'] = self
392                 if slot is not None:
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
400                         else:
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]
406                                         else:
407                                                 self.__dict__['sub_slot'] = slot_split[0]
408                                 else:
409                                         self.__dict__['slot'] = slot
410                                         self.__dict__['sub_slot'] = slot
411
412                 if repo is not None:
413                         repo = _gen_valid_repo(repo)
414                         if not repo:
415                                 repo = _unknown_repo
416                         self.__dict__['repo'] = repo
417
418         def __setattr__(self, name, value):
419                 raise AttributeError("_pkg_str instances are immutable",
420                         self.__class__, name, value)
421
422         @property
423         def stable(self):
424                 try:
425                         return self._stable
426                 except AttributeError:
427                         try:
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
439                         return stable
440
441 def pkgsplit(mypkg, silent=1, eapi=None):
442         """
443         @param mypkg: either a pv or cpv
444         @return:
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
448         """
449         catpsplit = catpkgsplit(mypkg, eapi=eapi)
450         if catpsplit is None:
451                 return None
452         cat, pn, ver, rev = catpsplit
453         if cat is _missing_cat and '/' not in mypkg:
454                 return (pn, ver, rev)
455         else:
456                 return (cat + '/' + pn, ver, rev)
457
458 def cpv_getkey(mycpv, eapi=None):
459         """Calls catpkgsplit on a cpv and returns only the cp."""
460         try:
461                 return mycpv.cp
462         except AttributeError:
463                 pass
464         mysplit = catpkgsplit(mycpv, eapi=eapi)
465         if mysplit is not None:
466                 return mysplit[0] + '/' + mysplit[1]
467
468         warnings.warn("portage.versions.cpv_getkey() " + \
469                 "called with invalid cpv: '%s'" % (mycpv,),
470                 DeprecationWarning, stacklevel=2)
471
472         myslash = mycpv.split("/", 1)
473         mysplit = _pkgsplit(myslash[-1], eapi=eapi)
474         if mysplit is None:
475                 return None
476         mylen = len(myslash)
477         if mylen == 2:
478                 return myslash[0] + "/" + mysplit[0]
479         else:
480                 return mysplit[0]
481
482 def cpv_getversion(mycpv, eapi=None):
483         """Returns the v (including revision) from an cpv."""
484         try:
485                 return mycpv.version
486         except AttributeError:
487                 pass
488         cp = cpv_getkey(mycpv, eapi=eapi)
489         if cp is None:
490                 return None
491         return mycpv[len(cp+"-"):]
492
493 def cpv_sort_key(eapi=None):
494         """
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 <)
499         comparison is used.
500
501         @rtype: key object for sorting
502         @return: object for use as the 'key' parameter in places like
503                 list.sort() or sorted()
504         """
505
506         split_cache = {}
507
508         def cmp_cpv(cpv1, cpv2):
509
510                 split1 = split_cache.get(cpv1, False)
511                 if split1 is False:
512                         split1 = None
513                         try:
514                                 split1 = cpv1.cpv
515                         except AttributeError:
516                                 try:
517                                         split1 = _pkg_str(cpv1, eapi=eapi)
518                                 except InvalidData:
519                                         pass
520                         split_cache[cpv1] = split1
521
522                 split2 = split_cache.get(cpv2, False)
523                 if split2 is False:
524                         split2 = None
525                         try:
526                                 split2 = cpv2.cpv
527                         except AttributeError:
528                                 try:
529                                         split2 = _pkg_str(cpv2, eapi=eapi)
530                                 except InvalidData:
531                                         pass
532                         split_cache[cpv2] = split2
533
534                 if split1 is None or split2 is None or split1.cp != split2.cp:
535                         return (cpv1 > cpv2) - (cpv1 < cpv2)
536
537                 return vercmp(split1.version, split2.version)
538
539         return cmp_sort_key(cmp_cpv)
540
541 def catsplit(mydep):
542         return mydep.split("/", 1)
543
544 def best(mymatches, eapi=None):
545         """Accepts None arguments; assumes matches are valid."""
546         if not mymatches:
547                 return ""
548         if len(mymatches) == 1:
549                 return mymatches[0]
550         bestmatch = mymatches[0]
551         try:
552                 v2 = bestmatch.version
553         except AttributeError:
554                 v2 = _pkg_str(bestmatch, eapi=eapi).version
555         for x in mymatches[1:]:
556                 try:
557                         v1 = x.version
558                 except AttributeError:
559                         v1 = _pkg_str(x, eapi=eapi).version
560                 if vercmp(v1, v2) > 0:
561                         bestmatch = x
562                         v2 = v1
563         return bestmatch