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