Backport of version code rewrite (bug 37406), should be completely backwards compatib...
authorMarius Mauch <genone@gentoo.org>
Sun, 13 Nov 2005 16:07:28 +0000 (16:07 -0000)
committerMarius Mauch <genone@gentoo.org>
Sun, 13 Nov 2005 16:07:28 +0000 (16:07 -0000)
Also adds some new features in version syntax like cvs version prefixes (in the same package) and multiple suffixes.

svn path=/main/trunk/; revision=2309

pym/portage.py
pym/portage_versions.py [new file with mode: 0644]

index 60ab80e4b5b33cf94b35180f19cef3002f735a5a..63a67f6e89d1582438901f82b952581fb2a93f11 100644 (file)
@@ -100,6 +100,10 @@ try:
        from portage_checksum import perform_md5,perform_checksum,prelink_capable
        import eclass_cache
        from portage_localization import _
+
+       # Need these functions directly in portage namespace to not break every external tool in existence
+       from portage_versions import ververify,vercmp,catsplit,catpkgsplit,pkgsplit,pkgcmp
+
 except SystemExit, e:
        raise
 except Exception, e:
@@ -3009,150 +3013,6 @@ def unmerge(cat,pkg,myroot,mysettings,mytrimworld=1):
                mylink.unmerge(trimworld=mytrimworld,cleanup=1)
        mylink.delete()
 
-def relparse(myver):
-       "converts last version part into three components"
-       number=0
-       suffix=0
-       endtype=0
-       endnumber=0
-
-       mynewver=string.split(myver,"_")
-       myver=mynewver[0]
-
-       #normal number or number with letter at end
-       divider=len(myver)-1
-       if myver[divider:] not in "1234567890":
-               #letter at end
-               suffix=ord(myver[divider:])
-               number=string.atof(myver[0:divider])
-       else:
-               number=string.atof(myver)
-
-       if len(mynewver)==2:
-               #an endversion
-               for x in endversion_keys:
-                       elen=len(x)
-                       if mynewver[1][:elen] == x:
-                               match=1
-                               endtype=endversion[x]
-                               try:
-                                       endnumber=string.atof(mynewver[1][elen:])
-                               except SystemExit, e:
-                                       raise
-                               except:
-                                       endnumber=0
-                               break
-       return [number,suffix,endtype,endnumber]
-
-#returns 1 if valid version string, else 0
-# valid string in format: <v1>.<v2>...<vx>[a-z,_{endversion}[vy]]
-# ververify doesn't do package rev.
-
-vercache={}
-def ververify(myorigval,silent=1):
-       try:
-               return vercache[myorigval]
-       except KeyError:
-               pass
-       if len(myorigval)==0:
-               if not silent:
-                       print "!!! Name error: package contains empty \"-\" part."
-               return 0
-       myval=string.split(myorigval,'.')
-       if len(myval)==0:
-               if not silent:
-                       print "!!! Name error: empty version string."
-               vercache[myorigval]=0
-               return 0
-       #all but the last version must be a numeric
-       for x in myval[:-1]:
-               if not len(x):
-                       if not silent:
-                               print "!!! Name error in",myorigval+": two decimal points in a row"
-                       vercache[myorigval]=0
-                       return 0
-               try:
-                       foo=int(x)
-               except SystemExit, e:
-                       raise
-               except:
-                       if not silent:
-                               print "!!! Name error in",myorigval+": \""+x+"\" is not a valid version component."
-                       vercache[myorigval]=0
-                       return 0
-       if not len(myval[-1]):
-                       if not silent:
-                               print "!!! Name error in",myorigval+": two decimal points in a row"
-                       vercache[myorigval]=0
-                       return 0
-       try:
-               foo=int(myval[-1])
-               vercache[myorigval]=1
-               return 1
-       except SystemExit, e:
-               raise
-       except:
-               pass
-       #ok, our last component is not a plain number or blank, let's continue
-       if myval[-1][-1] in string.lowercase:
-               try:
-                       foo=int(myval[-1][:-1])
-                       vercache[myorigval]=1
-                       return 1
-                       # 1a, 2.0b, etc.
-               except SystemExit, e:
-                       raise
-               except:
-                       pass
-       #ok, maybe we have a 1_alpha or 1_beta2; let's see
-       #ep="endpart"
-       ep=string.split(myval[-1],"_")
-       if len(ep)!=2:
-               if not silent:
-                       print "!!! Name error in",myorigval
-               vercache[myorigval]=0
-               return 0
-       try:
-               foo=int(ep[0][-1])
-               chk=ep[0]
-       except SystemExit, e:
-               raise
-       except:
-               # because it's ok last char is not numeric. example: foo-1.0.0a_pre1
-               chk=ep[0][:-1]
-
-       try:
-               foo=int(chk)
-       except SystemExit, e:
-               raise
-       except:
-               #this needs to be numeric or numeric+single letter,
-               #i.e. the "1" in "1_alpha" or "1a_alpha"
-               if not silent:
-                       print "!!! Name error in",myorigval+": characters before _ must be numeric or numeric+single letter"
-               vercache[myorigval]=0
-               return 0
-       for mye in endversion_keys:
-               if ep[1][0:len(mye)]==mye:
-                       if len(mye)==len(ep[1]):
-                               #no trailing numeric; ok
-                               vercache[myorigval]=1
-                               return 1
-                       else:
-                               try:
-                                       foo=int(ep[1][len(mye):])
-                                       vercache[myorigval]=1
-                                       return 1
-                               except SystemExit, e:
-                                       raise
-                               except:
-                                       #if no endversions work, *then* we return 0
-                                       pass
-       if not silent:
-               print "!!! Name error in",myorigval
-       vercache[myorigval]=0
-       return 0
-
 def isvalidatom(atom):
        mycpv_cps = catpkgsplit(dep_getcpv(atom))
        operator = get_operator(atom)
