summaryrefslogtreecommitdiff
path: root/bitbake/lib
diff options
context:
space:
mode:
authorRichard Purdie <rpurdie@linux.intel.com>2010-01-20 18:46:02 +0000
committerRichard Purdie <rpurdie@linux.intel.com>2010-01-20 18:46:02 +0000
commit22c29d8651668195f72e2f6a8e059d625eb511c3 (patch)
treedd1dd43f0ec47a9964c8a766eb8b3ad75cf51a64 /bitbake/lib
parent1bfd6edef9db9c9175058ae801d1b601e4f15263 (diff)
downloadopenembedded-core-22c29d8651668195f72e2f6a8e059d625eb511c3.tar.gz
openembedded-core-22c29d8651668195f72e2f6a8e059d625eb511c3.tar.bz2
openembedded-core-22c29d8651668195f72e2f6a8e059d625eb511c3.zip
bitbake: Switch to bitbake-dev version (bitbake master upstream)
Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
Diffstat (limited to 'bitbake/lib')
-rw-r--r--bitbake/lib/bb/__init__.py3
-rw-r--r--bitbake/lib/bb/build.py226
-rw-r--r--bitbake/lib/bb/cache.py89
-rw-r--r--bitbake/lib/bb/command.py271
-rw-r--r--bitbake/lib/bb/cooker.py761
-rw-r--r--bitbake/lib/bb/daemonize.py191
-rw-r--r--bitbake/lib/bb/data.py2
-rw-r--r--bitbake/lib/bb/event.py211
-rw-r--r--bitbake/lib/bb/fetch/__init__.py42
-rw-r--r--bitbake/lib/bb/fetch/cvs.py2
-rw-r--r--bitbake/lib/bb/fetch/git.py73
-rw-r--r--bitbake/lib/bb/fetch/local.py4
-rw-r--r--bitbake/lib/bb/fetch/svk.py2
-rw-r--r--bitbake/lib/bb/fetch/wget.py2
-rw-r--r--bitbake/lib/bb/msg.py26
-rw-r--r--bitbake/lib/bb/parse/parse_py/BBHandler.py32
-rw-r--r--bitbake/lib/bb/parse/parse_py/ConfHandler.py13
-rw-r--r--bitbake/lib/bb/providers.py4
-rw-r--r--bitbake/lib/bb/runqueue.py341
-rw-r--r--bitbake/lib/bb/server/__init__.py2
-rw-r--r--bitbake/lib/bb/server/none.py181
-rw-r--r--bitbake/lib/bb/server/xmlrpc.py187
-rw-r--r--bitbake/lib/bb/shell.py19
-rw-r--r--bitbake/lib/bb/taskdata.py38
-rw-r--r--bitbake/lib/bb/ui/__init__.py18
-rw-r--r--bitbake/lib/bb/ui/crumbs/__init__.py18
-rw-r--r--bitbake/lib/bb/ui/crumbs/buildmanager.py457
-rw-r--r--bitbake/lib/bb/ui/crumbs/puccho.glade606
-rw-r--r--bitbake/lib/bb/ui/crumbs/runningbuild.py180
-rw-r--r--bitbake/lib/bb/ui/depexp.py272
-rw-r--r--bitbake/lib/bb/ui/goggle.py77
-rw-r--r--bitbake/lib/bb/ui/knotty.py162
-rw-r--r--bitbake/lib/bb/ui/ncurses.py335
-rw-r--r--bitbake/lib/bb/ui/puccho.py425
-rw-r--r--bitbake/lib/bb/ui/uievent.py125
-rw-r--r--bitbake/lib/bb/ui/uihelper.py49
-rw-r--r--bitbake/lib/bb/utils.py20
37 files changed, 4731 insertions, 735 deletions
diff --git a/bitbake/lib/bb/__init__.py b/bitbake/lib/bb/__init__.py
index b8f7c7f59e..f2f8f656d8 100644
--- a/bitbake/lib/bb/__init__.py
+++ b/bitbake/lib/bb/__init__.py
@@ -21,7 +21,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-__version__ = "1.8.13"
+__version__ = "1.9.0"
__all__ = [
@@ -54,6 +54,7 @@ __all__ = [
# modules
"parse",
"data",
+ "command",
"event",
"build",
"fetch",
diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py
index 1d6742b6e6..6d80b4b549 100644
--- a/bitbake/lib/bb/build.py
+++ b/bitbake/lib/bb/build.py
@@ -25,8 +25,8 @@
#
#Based on functions from the base bb module, Copyright 2003 Holger Schurig
-from bb import data, fetch, event, mkdirhier, utils
-import bb, os
+from bb import data, event, mkdirhier, utils
+import bb, os, sys
# When we execute a python function we'd like certain things
# in all namespaces, hence we add them to __builtins__
@@ -37,7 +37,11 @@ __builtins__['os'] = os
# events
class FuncFailed(Exception):
- """Executed function failed"""
+ """
+ Executed function failed
+ First parameter a message
+ Second paramter is a logfile (optional)
+ """
class EventException(Exception):
"""Exception which is associated with an Event."""
@@ -50,7 +54,9 @@ class TaskBase(event.Event):
def __init__(self, t, d ):
self._task = t
- event.Event.__init__(self, d)
+ self._package = bb.data.getVar("PF", d, 1)
+ event.Event.__init__(self)
+ self._message = "package %s: task %s: %s" % (bb.data.getVar("PF", d, 1), t, bb.event.getName(self)[4:])
def getTask(self):
return self._task
@@ -68,6 +74,10 @@ class TaskSucceeded(TaskBase):
class TaskFailed(TaskBase):
"""Task execution failed"""
+ def __init__(self, msg, logfile, t, d ):
+ self.logfile = logfile
+ self.msg = msg
+ TaskBase.__init__(self, t, d)
class InvalidTask(TaskBase):
"""Invalid Task"""
@@ -104,42 +114,116 @@ def exec_func(func, d, dirs = None):
else:
adir = data.getVar('B', d, 1)
+ # Save current directory
try:
prevdir = os.getcwd()
except OSError:
prevdir = data.getVar('TOPDIR', d, True)
+
+ # Setup logfiles
+ t = data.getVar('T', d, 1)
+ if not t:
+ bb.msg.fatal(bb.msg.domain.Build, "T not set")
+ mkdirhier(t)
+ # Gross hack, FIXME
+ import random
+ logfile = "%s/log.%s.%s.%s" % (t, func, str(os.getpid()),random.random())
+ runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
+
+ # Change to correct directory (if specified)
if adir and os.access(adir, os.F_OK):
os.chdir(adir)
+ # Handle logfiles
+ si = file('/dev/null', 'r')
+ try:
+ if bb.msg.debug_level['default'] > 0 or ispython:
+ so = os.popen("tee \"%s\"" % logfile, "w")
+ else:
+ so = file(logfile, 'w')
+ except OSError, e:
+ bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
+ pass
+
+ se = so
+
+ # Dup the existing fds so we dont lose them
+ osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
+ oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
+ ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
+
+ # Replace those fds with our own
+ os.dup2(si.fileno(), osi[1])
+ os.dup2(so.fileno(), oso[1])
+ os.dup2(se.fileno(), ose[1])
+
locks = []
lockfiles = (data.expand(flags['lockfiles'], d) or "").split()
for lock in lockfiles:
locks.append(bb.utils.lockfile(lock))
- if flags['python']:
- exec_func_python(func, d)
- else:
- exec_func_shell(func, d, flags)
+ try:
+ # Run the function
+ if ispython:
+ exec_func_python(func, d, runfile, logfile)
+ else:
+ exec_func_shell(func, d, runfile, logfile, flags)
+
+ # Restore original directory
+ try:
+ os.chdir(prevdir)
+ except:
+ pass
- for lock in locks:
- bb.utils.unlockfile(lock)
+ finally:
- if os.path.exists(prevdir):
- os.chdir(prevdir)
+ # Unlock any lockfiles
+ for lock in locks:
+ bb.utils.unlockfile(lock)
+
+ # Restore the backup fds
+ os.dup2(osi[0], osi[1])
+ os.dup2(oso[0], oso[1])
+ os.dup2(ose[0], ose[1])
+
+ # Close our logs
+ si.close()
+ so.close()
+ se.close()
-def exec_func_python(func, d):
+ if os.path.exists(logfile) and os.path.getsize(logfile) == 0:
+ bb.msg.debug(2, bb.msg.domain.Build, "Zero size logfile %s, removing" % logfile)
+ os.remove(logfile)
+
+ # Close the backup fds
+ os.close(osi[0])
+ os.close(oso[0])
+ os.close(ose[0])
+
+def exec_func_python(func, d, runfile, logfile):
"""Execute a python BB 'function'"""
- import re
+ import re, os
bbfile = bb.data.getVar('FILE', d, 1)
tmp = "def " + func + "():\n%s" % data.getVar(func, d)
tmp += '\n' + func + '()'
+
+ f = open(runfile, "w")
+ f.write(tmp)
comp = utils.better_compile(tmp, func, bbfile)
g = {} # globals
g['d'] = d
- utils.better_exec(comp, g, tmp, bbfile)
+ try:
+ utils.better_exec(comp, g, tmp, bbfile)
+ except:
+ (t,value,tb) = sys.exc_info()
+
+ if t in [bb.parse.SkipPackage, bb.build.FuncFailed]:
+ raise
+ bb.msg.error(bb.msg.domain.Build, "Function %s failed" % func)
+ raise FuncFailed("function %s failed" % func, logfile)
-def exec_func_shell(func, d, flags):
+def exec_func_shell(func, d, runfile, logfile, flags):
"""Execute a shell BB 'function' Returns true if execution was successful.
For this, it creates a bash shell script in the tmp dectory, writes the local
@@ -149,23 +233,13 @@ def exec_func_shell(func, d, flags):
of the directories you need created prior to execution. The last
item in the list is where we will chdir/cd to.
"""
- import sys
deps = flags['deps']
check = flags['check']
- interact = flags['interactive']
if check in globals():
if globals()[check](func, deps):
return
- global logfile
- t = data.getVar('T', d, 1)
- if not t:
- return 0
- mkdirhier(t)
- logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
- runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
-
f = open(runfile, "w")
f.write("#!/bin/sh -e\n")
if bb.msg.debug_level['default'] > 0: f.write("set -x\n")
@@ -177,91 +251,21 @@ def exec_func_shell(func, d, flags):
os.chmod(runfile, 0775)
if not func:
bb.msg.error(bb.msg.domain.Build, "Function not specified")
- raise FuncFailed()
-
- # open logs
- si = file('/dev/null', 'r')
- try:
- if bb.msg.debug_level['default'] > 0:
- so = os.popen("tee \"%s\"" % logfile, "w")
- else:
- so = file(logfile, 'w')
- except OSError, e:
- bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
- pass
-
- se = so
-
- if not interact:
- # dup the existing fds so we dont lose them
- osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
- oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
- ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
-
- # replace those fds with our own
- os.dup2(si.fileno(), osi[1])
- os.dup2(so.fileno(), oso[1])
- os.dup2(se.fileno(), ose[1])
+ raise FuncFailed("Function not specified for exec_func_shell")
# execute function
- prevdir = os.getcwd()
if flags['fakeroot']:
maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
else:
maybe_fakeroot = ''
lang_environment = "LC_ALL=C "
ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile))
- try:
- os.chdir(prevdir)
- except:
- pass
-
- if not interact:
- # restore the backups
- os.dup2(osi[0], osi[1])
- os.dup2(oso[0], oso[1])
- os.dup2(ose[0], ose[1])
- # close our logs
- si.close()
- so.close()
- se.close()
-
- if os.path.exists(logfile) and os.path.getsize(logfile) == 0:
- bb.msg.debug(2, bb.msg.domain.Build, "Zero size logfile %s, removing" % logfile)
- os.remove(logfile)
-
- # close the backup fds
- os.close(osi[0])
- os.close(oso[0])
- os.close(ose[0])
-
- if ret==0:
- if bb.msg.debug_level['default'] > 0:
- os.remove(runfile)
-# os.remove(logfile)
+ if ret == 0:
return
- else:
- bb.msg.error(bb.msg.domain.Build, "function %s failed" % func)
- if data.getVar("BBINCLUDELOGS", d):
- bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
- number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
- if number_of_lines:
- os.system('tail -n%s %s' % (number_of_lines, logfile))
- elif os.path.exists(logfile):
- f = open(logfile, "r")
- while True:
- l = f.readline()
- if l == '':
- break
- l = l.rstrip()
- print '| %s' % l
- f.close()
- else:
- bb.msg.error(bb.msg.domain.Build, "There was no logfile output")
- else:
- bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
- raise FuncFailed( logfile )
+
+ bb.msg.error(bb.msg.domain.Build, "Function %s failed" % func)
+ raise FuncFailed("function %s failed" % func, logfile)
def exec_task(task, d):
@@ -282,14 +286,20 @@ def exec_task(task, d):
data.setVar('OVERRIDES', 'task-%s:%s' % (task[3:], old_overrides), localdata)
data.update_data(localdata)
data.expandKeys(localdata)
- event.fire(TaskStarted(task, localdata))
+ event.fire(TaskStarted(task, localdata), localdata)
exec_func(task, localdata)
- event.fire(TaskSucceeded(task, localdata))
- except FuncFailed, reason:
- bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason )
- failedevent = TaskFailed(task, d)
- event.fire(failedevent)
- raise EventException("Function failed in task: %s" % reason, failedevent)
+ event.fire(TaskSucceeded(task, localdata), localdata)
+ except FuncFailed, message:
+ # Try to extract the optional logfile
+ try:
+ (msg, logfile) = message
+ except:
+ logfile = None
+ msg = message
+ bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % message )
+ failedevent = TaskFailed(msg, logfile, task, d)
+ event.fire(failedevent, d)
+ raise EventException("Function failed in task: %s" % message, failedevent)
# make stamp, or cause event and raise exception
if not data.getVarFlag(task, 'nostamp', d) and not data.getVarFlag(task, 'selfstamp', d):
diff --git a/bitbake/lib/bb/cache.py b/bitbake/lib/bb/cache.py
index d30d57d33b..2f1b8fa601 100644
--- a/bitbake/lib/bb/cache.py
+++ b/bitbake/lib/bb/cache.py
@@ -134,7 +134,18 @@ class Cache:
self.data = data
# Make sure __depends makes the depends_cache
- self.getVar("__depends", virtualfn, True)
+ # If we're a virtual class we need to make sure all our depends are appended
+ # to the depends of fn.
+ depends = self.getVar("__depends", virtualfn, True) or []
+ if "__depends" not in self.depends_cache[fn] or not self.depends_cache[fn]["__depends"]:
+ self.depends_cache[fn]["__depends"] = depends
+ for dep in depends:
+ if dep not in self.depends_cache[fn]["__depends"]:
+ self.depends_cache[fn]["__depends"].append(dep)
+
+ # Make sure BBCLASSEXTEND always makes the cache too
+ self.getVar('BBCLASSEXTEND', virtualfn, True)
+
self.depends_cache[virtualfn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn)
def virtualfn2realfn(self, virtualfn):
@@ -170,11 +181,8 @@ class Cache:
bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s (full)" % fn)
- bb_data, skipped = self.load_bbfile(fn, cfgData)
- if isinstance(bb_data, dict):
- return bb_data[cls]
-
- return bb_data
+ bb_data = self.load_bbfile(fn, cfgData)
+ return bb_data[cls]
def loadData(self, fn, cfgData, cacheData):
"""
@@ -184,42 +192,39 @@ class Cache:
to record the variables accessed.
Return the cache status and whether the file was skipped when parsed
"""
+ skipped = 0
+ virtuals = 0
+
if fn not in self.checked:
self.cacheValidUpdate(fn)
+
if self.cacheValid(fn):
- if "SKIPPED" in self.depends_cache[fn]:
- return True, True
- self.handle_data(fn, cacheData)
multi = self.getVar('BBCLASSEXTEND', fn, True)
- if multi:
- for cls in multi.split():
- virtualfn = self.realfn2virtual(fn, cls)
- # Pretend we're clean so getVar works
- self.clean[virtualfn] = ""
- self.handle_data(virtualfn, cacheData)
- return True, False
+ for cls in (multi or "").split() + [""]:
+ virtualfn = self.realfn2virtual(fn, cls)
+ if self.depends_cache[virtualfn]["__SKIPPED"]:
+ skipped += 1
+ bb.msg.debug(1, bb.msg.domain.Cache, "Skipping %s" % virtualfn)
+ continue
+ self.handle_data(virtualfn, cacheData)
+ virtuals += 1
+ return True, skipped, virtuals
bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s" % fn)
- bb_data, skipped = self.load_bbfile(fn, cfgData)
-
- if skipped:
- if isinstance(bb_data, dict):
- self.setData(fn, fn, bb_data[""])
- else:
- self.setData(fn, fn, bb_data)
- return False, skipped
+ bb_data = self.load_bbfile(fn, cfgData)
- if isinstance(bb_data, dict):
- for data in bb_data:
- virtualfn = self.realfn2virtual(fn, data)
- self.setData(virtualfn, fn, bb_data[data])
+ for data in bb_data:
+ virtualfn = self.realfn2virtual(fn, data)
+ self.setData(virtualfn, fn, bb_data[data])
+ if self.getVar("__SKIPPED", virtualfn, True):
+ skipped += 1
+ bb.msg.debug(1, bb.msg.domain.Cache, "Skipping %s" % virtualfn)
+ else:
self.handle_data(virtualfn, cacheData)
- return False, skipped
+ virtuals += 1
+ return False, skipped, virtuals
- self.setData(fn, fn, bb_data)
- self.handle_data(fn, cacheData)
- return False, skipped
def cacheValid(self, fn):
"""
@@ -286,16 +291,13 @@ class Cache:
if not fn in self.clean:
self.clean[fn] = ""
- return True
+ # Mark extended class data as clean too
+ multi = self.getVar('BBCLASSEXTEND', fn, True)
+ for cls in (multi or "").split():
+ virtualfn = self.realfn2virtual(fn, cls)
+ self.clean[virtualfn] = ""
- def skip(self, fn):
- """
- Mark a fn as skipped
- Called from the parser
- """
- if not fn in self.depends_cache:
- self.depends_cache[fn] = {}
- self.depends_cache[fn]["SKIPPED"] = "1"
+ return True
def remove(self, fn):
"""
@@ -462,10 +464,7 @@ class Cache:
try:
bb_data = parse.handle(bbfile, bb_data) # read .bb data
os.chdir(oldpath)
- return bb_data, False
- except bb.parse.SkipPackage:
- os.chdir(oldpath)
- return bb_data, True
+ return bb_data
except:
os.chdir(oldpath)
raise
diff --git a/bitbake/lib/bb/command.py b/bitbake/lib/bb/command.py
new file mode 100644
index 0000000000..2bb5365c0c
--- /dev/null
+++ b/bitbake/lib/bb/command.py
@@ -0,0 +1,271 @@
+"""
+BitBake 'Command' module
+
+Provide an interface to interact with the bitbake server through 'commands'
+"""
+
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+The bitbake server takes 'commands' from its UI/commandline.
+Commands are either synchronous or asynchronous.
+Async commands return data to the client in the form of events.
+Sync commands must only return data through the function return value
+and must not trigger events, directly or indirectly.
+Commands are queued in a CommandQueue
+"""
+
+import bb
+
+async_cmds = {}
+sync_cmds = {}
+
+class Command:
+ """
+ A queue of asynchronous commands for bitbake
+ """
+ def __init__(self, cooker):
+
+ self.cooker = cooker
+ self.cmds_sync = CommandsSync()
+ self.cmds_async = CommandsAsync()
+
+ # FIXME Add lock for this
+ self.currentAsyncCommand = None
+
+ for attr in CommandsSync.__dict__:
+ command = attr[:].lower()
+ method = getattr(CommandsSync, attr)
+ sync_cmds[command] = (method)
+
+ for attr in CommandsAsync.__dict__:
+ command = attr[:].lower()
+ method = getattr(CommandsAsync, attr)
+ async_cmds[command] = (method)
+
+ def runCommand(self, commandline):
+ try:
+ command = commandline.pop(0)
+ if command in CommandsSync.__dict__:
+ # Can run synchronous commands straight away
+ return getattr(CommandsSync, command)(self.cmds_sync, self, commandline)
+ if self.currentAsyncCommand is not None:
+ return "Busy (%s in progress)" % self.currentAsyncCommand[0]
+ if command not in CommandsAsync.__dict__:
+ return "No such command"
+ self.currentAsyncCommand = (command, commandline)
+ self.cooker.server.register_idle_function(self.cooker.runCommands, self.cooker)
+ return True
+ except:
+ import traceback
+ return traceback.format_exc()
+
+ def runAsyncCommand(self):
+ try:
+ if self.currentAsyncCommand is not None:
+ (command, options) = self.currentAsyncCommand
+ commandmethod = getattr(CommandsAsync, command)
+ needcache = getattr( commandmethod, "needcache" )
+ if needcache and self.cooker.cookerState != bb.cooker.cookerParsed:
+ self.cooker.updateCache()
+ return True
+ else:
+ commandmethod(self.cmds_async, self, options)
+ return False
+ else:
+ return False
+ except:
+ import traceback
+ self.finishAsyncCommand(traceback.format_exc())
+ return False
+
+ def finishAsyncCommand(self, error = None):
+ if error:
+ bb.event.fire(bb.command.CookerCommandFailed(error), self.cooker.configuration.event_data)
+ else:
+ bb.event.fire(bb.command.CookerCommandCompleted(), self.cooker.configuration.event_data)
+ self.currentAsyncCommand = None
+
+
+class CommandsSync:
+ """
+ A class of synchronous commands
+ These should run quickly so as not to hurt interactive performance.
+ These must not influence any running synchronous command.
+ """
+
+ def stateShutdown(self, command, params):
+ """
+ Trigger cooker 'shutdown' mode
+ """
+ command.cooker.cookerAction = bb.cooker.cookerShutdown
+
+ def stateStop(self, command, params):
+ """
+ Stop the cooker
+ """
+ command.cooker.cookerAction = bb.cooker.cookerStop
+
+ def getCmdLineAction(self, command, params):
+ """
+ Get any command parsed from the commandline
+ """
+ return command.cooker.commandlineAction
+
+ def getVariable(self, command, params):
+ """
+ Read the value of a variable from configuration.data
+ """
+ varname = params[0]
+ expand = True
+ if len(params) > 1:
+ expand = params[1]
+
+ return bb.data.getVar(varname, command.cooker.configuration.data, expand)
+
+ def setVariable(self, command, params):
+ """
+ Set the value of variable in configuration.data
+ """
+ varname = params[0]
+ value = params[1]
+ bb.data.setVar(varname, value, command.cooker.configuration.data)
+
+
+class CommandsAsync:
+ """
+ A class of asynchronous commands
+ These functions communicate via generated events.
+ Any function that requires metadata parsing should be here.
+ """
+
+ def buildFile(self, command, params):
+ """
+ Build a single specified .bb file
+ """
+ bfile = params[0]
+ task = params[1]
+
+ command.cooker.buildFile(bfile, task)
+ buildFile.needcache = False
+
+ def buildTargets(self, command, params):
+ """
+ Build a set of targets
+ """
+ pkgs_to_build = params[0]
+ task = params[1]
+
+ command.cooker.buildTargets(pkgs_to_build, task)
+ buildTargets.needcache = True
+
+ def generateDepTreeEvent(self, command, params):
+ """
+ Generate an event containing the dependency information
+ """
+ pkgs_to_build = params[0]
+ task = params[1]
+
+ command.cooker.generateDepTreeEvent(pkgs_to_build, task)
+ command.finishAsyncCommand()
+ generateDepTreeEvent.needcache = True
+
+ def generateDotGraph(self, command, params):
+ """
+ Dump dependency information to disk as .dot files
+ """
+ pkgs_to_build = params[0]
+ task = params[1]
+
+ command.cooker.generateDotGraphFiles(pkgs_to_build, task)
+ command.finishAsyncCommand()
+ generateDotGraph.needcache = True
+
+ def showVersions(self, command, params):
+ """
+ Show the currently selected versions
+ """
+ command.cooker.showVersions()
+ command.finishAsyncCommand()
+ showVersions.needcache = True
+
+ def showEnvironmentTarget(self, command, params):
+ """
+ Print the environment of a target recipe
+ (needs the cache to work out which recipe to use)
+ """
+ pkg = params[0]
+
+ command.cooker.showEnvironment(None, pkg)
+ command.finishAsyncCommand()
+ showEnvironmentTarget.needcache = True
+
+ def showEnvironment(self, command, params):
+ """
+ Print the standard environment
+ or if specified the environment for a specified recipe
+ """
+ bfile = params[0]
+
+ command.cooker.showEnvironment(bfile)
+ command.finishAsyncCommand()
+ showEnvironment.needcache = False
+
+ def parseFiles(self, command, params):
+ """
+ Parse the .bb files
+ """
+ command.cooker.updateCache()
+ command.finishAsyncCommand()
+ parseFiles.needcache = True
+
+ def compareRevisions(self, command, params):
+ """
+ Parse the .bb files
+ """
+ command.cooker.compareRevisions()
+ command.finishAsyncCommand()
+ compareRevisions.needcache = True
+
+#
+# Events
+#
+class CookerCommandCompleted(bb.event.Event):
+ """
+ Cooker command completed
+ """
+ def __init__(self):
+ bb.event.Event.__init__(self)
+
+
+class CookerCommandFailed(bb.event.Event):
+ """
+ Cooker command completed
+ """
+ def __init__(self, error):
+ bb.event.Event.__init__(self)
+ self.error = error
+
+class CookerCommandSetExitCode(bb.event.Event):
+ """
+ Set the exit code for a cooker command
+ """
+ def __init__(self, exitcode):
+ bb.event.Event.__init__(self)
+ self.exitcode = int(exitcode)
+
+
+
diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
index 14ccfb59aa..8036d7e9d5 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...")
diff --git a/bitbake/lib/bb/daemonize.py b/bitbake/lib/bb/daemonize.py
new file mode 100644
index 0000000000..1a8bb379f4
--- /dev/null
+++ b/bitbake/lib/bb/daemonize.py
@@ -0,0 +1,191 @@
+"""
+Python Deamonizing helper
+
+Configurable daemon behaviors:
+
+ 1.) The current working directory set to the "/" directory.
+ 2.) The current file creation mode mask set to 0.
+ 3.) Close all open files (1024).
+ 4.) Redirect standard I/O streams to "/dev/null".
+
+A failed call to fork() now raises an exception.
+
+References:
+ 1) Advanced Programming in the Unix Environment: W. Richard Stevens
+ 2) Unix Programming Frequently Asked Questions:
+ http://www.erlenstar.demon.co.uk/unix/faq_toc.html
+
+Modified to allow a function to be daemonized and return for
+bitbake use by Richard Purdie
+"""
+
+__author__ = "Chad J. Schroeder"
+__copyright__ = "Copyright (C) 2005 Chad J. Schroeder"
+__version__ = "0.2"
+
+# Standard Python modules.
+import os # Miscellaneous OS interfaces.
+import sys # System-specific parameters and functions.
+
+# Default daemon parameters.
+# File mode creation mask of the daemon.
+# For BitBake's children, we do want to inherit the parent umask.
+UMASK = None
+
+# Default maximum for the number of available file descriptors.
+MAXFD = 1024
+
+# The standard I/O file descriptors are redirected to /dev/null by default.
+if (hasattr(os, "devnull")):
+ REDIRECT_TO = os.devnull
+else:
+ REDIRECT_TO = "/dev/null"
+
+def createDaemon(function, logfile):
+ """
+ Detach a process from the controlling terminal and run it in the
+ background as a daemon, returning control to the caller.
+ """
+
+ try:
+ # Fork a child process so the parent can exit. This returns control to
+ # the command-line or shell. It also guarantees that the child will not
+ # be a process group leader, since the child receives a new process ID
+ # and inherits the parent's process group ID. This step is required
+ # to insure that the next call to os.setsid is successful.
+ pid = os.fork()
+ except OSError, e:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+
+ if (pid == 0): # The first child.
+ # To become the session leader of this new session and the process group
+ # leader of the new process group, we call os.setsid(). The process is
+ # also guaranteed not to have a controlling terminal.
+ os.setsid()
+
+ # Is ignoring SIGHUP necessary?
+ #
+ # It's often suggested that the SIGHUP signal should be ignored before
+ # the second fork to avoid premature termination of the process. The
+ # reason is that when the first child terminates, all processes, e.g.
+ # the second child, in the orphaned group will be sent a SIGHUP.
+ #
+ # "However, as part of the session management system, there are exactly
+ # two cases where SIGHUP is sent on the death of a process:
+ #
+ # 1) When the process that dies is the session leader of a session that
+ # is attached to a terminal device, SIGHUP is sent to all processes
+ # in the foreground process group of that terminal device.
+ # 2) When the death of a process causes a process group to become
+ # orphaned, and one or more processes in the orphaned group are
+ # stopped, then SIGHUP and SIGCONT are sent to all members of the
+ # orphaned group." [2]
+ #
+ # The first case can be ignored since the child is guaranteed not to have
+ # a controlling terminal. The second case isn't so easy to dismiss.
+ # The process group is orphaned when the first child terminates and
+ # POSIX.1 requires that every STOPPED process in an orphaned process
+ # group be sent a SIGHUP signal followed by a SIGCONT signal. Since the
+ # second child is not STOPPED though, we can safely forego ignoring the
+ # SIGHUP signal. In any case, there are no ill-effects if it is ignored.
+ #
+ # import signal # Set handlers for asynchronous events.
+ # signal.signal(signal.SIGHUP, signal.SIG_IGN)
+
+ try:
+ # Fork a second child and exit immediately to prevent zombies. This
+ # causes the second child process to be orphaned, making the init
+ # process responsible for its cleanup. And, since the first child is
+ # a session leader without a controlling terminal, it's possible for
+ # it to acquire one by opening a terminal in the future (System V-
+ # based systems). This second fork guarantees that the child is no
+ # longer a session leader, preventing the daemon from ever acquiring
+ # a controlling terminal.
+ pid = os.fork() # Fork a second child.
+ except OSError, e:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+
+ if (pid == 0): # The second child.
+ # We probably don't want the file mode creation mask inherited from
+ # the parent, so we give the child complete control over permissions.
+ if UMASK is not None:
+ os.umask(UMASK)
+ else:
+ # Parent (the first child) of the second child.
+ os._exit(0)
+ else:
+ # exit() or _exit()?
+ # _exit is like exit(), but it doesn't call any functions registered
+ # with atexit (and on_exit) or any registered signal handlers. It also
+ # closes any open file descriptors. Using exit() may cause all stdio
+ # streams to be flushed twice and any temporary files may be unexpectedly
+ # removed. It's therefore recommended that child branches of a fork()
+ # and the parent branch(es) of a daemon use _exit().
+ return
+
+ # Close all open file descriptors. This prevents the child from keeping
+ # open any file descriptors inherited from the parent. There is a variety
+ # of methods to accomplish this task. Three are listed below.
+ #
+ # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum
+ # number of open file descriptors to close. If it doesn't exists, use
+ # the default value (configurable).
+ #
+ # try:
+ # maxfd = os.sysconf("SC_OPEN_MAX")
+ # except (AttributeError, ValueError):
+ # maxfd = MAXFD
+ #
+ # OR
+ #
+ # if (os.sysconf_names.has_key("SC_OPEN_MAX")):
+ # maxfd = os.sysconf("SC_OPEN_MAX")
+ # else:
+ # maxfd = MAXFD
+ #
+ # OR
+ #
+ # Use the getrlimit method to retrieve the maximum file descriptor number
+ # that can be opened by this process. If there is not limit on the
+ # resource, use the default value.
+ #
+ import resource # Resource usage information.
+ maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+ if (maxfd == resource.RLIM_INFINITY):
+ maxfd = MAXFD
+
+ # Iterate through and close all file descriptors.
+# for fd in range(0, maxfd):
+# try:
+# os.close(fd)
+# except OSError: # ERROR, fd wasn't open to begin with (ignored)
+# pass
+
+ # Redirect the standard I/O file descriptors to the specified file. Since
+ # the daemon has no controlling terminal, most daemons redirect stdin,
+ # stdout, and stderr to /dev/null. This is done to prevent side-effects
+ # from reads and writes to the standard I/O file descriptors.
+
+ # This call to open is guaranteed to return the lowest file descriptor,
+ # which will be 0 (stdin), since it was closed above.
+# os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
+
+ # Duplicate standard input to standard output and standard error.
+# os.dup2(0, 1) # standard output (1)
+# os.dup2(0, 2) # standard error (2)
+
+
+ si = file('/dev/null', 'r')
+ so = file(logfile, 'w')
+ se = so
+
+
+ # Replace those fds with our own
+ os.dup2(si.fileno(), sys.stdin.fileno())
+ os.dup2(so.fileno(), sys.stdout.fileno())
+ os.dup2(se.fileno(), sys.stderr.fileno())
+
+ function()
+
+ os._exit(0)
+
diff --git a/bitbake/lib/bb/data.py b/bitbake/lib/bb/data.py
index f424ac7a22..d3058b9a1d 100644
--- a/bitbake/lib/bb/data.py
+++ b/bitbake/lib/bb/data.py
@@ -37,7 +37,7 @@ the speed is more critical here.
#
#Based on functions from the base bb module, Copyright 2003 Holger Schurig
-import sys, os, re, time, types
+import sys, os, re, types
if sys.argv[0][-5:] == "pydoc":
path = os.path.dirname(os.path.dirname(sys.argv[1]))
else:
diff --git a/bitbake/lib/bb/event.py b/bitbake/lib/bb/event.py
index 9d7341f878..7251d78715 100644
--- a/bitbake/lib/bb/event.py
+++ b/bitbake/lib/bb/event.py
@@ -24,21 +24,18 @@ BitBake build tools.
import os, re
import bb.utils
+import pickle
+
+# This is the pid for which we should generate the event. This is set when
+# the runqueue forks off.
+worker_pid = 0
+worker_pipe = None
class Event:
"""Base class for events"""
- type = "Event"
-
- def __init__(self, d):
- self._data = d
-
- def getData(self):
- return self._data
-
- def setData(self, data):
- self._data = data
- data = property(getData, setData, None, "data property")
+ def __init__(self):
+ self.pid = worker_pid
NotHandled = 0
Handled = 1
@@ -47,75 +44,83 @@ Registered = 10
AlreadyRegistered = 14
# Internal
-_handlers = []
-_handlers_dict = {}
+_handlers = {}
+_ui_handlers = {}
+_ui_handler_seq = 0
-def tmpHandler(event):
- """Default handler for code events"""
- return NotHandled
+def fire(event, d):
+ """Fire off an Event"""
-def defaultTmpHandler():
- tmp = "def tmpHandler(e):\n\t\"\"\"heh\"\"\"\n\treturn NotHandled"
- comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event.defaultTmpHandler")
- return comp
+ if worker_pid != 0:
+ worker_fire(event, d)
+ return
-def fire(event):
- """Fire off an Event"""
- for h in _handlers:
+ for handler in _handlers:
+ h = _handlers[handler]
+ event.data = d
if type(h).__name__ == "code":
exec(h)
- if tmpHandler(event) == Handled:
- return Handled
+ tmpHandler(event)
else:
- if h(event) == Handled:
- return Handled
- return NotHandled
+ h(event)
+ del event.data
+
+ errors = []
+ for h in _ui_handlers:
+ #print "Sending event %s" % event
+ try:
+ # We use pickle here since it better handles object instances
+ # which xmlrpc's marshaller does not. Events *must* be serializable
+ # by pickle.
+ _ui_handlers[h].event.send((pickle.dumps(event)))
+ except:
+ errors.append(h)
+ for h in errors:
+ del _ui_handlers[h]
+
+def worker_fire(event, d):
+ data = "<event>" + pickle.dumps(event) + "</event>"
+ if os.write(worker_pipe, data) != len (data):
+ print "Error sending event to server (short write)"
+
+def fire_from_worker(event, d):
+ if not event.startswith("<event>") or not event.endswith("</event>"):
+ print "Error, not an event"
+ return
+ event = pickle.loads(event[7:-8])
+ bb.event.fire(event, d)
def register(name, handler):
"""Register an Event handler"""
# already registered
- if name in _handlers_dict:
+ if name in _handlers:
return AlreadyRegistered
if handler is not None:
-# handle string containing python code
+ # handle string containing python code
if type(handler).__name__ == "str":
- _registerCode(handler)
+ tmp = "def tmpHandler(e):\n%s" % handler
+ comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._registerCode")
+ _handlers[name] = comp
else:
- _handlers.append(handler)
+ _handlers[name] = handler
- _handlers_dict[name] = 1
return Registered
-def _registerCode(handlerStr):
- """Register a 'code' Event.
- Deprecated interface; call register instead.
-
- Expects to be passed python code as a string, which will
- be passed in turn to compile() and then exec(). Note that
- the code will be within a function, so should have had
- appropriate tabbing put in place."""
- tmp = "def tmpHandler(e):\n%s" % handlerStr
- comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._registerCode")
-# prevent duplicate registration
- _handlers.append(comp)
-
def remove(name, handler):
"""Remove an Event handler"""
+ _handlers.pop(name)
- _handlers_dict.pop(name)
- if type(handler).__name__ == "str":
- return _removeCode(handler)
- else:
- _handlers.remove(handler)
+def register_UIHhandler(handler):
+ bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
+ _ui_handlers[_ui_handler_seq] = handler
+ return _ui_handler_seq
-def _removeCode(handlerStr):
- """Remove a 'code' Event handler
- Deprecated interface; call remove instead."""
- tmp = "def tmpHandler(e):\n%s" % handlerStr
- comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._removeCode")
- _handlers.remove(comp)
+def unregister_UIHhandler(handlerNum):
+ if handlerNum in _ui_handlers:
+ del _ui_handlers[handlerNum]
+ return
def getName(e):
"""Returns the name of a class or class instance"""
@@ -130,17 +135,17 @@ class ConfigParsed(Event):
class RecipeParsed(Event):
""" Recipe Parsing Complete """
- def __init__(self, fn, d):
+ def __init__(self, fn):
self.fn = fn
- Event.__init__(self, d)
+ Event.__init__(self)
class StampUpdate(Event):
"""Trigger for any adjustment of the stamp files to happen"""
- def __init__(self, targets, stampfns, d):
+ def __init__(self, targets, stampfns):
self._targets = targets
self._stampfns = stampfns
- Event.__init__(self, d)
+ Event.__init__(self)
def getStampPrefix(self):
return self._stampfns
@@ -151,29 +156,13 @@ class StampUpdate(Event):
stampPrefix = property(getStampPrefix)
targets = property(getTargets)
-class PkgBase(Event):
- """Base class for package events"""
-
- def __init__(self, t, d):
- self._pkg = t
- Event.__init__(self, d)
-
- def getPkg(self):
- return self._pkg
-
- def setPkg(self, pkg):
- self._pkg = pkg
-
- pkg = property(getPkg, setPkg, None, "pkg property")
-
-
class BuildBase(Event):
"""Base class for bbmake run events"""
- def __init__(self, n, p, c, failures = 0):
+ def __init__(self, n, p, failures = 0):
self._name = n
self._pkgs = p
- Event.__init__(self, c)
+ Event.__init__(self)
self._failures = failures
def getPkgs(self):
@@ -205,33 +194,8 @@ class BuildBase(Event):
cfg = property(getCfg, setCfg, None, "cfg property")
-class DepBase(PkgBase):
- """Base class for dependency events"""
-
- def __init__(self, t, data, d):
- self._dep = d
- PkgBase.__init__(self, t, data)
-
- def getDep(self):
- return self._dep
-
- def setDep(self, dep):
- self._dep = dep
-
- dep = property(getDep, setDep, None, "dep property")
-
-
-class PkgStarted(PkgBase):
- """Package build started"""
-class PkgFailed(PkgBase):
- """Package build failed"""
-
-
-class PkgSucceeded(PkgBase):
- """Package build completed"""
-
class BuildStarted(BuildBase):
"""bbmake build run started"""
@@ -241,18 +205,13 @@ class BuildCompleted(BuildBase):
"""bbmake build run completed"""
-class UnsatisfiedDep(DepBase):
- """Unsatisfied Dependency"""
-class RecursiveDep(DepBase):
- """Recursive Dependency"""
-
class NoProvider(Event):
"""No Provider for an Event"""
- def __init__(self, item, data,runtime=False):
- Event.__init__(self, data)
+ def __init__(self, item, runtime=False):
+ Event.__init__(self)
self._item = item
self._runtime = runtime
@@ -265,8 +224,8 @@ class NoProvider(Event):
class MultipleProviders(Event):
"""Multiple Providers"""
- def __init__(self, item, candidates, data, runtime = False):
- Event.__init__(self, data)
+ def __init__(self, item, candidates, runtime = False):
+ Event.__init__(self)
self._item = item
self._candidates = candidates
self._is_runtime = runtime
@@ -288,3 +247,29 @@ class MultipleProviders(Event):
Get the possible Candidates for a PROVIDER.
"""
return self._candidates
+
+class ParseProgress(Event):
+ """
+ Parsing Progress Event
+ """
+
+ def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total):
+ Event.__init__(self)
+ self.cached = cached
+ self.parsed = parsed
+ self.skipped = skipped
+ self.virtuals = virtuals
+ self.masked = masked
+ self.errors = errors
+ self.sofar = cached + parsed
+ self.total = total
+
+class DepTreeGenerated(Event):
+ """
+ Event when a dependency tree has been generated
+ """
+
+ def __init__(self, depgraph):
+ Event.__init__(self)
+ self._depgraph = depgraph
+
diff --git a/bitbake/lib/bb/fetch/__init__.py b/bitbake/lib/bb/fetch/__init__.py
index 7326ed0f46..ab4658bc3b 100644
--- a/bitbake/lib/bb/fetch/__init__.py
+++ b/bitbake/lib/bb/fetch/__init__.py
@@ -99,6 +99,11 @@ def fetcher_init(d):
pd.delDomain("BB_URI_HEADREVS")
else:
bb.msg.fatal(bb.msg.domain.Fetcher, "Invalid SRCREV cache policy of: %s" % srcrev_policy)
+
+ for m in methods:
+ if hasattr(m, "init"):
+ m.init(d)
+
# Make sure our domains exist
pd.addDomain("BB_URI_HEADREVS")
pd.addDomain("BB_URI_LOCALCOUNT")
@@ -467,6 +472,23 @@ class Fetch(object):
srcrev_internal_helper = staticmethod(srcrev_internal_helper)
+ def localcount_internal_helper(ud, d):
+ """
+ Return:
+ a) a locked localcount if specified
+ b) None otherwise
+ """
+
+ localcount= None
+ if 'name' in ud.parm:
+ pn = data.getVar("PN", d, 1)
+ localcount = data.getVar("LOCALCOUNT_" + ud.parm['name'], d, 1)
+ if not localcount:
+ localcount = data.getVar("LOCALCOUNT", d, 1)
+ return localcount
+
+ localcount_internal_helper = staticmethod(localcount_internal_helper)
+
def try_mirror(d, tarfn):
"""
Try to use a mirrored version of the sources. We do this
@@ -555,12 +577,7 @@ class Fetch(object):
"""
"""
- has_sortable_valid = hasattr(self, "_sortable_revision_valid")
- has_sortable = hasattr(self, "_sortable_revision")
-
- if has_sortable and not has_sortable_valid:
- return self._sortable_revision(url, ud, d)
- elif has_sortable and self._sortable_revision_valid(url, ud, d):
+ if hasattr(self, "_sortable_revision"):
return self._sortable_revision(url, ud, d)
pd = persist_data.PersistData(d)
@@ -568,13 +585,24 @@ class Fetch(object):
latest_rev = self._build_revision(url, ud, d)
last_rev = pd.getValue("BB_URI_LOCALCOUNT", key + "_rev")
- count = pd.getValue("BB_URI_LOCALCOUNT", key + "_count")
+ uselocalcount = bb.data.getVar("BB_LOCALCOUNT_OVERRIDE", d, True) or False
+ count = None
+ if uselocalcount:
+ count = Fetch.localcount_internal_helper(ud, d)
+ if count is None:
+ count = pd.getValue("BB_URI_LOCALCOUNT", key + "_count")
if last_rev == latest_rev:
return str(count + "+" + latest_rev)
+ buildindex_provided = hasattr(self, "_sortable_buildindex")
+ if buildindex_provided:
+ count = self._sortable_buildindex(url, ud, d, latest_rev)
+
if count is None:
count = "0"
+ elif uselocalcount or buildindex_provided:
+ count = str(count)
else:
count = str(int(count) + 1)
diff --git a/bitbake/lib/bb/fetch/cvs.py b/bitbake/lib/bb/fetch/cvs.py
index d8bd4eaf75..90a006500e 100644
--- a/bitbake/lib/bb/fetch/cvs.py
+++ b/bitbake/lib/bb/fetch/cvs.py
@@ -41,7 +41,7 @@ class Cvs(Fetch):
"""
Check to see if a given url can be fetched with cvs.
"""
- return ud.type in ['cvs', 'pserver']
+ return ud.type in ['cvs']
def localpath(self, url, ud, d):
if not "module" in ud.parm:
diff --git a/bitbake/lib/bb/fetch/git.py b/bitbake/lib/bb/fetch/git.py
index 3016f0f00d..0e68325db9 100644
--- a/bitbake/lib/bb/fetch/git.py
+++ b/bitbake/lib/bb/fetch/git.py
@@ -28,6 +28,12 @@ from bb.fetch import runfetchcmd
class Git(Fetch):
"""Class to fetch a module or modules from git repositories"""
+ def init(self, d):
+ #
+ # Only enable _sortable revision if the key is set
+ #
+ if bb.data.getVar("BB_GIT_CLONE_FOR_SRCREV", d, True):
+ self._sortable_buildindex = self._sortable_buildindex_disabled
def supports(self, url, ud, d):
"""
Check to see if a given url can be fetched with git.
@@ -58,10 +64,18 @@ class Git(Fetch):
if not ud.tag or ud.tag == "master":
ud.tag = self.latest_revision(url, ud, d)
+ subdir = ud.parm.get("subpath", "")
+ if subdir != "":
+ if subdir.endswith("/"):
+ subdir = subdir[:-1]
+ subdirpath = os.path.join(ud.path, subdir);
+ else:
+ subdirpath = ud.path;
+
if 'fullclone' in ud.parm:
ud.localfile = ud.mirrortarball
else:
- ud.localfile = data.expand('git_%s%s_%s.tar.gz' % (ud.host, ud.path.replace('/', '.'), ud.tag), d)
+ ud.localfile = data.expand('git_%s%s_%s.tar.gz' % (ud.host, subdirpath.replace('/', '.'), ud.tag), d)
return os.path.join(data.getVar("DL_DIR", d, True), ud.localfile)
@@ -111,10 +125,27 @@ class Git(Fetch):
if os.path.exists(codir):
bb.utils.prunedir(codir)
+ subdir = ud.parm.get("subpath", "")
+ if subdir != "":
+ if subdir.endswith("/"):
+ subdirbase = os.path.basename(subdir[:-1])
+ else:
+ subdirbase = os.path.basename(subdir)
+ else:
+ subdirbase = ""
+
+ if subdir != "":
+ readpathspec = ":%s" % (subdir)
+ codir = os.path.join(codir, "git")
+ coprefix = os.path.join(codir, subdirbase, "")
+ else:
+ readpathspec = ""
+ coprefix = os.path.join(codir, "git", "")
+
bb.mkdirhier(codir)
os.chdir(ud.clonedir)
- runfetchcmd("git read-tree %s" % (ud.tag), d)
- runfetchcmd("git checkout-index -q -f --prefix=%s -a" % (os.path.join(codir, "git", "")), d)
+ runfetchcmd("git read-tree %s%s" % (ud.tag, readpathspec), d)
+ runfetchcmd("git checkout-index -q -f --prefix=%s -a" % (coprefix), d)
os.chdir(codir)
bb.msg.note(1, bb.msg.domain.Fetcher, "Creating tarball of git checkout")
@@ -154,42 +185,32 @@ class Git(Fetch):
def _build_revision(self, url, ud, d):
return ud.tag
- def _sortable_revision_valid(self, url, ud, d):
- return bb.data.getVar("BB_GIT_CLONE_FOR_SRCREV", d, True) or False
-
- def _sortable_revision(self, url, ud, d):
+ def _sortable_buildindex_disabled(self, url, ud, d, rev):
"""
- This is only called when _sortable_revision_valid called true
-
- We will have to get the updated revision.
+ Return a suitable buildindex for the revision specified. This is done by counting revisions
+ using "git rev-list" which may or may not work in different circumstances.
"""
- key = "GIT_CACHED_REVISION-%s-%s" % (gitsrcname, ud.tag)
- if bb.data.getVar(key, d):
- return bb.data.getVar(key, d)
-
-
- # Runtime warning on wrongly configured sources
- if ud.tag == "1":
- bb.msg.error(1, bb.msg.domain.Fetcher, "SRCREV is '1'. This indicates a configuration error of %s" % url)
- return "0+1"
-
cwd = os.getcwd()
# Check if we have the rev already
+
if not os.path.exists(ud.clonedir):
print "no repo"
self.go(None, ud, d)
+ if not os.path.exists(ud.clonedir):
+ bb.msg.error(bb.msg.domain.Fetcher, "GIT repository for %s doesn't exist in %s, cannot get sortable buildnumber, using old value" % (url, ud.clonedir))
+ return None
+
os.chdir(ud.clonedir)
- if not self._contains_ref(ud.tag, d):
+ if not self._contains_ref(rev, d):
self.go(None, ud, d)
- output = runfetchcmd("git rev-list %s -- 2> /dev/null | wc -l" % ud.tag, d, quiet=True)
+ output = runfetchcmd("git rev-list %s -- 2> /dev/null | wc -l" % rev, d, quiet=True)
os.chdir(cwd)
- sortable_revision = "%s+%s" % (output.split()[0], ud.tag)
- bb.data.setVar(key, sortable_revision, d)
- return sortable_revision
-
+ buildindex = "%s" % output.split()[0]
+ bb.msg.debug(1, bb.msg.domain.Fetcher, "GIT repository for %s in %s is returning %s revisions in rev-list before %s" % (url, repodir, buildindex, rev))
+ return buildindex
diff --git a/bitbake/lib/bb/fetch/local.py b/bitbake/lib/bb/fetch/local.py
index 577774e597..f9bdf589cb 100644
--- a/bitbake/lib/bb/fetch/local.py
+++ b/bitbake/lib/bb/fetch/local.py
@@ -33,9 +33,9 @@ from bb.fetch import Fetch
class Local(Fetch):
def supports(self, url, urldata, d):
"""
- Check to see if a given url can be fetched with cvs.
+ Check to see if a given url represents a local fetch.
"""
- return urldata.type in ['file','patch']
+ return urldata.type in ['file']
def localpath(self, url, urldata, d):
"""
diff --git a/bitbake/lib/bb/fetch/svk.py b/bitbake/lib/bb/fetch/svk.py
index 442f85804f..120dad9d4e 100644
--- a/bitbake/lib/bb/fetch/svk.py
+++ b/bitbake/lib/bb/fetch/svk.py
@@ -36,7 +36,7 @@ class Svk(Fetch):
"""Class to fetch a module or modules from svk repositories"""
def supports(self, url, ud, d):
"""
- Check to see if a given url can be fetched with cvs.
+ Check to see if a given url can be fetched with svk.
"""
return ud.type in ['svk']
diff --git a/bitbake/lib/bb/fetch/wget.py b/bitbake/lib/bb/fetch/wget.py
index a0dca94040..fd93c7ec46 100644
--- a/bitbake/lib/bb/fetch/wget.py
+++ b/bitbake/lib/bb/fetch/wget.py
@@ -36,7 +36,7 @@ class Wget(Fetch):
"""Class to fetch urls via 'wget'"""
def supports(self, url, ud, d):
"""
- Check to see if a given url can be fetched with cvs.
+ Check to see if a given url can be fetched with wget.
"""
return ud.type in ['http','https','ftp']
diff --git a/bitbake/lib/bb/msg.py b/bitbake/lib/bb/msg.py
index a1b31e5d60..3fcf7091be 100644
--- a/bitbake/lib/bb/msg.py
+++ b/bitbake/lib/bb/msg.py
@@ -22,8 +22,8 @@ Message handling infrastructure for bitbake
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import sys, os, re, bb
-from bb import utils, event
+import sys, bb
+from bb import event
debug_level = {}
@@ -47,9 +47,9 @@ domain = bb.utils.Enum(
class MsgBase(bb.event.Event):
"""Base class for messages"""
- def __init__(self, msg, d ):
+ def __init__(self, msg):
self._message = msg
- event.Event.__init__(self, d)
+ event.Event.__init__(self)
class MsgDebug(MsgBase):
"""Debug Message"""
@@ -97,33 +97,29 @@ def set_debug_domains(domains):
#
def debug(level, domain, msg, fn = None):
- bb.event.fire(MsgDebug(msg, None))
if not domain:
domain = 'default'
if debug_level[domain] >= level:
- print 'DEBUG: ' + msg
+ bb.event.fire(MsgDebug(msg), None)
def note(level, domain, msg, fn = None):
- bb.event.fire(MsgNote(msg, None))
if not domain:
domain = 'default'
if level == 1 or verbose or debug_level[domain] >= 1:
- print 'NOTE: ' + msg
+ bb.event.fire(MsgNote(msg), None)
def warn(domain, msg, fn = None):
- bb.event.fire(MsgWarn(msg, None))
- print 'WARNING: ' + msg
+ bb.event.fire(MsgWarn(msg), None)
def error(domain, msg, fn = None):
- bb.event.fire(MsgError(msg, None))
+ bb.event.fire(MsgError(msg), None)
print 'ERROR: ' + msg
def fatal(domain, msg, fn = None):
- bb.event.fire(MsgFatal(msg, None))
- print 'ERROR: ' + msg
+ bb.event.fire(MsgFatal(msg), None)
+ print 'FATAL: ' + msg
sys.exit(1)
def plain(msg, fn = None):
- bb.event.fire(MsgPlain(msg, None))
- print msg
+ bb.event.fire(MsgPlain(msg), None)
diff --git a/bitbake/lib/bb/parse/parse_py/BBHandler.py b/bitbake/lib/bb/parse/parse_py/BBHandler.py
index 915db214f5..86fa18ebd2 100644
--- a/bitbake/lib/bb/parse/parse_py/BBHandler.py
+++ b/bitbake/lib/bb/parse/parse_py/BBHandler.py
@@ -94,7 +94,7 @@ def finalise(fn, d):
for f in anonfuncs:
code = code + " %s(d)\n" % f
data.setVar("__anonfunc", code, d)
- build.exec_func_python("__anonfunc", d)
+ build.exec_func("__anonfunc", d)
data.delVar('T', d)
if t:
data.setVar('T', t, d)
@@ -114,7 +114,7 @@ def finalise(fn, d):
tasklist = data.getVar('__BBTASKS', d) or []
bb.build.add_tasks(tasklist, d)
- bb.event.fire(bb.event.RecipeParsed(fn, d))
+ bb.event.fire(bb.event.RecipeParsed(fn), d)
def handle(fn, d, include = 0):
@@ -185,18 +185,26 @@ def handle(fn, d, include = 0):
multi = data.getVar('BBCLASSEXTEND', d, 1)
if multi:
based = bb.data.createCopy(d)
+ else:
+ based = d
+ try:
finalise(fn, based)
- darray = {"": based}
- for cls in multi.split():
- pn = data.getVar('PN', d, True)
- based = bb.data.createCopy(d)
- data.setVar('PN', pn + '-' + cls, based)
- inherit([cls], based)
+ except bb.parse.SkipPackage:
+ bb.data.setVar("__SKIPPED", True, based)
+ darray = {"": based}
+
+ for cls in (multi or "").split():
+ pn = data.getVar('PN', d, True)
+ based = bb.data.createCopy(d)
+ data.setVar('PN', pn + '-' + cls, based)
+ inherit([cls], based)
+ try:
finalise(fn, based)
- darray[cls] = based
- return darray
- else:
- finalise(fn, d)
+ except bb.parse.SkipPackage:
+ bb.data.setVar("__SKIPPED", True, based)
+ darray[cls] = based
+ return darray
+
bbpath.pop(0)
if oldfile:
bb.data.setVar("FILE", oldfile, d)
diff --git a/bitbake/lib/bb/parse/parse_py/ConfHandler.py b/bitbake/lib/bb/parse/parse_py/ConfHandler.py
index c9f1ea13fb..23316ada58 100644
--- a/bitbake/lib/bb/parse/parse_py/ConfHandler.py
+++ b/bitbake/lib/bb/parse/parse_py/ConfHandler.py
@@ -34,10 +34,17 @@ __require_regexp__ = re.compile( r"require\s+(.+)" )
__export_regexp__ = re.compile( r"export\s+(.+)" )
def init(data):
- if not bb.data.getVar('TOPDIR', data):
- bb.data.setVar('TOPDIR', os.getcwd(), data)
+ topdir = bb.data.getVar('TOPDIR', data)
+ if not topdir:
+ topdir = os.getcwd()
+ bb.data.setVar('TOPDIR', topdir, data)
if not bb.data.getVar('BBPATH', data):
- bb.data.setVar('BBPATH', os.path.join(sys.prefix, 'share', 'bitbake'), data)
+ from pkg_resources import Requirement, resource_filename
+ bitbake = Requirement.parse("bitbake")
+ datadir = resource_filename(bitbake, "../share/bitbake")
+ basedir = resource_filename(bitbake, "..")
+ bb.data.setVar('BBPATH', '%s:%s:%s' % (topdir, datadir, basedir), data)
+
def supports(fn, d):
return localpath(fn, d)[-5:] == ".conf"
diff --git a/bitbake/lib/bb/providers.py b/bitbake/lib/bb/providers.py
index 001281a293..8617251ca3 100644
--- a/bitbake/lib/bb/providers.py
+++ b/bitbake/lib/bb/providers.py
@@ -21,7 +21,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import os, re
+import re
from bb import data, utils
import bb
@@ -203,7 +203,7 @@ def _filterProviders(providers, item, cfgData, dataCache):
eligible.append(preferred_versions[pn][1])
# Now add latest verisons
- for pn in pkg_pn.keys():
+ for pn in sortpkg_pn.keys():
if pn in preferred_versions and preferred_versions[pn][1]:
continue
preferred_versions[pn] = findLatestProvider(pn, cfgData, dataCache, sortpkg_pn[pn][0])
diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py
index cce5da4057..c3ad442e47 100644
--- a/bitbake/lib/bb/runqueue.py
+++ b/bitbake/lib/bb/runqueue.py
@@ -37,20 +37,38 @@ class RunQueueStats:
"""
Holds statistics on the tasks handled by the associated runQueue
"""
- def __init__(self):
+ def __init__(self, total):
self.completed = 0
self.skipped = 0
self.failed = 0
+ self.active = 0
+ self.total = total
def taskFailed(self):
+ self.active = self.active - 1
self.failed = self.failed + 1
def taskCompleted(self, number = 1):
+ self.active = self.active - number
self.completed = self.completed + number
def taskSkipped(self, number = 1):
+ self.active = self.active + number
self.skipped = self.skipped + number
+ def taskActive(self):
+ self.active = self.active + 1
+
+# These values indicate the next step due to be run in the
+# runQueue state machine
+runQueuePrepare = 2
+runQueueRunInit = 3
+runQueueRunning = 4
+runQueueFailed = 6
+runQueueCleanUp = 7
+runQueueComplete = 8
+runQueueChildProcess = 9
+
class RunQueueScheduler:
"""
Control the order tasks are scheduled in.
@@ -142,9 +160,9 @@ class RunQueue:
self.cooker = cooker
self.dataCache = dataCache
self.taskData = taskData
+ self.cfgData = cfgData
self.targets = targets
- self.cfgdata = cfgData
self.number_tasks = int(bb.data.getVar("BB_NUMBER_THREADS", cfgData, 1) or 1)
self.multi_provider_whitelist = (bb.data.getVar("MULTI_PROVIDER_WHITELIST", cfgData, 1) or "").split()
self.scheduler = bb.data.getVar("BB_SCHEDULER", cfgData, 1) or "speed"
@@ -152,12 +170,13 @@ class RunQueue:
self.stampwhitelist = bb.data.getVar("BB_STAMP_WHITELIST", cfgData, 1) or ""
def reset_runqueue(self):
-
self.runq_fnid = []
self.runq_task = []
self.runq_depends = []
self.runq_revdeps = []
+ self.state = runQueuePrepare
+
def get_user_idstring(self, task):
fn = self.taskData.fn_index[self.runq_fnid[task]]
taskname = self.runq_task[task]
@@ -653,6 +672,8 @@ class RunQueue:
#self.dump_data(taskData)
+ self.state = runQueueRunInit
+
def check_stamps(self):
unchecked = {}
current = []
@@ -796,39 +817,51 @@ class RunQueue:
(if the abort on failure configuration option isn't set)
"""
- failures = 0
- while 1:
- failed_fnids = []
- try:
- self.execute_runqueue_internal()
- finally:
- if self.master_process:
- failed_fnids = self.finish_runqueue()
- if len(failed_fnids) == 0:
- return failures
+ if self.state is runQueuePrepare:
+ self.prepare_runqueue()
+
+ if self.state is runQueueRunInit:
+ bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue")
+ self.execute_runqueue_initVars()
+
+ if self.state is runQueueRunning:
+ self.execute_runqueue_internal()
+
+ if self.state is runQueueCleanUp:
+ self.finish_runqueue()
+
+ if self.state is runQueueFailed:
if not self.taskData.tryaltconfigs:
- raise bb.runqueue.TaskFailure(failed_fnids)
- for fnid in failed_fnids:
- #print "Failure: %s %s %s" % (fnid, self.taskData.fn_index[fnid], self.runq_task[fnid])
+ raise bb.runqueue.TaskFailure(self.failed_fnids)
+ for fnid in self.failed_fnids:
self.taskData.fail_fnid(fnid)
- failures = failures + 1
self.reset_runqueue()
- self.prepare_runqueue()
+
+ if self.state is runQueueComplete:
+ # All done
+ bb.msg.note(1, bb.msg.domain.RunQueue, "Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed." % (self.stats.completed, self.stats.skipped, self.stats.failed))
+ return False
+
+ if self.state is runQueueChildProcess:
+ print "Child process"
+ return False
+
+ # Loop
+ return True
def execute_runqueue_initVars(self):
- self.stats = RunQueueStats()
+ self.stats = RunQueueStats(len(self.runq_fnid))
- self.active_builds = 0
self.runq_buildable = []
self.runq_running = []
self.runq_complete = []
self.build_pids = {}
+ self.build_pipes = {}
self.failed_fnids = []
- self.master_process = True
# Mark initial buildable tasks
- for task in range(len(self.runq_fnid)):
+ for task in range(self.stats.total):
self.runq_running.append(0)
self.runq_complete.append(0)
if len(self.runq_depends[task]) == 0:
@@ -836,6 +869,10 @@ class RunQueue:
else:
self.runq_buildable.append(0)
+ self.state = runQueueRunning
+
+ event.fire(bb.event.StampUpdate(self.target_pairs, self.dataCache.stamp), self.cfgData)
+
def task_complete(self, task):
"""
Mark a task as completed
@@ -858,26 +895,32 @@ class RunQueue:
taskname = self.runq_task[revdep]
bb.msg.debug(1, bb.msg.domain.RunQueue, "Marking task %s (%s, %s) as buildable" % (revdep, fn, taskname))
+ def task_fail(self, task, exitcode):
+ """
+ Called when a task has failed
+ Updates the state engine with the failure
+ """
+ bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed with %s" % (task, self.get_user_idstring(task), exitcode))
+ self.stats.taskFailed()
+ fnid = self.runq_fnid[task]
+ self.failed_fnids.append(fnid)
+ bb.event.fire(runQueueTaskFailed(task, self.stats, self), self.cfgData)
+ if self.taskData.abort:
+ self.state = runQueueCleanup
+
def execute_runqueue_internal(self):
"""
Run the tasks in a queue prepared by prepare_runqueue
"""
- bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue")
-
- self.execute_runqueue_initVars()
-
- if len(self.runq_fnid) == 0:
+ if self.stats.total == 0:
# nothing to do
- return []
-
- def sigint_handler(signum, frame):
- raise KeyboardInterrupt
-
- event.fire(bb.event.StampUpdate(self.target_pairs, self.dataCache.stamp, self.cfgdata))
+ self.state = runQueueCleanup
while True:
- task = self.sched.next()
+ task = None
+ if self.stats.active < self.number_tasks:
+ task = self.sched.next()
if task is not None:
fn = self.taskData.fn_index[self.runq_fnid[task]]
@@ -885,107 +928,143 @@ class RunQueue:
if self.check_stamp_task(task):
bb.msg.debug(2, bb.msg.domain.RunQueue, "Stamp current task %s (%s)" % (task, self.get_user_idstring(task)))
self.runq_running[task] = 1
+ self.runq_buildable[task] = 1
self.task_complete(task)
self.stats.taskCompleted()
self.stats.taskSkipped()
continue
- bb.msg.note(1, bb.msg.domain.RunQueue, "Running task %d of %d (ID: %s, %s)" % (self.stats.completed + self.active_builds + 1, len(self.runq_fnid), task, self.get_user_idstring(task)))
sys.stdout.flush()
sys.stderr.flush()
- try:
+ try:
+ pipein, pipeout = os.pipe()
pid = os.fork()
except OSError, e:
bb.msg.fatal(bb.msg.domain.RunQueue, "fork failed: %d (%s)" % (e.errno, e.strerror))
if pid == 0:
- # Bypass master process' handling
- self.master_process = False
- # Stop Ctrl+C being sent to children
- # signal.signal(signal.SIGINT, signal.SIG_IGN)
+ os.close(pipein)
+ # Save out the PID so that the event can include it the
+ # events
+ bb.event.worker_pid = os.getpid()
+ bb.event.worker_pipe = pipeout
+
+ self.state = runQueueChildProcess
# Make the child the process group leader
os.setpgid(0, 0)
+ # No stdin
newsi = os.open('/dev/null', os.O_RDWR)
os.dup2(newsi, sys.stdin.fileno())
- self.cooker.configuration.cmd = taskname[3:]
+
+ bb.event.fire(runQueueTaskStarted(task, self.stats, self), self.cfgData)
+ bb.msg.note(1, bb.msg.domain.RunQueue,
+ "Running task %d of %d (ID: %s, %s)" % (self.stats.completed + self.stats.active + 1,
+ self.stats.total,
+ task,
+ self.get_user_idstring(task)))
+
bb.data.setVar("__RUNQUEUE_DO_NOT_USE_EXTERNALLY", self, self.cooker.configuration.data)
try:
- self.cooker.tryBuild(fn)
+ self.cooker.tryBuild(fn, taskname[3:])
except bb.build.EventException:
bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
- sys.exit(1)
+ os._exit(1)
except:
bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
- raise
- sys.exit(0)
+ os._exit(1)
+ os._exit(0)
+
self.build_pids[pid] = task
+ self.build_pipes[pid] = runQueuePipe(pipein, pipeout, self.cfgData)
self.runq_running[task] = 1
- self.active_builds = self.active_builds + 1
- if self.active_builds < self.number_tasks:
+ self.stats.taskActive()
+ if self.stats.active < self.number_tasks:
continue
- if self.active_builds > 0:
- result = os.waitpid(-1, 0)
- self.active_builds = self.active_builds - 1
+
+ for pipe in self.build_pipes:
+ self.build_pipes[pipe].read()
+
+ if self.stats.active > 0:
+ result = os.waitpid(-1, os.WNOHANG)
+ if result[0] is 0 and result[1] is 0:
+ return
task = self.build_pids[result[0]]
+ del self.build_pids[result[0]]
+ self.build_pipes[result[0]].close()
+ del self.build_pipes[result[0]]
if result[1] != 0:
- del self.build_pids[result[0]]
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task)))
- self.failed_fnids.append(self.runq_fnid[task])
- self.stats.taskFailed()
- if not self.taskData.abort:
- continue
- break
+ self.task_fail(task, result[1])
+ return
self.task_complete(task)
self.stats.taskCompleted()
- del self.build_pids[result[0]]
+ bb.event.fire(runQueueTaskCompleted(task, self.stats, self), self.cfgData)
continue
+
+ if len(self.failed_fnids) != 0:
+ self.state = runQueueFailed
+ return
+
+ # Sanity Checks
+ for task in range(self.stats.total):
+ if self.runq_buildable[task] == 0:
+ bb.msg.error(bb.msg.domain.RunQueue, "Task %s never buildable!" % task)
+ if self.runq_running[task] == 0:
+ bb.msg.error(bb.msg.domain.RunQueue, "Task %s never ran!" % task)
+ if self.runq_complete[task] == 0:
+ bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task)
+ self.state = runQueueComplete
return
- def finish_runqueue(self):
+ def finish_runqueue_now(self):
+ bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGINT to remaining %s tasks" % self.stats.active)
+ for k, v in self.build_pids.iteritems():
+ try:
+ os.kill(-k, signal.SIGINT)
+ except:
+ pass
+ for pipe in self.build_pipes:
+ self.build_pipes[pipe].read()
+
+ def finish_runqueue(self, now = False):
+ self.state = runQueueCleanUp
+ if now:
+ self.finish_runqueue_now()
try:
- while self.active_builds > 0:
- bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % self.active_builds)
+ while self.stats.active > 0:
+ bb.event.fire(runQueueExitWait(self.stats.active), self.cfgData)
+ bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % self.stats.active)
tasknum = 1
for k, v in self.build_pids.iteritems():
- bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v), k))
- tasknum = tasknum + 1
- result = os.waitpid(-1, 0)
+ bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v), k))
+ tasknum = tasknum + 1
+ result = os.waitpid(-1, os.WNOHANG)
+ if result[0] is 0 and result[1] is 0:
+ return
task = self.build_pids[result[0]]
- if result[1] != 0:
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task)))
- self.failed_fnids.append(self.runq_fnid[task])
- self.stats.taskFailed()
del self.build_pids[result[0]]
- self.active_builds = self.active_builds - 1
- bb.msg.note(1, bb.msg.domain.RunQueue, "Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed." % (self.stats.completed, self.stats.skipped, self.stats.failed))
- return self.failed_fnids
- except KeyboardInterrupt:
- bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGINT to remaining %s tasks" % self.active_builds)
- for k, v in self.build_pids.iteritems():
- try:
- os.kill(-k, signal.SIGINT)
- except:
- pass
+ self.build_pipes[result[0]].close()
+ del self.build_pipes[result[0]]
+ if result[1] != 0:
+ self.task_fail(task, result[1])
+ else:
+ self.stats.taskCompleted()
+ bb.event.fire(runQueueTaskCompleted(task, self.stats, self), self.cfgData)
+ except:
+ self.finish_runqueue_now()
raise
- # Sanity Checks
- for task in range(len(self.runq_fnid)):
- if self.runq_buildable[task] == 0:
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s never buildable!" % task)
- if self.runq_running[task] == 0:
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s never ran!" % task)
- if self.runq_complete[task] == 0:
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task)
-
- bb.msg.note(1, bb.msg.domain.RunQueue, "Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed." % (self.stats.completed, self.stats.skipped, self.stats.failed))
+ if len(self.failed_fnids) != 0:
+ self.state = runQueueFailed
+ return
- return self.failed_fnids
+ self.state = runQueueComplete
+ return
def dump_data(self, taskQueue):
"""
Dump some debug information on the internal data structures
"""
bb.msg.debug(3, bb.msg.domain.RunQueue, "run_tasks:")
- for task in range(len(self.runq_fnid)):
+ for task in range(len(self.runq_task)):
bb.msg.debug(3, bb.msg.domain.RunQueue, " (%s)%s - %s: %s Deps %s RevDeps %s" % (task,
taskQueue.fn_index[self.runq_fnid[task]],
self.runq_task[task],
@@ -994,7 +1073,7 @@ class RunQueue:
self.runq_revdeps[task]))
bb.msg.debug(3, bb.msg.domain.RunQueue, "sorted_tasks:")
- for task1 in range(len(self.runq_fnid)):
+ for task1 in range(len(self.runq_task)):
if task1 in self.prio_map:
task = self.prio_map[task1]
bb.msg.debug(3, bb.msg.domain.RunQueue, " (%s)%s - %s: %s Deps %s RevDeps %s" % (task,
@@ -1005,6 +1084,58 @@ class RunQueue:
self.runq_revdeps[task]))
+class TaskFailure(Exception):
+ """
+ Exception raised when a task in a runqueue fails
+ """
+ def __init__(self, x):
+ self.args = x
+
+
+class runQueueExitWait(bb.event.Event):
+ """
+ Event when waiting for task processes to exit
+ """
+
+ def __init__(self, remain):
+ self.remain = remain
+ self.message = "Waiting for %s active tasks to finish" % remain
+ bb.event.Event.__init__(self)
+
+class runQueueEvent(bb.event.Event):
+ """
+ Base runQueue event class
+ """
+ def __init__(self, task, stats, rq):
+ self.taskid = task
+ self.taskstring = rq.get_user_idstring(task)
+ self.stats = stats
+ bb.event.Event.__init__(self)
+
+class runQueueTaskStarted(runQueueEvent):
+ """
+ Event notifing a task was started
+ """
+ def __init__(self, task, stats, rq):
+ runQueueEvent.__init__(self, task, stats, rq)
+ self.message = "Running task %s (%d of %d) (%s)" % (task, stats.completed + stats.active + 1, self.stats.total, self.taskstring)
+
+class runQueueTaskFailed(runQueueEvent):
+ """
+ Event notifing a task failed
+ """
+ def __init__(self, task, stats, rq):
+ runQueueEvent.__init__(self, task, stats, rq)
+ self.message = "Task %s failed (%s)" % (task, self.taskstring)
+
+class runQueueTaskCompleted(runQueueEvent):
+ """
+ Event notifing a task completed
+ """
+ def __init__(self, task, stats, rq):
+ runQueueEvent.__init__(self, task, stats, rq)
+ self.message = "Task %s completed (%s)" % (task, self.taskstring)
+
def check_stamp_fn(fn, taskname, d):
rq = bb.data.getVar("__RUNQUEUE_DO_NOT_USE_EXTERNALLY", d)
fnid = rq.taskData.getfn_id(fn)
@@ -1013,3 +1144,31 @@ def check_stamp_fn(fn, taskname, d):
return rq.check_stamp_task(taskid)
return None
+class runQueuePipe():
+ """
+ Abstraction for a pipe between a worker thread and the server
+ """
+ def __init__(self, pipein, pipeout, d):
+ self.fd = pipein
+ os.close(pipeout)
+ self.queue = ""
+ self.d = d
+
+ def read(self):
+ start = len(self.queue)
+ self.queue = self.queue + os.read(self.fd, 1024)
+ end = len(self.queue)
+ index = self.queue.find("</event>")
+ while index != -1:
+ bb.event.fire_from_worker(self.queue[:index+8], self.d)
+ self.queue = self.queue[index+8:]
+ index = self.queue.find("</event>")
+ return (end > start)
+
+ def close(self):
+ while self.read():
+ continue
+ if len(self.queue) > 0:
+ print "Warning, worker left partial message"
+ os.close(self.fd)
+
diff --git a/bitbake/lib/bb/server/__init__.py b/bitbake/lib/bb/server/__init__.py
new file mode 100644
index 0000000000..1a732236e2
--- /dev/null
+++ b/bitbake/lib/bb/server/__init__.py
@@ -0,0 +1,2 @@
+import xmlrpc
+import none
diff --git a/bitbake/lib/bb/server/none.py b/bitbake/lib/bb/server/none.py
new file mode 100644
index 0000000000..ebda111582
--- /dev/null
+++ b/bitbake/lib/bb/server/none.py
@@ -0,0 +1,181 @@
+#
+# BitBake 'dummy' Passthrough Server
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2008 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+ This module implements an xmlrpc server for BitBake.
+
+ Use this by deriving a class from BitBakeXMLRPCServer and then adding
+ methods which you want to "export" via XMLRPC. If the methods have the
+ prefix xmlrpc_, then registering those function will happen automatically,
+ if not, you need to call register_function.
+
+ Use register_idle_function() to add a function which the xmlrpc server
+ calls from within server_forever when no requests are pending. Make sure
+ that those functions are non-blocking or else you will introduce latency
+ in the server's main loop.
+"""
+
+import time
+import bb
+from bb.ui import uievent
+import xmlrpclib
+import pickle
+
+DEBUG = False
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+import inspect, select
+
+class BitBakeServerCommands():
+ def __init__(self, server, cooker):
+ self.cooker = cooker
+ self.server = server
+
+ def runCommand(self, command):
+ """
+ Run a cooker command on the server
+ """
+ #print "Running Command %s" % command
+ return self.cooker.command.runCommand(command)
+
+ def terminateServer(self):
+ """
+ Trigger the server to quit
+ """
+ self.server.server_exit()
+ #print "Server (cooker) exitting"
+ return
+
+ def ping(self):
+ """
+ Dummy method which can be used to check the server is still alive
+ """
+ return True
+
+eventQueue = []
+
+class BBUIEventQueue:
+ class event:
+ def __init__(self, parent):
+ self.parent = parent
+ @staticmethod
+ def send(event):
+ bb.server.none.eventQueue.append(pickle.loads(event))
+ @staticmethod
+ def quit():
+ return
+
+ def __init__(self, BBServer):
+ self.eventQueue = bb.server.none.eventQueue
+ self.BBServer = BBServer
+ self.EventHandle = bb.event.register_UIHhandler(self)
+
+ def getEvent(self):
+ if len(self.eventQueue) == 0:
+ return None
+
+ return self.eventQueue.pop(0)
+
+ def waitEvent(self, delay):
+ event = self.getEvent()
+ if event:
+ return event
+ self.BBServer.idle_commands(delay)
+ return self.getEvent()
+
+ def queue_event(self, event):
+ self.eventQueue.append(event)
+
+ def system_quit( self ):
+ bb.event.unregister_UIHhandler(self.EventHandle)
+
+class BitBakeServer():
+ # remove this when you're done with debugging
+ # allow_reuse_address = True
+
+ def __init__(self, cooker):
+ self._idlefuns = {}
+ self.commands = BitBakeServerCommands(self, cooker)
+
+ def register_idle_function(self, function, data):
+ """Register a function to be called while the server is idle"""
+ assert callable(function)
+ self._idlefuns[function] = data
+
+ def idle_commands(self, delay):
+ #print "Idle queue length %s" % len(self._idlefuns)
+ #print "Idle timeout, running idle functions"
+ #if len(self._idlefuns) == 0:
+ nextsleep = delay
+ for function, data in self._idlefuns.items():
+ try:
+ retval = function(self, data, False)
+ #print "Idle function returned %s" % (retval)
+ if retval is False:
+ del self._idlefuns[function]
+ elif retval is True:
+ nextsleep = None
+ elif nextsleep is None:
+ continue
+ elif retval < nextsleep:
+ nextsleep = retval
+ except SystemExit:
+ raise
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+ if nextsleep is not None:
+ #print "Sleeping for %s (%s)" % (nextsleep, delay)
+ time.sleep(nextsleep)
+
+ def server_exit(self):
+ # Tell idle functions we're exiting
+ for function, data in self._idlefuns.items():
+ try:
+ retval = function(self, data, True)
+ except:
+ pass
+
+class BitbakeServerInfo():
+ def __init__(self, server):
+ self.server = server
+ self.commands = server.commands
+
+class BitBakeServerFork():
+ def __init__(self, serverinfo, command, logfile):
+ serverinfo.forkCommand = command
+ serverinfo.logfile = logfile
+
+class BitBakeServerConnection():
+ def __init__(self, serverinfo):
+ self.server = serverinfo.server
+ self.connection = serverinfo.commands
+ self.events = bb.server.none.BBUIEventQueue(self.server)
+
+ def terminate(self):
+ try:
+ self.events.system_quit()
+ except:
+ pass
+ try:
+ self.connection.terminateServer()
+ except:
+ pass
+
diff --git a/bitbake/lib/bb/server/xmlrpc.py b/bitbake/lib/bb/server/xmlrpc.py
new file mode 100644
index 0000000000..3364918c77
--- /dev/null
+++ b/bitbake/lib/bb/server/xmlrpc.py
@@ -0,0 +1,187 @@
+#
+# BitBake XMLRPC Server
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2008 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+ This module implements an xmlrpc server for BitBake.
+
+ Use this by deriving a class from BitBakeXMLRPCServer and then adding
+ methods which you want to "export" via XMLRPC. If the methods have the
+ prefix xmlrpc_, then registering those function will happen automatically,
+ if not, you need to call register_function.
+
+ Use register_idle_function() to add a function which the xmlrpc server
+ calls from within server_forever when no requests are pending. Make sure
+ that those functions are non-blocking or else you will introduce latency
+ in the server's main loop.
+"""
+
+import bb
+import xmlrpclib, sys
+from bb import daemonize
+from bb.ui import uievent
+
+DEBUG = False
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+import inspect, select
+
+if sys.hexversion < 0x020600F0:
+ print "Sorry, python 2.6 or later is required for bitbake's XMLRPC mode"
+ sys.exit(1)
+
+class BitBakeServerCommands():
+ def __init__(self, server, cooker):
+ self.cooker = cooker
+ self.server = server
+
+ def registerEventHandler(self, host, port):
+ """
+ Register a remote UI Event Handler
+ """
+ s = xmlrpclib.Server("http://%s:%d" % (host, port), allow_none=True)
+ return bb.event.register_UIHhandler(s)
+
+ def unregisterEventHandler(self, handlerNum):
+ """
+ Unregister a remote UI Event Handler
+ """
+ return bb.event.unregister_UIHhandler(handlerNum)
+
+ def runCommand(self, command):
+ """
+ Run a cooker command on the server
+ """
+ return self.cooker.command.runCommand(command)
+
+ def terminateServer(self):
+ """
+ Trigger the server to quit
+ """
+ self.server.quit = True
+ print "Server (cooker) exitting"
+ return
+
+ def ping(self):
+ """
+ Dummy method which can be used to check the server is still alive
+ """
+ return True
+
+class BitBakeServer(SimpleXMLRPCServer):
+ # remove this when you're done with debugging
+ # allow_reuse_address = True
+
+ def __init__(self, cooker, interface = ("localhost", 0)):
+ """
+ Constructor
+ """
+ SimpleXMLRPCServer.__init__(self, interface,
+ requestHandler=SimpleXMLRPCRequestHandler,
+ logRequests=False, allow_none=True)
+ self._idlefuns = {}
+ self.host, self.port = self.socket.getsockname()
+ #self.register_introspection_functions()
+ commands = BitBakeServerCommands(self, cooker)
+ self.autoregister_all_functions(commands, "")
+
+ def autoregister_all_functions(self, context, prefix):
+ """
+ Convenience method for registering all functions in the scope
+ of this class that start with a common prefix
+ """
+ methodlist = inspect.getmembers(context, inspect.ismethod)
+ for name, method in methodlist:
+ if name.startswith(prefix):
+ self.register_function(method, name[len(prefix):])
+
+ def register_idle_function(self, function, data):
+ """Register a function to be called while the server is idle"""
+ assert callable(function)
+ self._idlefuns[function] = data
+
+ def serve_forever(self):
+ """
+ Serve Requests. Overloaded to honor a quit command
+ """
+ self.quit = False
+ self.timeout = 0 # Run Idle calls for our first callback
+ while not self.quit:
+ #print "Idle queue length %s" % len(self._idlefuns)
+ self.handle_request()
+ #print "Idle timeout, running idle functions"
+ nextsleep = None
+ for function, data in self._idlefuns.items():
+ try:
+ retval = function(self, data, False)
+ if retval is False:
+ del self._idlefuns[function]
+ elif retval is True:
+ nextsleep = 0
+ elif nextsleep is 0:
+ continue
+ elif nextsleep is None:
+ nextsleep = retval
+ elif retval < nextsleep:
+ nextsleep = retval
+ except SystemExit:
+ raise
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+ if nextsleep is None and len(self._idlefuns) > 0:
+ nextsleep = 0
+ self.timeout = nextsleep
+ # Tell idle functions we're exiting
+ for function, data in self._idlefuns.items():
+ try:
+ retval = function(self, data, True)
+ except:
+ pass
+
+ self.server_close()
+ return
+
+class BitbakeServerInfo():
+ def __init__(self, server):
+ self.host = server.host
+ self.port = server.port
+
+class BitBakeServerFork():
+ def __init__(self, serverinfo, command, logfile):
+ daemonize.createDaemon(command, logfile)
+
+class BitBakeServerConnection():
+ def __init__(self, serverinfo):
+ self.connection = xmlrpclib.Server("http://%s:%s" % (serverinfo.host, serverinfo.port), allow_none=True)
+ self.events = uievent.BBUIEventQueue(self.connection)
+
+ def terminate(self):
+ # Don't wait for server indefinitely
+ import socket
+ socket.setdefaulttimeout(2)
+ try:
+ self.events.system_quit()
+ except:
+ pass
+ try:
+ self.connection.terminateServer()
+ except:
+ pass
+
diff --git a/bitbake/lib/bb/shell.py b/bitbake/lib/bb/shell.py
index b1ad78306d..66e51719a4 100644
--- a/bitbake/lib/bb/shell.py
+++ b/bitbake/lib/bb/shell.py
@@ -151,9 +151,6 @@ class BitBakeShellCommands:
if len( names ) == 0: names = [ globexpr ]
print "SHELL: Building %s" % ' '.join( names )
- oldcmd = cooker.configuration.cmd
- cooker.configuration.cmd = cmd
-
td = taskdata.TaskData(cooker.configuration.abort)
localdata = data.createCopy(cooker.configuration.data)
data.update_data(localdata)
@@ -168,7 +165,7 @@ class BitBakeShellCommands:
if len(providers) == 0:
raise Providers.NoProvider
- tasks.append([name, "do_%s" % cooker.configuration.cmd])
+ tasks.append([name, "do_%s" % cmd])
td.add_unresolved(localdata, cooker.status)
@@ -189,7 +186,6 @@ class BitBakeShellCommands:
print "ERROR: Couldn't build '%s'" % names
last_exception = e
- cooker.configuration.cmd = oldcmd
build.usage = "<providee>"
@@ -208,6 +204,11 @@ class BitBakeShellCommands:
self.build( params, "configure" )
configure.usage = "<providee>"
+ def install( self, params ):
+ """Execute 'install' on a providee"""
+ self.build( params, "install" )
+ install.usage = "<providee>"
+
def edit( self, params ):
"""Call $EDITOR on a providee"""
name = params[0]
@@ -240,18 +241,14 @@ class BitBakeShellCommands:
bf = completeFilePath( name )
print "SHELL: Calling '%s' on '%s'" % ( cmd, bf )
- oldcmd = cooker.configuration.cmd
- cooker.configuration.cmd = cmd
-
try:
- cooker.buildFile(bf)
+ cooker.buildFile(bf, cmd)
except parse.ParseError:
print "ERROR: Unable to open or parse '%s'" % bf
except build.EventException, e:
print "ERROR: Couldn't build '%s'" % name
last_exception = e
- cooker.configuration.cmd = oldcmd
fileBuild.usage = "<bbfile>"
def fileClean( self, params ):
@@ -493,7 +490,7 @@ SRC_URI = ""
interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
def showdata( self, params ):
- """Show the parsed metadata for a given providee"""
+ """Execute 'showdata' on a providee"""
cooker.showEnvironment(None, params)
showdata.usage = "<providee>"
diff --git a/bitbake/lib/bb/taskdata.py b/bitbake/lib/bb/taskdata.py
index 976e0ca1f9..4a88e75f6d 100644
--- a/bitbake/lib/bb/taskdata.py
+++ b/bitbake/lib/bb/taskdata.py
@@ -23,8 +23,20 @@ Task data collection and handling
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from bb import data, event, mkdirhier, utils
-import bb, os
+import bb
+
+def re_match_strings(target, strings):
+ """
+ Whether or not the string 'target' matches
+ any one string of the strings which can be regular expression string
+ """
+ import re
+
+ for name in strings:
+ if (name==target or
+ re.search(name,target)!=None):
+ return True
+ return False
class TaskData:
"""
@@ -264,7 +276,7 @@ class TaskData:
"""
unresolved = []
for target in self.build_names_index:
- if target in dataCache.ignored_dependencies:
+ if re_match_strings(target, dataCache.ignored_dependencies):
continue
if self.build_names_index.index(target) in self.failed_deps:
continue
@@ -279,7 +291,7 @@ class TaskData:
"""
unresolved = []
for target in self.run_names_index:
- if target in dataCache.ignored_dependencies:
+ if re_match_strings(target, dataCache.ignored_dependencies):
continue
if self.run_names_index.index(target) in self.failed_rdeps:
continue
@@ -359,7 +371,7 @@ class TaskData:
added internally during dependency resolution
"""
- if item in dataCache.ignored_dependencies:
+ if re_match_strings(item, dataCache.ignored_dependencies):
return
if not item in dataCache.providers:
@@ -367,7 +379,7 @@ class TaskData:
bb.msg.note(2, bb.msg.domain.Provider, "Nothing PROVIDES '%s' (but '%s' DEPENDS on or otherwise requires it)" % (item, self.get_dependees_str(item)))
else:
bb.msg.note(2, bb.msg.domain.Provider, "Nothing PROVIDES '%s'" % (item))
- bb.event.fire(bb.event.NoProvider(item, cfgData))
+ bb.event.fire(bb.event.NoProvider(item), cfgData)
raise bb.providers.NoProvider(item)
if self.have_build_target(item):
@@ -380,7 +392,7 @@ class TaskData:
if not eligible:
bb.msg.note(2, bb.msg.domain.Provider, "No buildable provider PROVIDES '%s' but '%s' DEPENDS on or otherwise requires it. Enable debugging and see earlier logs to find unbuildable providers." % (item, self.get_dependees_str(item)))
- bb.event.fire(bb.event.NoProvider(item, cfgData))
+ bb.event.fire(bb.event.NoProvider(item), cfgData)
raise bb.providers.NoProvider(item)
if len(eligible) > 1 and foundUnique == False:
@@ -390,7 +402,7 @@ class TaskData:
providers_list.append(dataCache.pkg_fn[fn])
bb.msg.note(1, bb.msg.domain.Provider, "multiple providers are available for %s (%s);" % (item, ", ".join(providers_list)))
bb.msg.note(1, bb.msg.domain.Provider, "consider defining PREFERRED_PROVIDER_%s" % item)
- bb.event.fire(bb.event.MultipleProviders(item, providers_list, cfgData))
+ bb.event.fire(bb.event.MultipleProviders(item, providers_list), cfgData)
self.consider_msgs_cache.append(item)
for fn in eligible:
@@ -410,7 +422,7 @@ class TaskData:
(takes item names from RDEPENDS/PACKAGES namespace)
"""
- if item in dataCache.ignored_dependencies:
+ if re_match_strings(item, dataCache.ignored_dependencies):
return
if self.have_runtime_target(item):
@@ -420,7 +432,7 @@ class TaskData:
if not all_p:
bb.msg.error(bb.msg.domain.Provider, "'%s' RDEPENDS/RRECOMMENDS or otherwise requires the runtime entity '%s' but it wasn't found in any PACKAGE or RPROVIDES variables" % (self.get_rdependees_str(item), item))
- bb.event.fire(bb.event.NoProvider(item, cfgData, runtime=True))
+ bb.event.fire(bb.event.NoProvider(item, runtime=True), cfgData)
raise bb.providers.NoRProvider(item)
eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache)
@@ -428,7 +440,7 @@ class TaskData:
if not eligible:
bb.msg.error(bb.msg.domain.Provider, "'%s' RDEPENDS/RRECOMMENDS or otherwise requires the runtime entity '%s' but it wasn't found in any PACKAGE or RPROVIDES variables of any buildable targets.\nEnable debugging and see earlier logs to find unbuildable targets." % (self.get_rdependees_str(item), item))
- bb.event.fire(bb.event.NoProvider(item, cfgData, runtime=True))
+ bb.event.fire(bb.event.NoProvider(item, runtime=True), cfgData)
raise bb.providers.NoRProvider(item)
if len(eligible) > 1 and numberPreferred == 0:
@@ -438,7 +450,7 @@ class TaskData:
providers_list.append(dataCache.pkg_fn[fn])
bb.msg.note(2, bb.msg.domain.Provider, "multiple providers are available for runtime %s (%s);" % (item, ", ".join(providers_list)))
bb.msg.note(2, bb.msg.domain.Provider, "consider defining a PREFERRED_PROVIDER entry to match runtime %s" % item)
- bb.event.fire(bb.event.MultipleProviders(item,providers_list, cfgData, runtime=True))
+ bb.event.fire(bb.event.MultipleProviders(item,providers_list, runtime=True), cfgData)
self.consider_msgs_cache.append(item)
if numberPreferred > 1:
@@ -448,7 +460,7 @@ class TaskData:
providers_list.append(dataCache.pkg_fn[fn])
bb.msg.note(2, bb.msg.domain.Provider, "multiple providers are available for runtime %s (top %s entries preferred) (%s);" % (item, numberPreferred, ", ".join(providers_list)))
bb.msg.note(2, bb.msg.domain.Provider, "consider defining only one PREFERRED_PROVIDER entry to match runtime %s" % item)
- bb.event.fire(bb.event.MultipleProviders(item,providers_list, cfgData, runtime=True))
+ bb.event.fire(bb.event.MultipleProviders(item,providers_list, runtime=True), cfgData)
self.consider_msgs_cache.append(item)
# run through the list until we find one that we can build
diff --git a/bitbake/lib/bb/ui/__init__.py b/bitbake/lib/bb/ui/__init__.py
new file mode 100644
index 0000000000..c6a377a8e6
--- /dev/null
+++ b/bitbake/lib/bb/ui/__init__.py
@@ -0,0 +1,18 @@
+#
+# BitBake UI Implementation
+#
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
diff --git a/bitbake/lib/bb/ui/crumbs/__init__.py b/bitbake/lib/bb/ui/crumbs/__init__.py
new file mode 100644
index 0000000000..c6a377a8e6
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/__init__.py
@@ -0,0 +1,18 @@
+#
+# BitBake UI Implementation
+#
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
diff --git a/bitbake/lib/bb/ui/crumbs/buildmanager.py b/bitbake/lib/bb/ui/crumbs/buildmanager.py
new file mode 100644
index 0000000000..f89e8eefd4
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/buildmanager.py
@@ -0,0 +1,457 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import threading
+import os
+import datetime
+import time
+
+class BuildConfiguration:
+ """ Represents a potential *or* historic *or* concrete build. It
+ encompasses all the things that we need to tell bitbake to do to make it
+ build what we want it to build.
+
+ It also stored the metadata URL and the set of possible machines (and the
+ distros / images / uris for these. Apart from the metdata URL these are
+ not serialised to file (since they may be transient). In some ways this
+ functionality might be shifted to the loader class."""
+
+ def __init__ (self):
+ self.metadata_url = None
+
+ # Tuple of (distros, image, urls)
+ self.machine_options = {}
+
+ self.machine = None
+ self.distro = None
+ self.image = None
+ self.urls = []
+ self.extra_urls = []
+ self.extra_pkgs = []
+
+ def get_machines_model (self):
+ model = gtk.ListStore (gobject.TYPE_STRING)
+ for machine in self.machine_options.keys():
+ model.append ([machine])
+
+ return model
+
+ def get_distro_and_images_models (self, machine):
+ distro_model = gtk.ListStore (gobject.TYPE_STRING)
+
+ for distro in self.machine_options[machine][0]:
+ distro_model.append ([distro])
+
+ image_model = gtk.ListStore (gobject.TYPE_STRING)
+
+ for image in self.machine_options[machine][1]:
+ image_model.append ([image])
+
+ return (distro_model, image_model)
+
+ def get_repos (self):
+ self.urls = self.machine_options[self.machine][2]
+ return self.urls
+
+ # It might be a lot lot better if we stored these in like, bitbake conf
+ # file format.
+ @staticmethod
+ def load_from_file (filename):
+ f = open (filename, "r")
+
+ conf = BuildConfiguration()
+ for line in f.readlines():
+ data = line.split (";")[1]
+ if (line.startswith ("metadata-url;")):
+ conf.metadata_url = data.strip()
+ continue
+ if (line.startswith ("url;")):
+ conf.urls += [data.strip()]
+ continue
+ if (line.startswith ("extra-url;")):
+ conf.extra_urls += [data.strip()]
+ continue
+ if (line.startswith ("machine;")):
+ conf.machine = data.strip()
+ continue
+ if (line.startswith ("distribution;")):
+ conf.distro = data.strip()
+ continue
+ if (line.startswith ("image;")):
+ conf.image = data.strip()
+ continue
+
+ f.close ()
+ return conf
+
+ # Serialise to a file. This is part of the build process and we use this
+ # to be able to repeat a given build (using the same set of parameters)
+ # but also so that we can include the details of the image / machine /
+ # distro in the build manager tree view.
+ def write_to_file (self, filename):
+ f = open (filename, "w")
+
+ lines = []
+
+ if (self.metadata_url):
+ lines += ["metadata-url;%s\n" % (self.metadata_url)]
+
+ for url in self.urls:
+ lines += ["url;%s\n" % (url)]
+
+ for url in self.extra_urls:
+ lines += ["extra-url;%s\n" % (url)]
+
+ if (self.machine):
+ lines += ["machine;%s\n" % (self.machine)]
+
+ if (self.distro):
+ lines += ["distribution;%s\n" % (self.distro)]
+
+ if (self.image):
+ lines += ["image;%s\n" % (self.image)]
+
+ f.writelines (lines)
+ f.close ()
+
+class BuildResult(gobject.GObject):
+ """ Represents an historic build. Perhaps not successful. But it includes
+ things such as the files that are in the directory (the output from the
+ build) as well as a deserialised BuildConfiguration file that is stored in
+ ".conf" in the directory for the build.
+
+ This is GObject so that it can be included in the TreeStore."""
+
+ (STATE_COMPLETE, STATE_FAILED, STATE_ONGOING) = \
+ (0, 1, 2)
+
+ def __init__ (self, parent, identifier):
+ gobject.GObject.__init__ (self)
+ self.date = None
+
+ self.files = []
+ self.status = None
+ self.identifier = identifier
+ self.path = os.path.join (parent, identifier)
+
+ # Extract the date, since the directory name is of the
+ # format build-<year><month><day>-<ordinal> we can easily
+ # pull it out.
+ # TODO: Better to stat a file?
+ (_ , date, revision) = identifier.split ("-")
+ print date
+
+ year = int (date[0:4])
+ month = int (date[4:6])
+ day = int (date[6:8])
+
+ self.date = datetime.date (year, month, day)
+
+ self.conf = None
+
+ # By default builds are STATE_FAILED unless we find a "complete" file
+ # in which case they are STATE_COMPLETE
+ self.state = BuildResult.STATE_FAILED
+ for file in os.listdir (self.path):
+ if (file.startswith (".conf")):
+ conffile = os.path.join (self.path, file)
+ self.conf = BuildConfiguration.load_from_file (conffile)
+ elif (file.startswith ("complete")):
+ self.state = BuildResult.STATE_COMPLETE
+ else:
+ self.add_file (file)
+
+ def add_file (self, file):
+ # Just add the file for now. Don't care about the type.
+ self.files += [(file, None)]
+
+class BuildManagerModel (gtk.TreeStore):
+ """ Model for the BuildManagerTreeView. This derives from gtk.TreeStore
+ but it abstracts nicely what the columns mean and the setup of the columns
+ in the model. """
+
+ (COL_IDENT, COL_DESC, COL_MACHINE, COL_DISTRO, COL_BUILD_RESULT, COL_DATE, COL_STATE) = \
+ (0, 1, 2, 3, 4, 5, 6)
+
+ def __init__ (self):
+ gtk.TreeStore.__init__ (self,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_OBJECT,
+ gobject.TYPE_INT64,
+ gobject.TYPE_INT)
+
+class BuildManager (gobject.GObject):
+ """ This class manages the historic builds that have been found in the
+ "results" directory but is also used for starting a new build."""
+
+ __gsignals__ = {
+ 'population-finished' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'populate-error' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ())
+ }
+
+ def update_build_result (self, result, iter):
+ # Convert the date into something we can sort by.
+ date = long (time.mktime (result.date.timetuple()))
+
+ # Add a top level entry for the build
+
+ self.model.set (iter,
+ BuildManagerModel.COL_IDENT, result.identifier,
+ BuildManagerModel.COL_DESC, result.conf.image,
+ BuildManagerModel.COL_MACHINE, result.conf.machine,
+ BuildManagerModel.COL_DISTRO, result.conf.distro,
+ BuildManagerModel.COL_BUILD_RESULT, result,
+ BuildManagerModel.COL_DATE, date,
+ BuildManagerModel.COL_STATE, result.state)
+
+ # And then we use the files in the directory as the children for the
+ # top level iter.
+ for file in result.files:
+ self.model.append (iter, (None, file[0], None, None, None, date, -1))
+
+ # This function is called as an idle by the BuildManagerPopulaterThread
+ def add_build_result (self, result):
+ gtk.gdk.threads_enter()
+ self.known_builds += [result]
+
+ self.update_build_result (result, self.model.append (None))
+
+ gtk.gdk.threads_leave()
+
+ def notify_build_finished (self):
+ # This is a bit of a hack. If we have a running build running then we
+ # will have a row in the model in STATE_ONGOING. Find it and make it
+ # as if it was a proper historic build (well, it is completed now....)
+
+ # We need to use the iters here rather than the Python iterator
+ # interface to the model since we need to pass it into
+ # update_build_result
+
+ iter = self.model.get_iter_first()
+
+ while (iter):
+ (ident, state) = self.model.get(iter,
+ BuildManagerModel.COL_IDENT,
+ BuildManagerModel.COL_STATE)
+
+ if state == BuildResult.STATE_ONGOING:
+ result = BuildResult (self.results_directory, ident)
+ self.update_build_result (result, iter)
+ iter = self.model.iter_next(iter)
+
+ def notify_build_succeeded (self):
+ # Write the "complete" file so that when we create the BuildResult
+ # object we put into the model
+
+ complete_file_path = os.path.join (self.cur_build_directory, "complete")
+ f = file (complete_file_path, "w")
+ f.close()
+ self.notify_build_finished()
+
+ def notify_build_failed (self):
+ # Without a "complete" file then this will mark the build as failed:
+ self.notify_build_finished()
+
+ # This function is called as an idle
+ def emit_population_finished_signal (self):
+ gtk.gdk.threads_enter()
+ self.emit ("population-finished")
+ gtk.gdk.threads_leave()
+
+ class BuildManagerPopulaterThread (threading.Thread):
+ def __init__ (self, manager, directory):
+ threading.Thread.__init__ (self)
+ self.manager = manager
+ self.directory = directory
+
+ def run (self):
+ # For each of the "build-<...>" directories ..
+
+ if os.path.exists (self.directory):
+ for directory in os.listdir (self.directory):
+
+ if not directory.startswith ("build-"):
+ continue
+
+ build_result = BuildResult (self.directory, directory)
+ self.manager.add_build_result (build_result)
+
+ gobject.idle_add (BuildManager.emit_population_finished_signal,
+ self.manager)
+
+ def __init__ (self, server, results_directory):
+ gobject.GObject.__init__ (self)
+
+ # The builds that we've found from walking the result directory
+ self.known_builds = []
+
+ # Save out the bitbake server, we need this for issuing commands to
+ # the cooker:
+ self.server = server
+
+ # The TreeStore that we use
+ self.model = BuildManagerModel ()
+
+ # The results directory is where we create (and look for) the
+ # build-<xyz>-<n> directories. We need to populate ourselves from
+ # directory
+ self.results_directory = results_directory
+ self.populate_from_directory (self.results_directory)
+
+ def populate_from_directory (self, directory):
+ thread = BuildManager.BuildManagerPopulaterThread (self, directory)
+ thread.start()
+
+ # Come up with the name for the next build ident by combining "build-"
+ # with the date formatted as yyyymmdd and then an ordinal. We do this by
+ # an optimistic algorithm incrementing the ordinal if we find that it
+ # already exists.
+ def get_next_build_ident (self):
+ today = datetime.date.today ()
+ datestr = str (today.year) + str (today.month) + str (today.day)
+
+ revision = 0
+ test_name = "build-%s-%d" % (datestr, revision)
+ test_path = os.path.join (self.results_directory, test_name)
+
+ while (os.path.exists (test_path)):
+ revision += 1
+ test_name = "build-%s-%d" % (datestr, revision)
+ test_path = os.path.join (self.results_directory, test_name)
+
+ return test_name
+
+ # Take a BuildConfiguration and then try and build it based on the
+ # parameters of that configuration. S
+ def do_build (self, conf):
+ server = self.server
+
+ # Work out the build directory. Note we actually create the
+ # directories here since we need to write the ".conf" file. Otherwise
+ # we could have relied on bitbake's builder thread to actually make
+ # the directories as it proceeds with the build.
+ ident = self.get_next_build_ident ()
+ build_directory = os.path.join (self.results_directory,
+ ident)
+ self.cur_build_directory = build_directory
+ os.makedirs (build_directory)
+
+ conffile = os.path.join (build_directory, ".conf")
+ conf.write_to_file (conffile)
+
+ # Add a row to the model representing this ongoing build. It's kinda a
+ # fake entry. If this build completes or fails then this gets updated
+ # with the real stuff like the historic builds
+ date = long (time.time())
+ self.model.append (None, (ident, conf.image, conf.machine, conf.distro,
+ None, date, BuildResult.STATE_ONGOING))
+ try:
+ server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1])
+ server.runCommand(["setVariable", "MACHINE", conf.machine])
+ server.runCommand(["setVariable", "DISTRO", conf.distro])
+ server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"])
+ server.runCommand(["setVariable", "BBFILES", \
+ """${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""])
+ server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"])
+ server.runCommand(["setVariable", "IPK_FEED_URIS", \
+ " ".join(conf.get_repos())])
+ server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE",
+ build_directory])
+ server.runCommand(["buildTargets", [conf.image], "rootfs"])
+
+ except Exception, e:
+ print e
+
+class BuildManagerTreeView (gtk.TreeView):
+ """ The tree view for the build manager. This shows the historic builds
+ and so forth. """
+
+ # We use this function to control what goes in the cell since we store
+ # the date in the model as seconds since the epoch (for sorting) and so we
+ # need to make it human readable.
+ def date_format_custom_cell_data_func (self, col, cell, model, iter):
+ date = model.get (iter, BuildManagerModel.COL_DATE)[0]
+ datestr = time.strftime("%A %d %B %Y", time.localtime(date))
+ cell.set_property ("text", datestr)
+
+ # This format function controls what goes in the cell. We use this to map
+ # the integer state to a string and also to colourise the text
+ def state_format_custom_cell_data_fun (self, col, cell, model, iter):
+ state = model.get (iter, BuildManagerModel.COL_STATE)[0]
+
+ if (state == BuildResult.STATE_ONGOING):
+ cell.set_property ("text", "Active")
+ cell.set_property ("foreground", "#000000")
+ elif (state == BuildResult.STATE_FAILED):
+ cell.set_property ("text", "Failed")
+ cell.set_property ("foreground", "#ff0000")
+ elif (state == BuildResult.STATE_COMPLETE):
+ cell.set_property ("text", "Complete")
+ cell.set_property ("foreground", "#00ff00")
+ else:
+ cell.set_property ("text", "")
+
+ def __init__ (self):
+ gtk.TreeView.__init__(self)
+
+ # Misc descriptiony thing
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn (None, renderer,
+ text=BuildManagerModel.COL_DESC)
+ self.append_column (col)
+
+ # Machine
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Machine", renderer,
+ text=BuildManagerModel.COL_MACHINE)
+ self.append_column (col)
+
+ # distro
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Distribution", renderer,
+ text=BuildManagerModel.COL_DISTRO)
+ self.append_column (col)
+
+ # date (using a custom function for formatting the cell contents it
+ # takes epoch -> human readable string)
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Date", renderer,
+ text=BuildManagerModel.COL_DATE)
+ self.append_column (col)
+ col.set_cell_data_func (renderer,
+ self.date_format_custom_cell_data_func)
+
+ # For status.
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Status", renderer,
+ text = BuildManagerModel.COL_STATE)
+ self.append_column (col)
+ col.set_cell_data_func (renderer,
+ self.state_format_custom_cell_data_fun)
+
diff --git a/bitbake/lib/bb/ui/crumbs/puccho.glade b/bitbake/lib/bb/ui/crumbs/puccho.glade
new file mode 100644
index 0000000000..d7553a6e14
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/puccho.glade
@@ -0,0 +1,606 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.5 on Mon Nov 10 12:24:12 2008 -->
+<glade-interface>
+ <widget class="GtkDialog" id="build_dialog">
+ <property name="title" translatable="yes">Start a build</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTable" id="build_table">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">7</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">5</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkAlignment" id="status_alignment">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkHBox" id="status_hbox">
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkImage" id="status_image">
+ <property name="visible">True</property>
+ <property name="no_show_all">True</property>
+ <property name="xalign">0</property>
+ <property name="stock">gtk-dialog-error</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="status_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">If you see this text something is wrong...</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Build configuration&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="image_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="image_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Image:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="distribution_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="distribution_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Distribution:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="machine_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="machine_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Machine:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="refresh_button">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-refresh</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="location_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">32</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Location:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Repository&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="dialog2">
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">7</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Repositories&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTreeView" id="treeview1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Additional packages&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xscale">0</property>
+ <child>
+ <widget class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Location: </property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xscale">0</property>
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <widget class="GtkButton" id="button7">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button6">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-edit</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Search:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTreeView" id="treeview2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="button4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="main_window">
+ <child>
+ <widget class="GtkVBox" id="main_window_vbox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToolbar" id="main_toolbar">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToolButton" id="main_toolbutton_build">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Build</property>
+ <property name="stock_id">gtk-execute</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="results_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="progress_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py
new file mode 100644
index 0000000000..401559255b
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -0,0 +1,180 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+
+class RunningBuildModel (gtk.TreeStore):
+ (COL_TYPE, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_ACTIVE) = (0, 1, 2, 3, 4, 5)
+ def __init__ (self):
+ gtk.TreeStore.__init__ (self,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_BOOLEAN)
+
+class RunningBuild (gobject.GObject):
+ __gsignals__ = {
+ 'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'build-failed' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ())
+ }
+ pids_to_task = {}
+ tasks_to_iter = {}
+
+ def __init__ (self):
+ gobject.GObject.__init__ (self)
+ self.model = RunningBuildModel()
+
+ def handle_event (self, event):
+ # Handle an event from the event queue, this may result in updating
+ # the model and thus the UI. Or it may be to tell us that the build
+ # has finished successfully (or not, as the case may be.)
+
+ parent = None
+ pid = 0
+ package = None
+ task = None
+
+ # If we have a pid attached to this message/event try and get the
+ # (package, task) pair for it. If we get that then get the parent iter
+ # for the message.
+ if hassattr(event, 'pid'):
+ pid = event.pid
+ if self.pids_to_task.has_key(pid):
+ (package, task) = self.pids_to_task[pid]
+ parent = self.tasks_to_iter[(package, task)]
+
+ if isinstance(event, bb.msg.Msg):
+ # Set a pretty icon for the message based on it's type.
+ if isinstance(event, bb.msg.MsgWarn):
+ icon = "dialog-warning"
+ elif isinstance(event, bb.msg.MsgErr):
+ icon = "dialog-error"
+ else:
+ icon = None
+
+ # Ignore the "Running task i of n .." messages
+ if (event._message.startswith ("Running task")):
+ return
+
+ # Add the message to the tree either at the top level if parent is
+ # None otherwise as a descendent of a task.
+ self.model.append (parent,
+ (event.__name__.split()[-1], # e.g. MsgWarn, MsgError
+ package,
+ task,
+ event._message,
+ icon,
+ False))
+ elif isinstance(event, bb.build.TaskStarted):
+ (package, task) = (event._package, event._task)
+
+ # Save out this PID.
+ self.pids_to_task[pid] = (package,task)
+
+ # Check if we already have this package in our model. If so then
+ # that can be the parent for the task. Otherwise we create a new
+ # top level for the package.
+ if (self.tasks_to_iter.has_key ((package, None))):
+ parent = self.tasks_to_iter[(package, None)]
+ else:
+ parent = self.model.append (None, (None,
+ package,
+ None,
+ "Package: %s" % (package),
+ None,
+ False))
+ self.tasks_to_iter[(package, None)] = parent
+
+ # Because this parent package now has an active child mark it as
+ # such.
+ self.model.set(parent, self.model.COL_ICON, "gtk-execute")
+
+ # Add an entry in the model for this task
+ i = self.model.append (parent, (None,
+ package,
+ task,
+ "Task: %s" % (task),
+ None,
+ False))
+
+ # Save out the iter so that we can find it when we have a message
+ # that we need to attach to a task.
+ self.tasks_to_iter[(package, task)] = i
+
+ # Mark this task as active.
+ self.model.set(i, self.model.COL_ICON, "gtk-execute")
+
+ elif isinstance(event, bb.build.Task):
+
+ if isinstance(event, bb.build.TaskFailed):
+ # Mark the task as failed
+ i = self.tasks_to_iter[(package, task)]
+ self.model.set(i, self.model.COL_ICON, "dialog-error")
+
+ # Mark the parent package as failed
+ i = self.tasks_to_iter[(package, None)]
+ self.model.set(i, self.model.COL_ICON, "dialog-error")
+ else:
+ # Mark the task as inactive
+ i = self.tasks_to_iter[(package, task)]
+ self.model.set(i, self.model.COL_ICON, None)
+
+ # Mark the parent package as inactive
+ i = self.tasks_to_iter[(package, None)]
+ self.model.set(i, self.model.COL_ICON, None)
+
+
+ # Clear the iters and the pids since when the task goes away the
+ # pid will no longer be used for messages
+ del self.tasks_to_iter[(package, task)]
+ del self.pids_to_task[pid]
+
+ elif isinstance(event, bb.event.BuildCompleted):
+ failures = int (event._failures)
+
+ # Emit the appropriate signal depending on the number of failures
+ if (failures > 1):
+ self.emit ("build-failed")
+ else:
+ self.emit ("build-succeeded")
+
+class RunningBuildTreeView (gtk.TreeView):
+ def __init__ (self):
+ gtk.TreeView.__init__ (self)
+
+ # The icon that indicates whether we're building or failed.
+ renderer = gtk.CellRendererPixbuf ()
+ col = gtk.TreeViewColumn ("Status", renderer)
+ col.add_attribute (renderer, "icon-name", 4)
+ self.append_column (col)
+
+ # The message of the build.
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Message", renderer, text=3)
+ self.append_column (col)
+
+
diff --git a/bitbake/lib/bb/ui/depexp.py b/bitbake/lib/bb/ui/depexp.py
new file mode 100644
index 0000000000..cfa5b6564e
--- /dev/null
+++ b/bitbake/lib/bb/ui/depexp.py
@@ -0,0 +1,272 @@
+#
+# BitBake Graphical GTK based Dependency Explorer
+#
+# Copyright (C) 2007 Ross Burton
+# Copyright (C) 2007 - 2008 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+import threading
+import xmlrpclib
+
+# Package Model
+(COL_PKG_NAME) = (0)
+
+# Dependency Model
+(TYPE_DEP, TYPE_RDEP) = (0, 1)
+(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
+
+class PackageDepView(gtk.TreeView):
+ def __init__(self, model, dep_type, label):
+ gtk.TreeView.__init__(self)
+ self.current = None
+ self.dep_type = dep_type
+ self.filter_model = model.filter_new()
+ self.filter_model.set_visible_func(self._filter)
+ self.set_model(self.filter_model)
+ #self.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PACKAGE))
+
+ def _filter(self, model, iter):
+ (this_type, package) = model.get(iter, COL_DEP_TYPE, COL_DEP_PARENT)
+ if this_type != self.dep_type: return False
+ return package == self.current
+
+ def set_current_package(self, package):
+ self.current = package
+ self.filter_model.refilter()
+
+class PackageReverseDepView(gtk.TreeView):
+ def __init__(self, model, label):
+ gtk.TreeView.__init__(self)
+ self.current = None
+ self.filter_model = model.filter_new()
+ self.filter_model.set_visible_func(self._filter)
+ self.set_model(self.filter_model)
+ self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PARENT))
+
+ def _filter(self, model, iter):
+ package = model.get_value(iter, COL_DEP_PACKAGE)
+ return package == self.current
+
+ def set_current_package(self, package):
+ self.current = package
+ self.filter_model.refilter()
+
+class DepExplorer(gtk.Window):
+ def __init__(self):
+ gtk.Window.__init__(self)
+ self.set_title("Dependency Explorer")
+ self.set_default_size(500, 500)
+ self.connect("delete-event", gtk.main_quit)
+
+ # Create the data models
+ self.pkg_model = gtk.ListStore(gobject.TYPE_STRING)
+ self.depends_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)
+
+ pane = gtk.HPaned()
+ pane.set_position(250)
+ self.add(pane)
+
+ # The master list of packages
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.pkg_treeview = gtk.TreeView(self.pkg_model)
+ self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
+ self.pkg_treeview.append_column(gtk.TreeViewColumn("Package", gtk.CellRendererText(), text=COL_PKG_NAME))
+ pane.add1(scrolled)
+ scrolled.add(self.pkg_treeview)
+
+ box = gtk.VBox(homogeneous=True, spacing=4)
+
+ # Runtime Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.rdep_treeview = PackageDepView(self.depends_model, TYPE_RDEP, "Runtime Depends")
+ self.rdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ scrolled.add(self.rdep_treeview)
+ box.add(scrolled)
+
+ # Build Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Build Depends")
+ self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ scrolled.add(self.dep_treeview)
+ box.add(scrolled)
+ pane.add2(box)
+
+ # Reverse Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.revdep_treeview = PackageReverseDepView(self.depends_model, "Reverse Depends")
+ self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
+ scrolled.add(self.revdep_treeview)
+ box.add(scrolled)
+ pane.add2(box)
+
+ self.show_all()
+
+ def on_package_activated(self, treeview, path, column, data_col):
+ model = treeview.get_model()
+ package = model.get_value(model.get_iter(path), data_col)
+
+ pkg_path = []
+ def finder(model, path, iter, needle):
+ package = model.get_value(iter, COL_PKG_NAME)
+ if package == needle:
+ pkg_path.append(path)
+ return True
+ else:
+ return False
+ self.pkg_model.foreach(finder, package)
+ if pkg_path:
+ self.pkg_treeview.get_selection().select_path(pkg_path[0])
+ self.pkg_treeview.scroll_to_cell(pkg_path[0])
+
+ def on_cursor_changed(self, selection):
+ (model, it) = selection.get_selected()
+ if iter is None:
+ current_package = None
+ else:
+ current_package = model.get_value(it, COL_PKG_NAME)
+ self.rdep_treeview.set_current_package(current_package)
+ self.dep_treeview.set_current_package(current_package)
+ self.revdep_treeview.set_current_package(current_package)
+
+
+def parse(depgraph, pkg_model, depends_model):
+
+ for package in depgraph["pn"]:
+ pkg_model.set(pkg_model.append(), COL_PKG_NAME, package)
+
+ for package in depgraph["depends"]:
+ for depend in depgraph["depends"][package]:
+ depends_model.set (depends_model.append(),
+ COL_DEP_TYPE, TYPE_DEP,
+ COL_DEP_PARENT, package,
+ COL_DEP_PACKAGE, depend)
+
+ for package in depgraph["rdepends-pn"]:
+ for rdepend in depgraph["rdepends-pn"][package]:
+ depends_model.set (depends_model.append(),
+ COL_DEP_TYPE, TYPE_RDEP,
+ COL_DEP_PARENT, package,
+ COL_DEP_PACKAGE, rdepend)
+
+class ProgressBar(gtk.Window):
+ def __init__(self):
+
+ gtk.Window.__init__(self)
+ self.set_title("Parsing .bb files, please wait...")
+ self.set_default_size(500, 0)
+ self.connect("delete-event", gtk.main_quit)
+
+ self.progress = gtk.ProgressBar()
+ self.add(self.progress)
+ self.show_all()
+
+class gtkthread(threading.Thread):
+ quit = threading.Event()
+ def __init__(self, shutdown):
+ threading.Thread.__init__(self)
+ self.setDaemon(True)
+ self.shutdown = shutdown
+
+ def run(self):
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+ gtk.main()
+ gtkthread.quit.set()
+
+def init(server, eventHandler):
+
+ try:
+ cmdline = server.runCommand(["getCmdLineAction"])
+ if not cmdline or cmdline[0] != "generateDotGraph":
+ print "This UI is only compatible with the -g option"
+ return
+ ret = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
+ if ret != True:
+ print "Couldn't run command! %s" % ret
+ return
+ except xmlrpclib.Fault, x:
+ print "XMLRPC Fault getting commandline:\n %s" % x
+ return
+
+ shutdown = 0
+
+ gtkgui = gtkthread(shutdown)
+ gtkgui.start()
+
+ gtk.gdk.threads_enter()
+ pbar = ProgressBar()
+ dep = DepExplorer()
+ gtk.gdk.threads_leave()
+
+ while True:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if gtkthread.quit.isSet():
+ break
+
+ if event is None:
+ continue
+ if isinstance(event, bb.event.ParseProgress):
+ x = event.sofar
+ y = event.total
+ if x == y:
+ print("\nParsing finished. %d cached, %d parsed, %d skipped, %d masked, %d errors."
+ % ( event.cached, event.parsed, event.skipped, event.masked, event.errors))
+ pbar.hide()
+ gtk.gdk.threads_enter()
+ pbar.progress.set_fraction(float(x)/float(y))
+ pbar.progress.set_text("%d/%d (%2d %%)" % (x, y, x*100/y))
+ gtk.gdk.threads_leave()
+ continue
+
+ if isinstance(event, bb.event.DepTreeGenerated):
+ gtk.gdk.threads_enter()
+ parse(event._depgraph, dep.pkg_model, dep.depends_model)
+ gtk.gdk.threads_leave()
+
+ if isinstance(event, bb.command.CookerCommandCompleted):
+ continue
+ if isinstance(event, bb.command.CookerCommandFailed):
+ print "Command execution failed: %s" % event.error
+ break
+ if isinstance(event, bb.cooker.CookerExit):
+ break
+
+ continue
+
+ except KeyboardInterrupt:
+ if shutdown == 2:
+ print "\nThird Keyboard Interrupt, exit.\n"
+ break
+ if shutdown == 1:
+ print "\nSecond Keyboard Interrupt, stopping...\n"
+ server.runCommand(["stateStop"])
+ if shutdown == 0:
+ print "\nKeyboard Interrupt, closing down...\n"
+ server.runCommand(["stateShutdown"])
+ shutdown = shutdown + 1
+ pass
+
diff --git a/bitbake/lib/bb/ui/goggle.py b/bitbake/lib/bb/ui/goggle.py
new file mode 100644
index 0000000000..94995d82db
--- /dev/null
+++ b/bitbake/lib/bb/ui/goggle.py
@@ -0,0 +1,77 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+import xmlrpclib
+from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild
+
+def event_handle_idle_func (eventHandler, build):
+
+ # Consume as many messages as we can in the time available to us
+ event = eventHandler.getEvent()
+ while event:
+ build.handle_event (event)
+ event = eventHandler.getEvent()
+
+ return True
+
+class MainWindow (gtk.Window):
+ def __init__ (self):
+ gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL)
+
+ # Setup tree view and the scrolled window
+ scrolled_window = gtk.ScrolledWindow ()
+ self.add (scrolled_window)
+ self.cur_build_tv = RunningBuildTreeView()
+ scrolled_window.add (self.cur_build_tv)
+
+def init (server, eventHandler):
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+
+ window = MainWindow ()
+ window.show_all ()
+
+ # Create the object for the current build
+ running_build = RunningBuild ()
+ window.cur_build_tv.set_model (running_build.model)
+ try:
+ cmdline = server.runCommand(["getCmdLineAction"])
+ print cmdline
+ if not cmdline:
+ return 1
+ ret = server.runCommand(cmdline)
+ if ret != True:
+ print "Couldn't get default commandline! %s" % ret
+ return 1
+ except xmlrpclib.Fault, x:
+ print "XMLRPC Fault getting commandline:\n %s" % x
+ return 1
+
+ # Use a timeout function for probing the event queue to find out if we
+ # have a message waiting for us.
+ gobject.timeout_add (200,
+ event_handle_idle_func,
+ eventHandler,
+ running_build)
+
+ gtk.main()
+
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
new file mode 100644
index 0000000000..c69fd6ca64
--- /dev/null
+++ b/bitbake/lib/bb/ui/knotty.py
@@ -0,0 +1,162 @@
+#
+# BitBake (No)TTY UI Implementation
+#
+# Handling output to TTYs or files (no TTY)
+#
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+
+import sys
+import itertools
+import xmlrpclib
+
+parsespin = itertools.cycle( r'|/-\\' )
+
+def init(server, eventHandler):
+
+ # Get values of variables which control our output
+ includelogs = server.runCommand(["getVariable", "BBINCLUDELOGS"])
+ loglines = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
+
+ try:
+ cmdline = server.runCommand(["getCmdLineAction"])
+ #print cmdline
+ if not cmdline:
+ return 1
+ ret = server.runCommand(cmdline)
+ if ret != True:
+ print "Couldn't get default commandline! %s" % ret
+ return 1
+ except xmlrpclib.Fault, x:
+ print "XMLRPC Fault getting commandline:\n %s" % x
+ return 1
+
+ shutdown = 0
+ return_value = 0
+ while True:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if event is None:
+ continue
+ #print event
+ if isinstance(event, bb.msg.MsgPlain):
+ print event._message
+ continue
+ if isinstance(event, bb.msg.MsgDebug):
+ print 'DEBUG: ' + event._message
+ continue
+ if isinstance(event, bb.msg.MsgNote):
+ print 'NOTE: ' + event._message
+ continue
+ if isinstance(event, bb.msg.MsgWarn):
+ print 'WARNING: ' + event._message
+ continue
+ if isinstance(event, bb.msg.MsgError):
+ return_value = 1
+ print 'ERROR: ' + event._message
+ continue
+ if isinstance(event, bb.msg.MsgFatal):
+ return_value = 1
+ print 'FATAL: ' + event._message
+ break
+ if isinstance(event, bb.build.TaskFailed):
+ return_value = 1
+ logfile = event.logfile
+ if logfile:
+ print "ERROR: Logfile of failure stored in %s." % logfile
+ if 1 or includelogs:
+ print "Log data follows:"
+ f = open(logfile, "r")
+ lines = []
+ while True:
+ l = f.readline()
+ if l == '':
+ break
+ l = l.rstrip()
+ if loglines:
+ lines.append(' | %s' % l)
+ if len(lines) > int(loglines):
+ lines.pop(0)
+ else:
+ print '| %s' % l
+ f.close()
+ if lines:
+ for line in lines:
+ print line
+ if isinstance(event, bb.build.TaskBase):
+ print "NOTE: %s" % event._message
+ continue
+ if isinstance(event, bb.event.ParseProgress):
+ x = event.sofar
+ y = event.total
+ 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()
+ if x == y:
+ print("\nParsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
+ % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors))
+ continue
+
+ if isinstance(event, bb.command.CookerCommandCompleted):
+ break
+ if isinstance(event, bb.command.CookerCommandSetExitCode):
+ return_value = event.exitcode
+ continue
+ if isinstance(event, bb.command.CookerCommandFailed):
+ return_value = 1
+ print "Command execution failed: %s" % event.error
+ break
+ if isinstance(event, bb.cooker.CookerExit):
+ break
+
+ # ignore
+ if isinstance(event, bb.event.BuildStarted):
+ continue
+ if isinstance(event, bb.event.BuildCompleted):
+ continue
+ if isinstance(event, bb.event.MultipleProviders):
+ continue
+ if isinstance(event, bb.runqueue.runQueueEvent):
+ continue
+ if isinstance(event, bb.event.StampUpdate):
+ continue
+ if isinstance(event, bb.event.ConfigParsed):
+ continue
+ if isinstance(event, bb.event.RecipeParsed):
+ continue
+ print "Unknown Event: %s" % event
+
+ except KeyboardInterrupt:
+ if shutdown == 2:
+ print "\nThird Keyboard Interrupt, exit.\n"
+ break
+ if shutdown == 1:
+ print "\nSecond Keyboard Interrupt, stopping...\n"
+ server.runCommand(["stateStop"])
+ if shutdown == 0:
+ print "\nKeyboard Interrupt, closing down...\n"
+ server.runCommand(["stateShutdown"])
+ shutdown = shutdown + 1
+ pass
+ return return_value
diff --git a/bitbake/lib/bb/ui/ncurses.py b/bitbake/lib/bb/ui/ncurses.py
new file mode 100644
index 0000000000..14310dc124
--- /dev/null
+++ b/bitbake/lib/bb/ui/ncurses.py
@@ -0,0 +1,335 @@
+#
+# BitBake Curses UI Implementation
+#
+# Implements an ncurses frontend for the BitBake utility.
+#
+# Copyright (C) 2006 Michael 'Mickey' Lauer
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+ We have the following windows:
+
+ 1.) Main Window: Shows what we are ultimately building and how far we are. Includes status bar
+ 2.) Thread Activity Window: Shows one status line for every concurrent bitbake thread.
+ 3.) Command Line Window: Contains an interactive command line where you can interact w/ Bitbake.
+
+ Basic window layout is like that:
+
+ |---------------------------------------------------------|
+ | <Main Window> | <Thread Activity Window> |
+ | | 0: foo do_compile complete|
+ | Building Gtk+-2.6.10 | 1: bar do_patch complete |
+ | Status: 60% | ... |
+ | | ... |
+ | | ... |
+ |---------------------------------------------------------|
+ |<Command Line Window> |
+ |>>> which virtual/kernel |
+ |openzaurus-kernel |
+ |>>> _ |
+ |---------------------------------------------------------|
+
+"""
+
+import os, sys, curses, itertools, time
+import bb
+import xmlrpclib
+from bb import ui
+from bb.ui import uihelper
+
+parsespin = itertools.cycle( r'|/-\\' )
+
+X = 0
+Y = 1
+WIDTH = 2
+HEIGHT = 3
+
+MAXSTATUSLENGTH = 32
+
+class NCursesUI:
+ """
+ NCurses UI Class
+ """
+ class Window:
+ """Base Window Class"""
+ def __init__( self, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
+ self.win = curses.newwin( height, width, y, x )
+ self.dimensions = ( x, y, width, height )
+ """
+ if curses.has_colors():
+ color = 1
+ curses.init_pair( color, fg, bg )
+ self.win.bkgdset( ord(' '), curses.color_pair(color) )
+ else:
+ self.win.bkgdset( ord(' '), curses.A_BOLD )
+ """
+ self.erase()
+ self.setScrolling()
+ self.win.noutrefresh()
+
+ def erase( self ):
+ self.win.erase()
+
+ def setScrolling( self, b = True ):
+ self.win.scrollok( b )
+ self.win.idlok( b )
+
+ def setBoxed( self ):
+ self.boxed = True
+ self.win.box()
+ self.win.noutrefresh()
+
+ def setText( self, x, y, text, *args ):
+ self.win.addstr( y, x, text, *args )
+ self.win.noutrefresh()
+
+ def appendText( self, text, *args ):
+ self.win.addstr( text, *args )
+ self.win.noutrefresh()
+
+ def drawHline( self, y ):
+ self.win.hline( y, 0, curses.ACS_HLINE, self.dimensions[WIDTH] )
+ self.win.noutrefresh()
+
+ class DecoratedWindow( Window ):
+ """Base class for windows with a box and a title bar"""
+ def __init__( self, title, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
+ NCursesUI.Window.__init__( self, x+1, y+3, width-2, height-4, fg, bg )
+ self.decoration = NCursesUI.Window( x, y, width, height, fg, bg )
+ self.decoration.setBoxed()
+ self.decoration.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
+ self.setTitle( title )
+
+ def setTitle( self, title ):
+ self.decoration.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+
+ #-------------------------------------------------------------------------#
+# class TitleWindow( Window ):
+ #-------------------------------------------------------------------------#
+# """Title Window"""
+# def __init__( self, x, y, width, height ):
+# NCursesUI.Window.__init__( self, x, y, width, height )
+# version = bb.__version__
+# title = "BitBake %s" % version
+# credit = "(C) 2003-2007 Team BitBake"
+# #self.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
+# self.win.border()
+# self.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+# self.setText( 1, 2, credit.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+
+ #-------------------------------------------------------------------------#
+ class ThreadActivityWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Thread Activity Window"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.DecoratedWindow.__init__( self, "Thread Activity", x, y, width, height )
+
+ def setStatus( self, thread, text ):
+ line = "%02d: %s" % ( thread, text )
+ width = self.dimensions[WIDTH]
+ if ( len(line) > width ):
+ line = line[:width-3] + "..."
+ else:
+ line = line.ljust( width )
+ self.setText( 0, thread, line )
+
+ #-------------------------------------------------------------------------#
+ class MainWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Main Window"""
+ def __init__( self, x, y, width, height ):
+ self.StatusPosition = width - MAXSTATUSLENGTH
+ NCursesUI.DecoratedWindow.__init__( self, None, x, y, width, height )
+ curses.nl()
+
+ def setTitle( self, title ):
+ title = "BitBake %s" % bb.__version__
+ self.decoration.setText( 2, 1, title, curses.A_BOLD )
+ self.decoration.setText( self.StatusPosition - 8, 1, "Status:", curses.A_BOLD )
+
+ def setStatus(self, status):
+ while len(status) < MAXSTATUSLENGTH:
+ status = status + " "
+ self.decoration.setText( self.StatusPosition, 1, status, curses.A_BOLD )
+
+
+ #-------------------------------------------------------------------------#
+ class ShellOutputWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Interactive Command Line Output"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.DecoratedWindow.__init__( self, "Command Line Window", x, y, width, height )
+
+ #-------------------------------------------------------------------------#
+ class ShellInputWindow( Window ):
+ #-------------------------------------------------------------------------#
+ """Interactive Command Line Input"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.Window.__init__( self, x, y, width, height )
+
+# put that to the top again from curses.textpad import Textbox
+# self.textbox = Textbox( self.win )
+# t = threading.Thread()
+# t.run = self.textbox.edit
+# t.start()
+
+ #-------------------------------------------------------------------------#
+ def main(self, stdscr, server, eventHandler):
+ #-------------------------------------------------------------------------#
+ height, width = stdscr.getmaxyx()
+
+ # for now split it like that:
+ # MAIN_y + THREAD_y = 2/3 screen at the top
+ # MAIN_x = 2/3 left, THREAD_y = 1/3 right
+ # CLI_y = 1/3 of screen at the bottom
+ # CLI_x = full
+
+ main_left = 0
+ main_top = 0
+ main_height = ( height / 3 * 2 )
+ main_width = ( width / 3 ) * 2
+ clo_left = main_left
+ clo_top = main_top + main_height
+ clo_height = height - main_height - main_top - 1
+ clo_width = width
+ cli_left = main_left
+ cli_top = clo_top + clo_height
+ cli_height = 1
+ cli_width = width
+ thread_left = main_left + main_width
+ thread_top = main_top
+ thread_height = main_height
+ thread_width = width - main_width
+
+ #tw = self.TitleWindow( 0, 0, width, main_top )
+ mw = self.MainWindow( main_left, main_top, main_width, main_height )
+ taw = self.ThreadActivityWindow( thread_left, thread_top, thread_width, thread_height )
+ clo = self.ShellOutputWindow( clo_left, clo_top, clo_width, clo_height )
+ cli = self.ShellInputWindow( cli_left, cli_top, cli_width, cli_height )
+ cli.setText( 0, 0, "BB>" )
+
+ mw.setStatus("Idle")
+
+ helper = uihelper.BBUIHelper()
+ shutdown = 0
+
+ try:
+ cmdline = server.runCommand(["getCmdLineAction"])
+ if not cmdline:
+ return
+ ret = server.runCommand(cmdline)
+ if ret != True:
+ print "Couldn't get default commandlind! %s" % ret
+ return
+ except xmlrpclib.Fault, x:
+ print "XMLRPC Fault getting commandline:\n %s" % x
+ return
+
+ exitflag = False
+ while not exitflag:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if not event:
+ continue
+ helper.eventHandler(event)
+ #mw.appendText("%s\n" % event[0])
+ if isinstance(event, bb.build.Task):
+ mw.appendText("NOTE: %s\n" % event._message)
+ if isinstance(event, bb.msg.MsgDebug):
+ mw.appendText('DEBUG: ' + event._message + '\n')
+ if isinstance(event, bb.msg.MsgNote):
+ mw.appendText('NOTE: ' + event._message + '\n')
+ if isinstance(event, bb.msg.MsgWarn):
+ mw.appendText('WARNING: ' + event._message + '\n')
+ if isinstance(event, bb.msg.MsgError):
+ mw.appendText('ERROR: ' + event._message + '\n')
+ if isinstance(event, bb.msg.MsgFatal):
+ mw.appendText('FATAL: ' + event._message + '\n')
+ if isinstance(event, bb.event.ParseProgress):
+ x = event.sofar
+ y = event.total
+ if x == y:
+ mw.setStatus("Idle")
+ mw.appendText("Parsing finished. %d cached, %d parsed, %d skipped, %d masked."
+ % ( event.cached, event.parsed, event.skipped, event.masked ))
+ else:
+ mw.setStatus("Parsing: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
+# if isinstance(event, bb.build.TaskFailed):
+# if event.logfile:
+# if data.getVar("BBINCLUDELOGS", d):
+# bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
+# number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
+# if number_of_lines:
+# os.system('tail -n%s %s' % (number_of_lines, logfile))
+# else:
+# f = open(logfile, "r")
+# while True:
+# l = f.readline()
+# if l == '':
+# break
+# l = l.rstrip()
+# print '| %s' % l
+# f.close()
+# else:
+# bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
+
+ if isinstance(event, bb.command.CookerCommandCompleted):
+ exitflag = True
+ if isinstance(event, bb.command.CookerCommandFailed):
+ mw.appendText("Command execution failed: %s" % event.error)
+ time.sleep(2)
+ exitflag = True
+ if isinstance(event, bb.cooker.CookerExit):
+ exitflag = True
+
+ if helper.needUpdate:
+ activetasks, failedtasks = helper.getTasks()
+ taw.erase()
+ taw.setText(0, 0, "")
+ if activetasks:
+ taw.appendText("Active Tasks:\n")
+ for task in activetasks:
+ taw.appendText(task)
+ if failedtasks:
+ taw.appendText("Failed Tasks:\n")
+ for task in failedtasks:
+ taw.appendText(task)
+
+ curses.doupdate()
+ except KeyboardInterrupt:
+ if shutdown == 2:
+ mw.appendText("Third Keyboard Interrupt, exit.\n")
+ exitflag = True
+ if shutdown == 1:
+ mw.appendText("Second Keyboard Interrupt, stopping...\n")
+ server.runCommand(["stateStop"])
+ if shutdown == 0:
+ mw.appendText("Keyboard Interrupt, closing down...\n")
+ server.runCommand(["stateShutdown"])
+ shutdown = shutdown + 1
+ pass
+
+def init(server, eventHandler):
+ if not os.isatty(sys.stdout.fileno()):
+ print "FATAL: Unable to run 'ncurses' UI without a TTY."
+ return
+ ui = NCursesUI()
+ try:
+ curses.wrapper(ui.main, server, eventHandler)
+ except:
+ import traceback
+ traceback.print_exc()
+
diff --git a/bitbake/lib/bb/ui/puccho.py b/bitbake/lib/bb/ui/puccho.py
new file mode 100644
index 0000000000..713aa1f4a6
--- /dev/null
+++ b/bitbake/lib/bb/ui/puccho.py
@@ -0,0 +1,425 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import gtk.glade
+import threading
+import urllib2
+import os
+
+from bb.ui.crumbs.buildmanager import BuildManager, BuildConfiguration
+from bb.ui.crumbs.buildmanager import BuildManagerTreeView
+
+from bb.ui.crumbs.runningbuild import RunningBuild, RunningBuildTreeView
+
+# The metadata loader is used by the BuildSetupDialog to download the
+# available options to populate the dialog
+class MetaDataLoader(gobject.GObject):
+ """ This class provides the mechanism for loading the metadata (the
+ fetching and parsing) from a given URL. The metadata encompasses details
+ on what machines are available. The distribution and images available for
+ the machine and the the uris to use for building the given machine."""
+ __gsignals__ = {
+ 'success' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'error' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_STRING,))
+ }
+
+ # We use these little helper functions to ensure that we take the gdk lock
+ # when emitting the signal. These functions are called as idles (so that
+ # they happen in the gtk / main thread's main loop.
+ def emit_error_signal (self, remark):
+ gtk.gdk.threads_enter()
+ self.emit ("error", remark)
+ gtk.gdk.threads_leave()
+
+ def emit_success_signal (self):
+ gtk.gdk.threads_enter()
+ self.emit ("success")
+ gtk.gdk.threads_leave()
+
+ def __init__ (self):
+ gobject.GObject.__init__ (self)
+
+ class LoaderThread(threading.Thread):
+ """ This class provides an asynchronous loader for the metadata (by
+ using threads and signals). This is useful since the metadata may be
+ at a remote URL."""
+ class LoaderImportException (Exception):
+ pass
+
+ def __init__(self, loader, url):
+ threading.Thread.__init__ (self)
+ self.url = url
+ self.loader = loader
+
+ def run (self):
+ result = {}
+ try:
+ f = urllib2.urlopen (self.url)
+
+ # Parse the metadata format. The format is....
+ # <machine>;<default distro>|<distro>...;<default image>|<image>...;<type##url>|...
+ for line in f.readlines():
+ components = line.split(";")
+ if (len (components) < 4):
+ raise MetaDataLoader.LoaderThread.LoaderImportException
+ machine = components[0]
+ distros = components[1].split("|")
+ images = components[2].split("|")
+ urls = components[3].split("|")
+
+ result[machine] = (distros, images, urls)
+
+ # Create an object representing this *potential*
+ # configuration. It can become concrete if the machine, distro
+ # and image are all chosen in the UI
+ configuration = BuildConfiguration()
+ configuration.metadata_url = self.url
+ configuration.machine_options = result
+ self.loader.configuration = configuration
+
+ # Emit that we've actually got a configuration
+ gobject.idle_add (MetaDataLoader.emit_success_signal,
+ self.loader)
+
+ except MetaDataLoader.LoaderThread.LoaderImportException, e:
+ gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
+ "Repository metadata corrupt")
+ except Exception, e:
+ gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
+ "Unable to download repository metadata")
+ print e
+
+ def try_fetch_from_url (self, url):
+ # Try and download the metadata. Firing a signal if successful
+ thread = MetaDataLoader.LoaderThread(self, url)
+ thread.start()
+
+class BuildSetupDialog (gtk.Dialog):
+ RESPONSE_BUILD = 1
+
+ # A little helper method that just sets the states on the widgets based on
+ # whether we've got good metadata or not.
+ def set_configurable (self, configurable):
+ if (self.configurable == configurable):
+ return
+
+ self.configurable = configurable
+ for widget in self.conf_widgets:
+ widget.set_sensitive (configurable)
+
+ if not configurable:
+ self.machine_combo.set_active (-1)
+ self.distribution_combo.set_active (-1)
+ self.image_combo.set_active (-1)
+
+ # GTK widget callbacks
+ def refresh_button_clicked (self, button):
+ # Refresh button clicked.
+
+ url = self.location_entry.get_chars (0, -1)
+ self.loader.try_fetch_from_url(url)
+
+ def repository_entry_editable_changed (self, entry):
+ if (len (entry.get_chars (0, -1)) > 0):
+ self.refresh_button.set_sensitive (True)
+ else:
+ self.refresh_button.set_sensitive (False)
+ self.clear_status_message()
+
+ # If we were previously configurable we are no longer since the
+ # location entry has been changed
+ self.set_configurable (False)
+
+ def machine_combo_changed (self, combobox):
+ active_iter = combobox.get_active_iter()
+
+ if not active_iter:
+ return
+
+ model = combobox.get_model()
+
+ if model:
+ chosen_machine = model.get (active_iter, 0)[0]
+
+ (distros_model, images_model) = \
+ self.loader.configuration.get_distro_and_images_models (chosen_machine)
+
+ self.distribution_combo.set_model (distros_model)
+ self.image_combo.set_model (images_model)
+
+ # Callbacks from the loader
+ def loader_success_cb (self, loader):
+ self.status_image.set_from_icon_name ("info",
+ gtk.ICON_SIZE_BUTTON)
+ self.status_image.show()
+ self.status_label.set_label ("Repository metadata successfully downloaded")
+
+ # Set the models on the combo boxes based on the models generated from
+ # the configuration that the loader has created
+
+ # We just need to set the machine here, that then determines the
+ # distro and image options. Cunning huh? :-)
+
+ self.configuration = self.loader.configuration
+ model = self.configuration.get_machines_model ()
+ self.machine_combo.set_model (model)
+
+ self.set_configurable (True)
+
+ def loader_error_cb (self, loader, message):
+ self.status_image.set_from_icon_name ("error",
+ gtk.ICON_SIZE_BUTTON)
+ self.status_image.show()
+ self.status_label.set_text ("Error downloading repository metadata")
+ for widget in self.conf_widgets:
+ widget.set_sensitive (False)
+
+ def clear_status_message (self):
+ self.status_image.hide()
+ self.status_label.set_label (
+ """<i>Enter the repository location and press _Refresh</i>""")
+
+ def __init__ (self):
+ gtk.Dialog.__init__ (self)
+
+ # Cancel
+ self.add_button (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+
+ # Build
+ button = gtk.Button ("_Build", None, True)
+ image = gtk.Image ()
+ image.set_from_stock (gtk.STOCK_EXECUTE,gtk.ICON_SIZE_BUTTON)
+ button.set_image (image)
+ self.add_action_widget (button, BuildSetupDialog.RESPONSE_BUILD)
+ button.show_all ()
+
+ # Pull in *just* the table from the Glade XML data.
+ gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
+ root = "build_table")
+ table = gxml.get_widget ("build_table")
+ self.vbox.pack_start (table, True, False, 0)
+
+ # Grab all the widgets that we need to turn on/off when we refresh...
+ self.conf_widgets = []
+ self.conf_widgets += [gxml.get_widget ("machine_label")]
+ self.conf_widgets += [gxml.get_widget ("distribution_label")]
+ self.conf_widgets += [gxml.get_widget ("image_label")]
+ self.conf_widgets += [gxml.get_widget ("machine_combo")]
+ self.conf_widgets += [gxml.get_widget ("distribution_combo")]
+ self.conf_widgets += [gxml.get_widget ("image_combo")]
+
+ # Grab the status widgets
+ self.status_image = gxml.get_widget ("status_image")
+ self.status_label = gxml.get_widget ("status_label")
+
+ # Grab the refresh button and connect to the clicked signal
+ self.refresh_button = gxml.get_widget ("refresh_button")
+ self.refresh_button.connect ("clicked", self.refresh_button_clicked)
+
+ # Grab the location entry and connect to editable::changed
+ self.location_entry = gxml.get_widget ("location_entry")
+ self.location_entry.connect ("changed",
+ self.repository_entry_editable_changed)
+
+ # Grab the machine combo and hook onto the changed signal. This then
+ # allows us to populate the distro and image combos
+ self.machine_combo = gxml.get_widget ("machine_combo")
+ self.machine_combo.connect ("changed", self.machine_combo_changed)
+
+ # Setup the combo
+ cell = gtk.CellRendererText()
+ self.machine_combo.pack_start(cell, True)
+ self.machine_combo.add_attribute(cell, 'text', 0)
+
+ # Grab the distro and image combos. We need these to populate with
+ # models once the machine is chosen
+ self.distribution_combo = gxml.get_widget ("distribution_combo")
+ cell = gtk.CellRendererText()
+ self.distribution_combo.pack_start(cell, True)
+ self.distribution_combo.add_attribute(cell, 'text', 0)
+
+ self.image_combo = gxml.get_widget ("image_combo")
+ cell = gtk.CellRendererText()
+ self.image_combo.pack_start(cell, True)
+ self.image_combo.add_attribute(cell, 'text', 0)
+
+ # Put the default descriptive text in the status box
+ self.clear_status_message()
+
+ # Mark as non-configurable, this is just greys out the widgets the
+ # user can't yet use
+ self.configurable = False
+ self.set_configurable(False)
+
+ # Show the table
+ table.show_all ()
+
+ # The loader and some signals connected to it to update the status
+ # area
+ self.loader = MetaDataLoader()
+ self.loader.connect ("success", self.loader_success_cb)
+ self.loader.connect ("error", self.loader_error_cb)
+
+ def update_configuration (self):
+ """ A poorly named function but it updates the internal configuration
+ from the widgets. This can make that configuration concrete and can
+ thus be used for building """
+ # Extract the chosen machine from the combo
+ model = self.machine_combo.get_model()
+ active_iter = self.machine_combo.get_active_iter()
+ if (active_iter):
+ self.configuration.machine = model.get(active_iter, 0)[0]
+
+ # Extract the chosen distro from the combo
+ model = self.distribution_combo.get_model()
+ active_iter = self.distribution_combo.get_active_iter()
+ if (active_iter):
+ self.configuration.distro = model.get(active_iter, 0)[0]
+
+ # Extract the chosen image from the combo
+ model = self.image_combo.get_model()
+ active_iter = self.image_combo.get_active_iter()
+ if (active_iter):
+ self.configuration.image = model.get(active_iter, 0)[0]
+
+# This function operates to pull events out from the event queue and then push
+# them into the RunningBuild (which then drives the RunningBuild which then
+# pushes through and updates the progress tree view.)
+#
+# TODO: Should be a method on the RunningBuild class
+def event_handle_timeout (eventHandler, build):
+ # Consume as many messages as we can ...
+ event = eventHandler.getEvent()
+ while event:
+ build.handle_event (event)
+ event = eventHandler.getEvent()
+ return True
+
+class MainWindow (gtk.Window):
+
+ # Callback that gets fired when the user hits a button in the
+ # BuildSetupDialog.
+ def build_dialog_box_response_cb (self, dialog, response_id):
+ conf = None
+ if (response_id == BuildSetupDialog.RESPONSE_BUILD):
+ dialog.update_configuration()
+ print dialog.configuration.machine, dialog.configuration.distro, \
+ dialog.configuration.image
+ conf = dialog.configuration
+
+ dialog.destroy()
+
+ if conf:
+ self.manager.do_build (conf)
+
+ def build_button_clicked_cb (self, button):
+ dialog = BuildSetupDialog ()
+
+ # For some unknown reason Dialog.run causes nice little deadlocks ... :-(
+ dialog.connect ("response", self.build_dialog_box_response_cb)
+ dialog.show()
+
+ def __init__ (self):
+ gtk.Window.__init__ (self)
+
+ # Pull in *just* the main vbox from the Glade XML data and then pack
+ # that inside the window
+ gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
+ root = "main_window_vbox")
+ vbox = gxml.get_widget ("main_window_vbox")
+ self.add (vbox)
+
+ # Create the tree views for the build manager view and the progress view
+ self.build_manager_view = BuildManagerTreeView()
+ self.running_build_view = RunningBuildTreeView()
+
+ # Grab the scrolled windows that we put the tree views into
+ self.results_scrolledwindow = gxml.get_widget ("results_scrolledwindow")
+ self.progress_scrolledwindow = gxml.get_widget ("progress_scrolledwindow")
+
+ # Put the tree views inside ...
+ self.results_scrolledwindow.add (self.build_manager_view)
+ self.progress_scrolledwindow.add (self.running_build_view)
+
+ # Hook up the build button...
+ self.build_button = gxml.get_widget ("main_toolbutton_build")
+ self.build_button.connect ("clicked", self.build_button_clicked_cb)
+
+# I'm not very happy about the current ownership of the RunningBuild. I have
+# my suspicions that this object should be held by the BuildManager since we
+# care about the signals in the manager
+
+def running_build_succeeded_cb (running_build, manager):
+ # Notify the manager that a build has succeeded. This is necessary as part
+ # of the 'hack' that we use for making the row in the model / view
+ # representing the ongoing build change into a row representing the
+ # completed build. Since we know only one build can be running a time then
+ # we can handle this.
+
+ # FIXME: Refactor all this so that the RunningBuild is owned by the
+ # BuildManager. It can then hook onto the signals directly and drive
+ # interesting things it cares about.
+ manager.notify_build_succeeded ()
+ print "build succeeded"
+
+def running_build_failed_cb (running_build, manager):
+ # As above
+ print "build failed"
+ manager.notify_build_failed ()
+
+def init (server, eventHandler):
+ # Initialise threading...
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+
+ main_window = MainWindow ()
+ main_window.show_all ()
+
+ # Set up the build manager stuff in general
+ builds_dir = os.path.join (os.getcwd(), "results")
+ manager = BuildManager (server, builds_dir)
+ main_window.build_manager_view.set_model (manager.model)
+
+ # Do the running build setup
+ running_build = RunningBuild ()
+ main_window.running_build_view.set_model (running_build.model)
+ running_build.connect ("build-succeeded", running_build_succeeded_cb,
+ manager)
+ running_build.connect ("build-failed", running_build_failed_cb, manager)
+
+ # We need to save the manager into the MainWindow so that the toolbar
+ # button can use it.
+ # FIXME: Refactor ?
+ main_window.manager = manager
+
+ # Use a timeout function for probing the event queue to find out if we
+ # have a message waiting for us.
+ gobject.timeout_add (200,
+ event_handle_timeout,
+ eventHandler,
+ running_build)
+
+ gtk.main()
diff --git a/bitbake/lib/bb/ui/uievent.py b/bitbake/lib/bb/ui/uievent.py
new file mode 100644
index 0000000000..36302f4da7
--- /dev/null
+++ b/bitbake/lib/bb/ui/uievent.py
@@ -0,0 +1,125 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+"""
+Use this class to fork off a thread to recieve event callbacks from the bitbake
+server and queue them for the UI to process. This process must be used to avoid
+client/server deadlocks.
+"""
+
+import socket, threading, pickle
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+
+class BBUIEventQueue:
+ def __init__(self, BBServer):
+
+ self.eventQueue = []
+ self.eventQueueLock = threading.Lock()
+ self.eventQueueNotify = threading.Event()
+
+ self.BBServer = BBServer
+
+ self.t = threading.Thread()
+ self.t.setDaemon(True)
+ self.t.run = self.startCallbackHandler
+ self.t.start()
+
+ def getEvent(self):
+
+ self.eventQueueLock.acquire()
+
+ if len(self.eventQueue) == 0:
+ self.eventQueueLock.release()
+ return None
+
+ item = self.eventQueue.pop(0)
+
+ if len(self.eventQueue) == 0:
+ self.eventQueueNotify.clear()
+
+ self.eventQueueLock.release()
+ return item
+
+ def waitEvent(self, delay):
+ self.eventQueueNotify.wait(delay)
+ return self.getEvent()
+
+ def queue_event(self, event):
+ self.eventQueueLock.acquire()
+ self.eventQueue.append(pickle.loads(event))
+ self.eventQueueNotify.set()
+ self.eventQueueLock.release()
+
+ def startCallbackHandler(self):
+
+ server = UIXMLRPCServer()
+ self.host, self.port = server.socket.getsockname()
+
+ server.register_function( self.system_quit, "event.quit" )
+ server.register_function( self.queue_event, "event.send" )
+ server.socket.settimeout(1)
+
+ self.EventHandle = self.BBServer.registerEventHandler(self.host, self.port)
+
+ self.server = server
+ while not server.quit:
+ server.handle_request()
+ server.server_close()
+
+ def system_quit( self ):
+ """
+ Shut down the callback thread
+ """
+ try:
+ self.BBServer.unregisterEventHandler(self.EventHandle)
+ except:
+ pass
+ self.server.quit = True
+
+class UIXMLRPCServer (SimpleXMLRPCServer):
+
+ def __init__( self, interface = ("localhost", 0) ):
+ self.quit = False
+ SimpleXMLRPCServer.__init__( self,
+ interface,
+ requestHandler=SimpleXMLRPCRequestHandler,
+ logRequests=False, allow_none=True)
+
+ def get_request(self):
+ while not self.quit:
+ try:
+ sock, addr = self.socket.accept()
+ sock.settimeout(1)
+ return (sock, addr)
+ except socket.timeout:
+ pass
+ return (None,None)
+
+ def close_request(self, request):
+ if request is None:
+ return
+ SimpleXMLRPCServer.close_request(self, request)
+
+ def process_request(self, request, client_address):
+ if request is None:
+ return
+ SimpleXMLRPCServer.process_request(self, request, client_address)
+
+
diff --git a/bitbake/lib/bb/ui/uihelper.py b/bitbake/lib/bb/ui/uihelper.py
new file mode 100644
index 0000000000..151ffc5854
--- /dev/null
+++ b/bitbake/lib/bb/ui/uihelper.py
@@ -0,0 +1,49 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# 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
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+class BBUIHelper:
+ def __init__(self):
+ self.needUpdate = False
+ self.running_tasks = {}
+ self.failed_tasks = {}
+
+ def eventHandler(self, event):
+ if isinstance(event, bb.build.TaskStarted):
+ self.running_tasks["%s %s\n" % (event._package, event._task)] = ""
+ self.needUpdate = True
+ if isinstance(event, bb.build.TaskSucceeded):
+ del self.running_tasks["%s %s\n" % (event._package, event._task)]
+ self.needUpdate = True
+ if isinstance(event, bb.build.TaskFailed):
+ del self.running_tasks["%s %s\n" % (event._package, event._task)]
+ self.failed_tasks["%s %s\n" % (event._package, event._task)] = ""
+ self.needUpdate = True
+
+ # Add runqueue event handling
+ #if isinstance(event, bb.runqueue.runQueueTaskCompleted):
+ # a = 1
+ #if isinstance(event, bb.runqueue.runQueueTaskStarted):
+ # a = 1
+ #if isinstance(event, bb.runqueue.runQueueTaskFailed):
+ # a = 1
+ #if isinstance(event, bb.runqueue.runQueueExitWait):
+ # a = 1
+
+ def getTasks(self):
+ return (self.running_tasks, self.failed_tasks)
diff --git a/bitbake/lib/bb/utils.py b/bitbake/lib/bb/utils.py
index 3017ecfa4a..5fc1463e67 100644
--- a/bitbake/lib/bb/utils.py
+++ b/bitbake/lib/bb/utils.py
@@ -21,8 +21,9 @@ BitBake Utility Functions
digits = "0123456789"
ascii_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+separators = ".-"
-import re, fcntl, os
+import re, fcntl, os, types
def explode_version(s):
r = []
@@ -39,12 +40,15 @@ def explode_version(s):
r.append(m.group(1))
s = m.group(2)
continue
+ r.append(s[0])
s = s[1:]
return r
def vercmp_part(a, b):
va = explode_version(a)
vb = explode_version(b)
+ sa = False
+ sb = False
while True:
if va == []:
ca = None
@@ -56,6 +60,16 @@ def vercmp_part(a, b):
cb = vb.pop(0)
if ca == None and cb == None:
return 0
+
+ if type(ca) is types.StringType:
+ sa = ca in separators
+ if type(cb) is types.StringType:
+ sb = cb in separators
+ if sa and not sb:
+ return -1
+ if not sa and sb:
+ return 1
+
if ca > cb:
return 1
if ca < cb:
@@ -151,7 +165,7 @@ def better_compile(text, file, realfile):
# split the text into lines again
body = text.split('\n')
- bb.msg.error(bb.msg.domain.Util, "Error in compiling: ", realfile)
+ bb.msg.error(bb.msg.domain.Util, "Error in compiling python function in: ", realfile)
bb.msg.error(bb.msg.domain.Util, "The lines resulting into this error were:")
bb.msg.error(bb.msg.domain.Util, "\t%d:%s:'%s'" % (e.lineno, e.__class__.__name__, body[e.lineno-1]))
@@ -176,7 +190,7 @@ def better_exec(code, context, text, realfile):
raise
# print the Header of the Error Message
- bb.msg.error(bb.msg.domain.Util, "Error in executing: %s" % realfile)
+ bb.msg.error(bb.msg.domain.Util, "Error in executing python function in: %s" % realfile)
bb.msg.error(bb.msg.domain.Util, "Exception:%s Message:%s" % (t,value) )
# let us find the line number now