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