bin/bitbake: Update generateDotGraph to use taskData/runQueue correcting the dependen...
[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 a task dependency graph. 
190
191         pkgs_to_build A list of packages that needs to be built
192         ignore_deps   A list of names where processing of dependencies
193                       should be stopped. e.g. dependencies that get
194         """
195
196         for dep in ignore_deps:
197             self.status.ignored_dependencies.add(dep)
198
199         localdata = data.createCopy(self.configuration.data)
200         bb.data.update_data(localdata)
201         bb.data.expandKeys(localdata)
202         taskdata = bb.taskdata.TaskData(self.configuration.abort)
203
204         runlist = []
205         try:
206             for k in pkgs_to_build:
207                 taskdata.add_provider(localdata, self.status, k)
208                 runlist.append([k, "do_%s" % self.configuration.cmd])
209             taskdata.add_unresolved(localdata, self.status)
210         except bb.providers.NoProvider:
211             sys.exit(1)
212         rq = bb.runqueue.RunQueue()
213         rq.prepare_runqueue(self.configuration.data, self.status, taskdata, runlist)
214
215         seen_fnids = []  
216         depends_file = file('depends.dot', 'w' )
217         tdepends_file = file('task-depends.dot', 'w' )
218         print >> depends_file, "digraph depends {"
219         print >> tdepends_file, "digraph depends {"
220         rq.prio_map.reverse()
221         for task1 in range(len(rq.runq_fnid)):
222             task = rq.prio_map[task1]
223             taskname = rq.runq_task[task]t
224             fnid = rq.runq_fnid[task]
225             fn = taskdata.fn_index[fnid]
226             pn = self.status.pkg_fn[fn]
227             version  = self.bb_cache.getVar('PV', fn, True ) + '-' + self.bb_cache.getVar('PR', fn, True)
228             print >> tdepends_file, '"%s.%s" [label="%s %s\\n%s\\n%s"]' % (pn, taskname, pn, taskname, version, fn)
229             for dep in rq.runq_depends[task]:
230                 depfn = taskdata.fn_index[rq.runq_fnid[dep]]
231                 deppn = self.status.pkg_fn[depfn]
232                 print >> tdepends_file, '"%s.%s" -> "%s.%s"' % (pn, rq.runq_task[task], deppn, rq.runq_task[dep])
233             if fnid not in seen_fnids:
234                 seen_fnids.append(fnid)
235                 packages = []
236                 print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)              
237                 for depend in self.status.deps[fn]:
238                     print >> depends_file, '"%s" -> "%s"' % (pn, depend)
239                 rdepends = self.status.rundeps[fn]
240                 for package in rdepends:
241                     for rdepend in rdepends[package]:
242                         print >> depends_file, '"%s" -> "%s" [style=dashed]' % (package, rdepend)
243                     packages.append(package)
244                 rrecs = self.status.runrecs[fn]
245                 for package in rrecs:
246                     for rdepend in rrecs[package]:
247                         print >> depends_file, '"%s" -> "%s" [style=dashed]' % (package, rdepend)
248                     if not package in packages:
249                         packages.append(package)
250                 for package in packages:
251                     if package != pn:
252                         print >> depends_file, '"%s" [label="%s(%s) %s\\n%s"]' % (package, package, pn, version, fn)
253                         for depend in self.status.deps[fn]:
254                             print >> depends_file, '"%s" -> "%s"' % (package, depend)
255                 # Prints a flattened form of the above where subpackages of a package are merged into the main pn
256                 #print >> depends_file, '"%s" [label="%s %s\\n%s\\n%s"]' % (pn, pn, taskname, version, fn)
257                 #for rdep in taskdata.rdepids[fnid]:
258                 #    print >> depends_file, '"%s" -> "%s" [style=dashed]' % (pn, taskdata.run_names_index[rdep])
259                 #for dep in taskdata.depids[fnid]:
260                 #    print >> depends_file, '"%s" -> "%s"' % (pn, taskdata.build_names_index[dep])
261         print >> depends_file,  "}"
262         print >> tdepends_file,  "}"
263         bb.msg.note(1, bb.msg.domain.Collection, "Dependencies saved to 'depends.dot'")
264         bb.msg.note(1, bb.msg.domain.Collection, "Task dependencies saved to 'task-depends.dot'")
265
266     def buildDepgraph( self ):
267         all_depends = self.status.all_depends
268         pn_provides = self.status.pn_provides
269
270         localdata = data.createCopy(self.configuration.data)
271         bb.data.update_data(localdata)
272         bb.data.expandKeys(localdata)
273
274         def calc_bbfile_priority(filename):
275             for (regex, pri) in self.status.bbfile_config_priorities:
276                 if regex.match(filename):
277                     return pri
278             return 0
279
280         # Handle PREFERRED_PROVIDERS
281         for p in (bb.data.getVar('PREFERRED_PROVIDERS', localdata, 1) or "").split():
282             (providee, provider) = p.split(':')
283             if providee in self.status.preferred and self.status.preferred[providee] != provider:
284                 bb.msg.error(bb.msg.domain.Provider, "conflicting preferences for %s: both %s and %s specified" % (providee, provider, self.status.preferred[providee]))
285             self.status.preferred[providee] = provider
286
287         # Calculate priorities for each file
288         for p in self.status.pkg_fn.keys():
289             self.status.bbfile_priority[p] = calc_bbfile_priority(p)
290
291     def buildWorldTargetList(self):
292         """
293          Build package list for "bitbake world"
294         """
295         all_depends = self.status.all_depends
296         pn_provides = self.status.pn_provides
297         bb.msg.debug(1, bb.msg.domain.Parsing, "collating packages for \"world\"")
298         for f in self.status.possible_world:
299             terminal = True
300             pn = self.status.pkg_fn[f]
301
302             for p in pn_provides[pn]:
303                 if p.startswith('virtual/'):
304                     bb.msg.debug(2, bb.msg.domain.Parsing, "World build skipping %s due to %s provider starting with virtual/" % (f, p))
305                     terminal = False
306                     break
307                 for pf in self.status.providers[p]:
308                     if self.status.pkg_fn[pf] != pn:
309                         bb.msg.debug(2, bb.msg.domain.Parsing, "World build skipping %s due to both us and %s providing %s" % (f, pf, p))
310                         terminal = False
311                         break
312             if terminal:
313                 self.status.world_target.add(pn)
314
315             # drop reference count now
316             self.status.possible_world = None
317             self.status.all_depends    = None
318
319     def myProgressCallback( self, x, y, f, from_cache ):
320         """Update any tty with the progress change"""
321         if os.isatty(sys.stdout.fileno()):
322             sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
323             sys.stdout.flush()
324         else:
325             if x == 1:
326                 sys.stdout.write("Parsing .bb files, please wait...")
327                 sys.stdout.flush()
328             if x == y:
329                 sys.stdout.write("done.")
330                 sys.stdout.flush()
331
332     def interactiveMode( self ):
333         """Drop off into a shell"""
334         try:
335             from bb import shell
336         except ImportError, details:
337             bb.msg.fatal(bb.msg.domain.Parsing, "Sorry, shell not available (%s)" % details )
338         else:
339             bb.data.update_data( self.configuration.data )
340             bb.data.expandKeys(localdata)
341             shell.start( self )
342             sys.exit( 0 )
343
344     def parseConfigurationFile( self, afile ):
345         try:
346             self.configuration.data = bb.parse.handle( afile, self.configuration.data )
347
348             # Add the handlers we inherited by INHERIT
349             # we need to do this manually as it is not guranteed
350             # we will pick up these classes... as we only INHERIT
351             # on .inc and .bb files but not on .conf
352             data = bb.data.createCopy( self.configuration.data )
353             inherits  = ["base"] + (bb.data.getVar('INHERIT', data, True ) or "").split()
354             for inherit in inherits:
355                 data = bb.parse.handle( os.path.join('classes', '%s.bbclass' % inherit ), data, True )
356
357             # FIXME: This assumes that we included at least one .inc file
358             for var in bb.data.keys(data):
359                 if bb.data.getVarFlag(var, 'handler', data):
360                     bb.event.register(var,bb.data.getVar(var, data))
361
362         except IOError:
363             bb.msg.fatal(bb.msg.domain.Parsing, "Unable to open %s" % afile )
364         except bb.parse.ParseError, details:
365             bb.msg.fatal(bb.msg.domain.Parsing, "Unable to parse %s (%s)" % (afile, details) )
366
367     def handleCollections( self, collections ):
368         """Handle collections"""
369         if collections:
370             collection_list = collections.split()
371             for c in collection_list:
372                 regex = bb.data.getVar("BBFILE_PATTERN_%s" % c, self.configuration.data, 1)
373                 if regex == None:
374                     bb.msg.error(bb.msg.domain.Parsing, "BBFILE_PATTERN_%s not defined" % c)
375                     continue
376                 priority = bb.data.getVar("BBFILE_PRIORITY_%s" % c, self.configuration.data, 1)
377                 if priority == None:
378                     bb.msg.error(bb.msg.domain.Parsing, "BBFILE_PRIORITY_%s not defined" % c)
379                     continue
380                 try:
381                     cre = re.compile(regex)
382                 except re.error:
383                     bb.msg.error(bb.msg.domain.Parsing, "BBFILE_PATTERN_%s \"%s\" is not a valid regular expression" % (c, regex))
384                     continue
385                 try:
386                     pri = int(priority)
387                     self.status.bbfile_config_priorities.append((cre, pri))
388                 except ValueError:
389                     bb.msg.error(bb.msg.domain.Parsing, "invalid value for BBFILE_PRIORITY_%s: \"%s\"" % (c, priority))
390
391
392     def cook( self, configuration, args ):
393         """
394         We are building stuff here. We do the building
395         from here. By default we try to execute task
396         build.
397         """
398
399         self.configuration = configuration
400
401         if self.configuration.verbose:
402             bb.msg.set_verbose(True)
403
404         if self.configuration.debug:
405             bb.msg.set_debug_level(self.configuration.debug)
406         else:
407             bb.msg.set_debug_level(0)
408
409         if self.configuration.debug_domains:
410             bb.msg.set_debug_domains(self.configuration.debug_domains)
411
412         self.configuration.data = bb.data.init()
413
414         for f in self.configuration.file:
415             self.parseConfigurationFile( f )
416
417         self.parseConfigurationFile( os.path.join( "conf", "bitbake.conf" ) )
418
419         if not self.configuration.cmd:
420             self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data)
421
422         # For backwards compatibility - REMOVE ME
423         if not self.configuration.cmd:
424             self.configuration.cmd = "build"
425
426         #
427         # Special updated configuration we use for firing events
428         #
429         self.configuration.event_data = bb.data.createCopy(self.configuration.data)
430         bb.data.update_data(self.configuration.event_data)
431
432         if self.configuration.show_environment:
433             self.showEnvironment()
434             sys.exit( 0 )
435
436         # inject custom variables
437         if not bb.data.getVar("BUILDNAME", self.configuration.data):
438             bb.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), self.configuration.data)
439         bb.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S',time.gmtime()),self.configuration.data)
440
441         buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
442
443         if self.configuration.interactive:
444             self.interactiveMode()
445
446         if self.configuration.buildfile is not None:
447             bf = os.path.abspath( self.configuration.buildfile )
448             try:
449                 os.stat(bf)
450             except OSError:
451                 (filelist, masked) = self.collect_bbfiles()
452                 regexp = re.compile(self.configuration.buildfile)
453                 matches = []
454                 for f in filelist:
455                     if regexp.search(f) and os.path.isfile(f):
456                         bf = f
457                         matches.append(f)
458                 if len(matches) != 1:
459                     bb.msg.error(bb.msg.domain.Parsing, "Unable to match %s (%s matches found):" % (self.configuration.buildfile, len(matches)))
460                     for f in matches:
461                         bb.msg.error(bb.msg.domain.Parsing, "    %s" % f)
462                     sys.exit(1)
463                 bf = matches[0]             
464
465             bbfile_data = bb.parse.handle(bf, self.configuration.data)
466
467             item = bb.data.getVar('PN', bbfile_data, 1)
468             try:
469                 self.tryBuildPackage(bf, item, self.configuration.cmd, bbfile_data, True)
470             except bb.build.EventException:
471                 bb.msg.error(bb.msg.domain.Build,  "Build of '%s' failed" % item )
472
473             sys.exit( self.stats.show() )
474
475         # initialise the parsing status now we know we will need deps
476         self.status = bb.cache.CacheData()
477
478         ignore = bb.data.getVar("ASSUME_PROVIDED", self.configuration.data, 1) or ""
479         self.status.ignored_dependencies = Set( ignore.split() )
480
481         self.handleCollections( bb.data.getVar("BBFILE_COLLECTIONS", self.configuration.data, 1) )
482
483         pkgs_to_build = None
484         if args:
485             if not pkgs_to_build:
486                 pkgs_to_build = []
487             pkgs_to_build.extend(args)
488         if not pkgs_to_build:
489                 bbpkgs = bb.data.getVar('BBPKGS', self.configuration.data, 1)
490                 if bbpkgs:
491                         pkgs_to_build = bbpkgs.split()
492         if not pkgs_to_build and not self.configuration.show_versions \
493                              and not self.configuration.interactive \
494                              and not self.configuration.show_environment:
495                 print "Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help'"
496                 print "for usage information."
497                 sys.exit(0)
498
499         # Import Psyco if available and not disabled
500         if not self.configuration.disable_psyco:
501             try:
502                 import psyco
503             except ImportError:
504                 bb.msg.note(1, bb.msg.domain.Collection, "Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
505             else:
506                 psyco.bind( self.parse_bbfiles )
507         else:
508             bb.msg.note(1, bb.msg.domain.Collection, "You have disabled Psyco. This decreases performance.")
509
510         try:
511             bb.msg.debug(1, bb.msg.domain.Collection, "collecting .bb files")
512             (filelist, masked) = self.collect_bbfiles()
513             self.parse_bbfiles(filelist, masked, self.myProgressCallback)
514             bb.msg.debug(1, bb.msg.domain.Collection, "parsing complete")
515             print
516             if self.configuration.parse_only:
517                 bb.msg.note(1, bb.msg.domain.Collection, "Requested parsing .bb files only.  Exiting.")
518                 return
519
520
521             self.buildDepgraph()
522
523             if self.configuration.show_versions:
524                 self.showVersions()
525                 sys.exit( 0 )
526             if 'world' in pkgs_to_build:
527                 self.buildWorldTargetList()
528                 pkgs_to_build.remove('world')
529                 for t in self.status.world_target:
530                     pkgs_to_build.append(t)
531
532             if self.configuration.dot_graph:
533                 self.generateDotGraph( pkgs_to_build, self.configuration.ignored_dot_deps )
534                 sys.exit( 0 )
535
536             bb.event.fire(bb.event.BuildStarted(buildname, pkgs_to_build, self.configuration.event_data))
537
538             localdata = data.createCopy(self.configuration.data)
539             bb.data.update_data(localdata)
540             bb.data.expandKeys(localdata)
541
542             taskdata = bb.taskdata.TaskData(self.configuration.abort)
543
544             runlist = []
545             try:
546                 for k in pkgs_to_build:
547                     taskdata.add_provider(localdata, self.status, k)
548                     runlist.append([k, "do_%s" % self.configuration.cmd])
549                 taskdata.add_unresolved(localdata, self.status)
550             except bb.providers.NoProvider:
551                 sys.exit(1)
552
553             rq = bb.runqueue.RunQueue()
554             rq.prepare_runqueue(self.configuration.data, self.status, taskdata, runlist)
555             try:
556                 failures = rq.execute_runqueue(self, self.configuration.data, self.status, taskdata, runlist)
557             except runqueue.TaskFailure, fnids:
558                 for fnid in fnids:
559                     bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
560                 sys.exit(1)
561             bb.event.fire(bb.event.BuildCompleted(buildname, pkgs_to_build, self.configuration.event_data, failures))
562
563             sys.exit( self.stats.show() )
564
565         except KeyboardInterrupt:
566             bb.msg.note(1, bb.msg.domain.Collection, "KeyboardInterrupt - Build not completed.")
567             sys.exit(1)
568
569     def get_bbfiles( self, path = os.getcwd() ):
570         """Get list of default .bb files by reading out the current directory"""
571         contents = os.listdir(path)
572         bbfiles = []
573         for f in contents:
574             (root, ext) = os.path.splitext(f)
575             if ext == ".bb":
576                 bbfiles.append(os.path.abspath(os.path.join(os.getcwd(),f)))
577         return bbfiles
578
579     def find_bbfiles( self, path ):
580         """Find all the .bb files in a directory (uses find)"""
581         findcmd = 'find ' + path + ' -name *.bb | grep -v SCCS/'
582         try:
583             finddata = os.popen(findcmd)
584         except OSError:
585             return []
586         return finddata.readlines()
587
588     def collect_bbfiles( self ):
589         """Collect all available .bb build files"""
590         parsed, cached, skipped, masked = 0, 0, 0, 0
591         self.bb_cache = bb.cache.init(self)
592
593         files = (data.getVar( "BBFILES", self.configuration.data, 1 ) or "").split()
594         data.setVar("BBFILES", " ".join(files), self.configuration.data)
595
596         if not len(files):
597             files = self.get_bbfiles()
598
599         if not len(files):
600             bb.msg.error(bb.msg.domain.Collection, "no files to build.")
601
602         newfiles = []
603         for f in files:
604             if os.path.isdir(f):
605                 dirfiles = self.find_bbfiles(f)
606                 if dirfiles:
607                     newfiles += dirfiles
608                     continue
609             newfiles += glob.glob(f) or [ f ]
610
611         bbmask = bb.data.getVar('BBMASK', self.configuration.data, 1)
612
613         if not bbmask:
614             return (newfiles, 0)
615
616         try:
617             bbmask_compiled = re.compile(bbmask)
618         except sre_constants.error:
619             bb.msg.fatal(bb.msg.domain.Collection, "BBMASK is not a valid regular expression.")
620
621         finalfiles = []
622         for i in xrange( len( newfiles ) ):
623             f = newfiles[i]
624             if bbmask and bbmask_compiled.search(f):
625                 bb.msg.debug(1, bb.msg.domain.Collection, "skipping masked file %s" % f)
626                 masked += 1
627                 continue
628             finalfiles.append(f)
629
630         return (finalfiles, masked)
631
632     def parse_bbfiles(self, filelist, masked, progressCallback = None):
633         parsed, cached, skipped = 0, 0, 0
634         for i in xrange( len( filelist ) ):
635             f = filelist[i]
636
637             bb.msg.debug(1, bb.msg.domain.Collection, "parsing %s" % f)
638
639             # read a file's metadata
640             try:
641                 fromCache, skip = self.bb_cache.loadData(f, self.configuration.data)
642                 if skip:
643                     skipped += 1
644                     bb.msg.debug(2, bb.msg.domain.Collection, "skipping %s" % f)
645                     self.bb_cache.skip(f)
646                     continue
647                 elif fromCache: cached += 1
648                 else: parsed += 1
649                 deps = None
650
651                 # Disabled by RP as was no longer functional
652                 # allow metadata files to add items to BBFILES
653                 #data.update_data(self.pkgdata[f])
654                 #addbbfiles = self.bb_cache.getVar('BBFILES', f, False) or None
655                 #if addbbfiles:
656                 #    for aof in addbbfiles.split():
657                 #        if not files.count(aof):
658                 #            if not os.path.isabs(aof):
659                 #                aof = os.path.join(os.path.dirname(f),aof)
660                 #            files.append(aof)
661
662                 self.bb_cache.handle_data(f, self.status)
663
664                 # now inform the caller
665                 if progressCallback is not None:
666                     progressCallback( i + 1, len( filelist ), f, fromCache )
667
668             except IOError, e:
669                 self.bb_cache.remove(f)
670                 bb.msg.error(bb.msg.domain.Collection, "opening %s: %s" % (f, e))
671                 pass
672             except KeyboardInterrupt:
673                 self.bb_cache.sync()
674                 raise
675             except Exception, e:
676                 self.bb_cache.remove(f)
677                 bb.msg.error(bb.msg.domain.Collection, "%s while parsing %s" % (e, f))
678             except:
679                 self.bb_cache.remove(f)
680                 raise
681
682         if progressCallback is not None:
683             print "\r" # need newline after Handling Bitbake files message
684             bb.msg.note(1, bb.msg.domain.Collection, "Parsing finished. %d cached, %d parsed, %d skipped, %d masked." % ( cached, parsed, skipped, masked ))
685
686         self.bb_cache.sync()
687
688 #============================================================================#
689 # main
690 #============================================================================#
691
692 def main():
693     parser = optparse.OptionParser( version = "BitBake Build Tool Core version %s, %%prog version %s" % ( bb.__version__, __version__ ),
694     usage = """%prog [options] [package ...]
695
696 Executes the specified task (default is 'build') for a given set of BitBake files.
697 It expects that BBFILES is defined, which is a space seperated list of files to
698 be executed.  BBFILES does support wildcards.
699 Default BBFILES are the .bb files in the current directory.""" )
700
701     parser.add_option( "-b", "--buildfile", help = "execute the task against this .bb file, rather than a package from BBFILES.",
702                action = "store", dest = "buildfile", default = None )
703
704     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.",
705                action = "store_false", dest = "abort", default = True )
706
707     parser.add_option( "-f", "--force", help = "force run of specified cmd, regardless of stamp status",
708                action = "store_true", dest = "force", default = False )
709
710     parser.add_option( "-i", "--interactive", help = "drop into the interactive mode also called the BitBake shell.",
711                action = "store_true", dest = "interactive", default = False )
712
713     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",
714                action = "store", dest = "cmd" )
715
716     parser.add_option( "-r", "--read", help = "read the specified file before bitbake.conf",
717                action = "append", dest = "file", default = [] )
718
719     parser.add_option( "-v", "--verbose", help = "output more chit-chat to the terminal",
720                action = "store_true", dest = "verbose", default = False )
721
722     parser.add_option( "-D", "--debug", help = "Increase the debug level. You can specify this more than once.",
723                action = "count", dest="debug", default = 0)
724
725     parser.add_option( "-n", "--dry-run", help = "don't execute, just go through the motions",
726                action = "store_true", dest = "dry_run", default = False )
727
728     parser.add_option( "-p", "--parse-only", help = "quit after parsing the BB files (developers only)",
729                action = "store_true", dest = "parse_only", default = False )
730
731     parser.add_option( "-d", "--disable-psyco", help = "disable using the psyco just-in-time compiler (not recommended)",
732                action = "store_true", dest = "disable_psyco", default = False )
733
734     parser.add_option( "-s", "--show-versions", help = "show current and preferred versions of all packages",
735                action = "store_true", dest = "show_versions", default = False )
736
737     parser.add_option( "-e", "--environment", help = "show the global or per-package environment (this is what used to be bbread)",
738                action = "store_true", dest = "show_environment", default = False )
739
740     parser.add_option( "-g", "--graphviz", help = "emit the dependency trees of the specified packages in the dot syntax",
741                 action = "store_true", dest = "dot_graph", default = False )
742
743     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""",
744                 action = "append", dest = "ignored_dot_deps", default = [] )
745
746     parser.add_option( "-l", "--log-domains", help = """Show debug logging for the specified logging domains""",
747                 action = "append", dest = "debug_domains", default = [] )
748
749
750     options, args = parser.parse_args( sys.argv )
751
752     cooker = BBCooker()
753     cooker.cook( BBConfiguration( options ), args[1:] )
754
755
756
757 if __name__ == "__main__":
758     print """WARNING, WARNING, WARNING
759 This is a Bitbake from the Unstable/Development Branch.
760 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."""
761     import time
762     time.sleep(5)
763     main()