improve keyboard error handling
[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     options, args = parser.parse_args( args )
75     return options, args[1:]
76
77 def try_build(fn, virtual):
78     if fn in __building_list:
79         oe.error("%s depends on itself (eventually)" % fn)
80         oe.error("upwards chain is: %s" % (" -> ".join(__build_path)))
81         return False
82
83     __building_list.append(fn)
84
85     the_data = make.pkgdata[fn]
86     item = oe.data.getVar('PN', the_data, 1)
87     pathstr = "%s (%s)" % (item, virtual)
88     __build_path.append(pathstr)
89
90     depends_list = (oe.data.getVar('DEPENDS', the_data, 1) or "").split()
91     if make.options.verbose:
92         oe.note("current path: %s" % (" -> ".join(__build_path)))
93         oe.note("dependencies for %s are: %s" % (item, " ".join(depends_list)))
94
95     try:
96         failed = False
97
98         if __depcmd:
99             oldcmd = make.options.cmd
100             make.options.cmd = __depcmd
101
102         for d in depends_list:
103             if d in __ignored_dependencies:
104                 continue
105             if not __depcmd:
106                 continue
107             if buildPackage(d) == 0:
108                 oe.error("dependency %s (for %s) not satisfied" % (d,item))
109                 failed = True
110                 if make.options.abort:
111                     break
112
113         if __depcmd:
114             make.options.cmd = oldcmd
115
116         if failed:
117             __stats["deps"] += 1
118             return False
119
120         oe.event.fire(oe.event.PkgStarted(item, make.pkgdata[fn]))
121         try:
122             __stats["attempt"] += 1
123             if not make.options.dry_run:
124                 oe.build.exec_task('do_%s' % make.options.cmd, make.pkgdata[fn])
125             oe.event.fire(oe.event.PkgSucceeded(item, make.pkgdata[fn]))
126             __build_cache.append(fn)
127             return True
128         except oe.build.FuncFailed:
129             __stats["fail"] += 1
130             oe.error("task stack execution failed")
131             oe.event.fire(oe.event.PkgFailed(item, make.pkgdata[fn]))
132             __build_cache_fail.append(fn)
133             raise
134         except oe.build.EventException:
135             __stats["fail"] += 1
136             (type, value, traceback) = sys.exc_info()
137             e = value.event
138             oe.error("%s event exception, aborting" % oe.event.getName(e))
139             oe.event.fire(oe.event.PkgFailed(item, make.pkgdata[fn]))
140             __build_cache_fail.append(fn)
141             raise
142     finally:
143         __building_list.remove(fn)
144         __build_path.remove(pathstr)
145
146 def buildPackage(item):
147     fn = None
148
149     discriminated = False
150
151     if not providers.has_key(item):
152         oe.error("Nothing provides %s" % item)
153         return 0
154
155     all_p = providers[item]
156
157     for p in all_p:
158         if p in __build_cache:
159             oe.debug(1, "already built %s in this run\n" % p)
160             return 1
161
162     eligible = []
163     preferred_versions = {}
164
165     # Collate providers by PN
166     pkg_pn = {}
167     for p in all_p:
168         the_data = make.pkgdata[p]
169         pn = oe.data.getVar('PN', the_data, 1)
170         if not pkg_pn.has_key(pn):
171             pkg_pn[pn] = []
172         pkg_pn[pn].append(p)
173
174     oe.debug(1, "providers for %s are: %s" % (item, pkg_pn.keys()))
175
176     # Sort by priority
177     for pn in pkg_pn.keys():
178         files = pkg_pn[pn]
179         priorities = {}
180         for f in files:
181             priority = oefile_priority[f]
182             if not priorities.has_key(priority):
183                 priorities[priority] = []
184             priorities[priority].append(f)
185         p_list = priorities.keys()
186         p_list.sort(lambda a, b: a - b)
187         pkg_pn[pn] = []
188         for p in p_list:
189             pkg_pn[pn] = [ priorities[p] ] + pkg_pn[pn]
190
191     # If there is a PREFERRED_VERSION, find the highest-priority oefile providing that
192     # version.  If not, find the latest version provided by an oefile in the
193     # highest-priority set.
194     for pn in pkg_pn.keys():
195         preferred_file = None
196         
197         preferred_v = oe.data.getVar('PREFERRED_VERSION_%s' % pn, make.cfg, 1)
198         if preferred_v:
199             preferred_r = None
200             m = re.match('(.*)-(.*)', preferred_v)
201             if m:
202                 preferred_v = m.group(1)
203                 preferred_r = m.group(2)
204                 
205             for file_set in pkg_pn[pn]:
206                 for f in file_set:
207                     the_data = make.pkgdata[f]
208                     pv = oe.data.getVar('PV', the_data, 1)
209                     pr = oe.data.getVar('PR', the_data, 1)
210                     if preferred_v == pv and (preferred_r == pr or preferred_r == None):
211                         preferred_file = f
212                         preferred_ver = (pv, pr)
213                         break
214                 if preferred_file:
215                     break
216             if preferred_r:
217                 pv_str = '%s-%s' % (preferred_v, preferred_r)
218             else:
219                 pv_str = preferred_v
220             if preferred_file is None:
221                 oe.note("preferred version %s of %s not available" % (pv_str, pn))
222             else:
223                 oe.debug(1, "selecting %s as PREFERRED_VERSION %s of package %s" % (preferred_file, pv_str, pn))
224                 
225         if preferred_file is None:
226             # get highest priority file set
227             files = pkg_pn[pn][0]
228             latest = None
229             latest_p = 0
230             latest_f = None
231             for f in files:
232                 the_data = make.pkgdata[f]
233                 pv = oe.data.getVar('PV', the_data, 1)
234                 pr = oe.data.getVar('PR', the_data, 1)
235                 dp = int(oe.data.getVar('DEFAULT_PREFERENCE', the_data, 1) or "0")
236
237                 if (latest is None) or ((latest_p == dp) and (make.vercmp(latest, (pv, pr)) < 0)) or (dp > latest_p):
238                     latest = (pv, pr)
239                     latest_f = f
240                     latest_p = dp
241             preferred_file = latest_f
242             preferred_ver = latest
243             
244             oe.debug(1, "selecting %s as latest version of provider %s" % (preferred_file, pn))
245
246         preferred_versions[pn] = (preferred_ver, preferred_file)
247         eligible.append(preferred_file)
248
249     for p in eligible:
250         if p in __build_cache_fail:
251             oe.debug(1, "rejecting already-failed %s" % p)
252             eligible.remove(p)
253
254     if len(eligible) == 0:
255         oe.error("no eligible providers for %s" % item)
256         return 0
257
258     # look to see if one of them is already staged, or marked as preferred.
259     # if so, bump it to the head of the queue
260     for p in all_p:
261         the_data = make.pkgdata[p]
262         pn = oe.data.getVar('PN', the_data, 1)
263         pv = oe.data.getVar('PV', the_data, 1)
264         pr = oe.data.getVar('PR', the_data, 1)
265         tmpdir = oe.data.getVar('TMPDIR', the_data, 1)
266         stamp = '%s/stamps/%s-%s-%s.do_populate_staging' % (tmpdir, pn, pv, pr)
267         if os.path.exists(stamp):
268             (newvers, fn) = preferred_versions[pn]
269             if not fn in eligible:
270                 # package was made ineligible by already-failed check
271                 continue
272             oldver = "%s-%s" % (pv, pr)
273             newver = '-'.join(newvers)
274             if (newver != oldver):
275                 extra_chat = "; upgrading from %s to %s" % (oldver, newver)
276             else:
277                 extra_chat = ""
278             if make.options.verbose:
279                 oe.note("selecting already-staged %s to satisfy %s%s" % (pn, item, extra_chat))
280             eligible.remove(fn)
281             eligible = [fn] + eligible
282             discriminated = True
283             break
284
285     prefervar = oe.data.getVar('PREFERRED_PROVIDER_%s' % item, make.cfg, 1)
286     if prefervar:
287         __preferred[item] = prefervar
288
289     if __preferred.has_key(item):
290         for p in eligible:
291             the_data = make.pkgdata[p]
292             pn = oe.data.getVar('PN', the_data, 1)
293             if __preferred[item] == pn:
294                 if make.options.verbose:
295                     oe.note("selecting %s to satisfy %s due to PREFERRED_PROVIDERS" % (pn, item))
296                 eligible.remove(p)
297                 eligible = [p] + eligible
298                 discriminated = True
299                 break
300
301     if len(eligible) > 1 and discriminated == False:
302         providers_list = []
303         for fn in eligible:
304             providers_list.append(oe.data.getVar('PN', make.pkgdata[fn], 1))
305         oe.note("multiple providers are available (%s);" % ", ".join(providers_list))
306         oe.note("consider defining PREFERRED_PROVIDER_%s" % item)
307
308     # run through the list until we find one that we can build
309     for fn in eligible:
310         oe.debug(2, "selecting %s to satisfy %s" % (fn, item))
311         if try_build(fn, item):
312             return 1
313
314     oe.note("no buildable providers for %s" % item)
315     return 0
316
317 def build_depgraph():
318     all_depends = Set()
319     pn_provides = {}
320
321     def progress(p):
322         if oedebug or progress.p == p: return 
323         progress.p = p
324         if os.isatty(sys.stdout.fileno()):
325             sys.stdout.write("\rNOTE: Building provider hash: [%s%s] (%02d%%)" % ( "#" * (p/5), " " * ( 20 - p/5 ), p ) )
326             sys.stdout.flush()
327         else:
328             if p == 0:
329                 sys.stdout.write("NOTE: Building provider hash, please wait...\n")
330             if p == 100:
331                 sys.stdout.write("done.\n")
332     progress.p = 0
333
334     def calc_oefile_priority(filename):
335         for (regex, pri) in oefile_config_priorities:
336             if regex.match(filename):
337                 return pri
338         return 0
339
340     # Handle PREFERRED_PROVIDERS
341     for p in (oe.data.getVar('PREFERRED_PROVIDERS', make.cfg, 1) or "").split():
342         (providee, provider) = p.split(':')
343         if __preferred.has_key(providee) and __preferred[providee] != provider:
344             oe.error("conflicting preferences for %s: both %s and %s specified" % (providee, provider, __preferred[providee]))
345         __preferred[providee] = provider
346
347     # Calculate priorities for each file
348     for p in make.pkgdata.keys():
349         oefile_priority[p] = calc_oefile_priority(p)
350     
351     n = len(make.pkgdata.keys())
352     i = 0
353
354     op = -1
355
356     oe.debug(1, "OEMAKE building providers hashes")
357
358     # Build forward and reverse provider hashes
359     # Forward: virtual -> [filenames]
360     # Reverse: PN -> [virtuals]
361     for f in make.pkgdata.keys():
362         d = make.pkgdata[f]
363
364         pn = oe.data.getVar('PN', d, 1)
365         provides = Set([pn] + (oe.data.getVar("PROVIDES", d, 1) or "").split())
366
367         if not pn_provides.has_key(pn):
368             pn_provides[pn] = Set()
369         pn_provides[pn] |= provides
370
371         for provide in provides:
372             if not providers.has_key(provide):
373                 providers[provide] = []
374             providers[provide].append(f)
375
376         deps = (oe.data.getVar("DEPENDS", d, 1) or "").split()
377         for dep in deps:
378             all_depends.add(dep)
379
380         i += 1
381         p = (100 * i) / n
382         if p != op:
383             op = p
384             progress(p)
385
386     if oedebug == 0:
387         sys.stdout.write("\n")
388
389     # Build package list for "oemake world"
390     oe.debug(1, "OEMAKE collating packages for \"world\"")
391     for f in make.pkgdata.keys():
392         d = make.pkgdata[f]
393         if oe.data.getVar('BROKEN', d, 1):
394             continue
395         terminal = True
396         pn = oe.data.getVar('PN', d, 1)
397         for p in pn_provides[pn]:
398             if p in all_depends or p.startswith('virtual/'):
399                 terminal = False
400                 break
401         if terminal:
402             __world_target.add(pn)
403
404 def myProgressCallback( x, y, f ):
405     if oedebug > 0:
406         return
407     if os.isatty(sys.stdout.fileno()):
408         sys.stdout.write("\rNOTE: Parsing .oe files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
409         sys.stdout.flush()
410     else:
411         if x == 1:
412             sys.stdout.write("Parsing .oe files, please wait...")
413             sys.stdout.flush()
414         if x == y:
415             sys.stdout.write("done.")
416             sys.stdout.flush()
417
418
419 #
420 # main
421 #
422
423 if __name__ == "__main__":
424
425     if "OEDEBUG" in os.environ:
426         oedebug = int(os.environ["OEDEBUG"])
427
428     make.options, args = handle_options( sys.argv )
429
430     if not make.options.cmd:
431         make.options.cmd = "build"
432
433     if make.options.cmd in __depcmds:
434         __depcmd=__depcmds[make.options.cmd]
435     else:
436         __depcmd=make.options.cmd
437
438     make.pkgdata = {}
439     make.cfg = {}
440     providers = {}
441
442     for f in make.options.file:
443         try:
444             make.cfg = oe.parse.handle(f, make.cfg)
445         except IOError:
446             oe.fatal("Unable to open %s" % f)
447
448     try:
449         make.cfg = oe.parse.handle("conf/oe.conf", make.cfg)
450     except IOError:
451         oe.fatal("Unable to open oe.conf")
452
453     if not oe.data.getVar("BUILDNAME", make.cfg):
454         oe.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), make.cfg)
455
456     buildname = oe.data.getVar("BUILDNAME", make.cfg)
457
458     ignore = oe.data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
459     __ignored_dependencies = ignore.split()
460
461     collections = oe.data.getVar("OEFILE_COLLECTIONS", make.cfg, 1)
462     if collections:
463         collection_list = collections.split()
464         for c in collection_list:
465             regex = oe.data.getVar("OEFILE_PATTERN_%s" % c, make.cfg, 1)
466             if regex == None:
467                 oe.error("OEFILE_PATTERN_%s not defined" % c)
468                 continue
469             priority = oe.data.getVar("OEFILE_PRIORITY_%s" % c, make.cfg, 1)
470             if priority == None:
471                 oe.error("OEFILE_PRIORITY_%s not defined" % c)
472                 continue
473             try:
474                 cre = re.compile(regex)
475             except re.error:
476                 oe.error("OEFILE_PATTERN_%s \"%s\" is not a valid regular expression" % (c, regex))
477                 continue
478             try:
479                 pri = int(priority)
480                 oefile_config_priorities.append((cre, pri))
481             except ValueError:
482                 oe.error("invalid value for OEFILE_PRIORITY_%s: \"%s\"" % (c, priority))
483
484     pkgs_to_build = None
485     if args:
486         if not pkgs_to_build:
487             pkgs_to_build = []
488         pkgs_to_build.extend(args)
489     if not pkgs_to_build:
490             oepkgs = oe.data.getVar('OEPKGS', make.cfg, 1)
491             if oepkgs:
492                     pkgs_to_build = oepkgs.split()
493     if not pkgs_to_build:
494             print "Nothing to build. Use 'oemake world' to build everything."
495             sys.exit(0)
496
497     __stats["attempt"] = 0
498     __stats["success"] = 0
499     __stats["fail"] = 0
500     __stats["deps"] = 0
501
502     # Import Psyco if available and not disabled
503     if not make.options.disable_psyco:
504         try:
505             import psyco
506         except ImportError:
507             if oedebug == 0:
508                 oe.note("Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
509         else:
510             psyco.bind( make.collect_oefiles )
511     else:
512         oe.note("You have disabled Psyco. This decreases performance.")
513
514     try:
515         oe.debug(1, "OEMAKE collecting .oe files")
516         make.collect_oefiles( myProgressCallback )
517         oe.debug(1, "OEMAKE parsing complete")
518         if oedebug == 0:
519             print
520         if make.options.parse_only:
521             print "Requested parsing .oe files only.  Exiting."
522             sys.exit(0)
523         build_depgraph()
524
525         if 'world' in pkgs_to_build:
526             pkgs_to_build.remove('world')
527             for t in __world_target:
528                 pkgs_to_build.append(t)
529
530         oe.event.fire(oe.event.BuildStarted(buildname, pkgs_to_build, make.cfg))
531
532         for k in pkgs_to_build:
533             failed = False
534             try:
535                 if buildPackage(k) == 0:
536                     # already diagnosed
537                     failed = True
538             except oe.build.EventException:
539                 oe.error("Build of " + k + " failed")
540                 failed = True
541
542             if failed:
543                 if make.options.abort:
544                     sys.exit(1)
545
546         oe.event.fire(oe.event.BuildCompleted(buildname, pkgs_to_build, make.cfg))
547
548         print "Build statistics:"
549         print "  Attempted builds: %d" % __stats["attempt"]
550         if __stats["fail"] != 0:
551             print "  Failed builds: %d" % __stats["fail"]
552         if __stats["deps"] != 0:
553             print "  Dependencies not satisfied: %d" % __stats["deps"]
554         if __stats["fail"] != 0 or __stats["deps"] != 0:
555             sys.exit(1)
556         sys.exit(0)
557
558     except KeyboardInterrupt:
559         print "\nNOTE: KeyboardInterrupt - Build not completed."
560         sys.exit(1)