add support for PREFERRED_PROVIDER_<virtual>
[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     prefervar = oe.data.getVar('PREFERRED_PROVIDER_%s' % item, make.cfg, 1)
218     if prefervar:
219         __preferred[item] = prefervar
220
221     if __preferred.has_key(item):
222         for p in eligible:
223             the_data = make.pkgdata[p]
224             pn = oe.data.getVar('PN', the_data, 1)
225             if __preferred[item] == pn:
226                 if make.options.verbose:
227                     oe.note("selecting %s to satisfy %s due to PREFERRED_PROVIDERS" % (pn, item))
228                 eligible.remove(p)
229                 eligible = [p] + eligible
230                 break
231
232 #    # run through the list until we find one that we can build
233     for fn in eligible:
234         oe.debug(2, "selecting %s to satisfy %s" % (fn, item))
235         if try_build(fn, item):
236             return 1
237
238     oe.note("no buildable providers for %s" % item)
239     return 0
240
241 def build_depgraph():
242     all_depends = Set()
243     pn_provides = {}
244
245     def progress(p):
246         sys.stdout.write("\rNOTE: Building provider hash: [")
247         x = 0
248         for i in range(20):
249             if (p >= x):
250                 sys.stdout.write("#")
251             else:
252                 sys.stdout.write(" ")
253             x += 5
254         sys.stdout.write("] (%02d%%)" % p)
255         sys.stdout.flush()
256
257     n = len(make.pkgdata.keys())
258     i = 0
259
260     op = -1
261
262     for p in (oe.data.getVar('PREFERRED_PROVIDERS', make.cfg, 1) or "").split():
263         (providee, provider) = p.split(':')
264         if __preferred.has_key(providee) and __preferred[providee] != provider:
265             oe.error("conflicting preferences for %s: both %s and %s specified" % (providee, provider, __preferred[providee]))
266         __preferred[providee] = provider
267
268     for f in make.pkgdata.keys():
269         d = make.pkgdata[f]
270
271         pn = oe.data.getVar('PN', d, 1)
272
273         deps = (oe.data.getVar("DEPENDS", d, 1) or "").split()
274         provides = Set([pn] + (oe.data.getVar("PROVIDES", d, 1) or "").split())
275
276         for dep in deps:
277             all_depends.add(dep)
278
279         if not pn_provides.has_key(pn):
280             pn_provides[pn] = Set()
281         pn_provides[pn] |= provides
282
283         for provide in provides:
284             if not providers.has_key(provide):
285                 providers[provide] = []
286             providers[provide].append(f)
287
288         i += 1
289         p = (100 * i) / n
290         if p != op:
291             op = p
292             progress(p)
293
294     sys.stdout.write("\n")
295
296     for f in make.pkgdata.keys():
297         d = make.pkgdata[f]
298         if oe.data.getVar('BROKEN', d, 1):
299             continue
300         terminal = True
301         pn = oe.data.getVar('PN', d, 1)
302         for p in pn_provides[pn]:
303             if p in all_depends or p.startswith('virtual/'):
304                 terminal = False
305                 break
306         if terminal:
307             __world_target.add(pn)
308
309 def myProgressCallback( x, y, f ):
310     if os.isatty(sys.stdout.fileno()):
311         sys.stdout.write("\rNOTE: Parsing .oe files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
312         sys.stdout.flush()
313     else:
314         if x == 1:
315             sys.stdout.write("Parsing .oe files, please wait...")
316             sys.stdout.flush()
317         if x == y:
318             sys.stdout.write("done.\n")
319             sys.stdout.flush()
320
321
322 #
323 # main
324 #
325
326 if __name__ == "__main__":
327
328     make.options, args = handle_options( sys.argv )
329
330     if not make.options.cmd:
331         make.options.cmd = "build"
332
333     if make.options.cmd in __depcmds:
334         __depcmd=__depcmds[make.options.cmd]
335     else:
336         __depcmd=make.options.cmd
337
338     make.pkgdata = {}
339     make.cfg = {}
340     providers = {}
341
342     for f in make.options.file:
343         try:
344             make.cfg = oe.parse.handle(f, make.cfg)
345         except IOError:
346             oe.fatal("Unable to open %s" % f)
347
348     try:
349         make.cfg = oe.parse.handle("conf/oe.conf", make.cfg)
350     except IOError:
351         oe.fatal("Unable to open oe.conf")
352
353     if not oe.data.getVar("BUILDNAME", make.cfg):
354         oe.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), make.cfg)
355
356     buildname = oe.data.getVar("BUILDNAME", make.cfg)
357
358     ignore = oe.data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
359     __ignored_dependencies = ignore.split()
360
361     pkgs_to_build = None
362     if args:
363         if not pkgs_to_build:
364             pkgs_to_build = []
365         pkgs_to_build.extend(args)
366     if not pkgs_to_build:
367             oepkgs = oe.data.getVar('OEPKGS', make.cfg, 1)
368             if oepkgs:
369                     pkgs_to_build = oepkgs.split()
370     if not pkgs_to_build:
371             print "Nothing to build. Use 'oemake world' to build everything."
372             sys.exit(0)
373
374     __stats["attempt"] = 0
375     __stats["success"] = 0
376     __stats["fail"] = 0
377     __stats["deps"] = 0
378
379     # Import Psyco if available and not disabled
380     if not make.options.disable_psyco:
381         try:
382             import psyco
383         except ImportError:
384             print "NOTE: Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance."
385         else:
386             psyco.bind( make.collect_oefiles )
387     else:
388         print "NOTE: You have disabled Psyco. This decreases performance."
389
390     try:
391         make.collect_oefiles( myProgressCallback )
392         print
393         if make.options.parse_only:
394             print "Requested parsing .oe files only.  Exiting."
395             sys.exit(0)
396         build_depgraph()
397
398         if 'world' in pkgs_to_build:
399             pkgs_to_build.remove('world')
400             for t in __world_target:
401                 pkgs_to_build.append(t)
402
403         oe.event.fire(oe.event.BuildStarted(buildname, pkgs_to_build, make.cfg))
404
405         for k in pkgs_to_build:
406             if buildPackage(k) == 0:
407                 oe.error("Build of " + k + " failed")
408                 if make.options.abort:
409                     sys.exit(1)
410
411         oe.event.fire(oe.event.BuildCompleted(buildname, pkgs_to_build, make.cfg))
412
413         print "Build statistics:"
414         print "  Attempted builds: %d" % __stats["attempt"]
415         if __stats["fail"] != 0:
416             print "  Failed builds: %d" % __stats["fail"]
417         if __stats["deps"] != 0:
418             print "  Dependencies not satisfied: %d" % __stats["deps"]
419         if __stats["fail"] != 0 or __stats["deps"] != 0:
420             sys.exit(1)
421         sys.exit(0)
422
423     except KeyboardInterrupt:
424         print "\nNOTE: KeyboardInterrupt - Build not completed."