@@ -3198,201 +3058,10 @@ def isspecific(mypkg):
        iscache[mypkg]=0
        return 0
 
-# This function can be used as a package verification function, i.e.
-# "pkgsplit("foo-1.2-1") will return None if foo-1.2-1 isn't a valid
-# package (with version) name. If it is a valid name, pkgsplit will
-# return a list containing: [ pkgname, pkgversion(norev), pkgrev ].
-# For foo-1.2-1, this list would be [ "foo", "1.2", "1" ].  For
-# Mesa-3.0, this list would be [ "Mesa", "3.0", "0" ].
-pkgcache={}
-
-def pkgsplit(mypkg,silent=1):
-       try:
-               if not pkgcache[mypkg]:
-                       return None
-               return pkgcache[mypkg][:]
-       except KeyError:
-               pass
-       myparts=string.split(mypkg,'-')
-       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 SystemExit, e:
-                       raise
-               except:
-                       pass
-       if revok:
-               if ververify(myparts[-2]):
-                       if len(myparts)==2:
-                               pkgcache[mypkg]=None
-                               return None
-                       else:
-                               for x in myparts[:-2]:
-                                       if ververify(x):
-                                               pkgcache[mypkg]=None
-                                               return None
-                                               #names can't have versiony looking parts
-                               myval=[string.join(myparts[:-2],"-"),myparts[-2],myparts[-1]]
-                               pkgcache[mypkg]=myval
-                               return myval
-               else:
-                       pkgcache[mypkg]=None
-                       return None
-
-       elif ververify(myparts[-1],silent=silent):
-               if len(myparts)==1:
-                       if not silent:
-                               print "!!! Name error in",mypkg+": missing name part."
-                       pkgcache[mypkg]=None
-                       return None
-               else:
-                       for x in myparts[:-1]:
-                               if ververify(x):
-                                       if not silent:
-                                               print "!!! Name error in",mypkg+": multiple version parts."
-                                       pkgcache[mypkg]=None
-                                       return None
-                       myval=[string.join(myparts[:-1],"-"),myparts[-1],"r0"]
-                       pkgcache[mypkg]=myval[:]
-                       return myval
-       else:
-               pkgcache[mypkg]=None
-               return None
-
 def getCPFromCPV(mycpv):
        """Calls pkgsplit on a cpv and returns only the cp."""
        return pkgsplit(mycpv)[0]
 
