add progress bar during provider hash building
[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 def handle_options( args ):
29     parser = optparse.OptionParser( version = "OpenEmbedded Build Infrastructure Core version %s, %%prog version %s" % ( oe.__version__, __version__ ),
30     usage = """%prog [options] [package ...]
31
32 Builds specified packages, expecting that the .oe files
33 it has to work from are in OEFILES
34 Default packages are all packages in OEFILES.
35 Default OEFILES are the .oe files in the current directory.""" )
36
37     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.",
38                action = "store_false", dest = "abort", default = True )
39
40     parser.add_option( "-f", "--force", help = "force run of specified cmd, regardless of stamp status",
41                action = "store_true", dest = "force", default = False )
42
43
44     parser.add_option( "-c", "--cmd", help = "specify command to pass to oebuild. Valid commands are "
45                                              "'fetch' (fetch all sources), " 
46                                              "'unpack' (unpack the sources), "
47                                              "'patch' (apply the patches), "
48                                              "'configure' (configure the source tree), "
49                                              "'compile' (compile the source tree), "
50                                              "'stage' (install libraries and headers needed for subsequent packages), "
51                                              "'install' (install libraries and executables), and"
52                                              "'package' (package files into the selected package format)",
53                action = "store", dest = "cmd", default = "build" )
54
55     parser.add_option( "-r", "--read", help = "read the specified file before oe.conf",
56                action = "append", dest = "file", default = [] )
57
58     parser.add_option( "-v", "--verbose", help = "output more chit-chat to the terminal",
59                action = "store_true", dest = "verbose", default = False )
60
61     parser.add_option( "-n", "--dry-run", help = "don't call oebuild, just go through the motions",
62                action = "store_true", dest = "dry_run", default = False )
63
64     parser.add_option( "-p", "--parse-only", help = "quit after parsing the OE files (developers only)",
65                action = "store_true", dest = "parse_only", default = False )
66
67     parser.add_option( "-d", "--disable-psyco", help = "disable using the psyco just-in-time compiler (not recommended)",
68                action = "store_true", dest = "disable_psyco", default = False )
69
70     options, args = parser.parse_args( args )
71     return options, args[1:]
72
73 def try_build(fn, virtual):
74     if fn in __building_list:
75         oe.error("%s depends on itself (eventually)" % fn)
76         oe.error("upwards chain is: %s" % (" -> ".join(__build_path)))
77         return False
78
79     __building_list.append(fn)
80
81     the_data = make.pkgdata[fn]
82     item = oe.data.getVar('PN', the_data, 1)
83     pathstr = "%s (%s)" % (item, virtual)
84     __build_path.append(pathstr)
85
86     depends_list = (oe.data.getVar('DEPENDS', the_data, 1) or "").split()
87     if make.options.verbose:
88         oe.note("current path: %s" % (" -> ".join(__build_path)))
89         oe.note("dependencies for %s are: %s" % (item, " ".join(depends_list)))
90
91     try:
92         failed = False
93
94         if __depcmd:
95             oldcmd = make.options.cmd
96             make.options.cmd = __depcmd
97
98         for d in depends_list:
99             if d in __ignored_dependencies:
100                 continue
101             if not __depcmd:
102                 continue
103             if buildPackage(d) == 0:
104                 oe.error("dependency %s (for %s) not satisfied" % (d,item))
105                 failed = True
106                 if make.options.abort:
107                     break
108
109         if __depcmd:
110             make.options.cmd = oldcmd
111
112         if failed:
113             __stats["deps"] += 1
114             return False
115
116         oe.event.fire(oe.event.PkgStarted(item, make.pkgdata[fn]))
117         try:
118             __stats["attempt"] += 1
119             if not make.options.dry_run:
120                 oe.build.exec_task('do_%s' % make.options.cmd, make.pkgdata[fn])
121             oe.event.fire(oe.event.PkgSucceeded(item, make.pkgdata[fn]))
122             __build_cache.append(fn)
123             return True
124         except oe.build.FuncFailed:
125             __stats["fail"] += 1
126             oe.error("task stack execution failed")
127             oe.event.fire(oe.event.PkgFailed(item, make.pkgdata[fn]))
128             __build_cache_fail.append(fn)
129             return False
130         except oe.build.EventException:
131             __stats["fail"] += 1
132             (type, value, traceback) = sys.exc_info()
133             e = value.event
134             oe.error("%s event exception, aborting" % oe.event.getName(e))
135             oe.event.fire(oe.event.PkgFailed(item, make.pkgdata[fn]))
136             __build_cache_fail.append(fn)
137             return False
138     finally:
139         __building_list.remove(fn)
140         __build_path.remove(pathstr)
141
142 def buildPackage(item):
143     fn = None
144
145     if not providers.has_key(item):
146         oe.error("Nothing provides %s" % item)
147         return 0
148
149     all_p = providers[item]
150
151     for p in all_p:
152         if p in __build_cache:
153             return 1
154
155     versions = {}
156     for p in all_p:
157         the_data = make.pkgdata[p]
158         pn = oe.data.getVar('PN', the_data, 1)
159         pv = oe.data.getVar('PV', the_data, 1)
160         pr = oe.data.getVar('PR', the_data, 1)
161         if not versions.has_key(pn):
162             versions[pn] = []
163         versions[pn].append(((pv, pr), p))
164
165 #    # find the latest version of each provider
166     preferred_versions = {}
167     for p in versions.keys():
168         latest = None
169         latest_f = None
170         for (v, _fn) in versions[p]:
171             if (latest is None) or (make.vercmp(latest, v) < 0):
172                 latest = v
173                 latest_f = _fn
174         preferred_versions[p] = (latest, latest_f)
175
176 #    # build a new list with just the latest version of everything
177     eligible = []
178     for p in preferred_versions.keys():
179         (v, f) = preferred_versions[p]
180         eligible.append(f)
181
182     for p in eligible:
183         if p in __build_cache_fail:
184             oe.debug(1, "rejecting already-failed %s" % p)
185             eligible.remove(p)
186
187     if len(eligible) == 0:
188         oe.error("no eligible providers for %s" % item)
189         return 0
190
191 #    # look to see if one of them is already staged, or marked as preferred.
192 #    # if so, bump it to the head of the queue
193     for p in all_p:
194         the_data = make.pkgdata[p]
195         pn = oe.data.getVar('PN', the_data, 1)
196         pv = oe.data.getVar('PV', the_data, 1)
197         pr = oe.data.getVar('PR', the_data, 1)
198         tmpdir = oe.data.getVar('TMPDIR', the_data, 1)
199         stamp = '%s/stamps/%s-%s-%s.do_populate_staging' % (tmpdir, pn, pv, pr)
200         if os.path.exists(stamp):
201             (newvers, fn) = preferred_versions[pn]
202             if not fn in eligible:
203 #                # package was made ineligible by already-failed check
204                 continue
205             oldver = "%s-%s" % (pv, pr)
206             newver = '-'.join(newvers)
207             if (newver != oldver):
208                 extra_chat = "; upgrading from %s to %s" % (oldver, newver)
209             else:
210                 extra_chat = ""
211             if make.options.verbose:
212                 oe.note("selecting already-staged %s to satisfy %s%s" % (pn, item, extra_chat))
213             eligible.remove(fn)
214             eligible = [fn] + eligible
215             break
216
217     if __preferred.has_key(item):
218         for p in eligible:
219             the_data = make.pkgdata[p]
220             pn = oe.data.getVar('PN', the_data, 1)
221             if __preferred[item] == pn:
222                 if make.options.verbose:
223                     oe.note("selecting %s to satisfy %s due to PREFERRED_PROVIDERS" % (pn, item))
224                 eligible.remove(p)
225                 eligible = [p] + eligible
226                 break
227
228 #    # run through the list until we find one that we can build
229     for fn in eligible:
230         oe.debug(2, "selecting %s to satisfy %s" % (fn, item))
231         if try_build(fn, item):
232             return 1
233
234     oe.note("no buildable providers for %s" % item)
235     return 0
236
237 def build_depgraph():
238     all_depends = Set()
239     pn_provides = {}
240
241     def progress(p):
242         sys.stdout.write("\rNOTE: Building provider hash: [")
243         x = 0
244         for i in range(20):
245             if (p >= x):
246                 sys.stdout.write("#")
247             else:
248                 sys.stdout.write(" ")
249             x += 5
250         sys.stdout.write("] (%02d%%)" % p)
251         sys.stdout.flush()
252
253     n = len(make.pkgdata.keys())
254     i = 0
255
256     op = -1
257
258     for f in make.pkgdata.keys():
259         d = make.pkgdata[f]
260
261         pn = oe.data.getVar('PN', d, 1)
262
263         deps = (oe.data.getVar("DEPENDS", d, 1) or "").split()
264         provides = Set([pn] + (oe.data.getVar("PROVIDES", d, 1) or "").split())
265
266         for dep in deps:
267             all_depends.add(dep)
268
269         if not pn_provides.has_key(pn):
270             pn_provides[pn] = Set()
271         pn_provides[pn] |= provides
272
273         for provide in provides:
274             if not providers.has_key(provide):
275                 providers[provide] = []
276             providers[provide].append(f)
277
278         for p in (oe.data.getVar('PREFERRED_PROVIDERS', d, 1) or "").split():
279             (providee, provider) = p.split(':')
280             if __preferred.has_key(providee) and __preferred[providee] != provider:
281                 oe.error("conflicting preferences for %s: both %s and %s specified" % (providee, provider, __preferred[providee]))
282             __preferred[providee] = provider
283
284         i += 1
285         p = (100 * i) / n
286         if p != op:
287             op = p
288             progress(p)
289
290     sys.stdout.write("\n")
291
292     for f in make.pkgdata.keys():
293         d = make.pkgdata[f]
294         if oe.data.getVar('BROKEN', d, 1):
295             continue
296         terminal = True
297         pn = oe.data.getVar('PN', d, 1)
298         for p in pn_provides[pn]:
299             if p in all_depends or p.startswith('virtual/'):
300                 terminal = False
301                 break
302         if terminal:
303             __world_target.add(pn)
304
305 def myProgressCallback( x, y, f ):
306     if os.isatty(sys.stdout.fileno()):
307         sys.stdout.write("\rNOTE: Parsing .oe files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
308         sys.stdout.flush()
309     else:
310         if x == 1:
311             sys.stdout.write("Parsing .oe files, please wait...")
312             sys.stdout.flush()
313         if x == y:
314             sys.stdout.write("done.\n")
315             sys.stdout.flush()
316
317
318 #
319 # main
320 #
321
322 if __name__ == "__main__":
323
324     make.options, args = handle_options( sys.argv )
325
326     if not make.options.cmd:
327         make.options.cmd = "build"
328
329     if make.options.cmd in __depcmds:
330         __depcmd=__depcmds[make.options.cmd]
331     else:
332         __depcmd=make.options.cmd
333
334     make.pkgdata = {}
335     make.cfg = {}
336     providers = {}
337
338     for f in make.options.file:
339         try:
340             make.cfg = oe.parse.handle(f, make.cfg)
341         except IOError:
342             oe.fatal("Unable to open %s" % f)
343
344     try:
345         make.cfg = oe.parse.handle("conf/oe.conf", make.cfg)
346     except IOError:
347         oe.fatal("Unable to open oe.conf")
348
349     if not oe.data.getVar("BUILDNAME", make.cfg):
350         oe.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), make.cfg)
351
352     buildname = oe.data.getVar("BUILDNAME", make.cfg)
353
354     ignore = oe.data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
355     __ignored_dependencies = ignore.split()
356
357     pkgs_to_build = None
358     if args:
359         if not pkgs_to_build:
360             pkgs_to_build = []
361         pkgs_to_build.extend(args)
362     if not pkgs_to_build:
363             oepkgs = oe.data.getVar('OEPKGS', make.cfg, 1)
364             if oepkgs:
365                     pkgs_to_build = oepkgs.split()
366     if not pkgs_to_build:
367             print "Nothing to build. Use 'oemake world' to build everything."
368             sys.exit(0)
369
370     __stats["attempt"] = 0
371     __stats["success"] = 0
372     __stats["fail"] = 0
373     __stats["deps"] = 0
374
375     # Import Psyco if available and not disabled
376     if not make.options.disable_psyco:
377         try:
378             import psyco
379         except ImportError:
380             print "NOTE: Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance."
381         else:
382             psyco.bind( make.collect_oefiles )
383     else:
384         print "NOTE: You have disabled Psyco. This decreases performance."
385
386     try:
387         make.collect_oefiles( myProgressCallback )
388         print
389         if make.options.parse_only:
390             print "Requested parsing .oe files only.  Exiting."
391             sys.exit(0)
392         build_depgraph()
393
394         if 'world' in pkgs_to_build:
395             pkgs_to_build.remove('world')
396             for t in __world_target:
397                 pkgs_to_build.append(t)
398
399         oe.event.fire(oe.event.BuildStarted(buildname, pkgs_to_build, make.cfg))
400
401         for k in pkgs_to_build:
402             if buildPackage(k) == 0:
403                 oe.error("Build of " + k + " failed")
404                 if make.options.abort:
405                     sys.exit(1)
406
407         oe.event.fire(oe.event.BuildCompleted(buildname, pkgs_to_build, make.cfg))
408
409         print "Build statistics:"
410         print "  Attempted builds: %d" % __stats["attempt"]
411         if __stats["fail"] != 0:
412             print "  Failed builds: %d" % __stats["fail"]
413         if __stats["deps"] != 0:
414             print "  Dependencies not satisfied: %d" % __stats["deps"]
415         if __stats["fail"] != 0 or __stats["deps"] != 0:
416             sys.exit(1)
417         sys.exit(0)
418
419     except KeyboardInterrupt:
420         print "\nNOTE: KeyboardInterrupt - Build not completed."