summaryrefslogtreecommitdiffstats
path: root/lib/bb/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bb/__init__.py')
-rw-r--r--lib/bb/__init__.py1277
1 files changed, 1277 insertions, 0 deletions
diff --git a/lib/bb/__init__.py b/lib/bb/__init__.py
new file mode 100644
index 000000000..07a7f42b7
--- /dev/null
+++ b/lib/bb/__init__.py
@@ -0,0 +1,1277 @@
+#!/usr/bin/python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake Build System Python Library
+
+Copyright (C) 2003 Holger Schurig
+Copyright (C) 2003, 2004 Chris Larson
+
+Based on Gentoo's portage.py.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place, Suite 330, Boston, MA 02111-1307 USA.
+"""
+
+__version__ = "1.1"
+
+__all__ = [
+
+ "debug",
+ "note",
+ "error",
+ "fatal",
+
+ "mkdirhier",
+ "movefile",
+
+ "tokenize",
+ "evaluate",
+ "flatten",
+ "relparse",
+ "ververify",
+ "isjustname",
+ "isspecific",
+ "pkgsplit",
+ "catpkgsplit",
+ "vercmp",
+ "pkgcmp",
+ "dep_parenreduce",
+ "dep_opconvert",
+ "digraph",
+
+# fetch
+ "decodeurl",
+ "encodeurl",
+
+# modules
+ "parse",
+ "data",
+ "event",
+ "build",
+ "fetch",
+ "make",
+ "manifest"
+ ]
+
+whitespace = '\t\n\x0b\x0c\r '
+lowercase = 'abcdefghijklmnopqrstuvwxyz'
+
+import sys, os, types, re
+
+#
+# Check for the Python version. A lot of stuff needs Python 2.3 or later
+#
+if sys.version_info[:3] < (2, 3, 0):
+ print "BitBake needs Python 2.3 or later. Please upgrade."
+ sys.exit(-1)
+
+#projectdir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
+projectdir = os.getcwd()
+env = {}
+
+class VarExpandError(Exception):
+ pass
+
+class MalformedUrl(Exception):
+ """Exception raised when encountering an invalid url"""
+
+
+#######################################################################
+#######################################################################
+#
+# SECTION: Debug
+#
+# PURPOSE: little functions to make yourself known
+#
+#######################################################################
+#######################################################################
+
+debug_prepend = ''
+
+
+def debug(lvl, *args):
+ if 'BBDEBUG' in env and (env['BBDEBUG'] >= str(lvl)):
+ print debug_prepend + 'DEBUG:', ''.join(args)
+
+def note(*args):
+ print debug_prepend + 'NOTE:', ''.join(args)
+
+def error(*args):
+ print debug_prepend + 'ERROR:', ''.join(args)
+
+def fatal(*args):
+ print debug_prepend + 'ERROR:', ''.join(args)
+ sys.exit(1)
+
+
+#######################################################################
+#######################################################################
+#
+# SECTION: File
+#
+# PURPOSE: Basic file and directory tree related functions
+#
+#######################################################################
+#######################################################################
+
+def mkdirhier(dir):
+ """Create a directory like 'mkdir -p', but does not complain if
+ directory already exists like os.makedirs
+ """
+
+ debug(3, "mkdirhier(%s)" % dir)
+ try:
+ os.makedirs(dir)
+ debug(2, "created " + dir)
+ except OSError, e:
+ if e.errno != 17: raise e
+
+
+#######################################################################
+
+import stat
+
+def movefile(src,dest,newmtime=None,sstat=None):
+ """Moves a file from src to dest, preserving all permissions and
+ attributes; mtime will be preserved even when moving across
+ filesystems. Returns true on success and false on failure. Move is
+ atomic.
+ """
+
+ #print "movefile("+src+","+dest+","+str(newmtime)+","+str(sstat)+")"
+ try:
+ if not sstat:
+ sstat=os.lstat(src)
+ except Exception, e:
+ print "!!! Stating source file failed... movefile()"
+ print "!!!",e
+ return None
+
+ destexists=1
+ try:
+ dstat=os.lstat(dest)
+ except:
+ dstat=os.lstat(os.path.dirname(dest))
+ destexists=0
+
+ if destexists:
+ if stat.S_ISLNK(dstat[stat.ST_MODE]):
+ try:
+ os.unlink(dest)
+ destexists=0
+ except Exception, e:
+ pass
+
+ if stat.S_ISLNK(sstat[stat.ST_MODE]):
+ try:
+ target=os.readlink(src)
+ if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
+ os.unlink(dest)
+ os.symlink(target,dest)
+# os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
+ os.unlink(src)
+ return os.lstat(dest)
+ except Exception, e:
+ print "!!! failed to properly create symlink:"
+ print "!!!",dest,"->",target
+ print "!!!",e
+ return None
+
+ renamefailed=1
+ if sstat[stat.ST_DEV]==dstat[stat.ST_DEV]:
+ try:
+ ret=os.rename(src,dest)
+ renamefailed=0
+ except Exception, e:
+ import errno
+ if e[0]!=errno.EXDEV:
+ # Some random error.
+ print "!!! Failed to move",src,"to",dest
+ print "!!!",e
+ return None
+ # Invalid cross-device-link 'bind' mounted or actually Cross-Device
+
+ if renamefailed:
+ didcopy=0
+ if stat.S_ISREG(sstat[stat.ST_MODE]):
+ try: # For safety copy then move it over.
+ shutil.copyfile(src,dest+"#new")
+ os.rename(dest+"#new",dest)
+ didcopy=1
+ except Exception, e:
+ print '!!! copy',src,'->',dest,'failed.'
+ print "!!!",e
+ return None
+ else:
+ #we don't yet handle special, so we need to fall back to /bin/mv
+ a=getstatusoutput("/bin/mv -f "+"'"+src+"' '"+dest+"'")
+ if a[0]!=0:
+ print "!!! Failed to move special file:"
+ print "!!! '"+src+"' to '"+dest+"'"
+ print "!!!",a
+ return None # failure
+ try:
+ if didcopy:
+ missingos.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
+ os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
+ os.unlink(src)
+ except Exception, e:
+ print "!!! Failed to chown/chmod/unlink in movefile()"
+ print "!!!",dest
+ print "!!!",e
+ return None
+
+ if newmtime:
+ os.utime(dest,(newmtime,newmtime))
+ else:
+ os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
+ newmtime=sstat[stat.ST_MTIME]
+ return newmtime
+
+
+
+#######################################################################
+#######################################################################
+#
+# SECTION: Download
+#
+# PURPOSE: Download via HTTP, FTP, CVS, BITKEEPER, handling of MD5-signatures
+# and mirrors
+#
+#######################################################################
+#######################################################################
+
+def decodeurl(url):
+ """Decodes an URL into the tokens (scheme, network location, path,
+ user, password, parameters).
+
+ >>> decodeurl("http://www.google.com/index.html")
+ ('http', 'www.google.com', '/index.html', '', '', {})
+
+ CVS url with username, host and cvsroot. The cvs module to check out is in the
+ parameters:
+
+ >>> decodeurl("cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg")
+ ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'})
+
+ Dito, but this time the username has a password part. And we also request a special tag
+ to check out.
+
+ >>> decodeurl("cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;module=familiar/dist/ipkg;tag=V0-99-81")
+ ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'})
+ """
+
+ m = re.compile('(?P<type>[^:]*)://((?P<user>.+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
+ if not m:
+ raise MalformedUrl(url)
+
+ type = m.group('type')
+ location = m.group('location')
+ if not location:
+ raise MalformedUrl(url)
+ user = m.group('user')
+ parm = m.group('parm')
+ m = re.compile('(?P<host>[^/;]+)(?P<path>/[^;]+)').match(location)
+ if m:
+ host = m.group('host')
+ path = m.group('path')
+ else:
+ host = ""
+ path = location
+ if user:
+ m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
+ if m:
+ user = m.group('user')
+ pswd = m.group('pswd')
+ else:
+ user = ''
+ pswd = ''
+ #note("decodeurl: %s decoded to:" % url)
+ #note("decodeurl: type = '%s'" % type)
+ #note("decodeurl: host = '%s'" % host)
+ #note("decodeurl: path = '%s'" % path)
+ #note("decodeurl: parm = '%s'" % parm)
+ #note("decodeurl: user = '%s'" % user)
+ #note("decodeurl: pswd = '%s'" % pswd)
+ p = {}
+ if parm:
+ for s in parm.split(';'):
+ s1,s2 = s.split('=')
+ p[s1] = s2
+
+ return (type, host, path, user, pswd, p)
+
+#######################################################################
+
+def encodeurl(decoded):
+ """Encodes a URL from tokens (scheme, network location, path,
+ user, password, parameters).
+
+ >>> encodeurl(['http', 'www.google.com', '/index.html', '', '', {}])
+
+ "http://www.google.com/index.html"
+
+ CVS with username, host and cvsroot. The cvs module to check out is in the
+ parameters:
+
+ >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'}])
+
+ "cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg"
+
+ Dito, but this time the username has a password part. And we also request a special tag
+ to check out.
+
+ >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'}])
+
+ "cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;module=familiar/dist/ipkg;tag=V0-99-81"
+ """
+
+ (type, host, path, user, pswd, p) = decoded
+
+ if not type or not path:
+ fatal("invalid or missing parameters for url encoding")
+ url = '%s://' % type
+ if user:
+ url += "%s" % user
+ if pswd:
+ url += ":%s" % pswd
+ url += "@"
+ if host:
+ url += "%s" % host
+ url += "%s" % path
+ if p:
+ for parm in p.keys():
+ url += ";%s=%s" % (parm, p[parm])
+
+ return url
+
+#######################################################################
+
+def which(path, item, direction = 0):
+ """Useful function for locating a file in a PATH"""
+ found = ""
+ for p in (path or "").split(':'):
+ if os.path.exists(os.path.join(p, item)):
+ found = os.path.join(p, item)
+ if direction == 0:
+ break
+ return found
+
+#######################################################################
+
+
+
+
+#######################################################################
+#######################################################################
+#
+# SECTION: Dependency
+#
+# PURPOSE: Compare build & run dependencies
+#
+#######################################################################
+#######################################################################
+
+def tokenize(mystring):
+ """Breaks a string like 'foo? (bar) oni? (blah (blah))' into (possibly embedded) lists:
+
+ >>> tokenize("x")
+ ['x']
+ >>> tokenize("x y")
+ ['x', 'y']
+ >>> tokenize("(x y)")
+ [['x', 'y']]
+ >>> tokenize("(x y) b c")
+ [['x', 'y'], 'b', 'c']
+ >>> tokenize("foo? (bar) oni? (blah (blah))")
+ ['foo?', ['bar'], 'oni?', ['blah', ['blah']]]
+ >>> tokenize("sys-apps/linux-headers nls? (sys-devel/gettext)")
+ ['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']]
+ """
+
+ newtokens = []
+ curlist = newtokens
+ prevlists = []
+ level = 0
+ accum = ""
+ for x in mystring:
+ if x=="(":
+ if accum:
+ curlist.append(accum)
+ accum=""
+ prevlists.append(curlist)
+ curlist=[]
+ level=level+1
+ elif x==")":
+ if accum:
+ curlist.append(accum)
+ accum=""
+ if level==0:
+ print "!!! tokenizer: Unmatched left parenthesis in:\n'"+mystring+"'"
+ return None
+ newlist=curlist
+ curlist=prevlists.pop()
+ curlist.append(newlist)
+ level=level-1
+ elif x in whitespace:
+ if accum:
+ curlist.append(accum)
+ accum=""
+ else:
+ accum=accum+x
+ if accum:
+ curlist.append(accum)
+ if (level!=0):
+ print "!!! tokenizer: Exiting with unterminated parenthesis in:\n'"+mystring+"'"
+ return None
+ return newtokens
+
+
+#######################################################################
+
+def evaluate(tokens,mydefines,allon=0):
+ """Removes tokens based on whether conditional definitions exist or not.
+ Recognizes !
+
+ >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {})
+ ['sys-apps/linux-headers']
+
+ Negate the flag:
+
+ >>> evaluate(['sys-apps/linux-headers', '!nls?', ['sys-devel/gettext']], {})
+ ['sys-apps/linux-headers', ['sys-devel/gettext']]
+
+ Define 'nls':
+
+ >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {"nls":1})
+ ['sys-apps/linux-headers', ['sys-devel/gettext']]
+
+ Turn allon on:
+
+ >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {}, True)
+ ['sys-apps/linux-headers', ['sys-devel/gettext']]
+ """
+
+ if tokens == None:
+ return None
+ mytokens = tokens + [] # this copies the list
+ pos = 0
+ while pos < len(mytokens):
+ if type(mytokens[pos]) == types.ListType:
+ evaluate(mytokens[pos], mydefines)
+ if not len(mytokens[pos]):
+ del mytokens[pos]
+ continue
+ elif mytokens[pos][-1] == "?":
+ cur = mytokens[pos][:-1]
+ del mytokens[pos]
+ if allon:
+ if cur[0] == "!":
+ del mytokens[pos]
+ else:
+ if cur[0] == "!":
+ if (cur[1:] in mydefines) and (pos < len(mytokens)):
+ del mytokens[pos]
+ continue
+ elif (cur not in mydefines) and (pos < len(mytokens)):
+ del mytokens[pos]
+ continue
+ pos = pos + 1
+ return mytokens
+
+
+#######################################################################
+
+def flatten(mytokens):
+ """Converts nested arrays into a flat arrays:
+
+ >>> flatten([1,[2,3]])
+ [1, 2, 3]
+ >>> flatten(['sys-apps/linux-headers', ['sys-devel/gettext']])
+ ['sys-apps/linux-headers', 'sys-devel/gettext']
+ """
+
+ newlist=[]
+ for x in mytokens:
+ if type(x)==types.ListType:
+ newlist.extend(flatten(x))
+ else:
+ newlist.append(x)
+ return newlist
+
+
+#######################################################################
+
+_package_weights_ = {"pre":-2,"p":0,"alpha":-4,"beta":-3,"rc":-1} # dicts are unordered
+_package_ends_ = ["pre", "p", "alpha", "beta", "rc", "cvs", "bk", "HEAD" ] # so we need ordered list
+
+def relparse(myver):
+ """Parses the last elements of a version number into a triplet, that can
+ later be compared:
+
+ >>> relparse('1.2_pre3')
+ [1.2, -2, 3.0]
+ >>> relparse('1.2b')
+ [1.2, 98, 0]
+ >>> relparse('1.2')
+ [1.2, 0, 0]
+ """
+
+ number = 0
+ p1 = 0
+ p2 = 0
+ mynewver = myver.split('_')
+ if len(mynewver)==2:
+ # an _package_weights_
+ number = float(mynewver[0])
+ match = 0
+ for x in _package_ends_:
+ elen = len(x)
+ if mynewver[1][:elen] == x:
+ match = 1
+ p1 = _package_weights_[x]
+ try:
+ p2 = float(mynewver[1][elen:])
+ except:
+ p2 = 0
+ break
+ if not match:
+ # normal number or number with letter at end
+ divider = len(myver)-1
+ if myver[divider:] not in "1234567890":
+ # letter at end
+ p1 = ord(myver[divider:])
+ number = float(myver[0:divider])
+ else:
+ number = float(myver)
+ else:
+ # normal number or number with letter at end
+ divider = len(myver)-1
+ if myver[divider:] not in "1234567890":
+ #letter at end
+ p1 = ord(myver[divider:])
+ number = float(myver[0:divider])
+ else:
+ number = float(myver)
+ return [number,p1,p2]
+
+
+#######################################################################
+
+__ververify_cache__ = {}
+
+def ververify(myorigval,silent=1):
+ """Returns 1 if given a valid version string, els 0. Valid versions are in the format
+
+ <v1>.<v2>...<vx>[a-z,_{_package_weights_}[vy]]
+
+ >>> ververify('2.4.20')
+ 1
+ >>> ververify('2.4..20') # two dots
+ 0
+ >>> ververify('2.x.20') # 'x' is not numeric
+ 0
+ >>> ververify('2.4.20a')
+ 1
+ >>> ververify('2.4.20cvs') # only one trailing letter
+ 0
+ >>> ververify('1a')
+ 1
+ >>> ververify('test_a') # no version at all
+ 0
+ >>> ververify('2.4.20_beta1')
+ 1
+ >>> ververify('2.4.20_beta')
+ 1
+ >>> ververify('2.4.20_wrongext') # _wrongext is no valid trailer
+ 0
+ """
+
+ # Lookup the cache first
+ try:
+ return __ververify_cache__[myorigval]
+ except KeyError:
+ pass
+
+ if len(myorigval) == 0:
+ if not silent:
+ error("package version is empty")
+ __ververify_cache__[myorigval] = 0
+ return 0
+ myval = myorigval.split('.')
+ if len(myval)==0:
+ if not silent:
+ error("package name has empty version string")
+ __ververify_cache__[myorigval] = 0
+ return 0
+ # all but the last version must be a numeric
+ for x in myval[:-1]:
+ if not len(x):
+ if not silent:
+ error("package version has two points in a row")
+ __ververify_cache__[myorigval] = 0
+ return 0
+ try:
+ foo = int(x)
+ except:
+ if not silent:
+ error("package version contains non-numeric '"+x+"'")
+ __ververify_cache__[myorigval] = 0
+ return 0
+ if not len(myval[-1]):
+ if not silent:
+ error("package version has trailing dot")
+ __ververify_cache__[myorigval] = 0
+ return 0
+ try:
+ foo = int(myval[-1])
+ __ververify_cache__[myorigval] = 1
+ return 1
+ except:
+ pass
+
+ # ok, our last component is not a plain number or blank, let's continue
+ if myval[-1][-1] in lowercase:
+ try:
+ foo = int(myval[-1][:-1])
+ return 1
+ __ververify_cache__[myorigval] = 1
+ # 1a, 2.0b, etc.
+ except:
+ pass
+ # ok, maybe we have a 1_alpha or 1_beta2; let's see
+ ep=string.split(myval[-1],"_")
+ if len(ep)!= 2:
+ if not silent:
+ error("package version has more than one letter at then end")
+ __ververify_cache__[myorigval] = 0
+ return 0
+ try:
+ foo = string.atoi(ep[0])
+ except:
+ # this needs to be numeric, i.e. the "1" in "1_alpha"
+ if not silent:
+ error("package version must have numeric part before the '_'")
+ __ververify_cache__[myorigval] = 0
+ return 0
+
+ for mye in _package_ends_:
+ if ep[1][0:len(mye)] == mye:
+ if len(mye) == len(ep[1]):
+ # no trailing numeric is ok
+ __ververify_cache__[myorigval] = 1
+ return 1
+ else:
+ try:
+ foo = string.atoi(ep[1][len(mye):])
+ __ververify_cache__[myorigval] = 1
+ return 1
+ except:
+ # if no _package_weights_ work, *then* we return 0
+ pass
+ if not silent:
+ error("package version extension after '_' is invalid")
+ __ververify_cache__[myorigval] = 0
+ return 0
+
+
+def isjustname(mypkg):
+ myparts = string.split(mypkg,'-')
+ for x in myparts:
+ if ververify(x):
+ return 0
+ return 1
+
+
+_isspecific_cache_={}
+
+def isspecific(mypkg):
+ "now supports packages with no category"
+ try:
+ return __isspecific_cache__[mypkg]
+ except:
+ pass
+
+ mysplit = string.split(mypkg,"/")
+ if not isjustname(mysplit[-1]):
+ __isspecific_cache__[mypkg] = 1
+ return 1
+ __isspecific_cache__[mypkg] = 0
+ return 0
+
+
+#######################################################################
+
+__pkgsplit_cache__={}
+
+def pkgsplit(mypkg, silent=1):
+
+ """This function can be used as a package verification function. If
+ it is a valid name, pkgsplit will return a list containing:
+ [pkgname, pkgversion(norev), pkgrev ].
+
+ >>> pkgsplit('')
+ >>> pkgsplit('x')
+ >>> pkgsplit('x-')
+ >>> pkgsplit('-1')
+ >>> pkgsplit('glibc-1.2-8.9-r7')
+ >>> pkgsplit('glibc-2.2.5-r7')
+ ['glibc', '2.2.5', 'r7']
+ >>> pkgsplit('foo-1.2-1')
+ >>> pkgsplit('Mesa-3.0')
+ ['Mesa', '3.0', 'r0']
+ """
+
+ try:
+ return __pkgsplit_cache__[mypkg]
+ except KeyError:
+ pass
+
+ myparts = string.split(mypkg,'-')
+ if len(myparts) < 2:
+ if not silent:
+ error("package name without name or version part")
+ __pkgsplit_cache__[mypkg] = None
+ return None
+ for x in myparts:
+ if len(x) == 0:
+ if not silent:
+ error("package name with empty name or version part")
+ __pkgsplit_cache__[mypkg] = None
+ return None
+ # verify rev
+ revok = 0
+ myrev = myparts[-1]
+ ververify(myrev, 0)
+ if len(myrev) and myrev[0] == "r":
+ try:
+ string.atoi(myrev[1:])
+ revok = 1
+ except:
+ pass
+ if revok:
+ if ververify(myparts[-2]):
+ if len(myparts) == 2:
+ __pkgsplit_cache__[mypkg] = None
+ return None
+ else:
+ for x in myparts[:-2]:
+ if ververify(x):
+ __pkgsplit_cache__[mypkg]=None
+ return None
+ # names can't have versiony looking parts
+ myval=[string.join(myparts[:-2],"-"),myparts[-2],myparts[-1]]
+ __pkgsplit_cache__[mypkg]=myval
+ return myval
+ else:
+ __pkgsplit_cache__[mypkg] = None
+ return None
+
+ elif ververify(myparts[-1],silent):
+ if len(myparts)==1:
+ if not silent:
+ print "!!! Name error in",mypkg+": missing name part."
+ __pkgsplit_cache__[mypkg]=None
+ return None
+ else:
+ for x in myparts[:-1]:
+ if ververify(x):
+ if not silent: error("package name has multiple version parts")
+ __pkgsplit_cache__[mypkg] = None
+ return None
+ myval = [string.join(myparts[:-1],"-"), myparts[-1],"r0"]
+ __pkgsplit_cache__[mypkg] = myval
+ return myval
+ else:
+ __pkgsplit_cache__[mypkg] = None
+ return None
+
+
+#######################################################################
+
+__catpkgsplit_cache__ = {}
+
+def catpkgsplit(mydata,silent=1):
+ """returns [cat, pkgname, version, rev ]
+
+ >>> catpkgsplit('sys-libs/glibc-1.2-r7')
+ ['sys-libs', 'glibc', '1.2', 'r7']
+ >>> catpkgsplit('glibc-1.2-r7')
+ ['null', 'glibc', '1.2', 'r7']
+ """
+
+ try:
+ return __catpkgsplit_cache__[mydata]
+ except KeyError:
+ pass
+
+ cat = os.path.basename(os.path.dirname(mydata))
+ mydata = os.path.join(cat, os.path.basename(mydata))
+# if mydata[:len(projectdir)] == projectdir:
+# mydata = mydata[len(projectdir)+1:]
+ if mydata[-3:] == '.bb':
+ mydata = mydata[:-3]
+
+ mysplit = mydata.split("/")
+ p_split = None
+ splitlen = len(mysplit)
+ if splitlen == 1:
+ retval = [None]
+ p_split = pkgsplit(mydata,silent)
+ else:
+ retval = [mysplit[splitlen - 2]]
+ p_split = pkgsplit(mysplit[splitlen - 1],silent)
+ if not p_split:
+ __catpkgsplit_cache__[mydata] = None
+ return None
+ retval.extend(p_split)
+ __catpkgsplit_cache__[mydata] = retval
+ return retval
+
+
+#######################################################################
+
+__vercmp_cache__ = {}
+
+def vercmp(val1,val2):
+ """This takes two version strings and returns an integer to tell you whether
+ the versions are the same, val1>val2 or val2>val1.
+
+ >>> vercmp('1', '2')
+ -1.0
+ >>> vercmp('2', '1')
+ 1.0
+ >>> vercmp('1', '1.0')
+ 0
+ >>> vercmp('1', '1.1')
+ -1.0
+ >>> vercmp('1.1', '1_p2')
+ 1.0
+ """
+
+ # quick short-circuit
+ if val1 == val2:
+ return 0
+ valkey = val1+" "+val2
+
+ # cache lookup
+ try:
+ return __vercmp_cache__[valkey]
+ try:
+ return - __vercmp_cache__[val2+" "+val1]
+ except KeyError:
+ pass
+ except KeyError:
+ pass
+
+ # consider 1_p2 vc 1.1
+ # after expansion will become (1_p2,0) vc (1,1)
+ # then 1_p2 is compared with 1 before 0 is compared with 1
+ # to solve the bug we need to convert it to (1,0_p2)
+ # by splitting _prepart part and adding it back _after_expansion
+
+ val1_prepart = val2_prepart = ''
+ if val1.count('_'):
+ val1, val1_prepart = val1.split('_', 1)
+ if val2.count('_'):
+ val2, val2_prepart = val2.split('_', 1)
+
+ # replace '-' by '.'
+ # FIXME: Is it needed? can val1/2 contain '-'?
+
+ val1 = string.split(val1,'-')
+ if len(val1) == 2:
+ val1[0] = val1[0] +"."+ val1[1]
+ val2 = string.split(val2,'-')
+ if len(val2) == 2:
+ val2[0] = val2[0] +"."+ val2[1]
+
+ val1 = string.split(val1[0],'.')
+ val2 = string.split(val2[0],'.')
+
+ # add back decimal point so that .03 does not become "3" !
+ for x in range(1,len(val1)):
+ if val1[x][0] == '0' :
+ val1[x] = '.' + val1[x]
+ for x in range(1,len(val2)):
+ if val2[x][0] == '0' :
+ val2[x] = '.' + val2[x]
+
+ # extend varion numbers
+ if len(val2) < len(val1):
+ val2.extend(["0"]*(len(val1)-len(val2)))
+ elif len(val1) < len(val2):
+ val1.extend(["0"]*(len(val2)-len(val1)))
+
+ # add back _prepart tails
+ if val1_prepart:
+ val1[-1] += '_' + val1_prepart
+ if val2_prepart:
+ val2[-1] += '_' + val2_prepart
+ # The above code will extend version numbers out so they
+ # have the same number of digits.
+ for x in range(0,len(val1)):
+ cmp1 = relparse(val1[x])
+ cmp2 = relparse(val2[x])
+ for y in range(0,3):
+ myret = cmp1[y] - cmp2[y]
+ if myret != 0:
+ __vercmp_cache__[valkey] = myret
+ return myret
+ __vercmp_cache__[valkey] = 0
+ return 0
+
+
+#######################################################################
+
+def pkgcmp(pkg1,pkg2):
+ """ Compares two packages, which should have been split via
+ pkgsplit(). if the return value val is less than zero, then pkg2 is
+ newer than pkg1, zero if equal and positive if older.
+
+ >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r7'])
+ 0
+ >>> pkgcmp(['glibc', '2.2.5', 'r4'], ['glibc', '2.2.5', 'r7'])
+ -1
+ >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r2'])
+ 1
+ """
+
+ mycmp = vercmp(pkg1[1],pkg2[1])
+ if mycmp > 0:
+ return 1
+ if mycmp < 0:
+ return -1
+ r1=string.atoi(pkg1[2][1:])
+ r2=string.atoi(pkg2[2][1:])
+ if r1 > r2:
+ return 1
+ if r2 > r1:
+ return -1
+ return 0
+
+
+#######################################################################
+
+def dep_parenreduce(mysplit, mypos=0):
+ """Accepts a list of strings, and converts '(' and ')' surrounded items to sub-lists:
+
+ >>> dep_parenreduce([''])
+ ['']
+ >>> dep_parenreduce(['1', '2', '3'])
+ ['1', '2', '3']
+ >>> dep_parenreduce(['1', '(', '2', '3', ')', '4'])
+ ['1', ['2', '3'], '4']
+ """
+
+ while mypos < len(mysplit):
+ if mysplit[mypos] == "(":
+ firstpos = mypos
+ mypos = mypos + 1
+ while mypos < len(mysplit):
+ if mysplit[mypos] == ")":
+ mysplit[firstpos:mypos+1] = [mysplit[firstpos+1:mypos]]
+ mypos = firstpos
+ break
+ elif mysplit[mypos] == "(":
+ # recurse
+ mysplit = dep_parenreduce(mysplit,mypos)
+ mypos = mypos + 1
+ mypos = mypos + 1
+ return mysplit
+
+
+def dep_opconvert(mysplit, myuse):
+ "Does dependency operator conversion"
+
+ mypos = 0
+ newsplit = []
+ while mypos < len(mysplit):
+ if type(mysplit[mypos]) == types.ListType:
+ newsplit.append(dep_opconvert(mysplit[mypos],myuse))
+ mypos += 1
+ elif mysplit[mypos] == ")":
+ # mismatched paren, error
+ return None
+ elif mysplit[mypos]=="||":
+ if ((mypos+1)>=len(mysplit)) or (type(mysplit[mypos+1])!=types.ListType):
+ # || must be followed by paren'd list
+ return None
+ try:
+ mynew = dep_opconvert(mysplit[mypos+1],myuse)
+ except Exception, e:
+ error("unable to satisfy OR dependancy: " + string.join(mysplit," || "))
+ raise e
+ mynew[0:0] = ["||"]
+ newsplit.append(mynew)
+ mypos += 2
+ elif mysplit[mypos][-1] == "?":
+ # use clause, i.e "gnome? ( foo bar )"
+ # this is a quick and dirty hack so that repoman can enable all USE vars:
+ if (len(myuse) == 1) and (myuse[0] == "*"):
+ # enable it even if it's ! (for repoman) but kill it if it's
+ # an arch variable that isn't for this arch. XXX Sparc64?
+ if (mysplit[mypos][:-1] not in settings.usemask) or \
+ (mysplit[mypos][:-1]==settings["ARCH"]):
+ enabled=1
+ else:
+ enabled=0
+ else:
+ if mysplit[mypos][0] == "!":
+ myusevar = mysplit[mypos][1:-1]
+ enabled = not myusevar in myuse
+ #if myusevar in myuse:
+ # enabled = 0
+ #else:
+ # enabled = 1
+ else:
+ myusevar=mysplit[mypos][:-1]
+ enabled = myusevar in myuse
+ #if myusevar in myuse:
+ # enabled=1
+ #else:
+ # enabled=0
+ if (mypos +2 < len(mysplit)) and (mysplit[mypos+2] == ":"):
+ # colon mode
+ if enabled:
+ # choose the first option
+ if type(mysplit[mypos+1]) == types.ListType:
+ newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
+ else:
+ newsplit.append(mysplit[mypos+1])
+ else:
+ # choose the alternate option
+ if type(mysplit[mypos+1]) == types.ListType:
+ newsplit.append(dep_opconvert(mysplit[mypos+3],myuse))
+ else:
+ newsplit.append(mysplit[mypos+3])
+ mypos += 4
+ else:
+ # normal use mode
+ if enabled:
+ if type(mysplit[mypos+1]) == types.ListType:
+ newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
+ else:
+ newsplit.append(mysplit[mypos+1])
+ # otherwise, continue
+ mypos += 2
+ else:
+ # normal item
+ newsplit.append(mysplit[mypos])
+ mypos += 1
+ return newsplit
+
+class digraph:
+ """beautiful directed graph object"""
+
+ def __init__(self):
+ self.dict={}
+ #okeys = keys, in order they were added (to optimize firstzero() ordering)
+ self.okeys=[]
+ self.__callback_cache=[]
+
+ def __str__(self):
+ str = ""
+ for key in self.okeys:
+ str += "%s:\t%s\n" % (key, self.dict[key][1])
+ return str
+
+ def addnode(self,mykey,myparent):
+ if not mykey in self.dict:
+ self.okeys.append(mykey)
+ if myparent==None:
+ self.dict[mykey]=[0,[]]
+ else:
+ self.dict[mykey]=[0,[myparent]]
+ self.dict[myparent][0]=self.dict[myparent][0]+1
+ return
+ if myparent and (not myparent in self.dict[mykey][1]):
+ self.dict[mykey][1].append(myparent)
+ self.dict[myparent][0]=self.dict[myparent][0]+1
+
+ def delnode(self,mykey, ref = 1):
+ """Delete a node
+
+ If ref is 1, remove references to this node from other nodes.
+ If ref is 2, remove nodes that reference this node."""
+ if not mykey in self.dict:
+ return
+ for x in self.dict[mykey][1]:
+ self.dict[x][0]=self.dict[x][0]-1
+ del self.dict[mykey]
+ while 1:
+ try:
+ self.okeys.remove(mykey)
+ except ValueError:
+ break
+ if ref:
+ __kill = []
+ for k in self.okeys:
+ if mykey in self.dict[k][1]:
+ if ref == 1 or ref == 2:
+ self.dict[k][1].remove(mykey)
+ if ref == 2:
+ __kill.append(k)
+ for l in __kill:
+ self.delnode(l, ref)
+
+ def allnodes(self):
+ "returns all nodes in the dictionary"
+ return self.dict.keys()
+
+ def firstzero(self):
+ "returns first node with zero references, or NULL if no such node exists"
+ for x in self.okeys:
+ if self.dict[x][0]==0:
+ return x
+ return None
+
+ def firstnonzero(self):
+ "returns first node with nonzero references, or NULL if no such node exists"
+ for x in self.okeys:
+ if self.dict[x][0]!=0:
+ return x
+ return None
+
+
+ def allzeros(self):
+ "returns all nodes with zero references, or NULL if no such node exists"
+ zerolist = []
+ for x in self.dict.keys():
+ if self.dict[x][0]==0:
+ zerolist.append(x)
+ return zerolist
+
+ def hasallzeros(self):
+ "returns 0/1, Are all nodes zeros? 1 : 0"
+ zerolist = []
+ for x in self.dict.keys():
+ if self.dict[x][0]!=0:
+ return 0
+ return 1
+
+ def empty(self):
+ if len(self.dict)==0:
+ return 1
+ return 0
+
+ def hasnode(self,mynode):
+ return mynode in self.dict
+
+ def getparents(self, item):
+ if not self.hasnode(item):
+ return []
+ return self.dict[item][1]
+
+ def getchildren(self, item):
+ if not self.hasnode(item):
+ return []
+ children = [i for i in self.okeys if item in self.getparents(i)]
+ return children
+
+ def walkdown(self, item, callback, debug = None, usecache = False):
+ if not self.hasnode(item):
+ return 0
+
+ if usecache:
+ if self.__callback_cache.count(item):
+ if debug:
+ print "hit cache for item: %s" % item
+ return 1
+
+ parents = self.getparents(item)
+ children = self.getchildren(item)
+ for p in parents:
+ if p in children:
+# print "%s is both parent and child of %s" % (p, item)
+ if usecache:
+ self.__callback_cache.append(p)
+ ret = callback(self, p)
+ if ret == 0:
+ return 0
+ continue
+ if item == p:
+ print "eek, i'm my own parent!"
+ return 0
+ if debug:
+ print "item: %s, p: %s" % (item, p)
+ ret = self.walkdown(p, callback, debug, usecache)
+ if ret == 0:
+ return 0
+ if usecache:
+ self.__callback_cache.append(item)
+ return callback(self, item)
+
+ def walkup(self, item, callback):
+ if not self.hasnode(item):
+ return 0
+
+ parents = self.getparents(item)
+ children = self.getchildren(item)
+ for c in children:
+ if c in parents:
+ ret = callback(self, item)
+ if ret == 0:
+ return 0
+ continue
+ if item == c:
+ print "eek, i'm my own child!"
+ return 0
+ ret = self.walkup(c, callback)
+ if ret == 0:
+ return 0
+ return callback(self, item)
+
+ def copy(self):
+ mygraph=digraph()
+ for x in self.dict.keys():
+ mygraph.dict[x]=self.dict[x][:]
+ mygraph.okeys=self.okeys[:]
+ return mygraph
+
+#######################################################################
+#######################################################################
+#
+# SECTION: Config
+#
+# PURPOSE: Reading and handling of system/target-specific/local configuration
+# reading of package configuration
+#
+#######################################################################
+#######################################################################
+
+def reader(cfgfile, feeder):
+ """Generic configuration file reader that opens a file, reads the lines,
+ handles continuation lines, comments, empty lines and feed all read lines
+ into the function feeder(lineno, line).
+ """
+
+ f = open(cfgfile,'r')
+ lineno = 0
+ while 1:
+ lineno = lineno + 1
+ s = f.readline()
+ if not s: break
+ w = s.strip()
+ if not w: continue # skip empty lines
+ s = s.rstrip()
+ if s[0] == '#': continue # skip comments
+ while s[-1] == '\\':
+ s2 = f.readline()[:-1].strip()
+ s = s[:-1] + s2
+ feeder(lineno, s)
+
+if __name__ == "__main__":
+ import doctest, bb
+ doctest.testmod(bb)