2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5 BitBake Build System Python Library
7 Copyright (C) 2003 Holger Schurig
8 Copyright (C) 2003, 2004 Chris Larson
10 Based on Gentoo's portage.py.
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
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.
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.
67 whitespace = '\t\n\x0b\x0c\r '
68 lowercase = 'abcdefghijklmnopqrstuvwxyz'
70 import sys, os, types, re
73 # Check for the Python version. A lot of stuff needs Python 2.3 or later
75 if sys.version_info[:3] < (2, 3, 0):
76 print "BitBake needs Python 2.3 or later. Please upgrade."
79 #projectdir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
80 projectdir = os.getcwd()
84 if "BBDEBUG" in os.environ:
85 level = int(os.environ["BBDEBUG"])
91 class VarExpandError(Exception):
94 class MalformedUrl(Exception):
95 """Exception raised when encountering an invalid url"""
98 #######################################################################
99 #######################################################################
103 # PURPOSE: little functions to make yourself known
105 #######################################################################
106 #######################################################################
111 def debug(lvl, *args):
112 if debug_level >= lvl:
113 print debug_prepend + 'DEBUG:', ''.join(args)
116 print debug_prepend + 'NOTE:', ''.join(args)
119 print debug_prepend + 'ERROR:', ''.join(args)
122 print debug_prepend + 'ERROR:', ''.join(args)
126 #######################################################################
127 #######################################################################
131 # PURPOSE: Basic file and directory tree related functions
133 #######################################################################
134 #######################################################################
137 """Create a directory like 'mkdir -p', but does not complain if
138 directory already exists like os.makedirs
141 debug(3, "mkdirhier(%s)" % dir)
144 debug(2, "created " + dir)
146 if e.errno != 17: raise e
149 #######################################################################
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
160 #print "movefile("+src+","+dest+","+str(newmtime)+","+str(sstat)+")"
165 print "!!! Stating source file failed... movefile()"
173 dstat=os.lstat(os.path.dirname(dest))
177 if stat.S_ISLNK(dstat[stat.ST_MODE]):
184 if stat.S_ISLNK(sstat[stat.ST_MODE]):
186 target=os.readlink(src)
187 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
189 os.symlink(target,dest)
190 # os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
192 return os.lstat(dest)
194 print "!!! failed to properly create symlink:"
195 print "!!!",dest,"->",target
200 if sstat[stat.ST_DEV]==dstat[stat.ST_DEV]:
202 ret=os.rename(src,dest)
206 if e[0]!=errno.EXDEV:
208 print "!!! Failed to move",src,"to",dest
211 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
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)
221 print '!!! copy',src,'->',dest,'failed.'
225 #we don't yet handle special, so we need to fall back to /bin/mv
226 a=getstatusoutput("/bin/mv -f "+"'"+src+"' '"+dest+"'")
228 print "!!! Failed to move special file:"
229 print "!!! '"+src+"' to '"+dest+"'"
231 return None # failure
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
238 print "!!! Failed to chown/chmod/unlink in movefile()"
244 os.utime(dest,(newmtime,newmtime))
246 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
247 newmtime=sstat[stat.ST_MTIME]
252 #######################################################################
253 #######################################################################
257 # PURPOSE: Download via HTTP, FTP, CVS, BITKEEPER, handling of MD5-signatures
260 #######################################################################
261 #######################################################################
264 """Decodes an URL into the tokens (scheme, network location, path,
265 user, password, parameters).
267 >>> decodeurl("http://www.google.com/index.html")
268 ('http', 'www.google.com', '/index.html', '', '', {})
270 CVS url with username, host and cvsroot. The cvs module to check out is in the
273 >>> decodeurl("cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg")
274 ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'})
276 Dito, but this time the username has a password part. And we also request a special tag
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'})
283 m = re.compile('(?P<type>[^:]*)://((?P<user>.+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
285 raise MalformedUrl(url)
287 type = m.group('type')
288 location = m.group('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)
295 host = m.group('host')
296 path = m.group('path')
301 m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
303 user = m.group('user')
304 pswd = m.group('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)
317 for s in parm.split(';'):
321 return (type, host, path, user, pswd, p)
323 #######################################################################
325 def encodeurl(decoded):
326 """Encodes a URL from tokens (scheme, network location, path,
327 user, password, parameters).
329 >>> encodeurl(['http', 'www.google.com', '/index.html', '', '', {}])
331 "http://www.google.com/index.html"
333 CVS with username, host and cvsroot. The cvs module to check out is in the
336 >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'}])
338 "cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg"
340 Dito, but this time the username has a password part. And we also request a special tag
343 >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'}])
345 "cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;module=familiar/dist/ipkg;tag=V0-99-81"
348 (type, host, path, user, pswd, p) = decoded
350 if not type or not path:
351 fatal("invalid or missing parameters for url encoding")
362 for parm in p.keys():
363 url += ";%s=%s" % (parm, p[parm])
367 #######################################################################
369 def which(path, item, direction = 0):
370 """Useful function for locating a file in a PATH"""
372 for p in (path or "").split(':'):
373 if os.path.exists(os.path.join(p, item)):
374 found = os.path.join(p, item)
379 #######################################################################
384 #######################################################################
385 #######################################################################
387 # SECTION: Dependency
389 # PURPOSE: Compare build & run dependencies
391 #######################################################################
392 #######################################################################
394 def tokenize(mystring):
395 """Breaks a string like 'foo? (bar) oni? (blah (blah))' into (possibly embedded) lists:
401 >>> tokenize("(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']]
419 curlist.append(accum)
421 prevlists.append(curlist)
426 curlist.append(accum)
429 print "!!! tokenizer: Unmatched left parenthesis in:\n'"+mystring+"'"
432 curlist=prevlists.pop()
433 curlist.append(newlist)
435 elif x in whitespace:
437 curlist.append(accum)
442 curlist.append(accum)
444 print "!!! tokenizer: Exiting with unterminated parenthesis in:\n'"+mystring+"'"
449 #######################################################################
451 def evaluate(tokens,mydefines,allon=0):
452 """Removes tokens based on whether conditional definitions exist or not.
455 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {})
456 ['sys-apps/linux-headers']
460 >>> evaluate(['sys-apps/linux-headers', '!nls?', ['sys-devel/gettext']], {})
461 ['sys-apps/linux-headers', ['sys-devel/gettext']]
465 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {"nls":1})
466 ['sys-apps/linux-headers', ['sys-devel/gettext']]
470 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {}, True)
471 ['sys-apps/linux-headers', ['sys-devel/gettext']]
476 mytokens = tokens + [] # this copies the list
478 while pos < len(mytokens):
479 if type(mytokens[pos]) == types.ListType:
480 evaluate(mytokens[pos], mydefines)
481 if not len(mytokens[pos]):
484 elif mytokens[pos][-1] == "?":
485 cur = mytokens[pos][:-1]
492 if (cur[1:] in mydefines) and (pos < len(mytokens)):
495 elif (cur not in mydefines) and (pos < len(mytokens)):
502 #######################################################################
504 def flatten(mytokens):
505 """Converts nested arrays into a flat arrays:
507 >>> flatten([1,[2,3]])
509 >>> flatten(['sys-apps/linux-headers', ['sys-devel/gettext']])
510 ['sys-apps/linux-headers', 'sys-devel/gettext']
515 if type(x)==types.ListType:
516 newlist.extend(flatten(x))
522 #######################################################################
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
528 """Parses the last elements of a version number into a triplet, that can
531 >>> relparse('1.2_pre3')
542 mynewver = myver.split('_')
544 # an _package_weights_
545 number = float(mynewver[0])
547 for x in _package_ends_:
549 if mynewver[1][:elen] == x:
551 p1 = _package_weights_[x]
553 p2 = float(mynewver[1][elen:])
558 # normal number or number with letter at end
559 divider = len(myver)-1
560 if myver[divider:] not in "1234567890":
562 p1 = ord(myver[divider:])
563 number = float(myver[0:divider])
565 number = float(myver)
567 # normal number or number with letter at end
568 divider = len(myver)-1
569 if myver[divider:] not in "1234567890":
571 p1 = ord(myver[divider:])
572 number = float(myver[0:divider])
574 number = float(myver)
575 return [number,p1,p2]
578 #######################################################################
580 __ververify_cache__ = {}
582 def ververify(myorigval,silent=1):
583 """Returns 1 if given a valid version string, els 0. Valid versions are in the format
585 <v1>.<v2>...<vx>[a-z,_{_package_weights_}[vy]]
587 >>> ververify('2.4.20')
589 >>> ververify('2.4..20') # two dots
591 >>> ververify('2.x.20') # 'x' is not numeric
593 >>> ververify('2.4.20a')
595 >>> ververify('2.4.20cvs') # only one trailing letter
599 >>> ververify('test_a') # no version at all
601 >>> ververify('2.4.20_beta1')
603 >>> ververify('2.4.20_beta')
605 >>> ververify('2.4.20_wrongext') # _wrongext is no valid trailer
609 # Lookup the cache first
611 return __ververify_cache__[myorigval]
615 if len(myorigval) == 0:
617 error("package version is empty")
618 __ververify_cache__[myorigval] = 0
620 myval = myorigval.split('.')
623 error("package name has empty version string")
624 __ververify_cache__[myorigval] = 0
626 # all but the last version must be a numeric
630 error("package version has two points in a row")
631 __ververify_cache__[myorigval] = 0
637 error("package version contains non-numeric '"+x+"'")
638 __ververify_cache__[myorigval] = 0
640 if not len(myval[-1]):
642 error("package version has trailing dot")
643 __ververify_cache__[myorigval] = 0
647 __ververify_cache__[myorigval] = 1
652 # ok, our last component is not a plain number or blank, let's continue
653 if myval[-1][-1] in lowercase:
655 foo = int(myval[-1][:-1])
657 __ververify_cache__[myorigval] = 1
661 # ok, maybe we have a 1_alpha or 1_beta2; let's see
662 ep=string.split(myval[-1],"_")
665 error("package version has more than one letter at then end")
666 __ververify_cache__[myorigval] = 0
669 foo = string.atoi(ep[0])
671 # this needs to be numeric, i.e. the "1" in "1_alpha"
673 error("package version must have numeric part before the '_'")
674 __ververify_cache__[myorigval] = 0
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
685 foo = string.atoi(ep[1][len(mye):])
686 __ververify_cache__[myorigval] = 1
689 # if no _package_weights_ work, *then* we return 0
692 error("package version extension after '_' is invalid")
693 __ververify_cache__[myorigval] = 0
697 def isjustname(mypkg):
698 myparts = string.split(mypkg,'-')
705 _isspecific_cache_={}
707 def isspecific(mypkg):
708 "now supports packages with no category"
710 return __isspecific_cache__[mypkg]
714 mysplit = string.split(mypkg,"/")
715 if not isjustname(mysplit[-1]):
716 __isspecific_cache__[mypkg] = 1
718 __isspecific_cache__[mypkg] = 0
722 #######################################################################
724 __pkgsplit_cache__={}
726 def pkgsplit(mypkg, silent=1):
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 ].
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']
745 return __pkgsplit_cache__[mypkg]
749 myparts = string.split(mypkg,'-')
752 error("package name without name or version part")
753 __pkgsplit_cache__[mypkg] = None
758 error("package name with empty name or version part")
759 __pkgsplit_cache__[mypkg] = None
765 if len(myrev) and myrev[0] == "r":
767 string.atoi(myrev[1:])
772 if ververify(myparts[-2]):
773 if len(myparts) == 2:
774 __pkgsplit_cache__[mypkg] = None
777 for x in myparts[:-2]:
779 __pkgsplit_cache__[mypkg]=None
781 # names can't have versiony looking parts
782 myval=[string.join(myparts[:-2],"-"),myparts[-2],myparts[-1]]
783 __pkgsplit_cache__[mypkg]=myval
786 __pkgsplit_cache__[mypkg] = None
789 elif ververify(myparts[-1],silent):
792 print "!!! Name error in",mypkg+": missing name part."
793 __pkgsplit_cache__[mypkg]=None
796 for x in myparts[:-1]:
798 if not silent: error("package name has multiple version parts")
799 __pkgsplit_cache__[mypkg] = None
801 myval = [string.join(myparts[:-1],"-"), myparts[-1],"r0"]
802 __pkgsplit_cache__[mypkg] = myval
805 __pkgsplit_cache__[mypkg] = None
809 #######################################################################
811 __catpkgsplit_cache__ = {}
813 def catpkgsplit(mydata,silent=1):
814 """returns [cat, pkgname, version, rev ]
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']
823 return __catpkgsplit_cache__[mydata]
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':
834 mysplit = mydata.split("/")
836 splitlen = len(mysplit)
839 p_split = pkgsplit(mydata,silent)
841 retval = [mysplit[splitlen - 2]]
842 p_split = pkgsplit(mysplit[splitlen - 1],silent)
844 __catpkgsplit_cache__[mydata] = None
846 retval.extend(p_split)
847 __catpkgsplit_cache__[mydata] = retval
851 #######################################################################
853 __vercmp_cache__ = {}
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.
863 >>> vercmp('1', '1.0')
865 >>> vercmp('1', '1.1')
867 >>> vercmp('1.1', '1_p2')
871 # quick short-circuit
874 valkey = val1+" "+val2
878 return __vercmp_cache__[valkey]
880 return - __vercmp_cache__[val2+" "+val1]
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
892 val1_prepart = val2_prepart = ''
894 val1, val1_prepart = val1.split('_', 1)
896 val2, val2_prepart = val2.split('_', 1)
899 # FIXME: Is it needed? can val1/2 contain '-'?
901 val1 = string.split(val1,'-')
903 val1[0] = val1[0] +"."+ val1[1]
904 val2 = string.split(val2,'-')
906 val2[0] = val2[0] +"."+ val2[1]
908 val1 = string.split(val1[0],'.')
909 val2 = string.split(val2[0],'.')
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]
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)))
925 # add back _prepart tails
927 val1[-1] += '_' + val1_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])
936 myret = cmp1[y] - cmp2[y]
938 __vercmp_cache__[valkey] = myret
940 __vercmp_cache__[valkey] = 0
944 #######################################################################
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.
951 >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r7'])
953 >>> pkgcmp(['glibc', '2.2.5', 'r4'], ['glibc', '2.2.5', 'r7'])
955 >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r2'])
959 mycmp = vercmp(pkg1[1],pkg2[1])
964 r1=string.atoi(pkg1[2][1:])
965 r2=string.atoi(pkg2[2][1:])
973 #######################################################################
975 def dep_parenreduce(mysplit, mypos=0):
976 """Accepts a list of strings, and converts '(' and ')' surrounded items to sub-lists:
978 >>> dep_parenreduce([''])
980 >>> dep_parenreduce(['1', '2', '3'])
982 >>> dep_parenreduce(['1', '(', '2', '3', ')', '4'])
983 ['1', ['2', '3'], '4']
986 while mypos < len(mysplit):
987 if mysplit[mypos] == "(":
990 while mypos < len(mysplit):
991 if mysplit[mypos] == ")":
992 mysplit[firstpos:mypos+1] = [mysplit[firstpos+1:mypos]]
995 elif mysplit[mypos] == "(":
997 mysplit = dep_parenreduce(mysplit,mypos)
1003 def dep_opconvert(mysplit, myuse):
1004 "Does dependency operator conversion"
1008 while mypos < len(mysplit):
1009 if type(mysplit[mypos]) == types.ListType:
1010 newsplit.append(dep_opconvert(mysplit[mypos],myuse))
1012 elif mysplit[mypos] == ")":
1013 # mismatched paren, error
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
1020 mynew = dep_opconvert(mysplit[mypos+1],myuse)
1021 except Exception, e:
1022 error("unable to satisfy OR dependancy: " + string.join(mysplit," || "))
1025 newsplit.append(mynew)
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"]):
1039 if mysplit[mypos][0] == "!":
1040 myusevar = mysplit[mypos][1:-1]
1041 enabled = not myusevar in myuse
1042 #if myusevar in myuse:
1047 myusevar=mysplit[mypos][:-1]
1048 enabled = myusevar in myuse
1049 #if myusevar in myuse:
1053 if (mypos +2 < len(mysplit)) and (mysplit[mypos+2] == ":"):
1056 # choose the first option
1057 if type(mysplit[mypos+1]) == types.ListType:
1058 newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
1060 newsplit.append(mysplit[mypos+1])
1062 # choose the alternate option
1063 if type(mysplit[mypos+1]) == types.ListType:
1064 newsplit.append(dep_opconvert(mysplit[mypos+3],myuse))
1066 newsplit.append(mysplit[mypos+3])
1071 if type(mysplit[mypos+1]) == types.ListType:
1072 newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
1074 newsplit.append(mysplit[mypos+1])
1075 # otherwise, continue
1079 newsplit.append(mysplit[mypos])
1084 """beautiful directed graph object"""
1088 #okeys = keys, in order they were added (to optimize firstzero() ordering)
1090 self.__callback_cache=[]
1094 for key in self.okeys:
1095 str += "%s:\t%s\n" % (key, self.dict[key][1])
1098 def addnode(self,mykey,myparent):
1099 if not mykey in self.dict:
1100 self.okeys.append(mykey)
1102 self.dict[mykey]=[0,[]]
1104 self.dict[mykey]=[0,[myparent]]
1105 self.dict[myparent][0]=self.dict[myparent][0]+1
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
1111 def delnode(self,mykey, ref = 1):
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:
1118 for x in self.dict[mykey][1]:
1119 self.dict[x][0]=self.dict[x][0]-1
1120 del self.dict[mykey]
1123 self.okeys.remove(mykey)
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)
1135 self.delnode(l, ref)
1138 "returns all nodes in the dictionary"
1139 return self.dict.keys()
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:
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:
1157 "returns all nodes with zero references, or NULL if no such node exists"
1159 for x in self.dict.keys():
1160 if self.dict[x][0]==0:
1164 def hasallzeros(self):
1165 "returns 0/1, Are all nodes zeros? 1 : 0"
1167 for x in self.dict.keys():
1168 if self.dict[x][0]!=0:
1173 if len(self.dict)==0:
1177 def hasnode(self,mynode):
1178 return mynode in self.dict
1180 def getparents(self, item):
1181 if not self.hasnode(item):
1183 return self.dict[item][1]
1185 def getchildren(self, item):
1186 if not self.hasnode(item):
1188 children = [i for i in self.okeys if item in self.getparents(i)]
1191 def walkdown(self, item, callback, debug = None, usecache = False):
1192 if not self.hasnode(item):
1196 if self.__callback_cache.count(item):
1198 print "hit cache for item: %s" % item
1201 parents = self.getparents(item)
1202 children = self.getchildren(item)
1205 # print "%s is both parent and child of %s" % (p, item)
1207 self.__callback_cache.append(p)
1208 ret = callback(self, p)
1213 print "eek, i'm my own parent!"
1216 print "item: %s, p: %s" % (item, p)
1217 ret = self.walkdown(p, callback, debug, usecache)
1221 self.__callback_cache.append(item)
1222 return callback(self, item)
1224 def walkup(self, item, callback):
1225 if not self.hasnode(item):
1228 parents = self.getparents(item)
1229 children = self.getchildren(item)
1232 ret = callback(self, item)
1237 print "eek, i'm my own child!"
1239 ret = self.walkup(c, callback)
1242 return callback(self, item)
1246 for x in self.dict.keys():
1247 mygraph.dict[x]=self.dict[x][:]
1248 mygraph.okeys=self.okeys[:]
1251 #######################################################################
1252 #######################################################################
1256 # PURPOSE: Reading and handling of system/target-specific/local configuration
1257 # reading of package configuration
1259 #######################################################################
1260 #######################################################################
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).
1268 f = open(cfgfile,'r')
1275 if not w: continue # skip empty lines
1277 if s[0] == '#': continue # skip comments
1278 while s[-1] == '\\':
1279 s2 = f.readline()[:-1].strip()
1283 if __name__ == "__main__":