fix formatting of diagnostic message
[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_f = None
230             for f in files:
231                 the_data = make.pkgdata[f]
232                 pv = oe.data.getVar('PV', the_data, 1)
233                 pr = oe.data.getVar('PR', the_data, 1)
234                 if (latest is None) or (make.vercmp(latest, (pv, pr)) < 0):
235                     latest = (pv, pr)
236                     latest_f = f
237             preferred_file = latest_f
238             preferred_ver = latest
239
240             oe.debug(1, "selecting %s as latest version of provider %s" % (preferred_file, pn))
241
242         preferred_versions[pn] = (preferred_ver, preferred_file)
243         eligible.append(preferred_file)
244
245     for p in eligible:
246         if p in __build_cache_fail:
247             oe.debug(1, "rejecting already-failed %s" % p)
248             eligible.remove(p)
249
250     if len(eligible) == 0:
251         oe.error("no eligible providers for %s" % item)
252         return 0
253
254     # look to see if one of them is already staged, or marked as preferred.
255     # if so, bump it to the head of the queue
256     for p in all_p:
257         the_data = make.pkgdata[p]
258         pn = oe.data.getVar('PN', the_data, 1)
259         pv = oe.data.getVar('PV', the_data, 1)
260         pr = oe.data.getVar('PR', the_data, 1)
261         tmpdir = oe.data.getVar('TMPDIR', the_data, 1)
262         stamp = '%s/stamps/%s-%s-%s.do_populate_staging' % (tmpdir, pn, pv, pr)
263         if os.path.exists(stamp):
264             (newvers, fn) = preferred_versions[pn]
265             if not fn in eligible:
266                 # package was made ineligible by already-failed check
267                 continue
268             oldver = "%s-%s" % (pv, pr)
269             newver = '-'.join(newvers)
270             if (newver != oldver):
271                 extra_chat = "; upgrading from %s to %s" % (oldver, newver)
272             else:
273                 extra_chat = ""
274             if make.options.verbose:
275                 oe.note("selecting already-staged %s to satisfy %s%s" % (pn, item, extra_chat))
276             eligible.remove(fn)
277             eligible = [fn] + eligible
278             discriminated = True
279             break
280
281     prefervar = oe.data.getVar('PREFERRED_PROVIDER_%s' % item, make.cfg, 1)
282     if prefervar:
283         __preferred[item] = prefervar
284
285     if __preferred.has_key(item):
286         for p in eligible:
287             the_data = make.pkgdata[p]
288             pn = oe.data.getVar('PN', the_data, 1)
289             if __preferred[item] == pn:
290                 if make.options.verbose:
291                     oe.note("selecting %s to satisfy %s due to PREFERRED_PROVIDERS" % (pn, item))
292                 eligible.remove(p)
293                 eligible = [p] + eligible
294                 discriminated = True
295                 break
296
297     if len(eligible) > 1 and discriminated == False:
298         providers_list = []
299         for fn in eligible:
300             providers_list.append(oe.data.getVar('PN', make.pkgdata[fn], 1))
301         oe.note("multiple providers are available (%s);" % ", ".join(providers_list))
302         oe.note("consider defining PREFERRED_PROVIDER_%s" % item)
303
304     # run through the list until we find one that we can build
305     for fn in eligible:
306         oe.debug(2, "selecting %s to satisfy %s" % (fn, item))
307         if try_build(fn, item):
308             return 1
309
310     oe.note("no buildable providers for %s" % item)
311     return 0
312
313 def build_depgraph():
314     all_depends = Set()
315     pn_provides = {}
316
317     def progress(p):
318         if oedebug:
319             return
320         if os.isatty(sys.stdout.fileno()):
321             sys.stdout.write("\rNOTE: Building provider hash: [")
322             x = 0
323             for i in range(20):
324                 if (p >= x):
325                     sys.stdout.write("#")
326                 else:
327                     sys.stdout.write(" ")
328                 x += 5
329             sys.stdout.write("] (%02d%%)" % p)
330         else:
331             if p == 0:
332                 sys.stdout.write("\rNOTE: Building provider hash, please wait...")
333                 sys.stdout.flush()
334             if p == 100:
335                 sys.stdout.write("done.")
336                 sys.stdout.flush()
337
338     def calc_oefile_priority(filename):
339         for (regex, pri) in oefile_config_priorities:
340             if regex.match(filename):
341                 return pri
342         return 0
343
344     # Handle PREFERRED_PROVIDERS
345     for p in (oe.data.getVar('PREFERRED_PROVIDERS', make.cfg, 1) or "").split():
346         (providee, provider) = p.split(':')
347         if __preferred.has_key(providee) and __preferred[providee] != provider:
348             oe.error("conflicting preferences for %s: both %s and %s specified" % (providee, provider, __preferred[providee]))
349         __preferred[providee] = provider
350
351     # Calculate priorities for each file
352     for p in make.pkgdata.keys():
353         oefile_priority[p] = calc_oefile_priority(p)
354     
355     n = len(make.pkgdata.keys())
356     i = 0
357
358     op = -1
359
360     oe.debug(1, "OEMAKE building providers hashes")
361
362     # Build forward and reverse provider hashes
363     # Forward: virtual -> [filenames]
364     # Reverse: PN -> [virtuals]
365     for f in make.pkgdata.keys():
366         d = make.pkgdata[f]
367
368         pn = oe.data.getVar('PN', d, 1)
369         provides = Set([pn] + (oe.data.getVar("PROVIDES", d, 1) or "").split())
370
371         if not pn_provides.has_key(pn):
372             pn_provides[pn] = Set()
373         pn_provides[pn] |= provides
374
375         for provide in provides:
376             if not providers.has_key(provide):
377                 providers[provide] = []
378             providers[provide].append(f)
379
380         deps = (oe.data.getVar("DEPENDS", d, 1) or "").split()
381         for dep in deps:
382             all_depends.add(dep)
383
384         i += 1
385         p = (100 * i) / n
386         if p != op:
387             op = p
388             progress(p)
389
390     if oedebug == 0:
391         sys.stdout.write("\n")
392
393     # Build package list for "oemake world"
394     oe.debug(1, "OEMAKE collating packages for \"world\"")
395     for f in make.pkgdata.keys():
396         d = make.pkgdata[f]
397         if oe.data.getVar('BROKEN', d, 1):
398             continue
399         terminal = True
400         pn = oe.data.getVar('PN', d, 1)
401         for p in pn_provides[pn]:
402             if p in all_depends or p.startswith('virtual/'):
403                 terminal = False
404                 break
405         if terminal:
406             __world_target.add(pn)
407
408 def myProgressCallback( x, y, f ):
409     if oedebug > 0:
410         return
411     if os.isatty(sys.stdout.fileno()):
412         sys.stdout.write("\rNOTE: Parsing .oe files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
413         sys.stdout.flush()
414     else:
415         if x == 1:
416             sys.stdout.write("Parsing .oe files, please wait...")
417             sys.stdout.flush()
418         if x == y:
419             sys.stdout.write("done.")
420             sys.stdout.flush()
421
422
423 #
424 # main
425 #
426
427 if __name__ == "__main__":
428
429     if "OEDEBUG" in os.environ:
430         oedebug = int(os.environ["OEDEBUG"])
431
432     make.options, args = handle_options( sys.argv )
433
434     if not make.options.cmd:
435         make.options.cmd = "build"
436
437     if make.options.cmd in __depcmds:
438         __depcmd=__depcmds[make.options.cmd]
439     else:
440         __depcmd=make.options.cmd
441
442     make.pkgdata = {}
443     make.cfg = {}
444     providers = {}
445
446     for f in make.options.file:
447         try:
448             make.cfg = oe.parse.handle(f, make.cfg)
449         except IOError:
450             oe.fatal("Unable to open %s" % f)
451
452     try:
453         make.cfg = oe.parse.handle("conf/oe.conf", make.cfg)
454     except IOError:
455         oe.fatal("Unable to open oe.conf")
456
457     if not oe.data.getVar("BUILDNAME", make.cfg):
458         oe.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), make.cfg)
459
460     buildname = oe.data.getVar("BUILDNAME", make.cfg)
461
462     ignore = oe.data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
463     __ignored_dependencies = ignore.split()
464
465     collections = oe.data.getVar("OEFILE_COLLECTIONS", make.cfg, 1)
466     if collections:
467         collection_list = collections.split()
468         for c in collection_list:
469             regex = oe.data.getVar("OEFILE_PATTERN_%s" % c, make.cfg, 1)
470             if regex == None:
471                 oe.error("OEFILE_PATTERN_%s not defined" % c)
472                 continue
473             priority = oe.data.getVar("OEFILE_PRIORITY_%s" % c, make.cfg, 1)
474             if priority == None:
475                 oe.error("OEFILE_PRIORITY_%s not defined" % c)
476                 continue
477             try:
478                 cre = re.compile(regex)
479             except re.error:
480                 oe.error("OEFILE_PATTERN_%s \"%s\" is not a valid regular expression" % (c, regex))
481                 continue
482             try:
483                 pri = int(priority)
484                 oefile_config_priorities.append((cre, pri))
485             except ValueError:
486                 oe.error("invalid value for OEFILE_PRIORITY_%s: \"%s\"" % (c, priority))
487
488     pkgs_to_build = None
489     if args:
490         if not pkgs_to_build:
491             pkgs_to_build = []
492         pkgs_to_build.extend(args)
493     if not pkgs_to_build:
494             oepkgs = oe.data.getVar('OEPKGS', make.cfg, 1)
495             if oepkgs:
496                     pkgs_to_build = oepkgs.split()
497     if not pkgs_to_build:
498             print "Nothing to build. Use 'oemake world' to build everything."
499             sys.exit(0)
500
501     __stats["attempt"] = 0
502     __stats["success"] = 0
503     __stats["fail"] = 0
504     __stats["deps"] = 0
505
506     # Import Psyco if available and not disabled
507     if not make.options.disable_psyco:
508         try:
509             import psyco
510         except ImportError:
511             if oedebug == 0:
512                 oe.note("Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
513         else:
514             psyco.bind( make.collect_oefiles )
515     else:
516         oe.note("You have disabled Psyco. This decreases performance.")
517
518     try:
519         oe.debug(1, "OEMAKE collecting .oe files")
520         make.collect_oefiles( myProgressCallback )
521         oe.debug(1, "OEMAKE parsing complete")
522         if oedebug == 0:
523             print
524         if make.options.parse_only:
525             print "Requested parsing .oe files only.  Exiting."
526             sys.exit(0)
527         build_depgraph()
528
529         if 'world' in pkgs_to_build:
530             pkgs_to_build.remove('world')
531             for t in __world_target:
532                 pkgs_to_build.append(t)
533
534         oe.event.fire(oe.event.BuildStarted(buildname, pkgs_to_build, make.cfg))
535
536         for k in pkgs_to_build:
537             failed = False
538             try:
539                 if buildPackage(k) == 0:
540                     # already diagnosed
541                     failed = True
542             except oe.build.EventException:
543                 oe.error("Build of " + k + " failed")
544                 failed = True
545
546             if failed:
547                 if make.options.abort:
548                     sys.exit(1)
549
550         oe.event.fire(oe.event.BuildCompleted(buildname, pkgs_to_build, make.cfg))
551
552         print "Build statistics:"
553         print "  Attempted builds: %d" % __stats["attempt"]
554         if __stats["fail"] != 0:
555             print "  Failed builds: %d" % __stats["fail"]
556         if __stats["deps"] != 0:
557             print "  Dependencies not satisfied: %d" % __stats["deps"]
558         if __stats["fail"] != 0 or __stats["deps"] != 0:
559             sys.exit(1)
560         sys.exit(0)
561
562     except KeyboardInterrupt:
563         print "\nNOTE: KeyboardInterrupt - Build not completed."