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