add 'oemake -s' option to dump out a table of latest and preferred versions for all...
[bitbake.git] / bin / oemake
1 #!/usr/bin/env python
2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4
5 import sys, os, getopt, glob, copy, os.path, re
6 sys.path.append('/usr/share/oe')
7 import oe
8 from oe import make
9 from sets import Set
10 import itertools, optparse
11
12 parsespin = itertools.cycle( r'|/-\\' )
13
14 __version__ = 1.2
15 __build_cache_fail = []
16 __build_cache = []
17 __building_list = []
18 __build_path = []
19
20 __preferred = {}
21 __world_target = Set()
22 __ignored_dependencies = Set()
23 __depcmds = { "clean": None,
24              "mrproper": None }
25
26 __stats = {}
27
28 oefile_config_priorities = []
29 oefile_priority = {}
30 oedebug = 0
31
32 def handle_options( args ):
33     parser = optparse.OptionParser( version = "OpenEmbedded Build Infrastructure Core version %s, %%prog version %s" % ( oe.__version__, __version__ ),
34     usage = """%prog [options] [package ...]
35
36 Builds specified packages, expecting that the .oe files
37 it has to work from are in OEFILES
38 Default packages are all packages in OEFILES.
39 Default OEFILES are the .oe files in the current directory.""" )
40
41     parser.add_option( "-k", "--continue", help = "continue as much as possible after an error. While the target that failed, and those that depend on it, cannot be remade, the other dependencies of these targets can be processed all the same.",
42                action = "store_false", dest = "abort", default = True )
43
44     parser.add_option( "-f", "--force", help = "force run of specified cmd, regardless of stamp status",
45                action = "store_true", dest = "force", default = False )
46
47
48     parser.add_option( "-c", "--cmd", help = "specify command to pass to oebuild. Valid commands are "
49                                              "'fetch' (fetch all sources), " 
50                                              "'unpack' (unpack the sources), "
51                                              "'patch' (apply the patches), "
52                                              "'configure' (configure the source tree), "
53                                              "'compile' (compile the source tree), "
54                                              "'stage' (install libraries and headers needed for subsequent packages), "
55                                              "'install' (install libraries and executables), and"
56                                              "'package' (package files into the selected package format)",
57                action = "store", dest = "cmd", default = "build" )
58
59     parser.add_option( "-r", "--read", help = "read the specified file before oe.conf",
60                action = "append", dest = "file", default = [] )
61
62     parser.add_option( "-v", "--verbose", help = "output more chit-chat to the terminal",
63                action = "store_true", dest = "verbose", default = False )
64
65     parser.add_option( "-n", "--dry-run", help = "don't call oebuild, just go through the motions",
66                action = "store_true", dest = "dry_run", default = False )
67
68     parser.add_option( "-p", "--parse-only", help = "quit after parsing the OE files (developers only)",
69                action = "store_true", dest = "parse_only", default = False )
70
71     parser.add_option( "-d", "--disable-psyco", help = "disable using the psyco just-in-time compiler (not recommended)",
72                action = "store_true", dest = "disable_psyco", default = False )
73
74     parser.add_option( "-s", "--show-versions", help = "show current and preferred versions of all packages",
75                action = "store_true", dest = "show_versions", default = False )
76
77     options, args = parser.parse_args( args )
78     return options, args[1:]
79
80 def try_build(fn, virtual):
81     if fn in __building_list:
82         oe.error("%s depends on itself (eventually)" % fn)
83         oe.error("upwards chain is: %s" % (" -> ".join(__build_path)))
84         return False
85
86     __building_list.append(fn)
87
88     the_data = make.pkgdata[fn]
89     item = oe.data.getVar('PN', the_data, 1)
90     pathstr = "%s (%s)" % (item, virtual)
91     __build_path.append(pathstr)
92
93     depends_list = (oe.data.getVar('DEPENDS', the_data, 1) or "").split()
94     if make.options.verbose:
95         oe.note("current path: %s" % (" -> ".join(__build_path)))
96         oe.note("dependencies for %s are: %s" % (item, " ".join(depends_list)))
97
98     try:
99         failed = False
100
101         if __depcmd:
102             oldcmd = make.options.cmd
103             make.options.cmd = __depcmd
104
105         for d in depends_list:
106             if d in __ignored_dependencies:
107                 continue
108             if not __depcmd:
109                 continue
110             if buildPackage(d) == 0:
111                 oe.error("dependency %s (for %s) not satisfied" % (d,item))
112                 failed = True
113                 if make.options.abort:
114                     break
115
116         if __depcmd:
117             make.options.cmd = oldcmd
118
119         if failed:
120             __stats["deps"] += 1
121             return False
122
123         oe.event.fire(oe.event.PkgStarted(item, make.pkgdata[fn]))
124         try:
125             __stats["attempt"] += 1
126             if not make.options.dry_run:
127                 oe.build.exec_task('do_%s' % make.options.cmd, make.pkgdata[fn])
128             oe.event.fire(oe.event.PkgSucceeded(item, make.pkgdata[fn]))
129             __build_cache.append(fn)
130             return True
131         except oe.build.FuncFailed:
132             __stats["fail"] += 1
133             oe.error("task stack execution failed")
134             oe.event.fire(oe.event.PkgFailed(item, make.pkgdata[fn]))
135             __build_cache_fail.append(fn)
136             raise
137         except oe.build.EventException:
138             __stats["fail"] += 1
139             (type, value, traceback) = sys.exc_info()
140             e = value.event
141             oe.error("%s event exception, aborting" % oe.event.getName(e))
142             oe.event.fire(oe.event.PkgFailed(item, make.pkgdata[fn]))
143             __build_cache_fail.append(fn)
144             raise
145     finally:
146         __building_list.remove(fn)
147         __build_path.remove(pathstr)
148
149 def showVersions():
150     pkg_pn = {}
151     preferred_versions = {}
152     latest_versions = {}
153
154     for p in make.pkgdata.keys():
155         pn = oe.data.getVar('PN', make.pkgdata[p], 1)
156         if not pkg_pn.has_key(pn):
157             pkg_pn[pn] = []
158         pkg_pn[pn].append(p)
159     
160     # Sort by priority
161     for pn in pkg_pn.keys():
162         files = pkg_pn[pn]
163         priorities = {}
164         for f in files:
165             priority = oefile_priority[f]
166             if not priorities.has_key(priority):
167                 priorities[priority] = []
168             priorities[priority].append(f)
169         p_list = priorities.keys()
170         p_list.sort(lambda a, b: a - b)
171         pkg_pn[pn] = []
172         for p in p_list:
173             pkg_pn[pn] = [ priorities[p] ] + pkg_pn[pn]
174
175     # If there is a PREFERRED_VERSION, find the highest-priority oefile providing that
176     # version.  If not, find the latest version provided by an oefile in the
177     # highest-priority set.
178     for pn in pkg_pn.keys():
179         preferred_file = None
180         
181         preferred_v = oe.data.getVar('PREFERRED_VERSION_%s' % pn, make.cfg, 1)
182         if preferred_v:
183             preferred_r = None
184             m = re.match('(.*)_(.*)', preferred_v)
185             if m:
186                 preferred_v = m.group(1)
187                 preferred_r = m.group(2)
188                 
189             for file_set in pkg_pn[pn]:
190                 for f in file_set:
191                     the_data = make.pkgdata[f]
192                     pv = oe.data.getVar('PV', the_data, 1)
193                     pr = oe.data.getVar('PR', the_data, 1)
194                     if preferred_v == pv and (preferred_r == pr or preferred_r == None):
195                         preferred_file = f
196                         preferred_ver = (pv, pr)
197                         break
198                 if preferred_file:
199                     break
200             if preferred_r:
201                 pv_str = '%s-%s' % (preferred_v, preferred_r)
202             else:
203                 pv_str = preferred_v
204             if preferred_file is None:
205                 oe.note("preferred version %s of %s not available" % (pv_str, pn))
206             else:
207                 oe.debug(1, "selecting %s as PREFERRED_VERSION %s of package %s" % (preferred_file, pv_str, pn))
208                
209         # get highest priority file set
210         files = pkg_pn[pn][0]
211         latest = None
212         latest_p = 0
213         latest_f = None
214         for f in files:
215             the_data = make.pkgdata[f]
216             pv = oe.data.getVar('PV', the_data, 1)
217             pr = oe.data.getVar('PR', the_data, 1)
218             dp = int(oe.data.getVar('DEFAULT_PREFERENCE', the_data, 1) or "0")
219
220             if (latest is None) or ((latest_p == dp) and (make.vercmp(latest, (pv, pr)) < 0)) or (dp > latest_p):
221                 latest = (pv, pr)
222                 latest_f = f
223                 latest_p = dp
224         if preferred_file is None:
225             preferred_file = latest_f
226             preferred_ver = latest
227             
228         preferred_versions[pn] = (preferred_ver, preferred_file)
229         latest_versions[pn] = (latest, latest_f)
230
231     pkg_list = pkg_pn.keys()
232     pkg_list.sort()
233     
234     for p in pkg_list:
235         pref = preferred_versions[p]
236         latest = latest_versions[p]
237
238         if pref != latest:
239             prefstr = pref[0][0] + "-" + pref[0][1]
240         else:
241             prefstr = ""
242
243         print "%-30s %20s %20s" % (p, latest[0][0] + "-" + latest[0][1],
244                                    prefstr)
245
246 def buildPackage(item):
247     fn = None
248
249     discriminated = False
250
251     if not providers.has_key(item):
252         oe.error("Nothing provides %s" % item)
253         return 0
254
255     all_p = providers[item]
256
257     for p in all_p:
258         if p in __build_cache:
259             oe.debug(1, "already built %s in this run\n" % p)
260             return 1
261
262     eligible = []
263     preferred_versions = {}
264
265     # Collate providers by PN
266     pkg_pn = {}
267     for p in all_p:
268         the_data = make.pkgdata[p]
269         pn = oe.data.getVar('PN', the_data, 1)
270         if not pkg_pn.has_key(pn):
271             pkg_pn[pn] = []
272         pkg_pn[pn].append(p)
273
274     oe.debug(1, "providers for %s are: %s" % (item, pkg_pn.keys()))
275
276     # Sort by priority
277     for pn in pkg_pn.keys():
278         files = pkg_pn[pn]
279         priorities = {}
280         for f in files:
281             priority = oefile_priority[f]
282             if not priorities.has_key(priority):
283                 priorities[priority] = []
284             priorities[priority].append(f)
285         p_list = priorities.keys()
286         p_list.sort(lambda a, b: a - b)
287         pkg_pn[pn] = []
288         for p in p_list:
289             pkg_pn[pn] = [ priorities[p] ] + pkg_pn[pn]
290
291     # If there is a PREFERRED_VERSION, find the highest-priority oefile providing that
292     # version.  If not, find the latest version provided by an oefile in the
293     # highest-priority set.
294     for pn in pkg_pn.keys():
295         preferred_file = None
296         
297         preferred_v = oe.data.getVar('PREFERRED_VERSION_%s' % pn, make.cfg, 1)
298         if preferred_v:
299             preferred_r = None
300             m = re.match('(.*)_(.*)', preferred_v)
301             if m:
302                 preferred_v = m.group(1)
303                 preferred_r = m.group(2)
304                 
305             for file_set in pkg_pn[pn]:
306                 for f in file_set:
307                     the_data = make.pkgdata[f]
308                     pv = oe.data.getVar('PV', the_data, 1)
309                     pr = oe.data.getVar('PR', the_data, 1)
310                     if preferred_v == pv and (preferred_r == pr or preferred_r == None):
311                         preferred_file = f
312                         preferred_ver = (pv, pr)
313                         break
314                 if preferred_file:
315                     break
316             if preferred_r:
317                 pv_str = '%s-%s' % (preferred_v, preferred_r)
318             else:
319                 pv_str = preferred_v
320             if preferred_file is None:
321                 oe.note("preferred version %s of %s not available" % (pv_str, pn))
322             else:
323                 oe.debug(1, "selecting %s as PREFERRED_VERSION %s of package %s" % (preferred_file, pv_str, pn))
324                 
325         if preferred_file is None:
326             # get highest priority file set
327             files = pkg_pn[pn][0]
328             latest = None
329             latest_p = 0
330             latest_f = None
331             for f in files:
332                 the_data = make.pkgdata[f]
333                 pv = oe.data.getVar('PV', the_data, 1)
334                 pr = oe.data.getVar('PR', the_data, 1)
335                 dp = int(oe.data.getVar('DEFAULT_PREFERENCE', the_data, 1) or "0")
336
337                 if (latest is None) or ((latest_p == dp) and (make.vercmp(latest, (pv, pr)) < 0)) or (dp > latest_p):
338                     latest = (pv, pr)
339                     latest_f = f
340                     latest_p = dp
341             preferred_file = latest_f
342             preferred_ver = latest
343             
344             oe.debug(1, "selecting %s as latest version of provider %s" % (preferred_file, pn))
345
346         preferred_versions[pn] = (preferred_ver, preferred_file)
347         eligible.append(preferred_file)
348
349     for p in eligible:
350         if p in __build_cache_fail:
351             oe.debug(1, "rejecting already-failed %s" % p)
352             eligible.remove(p)
353
354     if len(eligible) == 0:
355         oe.error("no eligible providers for %s" % item)
356         return 0
357
358     # look to see if one of them is already staged, or marked as preferred.
359     # if so, bump it to the head of the queue
360     for p in all_p:
361         the_data = make.pkgdata[p]
362         pn = oe.data.getVar('PN', the_data, 1)
363         pv = oe.data.getVar('PV', the_data, 1)
364         pr = oe.data.getVar('PR', the_data, 1)
365         tmpdir = oe.data.getVar('TMPDIR', the_data, 1)
366         stamp = '%s/stamps/%s-%s-%s.do_populate_staging' % (tmpdir, pn, pv, pr)
367         if os.path.exists(stamp):
368             (newvers, fn) = preferred_versions[pn]
369             if not fn in eligible:
370                 # package was made ineligible by already-failed check
371                 continue
372             oldver = "%s-%s" % (pv, pr)
373             newver = '-'.join(newvers)
374             if (newver != oldver):
375                 extra_chat = "; upgrading from %s to %s" % (oldver, newver)
376             else:
377                 extra_chat = ""
378             if make.options.verbose:
379                 oe.note("selecting already-staged %s to satisfy %s%s" % (pn, item, extra_chat))
380             eligible.remove(fn)
381             eligible = [fn] + eligible
382             discriminated = True
383             break
384
385     prefervar = oe.data.getVar('PREFERRED_PROVIDER_%s' % item, make.cfg, 1)
386     if prefervar:
387         __preferred[item] = prefervar
388
389     if __preferred.has_key(item):
390         for p in eligible:
391             the_data = make.pkgdata[p]
392             pn = oe.data.getVar('PN', the_data, 1)
393             if __preferred[item] == pn:
394                 if make.options.verbose:
395                     oe.note("selecting %s to satisfy %s due to PREFERRED_PROVIDERS" % (pn, item))
396                 eligible.remove(p)
397                 eligible = [p] + eligible
398                 discriminated = True
399                 break
400
401     if len(eligible) > 1 and discriminated == False:
402         providers_list = []
403         for fn in eligible:
404             providers_list.append(oe.data.getVar('PN', make.pkgdata[fn], 1))
405         oe.note("multiple providers are available (%s);" % ", ".join(providers_list))
406         oe.note("consider defining PREFERRED_PROVIDER_%s" % item)
407
408     # run through the list until we find one that we can build
409     for fn in eligible:
410         oe.debug(2, "selecting %s to satisfy %s" % (fn, item))
411         if try_build(fn, item):
412             return 1
413
414     oe.note("no buildable providers for %s" % item)
415     return 0
416
417 def build_depgraph():
418     all_depends = Set()
419     pn_provides = {}
420
421     def progress(p):
422         if oedebug or progress.p == p: return 
423         progress.p = p
424         if os.isatty(sys.stdout.fileno()):
425             sys.stdout.write("\rNOTE: Building provider hash: [%s%s] (%02d%%)" % ( "#" * (p/5), " " * ( 20 - p/5 ), p ) )
426             sys.stdout.flush()
427         else:
428             if p == 0:
429                 sys.stdout.write("NOTE: Building provider hash, please wait...\n")
430             if p == 100:
431                 sys.stdout.write("done.\n")
432     progress.p = 0
433
434     def calc_oefile_priority(filename):
435         for (regex, pri) in oefile_config_priorities:
436             if regex.match(filename):
437                 return pri
438         return 0
439
440     # Handle PREFERRED_PROVIDERS
441     for p in (oe.data.getVar('PREFERRED_PROVIDERS', make.cfg, 1) or "").split():
442         (providee, provider) = p.split(':')
443         if __preferred.has_key(providee) and __preferred[providee] != provider:
444             oe.error("conflicting preferences for %s: both %s and %s specified" % (providee, provider, __preferred[providee]))
445         __preferred[providee] = provider
446
447     # Calculate priorities for each file
448     for p in make.pkgdata.keys():
449         oefile_priority[p] = calc_oefile_priority(p)
450     
451     n = len(make.pkgdata.keys())
452     i = 0
453
454     op = -1
455
456     oe.debug(1, "OEMAKE building providers hashes")
457
458     # Build forward and reverse provider hashes
459     # Forward: virtual -> [filenames]
460     # Reverse: PN -> [virtuals]
461     for f in make.pkgdata.keys():
462         d = make.pkgdata[f]
463
464         pn = oe.data.getVar('PN', d, 1)
465         provides = Set([pn] + (oe.data.getVar("PROVIDES", d, 1) or "").split())
466
467         if not pn_provides.has_key(pn):
468             pn_provides[pn] = Set()
469         pn_provides[pn] |= provides
470
471         for provide in provides:
472             if not providers.has_key(provide):
473                 providers[provide] = []
474             providers[provide].append(f)
475
476         deps = (oe.data.getVar("DEPENDS", d, 1) or "").split()
477         for dep in deps:
478             all_depends.add(dep)
479
480         i += 1
481         p = (100 * i) / n
482         if p != op:
483             op = p
484             progress(p)
485
486     if oedebug == 0:
487         sys.stdout.write("\n")
488
489     # Build package list for "oemake world"
490     oe.debug(1, "OEMAKE collating packages for \"world\"")
491     for f in make.pkgdata.keys():
492         d = make.pkgdata[f]
493         if oe.data.getVar('BROKEN', d, 1):
494             continue
495         terminal = True
496         pn = oe.data.getVar('PN', d, 1)
497         for p in pn_provides[pn]:
498             if p in all_depends or p.startswith('virtual/'):
499                 terminal = False
500                 break
501         if terminal:
502             __world_target.add(pn)
503
504 def myProgressCallback( x, y, f ):
505     if oedebug > 0:
506         return
507     if os.isatty(sys.stdout.fileno()):
508         sys.stdout.write("\rNOTE: Parsing .oe files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
509         sys.stdout.flush()
510     else:
511         if x == 1:
512             sys.stdout.write("Parsing .oe files, please wait...")
513             sys.stdout.flush()
514         if x == y:
515             sys.stdout.write("done.")
516             sys.stdout.flush()
517
518
519 #
520 # main
521 #
522
523 if __name__ == "__main__":
524
525     if "OEDEBUG" in os.environ:
526         oedebug = int(os.environ["OEDEBUG"])
527
528     make.options, args = handle_options( sys.argv )
529
530     if not make.options.cmd:
531         make.options.cmd = "build"
532
533     if make.options.cmd in __depcmds:
534         __depcmd=__depcmds[make.options.cmd]
535     else:
536         __depcmd=make.options.cmd
537
538     make.pkgdata = {}
539     make.cfg = oe.data.init()
540     providers = {}
541
542     for f in make.options.file:
543         try:
544             make.cfg = oe.parse.handle(f, make.cfg)
545         except IOError:
546             oe.fatal("Unable to open %s" % f)
547
548     try:
549         make.cfg = oe.parse.handle("conf/oe.conf", make.cfg)
550     except IOError:
551         oe.fatal("Unable to open oe.conf")
552
553     if not oe.data.getVar("BUILDNAME", make.cfg):
554         oe.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), make.cfg)
555
556     buildname = oe.data.getVar("BUILDNAME", make.cfg)
557
558     ignore = oe.data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
559     __ignored_dependencies = ignore.split()
560
561     collections = oe.data.getVar("OEFILE_COLLECTIONS", make.cfg, 1)
562     if collections:
563         collection_list = collections.split()
564         for c in collection_list:
565             regex = oe.data.getVar("OEFILE_PATTERN_%s" % c, make.cfg, 1)
566             if regex == None:
567                 oe.error("OEFILE_PATTERN_%s not defined" % c)
568                 continue
569             priority = oe.data.getVar("OEFILE_PRIORITY_%s" % c, make.cfg, 1)
570             if priority == None:
571                 oe.error("OEFILE_PRIORITY_%s not defined" % c)
572                 continue
573             try:
574                 cre = re.compile(regex)
575             except re.error:
576                 oe.error("OEFILE_PATTERN_%s \"%s\" is not a valid regular expression" % (c, regex))
577                 continue
578             try:
579                 pri = int(priority)
580                 oefile_config_priorities.append((cre, pri))
581             except ValueError:
582                 oe.error("invalid value for OEFILE_PRIORITY_%s: \"%s\"" % (c, priority))
583
584     pkgs_to_build = None
585     if args:
586         if not pkgs_to_build:
587             pkgs_to_build = []
588         pkgs_to_build.extend(args)
589     if not pkgs_to_build:
590             oepkgs = oe.data.getVar('OEPKGS', make.cfg, 1)
591             if oepkgs:
592                     pkgs_to_build = oepkgs.split()
593     if not pkgs_to_build and not make.options.show_versions:
594             print "Nothing to build. Use 'oemake world' to build everything."
595             sys.exit(0)
596
597     __stats["attempt"] = 0
598     __stats["success"] = 0
599     __stats["fail"] = 0
600     __stats["deps"] = 0
601
602     # Import Psyco if available and not disabled
603     if not make.options.disable_psyco:
604         try:
605             import psyco
606         except ImportError:
607             if oedebug == 0:
608                 oe.note("Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
609         else:
610             psyco.bind( make.collect_oefiles )
611     else:
612         oe.note("You have disabled Psyco. This decreases performance.")
613
614     try:
615         oe.debug(1, "OEMAKE collecting .oe files")
616         make.collect_oefiles( myProgressCallback )
617         oe.debug(1, "OEMAKE parsing complete")
618         if oedebug == 0:
619             print
620         if make.options.parse_only:
621             print "Requested parsing .oe files only.  Exiting."
622             sys.exit(0)
623
624         build_depgraph()
625
626         if make.options.show_versions:
627             showVersions()
628             sys.exit(0)
629             
630         if 'world' in pkgs_to_build:
631             pkgs_to_build.remove('world')
632             for t in __world_target:
633                 pkgs_to_build.append(t)
634
635         oe.event.fire(oe.event.BuildStarted(buildname, pkgs_to_build, make.cfg))
636
637         for k in pkgs_to_build:
638             failed = False
639             try:
640                 if buildPackage(k) == 0:
641                     # already diagnosed
642                     failed = True
643             except oe.build.EventException:
644                 oe.error("Build of " + k + " failed")
645                 failed = True
646
647             if failed:
648                 if make.options.abort:
649                     sys.exit(1)
650
651         oe.event.fire(oe.event.BuildCompleted(buildname, pkgs_to_build, make.cfg))
652
653         print "Build statistics:"
654         print "  Attempted builds: %d" % __stats["attempt"]
655         if __stats["fail"] != 0:
656             print "  Failed builds: %d" % __stats["fail"]
657         if __stats["deps"] != 0:
658             print "  Dependencies not satisfied: %d" % __stats["deps"]
659         if __stats["fail"] != 0 or __stats["deps"] != 0:
660             sys.exit(1)
661         sys.exit(0)
662
663     except KeyboardInterrupt:
664         print "\nNOTE: KeyboardInterrupt - Build not completed."
665         sys.exit(1)