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