runqueue.py: Change failed tasks handling so all failed tasks are reported, not just...
[bitbake.git] / bin / bitbake
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 # Copyright (C) 2003, 2004  Chris Larson
6 # Copyright (C) 2003, 2004  Phil Blundell
7 # Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
8 # Copyright (C) 2005        Holger Hans Peter Freyther
9 # Copyright (C) 2005        ROAD GmbH
10 # Copyright (C) 2006        Richard Purdie
11 #
12 # This program is free software; you can redistribute it and/or modify it under
13 # the terms of the GNU General Public License as published by the Free Software
14 # Foundation; either version 2 of the License, or (at your option) any later
15 # version.
16 #
17 # This program is distributed in the hope that it will be useful, but WITHOUT
18 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License along with
22 # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
23 # Place, Suite 330, Boston, MA 02111-1307 USA.
24
25 import sys, os, getopt, glob, copy, os.path, re, time
26 sys.path.insert(0,os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
27 import bb
28 from bb import utils, data, parse, event, cache, providers, taskdata, runqueue
29 from sets import Set
30 import itertools, optparse
31
32 parsespin = itertools.cycle( r'|/-\\' )
33
34 __version__ = "1.7.4"
35
36 #============================================================================#
37 # BBStatistics
38 #============================================================================#
39 class BBStatistics:
40     """
41     Manage build statistics for one run
42     """
43     def __init__(self ):
44         self.attempt = 0
45         self.success = 0
46         self.fail = 0
47         self.deps = 0
48
49     def show( self ):
50         print "Build statistics:"
51         print "  Attempted builds: %d" % self.attempt
52         if self.fail:
53             print "  Failed builds: %d" % self.fail
54         if self.deps:
55             print "  Dependencies not satisfied: %d" % self.deps
56         if self.fail or self.deps: return 1
57         else: return 0
58
59
60 #============================================================================#
61 # BBOptions
62 #============================================================================#
63 class BBConfiguration( object ):
64     """
65     Manages build options and configurations for one run
66     """
67     def __init__( self, options ):
68         for key, val in options.__dict__.items():
69             setattr( self, key, val )
70
71 #============================================================================#
72 # BBCooker
73 #============================================================================#
74 class BBCooker:
75     """
76     Manages one bitbake build run
77     """
78
79     Statistics = BBStatistics           # make it visible from the shell
80
81     def __init__( self ):
82         self.build_cache_fail = []
83         self.build_cache = []
84         self.stats = BBStatistics()
85         self.status = None
86
87         self.cache = None
88         self.bb_cache = None
89
90     def tryBuildPackage(self, fn, item, task, the_data, build_depends):
91         """
92         Build one task of a package, optionally build following task depends
93         """
94         bb.event.fire(bb.event.PkgStarted(item, the_data))
95         try:
96             self.stats.attempt += 1
97             if self.configuration.force:
98                 bb.data.setVarFlag('do_%s' % task, 'force', 1, the_data)
99             if not build_depends:
100                 bb.data.setVarFlag('do_%s' % task, 'dontrundeps', 1, the_data)
101             if not self.configuration.dry_run:
102                 bb.build.exec_task('do_%s' % task, the_data)
103             bb.event.fire(bb.event.PkgSucceeded(item, the_data))
104             self.build_cache.append(fn)
105             return True
106         except bb.build.FuncFailed:
107             self.stats.fail += 1
108             bb.msg.error(bb.msg.domain.Build, "task stack execution failed")
109             bb.event.fire(bb.event.PkgFailed(item, the_data))
110             self.build_cache_fail.append(fn)
111             raise
112         except bb.build.EventException, e:
113             self.stats.fail += 1
114             event = e.args[1]
115             bb.msg.error(bb.msg.domain.Build, "%s event exception, aborting" % bb.event.getName(event))
116             bb.event.fire(bb.event.PkgFailed(item, the_data))
117             self.build_cache_fail.append(fn)
118             raise
119
120     def tryBuild( self, fn, build_depends):
121         """
122         Build a provider and its dependencies. 
123         build_depends is a list of previous build dependencies (not runtime)
124         If build_depends is empty, we're dealing with a runtime depends
125         """
126
127         the_data = self.bb_cache.loadDataFull(fn, self.configuration.data)
128
129         item = self.status.pkg_fn[fn]
130
131         if bb.build.stamp_is_current('do_%s' % self.configuration.cmd, the_data) and not self.configuration.force:
132             self.build_cache.append(fn)
133             return True
134
135         return self.tryBuildPackage(fn, item, self.configuration.cmd, the_data, build_depends)
136
137     def showVersions( self ):
138         pkg_pn = self.status.pkg_pn
139         preferred_versions = {}
140         latest_versions = {}
141
142         # Sort by priority
143         for pn in pkg_pn.keys():
144             (last_ver,last_file,pref_ver,pref_file) = bb.providers.findBestProvider(pn, self.configuration.data, self.status)
145             preferred_versions[pn] = (pref_ver, pref_file)
146             latest_versions[pn] = (last_ver, last_file)
147
148         pkg_list = pkg_pn.keys()
149         pkg_list.sort()
150
151         for p in pkg_list:
152             pref = preferred_versions[p]
153             latest = latest_versions[p]
154
155             if pref != latest:
156                 prefstr = pref[0][0] + "-" + pref[0][1]
157             else:
158                 prefstr = ""
159
160             print "%-30s %20s %20s" % (p, latest[0][0] + "-" + latest[0][1],
161                                         prefstr)
162
163
164     def showEnvironment( self ):
165         """Show the outer or per-package environment"""
166         if self.configuration.buildfile:
167             self.cb = None
168             self.bb_cache = bb.cache.init(self)
169             try:
170                 self.configuration.data = self.bb_cache.loadDataFull(self.configuration.buildfile, self.configuration.data)
171             except IOError, e:
172                 bb.msg.fatal(bb.msg.domain.Parsing, "Unable to read %s: %s" % ( self.configuration.buildfile, e ))
173             except Exception, e:
174                 bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e)
175         # emit variables and shell functions
176         try:
177             data.update_data( self.configuration.data )
178             data.emit_env(sys.__stdout__, self.configuration.data, True)
179         except Exception, e:
180             bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e)
181         # emit the metadata which isnt valid shell
182         data.expandKeys( self.configuration.data )      
183         for e in self.configuration.data.keys():
184             if data.getVarFlag( e, 'python', self.configuration.data ):
185                 sys.__stdout__.write("\npython %s () {\n%s}\n" % (e, data.getVar(e, self.configuration.data, 1)))
186
187     def generateDotGraph( self, pkgs_to_build, ignore_deps ):
188         """
189         Generate two graphs one for the DEPENDS and RDEPENDS. The current
190         implementation creates crappy graphs ;)
191
192         pkgs_to_build A list of packages that needs to be built
193         ignore_deps   A list of names where processing of dependencies
194                       should be stopped. e.g. dependencies that get
195         """
196
197         def myFilterProvider( providers, item):
198             """
199             Take a list of providers and filter according to environment
200             variables. In contrast to filterProviders we do not discriminate
201             and take PREFERRED_PROVIDER into account.
202             """
203             eligible = []
204             preferred_versions = {}
205
206             # Collate providers by PN
207             pkg_pn = {}
208             for p in providers:
209                 pn = self.status.pkg_fn[p]
210                 if pn not in pkg_pn:
211                     pkg_pn[pn] = []
212                 pkg_pn[pn].append(p)
213
214             bb.msg.debug(1, bb.msg.domain.Provider, "providers for %s are: %s" % (item, pkg_pn.keys()))
215
216             for pn in pkg_pn.keys():
217                 preferred_versions[pn] = bb.providers.findBestProvider(pn, self.configuration.data, self.status, pkg_pn)[2:4]
218                 eligible.append(preferred_versions[pn][1])
219
220             for p in eligible:
221                 if p in self.build_cache_fail:
222                     bb.msg.debug(1, bb.msg.domain.Provider, "rejecting already-failed %s" % p)
223                     eligible.remove(p)
224
225             if len(eligible) == 0:
226                 bb.msg.error(bb.msg.domain.Provider, "no eligible providers for %s" % item)
227                 return 0
228
229             prefervar = bb.data.getVar('PREFERRED_PROVIDER_%s' % item, self.configuration.data, 1)
230
231             # try the preferred provider first
232             if prefervar:
233                 for p in eligible:
234                     if prefervar == self.status.pkg_fn[p]:
235                         bb.msg.note(1, bb.msg.domain.Provider, "Selecting PREFERRED_PROVIDER %s" % prefervar)
236                         eligible.remove(p)
237                         eligible = [p] + eligible
238
239             return eligible
240
241
242         # try to avoid adding the same rdepends over an over again
243         seen_depends  = []
244         seen_rdepends = []
245
246
247         def add_depends(package_list):
248             """
249             Add all depends of all packages from this list
250             """
251             for package in package_list:
252                 if package in seen_depends or package in ignore_deps:
253                     continue
254
255                 seen_depends.append( package )
256                 if not package in self.status.providers:
257                     """
258                     We have not seen this name -> error in
259                     dependency handling
260                     """
261                     bb.msg.note(1, bb.msg.domain.Depends, "ERROR with provider: %(package)s" % vars() )
262                     print >> depends_file, '"%(package)s" -> ERROR' % vars()
263                     continue
264
265                 # get all providers for this package
266                 providers = self.status.providers[package]
267
268                 # now let us find the bestProvider for it
269                 fn = myFilterProvider(providers, package)[0]
270
271                 depends  = bb.utils.explode_deps(self.bb_cache.getVar('DEPENDS', fn, True) or "")
272                 version  = self.bb_cache.getVar('PV', fn, True ) + '-' + self.bb_cache.getVar('PR', fn, True)
273                 add_depends ( depends )
274
275                 # now create the node
276                 print >> depends_file, '"%(package)s" [label="%(package)s\\n%(version)s"]' % vars()
277
278                 depends = filter( (lambda x: x not in ignore_deps), depends )
279                 for depend in depends:
280                     print >> depends_file, '"%(package)s" -> "%(depend)s"' % vars()
281
282
283         def add_all_depends( the_depends, the_rdepends ):
284             """
285             Add both DEPENDS and RDEPENDS. RDEPENDS will get dashed
286             lines
287             """
288             package_list = the_depends + the_rdepends
289             for package in package_list:
290                 if package in seen_rdepends or package in ignore_deps:
291                     continue
292
293                 seen_rdepends.append( package )
294
295                 # Let us find out if the package is a DEPENDS or RDEPENDS
296                 # and we will set 'providers' with the avilable providers
297                 # for the package.
298                 if package in the_depends:
299                     if not package in self.status.providers:
300                         bb.msg.note(1, bb.msg.domain.Depends, "ERROR with provider: %(package)s" % vars() )
301                         print >> alldepends_file, '"%(package)s" -> ERROR' % vars()
302                         continue
303
304                     providers = self.status.providers[package]
305                 elif package in the_rdepends:
306                     if len(bb.providers.getRuntimeProviders(self.status, package)) == 0:
307                         bb.msg.note(1, bb.msg.domain.Depends, "ERROR with rprovider: %(package)s" % vars() )
308                         print >> alldepends_file, '"%(package)s" -> ERROR [style="dashed"]' % vars()
309                         continue
310
311                     providers = bb.providers.getRuntimeProviders(self.status, package)
312                 else:
313                     # something went wrong...
314                     print "Complete ERROR! %s" % package
315                     continue
316
317                 # now let us find the bestProvider for it
318                 fn = myFilterProvider(providers, package)[0]
319
320                 # Now we have a filename let us get the depends and RDEPENDS of it
321                 depends  = bb.utils.explode_deps(self.bb_cache.getVar('DEPENDS', fn, True) or "")
322                 if fn in self.status.rundeps and package in self.status.rundeps[fn]:
323                     rdepends= self.status.rundeps[fn][package].keys()
324                 else:
325                     rdepends = []
326                 version  = self.bb_cache.getVar('PV', fn, True ) + '-' + self.bb_cache.getVar('PR', fn, True)
327
328                 # handle all the depends and rdepends of package
329                 add_all_depends ( depends, rdepends )
330
331                 # now create the node using package name
332                 print >> alldepends_file, '"%(package)s" [label="%(package)s\\n%(version)s"]' % vars()
333
334                 # remove the stuff we want to ignore and add the edges
335                 depends = filter( (lambda x: x not in ignore_deps), depends )
336                 rdepends = filter( (lambda x: x not in ignore_deps), rdepends )
337                 for depend in depends:
338                     print >> alldepends_file, '"%(package)s" -> "%(depend)s"' % vars()
339                 for depend in rdepends:
340                     print >> alldepends_file, '"%(package)s" -> "%(depend)s" [style=dashed]' % vars()
341
342
343         # Add depends now
344         depends_file = file('depends.dot', 'w' )
345         print >> depends_file, "digraph depends {"
346         add_depends( pkgs_to_build )
347         print >> depends_file,  "}"
348
349         # Add all depends now
350         alldepends_file = file('alldepends.dot', 'w' )
351         print >> alldepends_file, "digraph alldepends {"
352         add_all_depends( pkgs_to_build, [] )
353         print >> alldepends_file, "}"
354
355     def buildDepgraph( self ):
356         all_depends = self.status.all_depends
357         pn_provides = self.status.pn_provides
358
359         localdata = data.createCopy(self.configuration.data)
360         bb.data.update_data(localdata)
361         bb.data.expandKeys(localdata)
362
363         def calc_bbfile_priority(filename):
364             for (regex, pri) in self.status.bbfile_config_priorities:
365                 if regex.match(filename):
366                     return pri
367             return 0
368
369         # Handle PREFERRED_PROVIDERS
370         for p in (bb.data.getVar('PREFERRED_PROVIDERS', localdata, 1) or "").split():
371             (providee, provider) = p.split(':')
372             if providee in self.status.preferred and self.status.preferred[providee] != provider:
373                 bb.msg.error(bb.msg.domain.Provider, "conflicting preferences for %s: both %s and %s specified" % (providee, provider, self.status.preferred[providee]))
374             self.status.preferred[providee] = provider
375
376         # Calculate priorities for each file
377         for p in self.status.pkg_fn.keys():
378             self.status.bbfile_priority[p] = calc_bbfile_priority(p)
379
380     def buildWorldTargetList(self):
381         """
382          Build package list for "bitbake world"
383         """
384         all_depends = self.status.all_depends
385         pn_provides = self.status.pn_provides
386         bb.msg.debug(1, bb.msg.domain.Parsing, "collating packages for \"world\"")
387         for f in self.status.possible_world:
388             terminal = True
389             pn = self.status.pkg_fn[f]
390
391             for p in pn_provides[pn]:
392                 if p.startswith('virtual/'):
393                     bb.msg.debug(2, bb.msg.domain.Parsing, "World build skipping %s due to %s provider starting with virtual/" % (f, p))
394                     terminal = False
395                     break
396                 for pf in self.status.providers[p]:
397                     if self.status.pkg_fn[pf] != pn:
398                         bb.msg.debug(2, bb.msg.domain.Parsing, "World build skipping %s due to both us and %s providing %s" % (f, pf, p))
399                         terminal = False
400                         break
401             if terminal:
402                 self.status.world_target.add(pn)
403
404             # drop reference count now
405             self.status.possible_world = None
406             self.status.all_depends    = None
407
408     def myProgressCallback( self, x, y, f, from_cache ):
409         """Update any tty with the progress change"""
410         if os.isatty(sys.stdout.fileno()):
411             sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
412             sys.stdout.flush()
413         else:
414             if x == 1:
415                 sys.stdout.write("Parsing .bb files, please wait...")
416                 sys.stdout.flush()
417             if x == y:
418                 sys.stdout.write("done.")
419                 sys.stdout.flush()
420
421     def interactiveMode( self ):
422         """Drop off into a shell"""
423         try:
424             from bb import shell
425         except ImportError, details:
426             bb.msg.fatal(bb.msg.domain.Parsing, "Sorry, shell not available (%s)" % details )
427         else:
428             bb.data.update_data( self.configuration.data )
429             bb.data.expandKeys(localdata)
430             shell.start( self )
431             sys.exit( 0 )
432
433     def parseConfigurationFile( self, afile ):
434         try:
435             self.configuration.data = bb.parse.handle( afile, self.configuration.data )
436
437             # Add the handlers we inherited by INHERIT
438             # we need to do this manually as it is not guranteed
439             # we will pick up these classes... as we only INHERIT
440             # on .inc and .bb files but not on .conf
441             data = bb.data.createCopy( self.configuration.data )
442             inherits  = ["base"] + (bb.data.getVar('INHERIT', data, True ) or "").split()
443             for inherit in inherits:
444                 data = bb.parse.handle( os.path.join('classes', '%s.bbclass' % inherit ), data, True )
445
446             # FIXME: This assumes that we included at least one .inc file
447             for var in bb.data.keys(data):
448                 if bb.data.getVarFlag(var, 'handler', data):
449                     bb.event.register(var,bb.data.getVar(var, data))
450
451         except IOError:
452             bb.msg.fatal(bb.msg.domain.Parsing, "Unable to open %s" % afile )
453         except bb.parse.ParseError, details:
454             bb.msg.fatal(bb.msg.domain.Parsing, "Unable to parse %s (%s)" % (afile, details) )
455
456     def handleCollections( self, collections ):
457         """Handle collections"""
458         if collections:
459             collection_list = collections.split()
460             for c in collection_list:
461                 regex = bb.data.getVar("BBFILE_PATTERN_%s" % c, self.configuration.data, 1)
462                 if regex == None:
463                     bb.msg.error(bb.msg.domain.Parsing, "BBFILE_PATTERN_%s not defined" % c)
464                     continue
465                 priority = bb.data.getVar("BBFILE_PRIORITY_%s" % c, self.configuration.data, 1)
466                 if priority == None:
467                     bb.msg.error(bb.msg.domain.Parsing, "BBFILE_PRIORITY_%s not defined" % c)
468                     continue
469                 try:
470                     cre = re.compile(regex)
471                 except re.error:
472                     bb.msg.error(bb.msg.domain.Parsing, "BBFILE_PATTERN_%s \"%s\" is not a valid regular expression" % (c, regex))
473                     continue
474                 try:
475                     pri = int(priority)
476                     self.status.bbfile_config_priorities.append((cre, pri))
477                 except ValueError:
478                     bb.msg.error(bb.msg.domain.Parsing, "invalid value for BBFILE_PRIORITY_%s: \"%s\"" % (c, priority))
479
480
481     def cook( self, configuration, args ):
482         """
483         We are building stuff here. We do the building
484         from here. By default we try to execute task
485         build.
486         """
487
488         self.configuration = configuration
489
490         if self.configuration.verbose:
491             bb.msg.set_verbose(True)
492
493         if self.configuration.debug:
494             bb.msg.set_debug_level(self.configuration.debug)
495         else:
496             bb.msg.set_debug_level(0)
497
498         if self.configuration.debug_domains:
499             bb.msg.set_debug_domains(self.configuration.debug_domains)
500
501         self.configuration.data = bb.data.init()
502
503         for f in self.configuration.file:
504             self.parseConfigurationFile( f )
505
506         self.parseConfigurationFile( os.path.join( "conf", "bitbake.conf" ) )
507
508         if not self.configuration.cmd:
509             self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data)
510
511         # For backwards compatibility - REMOVE ME
512         if not self.configuration.cmd:
513             self.configuration.cmd = "build"
514
515         #
516         # Special updated configuration we use for firing events
517         #
518         self.configuration.event_data = bb.data.createCopy(self.configuration.data)
519         bb.data.update_data(self.configuration.event_data)
520
521         if self.configuration.show_environment:
522             self.showEnvironment()
523             sys.exit( 0 )
524
525         # inject custom variables
526         if not bb.data.getVar("BUILDNAME", self.configuration.data):
527             bb.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), self.configuration.data)
528         bb.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S',time.gmtime()),self.configuration.data)
529
530         buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
531
532         if self.configuration.interactive:
533             self.interactiveMode()
534
535         if self.configuration.buildfile is not None:
536             bf = os.path.abspath( self.configuration.buildfile )
537             try:
538                 os.stat(bf)
539             except OSError:
540                 (filelist, masked) = self.collect_bbfiles()
541                 regexp = re.compile(self.configuration.buildfile)
542                 matches = []
543                 for f in filelist:
544                     if regexp.search(f) and os.path.isfile(f):
545                         bf = f
546                         matches.append(f)
547                 if len(matches) != 1:
548                     bb.msg.error(bb.msg.domain.Parsing, "Unable to match %s (%s matches found):" % (self.configuration.buildfile, len(matches)))
549                     for f in matches:
550                         bb.msg.error(bb.msg.domain.Parsing, "    %s" % f)
551                     sys.exit(1)
552                 bf = matches[0]             
553
554             bbfile_data = bb.parse.handle(bf, self.configuration.data)
555
556             item = bb.data.getVar('PN', bbfile_data, 1)
557             try:
558                 self.tryBuildPackage(bf, item, self.configuration.cmd, bbfile_data, True)
559             except bb.build.EventException:
560                 bb.msg.error(bb.msg.domain.Build,  "Build of '%s' failed" % item )
561
562             sys.exit( self.stats.show() )
563
564         # initialise the parsing status now we know we will need deps
565         self.status = bb.cache.CacheData()
566
567         ignore = bb.data.getVar("ASSUME_PROVIDED", self.configuration.data, 1) or ""
568         self.status.ignored_dependencies = Set( ignore.split() )
569
570         self.handleCollections( bb.data.getVar("BBFILE_COLLECTIONS", self.configuration.data, 1) )
571
572         pkgs_to_build = None
573         if args:
574             if not pkgs_to_build:
575                 pkgs_to_build = []
576             pkgs_to_build.extend(args)
577         if not pkgs_to_build:
578                 bbpkgs = bb.data.getVar('BBPKGS', self.configuration.data, 1)
579                 if bbpkgs:
580                         pkgs_to_build = bbpkgs.split()
581         if not pkgs_to_build and not self.configuration.show_versions \
582                              and not self.configuration.interactive \
583                              and not self.configuration.show_environment:
584                 print "Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help'"
585                 print "for usage information."
586                 sys.exit(0)
587
588         # Import Psyco if available and not disabled
589         if not self.configuration.disable_psyco:
590             try:
591                 import psyco
592             except ImportError:
593                 bb.msg.note(1, bb.msg.domain.Collection, "Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
594             else:
595                 psyco.bind( self.parse_bbfiles )
596         else:
597             bb.msg.note(1, bb.msg.domain.Collection, "You have disabled Psyco. This decreases performance.")
598
599         try:
600             bb.msg.debug(1, bb.msg.domain.Collection, "collecting .bb files")
601             (filelist, masked) = self.collect_bbfiles()
602             self.parse_bbfiles(filelist, masked, self.myProgressCallback)
603             bb.msg.debug(1, bb.msg.domain.Collection, "parsing complete")
604             print
605             if self.configuration.parse_only:
606                 bb.msg.note(1, bb.msg.domain.Collection, "Requested parsing .bb files only.  Exiting.")
607                 return
608
609
610             self.buildDepgraph()
611
612             if self.configuration.show_versions:
613                 self.showVersions()
614                 sys.exit( 0 )
615             if 'world' in pkgs_to_build:
616                 self.buildWorldTargetList()
617                 pkgs_to_build.remove('world')
618                 for t in self.status.world_target:
619                     pkgs_to_build.append(t)
620
621             if self.configuration.dot_graph:
622                 self.generateDotGraph( pkgs_to_build, self.configuration.ignored_dot_deps )
623                 sys.exit( 0 )
624
625             bb.event.fire(bb.event.BuildStarted(buildname, pkgs_to_build, self.configuration.event_data))
626
627             localdata = data.createCopy(self.configuration.data)
628             bb.data.update_data(localdata)
629             bb.data.expandKeys(localdata)
630
631             taskdata = bb.taskdata.TaskData(self.configuration.abort)
632
633             runlist = []
634             try:
635                 for k in pkgs_to_build:
636                     taskdata.add_provider(localdata, self.status, k)
637                     runlist.append([k, "do_%s" % self.configuration.cmd])
638                 taskdata.add_unresolved(localdata, self.status)
639             except bb.providers.NoProvider:
640                 sys.exit(1)
641
642             rq = bb.runqueue.RunQueue()
643             rq.prepare_runqueue(self.configuration.data, self.status, taskdata, runlist)
644             try:
645                 failures = rq.execute_runqueue(self, self.configuration.data, self.status, taskdata, runlist)
646             except runqueue.TaskFailure, fnids:
647                 for fnid in fnids:
648                     bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
649                 sys.exit(1)
650             bb.event.fire(bb.event.BuildCompleted(buildname, pkgs_to_build, self.configuration.event_data, failures))
651
652             sys.exit( self.stats.show() )
653
654         except KeyboardInterrupt:
655             bb.msg.note(1, bb.msg.domain.Collection, "KeyboardInterrupt - Build not completed.")
656             sys.exit(1)
657
658     def get_bbfiles( self, path = os.getcwd() ):
659         """Get list of default .bb files by reading out the current directory"""
660         contents = os.listdir(path)
661         bbfiles = []
662         for f in contents:
663             (root, ext) = os.path.splitext(f)
664             if ext == ".bb":
665                 bbfiles.append(os.path.abspath(os.path.join(os.getcwd(),f)))
666         return bbfiles
667
668     def find_bbfiles( self, path ):
669         """Find all the .bb files in a directory (uses find)"""
670         findcmd = 'find ' + path + ' -name *.bb | grep -v SCCS/'
671         try:
672             finddata = os.popen(findcmd)
673         except OSError:
674             return []
675         return finddata.readlines()
676
677     def collect_bbfiles( self ):
678         """Collect all available .bb build files"""
679         parsed, cached, skipped, masked = 0, 0, 0, 0
680         self.bb_cache = bb.cache.init(self)
681
682         files = (data.getVar( "BBFILES", self.configuration.data, 1 ) or "").split()
683         data.setVar("BBFILES", " ".join(files), self.configuration.data)
684
685         if not len(files):
686             files = self.get_bbfiles()
687
688         if not len(files):
689             bb.msg.error(bb.msg.domain.Collection, "no files to build.")
690
691         newfiles = []
692         for f in files:
693             if os.path.isdir(f):
694                 dirfiles = self.find_bbfiles(f)
695                 if dirfiles:
696                     newfiles += dirfiles
697                     continue
698             newfiles += glob.glob(f) or [ f ]
699
700         bbmask = bb.data.getVar('BBMASK', self.configuration.data, 1)
701
702         if not bbmask:
703             return (newfiles, 0)
704
705         try:
706             bbmask_compiled = re.compile(bbmask)
707         except sre_constants.error:
708             bb.msg.fatal(bb.msg.domain.Collection, "BBMASK is not a valid regular expression.")
709
710         finalfiles = []
711         for i in xrange( len( newfiles ) ):
712             f = newfiles[i]
713             if bbmask and bbmask_compiled.search(f):
714                 bb.msg.debug(1, bb.msg.domain.Collection, "skipping masked file %s" % f)
715                 masked += 1
716                 continue
717             finalfiles.append(f)
718
719         return (finalfiles, masked)
720
721     def parse_bbfiles(self, filelist, masked, progressCallback = None):
722         parsed, cached, skipped = 0, 0, 0
723         for i in xrange( len( filelist ) ):
724             f = filelist[i]
725
726             bb.msg.debug(1, bb.msg.domain.Collection, "parsing %s" % f)
727
728             # read a file's metadata
729             try:
730                 fromCache, skip = self.bb_cache.loadData(f, self.configuration.data)
731                 if skip:
732                     skipped += 1
733                     bb.msg.debug(2, bb.msg.domain.Collection, "skipping %s" % f)
734                     self.bb_cache.skip(f)
735                     continue
736                 elif fromCache: cached += 1
737                 else: parsed += 1
738                 deps = None
739
740                 # Disabled by RP as was no longer functional
741                 # allow metadata files to add items to BBFILES
742                 #data.update_data(self.pkgdata[f])
743                 #addbbfiles = self.bb_cache.getVar('BBFILES', f, False) or None
744                 #if addbbfiles:
745                 #    for aof in addbbfiles.split():
746                 #        if not files.count(aof):
747                 #            if not os.path.isabs(aof):
748                 #                aof = os.path.join(os.path.dirname(f),aof)
749                 #            files.append(aof)
750
751                 self.bb_cache.handle_data(f, self.status)
752
753                 # now inform the caller
754                 if progressCallback is not None:
755                     progressCallback( i + 1, len( filelist ), f, fromCache )
756
757             except IOError, e:
758                 self.bb_cache.remove(f)
759                 bb.msg.error(bb.msg.domain.Collection, "opening %s: %s" % (f, e))
760                 pass
761             except KeyboardInterrupt:
762                 self.bb_cache.sync()
763                 raise
764             except Exception, e:
765                 self.bb_cache.remove(f)
766                 bb.msg.error(bb.msg.domain.Collection, "%s while parsing %s" % (e, f))
767             except:
768                 self.bb_cache.remove(f)
769                 raise
770
771         if progressCallback is not None:
772             print "\r" # need newline after Handling Bitbake files message
773             bb.msg.note(1, bb.msg.domain.Collection, "Parsing finished. %d cached, %d parsed, %d skipped, %d masked." % ( cached, parsed, skipped, masked ))
774
775         self.bb_cache.sync()
776
777 #============================================================================#
778 # main
779 #============================================================================#
780
781 def main():
782     parser = optparse.OptionParser( version = "BitBake Build Tool Core version %s, %%prog version %s" % ( bb.__version__, __version__ ),
783     usage = """%prog [options] [package ...]
784
785 Executes the specified task (default is 'build') for a given set of BitBake files.
786 It expects that BBFILES is defined, which is a space seperated list of files to
787 be executed.  BBFILES does support wildcards.
788 Default BBFILES are the .bb files in the current directory.""" )
789
790     parser.add_option( "-b", "--buildfile", help = "execute the task against this .bb file, rather than a package from BBFILES.",
791                action = "store", dest = "buildfile", default = None )
792
793     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.",
794                action = "store_false", dest = "abort", default = True )
795
796     parser.add_option( "-f", "--force", help = "force run of specified cmd, regardless of stamp status",
797                action = "store_true", dest = "force", default = False )
798
799     parser.add_option( "-i", "--interactive", help = "drop into the interactive mode also called the BitBake shell.",
800                action = "store_true", dest = "interactive", default = False )
801
802     parser.add_option( "-c", "--cmd", help = "Specify task to execute. Note that this only executes the specified task for the providee and the packages it depends on, i.e. 'compile' does not implicitly call stage for the dependencies (IOW: use only if you know what you are doing). Depending on the base.bbclass a listtaks tasks is defined and will show available tasks",
803                action = "store", dest = "cmd" )
804
805     parser.add_option( "-r", "--read", help = "read the specified file before bitbake.conf",
806                action = "append", dest = "file", default = [] )
807
808     parser.add_option( "-v", "--verbose", help = "output more chit-chat to the terminal",
809                action = "store_true", dest = "verbose", default = False )
810
811     parser.add_option( "-D", "--debug", help = "Increase the debug level. You can specify this more than once.",
812                action = "count", dest="debug", default = 0)
813
814     parser.add_option( "-n", "--dry-run", help = "don't execute, just go through the motions",
815                action = "store_true", dest = "dry_run", default = False )
816
817     parser.add_option( "-p", "--parse-only", help = "quit after parsing the BB files (developers only)",
818                action = "store_true", dest = "parse_only", default = False )
819
820     parser.add_option( "-d", "--disable-psyco", help = "disable using the psyco just-in-time compiler (not recommended)",
821                action = "store_true", dest = "disable_psyco", default = False )
822
823     parser.add_option( "-s", "--show-versions", help = "show current and preferred versions of all packages",
824                action = "store_true", dest = "show_versions", default = False )
825
826     parser.add_option( "-e", "--environment", help = "show the global or per-package environment (this is what used to be bbread)",
827                action = "store_true", dest = "show_environment", default = False )
828
829     parser.add_option( "-g", "--graphviz", help = "emit the dependency trees of the specified packages in the dot syntax",
830                 action = "store_true", dest = "dot_graph", default = False )
831
832     parser.add_option( "-I", "--ignore-deps", help = """Stop processing at the given list of dependencies when generating dependency graphs. This can help to make the graph more appealing""",
833                 action = "append", dest = "ignored_dot_deps", default = [] )
834
835     parser.add_option( "-l", "--log-domains", help = """Show debug logging for the specified logging domains""",
836                 action = "append", dest = "debug_domains", default = [] )
837
838
839     options, args = parser.parse_args( sys.argv )
840
841     cooker = BBCooker()
842     cooker.cook( BBConfiguration( options ), args[1:] )
843
844
845
846 if __name__ == "__main__":
847     print """WARNING, WARNING, WARNING
848 This is a Bitbake from the Unstable/Development Branch.
849 You might want to use the bitbake-1.6 stable branch (if you are not a BitBake developer or tester). I'm going to sleep 5 seconds now to make sure you see that."""
850     import time
851     time.sleep(5)
852     main()