summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/cooker.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/cooker.py')
-rw-r--r--bitbake/lib/bb/cooker.py761
1 files changed, 482 insertions, 279 deletions
diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
index 14ccfb5..8036d7e 100644
--- a/bitbake/lib/bb/cooker.py
+++ b/bitbake/lib/bb/cooker.py
@@ -7,7 +7,7 @@
# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
# Copyright (C) 2005 Holger Hans Peter Freyther
# Copyright (C) 2005 ROAD GmbH
-# Copyright (C) 2006 Richard Purdie
+# Copyright (C) 2006 - 2007 Richard Purdie
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -25,9 +25,35 @@
import sys, os, getopt, glob, copy, os.path, re, time
import bb
from bb import utils, data, parse, event, cache, providers, taskdata, runqueue
+from bb import command
+import bb.server.xmlrpc
import itertools, sre_constants
-parsespin = itertools.cycle( r'|/-\\' )
+class MultipleMatches(Exception):
+ """
+ Exception raised when multiple file matches are found
+ """
+
+class ParsingErrorsFound(Exception):
+ """
+ Exception raised when parsing errors are found
+ """
+
+class NothingToBuild(Exception):
+ """
+ Exception raised when there is nothing to build
+ """
+
+
+# Different states cooker can be in
+cookerClean = 1
+cookerParsing = 2
+cookerParsed = 3
+
+# Different action states the cooker can be in
+cookerRun = 1 # Cooker is running normally
+cookerShutdown = 2 # Active tasks should be brought to a controlled stop
+cookerStop = 3 # Stop, now!
#============================================================================#
# BBCooker
@@ -37,12 +63,14 @@ class BBCooker:
Manages one bitbake build run
"""
- def __init__(self, configuration):
+ def __init__(self, configuration, server):
self.status = None
self.cache = None
self.bb_cache = None
+ self.server = server.BitBakeServer(self)
+
self.configuration = configuration
if self.configuration.verbose:
@@ -58,17 +86,15 @@ class BBCooker:
self.configuration.data = bb.data.init()
- def parseConfiguration(self):
-
bb.data.inheritFromOS(self.configuration.data)
- # Add conf/bitbake.conf to the list of configuration files to read
- self.configuration.file.append( os.path.join( "conf", "bitbake.conf" ) )
+ for f in self.configuration.file:
+ self.parseConfigurationFile( f )
- self.parseConfigurationFile(self.configuration.file)
+ self.parseConfigurationFile( os.path.join( "conf", "bitbake.conf" ) )
if not self.configuration.cmd:
- self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data) or "build"
+ self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data, True) or "build"
bbpkgs = bb.data.getVar('BBPKGS', self.configuration.data, True)
if bbpkgs and len(self.configuration.pkgs_to_build) == 0:
@@ -80,9 +106,7 @@ class BBCooker:
self.configuration.event_data = bb.data.createCopy(self.configuration.data)
bb.data.update_data(self.configuration.event_data)
- #
# TOSTOP must not be set or our children will hang when they output
- #
fd = sys.stdout.fileno()
if os.isatty(fd):
import termios
@@ -92,40 +116,91 @@ class BBCooker:
tcattr[3] = tcattr[3] & ~termios.TOSTOP
termios.tcsetattr(fd, termios.TCSANOW, tcattr)
+ self.command = bb.command.Command(self)
+ self.cookerState = cookerClean
+ self.cookerAction = cookerRun
+
+ def parseConfiguration(self):
+
+
# Change nice level if we're asked to
nice = bb.data.getVar("BB_NICE_LEVEL", self.configuration.data, True)
if nice:
curnice = os.nice(0)
nice = int(nice) - curnice
bb.msg.note(2, bb.msg.domain.Build, "Renice to %s " % os.nice(nice))
-
+
+ def parseCommandLine(self):
+ # Parse any commandline into actions
+ if self.configuration.show_environment:
+ self.commandlineAction = None
+
+ if 'world' in self.configuration.pkgs_to_build:
+ bb.error("'world' is not a valid target for --environment.")
+ elif len(self.configuration.pkgs_to_build) > 1:
+ bb.error("Only one target can be used with the --environment option.")
+ elif self.configuration.buildfile and len(self.configuration.pkgs_to_build) > 0:
+ bb.error("No target should be used with the --environment and --buildfile options.")
+ elif len(self.configuration.pkgs_to_build) > 0:
+ self.commandlineAction = ["showEnvironmentTarget", self.configuration.pkgs_to_build]
+ else:
+ self.commandlineAction = ["showEnvironment", self.configuration.buildfile]
+ elif self.configuration.buildfile is not None:
+ self.commandlineAction = ["buildFile", self.configuration.buildfile, self.configuration.cmd]
+ elif self.configuration.revisions_changed:
+ self.commandlineAction = ["compareRevisions"]
+ elif self.configuration.show_versions:
+ self.commandlineAction = ["showVersions"]
+ elif self.configuration.parse_only:
+ self.commandlineAction = ["parseFiles"]
+ # FIXME - implement
+ #elif self.configuration.interactive:
+ # self.interactiveMode()
+ elif self.configuration.dot_graph:
+ if self.configuration.pkgs_to_build:
+ self.commandlineAction = ["generateDotGraph", self.configuration.pkgs_to_build, self.configuration.cmd]
+ else:
+ self.commandlineAction = None
+ bb.error("Please specify a package name for dependency graph generation.")
+ else:
+ if self.configuration.pkgs_to_build:
+ self.commandlineAction = ["buildTargets", self.configuration.pkgs_to_build, self.configuration.cmd]
+ else:
+ self.commandlineAction = None
+ bb.error("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+
+ def runCommands(self, server, data, abort):
+ """
+ Run any queued asynchronous command
+ This is done by the idle handler so it runs in true context rather than
+ tied to any UI.
+ """
+
+ return self.command.runAsyncCommand()
def tryBuildPackage(self, fn, item, task, the_data):
"""
Build one task of a package, optionally build following task depends
"""
- bb.event.fire(bb.event.PkgStarted(item, the_data))
try:
if not self.configuration.dry_run:
bb.build.exec_task('do_%s' % task, the_data)
- bb.event.fire(bb.event.PkgSucceeded(item, the_data))
return True
except bb.build.FuncFailed:
bb.msg.error(bb.msg.domain.Build, "task stack execution failed")
- bb.event.fire(bb.event.PkgFailed(item, the_data))
raise
except bb.build.EventException, e:
event = e.args[1]
bb.msg.error(bb.msg.domain.Build, "%s event exception, aborting" % bb.event.getName(event))
- bb.event.fire(bb.event.PkgFailed(item, the_data))
raise
- def tryBuild(self, fn):
+ def tryBuild(self, fn, task):
"""
Build a provider and its dependencies.
build_depends is a list of previous build dependencies (not runtime)
If build_depends is empty, we're dealing with a runtime depends
"""
+
the_data = self.bb_cache.loadDataFull(fn, self.configuration.data)
item = self.status.pkg_fn[fn]
@@ -133,9 +208,13 @@ class BBCooker:
#if bb.build.stamp_is_current('do_%s' % self.configuration.cmd, the_data):
# return True
- return self.tryBuildPackage(fn, item, self.configuration.cmd, the_data)
+ return self.tryBuildPackage(fn, item, task, the_data)
def showVersions(self):
+
+ # Need files parsed
+ self.updateCache()
+
pkg_pn = self.status.pkg_pn
preferred_versions = {}
latest_versions = {}
@@ -149,43 +228,36 @@ class BBCooker:
pkg_list = pkg_pn.keys()
pkg_list.sort()
+ bb.msg.plain("%-35s %25s %25s" % ("Package Name", "Latest Version", "Preferred Version"))
+ bb.msg.plain("%-35s %25s %25s\n" % ("============", "==============", "================="))
+
for p in pkg_list:
pref = preferred_versions[p]
latest = latest_versions[p]
- if pref != latest:
- prefstr = pref[0][0] + ":" + pref[0][1] + '-' + pref[0][2]
- else:
+ prefstr = pref[0][0] + ":" + pref[0][1] + '-' + pref[0][2]
+ lateststr = latest[0][0] + ":" + latest[0][1] + "-" + latest[0][2]
+
+ if pref == latest:
prefstr = ""
- print "%-30s %20s %20s" % (p, latest[0][0] + ":" + latest[0][1] + "-" + latest[0][2],
- prefstr)
+ bb.msg.plain("%-35s %25s %25s" % (p, lateststr, prefstr))
+ def compareRevisions(self):
+ ret = bb.fetch.fetcher_compare_revisons(self.configuration.data)
+ bb.event.fire(bb.command.CookerCommandSetExitCode(ret), self.configuration.event_data)
- def showEnvironment(self , buildfile = None, pkgs_to_build = []):
+ def showEnvironment(self, buildfile = None, pkgs_to_build = []):
"""
Show the outer or per-package environment
"""
fn = None
envdata = None
- if 'world' in pkgs_to_build:
- print "'world' is not a valid target for --environment."
- sys.exit(1)
-
- if len(pkgs_to_build) > 1:
- print "Only one target can be used with the --environment option."
- sys.exit(1)
-
if buildfile:
- if len(pkgs_to_build) > 0:
- print "No target should be used with the --environment and --buildfile options."
- sys.exit(1)
self.cb = None
self.bb_cache = bb.cache.init(self)
fn = self.matchFile(buildfile)
- if not fn:
- sys.exit(1)
elif len(pkgs_to_build) == 1:
self.updateCache()
@@ -193,13 +265,9 @@ class BBCooker:
bb.data.update_data(localdata)
bb.data.expandKeys(localdata)
- taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs)
-
- try:
- taskdata.add_provider(localdata, self.status, pkgs_to_build[0])
- taskdata.add_unresolved(localdata, self.status)
- except bb.providers.NoProvider:
- sys.exit(1)
+ taskdata = bb.taskdata.TaskData(self.configuration.abort)
+ taskdata.add_provider(localdata, self.status, pkgs_to_build[0])
+ taskdata.add_unresolved(localdata, self.status)
targetid = taskdata.getbuild_id(pkgs_to_build[0])
fnid = taskdata.build_targets[targetid][0]
@@ -211,55 +279,69 @@ class BBCooker:
try:
envdata = self.bb_cache.loadDataFull(fn, self.configuration.data)
except IOError, e:
- bb.msg.fatal(bb.msg.domain.Parsing, "Unable to read %s: %s" % (fn, e))
+ bb.msg.error(bb.msg.domain.Parsing, "Unable to read %s: %s" % (fn, e))
+ raise
except Exception, e:
- bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e)
+ bb.msg.error(bb.msg.domain.Parsing, "%s" % e)
+ raise
+
+ class dummywrite:
+ def __init__(self):
+ self.writebuf = ""
+ def write(self, output):
+ self.writebuf = self.writebuf + output
# emit variables and shell functions
try:
- data.update_data( envdata )
- data.emit_env(sys.__stdout__, envdata, True)
+ data.update_data(envdata)
+ wb = dummywrite()
+ data.emit_env(wb, envdata, True)
+ bb.msg.plain(wb.writebuf)
except Exception, e:
bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e)
# emit the metadata which isnt valid shell
- data.expandKeys( envdata )
+ data.expandKeys(envdata)
for e in envdata.keys():
if data.getVarFlag( e, 'python', envdata ):
- sys.__stdout__.write("\npython %s () {\n%s}\n" % (e, data.getVar(e, envdata, 1)))
+ bb.msg.plain("\npython %s () {\n%s}\n" % (e, data.getVar(e, envdata, 1)))
- def generateDotGraph( self, pkgs_to_build, ignore_deps ):
+ def generateDepTreeData(self, pkgs_to_build, task):
"""
- Generate a task dependency graph.
-
- pkgs_to_build A list of packages that needs to be built
- ignore_deps A list of names where processing of dependencies
- should be stopped. e.g. dependencies that get
+ Create a dependency tree of pkgs_to_build, returning the data.
"""
- for dep in ignore_deps:
- self.status.ignored_dependencies.add(dep)
+ # Need files parsed
+ self.updateCache()
+
+ # If we are told to do the None task then query the default task
+ if (task == None):
+ task = self.configuration.cmd
+
+ pkgs_to_build = self.checkPackages(pkgs_to_build)
localdata = data.createCopy(self.configuration.data)
bb.data.update_data(localdata)
bb.data.expandKeys(localdata)
- taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs)
+ taskdata = bb.taskdata.TaskData(self.configuration.abort)
runlist = []
- try:
- for k in pkgs_to_build:
- taskdata.add_provider(localdata, self.status, k)
- runlist.append([k, "do_%s" % self.configuration.cmd])
- taskdata.add_unresolved(localdata, self.status)
- except bb.providers.NoProvider:
- sys.exit(1)
+ for k in pkgs_to_build:
+ taskdata.add_provider(localdata, self.status, k)
+ runlist.append([k, "do_%s" % task])
+ taskdata.add_unresolved(localdata, self.status)
+
rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
rq.prepare_runqueue()
seen_fnids = []
- depends_file = file('depends.dot', 'w' )
- tdepends_file = file('task-depends.dot', 'w' )
- print >> depends_file, "digraph depends {"
- print >> tdepends_file, "digraph depends {"
+ depend_tree = {}
+ depend_tree["depends"] = {}
+ depend_tree["tdepends"] = {}
+ depend_tree["pn"] = {}
+ depend_tree["rdepends-pn"] = {}
+ depend_tree["packages"] = {}
+ depend_tree["rdepends-pkg"] = {}
+ depend_tree["rrecs-pkg"] = {}
for task in range(len(rq.runq_fnid)):
taskname = rq.runq_task[task]
@@ -267,43 +349,118 @@ class BBCooker:
fn = taskdata.fn_index[fnid]
pn = self.status.pkg_fn[fn]
version = "%s:%s-%s" % self.status.pkg_pepvpr[fn]
- print >> tdepends_file, '"%s.%s" [label="%s %s\\n%s\\n%s"]' % (pn, taskname, pn, taskname, version, fn)
+ if pn not in depend_tree["pn"]:
+ depend_tree["pn"][pn] = {}
+ depend_tree["pn"][pn]["filename"] = fn
+ depend_tree["pn"][pn]["version"] = version
for dep in rq.runq_depends[task]:
depfn = taskdata.fn_index[rq.runq_fnid[dep]]
deppn = self.status.pkg_fn[depfn]
- print >> tdepends_file, '"%s.%s" -> "%s.%s"' % (pn, rq.runq_task[task], deppn, rq.runq_task[dep])
+ dotname = "%s.%s" % (pn, rq.runq_task[task])
+ if not dotname in depend_tree["tdepends"]:
+ depend_tree["tdepends"][dotname] = []
+ depend_tree["tdepends"][dotname].append("%s.%s" % (deppn, rq.runq_task[dep]))
if fnid not in seen_fnids:
seen_fnids.append(fnid)
packages = []
- print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)
- for depend in self.status.deps[fn]:
- print >> depends_file, '"%s" -> "%s"' % (pn, depend)
+
+ depend_tree["depends"][pn] = []
+ for dep in taskdata.depids[fnid]:
+ depend_tree["depends"][pn].append(taskdata.build_names_index[dep])
+
+ depend_tree["rdepends-pn"][pn] = []
+ for rdep in taskdata.rdepids[fnid]:
+ depend_tree["rdepends-pn"][pn].append(taskdata.run_names_index[rdep])
+
rdepends = self.status.rundeps[fn]
for package in rdepends:
- for rdepend in re.findall("([\w.-]+)(\ \(.+\))?", rdepends[package]):
- print >> depends_file, '"%s" -> "%s%s" [style=dashed]' % (package, rdepend[0], rdepend[1])
+ depend_tree["rdepends-pkg"][package] = []
+ for rdepend in rdepends[package]:
+ depend_tree["rdepends-pkg"][package].append(rdepend)
packages.append(package)
+
rrecs = self.status.runrecs[fn]
for package in rrecs:
- for rdepend in re.findall("([\w.-]+)(\ \(.+\))?", rrecs[package]):
- print >> depends_file, '"%s" -> "%s%s" [style=dashed]' % (package, rdepend[0], rdepend[1])
+ depend_tree["rrecs-pkg"][package] = []
+ for rdepend in rrecs[package]:
+ depend_tree["rrecs-pkg"][package].append(rdepend)
if not package in packages:
packages.append(package)
+
for package in packages:
- if package != pn:
- print >> depends_file, '"%s" [label="%s(%s) %s\\n%s"]' % (package, package, pn, version, fn)
- for depend in self.status.deps[fn]:
- print >> depends_file, '"%s" -> "%s"' % (package, depend)
- # Prints a flattened form of the above where subpackages of a package are merged into the main pn
- #print >> depends_file, '"%s" [label="%s %s\\n%s\\n%s"]' % (pn, pn, taskname, version, fn)
- #for rdep in taskdata.rdepids[fnid]:
- # print >> depends_file, '"%s" -> "%s" [style=dashed]' % (pn, taskdata.run_names_index[rdep])
- #for dep in taskdata.depids[fnid]:
- # print >> depends_file, '"%s" -> "%s"' % (pn, taskdata.build_names_index[dep])
+ if package not in depend_tree["packages"]:
+ depend_tree["packages"][package] = {}
+ depend_tree["packages"][package]["pn"] = pn
+ depend_tree["packages"][package]["filename"] = fn
+ depend_tree["packages"][package]["version"] = version
+
+ return depend_tree
+
+
+ def generateDepTreeEvent(self, pkgs_to_build, task):
+ """
+ Create a task dependency graph of pkgs_to_build.
+ Generate an event with the result
+ """
+ depgraph = self.generateDepTreeData(pkgs_to_build, task)
+ bb.event.fire(bb.event.DepTreeGenerated(depgraph), self.configuration.data)
+
+ def generateDotGraphFiles(self, pkgs_to_build, task):
+ """
+ Create a task dependency graph of pkgs_to_build.
+ Save the result to a set of .dot files.
+ """
+
+ depgraph = self.generateDepTreeData(pkgs_to_build, task)
+
+ # Prints a flattened form of package-depends below where subpackages of a package are merged into the main pn
+ depends_file = file('pn-depends.dot', 'w' )
+ print >> depends_file, "digraph depends {"
+ for pn in depgraph["pn"]:
+ fn = depgraph["pn"][pn]["filename"]
+ version = depgraph["pn"][pn]["version"]
+ print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)
+ for pn in depgraph["depends"]:
+ for depend in depgraph["depends"][pn]:
+ print >> depends_file, '"%s" -> "%s"' % (pn, depend)
+ for pn in depgraph["rdepends-pn"]:
+ for rdepend in depgraph["rdepends-pn"][pn]:
+ print >> depends_file, '"%s" -> "%s" [style=dashed]' % (pn, rdepend)
+ print >> depends_file, "}"
+ bb.msg.plain("PN dependencies saved to 'pn-depends.dot'")
+
+ depends_file = file('package-depends.dot', 'w' )
+ print >> depends_file, "digraph depends {"
+ for package in depgraph["packages"]:
+ pn = depgraph["packages"][package]["pn"]
+ fn = depgraph["packages"][package]["filename"]
+ version = depgraph["packages"][package]["version"]
+ if package == pn:
+ print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)
+ else:
+ print >> depends_file, '"%s" [label="%s(%s) %s\\n%s"]' % (package, package, pn, version, fn)
+ for depend in depgraph["depends"][pn]:
+ print >> depends_file, '"%s" -> "%s"' % (package, depend)
+ for package in depgraph["rdepends-pkg"]:
+ for rdepend in depgraph["rdepends-pkg"][package]:
+ print >> depends_file, '"%s" -> "%s" [style=dashed]' % (package, rdepend)
+ for package in depgraph["rrecs-pkg"]:
+ for rdepend in depgraph["rrecs-pkg"][package]:
+ print >> depends_file, '"%s" -> "%s" [style=dashed]' % (package, rdepend)
print >> depends_file, "}"
+ bb.msg.plain("Package dependencies saved to 'package-depends.dot'")
+
+ tdepends_file = file('task-depends.dot', 'w' )
+ print >> tdepends_file, "digraph depends {"
+ for task in depgraph["tdepends"]:
+ (pn, taskname) = task.rsplit(".", 1)
+ fn = depgraph["pn"][pn]["filename"]
+ version = depgraph["pn"][pn]["version"]
+ print >> tdepends_file, '"%s.%s" [label="%s %s\\n%s\\n%s"]' % (pn, taskname, pn, taskname, version, fn)
+ for dep in depgraph["tdepends"][task]:
+ print >> tdepends_file, '"%s" -> "%s"' % (task, dep)
print >> tdepends_file, "}"
- bb.msg.note(1, bb.msg.domain.Collection, "Dependencies saved to 'depends.dot'")
- bb.msg.note(1, bb.msg.domain.Collection, "Task dependencies saved to 'task-depends.dot'")
+ bb.msg.plain("Task dependencies saved to 'task-depends.dot'")
def buildDepgraph( self ):
all_depends = self.status.all_depends
@@ -324,7 +481,7 @@ class BBCooker:
try:
(providee, provider) = p.split(':')
except:
- bb.msg.error(bb.msg.domain.Provider, "Malformed option in PREFERRED_PROVIDERS variable: %s" % p)
+ bb.msg.fatal(bb.msg.domain.Provider, "Malformed option in PREFERRED_PROVIDERS variable: %s" % p)
continue
if providee in self.status.preferred and self.status.preferred[providee] != provider:
bb.msg.error(bb.msg.domain.Provider, "conflicting preferences for %s: both %s and %s specified" % (providee, provider, self.status.preferred[providee]))
@@ -362,19 +519,6 @@ class BBCooker:
self.status.possible_world = None
self.status.all_depends = None
- def myProgressCallback( self, x, y, f, from_cache ):
- """Update any tty with the progress change"""
- if os.isatty(sys.stdout.fileno()):
- sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
- sys.stdout.flush()
- else:
- if x == 1:
- sys.stdout.write("Parsing .bb files, please wait...")
- sys.stdout.flush()
- if x == y:
- sys.stdout.write("done.")
- sys.stdout.flush()
-
def interactiveMode( self ):
"""Drop off into a shell"""
try:
@@ -383,12 +527,10 @@ class BBCooker:
bb.msg.fatal(bb.msg.domain.Parsing, "Sorry, shell not available (%s)" % details )
else:
shell.start( self )
- sys.exit( 0 )
- def parseConfigurationFile( self, afiles ):
+ def parseConfigurationFile( self, afile ):
try:
- for afile in afiles:
- self.configuration.data = bb.parse.handle( afile, self.configuration.data )
+ self.configuration.data = bb.parse.handle( afile, self.configuration.data )
# Handle any INHERITs and inherit the base class
inherits = ["base"] + (bb.data.getVar('INHERIT', self.configuration.data, True ) or "").split()
@@ -402,10 +544,10 @@ class BBCooker:
bb.fetch.fetcher_init(self.configuration.data)
- bb.event.fire(bb.event.ConfigParsed(self.configuration.data))
+ bb.event.fire(bb.event.ConfigParsed(), self.configuration.data)
except IOError, e:
- bb.msg.fatal(bb.msg.domain.Parsing, "IO Error: %s" % str(e) )
+ bb.msg.fatal(bb.msg.domain.Parsing, "Error when parsing %s: %s" % (afile, str(e)))
except bb.parse.ParseError, details:
bb.msg.fatal(bb.msg.domain.Parsing, "Unable to parse %s (%s)" % (afile, details) )
@@ -439,17 +581,17 @@ class BBCooker:
"""
if not bb.data.getVar("BUILDNAME", self.configuration.data):
bb.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), self.configuration.data)
- bb.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S',time.gmtime()),self.configuration.data)
+ bb.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S',time.gmtime()), self.configuration.data)
- def matchFile(self, buildfile):
+ def matchFiles(self, buildfile):
"""
- Convert the fragment buildfile into a real file
- Error if there are too many matches
+ Find the .bb files which match the expression in 'buildfile'.
"""
+
bf = os.path.abspath(buildfile)
try:
os.stat(bf)
- return bf
+ return [bf]
except OSError:
(filelist, masked) = self.collect_bbfiles()
regexp = re.compile(buildfile)
@@ -458,27 +600,41 @@ class BBCooker:
if regexp.search(f) and os.path.isfile(f):
bf = f
matches.append(f)
- if len(matches) != 1:
- bb.msg.error(bb.msg.domain.Parsing, "Unable to match %s (%s matches found):" % (buildfile, len(matches)))
- for f in matches:
- bb.msg.error(bb.msg.domain.Parsing, " %s" % f)
- return False
- return matches[0]
+ return matches
- def buildFile(self, buildfile):
+ def matchFile(self, buildfile):
+ """
+ Find the .bb file which matches the expression in 'buildfile'.
+ Raise an error if multiple files
+ """
+ matches = self.matchFiles(buildfile)
+ if len(matches) != 1:
+ bb.msg.error(bb.msg.domain.Parsing, "Unable to match %s (%s matches found):" % (buildfile, len(matches)))
+ for f in matches:
+ bb.msg.error(bb.msg.domain.Parsing, " %s" % f)
+ raise MultipleMatches
+ return matches[0]
+
+ def buildFile(self, buildfile, task):
"""
Build the file matching regexp buildfile
"""
- # Make sure our target is a fully qualified filename
+ # Parse the configuration here. We need to do it explicitly here since
+ # buildFile() doesn't use the cache
+ self.parseConfiguration()
+
+ # If we are told to do the None task then query the default task
+ if (task == None):
+ task = self.configuration.cmd
+
fn = self.matchFile(buildfile)
- if not fn:
- return False
+ self.buildSetVars()
# Load data into the cache for fn and parse the loaded cache data
self.bb_cache = bb.cache.init(self)
self.status = bb.cache.CacheData()
- self.bb_cache.loadData(fn, self.configuration.data, self.status)
+ self.bb_cache.loadData(fn, self.configuration.data, self.status)
# Tweak some variables
item = self.bb_cache.getVar('PN', fn, True)
@@ -493,159 +649,157 @@ class BBCooker:
# Remove stamp for target if force mode active
if self.configuration.force:
- bb.msg.note(2, bb.msg.domain.RunQueue, "Remove stamp %s, %s" % (self.configuration.cmd, fn))
- bb.build.del_stamp('do_%s' % self.configuration.cmd, self.configuration.data)
+ bb.msg.note(2, bb.msg.domain.RunQueue, "Remove stamp %s, %s" % (task, fn))
+ bb.build.del_stamp('do_%s' % task, self.status, fn)
# Setup taskdata structure
- taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs)
+ taskdata = bb.taskdata.TaskData(self.configuration.abort)
taskdata.add_provider(self.configuration.data, self.status, item)
buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
- bb.event.fire(bb.event.BuildStarted(buildname, [item], self.configuration.event_data))
+ bb.event.fire(bb.event.BuildStarted(buildname, [item]), self.configuration.event_data)
# Execute the runqueue
- runlist = [[item, "do_%s" % self.configuration.cmd]]
+ runlist = [[item, "do_%s" % task]]
+
rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
- rq.prepare_runqueue()
- try:
- failures = rq.execute_runqueue()
- except runqueue.TaskFailure, fnids:
+
+ def buildFileIdle(server, rq, abort):
+
+ if abort or self.cookerAction == cookerStop:
+ rq.finish_runqueue(True)
+ elif self.cookerAction == cookerShutdown:
+ rq.finish_runqueue(False)
failures = 0
- for fnid in fnids:
- bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
- failures = failures + 1
- bb.event.fire(bb.event.BuildCompleted(buildname, [item], self.configuration.event_data, failures))
- return False
- bb.event.fire(bb.event.BuildCompleted(buildname, [item], self.configuration.event_data, failures))
- return True
+ try:
+ retval = rq.execute_runqueue()
+ except runqueue.TaskFailure, fnids:
+ for fnid in fnids:
+ bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
+ failures = failures + 1
+ retval = False
+ if not retval:
+ self.command.finishAsyncCommand()
+ bb.event.fire(bb.event.BuildCompleted(buildname, item, failures), self.configuration.event_data)
+ return False
+ return 0.5
+
+ self.server.register_idle_function(buildFileIdle, rq)
- def buildTargets(self, targets):
+ def buildTargets(self, targets, task):
"""
Attempt to build the targets specified
"""
- buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
- bb.event.fire(bb.event.BuildStarted(buildname, targets, self.configuration.event_data))
+ # Need files parsed
+ self.updateCache()
- localdata = data.createCopy(self.configuration.data)
- bb.data.update_data(localdata)
- bb.data.expandKeys(localdata)
+ # If we are told to do the NULL task then query the default task
+ if (task == None):
+ task = self.configuration.cmd
- taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs)
+ targets = self.checkPackages(targets)
- runlist = []
- try:
- for k in targets:
- taskdata.add_provider(localdata, self.status, k)
- runlist.append([k, "do_%s" % self.configuration.cmd])
- taskdata.add_unresolved(localdata, self.status)
- except bb.providers.NoProvider:
- sys.exit(1)
+ def buildTargetsIdle(server, rq, abort):
- rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
- rq.prepare_runqueue()
- try:
- failures = rq.execute_runqueue()
- except runqueue.TaskFailure, fnids:
+ if abort or self.cookerAction == cookerStop:
+ rq.finish_runqueue(True)
+ elif self.cookerAction == cookerShutdown:
+ rq.finish_runqueue(False)
failures = 0
- for fnid in fnids:
- bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
- failures = failures + 1
- bb.event.fire(bb.event.BuildCompleted(buildname, targets, self.configuration.event_data, failures))
- sys.exit(1)
- bb.event.fire(bb.event.BuildCompleted(buildname, targets, self.configuration.event_data, failures))
+ try:
+ retval = rq.execute_runqueue()
+ except runqueue.TaskFailure, fnids:
+ for fnid in fnids:
+ bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
+ failures = failures + 1
+ retval = False
+ if not retval:
+ self.command.finishAsyncCommand()
+ bb.event.fire(bb.event.BuildCompleted(buildname, targets, failures), self.configuration.event_data)
+ return None
+ return 0.5
- sys.exit(0)
+ self.buildSetVars()
- def updateCache(self):
- # Import Psyco if available and not disabled
- import platform
- if platform.machine() in ['i386', 'i486', 'i586', 'i686']:
- if not self.configuration.disable_psyco:
- try:
- import psyco
- except ImportError:
- bb.msg.note(1, bb.msg.domain.Collection, "Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
- else:
- psyco.bind( self.parse_bbfiles )
- else:
- bb.msg.note(1, bb.msg.domain.Collection, "You have disabled Psyco. This decreases performance.")
+ buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
+ bb.event.fire(bb.event.BuildStarted(buildname, targets), self.configuration.event_data)
- self.status = bb.cache.CacheData()
+ localdata = data.createCopy(self.configuration.data)
+ bb.data.update_data(localdata)
+ bb.data.expandKeys(localdata)
- ignore = bb.data.getVar("ASSUME_PROVIDED", self.configuration.data, 1) or ""
- self.status.ignored_dependencies = set( ignore.split() )
+ taskdata = bb.taskdata.TaskData(self.configuration.abort)
- self.handleCollections( bb.data.getVar("BBFILE_COLLECTIONS", self.configuration.data, 1) )
+ runlist = []
+ for k in targets:
+ taskdata.add_provider(localdata, self.status, k)
+ runlist.append([k, "do_%s" % task])
+ taskdata.add_unresolved(localdata, self.status)
- bb.msg.debug(1, bb.msg.domain.Collection, "collecting .bb files")
- (filelist, masked) = self.collect_bbfiles()
- bb.data.renameVar("__depends", "__base_depends", self.configuration.data)
- self.parse_bbfiles(filelist, masked, self.myProgressCallback)
- bb.msg.debug(1, bb.msg.domain.Collection, "parsing complete")
+ rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
- self.buildDepgraph()
+ self.server.register_idle_function(buildTargetsIdle, rq)
- def cook(self):
- """
- We are building stuff here. We do the building
- from here. By default we try to execute task
- build.
- """
+ def updateCache(self):
- # Wipe the OS environment
- bb.utils.empty_environment()
+ if self.cookerState == cookerParsed:
+ return
- if self.configuration.show_environment:
- self.showEnvironment(self.configuration.buildfile, self.configuration.pkgs_to_build)
- sys.exit( 0 )
+ if self.cookerState != cookerParsing:
- self.buildSetVars()
+ self.parseConfiguration ()
- if self.configuration.interactive:
- self.interactiveMode()
+ # Import Psyco if available and not disabled
+ import platform
+ if platform.machine() in ['i386', 'i486', 'i586', 'i686']:
+ if not self.configuration.disable_psyco:
+ try:
+ import psyco
+ except ImportError:
+ bb.msg.note(1, bb.msg.domain.Collection, "Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
+ else:
+ psyco.bind( CookerParser.parse_next )
+ else:
+ bb.msg.note(1, bb.msg.domain.Collection, "You have disabled Psyco. This decreases performance.")
- if self.configuration.buildfile is not None:
- if not self.buildFile(self.configuration.buildfile):
- sys.exit(1)
- sys.exit(0)
+ self.status = bb.cache.CacheData()
- # initialise the parsing status now we know we will need deps
- self.updateCache()
+ ignore = bb.data.getVar("ASSUME_PROVIDED", self.configuration.data, 1) or ""
+ self.status.ignored_dependencies = set(ignore.split())
+
+ for dep in self.configuration.extra_assume_provided:
+ self.status.ignored_dependencies.add(dep)
+
+ self.handleCollections( bb.data.getVar("BBFILE_COLLECTIONS", self.configuration.data, 1) )
- if self.configuration.revisions_changed:
- sys.exit(bb.fetch.fetcher_compare_revisons(self.configuration.data))
+ bb.msg.debug(1, bb.msg.domain.Collection, "collecting .bb files")
+ (filelist, masked) = self.collect_bbfiles()
+ bb.data.renameVar("__depends", "__base_depends", self.configuration.data)
- if self.configuration.parse_only:
- bb.msg.note(1, bb.msg.domain.Collection, "Requested parsing .bb files only. Exiting.")
- return 0
+ self.parser = CookerParser(self, filelist, masked)
+ self.cookerState = cookerParsing
- pkgs_to_build = self.configuration.pkgs_to_build
+ if not self.parser.parse_next():
+ bb.msg.debug(1, bb.msg.domain.Collection, "parsing complete")
+ self.buildDepgraph()
+ self.cookerState = cookerParsed
+ return None
- if len(pkgs_to_build) == 0 and not self.configuration.show_versions:
- print "Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help'"
- print "for usage information."
- sys.exit(0)
+ return True
- try:
- if self.configuration.show_versions:
- self.showVersions()
- sys.exit( 0 )
- if 'world' in pkgs_to_build:
- self.buildWorldTargetList()
- pkgs_to_build.remove('world')
- for t in self.status.world_target:
- pkgs_to_build.append(t)
+ def checkPackages(self, pkgs_to_build):
- if self.configuration.dot_graph:
- self.generateDotGraph( pkgs_to_build, self.configuration.ignored_dot_deps )
- sys.exit( 0 )
+ if len(pkgs_to_build) == 0:
+ raise NothingToBuild
- return self.buildTargets(pkgs_to_build)
+ if 'world' in pkgs_to_build:
+ self.buildWorldTargetList()
+ pkgs_to_build.remove('world')
+ for t in self.status.world_target:
+ pkgs_to_build.append(t)
- except KeyboardInterrupt:
- bb.msg.note(1, bb.msg.domain.Collection, "KeyboardInterrupt - Build not completed.")
- sys.exit(1)
+ return pkgs_to_build
def get_bbfiles( self, path = os.getcwd() ):
"""Get list of default .bb files by reading out the current directory"""
@@ -717,59 +871,108 @@ class BBCooker:
return (finalfiles, masked)
- def parse_bbfiles(self, filelist, masked, progressCallback = None):
- parsed, cached, skipped, error = 0, 0, 0, 0
- for i in xrange( len( filelist ) ):
- f = filelist[i]
+ def serve(self):
- #bb.msg.debug(1, bb.msg.domain.Collection, "parsing %s" % f)
+ # Empty the environment. The environment will be populated as
+ # necessary from the data store.
+ bb.utils.empty_environment()
- # read a file's metadata
+ if self.configuration.profile:
try:
- fromCache, skip = self.bb_cache.loadData(f, self.configuration.data, self.status)
- if skip:
- skipped += 1
- bb.msg.debug(2, bb.msg.domain.Collection, "skipping %s" % f)
- self.bb_cache.skip(f)
- continue
- elif fromCache: cached += 1
- else: parsed += 1
-
- # Disabled by RP as was no longer functional
- # allow metadata files to add items to BBFILES
- #data.update_data(self.pkgdata[f])
- #addbbfiles = self.bb_cache.getVar('BBFILES', f, False) or None
- #if addbbfiles:
- # for aof in addbbfiles.split():
- # if not files.count(aof):
- # if not os.path.isabs(aof):
- # aof = os.path.join(os.path.dirname(f),aof)
- # files.append(aof)
-
- # now inform the caller
- if progressCallback is not None:
- progressCallback( i + 1, len( filelist ), f, fromCache )
+ import cProfile as profile
+ except:
+ import profile
+
+ profile.runctx("self.server.serve_forever()", globals(), locals(), "profile.log")
+
+ # Redirect stdout to capture profile information
+ pout = open('profile.log.processed', 'w')
+ so = sys.stdout.fileno()
+ os.dup2(pout.fileno(), so)
+
+ import pstats
+ p = pstats.Stats('profile.log')
+ p.sort_stats('time')
+ p.print_stats()
+ p.print_callers()
+ p.sort_stats('cumulative')
+ p.print_stats()
+
+ os.dup2(so, pout.fileno())
+ pout.flush()
+ pout.close()
+ else:
+ self.server.serve_forever()
+
+ bb.event.fire(CookerExit(), self.configuration.event_data)
+
+class CookerExit(bb.event.Event):
+ """
+ Notify clients of the Cooker shutdown
+ """
+
+ def __init__(self):
+ bb.event.Event.__init__(self)
+
+class CookerParser:
+ def __init__(self, cooker, filelist, masked):
+ # Internal data
+ self.filelist = filelist
+ self.cooker = cooker
+
+ # Accounting statistics
+ self.parsed = 0
+ self.cached = 0
+ self.error = 0
+ self.masked = masked
+ self.total = len(filelist)
+
+ self.skipped = 0
+ self.virtuals = 0
+
+ # Pointer to the next file to parse
+ self.pointer = 0
+
+ def parse_next(self):
+ if self.pointer < len(self.filelist):
+ f = self.filelist[self.pointer]
+ cooker = self.cooker
+
+ try:
+ fromCache, skipped, virtuals = cooker.bb_cache.loadData(f, cooker.configuration.data, cooker.status)
+ if fromCache:
+ self.cached += 1
+ else:
+ self.parsed += 1
+
+ self.skipped += skipped
+ self.virtuals += virtuals
except IOError, e:
- self.bb_cache.remove(f)
+ self.error += 1
+ cooker.bb_cache.remove(f)
bb.msg.error(bb.msg.domain.Collection, "opening %s: %s" % (f, e))
pass
except KeyboardInterrupt:
- self.bb_cache.sync()
+ cooker.bb_cache.remove(f)
+ cooker.bb_cache.sync()
raise
except Exception, e:
- error += 1
- self.bb_cache.remove(f)
+ self.error += 1
+ cooker.bb_cache.remove(f)
bb.msg.error(bb.msg.domain.Collection, "%s while parsing %s" % (e, f))
except:
- self.bb_cache.remove(f)
+ cooker.bb_cache.remove(f)
raise
+ finally:
+ bb.event.fire(bb.event.ParseProgress(self.cached, self.parsed, self.skipped, self.masked, self.virtuals, self.error, self.total), cooker.configuration.event_data)
- if progressCallback is not None:
- print "\r" # need newline after Handling Bitbake files message
- bb.msg.note(1, bb.msg.domain.Collection, "Parsing finished. %d cached, %d parsed, %d skipped, %d masked." % ( cached, parsed, skipped, masked ))
+ self.pointer += 1
- self.bb_cache.sync()
+ if self.pointer >= self.total:
+ cooker.bb_cache.sync()
+ if self.error > 0:
+ raise ParsingErrorsFound
+ return False
+ return True
- if error > 0:
- bb.msg.fatal(bb.msg.domain.Collection, "Parsing errors found, exiting...")
OpenPOWER on IntegriCloud