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