Fix oemake 'world' logic bug.
[vuplus_bitbake] / 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) or oe.data.getVar('EXCLUDE_FROM_WORLD', d, 1):
494             oe.debug(2, "OEMAKE skipping %s due to BROKEN/EXCLUDE_FROM_WORLD" % f)
495             continue
496         terminal = True
497         pn = oe.data.getVar('PN', d, 1)
498         for p in pn_provides[pn]:
499             if p.startswith('virtual/'):
500                 oe.debug(2, "OEMAKE skipping %s due to %s provider starting with virtual/" % (f, p))
501                 terminal = False
502                 break
503             for pf in providers[p]:
504                 if oe.data.getVar('PN', make.pkgdata[pf], 1) != pn:
505                     oe.debug(2, "OEMAKE skipping %s due to both us and %s providing %s" % (f, pf, p))
506                     terminal = False
507                     break
508         if terminal:
509             __world_target.add(pn)
510
511 def myProgressCallback( x, y, f ):
512     if oedebug > 0:
513         return
514     if os.isatty(sys.stdout.fileno()):
515         sys.stdout.write("\rNOTE: Parsing .oe files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
516         sys.stdout.flush()
517     else:
518         if x == 1:
519             sys.stdout.write("Parsing .oe files, please wait...")
520             sys.stdout.flush()
521         if x == y:
522             sys.stdout.write("done.")
523             sys.stdout.flush()
524
525
526 #
527 # main
528 #
529
530 if __name__ == "__main__":
531
532     if "OEDEBUG" in os.environ:
533         oedebug = int(os.environ["OEDEBUG"])
534
535     make.options, args = handle_options( sys.argv )
536
537     if not make.options.cmd:
538         make.options.cmd = "build"
539
540     if make.options.cmd in __depcmds:
541         __depcmd=__depcmds[make.options.cmd]
542     else:
543         __depcmd=make.options.cmd
544
545     make.pkgdata = {}
546     make.cfg = oe.data.init()
547     providers = {}
548
549     for f in make.options.file:
550         try:
551             make.cfg = oe.parse.handle(f, make.cfg)
552         except IOError:
553             oe.fatal("Unable to open %s" % f)
554
555     try:
556         make.cfg = oe.parse.handle("conf/oe.conf", make.cfg)
557     except IOError:
558         oe.fatal("Unable to open oe.conf")
559
560     if not oe.data.getVar("BUILDNAME", make.cfg):
561         oe.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), make.cfg)
562
563     buildname = oe.data.getVar("BUILDNAME", make.cfg)
564
565     ignore = oe.data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
566     __ignored_dependencies = ignore.split()
567
568     collections = oe.data.getVar("OEFILE_COLLECTIONS", make.cfg, 1)
569     if collections:
570         collection_list = collections.split()
571         for c in collection_list:
572             regex = oe.data.getVar("OEFILE_PATTERN_%s" % c, make.cfg, 1)
573             if regex == None:
574                 oe.error("OEFILE_PATTERN_%s not defined" % c)
575                 continue
576             priority = oe.data.getVar("OEFILE_PRIORITY_%s" % c, make.cfg, 1)
577             if priority == None:
578                 oe.error("OEFILE_PRIORITY_%s not defined" % c)
579                 continue
580             try:
581                 cre = re.compile(regex)
582             except re.error:
583                 oe.error("OEFILE_PATTERN_%s \"%s\" is not a valid regular expression" % (c, regex))
584                 continue
585             try:
586                 pri = int(priority)
587                 oefile_config_priorities.append((cre, pri))
588             except ValueError:
589                 oe.error("invalid value for OEFILE_PRIORITY_%s: \"%s\"" % (c, priority))
590
591     pkgs_to_build = None
592     if args:
593         if not pkgs_to_build:
594             pkgs_to_build = []
595         pkgs_to_build.extend(args)
596     if not pkgs_to_build:
597             oepkgs = oe.data.getVar('OEPKGS', make.cfg, 1)
598             if oepkgs:
599                     pkgs_to_build = oepkgs.split()
600     if not pkgs_to_build and not make.options.show_versions:
601             print "Nothing to build. Use 'oemake world' to build everything."
602             sys.exit(0)
603
604     __stats["attempt"] = 0
605     __stats["success"] = 0
606     __stats["fail"] = 0
607     __stats["deps"] = 0
608
609     # Import Psyco if available and not disabled
610     if not make.options.disable_psyco:
611         try:
612             import psyco
613         except ImportError:
614             if oedebug == 0:
615                 oe.note("Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
616         else:
617             psyco.bind( make.collect_oefiles )
618     else:
619         oe.note("You have disabled Psyco. This decreases performance.")
620
621     try:
622         oe.debug(1, "OEMAKE collecting .oe files")
623         make.collect_oefiles( myProgressCallback )
624         oe.debug(1, "OEMAKE parsing complete")
625         if oedebug == 0:
626             print
627         if make.options.parse_only:
628             print "Requested parsing .oe files only.  Exiting."
629             sys.exit(0)
630
631         build_depgraph()
632
633         if make.options.show_versions:
634             showVersions()
635             sys.exit(0)
636             
637         if 'world' in pkgs_to_build:
638             pkgs_to_build.remove('world')
639             for t in __world_target:
640                 pkgs_to_build.append(t)
641
642         oe.event.fire(oe.event.BuildStarted(buildname, pkgs_to_build, make.cfg))
643
644         for k in pkgs_to_build:
645             failed = False
646             try:
647                 if buildPackage(k) == 0:
648                     # already diagnosed
649                     failed = True
650             except oe.build.EventException:
651                 oe.error("Build of " + k + " failed")
652                 failed = True
653
654             if failed:
655                 if make.options.abort:
656                     sys.exit(1)
657
658         oe.event.fire(oe.event.BuildCompleted(buildname, pkgs_to_build, make.cfg))
659
660         print "Build statistics:"
661         print "  Attempted builds: %d" % __stats["attempt"]
662         if __stats["fail"] != 0:
663             print "  Failed builds: %d" % __stats["fail"]
664         if __stats["deps"] != 0:
665             print "  Dependencies not satisfied: %d" % __stats["deps"]
666         if __stats["fail"] != 0 or __stats["deps"] != 0:
667             sys.exit(1)
668         sys.exit(0)
669
670     except KeyboardInterrupt:
671         print "\nNOTE: KeyboardInterrupt - Build not completed."
672         sys.exit(1)