slightly improve provider hash progress output
[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 or progress.p == p: return 
319         progress.p = p
320         if os.isatty(sys.stdout.fileno()):
321             sys.stdout.write("\rNOTE: Building provider hash: [%s%s] (%02d%%)" % ( "#" * (p/5), " " * ( 20 - p/5 ), p ) )
322             sys.stdout.flush()
323         else:
324             if p == 0:
325                 sys.stdout.write("NOTE: Building provider hash, please wait...\n")
326             if p == 100:
327                 sys.stdout.write("done.\n")
328     progress.p = 0
329
330     def calc_oefile_priority(filename):
331         for (regex, pri) in oefile_config_priorities:
332             if regex.match(filename):
333                 return pri
334         return 0
335
336     # Handle PREFERRED_PROVIDERS
337     for p in (oe.data.getVar('PREFERRED_PROVIDERS', make.cfg, 1) or "").split():
338         (providee, provider) = p.split(':')
339         if __preferred.has_key(providee) and __preferred[providee] != provider:
340             oe.error("conflicting preferences for %s: both %s and %s specified" % (providee, provider, __preferred[providee]))
341         __preferred[providee] = provider
342
343     # Calculate priorities for each file
344     for p in make.pkgdata.keys():
345         oefile_priority[p] = calc_oefile_priority(p)
346     
347     n = len(make.pkgdata.keys())
348     i = 0
349
350     op = -1
351
352     oe.debug(1, "OEMAKE building providers hashes")
353
354     # Build forward and reverse provider hashes
355     # Forward: virtual -> [filenames]
356     # Reverse: PN -> [virtuals]
357     for f in make.pkgdata.keys():
358         d = make.pkgdata[f]
359
360         pn = oe.data.getVar('PN', d, 1)
361         provides = Set([pn] + (oe.data.getVar("PROVIDES", d, 1) or "").split())
362
363         if not pn_provides.has_key(pn):
364             pn_provides[pn] = Set()
365         pn_provides[pn] |= provides
366
367         for provide in provides:
368             if not providers.has_key(provide):
369                 providers[provide] = []
370             providers[provide].append(f)
371
372         deps = (oe.data.getVar("DEPENDS", d, 1) or "").split()
373         for dep in deps:
374             all_depends.add(dep)
375
376         i += 1
377         p = (100 * i) / n
378         if p != op:
379             op = p
380             progress(p)
381
382     if oedebug == 0:
383         sys.stdout.write("\n")
384
385     # Build package list for "oemake world"
386     oe.debug(1, "OEMAKE collating packages for \"world\"")
387     for f in make.pkgdata.keys():
388         d = make.pkgdata[f]
389         if oe.data.getVar('BROKEN', d, 1):
390             continue
391         terminal = True
392         pn = oe.data.getVar('PN', d, 1)
393         for p in pn_provides[pn]:
394             if p in all_depends or p.startswith('virtual/'):
395                 terminal = False
396                 break
397         if terminal:
398             __world_target.add(pn)
399
400 def myProgressCallback( x, y, f ):
401     if oedebug > 0:
402         return
403     if os.isatty(sys.stdout.fileno()):
404         sys.stdout.write("\rNOTE: Parsing .oe files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
405         sys.stdout.flush()
406     else:
407         if x == 1:
408             sys.stdout.write("Parsing .oe files, please wait...")
409             sys.stdout.flush()
410         if x == y:
411             sys.stdout.write("done.")
412             sys.stdout.flush()
413
414
415 #
416 # main
417 #
418
419 if __name__ == "__main__":
420
421     if "OEDEBUG" in os.environ:
422         oedebug = int(os.environ["OEDEBUG"])
423
424     make.options, args = handle_options( sys.argv )
425
426     if not make.options.cmd:
427         make.options.cmd = "build"
428
429     if make.options.cmd in __depcmds:
430         __depcmd=__depcmds[make.options.cmd]
431     else:
432         __depcmd=make.options.cmd
433
434     make.pkgdata = {}
435     make.cfg = {}
436     providers = {}
437
438     for f in make.options.file:
439         try:
440             make.cfg = oe.parse.handle(f, make.cfg)
441         except IOError:
442             oe.fatal("Unable to open %s" % f)
443
444     try:
445         make.cfg = oe.parse.handle("conf/oe.conf", make.cfg)
446     except IOError:
447         oe.fatal("Unable to open oe.conf")
448
449     if not oe.data.getVar("BUILDNAME", make.cfg):
450         oe.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), make.cfg)
451
452     buildname = oe.data.getVar("BUILDNAME", make.cfg)
453
454     ignore = oe.data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
455     __ignored_dependencies = ignore.split()
456
457     collections = oe.data.getVar("OEFILE_COLLECTIONS", make.cfg, 1)
458     if collections:
459         collection_list = collections.split()
460         for c in collection_list:
461             regex = oe.data.getVar("OEFILE_PATTERN_%s" % c, make.cfg, 1)
462             if regex == None:
463                 oe.error("OEFILE_PATTERN_%s not defined" % c)
464                 continue
465             priority = oe.data.getVar("OEFILE_PRIORITY_%s" % c, make.cfg, 1)
466             if priority == None:
467                 oe.error("OEFILE_PRIORITY_%s not defined" % c)
468                 continue
469             try:
470                 cre = re.compile(regex)
471             except re.error:
472                 oe.error("OEFILE_PATTERN_%s \"%s\" is not a valid regular expression" % (c, regex))
473                 continue
474             try:
475                 pri = int(priority)
476                 oefile_config_priorities.append((cre, pri))
477             except ValueError:
478                 oe.error("invalid value for OEFILE_PRIORITY_%s: \"%s\"" % (c, priority))
479
480     pkgs_to_build = None
481     if args:
482         if not pkgs_to_build:
483             pkgs_to_build = []
484         pkgs_to_build.extend(args)
485     if not pkgs_to_build:
486             oepkgs = oe.data.getVar('OEPKGS', make.cfg, 1)
487             if oepkgs:
488                     pkgs_to_build = oepkgs.split()
489     if not pkgs_to_build:
490             print "Nothing to build. Use 'oemake world' to build everything."
491             sys.exit(0)
492
493     __stats["attempt"] = 0
494     __stats["success"] = 0
495     __stats["fail"] = 0
496     __stats["deps"] = 0
497
498     # Import Psyco if available and not disabled
499     if not make.options.disable_psyco:
500         try:
501             import psyco
502         except ImportError:
503             if oedebug == 0:
504                 oe.note("Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
505         else:
506             psyco.bind( make.collect_oefiles )
507     else:
508         oe.note("You have disabled Psyco. This decreases performance.")
509
510     try:
511         oe.debug(1, "OEMAKE collecting .oe files")
512         make.collect_oefiles( myProgressCallback )
513         oe.debug(1, "OEMAKE parsing complete")
514         if oedebug == 0:
515             print
516         if make.options.parse_only:
517             print "Requested parsing .oe files only.  Exiting."
518             sys.exit(0)
519         build_depgraph()
520
521         if 'world' in pkgs_to_build:
522             pkgs_to_build.remove('world')
523             for t in __world_target:
524                 pkgs_to_build.append(t)
525
526         oe.event.fire(oe.event.BuildStarted(buildname, pkgs_to_build, make.cfg))
527
528         for k in pkgs_to_build:
529             failed = False
530             try:
531                 if buildPackage(k) == 0:
532                     # already diagnosed
533                     failed = True
534             except oe.build.EventException:
535                 oe.error("Build of " + k + " failed")
536                 failed = True
537
538             if failed:
539                 if make.options.abort:
540                     sys.exit(1)
541
542         oe.event.fire(oe.event.BuildCompleted(buildname, pkgs_to_build, make.cfg))
543
544         print "Build statistics:"
545         print "  Attempted builds: %d" % __stats["attempt"]
546         if __stats["fail"] != 0:
547             print "  Failed builds: %d" % __stats["fail"]
548         if __stats["deps"] != 0:
549             print "  Dependencies not satisfied: %d" % __stats["deps"]
550         if __stats["fail"] != 0 or __stats["deps"] != 0:
551             sys.exit(1)
552         sys.exit(0)
553
554     except KeyboardInterrupt:
555         print "\nNOTE: KeyboardInterrupt - Build not completed."