# portage_versions.py -- core Portage functionality # Copyright 1998-2006 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Id$ import re ver_regexp = re.compile("^(cvs\\.)?(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?$") 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 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 len(match1.group(3)) or len(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])) # and now the final letter 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]: vercmp_cache[mykey] = list1[i] - list2[i] return list1[i] - list2[i] # 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))): if len(list1) <= i: s1 = ("p","0") else: s1 = suffix_regexp.match(list1[i]).groups() if len(list2) <= i: s2 = ("p","0") else: s2 = suffix_regexp.match(list2[i]).groups() if s1[0] != s2[0]: return suffix_value[s1[0]] - suffix_value[s2[0]] 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 return r1 - r2 # 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 vercmp_cache[mykey] = r1 - r2 return r1 - r2 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 mycmp=vercmp(pkg1[1],pkg2[1]) if mycmp>0: return 1 if mycmp<0: return -1 r1=float(pkg1[2][1:]) r2=float(pkg2[2][1:]) if r1>r2: return 1 if r2>r1: return -1 return 0 pkgcache={} def pkgsplit(mypkg,silent=1): try: if not pkgcache[mypkg]: return None return pkgcache[mypkg][:] except KeyError: pass myparts=mypkg.split("-") if len(myparts)<2: if not silent: print "!!! Name error in",mypkg+": missing a version or name part." pkgcache[mypkg]=None return None for x in myparts: if len(x)==0: if not silent: print "!!! Name error in",mypkg+": empty \"-\" part." pkgcache[mypkg]=None return None #verify rev revok=0 myrev=myparts[-1] if len(myrev) and myrev[0]=="r": try: int(myrev[1:]) revok=1 except ValueError: # from int() pass if revok: verPos = -2 revision = myparts[-1] else: verPos = -1 revision = "r0" if ververify(myparts[verPos]): if len(myparts)== (-1*verPos): pkgcache[mypkg]=None return None else: for x in myparts[:verPos]: if ververify(x): pkgcache[mypkg]=None return None #names can't have versiony looking parts myval=["-".join(myparts[:verPos]),myparts[verPos],revision] pkgcache[mypkg]=myval return myval else: pkgcache[mypkg]=None return None _valid_category = re.compile("^\w[\w-]*") 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' 4. If cat is invalid (specified but has incorrect syntax) an InvalidData Exception will be thrown """ # Categories may contain a-zA-z0-9+_- but cannot start with - global _valid_category import portage_dep try: if not catcache[mydata]: return None return catcache[mydata][:] except KeyError: pass mysplit=mydata.split("/") p_split=None if len(mysplit)==1: retval=["null"] p_split=pkgsplit(mydata,silent=silent) elif len(mysplit)==2: if portage_dep._dep_check_strict and \ not _valid_category.match(mysplit[0]): raise InvalidData("Invalid category in %s" %mydata ) retval=[mysplit[0]] p_split=pkgsplit(mysplit[1],silent=silent) if not p_split: catcache[mydata]=None return None retval.extend(p_split) catcache[mydata]=retval return retval def catsplit(mydep): return mydep.split("/", 1) def best(mymatches): """Accepts None arguments; assumes matches are valid.""" if mymatches is None: return "" if not len(mymatches): return "" 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