-catcache={}
-def catpkgsplit(mydata,silent=1):
-       "returns [cat, pkgname, version, rev ]"
-       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:
-               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
-
-# vercmp:
-# This takes two version strings and returns an integer to tell you whether
-# the versions are the same, val1>val2 or val2>val1.
-vcmpcache={}
-def vercmp(val1,val2):
-       if val1==val2:
-               #quick short-circuit
-               return 0
-       valkey=val1+" "+val2
-       try:
-               return vcmpcache[valkey]
-               try:
-                       return -vcmpcache[val2+" "+val1]
-               except KeyError:
-                       pass
-       except KeyError:
-               pass
-
-       # consider 1_p2 vc 1.1
-       # after expansion will become (1_p2,0) vc (1,1)
-       # then 1_p2 is compared with 1 before 0 is compared with 1
-       # to solve the bug we need to convert it to (1,0_p2)
-       # by splitting _prepart part and adding it back _after_expansion
-       val1_prepart = val2_prepart = ''
-       if val1.count('_'):
-               val1, val1_prepart = val1.split('_', 1)
-       if val2.count('_'):
-               val2, val2_prepart = val2.split('_', 1)
-
-       # replace '-' by '.'
-       # FIXME: Is it needed? can val1/2 contain '-'?
-       val1=string.split(val1,'-')
-       if len(val1)==2:
-               val1[0]=val1[0]+"."+val1[1]
-       val2=string.split(val2,'-')
-       if len(val2)==2:
-               val2[0]=val2[0]+"."+val2[1]
-
-       val1=string.split(val1[0],'.')
-       val2=string.split(val2[0],'.')
-
-       #add back decimal point so that .03 does not become "3" !
-       for x in range(1,len(val1)):
-               if val1[x][0] == '0' :
-                       val1[x]='.' + val1[x]
-       for x in range(1,len(val2)):
-               if val2[x][0] == '0' :
-                       val2[x]='.' + val2[x]
-
-       # extend version numbers
-       if len(val2)<len(val1):
-               val2.extend(["0"]*(len(val1)-len(val2)))
-       elif len(val1)<len(val2):
-               val1.extend(["0"]*(len(val2)-len(val1)))
-
-       # add back _prepart tails
-       if val1_prepart:
-               val1[-1] += '_' + val1_prepart
-       if val2_prepart:
-               val2[-1] += '_' + val2_prepart
-       #The above code will extend version numbers out so they
-       #have the same number of digits.
-       for x in range(0,len(val1)):
-               cmp1=relparse(val1[x])
-               cmp2=relparse(val2[x])
-               for y in range(0,4):
-                       myret=cmp1[y]-cmp2[y]
-                       if myret != 0:
-                               vcmpcache[valkey]=myret
-                               return myret
-       vcmpcache[valkey]=0
-       return 0
-
-
-def pkgcmp(pkg1,pkg2):
-       """if returnval is less than zero, then pkg2 is newer than pkg1, zero if equal and positive if older."""
-       if pkg1[0] != pkg2[0]:
-               return None
-       mycmp=vercmp(pkg1[1],pkg2[1])
-       if mycmp>0:
-               return 1
-       if mycmp<0:
-               return -1
-       r1=int(pkg1[2][1:])
-       r2=int(pkg2[2][1:])
-       if r1>r2:
-               return 1
-       if r2>r1:
-               return -1
-       return 0
-
 def dep_parenreduce(mysplit,mypos=0):
        "Accepts a list of strings, and converts '(' and ')' surrounded items to sub-lists"
        while (mypos<len(mysplit)):
diff --git a/pym/portage_versions.py b/pym/portage_versions.py
new file mode 100644 (file)
index 0000000..ddb35a3
--- /dev/null
@@ -0,0 +1,222 @@
+import re,string
+
+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}
+
+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):
+       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 = [string.atoi(match1.group(2))]
+       list2 = [string.atoi(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))):
+                       if len(vlist1) <= i or len(vlist1[i]) == 0:
+                               list1.append(0)
+                               list2.append(string.atoi(vlist2[i]))
+                       elif len(vlist2) <= i or len(vlist2[i]) == 0:
+                               list1.append(string.atoi(vlist1[i]))
+                               list2.append(0)
+                       # 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(string.atoi(vlist1[i]))
+                               list2.append(string.atoi(vlist2[i]))
+                       # now we have to use floats so 1.02 compares correctly against 1.1
+                       else:
+                               list1.append(string.atof("0."+vlist1[i]))
+                               list2.append(string.atof("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 = string.atoi(s1[1])
+                       except ValueError:      r1 = 0
+                       try:                    r2 = string.atoi(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 = string.atoi(match1.group(10))
+       else:
+               r1 = 0
+       if match2.group(10):
+               r2 = string.atoi(match2.group(10))
+       else:
+               r2 = 0
+       vercmp_cache[mykey] = r1 - r2
+       return r1 - r2
+       
+def pkgcmp(pkg1, 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=string.atof(pkg1[2][1:])
+       r2=string.atof(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=string.split(mypkg,'-')
+       
+       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:
+                       string.atoi(myrev[1:])
+                       revok=1
+               except: 
+                       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=[string.join(myparts[:verPos],"-"),myparts[verPos],revision]
+                       pkgcache[mypkg]=myval
+                       return myval
+       else:
+               pkgcache[mypkg]=None
+               return None
+
+catcache={}
+def catpkgsplit(mydata,silent=1):
+       "returns [cat, pkgname, version, rev ]"
+       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:
+               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)
+