Fix bb.plain and bb.warn function, initial patch from Petri Lehtinen <petri.lehtinen...
[bitbake.git] / lib / bb / __init__.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 #
4 # BitBake Build System Python Library
5 #
6 # Copyright (C) 2003  Holger Schurig
7 # Copyright (C) 2003, 2004  Chris Larson
8 #
9 # Based on Gentoo's portage.py.
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License version 2 as
13 # published by the Free Software Foundation.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License along
21 # with this program; if not, write to the Free Software Foundation, Inc.,
22 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24 __version__ = "1.8.19"
25
26 __all__ = [
27
28     "debug",
29     "note",
30     "error",
31     "fatal",
32
33     "mkdirhier",
34     "movefile",
35
36     "tokenize",
37     "evaluate",
38     "flatten",
39     "relparse",
40     "ververify",
41     "isjustname",
42     "isspecific",
43     "pkgsplit",
44     "catpkgsplit",
45     "vercmp",
46     "pkgcmp",
47     "dep_parenreduce",
48     "dep_opconvert",
49
50 # fetch
51     "decodeurl",
52     "encodeurl",
53
54 # modules
55     "parse",
56     "data",
57     "event",
58     "build",
59     "fetch",
60     "manifest",
61     "methodpool",
62     "cache",
63     "runqueue",
64     "taskdata",
65     "providers",
66  ]
67
68 whitespace = '\t\n\x0b\x0c\r '
69 lowercase = 'abcdefghijklmnopqrstuvwxyz'
70
71 import sys, os, types, re, string, bb
72 from bb import msg
73
74 #projectdir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
75 projectdir = os.getcwd()
76
77 if "BBDEBUG" in os.environ:
78     level = int(os.environ["BBDEBUG"])
79     if level:
80         bb.msg.set_debug_level(level)
81
82 class VarExpandError(Exception):
83     pass
84
85 class MalformedUrl(Exception):
86     """Exception raised when encountering an invalid url"""
87
88
89 #######################################################################
90 #######################################################################
91 #
92 # SECTION: Debug
93 #
94 # PURPOSE: little functions to make yourself known
95 #
96 #######################################################################
97 #######################################################################
98
99 def plain(*args):
100     bb.msg.plain(''.join(args))
101
102 def debug(lvl, *args):
103     bb.msg.debug(lvl, None, ''.join(args))
104
105 def note(*args):
106     bb.msg.note(1, None, ''.join(args))
107
108 def warn(*args):
109     bb.msg.warn(None, ''.join(args))
110
111 def error(*args):
112     bb.msg.error(None, ''.join(args))
113
114 def fatal(*args):
115     bb.msg.fatal(None, ''.join(args))
116
117
118 #######################################################################
119 #######################################################################
120 #
121 # SECTION: File
122 #
123 # PURPOSE: Basic file and directory tree related functions
124 #
125 #######################################################################
126 #######################################################################
127
128 def mkdirhier(dir):
129     """Create a directory like 'mkdir -p', but does not complain if
130     directory already exists like os.makedirs
131     """
132
133     debug(3, "mkdirhier(%s)" % dir)
134     try:
135         os.makedirs(dir)
136         debug(2, "created " + dir)
137     except OSError, e:
138         if e.errno != 17: raise e
139
140
141 #######################################################################
142
143 import stat
144
145 def movefile(src,dest,newmtime=None,sstat=None):
146     """Moves a file from src to dest, preserving all permissions and
147     attributes; mtime will be preserved even when moving across
148     filesystems.  Returns true on success and false on failure. Move is
149     atomic.
150     """
151
152     #print "movefile("+src+","+dest+","+str(newmtime)+","+str(sstat)+")"
153     try:
154         if not sstat:
155             sstat=os.lstat(src)
156     except Exception, e:
157         print "movefile: Stating source file failed...", e
158         return None
159
160     destexists=1
161     try:
162         dstat=os.lstat(dest)
163     except:
164         dstat=os.lstat(os.path.dirname(dest))
165         destexists=0
166
167     if destexists:
168         if stat.S_ISLNK(dstat[stat.ST_MODE]):
169             try:
170                 os.unlink(dest)
171                 destexists=0
172             except Exception, e:
173                 pass
174
175     if stat.S_ISLNK(sstat[stat.ST_MODE]):
176         try:
177             target=os.readlink(src)
178             if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
179                 os.unlink(dest)
180             os.symlink(target,dest)
181             #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
182             os.unlink(src)
183             return os.lstat(dest)
184         except Exception, e:
185             print "movefile: failed to properly create symlink:", dest, "->", target, e
186             return None
187
188     renamefailed=1
189     if sstat[stat.ST_DEV]==dstat[stat.ST_DEV]:
190         try:
191             ret=os.rename(src,dest)
192             renamefailed=0
193         except Exception, e:
194             import errno
195             if e[0]!=errno.EXDEV:
196                 # Some random error.
197                 print "movefile: Failed to move", src, "to", dest, e
198                 return None
199             # Invalid cross-device-link 'bind' mounted or actually Cross-Device
200
201     if renamefailed:
202         didcopy=0
203         if stat.S_ISREG(sstat[stat.ST_MODE]):
204             try: # For safety copy then move it over.
205                 shutil.copyfile(src,dest+"#new")
206                 os.rename(dest+"#new",dest)
207                 didcopy=1
208             except Exception, e:
209                 print 'movefile: copy', src, '->', dest, 'failed.', e
210                 return None
211         else:
212             #we don't yet handle special, so we need to fall back to /bin/mv
213             a=getstatusoutput("/bin/mv -f "+"'"+src+"' '"+dest+"'")
214             if a[0]!=0:
215                 print "movefile: Failed to move special file:" + src + "' to '" + dest + "'", a
216                 return None # failure
217         try:
218             if didcopy:
219                 missingos.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
220                 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
221                 os.unlink(src)
222         except Exception, e:
223             print "movefile: Failed to chown/chmod/unlink", dest, e
224             return None
225
226     if newmtime:
227         os.utime(dest,(newmtime,newmtime))
228     else:
229         os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
230         newmtime=sstat[stat.ST_MTIME]
231     return newmtime
232
233 def copyfile(src,dest,newmtime=None,sstat=None):
234     """
235     Copies a file from src to dest, preserving all permissions and
236     attributes; mtime will be preserved even when moving across
237     filesystems.  Returns true on success and false on failure.
238     """
239     import os, stat, shutil
240
241     #print "copyfile("+src+","+dest+","+str(newmtime)+","+str(sstat)+")"
242     try:
243         if not sstat:
244             sstat=os.lstat(src)
245     except Exception, e:
246         print "copyfile: Stating source file failed...", e
247         return False
248
249     destexists=1
250     try:
251         dstat=os.lstat(dest)
252     except:
253         dstat=os.lstat(os.path.dirname(dest))
254         destexists=0
255
256     if destexists:
257         if stat.S_ISLNK(dstat[stat.ST_MODE]):
258             try:
259                 os.unlink(dest)
260                 destexists=0
261             except Exception, e:
262                 pass
263
264     if stat.S_ISLNK(sstat[stat.ST_MODE]):
265         try:
266             target=os.readlink(src)
267             if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
268                 os.unlink(dest)
269             os.symlink(target,dest)
270             #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
271             return os.lstat(dest)
272         except Exception, e:
273             print "copyfile: failed to properly create symlink:", dest, "->", target, e
274             return False
275
276     if stat.S_ISREG(sstat[stat.ST_MODE]):
277             try: # For safety copy then move it over.
278                 shutil.copyfile(src,dest+"#new")
279                 os.rename(dest+"#new",dest)
280             except Exception, e:
281                 print 'copyfile: copy', src, '->', dest, 'failed.', e
282                 return False
283     else:
284             #we don't yet handle special, so we need to fall back to /bin/mv
285             a=getstatusoutput("/bin/cp -f "+"'"+src+"' '"+dest+"'")
286             if a[0]!=0:
287                 print "copyfile: Failed to copy special file:" + src + "' to '" + dest + "'", a
288                 return False # failure
289     try:
290         os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
291         os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
292     except Exception, e:
293         print "copyfile: Failed to chown/chmod/unlink", dest, e
294         return False
295
296     if newmtime:
297         os.utime(dest,(newmtime,newmtime))
298     else:
299         os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
300         newmtime=sstat[stat.ST_MTIME]
301     return newmtime
302
303 #######################################################################
304 #######################################################################
305 #
306 # SECTION: Download
307 #
308 # PURPOSE: Download via HTTP, FTP, CVS, BITKEEPER, handling of MD5-signatures
309 #          and mirrors
310 #
311 #######################################################################
312 #######################################################################
313
314 def decodeurl(url):
315     """Decodes an URL into the tokens (scheme, network location, path,
316     user, password, parameters).
317
318     >>> decodeurl("http://www.google.com/index.html")
319     ('http', 'www.google.com', '/index.html', '', '', {})
320
321     CVS url with username, host and cvsroot. The cvs module to check out is in the
322     parameters:
323
324     >>> decodeurl("cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg")
325     ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'})
326
327     Dito, but this time the username has a password part. And we also request a special tag
328     to check out.
329
330     >>> decodeurl("cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;module=familiar/dist/ipkg;tag=V0-99-81")
331     ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'})
332     """
333
334     m = re.compile('(?P<type>[^:]*)://((?P<user>.+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
335     if not m:
336         raise MalformedUrl(url)
337
338     type = m.group('type')
339     location = m.group('location')
340     if not location:
341         raise MalformedUrl(url)
342     user = m.group('user')
343     parm = m.group('parm')
344
345     locidx = location.find('/')
346     if locidx != -1:
347         host = location[:locidx]
348         path = location[locidx:]
349     else:
350         host = ""
351         path = location
352     if user:
353         m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
354         if m:
355             user = m.group('user')
356             pswd = m.group('pswd')
357     else:
358         user = ''
359         pswd = ''
360
361     p = {}
362     if parm:
363         for s in parm.split(';'):
364             s1,s2 = s.split('=')
365             p[s1] = s2
366
367     return (type, host, path, user, pswd, p)
368
369 #######################################################################
370
371 def encodeurl(decoded):
372     """Encodes a URL from tokens (scheme, network location, path,
373     user, password, parameters).
374
375     >>> encodeurl(['http', 'www.google.com', '/index.html', '', '', {}])
376     'http://www.google.com/index.html'
377
378     CVS with username, host and cvsroot. The cvs module to check out is in the
379     parameters:
380
381     >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'}])
382     'cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg'
383
384     Dito, but this time the username has a password part. And we also request a special tag
385     to check out.
386
387     >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'}])
388     'cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg'
389     """
390
391     (type, host, path, user, pswd, p) = decoded
392
393     if not type or not path:
394         fatal("invalid or missing parameters for url encoding")
395     url = '%s://' % type
396     if user:
397         url += "%s" % user
398         if pswd:
399             url += ":%s" % pswd
400         url += "@"
401     if host:
402         url += "%s" % host
403     url += "%s" % path
404     if p:
405         for parm in p.keys():
406             url += ";%s=%s" % (parm, p[parm])
407
408     return url
409
410 #######################################################################
411
412 def which(path, item, direction = 0):
413     """
414     Locate a file in a PATH
415     """
416
417     paths = (path or "").split(':')
418     if direction != 0:
419         paths.reverse()
420
421     for p in (path or "").split(':'):
422         next = os.path.join(p, item)
423         if os.path.exists(next):
424             return next
425
426     return ""
427
428 #######################################################################
429
430
431
432
433 #######################################################################
434 #######################################################################
435 #
436 # SECTION: Dependency
437 #
438 # PURPOSE: Compare build & run dependencies
439 #
440 #######################################################################
441 #######################################################################
442
443 def tokenize(mystring):
444     """Breaks a string like 'foo? (bar) oni? (blah (blah))' into (possibly embedded) lists:
445
446     >>> tokenize("x")
447     ['x']
448     >>> tokenize("x y")
449     ['x', 'y']
450     >>> tokenize("(x y)")
451     [['x', 'y']]
452     >>> tokenize("(x y) b c")
453     [['x', 'y'], 'b', 'c']
454     >>> tokenize("foo? (bar) oni? (blah (blah))")
455     ['foo?', ['bar'], 'oni?', ['blah', ['blah']]]
456     >>> tokenize("sys-apps/linux-headers nls? (sys-devel/gettext)")
457     ['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']]
458     """
459
460     newtokens = []
461     curlist   = newtokens
462     prevlists = []
463     level     = 0
464     accum     = ""
465     for x in mystring:
466         if x=="(":
467             if accum:
468                 curlist.append(accum)
469                 accum=""
470             prevlists.append(curlist)
471             curlist=[]
472             level=level+1
473         elif x==")":
474             if accum:
475                 curlist.append(accum)
476                 accum=""
477             if level==0:
478                 print "!!! tokenizer: Unmatched left parenthesis in:\n'"+mystring+"'"
479                 return None
480             newlist=curlist
481             curlist=prevlists.pop()
482             curlist.append(newlist)
483             level=level-1
484         elif x in whitespace:
485             if accum:
486                 curlist.append(accum)
487                 accum=""
488         else:
489             accum=accum+x
490     if accum:
491         curlist.append(accum)
492     if (level!=0):
493         print "!!! tokenizer: Exiting with unterminated parenthesis in:\n'"+mystring+"'"
494         return None
495     return newtokens
496
497
498 #######################################################################
499
500 def evaluate(tokens,mydefines,allon=0):
501     """Removes tokens based on whether conditional definitions exist or not.
502     Recognizes !
503
504     >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {})
505     ['sys-apps/linux-headers']
506
507     Negate the flag:
508
509     >>> evaluate(['sys-apps/linux-headers', '!nls?', ['sys-devel/gettext']], {})
510     ['sys-apps/linux-headers', ['sys-devel/gettext']]
511
512     Define 'nls':
513
514     >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {"nls":1})
515     ['sys-apps/linux-headers', ['sys-devel/gettext']]
516
517     Turn allon on:
518
519     >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {}, True)
520     ['sys-apps/linux-headers', ['sys-devel/gettext']]
521     """
522
523     if tokens == None:
524         return None
525     mytokens = tokens + []        # this copies the list
526     pos = 0
527     while pos < len(mytokens):
528         if type(mytokens[pos]) == types.ListType:
529             evaluate(mytokens[pos], mydefines)
530             if not len(mytokens[pos]):
531                 del mytokens[pos]
532                 continue
533         elif mytokens[pos][-1] == "?":
534             cur = mytokens[pos][:-1]
535             del mytokens[pos]
536             if allon:
537                 if cur[0] == "!":
538                     del mytokens[pos]
539             else:
540                 if cur[0] == "!":
541                     if (cur[1:] in mydefines) and (pos < len(mytokens)):
542                         del mytokens[pos]
543                         continue
544                 elif (cur not in mydefines) and (pos < len(mytokens)):
545                     del mytokens[pos]
546                     continue
547         pos = pos + 1
548     return mytokens
549
550
551 #######################################################################
552
553 def flatten(mytokens):
554     """Converts nested arrays into a flat arrays:
555
556     >>> flatten([1,[2,3]])
557     [1, 2, 3]
558     >>> flatten(['sys-apps/linux-headers', ['sys-devel/gettext']])
559     ['sys-apps/linux-headers', 'sys-devel/gettext']
560     """
561
562     newlist=[]
563     for x in mytokens:
564         if type(x)==types.ListType:
565             newlist.extend(flatten(x))
566         else:
567             newlist.append(x)
568     return newlist
569
570
571 #######################################################################
572
573 _package_weights_ = {"pre":-2,"p":0,"alpha":-4,"beta":-3,"rc":-1}    # dicts are unordered
574 _package_ends_    = ["pre", "p", "alpha", "beta", "rc", "cvs", "bk", "HEAD" ]            # so we need ordered list
575
576 def relparse(myver):
577     """Parses the last elements of a version number into a triplet, that can
578     later be compared:
579
580     >>> relparse('1.2_pre3')
581     [1.2, -2, 3.0]
582     >>> relparse('1.2b')
583     [1.2, 98, 0]
584     >>> relparse('1.2')
585     [1.2, 0, 0]
586     """
587
588     number   = 0
589     p1       = 0
590     p2       = 0
591     mynewver = myver.split('_')
592     if len(mynewver)==2:
593         # an _package_weights_
594         number = float(mynewver[0])
595         match = 0
596         for x in _package_ends_:
597             elen = len(x)
598             if mynewver[1][:elen] == x:
599                 match = 1
600                 p1 = _package_weights_[x]
601                 try:
602                     p2 = float(mynewver[1][elen:])
603                 except:
604                     p2 = 0
605                 break
606         if not match:
607             # normal number or number with letter at end
608             divider = len(myver)-1
609             if myver[divider:] not in "1234567890":
610                 # letter at end
611                 p1 = ord(myver[divider:])
612                 number = float(myver[0:divider])
613             else:
614                 number = float(myver)
615     else:
616         # normal number or number with letter at end
617         divider = len(myver)-1
618         if myver[divider:] not in "1234567890":
619             #letter at end
620             p1     = ord(myver[divider:])
621             number = float(myver[0:divider])
622         else:
623             number = float(myver)
624     return [number,p1,p2]
625
626
627 #######################################################################
628
629 __ververify_cache__ = {}
630
631 def ververify(myorigval,silent=1):
632     """Returns 1 if given a valid version string, els 0. Valid versions are in the format
633
634     <v1>.<v2>...<vx>[a-z,_{_package_weights_}[vy]]
635
636     >>> ververify('2.4.20')
637     1
638     >>> ververify('2.4..20')        # two dots
639     0
640     >>> ververify('2.x.20')            # 'x' is not numeric
641     0
642     >>> ververify('2.4.20a')
643     1
644     >>> ververify('2.4.20cvs')        # only one trailing letter
645     0
646     >>> ververify('1a')
647     1
648     >>> ververify('test_a')            # no version at all
649     0
650     >>> ververify('2.4.20_beta1')
651     1
652     >>> ververify('2.4.20_beta')
653     1
654     >>> ververify('2.4.20_wrongext')    # _wrongext is no valid trailer
655     0
656     """
657
658     # Lookup the cache first
659     try:
660         return __ververify_cache__[myorigval]
661     except KeyError:
662         pass
663
664     if len(myorigval) == 0:
665         if not silent:
666             error("package version is empty")
667         __ververify_cache__[myorigval] = 0
668         return 0
669     myval = myorigval.split('.')
670     if len(myval)==0:
671         if not silent:
672             error("package name has empty version string")
673         __ververify_cache__[myorigval] = 0
674         return 0
675     # all but the last version must be a numeric
676     for x in myval[:-1]:
677         if not len(x):
678             if not silent:
679                 error("package version has two points in a row")
680             __ververify_cache__[myorigval] = 0
681             return 0
682         try:
683             foo = int(x)
684         except:
685             if not silent:
686                 error("package version contains non-numeric '"+x+"'")
687             __ververify_cache__[myorigval] = 0
688             return 0
689     if not len(myval[-1]):
690             if not silent:
691                 error("package version has trailing dot")
692             __ververify_cache__[myorigval] = 0
693             return 0
694     try:
695         foo = int(myval[-1])
696         __ververify_cache__[myorigval] = 1
697         return 1
698     except:
699         pass
700
701     # ok, our last component is not a plain number or blank, let's continue
702     if myval[-1][-1] in lowercase:
703         try:
704             foo = int(myval[-1][:-1])
705             return 1
706             __ververify_cache__[myorigval] = 1
707             # 1a, 2.0b, etc.
708         except:
709             pass
710     # ok, maybe we have a 1_alpha or 1_beta2; let's see
711     ep=string.split(myval[-1],"_")
712     if len(ep)!= 2:
713         if not silent:
714             error("package version has more than one letter at then end")
715         __ververify_cache__[myorigval] = 0
716         return 0
717     try:
718         foo = string.atoi(ep[0])
719     except:
720         # this needs to be numeric, i.e. the "1" in "1_alpha"
721         if not silent:
722             error("package version must have numeric part before the '_'")
723         __ververify_cache__[myorigval] = 0
724         return 0
725
726     for mye in _package_ends_:
727         if ep[1][0:len(mye)] == mye:
728             if len(mye) == len(ep[1]):
729                 # no trailing numeric is ok
730                 __ververify_cache__[myorigval] = 1
731                 return 1
732             else:
733                 try:
734                     foo = string.atoi(ep[1][len(mye):])
735                     __ververify_cache__[myorigval] = 1
736                     return 1
737                 except:
738                     # if no _package_weights_ work, *then* we return 0
739                     pass
740     if not silent:
741         error("package version extension after '_' is invalid")
742     __ververify_cache__[myorigval] = 0
743     return 0
744
745
746 def isjustname(mypkg):
747     myparts = string.split(mypkg,'-')
748     for x in myparts:
749         if ververify(x):
750             return 0
751     return 1
752
753
754 _isspecific_cache_={}
755
756 def isspecific(mypkg):
757     "now supports packages with no category"
758     try:
759         return __isspecific_cache__[mypkg]
760     except:
761         pass
762
763     mysplit = string.split(mypkg,"/")
764     if not isjustname(mysplit[-1]):
765             __isspecific_cache__[mypkg] = 1
766             return 1
767     __isspecific_cache__[mypkg] = 0
768     return 0
769
770
771 #######################################################################
772
773 __pkgsplit_cache__={}
774
775 def pkgsplit(mypkg, silent=1):
776
777     """This function can be used as a package verification function. If
778     it is a valid name, pkgsplit will return a list containing:
779     [pkgname, pkgversion(norev), pkgrev ].
780
781     >>> pkgsplit('')
782     >>> pkgsplit('x')
783     >>> pkgsplit('x-')
784     >>> pkgsplit('-1')
785     >>> pkgsplit('glibc-1.2-8.9-r7')
786     >>> pkgsplit('glibc-2.2.5-r7')
787     ['glibc', '2.2.5', 'r7']
788     >>> pkgsplit('foo-1.2-1')
789     >>> pkgsplit('Mesa-3.0')
790     ['Mesa', '3.0', 'r0']
791     """
792
793     try:
794         return __pkgsplit_cache__[mypkg]
795     except KeyError:
796         pass
797
798     myparts = string.split(mypkg,'-')
799     if len(myparts) < 2:
800         if not silent:
801             error("package name without name or version part")
802         __pkgsplit_cache__[mypkg] = None
803         return None
804     for x in myparts:
805         if len(x) == 0:
806             if not silent:
807                 error("package name with empty name or version part")
808             __pkgsplit_cache__[mypkg] = None
809             return None
810     # verify rev
811     revok = 0
812     myrev = myparts[-1]
813     ververify(myrev, silent)
814     if len(myrev) and myrev[0] == "r":
815         try:
816             string.atoi(myrev[1:])
817             revok = 1
818         except:
819             pass
820     if revok:
821         if ververify(myparts[-2]):
822             if len(myparts) == 2:
823                 __pkgsplit_cache__[mypkg] = None
824                 return None
825             else:
826                 for x in myparts[:-2]:
827                     if ververify(x):
828                         __pkgsplit_cache__[mypkg]=None
829                         return None
830                         # names can't have versiony looking parts
831                 myval=[string.join(myparts[:-2],"-"),myparts[-2],myparts[-1]]
832                 __pkgsplit_cache__[mypkg]=myval
833                 return myval
834         else:
835             __pkgsplit_cache__[mypkg] = None
836             return None
837
838     elif ververify(myparts[-1],silent):
839         if len(myparts)==1:
840             if not silent:
841                 print "!!! Name error in",mypkg+": missing name part."
842             __pkgsplit_cache__[mypkg]=None
843             return None
844         else:
845             for x in myparts[:-1]:
846                 if ververify(x):
847                     if not silent: error("package name has multiple version parts")
848                     __pkgsplit_cache__[mypkg] = None
849                     return None
850             myval = [string.join(myparts[:-1],"-"), myparts[-1],"r0"]
851             __pkgsplit_cache__[mypkg] = myval
852             return myval
853     else:
854         __pkgsplit_cache__[mypkg] = None
855         return None
856
857
858 #######################################################################
859
860 __catpkgsplit_cache__ = {}
861
862 def catpkgsplit(mydata,silent=1):
863     """returns [cat, pkgname, version, rev ]
864
865     >>> catpkgsplit('sys-libs/glibc-1.2-r7')
866     ['sys-libs', 'glibc', '1.2', 'r7']
867     >>> catpkgsplit('glibc-1.2-r7')
868     [None, 'glibc', '1.2', 'r7']
869     """
870
871     try:
872         return __catpkgsplit_cache__[mydata]
873     except KeyError:
874         pass
875
876     cat = os.path.basename(os.path.dirname(mydata))
877     mydata = os.path.join(cat, os.path.basename(mydata))
878     if mydata[-3:] == '.bb':
879         mydata = mydata[:-3]
880
881     mysplit = mydata.split("/")
882     p_split = None
883     splitlen = len(mysplit)
884     if splitlen == 1:
885         retval = [None]
886         p_split = pkgsplit(mydata,silent)
887     else:
888         retval = [mysplit[splitlen - 2]]
889         p_split = pkgsplit(mysplit[splitlen - 1],silent)
890     if not p_split:
891         __catpkgsplit_cache__[mydata] = None
892         return None
893     retval.extend(p_split)
894     __catpkgsplit_cache__[mydata] = retval
895     return retval
896
897
898 #######################################################################
899
900 __vercmp_cache__ = {}
901
902 def vercmp(val1,val2):
903     """This takes two version strings and returns an integer to tell you whether
904     the versions are the same, val1>val2 or val2>val1.
905
906     >>> vercmp('1', '2')
907     -1.0
908     >>> vercmp('2', '1')
909     1.0
910     >>> vercmp('1', '1.0')
911     0
912     >>> vercmp('1', '1.1')
913     -1.0
914     >>> vercmp('1.1', '1_p2')
915     1.0
916     """
917
918     # quick short-circuit
919     if val1 == val2:
920         return 0
921     valkey = val1+" "+val2
922
923     # cache lookup
924     try:
925         return __vercmp_cache__[valkey]
926         try:
927             return - __vercmp_cache__[val2+" "+val1]
928         except KeyError:
929             pass
930     except KeyError:
931         pass
932
933     # consider 1_p2 vc 1.1
934     # after expansion will become (1_p2,0) vc (1,1)
935     # then 1_p2 is compared with 1 before 0 is compared with 1
936     # to solve the bug we need to convert it to (1,0_p2)
937     # by splitting _prepart part and adding it back _after_expansion
938
939     val1_prepart = val2_prepart = ''
940     if val1.count('_'):
941         val1, val1_prepart = val1.split('_', 1)
942     if val2.count('_'):
943         val2, val2_prepart = val2.split('_', 1)
944
945     # replace '-' by '.'
946     # FIXME: Is it needed? can val1/2 contain '-'?
947
948     val1 = string.split(val1,'-')
949     if len(val1) == 2:
950         val1[0] = val1[0] +"."+ val1[1]
951     val2 = string.split(val2,'-')
952     if len(val2) == 2:
953         val2[0] = val2[0] +"."+ val2[1]
954
955     val1 = string.split(val1[0],'.')
956     val2 = string.split(val2[0],'.')
957
958     # add back decimal point so that .03 does not become "3" !
959     for x in range(1,len(val1)):
960         if val1[x][0] == '0' :
961             val1[x] = '.' + val1[x]
962     for x in range(1,len(val2)):
963         if val2[x][0] == '0' :
964             val2[x] = '.' + val2[x]
965
966     # extend varion numbers
967     if len(val2) < len(val1):
968         val2.extend(["0"]*(len(val1)-len(val2)))
969     elif len(val1) < len(val2):
970         val1.extend(["0"]*(len(val2)-len(val1)))
971
972     # add back _prepart tails
973     if val1_prepart:
974         val1[-1] += '_' + val1_prepart
975     if val2_prepart:
976         val2[-1] += '_' + val2_prepart
977     # The above code will extend version numbers out so they
978     # have the same number of digits.
979     for x in range(0,len(val1)):
980         cmp1 = relparse(val1[x])
981         cmp2 = relparse(val2[x])
982         for y in range(0,3):
983             myret = cmp1[y] - cmp2[y]
984             if myret != 0:
985                 __vercmp_cache__[valkey] = myret
986                 return myret
987     __vercmp_cache__[valkey] = 0
988     return 0
989
990
991 #######################################################################
992
993 def pkgcmp(pkg1,pkg2):
994     """ Compares two packages, which should have been split via
995     pkgsplit(). if the return value val is less than zero, then pkg2 is
996     newer than pkg1, zero if equal and positive if older.
997
998     >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r7'])
999     0
1000     >>> pkgcmp(['glibc', '2.2.5', 'r4'], ['glibc', '2.2.5', 'r7'])
1001     -1
1002     >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r2'])
1003     1
1004     """
1005
1006     mycmp = vercmp(pkg1[1],pkg2[1])
1007     if mycmp > 0:
1008         return 1
1009     if mycmp < 0:
1010         return -1
1011     r1=string.atoi(pkg1[2][1:])
1012     r2=string.atoi(pkg2[2][1:])
1013     if r1 > r2:
1014         return 1
1015     if r2 > r1:
1016         return -1
1017     return 0
1018
1019
1020 #######################################################################
1021
1022 def dep_parenreduce(mysplit, mypos=0):
1023     """Accepts a list of strings, and converts '(' and ')' surrounded items to sub-lists:
1024
1025     >>> dep_parenreduce([''])
1026     ['']
1027     >>> dep_parenreduce(['1', '2', '3'])
1028     ['1', '2', '3']
1029     >>> dep_parenreduce(['1', '(', '2', '3', ')', '4'])
1030     ['1', ['2', '3'], '4']
1031     """
1032
1033     while mypos < len(mysplit):
1034         if mysplit[mypos] == "(":
1035             firstpos = mypos
1036             mypos = mypos + 1
1037             while mypos < len(mysplit):
1038                 if mysplit[mypos] == ")":
1039                     mysplit[firstpos:mypos+1] = [mysplit[firstpos+1:mypos]]
1040                     mypos = firstpos
1041                     break
1042                 elif mysplit[mypos] == "(":
1043                     # recurse
1044                     mysplit = dep_parenreduce(mysplit,mypos)
1045                 mypos = mypos + 1
1046         mypos = mypos + 1
1047     return mysplit
1048
1049
1050 def dep_opconvert(mysplit, myuse):
1051     "Does dependency operator conversion"
1052
1053     mypos   = 0
1054     newsplit = []
1055     while mypos < len(mysplit):
1056         if type(mysplit[mypos]) == types.ListType:
1057             newsplit.append(dep_opconvert(mysplit[mypos],myuse))
1058             mypos += 1
1059         elif mysplit[mypos] == ")":
1060             # mismatched paren, error
1061             return None
1062         elif mysplit[mypos]=="||":
1063             if ((mypos+1)>=len(mysplit)) or (type(mysplit[mypos+1])!=types.ListType):
1064                 # || must be followed by paren'd list
1065                 return None
1066             try:
1067                 mynew = dep_opconvert(mysplit[mypos+1],myuse)
1068             except Exception, e:
1069                 error("unable to satisfy OR dependancy: " + string.join(mysplit," || "))
1070                 raise e
1071             mynew[0:0] = ["||"]
1072             newsplit.append(mynew)
1073             mypos += 2
1074         elif mysplit[mypos][-1] == "?":
1075             # use clause, i.e "gnome? ( foo bar )"
1076             # this is a quick and dirty hack so that repoman can enable all USE vars:
1077             if (len(myuse) == 1) and (myuse[0] == "*"):
1078                 # enable it even if it's ! (for repoman) but kill it if it's
1079                 # an arch variable that isn't for this arch. XXX Sparc64?
1080                 if (mysplit[mypos][:-1] not in settings.usemask) or \
1081                         (mysplit[mypos][:-1]==settings["ARCH"]):
1082                     enabled=1
1083                 else:
1084                     enabled=0
1085             else:
1086                 if mysplit[mypos][0] == "!":
1087                     myusevar = mysplit[mypos][1:-1]
1088                     enabled = not myusevar in myuse
1089                     #if myusevar in myuse:
1090                     #    enabled = 0
1091                     #else:
1092                     #    enabled = 1
1093                 else:
1094                     myusevar=mysplit[mypos][:-1]
1095                     enabled = myusevar in myuse
1096                     #if myusevar in myuse:
1097                     #    enabled=1
1098                     #else:
1099                     #    enabled=0
1100             if (mypos +2 < len(mysplit)) and (mysplit[mypos+2] == ":"):
1101                 # colon mode
1102                 if enabled:
1103                     # choose the first option
1104                     if type(mysplit[mypos+1]) == types.ListType:
1105                         newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
1106                     else:
1107                         newsplit.append(mysplit[mypos+1])
1108                 else:
1109                     # choose the alternate option
1110                     if type(mysplit[mypos+1]) == types.ListType:
1111                         newsplit.append(dep_opconvert(mysplit[mypos+3],myuse))
1112                     else:
1113                         newsplit.append(mysplit[mypos+3])
1114                 mypos += 4
1115             else:
1116                 # normal use mode
1117                 if enabled:
1118                     if type(mysplit[mypos+1]) == types.ListType:
1119                         newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
1120                     else:
1121                         newsplit.append(mysplit[mypos+1])
1122                 # otherwise, continue
1123                 mypos += 2
1124         else:
1125             # normal item
1126             newsplit.append(mysplit[mypos])
1127             mypos += 1
1128     return newsplit
1129
1130 if __name__ == "__main__":
1131     import doctest, bb
1132     bb.msg.set_debug_level(0)
1133     doctest.testmod(bb)