# versions.py -- core Portage functionality # Copyright 1998-2006 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 __all__ = [ 'best', 'catpkgsplit', 'catsplit', 'cpv_getkey', 'cpv_getversion', 'pkgcmp', 'pkgsplit', 'ververify', 'vercmp' ] import re import warnings # \w is [a-zA-Z0-9_] # 2.1.1 A category name may contain any of the characters [A-Za-z0-9+_.-]. # It must not begin with a hyphen or a dot. _cat = r'[\w+][\w+.-]*' # 2.1.2 A package name may contain any of the characters [A-Za-z0-9+_-]. # It must not begin with a hyphen, # and must not end in a hyphen followed by one or more digits. _pkg = r'[\w+][\w+-]*?' _v = r'(cvs\.)?(\d+)((\.\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\d*)*)' _rev = r'\d+' _vr = _v + '(-r(' + _rev + '))?' _cp = '(' + _cat + '/' + _pkg + '(-' + _vr + ')?)' _cpv = '(' + _cp + '-' + _vr + ')' _pv = '(?P' + _pkg + '(?P-' + _vr + ')?)' + '-(?P' + _v + ')(-r(?P' + _rev + '))?' ver_regexp = re.compile("^" + _vr + "$") suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$") suffix_value = {"pre": -2, "p": 0, "alpha": -4, "beta": -3, "rc": -1} endversion_keys = ["pre", "p", "alpha", "beta", "rc"] from portage.exception import InvalidData from portage.localization import _ def ververify(myver, silent=1): if ver_regexp.match(myver): return 1 else: if not silent: print(_("!!! syntax error in version: %s") % myver) return 0 vercmp_cache = {} def vercmp(ver1, ver2, silent=1): """ Compare two versions Example usage: >>> from portage.versions import vercmp >>> vercmp('1.0-r1','1.2-r3') negative number >>> vercmp('1.3','1.2-r3') positive number >>> vercmp('1.0_p3','1.0_p3') 0 @param pkg1: version to compare with (see ver_regexp in portage.versions.py) @type pkg1: string (example: "2.1.2-r3") @param pkg2: version to compare againts (see ver_regexp in portage.versions.py) @type pkg2: string (example: "2.1.2_rc5") @rtype: None or float @return: 1. positive if ver1 is greater than ver2 2. negative if ver1 is less than ver2 3. 0 if ver1 equals ver2 4. None if ver1 or ver2 are invalid (see ver_regexp in portage.versions.py) """ if ver1 == ver2: return 0 mykey=ver1+":"+ver2 try: return vercmp_cache[mykey] except KeyError: pass match1 = ver_regexp.match(ver1) match2 = ver_regexp.match(ver2) # checking that the versions are valid if not match1 or not match1.groups(): if not silent: print(_("!!! syntax error in version: %s") % ver1) return None if not match2 or not match2.groups(): if not silent: print(_("!!! syntax error in version: %s") % ver2) return None # shortcut for cvs ebuilds (new style) if match1.group(1) and not match2.group(1): vercmp_cache[mykey] = 1 return 1 elif match2.group(1) and not match1.group(1): vercmp_cache[mykey] = -1 return -1 # building lists of the version parts before the suffix # first part is simple list1 = [int(match1.group(2))] list2 = [int(match2.group(2))] # this part would greatly benefit from a fixed-length version pattern if match1.group(3) or match2.group(3): vlist1 = match1.group(3)[1:].split(".") vlist2 = match2.group(3)[1:].split(".") for i in range(0, max(len(vlist1), len(vlist2))): # Implcit .0 is given a value of -1, so that 1.0.0 > 1.0, since it # would be ambiguous if two versions that aren't literally equal # are given the same value (in sorting, for example). if len(vlist1) <= i or len(vlist1[i]) == 0: list1.append(-1) list2.append(int(vlist2[i])) elif len(vlist2) <= i or len(vlist2[i]) == 0: list1.append(int(vlist1[i])) list2.append(-1) # Let's make life easy and use integers unless we're forced to use floats elif (vlist1[i][0] != "0" and vlist2[i][0] != "0"): list1.append(int(vlist1[i])) list2.append(int(vlist2[i])) # now we have to use floats so 1.02 compares correctly against 1.1 else: # list1.append(float("0."+vlist1[i])) # list2.append(float("0."+vlist2[i])) # Since python floats have limited range, we multiply both # floating point representations by a constant so that they are # transformed into whole numbers. This allows the practically # infinite range of a python int to be exploited. The # multiplication is done by padding both literal strings with # zeros as necessary to ensure equal length. max_len = max(len(vlist1[i]), len(vlist2[i])) list1.append(int(vlist1[i].ljust(max_len, "0"))) list2.append(int(vlist2[i].ljust(max_len, "0"))) # and now the final letter # NOTE: Behavior changed in r2309 (between portage-2.0.x and portage-2.1). # The new behavior is 12.2.5 > 12.2b which, depending on how you look at, # may seem counter-intuitive. However, if you really think about it, it # seems like it's probably safe to assume that this is the behavior that # is intended by anyone who would use versions such as these. if len(match1.group(5)): list1.append(ord(match1.group(5))) if len(match2.group(5)): list2.append(ord(match2.group(5))) for i in range(0, max(len(list1), len(list2))): if len(list1) <= i: vercmp_cache[mykey] = -1 return -1 elif len(list2) <= i: vercmp_cache[mykey] = 1 return 1 elif list1[i] != list2[i]: a = list1[i] b = list2[i] rval = (a > b) - (a < b) vercmp_cache[mykey] = rval return rval # main version is equal, so now compare the _suffix part list1 = match1.group(6).split("_")[1:] list2 = match2.group(6).split("_")[1:] for i in range(0, max(len(list1), len(list2))): # Implicit _p0 is given a value of -1, so that 1 < 1_p0 if len(list1) <= i: s1 = ("p","-1") else: s1 = suffix_regexp.match(list1[i]).groups() if len(list2) <= i: s2 = ("p","-1") else: s2 = suffix_regexp.match(list2[i]).groups() if s1[0] != s2[0]: a = suffix_value[s1[0]] b = suffix_value[s2[0]] rval = (a > b) - (a < b) vercmp_cache[mykey] = rval return rval if s1[1] != s2[1]: # it's possible that the s(1|2)[1] == '' # in such a case, fudge it. try: r1 = int(s1[1]) except ValueError: r1 = 0 try: r2 = int(s2[1]) except ValueError: r2 = 0 rval = (r1 > r2) - (r1 < r2) if rval: vercmp_cache[mykey] = rval return rval # the suffix part is equal to, so finally check the revision if match1.group(10): r1 = int(match1.group(10)) else: r1 = 0 if match2.group(10): r2 = int(match2.group(10)) else: r2 = 0 rval = (r1 > r2) - (r1 < r2) vercmp_cache[mykey] = rval return rval def pkgcmp(pkg1, pkg2): """ Compare 2 package versions created in pkgsplit format. Example usage: >>> from portage.versions import * >>> pkgcmp(pkgsplit('test-1.0-r1'),pkgsplit('test-1.2-r3')) -1 >>> pkgcmp(pkgsplit('test-1.3'),pkgsplit('test-1.2-r3')) 1 @param pkg1: package to compare with @type pkg1: list (example: ['test', '1.0', 'r1']) @param pkg2: package to compare againts @type pkg2: list (example: ['test', '1.0', 'r1']) @rtype: None or integer @return: 1. None if package names are not the same 2. 1 if pkg1 is greater than pkg2 3. -1 if pkg1 is less than pkg2 4. 0 if pkg1 equals pkg2 """ if pkg1[0] != pkg2[0]: return None return vercmp("-".join(pkg1[1:]), "-".join(pkg2[1:])) _pv_re = re.compile('^' + _pv + '$', re.VERBOSE) def _pkgsplit(mypkg): """ @param mypkg: pv @return: 1. None if input is invalid. 2. (pn, ver, rev) if input is pv """ m = _pv_re.match(mypkg) if m is None: return None if m.group('pn_inval') is not None: # package name appears to have a version-like suffix return None rev = m.group('rev') if rev is None: rev = '0' rev = 'r' + rev return (m.group('pn'), m.group('ver'), rev) _missing_cat = 'null' catcache={} def catpkgsplit(mydata,silent=1): """ Takes a Category/Package-Version-Rev and returns a list of each. @param mydata: Data to split @type mydata: string @param silent: suppress error messages @type silent: Boolean (integer) @rype: list @return: 1. If each exists, it returns [cat, pkgname, version, rev] 2. If cat is not specificed in mydata, cat will be "null" 3. if rev does not exist it will be '-r0' """ try: return catcache[mydata] except KeyError: pass mysplit = mydata.split('/', 1) p_split=None if len(mysplit)==1: cat = _missing_cat p_split = _pkgsplit(mydata) elif len(mysplit)==2: cat = mysplit[0] p_split = _pkgsplit(mysplit[1]) if not p_split: catcache[mydata]=None return None retval = (cat, p_split[0], p_split[1], p_split[2]) catcache[mydata]=retval return retval def pkgsplit(mypkg, silent=1): """ @param mypkg: either a pv or cpv @return: 1. None if input is invalid. 2. (pn, ver, rev) if input is pv 3. (cp, ver, rev) if input is a cpv """ catpsplit = catpkgsplit(mypkg) if catpsplit is None: return None cat, pn, ver, rev = catpsplit if cat is _missing_cat and '/' not in mypkg: return (pn, ver, rev) else: return (cat + '/' + pn, ver, rev) def cpv_getkey(mycpv): """Calls catpkgsplit on a cpv and returns only the cp.""" mysplit = catpkgsplit(mycpv) if mysplit is not None: return mysplit[0] + '/' + mysplit[1] warnings.warn("portage.versions.cpv_getkey() " + \ "called with invalid cpv: '%s'" % (mycpv,), DeprecationWarning, stacklevel=2) myslash = mycpv.split("/", 1) mysplit = _pkgsplit(myslash[-1]) if mysplit is None: return None mylen = len(myslash) if mylen == 2: return myslash[0] + "/" + mysplit[0] else: return mysplit[0] def cpv_getversion(mycpv): """Returns the v (including revision) from an cpv.""" cp = cpv_getkey(mycpv) if cp is None: return None return mycpv[len(cp+"-"):] def catsplit(mydep): return mydep.split("/", 1) def best(mymatches): """Accepts None arguments; assumes matches are valid.""" if not mymatches: return "" if len(mymatches) == 1: return mymatches[0] bestmatch = mymatches[0] p2 = catpkgsplit(bestmatch)[1:] for x in mymatches[1:]: p1 = catpkgsplit(x)[1:] if pkgcmp(p1, p2) > 0: bestmatch = x p2 = catpkgsplit(bestmatch)[1:] return bestmatch