summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb
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/bb
parent1bfd6edef9db9c9175058ae801d1b601e4f15263 (diff)
downloadpoky-22c29d8651668195f72e2f6a8e059d625eb511c3.tar.gz
bitbake: Switch to bitbake-dev version (bitbake master upstream)
Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
Diffstat (limited to 'bitbake/lib/bb')
-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 @@
21# with this program; if not, write to the Free Software Foundation, Inc., 21# with this program; if not, write to the Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 23
24__version__ = "1.8.13" 24__version__ = "1.9.0"
25 25
26__all__ = [ 26__all__ = [
27 27
@@ -54,6 +54,7 @@ __all__ = [
54# modules 54# modules
55 "parse", 55 "parse",
56 "data", 56 "data",
57 "command",
57 "event", 58 "event",
58 "build", 59 "build",
59 "fetch", 60 "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 @@
25# 25#
26#Based on functions from the base bb module, Copyright 2003 Holger Schurig 26#Based on functions from the base bb module, Copyright 2003 Holger Schurig
27 27
28from bb import data, fetch, event, mkdirhier, utils 28from bb import data, event, mkdirhier, utils
29import bb, os 29import bb, os, sys
30 30
31# When we execute a python function we'd like certain things 31# When we execute a python function we'd like certain things
32# in all namespaces, hence we add them to __builtins__ 32# in all namespaces, hence we add them to __builtins__
@@ -37,7 +37,11 @@ __builtins__['os'] = os
37 37
38# events 38# events
39class FuncFailed(Exception): 39class FuncFailed(Exception):
40 """Executed function failed""" 40 """
41 Executed function failed
42 First parameter a message
43 Second paramter is a logfile (optional)
44 """
41 45
42class EventException(Exception): 46class EventException(Exception):
43 """Exception which is associated with an Event.""" 47 """Exception which is associated with an Event."""
@@ -50,7 +54,9 @@ class TaskBase(event.Event):
50 54
51 def __init__(self, t, d ): 55 def __init__(self, t, d ):
52 self._task = t 56 self._task = t
53 event.Event.__init__(self, d) 57 self._package = bb.data.getVar("PF", d, 1)
58 event.Event.__init__(self)
59 self._message = "package %s: task %s: %s" % (bb.data.getVar("PF", d, 1), t, bb.event.getName(self)[4:])
54 60
55 def getTask(self): 61 def getTask(self):
56 return self._task 62 return self._task
@@ -68,6 +74,10 @@ class TaskSucceeded(TaskBase):
68 74
69class TaskFailed(TaskBase): 75class TaskFailed(TaskBase):
70 """Task execution failed""" 76 """Task execution failed"""
77 def __init__(self, msg, logfile, t, d ):
78 self.logfile = logfile
79 self.msg = msg
80 TaskBase.__init__(self, t, d)
71 81
72class InvalidTask(TaskBase): 82class InvalidTask(TaskBase):
73 """Invalid Task""" 83 """Invalid Task"""
@@ -104,42 +114,116 @@ def exec_func(func, d, dirs = None):
104 else: 114 else:
105 adir = data.getVar('B', d, 1) 115 adir = data.getVar('B', d, 1)
106 116
117 # Save current directory
107 try: 118 try:
108 prevdir = os.getcwd() 119 prevdir = os.getcwd()
109 except OSError: 120 except OSError:
110 prevdir = data.getVar('TOPDIR', d, True) 121 prevdir = data.getVar('TOPDIR', d, True)
122
123 # Setup logfiles
124 t = data.getVar('T', d, 1)
125 if not t:
126 bb.msg.fatal(bb.msg.domain.Build, "T not set")
127 mkdirhier(t)
128 # Gross hack, FIXME
129 import random
130 logfile = "%s/log.%s.%s.%s" % (t, func, str(os.getpid()),random.random())
131 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
132
133 # Change to correct directory (if specified)
111 if adir and os.access(adir, os.F_OK): 134 if adir and os.access(adir, os.F_OK):
112 os.chdir(adir) 135 os.chdir(adir)
113 136
137 # Handle logfiles
138 si = file('/dev/null', 'r')
139 try:
140 if bb.msg.debug_level['default'] > 0 or ispython:
141 so = os.popen("tee \"%s\"" % logfile, "w")
142 else:
143 so = file(logfile, 'w')
144 except OSError, e:
145 bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
146 pass
147
148 se = so
149
150 # Dup the existing fds so we dont lose them
151 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
152 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
153 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
154
155 # Replace those fds with our own
156 os.dup2(si.fileno(), osi[1])
157 os.dup2(so.fileno(), oso[1])
158 os.dup2(se.fileno(), ose[1])
159
114 locks = [] 160 locks = []
115 lockfiles = (data.expand(flags['lockfiles'], d) or "").split() 161 lockfiles = (data.expand(flags['lockfiles'], d) or "").split()
116 for lock in lockfiles: 162 for lock in lockfiles:
117 locks.append(bb.utils.lockfile(lock)) 163 locks.append(bb.utils.lockfile(lock))
118 164
119 if flags['python']: 165 try:
120 exec_func_python(func, d) 166 # Run the function
121 else: 167 if ispython:
122 exec_func_shell(func, d, flags) 168 exec_func_python(func, d, runfile, logfile)
169 else:
170 exec_func_shell(func, d, runfile, logfile, flags)
171
172 # Restore original directory
173 try:
174 os.chdir(prevdir)
175 except:
176 pass
123 177
124 for lock in locks: 178 finally:
125 bb.utils.unlockfile(lock)
126 179
127 if os.path.exists(prevdir): 180 # Unlock any lockfiles
128 os.chdir(prevdir) 181 for lock in locks:
182 bb.utils.unlockfile(lock)
183
184 # Restore the backup fds
185 os.dup2(osi[0], osi[1])
186 os.dup2(oso[0], oso[1])
187 os.dup2(ose[0], ose[1])
188
189 # Close our logs
190 si.close()
191 so.close()
192 se.close()
129 193
130def exec_func_python(func, d): 194 if os.path.exists(logfile) and os.path.getsize(logfile) == 0:
195 bb.msg.debug(2, bb.msg.domain.Build, "Zero size logfile %s, removing" % logfile)
196 os.remove(logfile)
197
198 # Close the backup fds
199 os.close(osi[0])
200 os.close(oso[0])
201 os.close(ose[0])
202
203def exec_func_python(func, d, runfile, logfile):
131 """Execute a python BB 'function'""" 204 """Execute a python BB 'function'"""
132 import re 205 import re, os
133 206
134 bbfile = bb.data.getVar('FILE', d, 1) 207 bbfile = bb.data.getVar('FILE', d, 1)
135 tmp = "def " + func + "():\n%s" % data.getVar(func, d) 208 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
136 tmp += '\n' + func + '()' 209 tmp += '\n' + func + '()'
210
211 f = open(runfile, "w")
212 f.write(tmp)
137 comp = utils.better_compile(tmp, func, bbfile) 213 comp = utils.better_compile(tmp, func, bbfile)
138 g = {} # globals 214 g = {} # globals
139 g['d'] = d 215 g['d'] = d
140 utils.better_exec(comp, g, tmp, bbfile) 216 try:
217 utils.better_exec(comp, g, tmp, bbfile)
218 except:
219 (t,value,tb) = sys.exc_info()
220
221 if t in [bb.parse.SkipPackage, bb.build.FuncFailed]:
222 raise
223 bb.msg.error(bb.msg.domain.Build, "Function %s failed" % func)
224 raise FuncFailed("function %s failed" % func, logfile)
141 225
142def exec_func_shell(func, d, flags): 226def exec_func_shell(func, d, runfile, logfile, flags):
143 """Execute a shell BB 'function' Returns true if execution was successful. 227 """Execute a shell BB 'function' Returns true if execution was successful.
144 228
145 For this, it creates a bash shell script in the tmp dectory, writes the local 229 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):
149 of the directories you need created prior to execution. The last 233 of the directories you need created prior to execution. The last
150 item in the list is where we will chdir/cd to. 234 item in the list is where we will chdir/cd to.
151 """ 235 """
152 import sys
153 236
154 deps = flags['deps'] 237 deps = flags['deps']
155 check = flags['check'] 238 check = flags['check']
156 interact = flags['interactive']
157 if check in globals(): 239 if check in globals():
158 if globals()[check](func, deps): 240 if globals()[check](func, deps):
159 return 241 return
160 242
161 global logfile
162 t = data.getVar('T', d, 1)
163 if not t:
164 return 0
165 mkdirhier(t)
166 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
167 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
168
169 f = open(runfile, "w") 243 f = open(runfile, "w")
170 f.write("#!/bin/sh -e\n") 244 f.write("#!/bin/sh -e\n")
171 if bb.msg.debug_level['default'] > 0: f.write("set -x\n") 245 if bb.msg.debug_level['default'] > 0: f.write("set -x\n")
@@ -177,91 +251,21 @@ def exec_func_shell(func, d, flags):
177 os.chmod(runfile, 0775) 251 os.chmod(runfile, 0775)
178 if not func: 252 if not func:
179 bb.msg.error(bb.msg.domain.Build, "Function not specified") 253 bb.msg.error(bb.msg.domain.Build, "Function not specified")
180 raise FuncFailed() 254 raise FuncFailed("Function not specified for exec_func_shell")
181
182 # open logs
183 si = file('/dev/null', 'r')
184 try:
185 if bb.msg.debug_level['default'] > 0:
186 so = os.popen("tee \"%s\"" % logfile, "w")
187 else:
188 so = file(logfile, 'w')
189 except OSError, e:
190 bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
191 pass
192
193 se = so
194
195 if not interact:
196 # dup the existing fds so we dont lose them
197 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
198 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
199 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
200
201 # replace those fds with our own
202 os.dup2(si.fileno(), osi[1])
203 os.dup2(so.fileno(), oso[1])
204 os.dup2(se.fileno(), ose[1])
205 255
206 # execute function 256 # execute function
207 prevdir = os.getcwd()
208 if flags['fakeroot']: 257 if flags['fakeroot']:
209 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1) 258 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
210 else: 259 else:
211 maybe_fakeroot = '' 260 maybe_fakeroot = ''
212 lang_environment = "LC_ALL=C " 261 lang_environment = "LC_ALL=C "
213 ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile)) 262 ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile))
214 try:
215 os.chdir(prevdir)
216 except:
217 pass
218
219 if not interact:
220 # restore the backups
221 os.dup2(osi[0], osi[1])
222 os.dup2(oso[0], oso[1])
223 os.dup2(ose[0], ose[1])
224 263
225 # close our logs 264 if ret == 0:
226 si.close()
227 so.close()
228 se.close()
229
230 if os.path.exists(logfile) and os.path.getsize(logfile) == 0:
231 bb.msg.debug(2, bb.msg.domain.Build, "Zero size logfile %s, removing" % logfile)
232 os.remove(logfile)
233
234 # close the backup fds
235 os.close(osi[0])
236 os.close(oso[0])
237 os.close(ose[0])
238
239 if ret==0:
240 if bb.msg.debug_level['default'] > 0:
241 os.remove(runfile)
242# os.remove(logfile)
243 return 265 return
244 else: 266
245 bb.msg.error(bb.msg.domain.Build, "function %s failed" % func) 267 bb.msg.error(bb.msg.domain.Build, "Function %s failed" % func)
246 if data.getVar("BBINCLUDELOGS", d): 268 raise FuncFailed("function %s failed" % func, logfile)
247 bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
248 number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
249 if number_of_lines:
250 os.system('tail -n%s %s' % (number_of_lines, logfile))
251 elif os.path.exists(logfile):
252 f = open(logfile, "r")
253 while True:
254 l = f.readline()
255 if l == '':
256 break
257 l = l.rstrip()
258 print '| %s' % l
259 f.close()
260 else:
261 bb.msg.error(bb.msg.domain.Build, "There was no logfile output")
262 else:
263 bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
264 raise FuncFailed( logfile )
265 269
266 270
267def exec_task(task, d): 271def exec_task(task, d):
@@ -282,14 +286,20 @@ def exec_task(task, d):
282 data.setVar('OVERRIDES', 'task-%s:%s' % (task[3:], old_overrides), localdata) 286 data.setVar('OVERRIDES', 'task-%s:%s' % (task[3:], old_overrides), localdata)
283 data.update_data(localdata) 287 data.update_data(localdata)
284 data.expandKeys(localdata) 288 data.expandKeys(localdata)
285 event.fire(TaskStarted(task, localdata)) 289 event.fire(TaskStarted(task, localdata), localdata)
286 exec_func(task, localdata) 290 exec_func(task, localdata)
287 event.fire(TaskSucceeded(task, localdata)) 291 event.fire(TaskSucceeded(task, localdata), localdata)
288 except FuncFailed, reason: 292 except FuncFailed, message:
289 bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason ) 293 # Try to extract the optional logfile
290 failedevent = TaskFailed(task, d) 294 try:
291 event.fire(failedevent) 295 (msg, logfile) = message
292 raise EventException("Function failed in task: %s" % reason, failedevent) 296 except:
297 logfile = None
298 msg = message
299 bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % message )
300 failedevent = TaskFailed(msg, logfile, task, d)
301 event.fire(failedevent, d)
302 raise EventException("Function failed in task: %s" % message, failedevent)
293 303
294 # make stamp, or cause event and raise exception 304 # make stamp, or cause event and raise exception
295 if not data.getVarFlag(task, 'nostamp', d) and not data.getVarFlag(task, 'selfstamp', d): 305 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:
134 self.data = data 134 self.data = data
135 135
136 # Make sure __depends makes the depends_cache 136 # Make sure __depends makes the depends_cache
137 self.getVar("__depends", virtualfn, True) 137 # If we're a virtual class we need to make sure all our depends are appended
138 # to the depends of fn.
139 depends = self.getVar("__depends", virtualfn, True) or []
140 if "__depends" not in self.depends_cache[fn] or not self.depends_cache[fn]["__depends"]:
141 self.depends_cache[fn]["__depends"] = depends
142 for dep in depends:
143 if dep not in self.depends_cache[fn]["__depends"]:
144 self.depends_cache[fn]["__depends"].append(dep)
145
146 # Make sure BBCLASSEXTEND always makes the cache too
147 self.getVar('BBCLASSEXTEND', virtualfn, True)
148
138 self.depends_cache[virtualfn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn) 149 self.depends_cache[virtualfn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn)
139 150
140 def virtualfn2realfn(self, virtualfn): 151 def virtualfn2realfn(self, virtualfn):
@@ -170,11 +181,8 @@ class Cache:
170 181
171 bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s (full)" % fn) 182 bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s (full)" % fn)
172 183
173 bb_data, skipped = self.load_bbfile(fn, cfgData) 184 bb_data = self.load_bbfile(fn, cfgData)
174 if isinstance(bb_data, dict): 185 return bb_data[cls]
175 return bb_data[cls]
176
177 return bb_data
178 186
179 def loadData(self, fn, cfgData, cacheData): 187 def loadData(self, fn, cfgData, cacheData):
180 """ 188 """
@@ -184,42 +192,39 @@ class Cache:
184 to record the variables accessed. 192 to record the variables accessed.
185 Return the cache status and whether the file was skipped when parsed 193 Return the cache status and whether the file was skipped when parsed
186 """ 194 """
195 skipped = 0
196 virtuals = 0
197
187 if fn not in self.checked: 198 if fn not in self.checked:
188 self.cacheValidUpdate(fn) 199 self.cacheValidUpdate(fn)
200
189 if self.cacheValid(fn): 201 if self.cacheValid(fn):
190 if "SKIPPED" in self.depends_cache[fn]:
191 return True, True
192 self.handle_data(fn, cacheData)
193 multi = self.getVar('BBCLASSEXTEND', fn, True) 202 multi = self.getVar('BBCLASSEXTEND', fn, True)
194 if multi: 203 for cls in (multi or "").split() + [""]:
195 for cls in multi.split(): 204 virtualfn = self.realfn2virtual(fn, cls)
196 virtualfn = self.realfn2virtual(fn, cls) 205 if self.depends_cache[virtualfn]["__SKIPPED"]:
197 # Pretend we're clean so getVar works 206 skipped += 1
198 self.clean[virtualfn] = "" 207 bb.msg.debug(1, bb.msg.domain.Cache, "Skipping %s" % virtualfn)
199 self.handle_data(virtualfn, cacheData) 208 continue
200 return True, False 209 self.handle_data(virtualfn, cacheData)
210 virtuals += 1
211 return True, skipped, virtuals
201 212
202 bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s" % fn) 213 bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s" % fn)
203 214
204 bb_data, skipped = self.load_bbfile(fn, cfgData) 215 bb_data = self.load_bbfile(fn, cfgData)
205
206 if skipped:
207 if isinstance(bb_data, dict):
208 self.setData(fn, fn, bb_data[""])
209 else:
210 self.setData(fn, fn, bb_data)
211 return False, skipped
212 216
213 if isinstance(bb_data, dict): 217 for data in bb_data:
214 for data in bb_data: 218 virtualfn = self.realfn2virtual(fn, data)
215 virtualfn = self.realfn2virtual(fn, data) 219 self.setData(virtualfn, fn, bb_data[data])
216 self.setData(virtualfn, fn, bb_data[data]) 220 if self.getVar("__SKIPPED", virtualfn, True):
221 skipped += 1
222 bb.msg.debug(1, bb.msg.domain.Cache, "Skipping %s" % virtualfn)
223 else:
217 self.handle_data(virtualfn, cacheData) 224 self.handle_data(virtualfn, cacheData)
218 return False, skipped 225 virtuals += 1
226 return False, skipped, virtuals
219 227
220 self.setData(fn, fn, bb_data)
221 self.handle_data(fn, cacheData)
222 return False, skipped
223 228
224 def cacheValid(self, fn): 229 def cacheValid(self, fn):
225 """ 230 """
@@ -286,16 +291,13 @@ class Cache:
286 if not fn in self.clean: 291 if not fn in self.clean:
287 self.clean[fn] = "" 292 self.clean[fn] = ""
288 293
289 return True 294 # Mark extended class data as clean too
295 multi = self.getVar('BBCLASSEXTEND', fn, True)
296 for cls in (multi or "").split():
297 virtualfn = self.realfn2virtual(fn, cls)
298 self.clean[virtualfn] = ""
290 299
291 def skip(self, fn): 300 return True
292 """
293 Mark a fn as skipped
294 Called from the parser
295 """
296 if not fn in self.depends_cache:
297 self.depends_cache[fn] = {}
298 self.depends_cache[fn]["SKIPPED"] = "1"
299 301
300 def remove(self, fn): 302 def remove(self, fn):
301 """ 303 """
@@ -462,10 +464,7 @@ class Cache:
462 try: 464 try:
463 bb_data = parse.handle(bbfile, bb_data) # read .bb data 465 bb_data = parse.handle(bbfile, bb_data) # read .bb data
464 os.chdir(oldpath) 466 os.chdir(oldpath)
465 return bb_data, False 467 return bb_data
466 except bb.parse.SkipPackage:
467 os.chdir(oldpath)
468 return bb_data, True
469 except: 468 except:
470 os.chdir(oldpath) 469 os.chdir(oldpath)
471 raise 470 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 @@
1"""
2BitBake 'Command' module
3
4Provide an interface to interact with the bitbake server through 'commands'
5"""
6
7# Copyright (C) 2006-2007 Richard Purdie
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22"""
23The bitbake server takes 'commands' from its UI/commandline.
24Commands are either synchronous or asynchronous.
25Async commands return data to the client in the form of events.
26Sync commands must only return data through the function return value
27and must not trigger events, directly or indirectly.
28Commands are queued in a CommandQueue
29"""
30
31import bb
32
33async_cmds = {}
34sync_cmds = {}
35
36class Command:
37 """
38 A queue of asynchronous commands for bitbake
39 """
40 def __init__(self, cooker):
41
42 self.cooker = cooker
43 self.cmds_sync = CommandsSync()
44 self.cmds_async = CommandsAsync()
45
46 # FIXME Add lock for this
47 self.currentAsyncCommand = None
48
49 for attr in CommandsSync.__dict__:
50 command = attr[:].lower()
51 method = getattr(CommandsSync, attr)
52 sync_cmds[command] = (method)
53
54 for attr in CommandsAsync.__dict__:
55 command = attr[:].lower()
56 method = getattr(CommandsAsync, attr)
57 async_cmds[command] = (method)
58
59 def runCommand(self, commandline):
60 try:
61 command = commandline.pop(0)
62 if command in CommandsSync.__dict__:
63 # Can run synchronous commands straight away
64 return getattr(CommandsSync, command)(self.cmds_sync, self, commandline)
65 if self.currentAsyncCommand is not None:
66 return "Busy (%s in progress)" % self.currentAsyncCommand[0]
67 if command not in CommandsAsync.__dict__:
68 return "No such command"
69 self.currentAsyncCommand = (command, commandline)
70 self.cooker.server.register_idle_function(self.cooker.runCommands, self.cooker)
71 return True
72 except:
73 import traceback
74 return traceback.format_exc()
75
76 def runAsyncCommand(self):
77 try:
78 if self.currentAsyncCommand is not None:
79 (command, options) = self.currentAsyncCommand
80 commandmethod = getattr(CommandsAsync, command)
81 needcache = getattr( commandmethod, "needcache" )
82 if needcache and self.cooker.cookerState != bb.cooker.cookerParsed:
83 self.cooker.updateCache()
84 return True
85 else:
86 commandmethod(self.cmds_async, self, options)
87 return False
88 else:
89 return False
90 except:
91 import traceback
92 self.finishAsyncCommand(traceback.format_exc())
93 return False
94
95 def finishAsyncCommand(self, error = None):
96 if error:
97 bb.event.fire(bb.command.CookerCommandFailed(error), self.cooker.configuration.event_data)
98 else:
99 bb.event.fire(bb.command.CookerCommandCompleted(), self.cooker.configuration.event_data)
100 self.currentAsyncCommand = None
101
102
103class CommandsSync:
104 """
105 A class of synchronous commands
106 These should run quickly so as not to hurt interactive performance.
107 These must not influence any running synchronous command.
108 """
109
110 def stateShutdown(self, command, params):
111 """
112 Trigger cooker 'shutdown' mode
113 """
114 command.cooker.cookerAction = bb.cooker.cookerShutdown
115
116 def stateStop(self, command, params):
117 """
118 Stop the cooker
119 """
120 command.cooker.cookerAction = bb.cooker.cookerStop
121
122 def getCmdLineAction(self, command, params):
123 """
124 Get any command parsed from the commandline
125 """
126 return command.cooker.commandlineAction
127
128 def getVariable(self, command, params):
129 """
130 Read the value of a variable from configuration.data
131 """
132 varname = params[0]
133 expand = True
134 if len(params) > 1:
135 expand = params[1]
136
137 return bb.data.getVar(varname, command.cooker.configuration.data, expand)
138
139 def setVariable(self, command, params):
140 """
141 Set the value of variable in configuration.data
142 """
143 varname = params[0]
144 value = params[1]
145 bb.data.setVar(varname, value, command.cooker.configuration.data)
146
147
148class CommandsAsync:
149 """
150 A class of asynchronous commands
151 These functions communicate via generated events.
152 Any function that requires metadata parsing should be here.
153 """
154
155 def buildFile(self, command, params):
156 """
157 Build a single specified .bb file
158 """
159 bfile = params[0]
160 task = params[1]
161
162 command.cooker.buildFile(bfile, task)
163 buildFile.needcache = False
164
165 def buildTargets(self, command, params):
166 """
167 Build a set of targets
168 """
169 pkgs_to_build = params[0]
170 task = params[1]
171
172 command.cooker.buildTargets(pkgs_to_build, task)
173 buildTargets.needcache = True
174
175 def generateDepTreeEvent(self, command, params):
176 """
177 Generate an event containing the dependency information
178 """
179 pkgs_to_build = params[0]
180 task = params[1]
181
182 command.cooker.generateDepTreeEvent(pkgs_to_build, task)
183 command.finishAsyncCommand()
184 generateDepTreeEvent.needcache = True
185
186 def generateDotGraph(self, command, params):
187 """
188 Dump dependency information to disk as .dot files
189 """
190 pkgs_to_build = params[0]
191 task = params[1]
192
193 command.cooker.generateDotGraphFiles(pkgs_to_build, task)
194 command.finishAsyncCommand()
195 generateDotGraph.needcache = True
196
197 def showVersions(self, command, params):
198 """
199 Show the currently selected versions
200 """
201 command.cooker.showVersions()
202 command.finishAsyncCommand()
203 showVersions.needcache = True
204
205 def showEnvironmentTarget(self, command, params):
206 """
207 Print the environment of a target recipe
208 (needs the cache to work out which recipe to use)
209 """
210 pkg = params[0]
211
212 command.cooker.showEnvironment(None, pkg)
213 command.finishAsyncCommand()
214 showEnvironmentTarget.needcache = True
215
216 def showEnvironment(self, command, params):
217 """
218 Print the standard environment
219 or if specified the environment for a specified recipe
220 """
221 bfile = params[0]
222
223 command.cooker.showEnvironment(bfile)
224 command.finishAsyncCommand()
225 showEnvironment.needcache = False
226
227 def parseFiles(self, command, params):
228 """
229 Parse the .bb files
230 """
231 command.cooker.updateCache()
232 command.finishAsyncCommand()
233 parseFiles.needcache = True
234
235 def compareRevisions(self, command, params):
236 """
237 Parse the .bb files
238 """
239 command.cooker.compareRevisions()
240 command.finishAsyncCommand()
241 compareRevisions.needcache = True
242
243#
244# Events
245#
246class CookerCommandCompleted(bb.event.Event):
247 """
248 Cooker command completed
249 """
250 def __init__(self):
251 bb.event.Event.__init__(self)
252
253
254class CookerCommandFailed(bb.event.Event):
255 """
256 Cooker command completed
257 """
258 def __init__(self, error):
259 bb.event.Event.__init__(self)
260 self.error = error
261
262class CookerCommandSetExitCode(bb.event.Event):
263 """
264 Set the exit code for a cooker command
265 """
266 def __init__(self, exitcode):
267 bb.event.Event.__init__(self)
268 self.exitcode = int(exitcode)
269
270
271
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 @@
7# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer 7# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
8# Copyright (C) 2005 Holger Hans Peter Freyther 8# Copyright (C) 2005 Holger Hans Peter Freyther
9# Copyright (C) 2005 ROAD GmbH 9# Copyright (C) 2005 ROAD GmbH
10# Copyright (C) 2006 Richard Purdie 10# Copyright (C) 2006 - 2007 Richard Purdie
11# 11#
12# This program is free software; you can redistribute it and/or modify 12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License version 2 as 13# it under the terms of the GNU General Public License version 2 as
@@ -25,9 +25,35 @@
25import sys, os, getopt, glob, copy, os.path, re, time 25import sys, os, getopt, glob, copy, os.path, re, time
26import bb 26import bb
27from bb import utils, data, parse, event, cache, providers, taskdata, runqueue 27from bb import utils, data, parse, event, cache, providers, taskdata, runqueue
28from bb import command
29import bb.server.xmlrpc
28import itertools, sre_constants 30import itertools, sre_constants
29 31
30parsespin = itertools.cycle( r'|/-\\' ) 32class MultipleMatches(Exception):
33 """
34 Exception raised when multiple file matches are found
35 """
36
37class ParsingErrorsFound(Exception):
38 """
39 Exception raised when parsing errors are found
40 """
41
42class NothingToBuild(Exception):
43 """
44 Exception raised when there is nothing to build
45 """
46
47
48# Different states cooker can be in
49cookerClean = 1
50cookerParsing = 2
51cookerParsed = 3
52
53# Different action states the cooker can be in
54cookerRun = 1 # Cooker is running normally
55cookerShutdown = 2 # Active tasks should be brought to a controlled stop
56cookerStop = 3 # Stop, now!
31 57
32#============================================================================# 58#============================================================================#
33# BBCooker 59# BBCooker
@@ -37,12 +63,14 @@ class BBCooker:
37 Manages one bitbake build run 63 Manages one bitbake build run
38 """ 64 """
39 65
40 def __init__(self, configuration): 66 def __init__(self, configuration, server):
41 self.status = None 67 self.status = None
42 68
43 self.cache = None 69 self.cache = None
44 self.bb_cache = None 70 self.bb_cache = None
45 71
72 self.server = server.BitBakeServer(self)
73
46 self.configuration = configuration 74 self.configuration = configuration
47 75
48 if self.configuration.verbose: 76 if self.configuration.verbose:
@@ -58,17 +86,15 @@ class BBCooker:
58 86
59 self.configuration.data = bb.data.init() 87 self.configuration.data = bb.data.init()
60 88
61 def parseConfiguration(self):
62
63 bb.data.inheritFromOS(self.configuration.data) 89 bb.data.inheritFromOS(self.configuration.data)
64 90
65 # Add conf/bitbake.conf to the list of configuration files to read 91 for f in self.configuration.file:
66 self.configuration.file.append( os.path.join( "conf", "bitbake.conf" ) ) 92 self.parseConfigurationFile( f )
67 93
68 self.parseConfigurationFile(self.configuration.file) 94 self.parseConfigurationFile( os.path.join( "conf", "bitbake.conf" ) )
69 95
70 if not self.configuration.cmd: 96 if not self.configuration.cmd:
71 self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data) or "build" 97 self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data, True) or "build"
72 98
73 bbpkgs = bb.data.getVar('BBPKGS', self.configuration.data, True) 99 bbpkgs = bb.data.getVar('BBPKGS', self.configuration.data, True)
74 if bbpkgs and len(self.configuration.pkgs_to_build) == 0: 100 if bbpkgs and len(self.configuration.pkgs_to_build) == 0:
@@ -80,9 +106,7 @@ class BBCooker:
80 self.configuration.event_data = bb.data.createCopy(self.configuration.data) 106 self.configuration.event_data = bb.data.createCopy(self.configuration.data)
81 bb.data.update_data(self.configuration.event_data) 107 bb.data.update_data(self.configuration.event_data)
82 108
83 #
84 # TOSTOP must not be set or our children will hang when they output 109 # TOSTOP must not be set or our children will hang when they output
85 #
86 fd = sys.stdout.fileno() 110 fd = sys.stdout.fileno()
87 if os.isatty(fd): 111 if os.isatty(fd):
88 import termios 112 import termios
@@ -92,40 +116,91 @@ class BBCooker:
92 tcattr[3] = tcattr[3] & ~termios.TOSTOP 116 tcattr[3] = tcattr[3] & ~termios.TOSTOP
93 termios.tcsetattr(fd, termios.TCSANOW, tcattr) 117 termios.tcsetattr(fd, termios.TCSANOW, tcattr)
94 118
119 self.command = bb.command.Command(self)
120 self.cookerState = cookerClean
121 self.cookerAction = cookerRun
122
123 def parseConfiguration(self):
124
125
95 # Change nice level if we're asked to 126 # Change nice level if we're asked to
96 nice = bb.data.getVar("BB_NICE_LEVEL", self.configuration.data, True) 127 nice = bb.data.getVar("BB_NICE_LEVEL", self.configuration.data, True)
97 if nice: 128 if nice:
98 curnice = os.nice(0) 129 curnice = os.nice(0)
99 nice = int(nice) - curnice 130 nice = int(nice) - curnice
100 bb.msg.note(2, bb.msg.domain.Build, "Renice to %s " % os.nice(nice)) 131 bb.msg.note(2, bb.msg.domain.Build, "Renice to %s " % os.nice(nice))
101 132
133 def parseCommandLine(self):
134 # Parse any commandline into actions
135 if self.configuration.show_environment:
136 self.commandlineAction = None
137
138 if 'world' in self.configuration.pkgs_to_build:
139 bb.error("'world' is not a valid target for --environment.")
140 elif len(self.configuration.pkgs_to_build) > 1:
141 bb.error("Only one target can be used with the --environment option.")
142 elif self.configuration.buildfile and len(self.configuration.pkgs_to_build) > 0:
143 bb.error("No target should be used with the --environment and --buildfile options.")
144 elif len(self.configuration.pkgs_to_build) > 0:
145 self.commandlineAction = ["showEnvironmentTarget", self.configuration.pkgs_to_build]
146 else:
147 self.commandlineAction = ["showEnvironment", self.configuration.buildfile]
148 elif self.configuration.buildfile is not None:
149 self.commandlineAction = ["buildFile", self.configuration.buildfile, self.configuration.cmd]
150 elif self.configuration.revisions_changed:
151 self.commandlineAction = ["compareRevisions"]
152 elif self.configuration.show_versions:
153 self.commandlineAction = ["showVersions"]
154 elif self.configuration.parse_only:
155 self.commandlineAction = ["parseFiles"]
156 # FIXME - implement
157 #elif self.configuration.interactive:
158 # self.interactiveMode()
159 elif self.configuration.dot_graph:
160 if self.configuration.pkgs_to_build:
161 self.commandlineAction = ["generateDotGraph", self.configuration.pkgs_to_build, self.configuration.cmd]
162 else:
163 self.commandlineAction = None
164 bb.error("Please specify a package name for dependency graph generation.")
165 else:
166 if self.configuration.pkgs_to_build:
167 self.commandlineAction = ["buildTargets", self.configuration.pkgs_to_build, self.configuration.cmd]
168 else:
169 self.commandlineAction = None
170 bb.error("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
171
172 def runCommands(self, server, data, abort):
173 """
174 Run any queued asynchronous command
175 This is done by the idle handler so it runs in true context rather than
176 tied to any UI.
177 """
178
179 return self.command.runAsyncCommand()
102 180
103 def tryBuildPackage(self, fn, item, task, the_data): 181 def tryBuildPackage(self, fn, item, task, the_data):
104 """ 182 """
105 Build one task of a package, optionally build following task depends 183 Build one task of a package, optionally build following task depends
106 """ 184 """
107 bb.event.fire(bb.event.PkgStarted(item, the_data))
108 try: 185 try:
109 if not self.configuration.dry_run: 186 if not self.configuration.dry_run:
110 bb.build.exec_task('do_%s' % task, the_data) 187 bb.build.exec_task('do_%s' % task, the_data)
111 bb.event.fire(bb.event.PkgSucceeded(item, the_data))
112 return True 188 return True
113 except bb.build.FuncFailed: 189 except bb.build.FuncFailed:
114 bb.msg.error(bb.msg.domain.Build, "task stack execution failed") 190 bb.msg.error(bb.msg.domain.Build, "task stack execution failed")
115 bb.event.fire(bb.event.PkgFailed(item, the_data))
116 raise 191 raise
117 except bb.build.EventException, e: 192 except bb.build.EventException, e:
118 event = e.args[1] 193 event = e.args[1]
119 bb.msg.error(bb.msg.domain.Build, "%s event exception, aborting" % bb.event.getName(event)) 194 bb.msg.error(bb.msg.domain.Build, "%s event exception, aborting" % bb.event.getName(event))
120 bb.event.fire(bb.event.PkgFailed(item, the_data))
121 raise 195 raise
122 196
123 def tryBuild(self, fn): 197 def tryBuild(self, fn, task):
124 """ 198 """
125 Build a provider and its dependencies. 199 Build a provider and its dependencies.
126 build_depends is a list of previous build dependencies (not runtime) 200 build_depends is a list of previous build dependencies (not runtime)
127 If build_depends is empty, we're dealing with a runtime depends 201 If build_depends is empty, we're dealing with a runtime depends
128 """ 202 """
203
129 the_data = self.bb_cache.loadDataFull(fn, self.configuration.data) 204 the_data = self.bb_cache.loadDataFull(fn, self.configuration.data)
130 205
131 item = self.status.pkg_fn[fn] 206 item = self.status.pkg_fn[fn]
@@ -133,9 +208,13 @@ class BBCooker:
133 #if bb.build.stamp_is_current('do_%s' % self.configuration.cmd, the_data): 208 #if bb.build.stamp_is_current('do_%s' % self.configuration.cmd, the_data):
134 # return True 209 # return True
135 210
136 return self.tryBuildPackage(fn, item, self.configuration.cmd, the_data) 211 return self.tryBuildPackage(fn, item, task, the_data)
137 212
138 def showVersions(self): 213 def showVersions(self):
214
215 # Need files parsed
216 self.updateCache()
217
139 pkg_pn = self.status.pkg_pn 218 pkg_pn = self.status.pkg_pn
140 preferred_versions = {} 219 preferred_versions = {}
141 latest_versions = {} 220 latest_versions = {}
@@ -149,43 +228,36 @@ class BBCooker:
149 pkg_list = pkg_pn.keys() 228 pkg_list = pkg_pn.keys()
150 pkg_list.sort() 229 pkg_list.sort()
151 230
231 bb.msg.plain("%-35s %25s %25s" % ("Package Name", "Latest Version", "Preferred Version"))
232 bb.msg.plain("%-35s %25s %25s\n" % ("============", "==============", "================="))
233
152 for p in pkg_list: 234 for p in pkg_list:
153 pref = preferred_versions[p] 235 pref = preferred_versions[p]
154 latest = latest_versions[p] 236 latest = latest_versions[p]
155 237
156 if pref != latest: 238 prefstr = pref[0][0] + ":" + pref[0][1] + '-' + pref[0][2]
157 prefstr = pref[0][0] + ":" + pref[0][1] + '-' + pref[0][2] 239 lateststr = latest[0][0] + ":" + latest[0][1] + "-" + latest[0][2]
158 else: 240
241 if pref == latest:
159 prefstr = "" 242 prefstr = ""
160 243
161 print "%-30s %20s %20s" % (p, latest[0][0] + ":" + latest[0][1] + "-" + latest[0][2], 244 bb.msg.plain("%-35s %25s %25s" % (p, lateststr, prefstr))
162 prefstr)
163 245
246 def compareRevisions(self):
247 ret = bb.fetch.fetcher_compare_revisons(self.configuration.data)
248 bb.event.fire(bb.command.CookerCommandSetExitCode(ret), self.configuration.event_data)
164 249
165 def showEnvironment(self , buildfile = None, pkgs_to_build = []): 250 def showEnvironment(self, buildfile = None, pkgs_to_build = []):
166 """ 251 """
167 Show the outer or per-package environment 252 Show the outer or per-package environment
168 """ 253 """
169 fn = None 254 fn = None
170 envdata = None 255 envdata = None
171 256
172 if 'world' in pkgs_to_build:
173 print "'world' is not a valid target for --environment."
174 sys.exit(1)
175
176 if len(pkgs_to_build) > 1:
177 print "Only one target can be used with the --environment option."
178 sys.exit(1)
179
180 if buildfile: 257 if buildfile:
181 if len(pkgs_to_build) > 0:
182 print "No target should be used with the --environment and --buildfile options."
183 sys.exit(1)
184 self.cb = None 258 self.cb = None
185 self.bb_cache = bb.cache.init(self) 259 self.bb_cache = bb.cache.init(self)
186 fn = self.matchFile(buildfile) 260 fn = self.matchFile(buildfile)
187 if not fn:
188 sys.exit(1)
189 elif len(pkgs_to_build) == 1: 261 elif len(pkgs_to_build) == 1:
190 self.updateCache() 262 self.updateCache()
191 263
@@ -193,13 +265,9 @@ class BBCooker:
193 bb.data.update_data(localdata) 265 bb.data.update_data(localdata)
194 bb.data.expandKeys(localdata) 266 bb.data.expandKeys(localdata)
195 267
196 taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs) 268 taskdata = bb.taskdata.TaskData(self.configuration.abort)
197 269 taskdata.add_provider(localdata, self.status, pkgs_to_build[0])
198 try: 270 taskdata.add_unresolved(localdata, self.status)
199 taskdata.add_provider(localdata, self.status, pkgs_to_build[0])
200 taskdata.add_unresolved(localdata, self.status)
201 except bb.providers.NoProvider:
202 sys.exit(1)
203 271
204 targetid = taskdata.getbuild_id(pkgs_to_build[0]) 272 targetid = taskdata.getbuild_id(pkgs_to_build[0])
205 fnid = taskdata.build_targets[targetid][0] 273 fnid = taskdata.build_targets[targetid][0]
@@ -211,55 +279,69 @@ class BBCooker:
211 try: 279 try:
212 envdata = self.bb_cache.loadDataFull(fn, self.configuration.data) 280 envdata = self.bb_cache.loadDataFull(fn, self.configuration.data)
213 except IOError, e: 281 except IOError, e:
214 bb.msg.fatal(bb.msg.domain.Parsing, "Unable to read %s: %s" % (fn, e)) 282 bb.msg.error(bb.msg.domain.Parsing, "Unable to read %s: %s" % (fn, e))
283 raise
215 except Exception, e: 284 except Exception, e:
216 bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e) 285 bb.msg.error(bb.msg.domain.Parsing, "%s" % e)
286 raise
287
288 class dummywrite:
289 def __init__(self):
290 self.writebuf = ""
291 def write(self, output):
292 self.writebuf = self.writebuf + output
217 293
218 # emit variables and shell functions 294 # emit variables and shell functions
219 try: 295 try:
220 data.update_data( envdata ) 296 data.update_data(envdata)
221 data.emit_env(sys.__stdout__, envdata, True) 297 wb = dummywrite()
298 data.emit_env(wb, envdata, True)
299 bb.msg.plain(wb.writebuf)
222 except Exception, e: 300 except Exception, e:
223 bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e) 301 bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e)
224 # emit the metadata which isnt valid shell 302 # emit the metadata which isnt valid shell
225 data.expandKeys( envdata ) 303 data.expandKeys(envdata)
226 for e in envdata.keys(): 304 for e in envdata.keys():
227 if data.getVarFlag( e, 'python', envdata ): 305 if data.getVarFlag( e, 'python', envdata ):
228 sys.__stdout__.write("\npython %s () {\n%s}\n" % (e, data.getVar(e, envdata, 1))) 306 bb.msg.plain("\npython %s () {\n%s}\n" % (e, data.getVar(e, envdata, 1)))
229 307
230 def generateDotGraph( self, pkgs_to_build, ignore_deps ): 308 def generateDepTreeData(self, pkgs_to_build, task):
231 """ 309 """
232 Generate a task dependency graph. 310 Create a dependency tree of pkgs_to_build, returning the data.
233
234 pkgs_to_build A list of packages that needs to be built
235 ignore_deps A list of names where processing of dependencies
236 should be stopped. e.g. dependencies that get
237 """ 311 """
238 312
239 for dep in ignore_deps: 313 # Need files parsed
240 self.status.ignored_dependencies.add(dep) 314 self.updateCache()
315
316 # If we are told to do the None task then query the default task
317 if (task == None):
318 task = self.configuration.cmd
319
320 pkgs_to_build = self.checkPackages(pkgs_to_build)
241 321
242 localdata = data.createCopy(self.configuration.data) 322 localdata = data.createCopy(self.configuration.data)
243 bb.data.update_data(localdata) 323 bb.data.update_data(localdata)
244 bb.data.expandKeys(localdata) 324 bb.data.expandKeys(localdata)
245 taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs) 325 taskdata = bb.taskdata.TaskData(self.configuration.abort)
246 326
247 runlist = [] 327 runlist = []
248 try: 328 for k in pkgs_to_build:
249 for k in pkgs_to_build: 329 taskdata.add_provider(localdata, self.status, k)
250 taskdata.add_provider(localdata, self.status, k) 330 runlist.append([k, "do_%s" % task])
251 runlist.append([k, "do_%s" % self.configuration.cmd]) 331 taskdata.add_unresolved(localdata, self.status)
252 taskdata.add_unresolved(localdata, self.status) 332
253 except bb.providers.NoProvider:
254 sys.exit(1)
255 rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist) 333 rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
256 rq.prepare_runqueue() 334 rq.prepare_runqueue()
257 335
258 seen_fnids = [] 336 seen_fnids = []
259 depends_file = file('depends.dot', 'w' ) 337 depend_tree = {}
260 tdepends_file = file('task-depends.dot', 'w' ) 338 depend_tree["depends"] = {}
261 print >> depends_file, "digraph depends {" 339 depend_tree["tdepends"] = {}
262 print >> tdepends_file, "digraph depends {" 340 depend_tree["pn"] = {}
341 depend_tree["rdepends-pn"] = {}
342 depend_tree["packages"] = {}
343 depend_tree["rdepends-pkg"] = {}
344 depend_tree["rrecs-pkg"] = {}
263 345
264 for task in range(len(rq.runq_fnid)): 346 for task in range(len(rq.runq_fnid)):
265 taskname = rq.runq_task[task] 347 taskname = rq.runq_task[task]
@@ -267,43 +349,118 @@ class BBCooker:
267 fn = taskdata.fn_index[fnid] 349 fn = taskdata.fn_index[fnid]
268 pn = self.status.pkg_fn[fn] 350 pn = self.status.pkg_fn[fn]
269 version = "%s:%s-%s" % self.status.pkg_pepvpr[fn] 351 version = "%s:%s-%s" % self.status.pkg_pepvpr[fn]
270 print >> tdepends_file, '"%s.%s" [label="%s %s\\n%s\\n%s"]' % (pn, taskname, pn, taskname, version, fn) 352 if pn not in depend_tree["pn"]:
353 depend_tree["pn"][pn] = {}
354 depend_tree["pn"][pn]["filename"] = fn
355 depend_tree["pn"][pn]["version"] = version
271 for dep in rq.runq_depends[task]: 356 for dep in rq.runq_depends[task]:
272 depfn = taskdata.fn_index[rq.runq_fnid[dep]] 357 depfn = taskdata.fn_index[rq.runq_fnid[dep]]
273 deppn = self.status.pkg_fn[depfn] 358 deppn = self.status.pkg_fn[depfn]
274 print >> tdepends_file, '"%s.%s" -> "%s.%s"' % (pn, rq.runq_task[task], deppn, rq.runq_task[dep]) 359 dotname = "%s.%s" % (pn, rq.runq_task[task])
360 if not dotname in depend_tree["tdepends"]:
361 depend_tree["tdepends"][dotname] = []
362 depend_tree["tdepends"][dotname].append("%s.%s" % (deppn, rq.runq_task[dep]))
275 if fnid not in seen_fnids: 363 if fnid not in seen_fnids:
276 seen_fnids.append(fnid) 364 seen_fnids.append(fnid)
277 packages = [] 365 packages = []
278 print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn) 366
279 for depend in self.status.deps[fn]: 367 depend_tree["depends"][pn] = []
280 print >> depends_file, '"%s" -> "%s"' % (pn, depend) 368 for dep in taskdata.depids[fnid]:
369 depend_tree["depends"][pn].append(taskdata.build_names_index[dep])
370
371 depend_tree["rdepends-pn"][pn] = []
372 for rdep in taskdata.rdepids[fnid]:
373 depend_tree["rdepends-pn"][pn].append(taskdata.run_names_index[rdep])
374
281 rdepends = self.status.rundeps[fn] 375 rdepends = self.status.rundeps[fn]
282 for package in rdepends: 376 for package in rdepends:
283 for rdepend in re.findall("([\w.-]+)(\ \(.+\))?", rdepends[package]): 377 depend_tree["rdepends-pkg"][package] = []
284 print >> depends_file, '"%s" -> "%s%s" [style=dashed]' % (package, rdepend[0], rdepend[1]) 378 for rdepend in rdepends[package]:
379 depend_tree["rdepends-pkg"][package].append(rdepend)
285 packages.append(package) 380 packages.append(package)
381
286 rrecs = self.status.runrecs[fn] 382 rrecs = self.status.runrecs[fn]
287 for package in rrecs: 383 for package in rrecs:
288 for rdepend in re.findall("([\w.-]+)(\ \(.+\))?", rrecs[package]): 384 depend_tree["rrecs-pkg"][package] = []
289 print >> depends_file, '"%s" -> "%s%s" [style=dashed]' % (package, rdepend[0], rdepend[1]) 385 for rdepend in rrecs[package]:
386 depend_tree["rrecs-pkg"][package].append(rdepend)
290 if not package in packages: 387 if not package in packages:
291 packages.append(package) 388 packages.append(package)
389
292 for package in packages: 390 for package in packages:
293 if package != pn: 391 if package not in depend_tree["packages"]:
294 print >> depends_file, '"%s" [label="%s(%s) %s\\n%s"]' % (package, package, pn, version, fn) 392 depend_tree["packages"][package] = {}
295 for depend in self.status.deps[fn]: 393 depend_tree["packages"][package]["pn"] = pn
296 print >> depends_file, '"%s" -> "%s"' % (package, depend) 394 depend_tree["packages"][package]["filename"] = fn
297 # Prints a flattened form of the above where subpackages of a package are merged into the main pn 395 depend_tree["packages"][package]["version"] = version
298 #print >> depends_file, '"%s" [label="%s %s\\n%s\\n%s"]' % (pn, pn, taskname, version, fn) 396
299 #for rdep in taskdata.rdepids[fnid]: 397 return depend_tree
300 # print >> depends_file, '"%s" -> "%s" [style=dashed]' % (pn, taskdata.run_names_index[rdep]) 398
301 #for dep in taskdata.depids[fnid]: 399
302 # print >> depends_file, '"%s" -> "%s"' % (pn, taskdata.build_names_index[dep]) 400 def generateDepTreeEvent(self, pkgs_to_build, task):
401 """
402 Create a task dependency graph of pkgs_to_build.
403 Generate an event with the result
404 """
405 depgraph = self.generateDepTreeData(pkgs_to_build, task)
406 bb.event.fire(bb.event.DepTreeGenerated(depgraph), self.configuration.data)
407
408 def generateDotGraphFiles(self, pkgs_to_build, task):
409 """
410 Create a task dependency graph of pkgs_to_build.
411 Save the result to a set of .dot files.
412 """
413
414 depgraph = self.generateDepTreeData(pkgs_to_build, task)
415
416 # Prints a flattened form of package-depends below where subpackages of a package are merged into the main pn
417 depends_file = file('pn-depends.dot', 'w' )
418 print >> depends_file, "digraph depends {"
419 for pn in depgraph["pn"]:
420 fn = depgraph["pn"][pn]["filename"]
421 version = depgraph["pn"][pn]["version"]
422 print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)
423 for pn in depgraph["depends"]:
424 for depend in depgraph["depends"][pn]:
425 print >> depends_file, '"%s" -> "%s"' % (pn, depend)
426 for pn in depgraph["rdepends-pn"]:
427 for rdepend in depgraph["rdepends-pn"][pn]:
428 print >> depends_file, '"%s" -> "%s" [style=dashed]' % (pn, rdepend)
429 print >> depends_file, "}"
430 bb.msg.plain("PN dependencies saved to 'pn-depends.dot'")
431
432 depends_file = file('package-depends.dot', 'w' )
433 print >> depends_file, "digraph depends {"
434 for package in depgraph["packages"]:
435 pn = depgraph["packages"][package]["pn"]
436 fn = depgraph["packages"][package]["filename"]
437 version = depgraph["packages"][package]["version"]
438 if package == pn:
439 print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)
440 else:
441 print >> depends_file, '"%s" [label="%s(%s) %s\\n%s"]' % (package, package, pn, version, fn)
442 for depend in depgraph["depends"][pn]:
443 print >> depends_file, '"%s" -> "%s"' % (package, depend)
444 for package in depgraph["rdepends-pkg"]:
445 for rdepend in depgraph["rdepends-pkg"][package]:
446 print >> depends_file, '"%s" -> "%s" [style=dashed]' % (package, rdepend)
447 for package in depgraph["rrecs-pkg"]:
448 for rdepend in depgraph["rrecs-pkg"][package]:
449 print >> depends_file, '"%s" -> "%s" [style=dashed]' % (package, rdepend)
303 print >> depends_file, "}" 450 print >> depends_file, "}"
451 bb.msg.plain("Package dependencies saved to 'package-depends.dot'")
452
453 tdepends_file = file('task-depends.dot', 'w' )
454 print >> tdepends_file, "digraph depends {"
455 for task in depgraph["tdepends"]:
456 (pn, taskname) = task.rsplit(".", 1)
457 fn = depgraph["pn"][pn]["filename"]
458 version = depgraph["pn"][pn]["version"]
459 print >> tdepends_file, '"%s.%s" [label="%s %s\\n%s\\n%s"]' % (pn, taskname, pn, taskname, version, fn)
460 for dep in depgraph["tdepends"][task]:
461 print >> tdepends_file, '"%s" -> "%s"' % (task, dep)
304 print >> tdepends_file, "}" 462 print >> tdepends_file, "}"
305 bb.msg.note(1, bb.msg.domain.Collection, "Dependencies saved to 'depends.dot'") 463 bb.msg.plain("Task dependencies saved to 'task-depends.dot'")
306 bb.msg.note(1, bb.msg.domain.Collection, "Task dependencies saved to 'task-depends.dot'")
307 464
308 def buildDepgraph( self ): 465 def buildDepgraph( self ):
309 all_depends = self.status.all_depends 466 all_depends = self.status.all_depends
@@ -324,7 +481,7 @@ class BBCooker:
324 try: 481 try:
325 (providee, provider) = p.split(':') 482 (providee, provider) = p.split(':')
326 except: 483 except:
327 bb.msg.error(bb.msg.domain.Provider, "Malformed option in PREFERRED_PROVIDERS variable: %s" % p) 484 bb.msg.fatal(bb.msg.domain.Provider, "Malformed option in PREFERRED_PROVIDERS variable: %s" % p)
328 continue 485 continue
329 if providee in self.status.preferred and self.status.preferred[providee] != provider: 486 if providee in self.status.preferred and self.status.preferred[providee] != provider:
330 bb.msg.error(bb.msg.domain.Provider, "conflicting preferences for %s: both %s and %s specified" % (providee, provider, self.status.preferred[providee])) 487 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:
362 self.status.possible_world = None 519 self.status.possible_world = None
363 self.status.all_depends = None 520 self.status.all_depends = None
364 521
365 def myProgressCallback( self, x, y, f, from_cache ):
366 """Update any tty with the progress change"""
367 if os.isatty(sys.stdout.fileno()):
368 sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
369 sys.stdout.flush()
370 else:
371 if x == 1:
372 sys.stdout.write("Parsing .bb files, please wait...")
373 sys.stdout.flush()
374 if x == y:
375 sys.stdout.write("done.")
376 sys.stdout.flush()
377
378 def interactiveMode( self ): 522 def interactiveMode( self ):
379 """Drop off into a shell""" 523 """Drop off into a shell"""
380 try: 524 try:
@@ -383,12 +527,10 @@ class BBCooker:
383 bb.msg.fatal(bb.msg.domain.Parsing, "Sorry, shell not available (%s)" % details ) 527 bb.msg.fatal(bb.msg.domain.Parsing, "Sorry, shell not available (%s)" % details )
384 else: 528 else:
385 shell.start( self ) 529 shell.start( self )
386 sys.exit( 0 )
387 530
388 def parseConfigurationFile( self, afiles ): 531 def parseConfigurationFile( self, afile ):
389 try: 532 try:
390 for afile in afiles: 533 self.configuration.data = bb.parse.handle( afile, self.configuration.data )
391 self.configuration.data = bb.parse.handle( afile, self.configuration.data )
392 534
393 # Handle any INHERITs and inherit the base class 535 # Handle any INHERITs and inherit the base class
394 inherits = ["base"] + (bb.data.getVar('INHERIT', self.configuration.data, True ) or "").split() 536 inherits = ["base"] + (bb.data.getVar('INHERIT', self.configuration.data, True ) or "").split()
@@ -402,10 +544,10 @@ class BBCooker:
402 544
403 bb.fetch.fetcher_init(self.configuration.data) 545 bb.fetch.fetcher_init(self.configuration.data)
404 546
405 bb.event.fire(bb.event.ConfigParsed(self.configuration.data)) 547 bb.event.fire(bb.event.ConfigParsed(), self.configuration.data)
406 548
407 except IOError, e: 549 except IOError, e:
408 bb.msg.fatal(bb.msg.domain.Parsing, "IO Error: %s" % str(e) ) 550 bb.msg.fatal(bb.msg.domain.Parsing, "Error when parsing %s: %s" % (afile, str(e)))
409 except bb.parse.ParseError, details: 551 except bb.parse.ParseError, details:
410 bb.msg.fatal(bb.msg.domain.Parsing, "Unable to parse %s (%s)" % (afile, details) ) 552 bb.msg.fatal(bb.msg.domain.Parsing, "Unable to parse %s (%s)" % (afile, details) )
411 553
@@ -439,17 +581,17 @@ class BBCooker:
439 """ 581 """
440 if not bb.data.getVar("BUILDNAME", self.configuration.data): 582 if not bb.data.getVar("BUILDNAME", self.configuration.data):
441 bb.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), self.configuration.data) 583 bb.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), self.configuration.data)
442 bb.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S',time.gmtime()),self.configuration.data) 584 bb.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S',time.gmtime()), self.configuration.data)
443 585
444 def matchFile(self, buildfile): 586 def matchFiles(self, buildfile):
445 """ 587 """
446 Convert the fragment buildfile into a real file 588 Find the .bb files which match the expression in 'buildfile'.
447 Error if there are too many matches
448 """ 589 """
590
449 bf = os.path.abspath(buildfile) 591 bf = os.path.abspath(buildfile)
450 try: 592 try:
451 os.stat(bf) 593 os.stat(bf)
452 return bf 594 return [bf]
453 except OSError: 595 except OSError:
454 (filelist, masked) = self.collect_bbfiles() 596 (filelist, masked) = self.collect_bbfiles()
455 regexp = re.compile(buildfile) 597 regexp = re.compile(buildfile)
@@ -458,27 +600,41 @@ class BBCooker:
458 if regexp.search(f) and os.path.isfile(f): 600 if regexp.search(f) and os.path.isfile(f):
459 bf = f 601 bf = f
460 matches.append(f) 602 matches.append(f)
461 if len(matches) != 1: 603 return matches
462 bb.msg.error(bb.msg.domain.Parsing, "Unable to match %s (%s matches found):" % (buildfile, len(matches)))
463 for f in matches:
464 bb.msg.error(bb.msg.domain.Parsing, " %s" % f)
465 return False
466 return matches[0]
467 604
468 def buildFile(self, buildfile): 605 def matchFile(self, buildfile):
606 """
607 Find the .bb file which matches the expression in 'buildfile'.
608 Raise an error if multiple files
609 """
610 matches = self.matchFiles(buildfile)
611 if len(matches) != 1:
612 bb.msg.error(bb.msg.domain.Parsing, "Unable to match %s (%s matches found):" % (buildfile, len(matches)))
613 for f in matches:
614 bb.msg.error(bb.msg.domain.Parsing, " %s" % f)
615 raise MultipleMatches
616 return matches[0]
617
618 def buildFile(self, buildfile, task):
469 """ 619 """
470 Build the file matching regexp buildfile 620 Build the file matching regexp buildfile
471 """ 621 """
472 622
473 # Make sure our target is a fully qualified filename 623 # Parse the configuration here. We need to do it explicitly here since
624 # buildFile() doesn't use the cache
625 self.parseConfiguration()
626
627 # If we are told to do the None task then query the default task
628 if (task == None):
629 task = self.configuration.cmd
630
474 fn = self.matchFile(buildfile) 631 fn = self.matchFile(buildfile)
475 if not fn: 632 self.buildSetVars()
476 return False
477 633
478 # Load data into the cache for fn and parse the loaded cache data 634 # Load data into the cache for fn and parse the loaded cache data
479 self.bb_cache = bb.cache.init(self) 635 self.bb_cache = bb.cache.init(self)
480 self.status = bb.cache.CacheData() 636 self.status = bb.cache.CacheData()
481 self.bb_cache.loadData(fn, self.configuration.data, self.status) 637 self.bb_cache.loadData(fn, self.configuration.data, self.status)
482 638
483 # Tweak some variables 639 # Tweak some variables
484 item = self.bb_cache.getVar('PN', fn, True) 640 item = self.bb_cache.getVar('PN', fn, True)
@@ -493,159 +649,157 @@ class BBCooker:
493 649
494 # Remove stamp for target if force mode active 650 # Remove stamp for target if force mode active
495 if self.configuration.force: 651 if self.configuration.force:
496 bb.msg.note(2, bb.msg.domain.RunQueue, "Remove stamp %s, %s" % (self.configuration.cmd, fn)) 652 bb.msg.note(2, bb.msg.domain.RunQueue, "Remove stamp %s, %s" % (task, fn))
497 bb.build.del_stamp('do_%s' % self.configuration.cmd, self.configuration.data) 653 bb.build.del_stamp('do_%s' % task, self.status, fn)
498 654
499 # Setup taskdata structure 655 # Setup taskdata structure
500 taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs) 656 taskdata = bb.taskdata.TaskData(self.configuration.abort)
501 taskdata.add_provider(self.configuration.data, self.status, item) 657 taskdata.add_provider(self.configuration.data, self.status, item)
502 658
503 buildname = bb.data.getVar("BUILDNAME", self.configuration.data) 659 buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
504 bb.event.fire(bb.event.BuildStarted(buildname, [item], self.configuration.event_data)) 660 bb.event.fire(bb.event.BuildStarted(buildname, [item]), self.configuration.event_data)
505 661
506 # Execute the runqueue 662 # Execute the runqueue
507 runlist = [[item, "do_%s" % self.configuration.cmd]] 663 runlist = [[item, "do_%s" % task]]
664
508 rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist) 665 rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
509 rq.prepare_runqueue() 666
510 try: 667 def buildFileIdle(server, rq, abort):
511 failures = rq.execute_runqueue() 668
512 except runqueue.TaskFailure, fnids: 669 if abort or self.cookerAction == cookerStop:
670 rq.finish_runqueue(True)
671 elif self.cookerAction == cookerShutdown:
672 rq.finish_runqueue(False)
513 failures = 0 673 failures = 0
514 for fnid in fnids: 674 try:
515 bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid]) 675 retval = rq.execute_runqueue()
516 failures = failures + 1 676 except runqueue.TaskFailure, fnids:
517 bb.event.fire(bb.event.BuildCompleted(buildname, [item], self.configuration.event_data, failures)) 677 for fnid in fnids:
518 return False 678 bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
519 bb.event.fire(bb.event.BuildCompleted(buildname, [item], self.configuration.event_data, failures)) 679 failures = failures + 1
520 return True 680 retval = False
681 if not retval:
682 self.command.finishAsyncCommand()
683 bb.event.fire(bb.event.BuildCompleted(buildname, item, failures), self.configuration.event_data)
684 return False
685 return 0.5
686
687 self.server.register_idle_function(buildFileIdle, rq)
521 688
522 def buildTargets(self, targets): 689 def buildTargets(self, targets, task):
523 """ 690 """
524 Attempt to build the targets specified 691 Attempt to build the targets specified
525 """ 692 """
526 693
527 buildname = bb.data.getVar("BUILDNAME", self.configuration.data) 694 # Need files parsed
528 bb.event.fire(bb.event.BuildStarted(buildname, targets, self.configuration.event_data)) 695 self.updateCache()
529 696
530 localdata = data.createCopy(self.configuration.data) 697 # If we are told to do the NULL task then query the default task
531 bb.data.update_data(localdata) 698 if (task == None):
532 bb.data.expandKeys(localdata) 699 task = self.configuration.cmd
533 700
534 taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs) 701 targets = self.checkPackages(targets)
535 702
536 runlist = [] 703 def buildTargetsIdle(server, rq, abort):
537 try:
538 for k in targets:
539 taskdata.add_provider(localdata, self.status, k)
540 runlist.append([k, "do_%s" % self.configuration.cmd])
541 taskdata.add_unresolved(localdata, self.status)
542 except bb.providers.NoProvider:
543 sys.exit(1)
544 704
545 rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist) 705 if abort or self.cookerAction == cookerStop:
546 rq.prepare_runqueue() 706 rq.finish_runqueue(True)
547 try: 707 elif self.cookerAction == cookerShutdown:
548 failures = rq.execute_runqueue() 708 rq.finish_runqueue(False)
549 except runqueue.TaskFailure, fnids:
550 failures = 0 709 failures = 0
551 for fnid in fnids: 710 try:
552 bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid]) 711 retval = rq.execute_runqueue()
553 failures = failures + 1 712 except runqueue.TaskFailure, fnids:
554 bb.event.fire(bb.event.BuildCompleted(buildname, targets, self.configuration.event_data, failures)) 713 for fnid in fnids:
555 sys.exit(1) 714 bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
556 bb.event.fire(bb.event.BuildCompleted(buildname, targets, self.configuration.event_data, failures)) 715 failures = failures + 1
716 retval = False
717 if not retval:
718 self.command.finishAsyncCommand()
719 bb.event.fire(bb.event.BuildCompleted(buildname, targets, failures), self.configuration.event_data)
720 return None
721 return 0.5
557 722
558 sys.exit(0) 723 self.buildSetVars()
559 724
560 def updateCache(self): 725 buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
561 # Import Psyco if available and not disabled 726 bb.event.fire(bb.event.BuildStarted(buildname, targets), self.configuration.event_data)
562 import platform
563 if platform.machine() in ['i386', 'i486', 'i586', 'i686']:
564 if not self.configuration.disable_psyco:
565 try:
566 import psyco
567 except ImportError:
568 bb.msg.note(1, bb.msg.domain.Collection, "Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
569 else:
570 psyco.bind( self.parse_bbfiles )
571 else:
572 bb.msg.note(1, bb.msg.domain.Collection, "You have disabled Psyco. This decreases performance.")
573 727
574 self.status = bb.cache.CacheData() 728 localdata = data.createCopy(self.configuration.data)
729 bb.data.update_data(localdata)
730 bb.data.expandKeys(localdata)
575 731
576 ignore = bb.data.getVar("ASSUME_PROVIDED", self.configuration.data, 1) or "" 732 taskdata = bb.taskdata.TaskData(self.configuration.abort)
577 self.status.ignored_dependencies = set( ignore.split() )
578 733
579 self.handleCollections( bb.data.getVar("BBFILE_COLLECTIONS", self.configuration.data, 1) ) 734 runlist = []
735 for k in targets:
736 taskdata.add_provider(localdata, self.status, k)
737 runlist.append([k, "do_%s" % task])
738 taskdata.add_unresolved(localdata, self.status)
580 739
581 bb.msg.debug(1, bb.msg.domain.Collection, "collecting .bb files") 740 rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
582 (filelist, masked) = self.collect_bbfiles()
583 bb.data.renameVar("__depends", "__base_depends", self.configuration.data)
584 self.parse_bbfiles(filelist, masked, self.myProgressCallback)
585 bb.msg.debug(1, bb.msg.domain.Collection, "parsing complete")
586 741
587 self.buildDepgraph() 742 self.server.register_idle_function(buildTargetsIdle, rq)
588 743
589 def cook(self): 744 def updateCache(self):
590 """
591 We are building stuff here. We do the building
592 from here. By default we try to execute task
593 build.
594 """
595 745
596 # Wipe the OS environment 746 if self.cookerState == cookerParsed:
597 bb.utils.empty_environment() 747 return
598 748
599 if self.configuration.show_environment: 749 if self.cookerState != cookerParsing:
600 self.showEnvironment(self.configuration.buildfile, self.configuration.pkgs_to_build)
601 sys.exit( 0 )
602 750
603 self.buildSetVars() 751 self.parseConfiguration ()
604 752
605 if self.configuration.interactive: 753 # Import Psyco if available and not disabled
606 self.interactiveMode() 754 import platform
755 if platform.machine() in ['i386', 'i486', 'i586', 'i686']:
756 if not self.configuration.disable_psyco:
757 try:
758 import psyco
759 except ImportError:
760 bb.msg.note(1, bb.msg.domain.Collection, "Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
761 else:
762 psyco.bind( CookerParser.parse_next )
763 else:
764 bb.msg.note(1, bb.msg.domain.Collection, "You have disabled Psyco. This decreases performance.")
607 765
608 if self.configuration.buildfile is not None: 766 self.status = bb.cache.CacheData()
609 if not self.buildFile(self.configuration.buildfile):
610 sys.exit(1)
611 sys.exit(0)
612 767
613 # initialise the parsing status now we know we will need deps 768 ignore = bb.data.getVar("ASSUME_PROVIDED", self.configuration.data, 1) or ""
614 self.updateCache() 769 self.status.ignored_dependencies = set(ignore.split())
770
771 for dep in self.configuration.extra_assume_provided:
772 self.status.ignored_dependencies.add(dep)
773
774 self.handleCollections( bb.data.getVar("BBFILE_COLLECTIONS", self.configuration.data, 1) )
615 775
616 if self.configuration.revisions_changed: 776 bb.msg.debug(1, bb.msg.domain.Collection, "collecting .bb files")
617 sys.exit(bb.fetch.fetcher_compare_revisons(self.configuration.data)) 777 (filelist, masked) = self.collect_bbfiles()
778 bb.data.renameVar("__depends", "__base_depends", self.configuration.data)
618 779
619 if self.configuration.parse_only: 780 self.parser = CookerParser(self, filelist, masked)
620 bb.msg.note(1, bb.msg.domain.Collection, "Requested parsing .bb files only. Exiting.") 781 self.cookerState = cookerParsing
621 return 0
622 782
623 pkgs_to_build = self.configuration.pkgs_to_build 783 if not self.parser.parse_next():
784 bb.msg.debug(1, bb.msg.domain.Collection, "parsing complete")
785 self.buildDepgraph()
786 self.cookerState = cookerParsed
787 return None
624 788
625 if len(pkgs_to_build) == 0 and not self.configuration.show_versions: 789 return True
626 print "Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help'"
627 print "for usage information."
628 sys.exit(0)
629 790
630 try: 791 def checkPackages(self, pkgs_to_build):
631 if self.configuration.show_versions:
632 self.showVersions()
633 sys.exit( 0 )
634 if 'world' in pkgs_to_build:
635 self.buildWorldTargetList()
636 pkgs_to_build.remove('world')
637 for t in self.status.world_target:
638 pkgs_to_build.append(t)
639 792
640 if self.configuration.dot_graph: 793 if len(pkgs_to_build) == 0:
641 self.generateDotGraph( pkgs_to_build, self.configuration.ignored_dot_deps ) 794 raise NothingToBuild
642 sys.exit( 0 )
643 795
644 return self.buildTargets(pkgs_to_build) 796 if 'world' in pkgs_to_build:
797 self.buildWorldTargetList()
798 pkgs_to_build.remove('world')
799 for t in self.status.world_target:
800 pkgs_to_build.append(t)
645 801
646 except KeyboardInterrupt: 802 return pkgs_to_build
647 bb.msg.note(1, bb.msg.domain.Collection, "KeyboardInterrupt - Build not completed.")
648 sys.exit(1)
649 803
650 def get_bbfiles( self, path = os.getcwd() ): 804 def get_bbfiles( self, path = os.getcwd() ):
651 """Get list of default .bb files by reading out the current directory""" 805 """Get list of default .bb files by reading out the current directory"""
@@ -717,59 +871,108 @@ class BBCooker:
717 871
718 return (finalfiles, masked) 872 return (finalfiles, masked)
719 873
720 def parse_bbfiles(self, filelist, masked, progressCallback = None): 874 def serve(self):
721 parsed, cached, skipped, error = 0, 0, 0, 0
722 for i in xrange( len( filelist ) ):
723 f = filelist[i]
724 875
725 #bb.msg.debug(1, bb.msg.domain.Collection, "parsing %s" % f) 876 # Empty the environment. The environment will be populated as
877 # necessary from the data store.
878 bb.utils.empty_environment()
726 879
727 # read a file's metadata 880 if self.configuration.profile:
728 try: 881 try:
729 fromCache, skip = self.bb_cache.loadData(f, self.configuration.data, self.status) 882 import cProfile as profile
730 if skip: 883 except:
731 skipped += 1 884 import profile
732 bb.msg.debug(2, bb.msg.domain.Collection, "skipping %s" % f) 885
733 self.bb_cache.skip(f) 886 profile.runctx("self.server.serve_forever()", globals(), locals(), "profile.log")
734 continue 887
735 elif fromCache: cached += 1 888 # Redirect stdout to capture profile information
736 else: parsed += 1 889 pout = open('profile.log.processed', 'w')
737 890 so = sys.stdout.fileno()
738 # Disabled by RP as was no longer functional 891 os.dup2(pout.fileno(), so)
739 # allow metadata files to add items to BBFILES 892
740 #data.update_data(self.pkgdata[f]) 893 import pstats
741 #addbbfiles = self.bb_cache.getVar('BBFILES', f, False) or None 894 p = pstats.Stats('profile.log')
742 #if addbbfiles: 895 p.sort_stats('time')
743 # for aof in addbbfiles.split(): 896 p.print_stats()
744 # if not files.count(aof): 897 p.print_callers()
745 # if not os.path.isabs(aof): 898 p.sort_stats('cumulative')
746 # aof = os.path.join(os.path.dirname(f),aof) 899 p.print_stats()
747 # files.append(aof) 900
748 901 os.dup2(so, pout.fileno())
749 # now inform the caller 902 pout.flush()
750 if progressCallback is not None: 903 pout.close()
751 progressCallback( i + 1, len( filelist ), f, fromCache ) 904 else:
905 self.server.serve_forever()
906
907 bb.event.fire(CookerExit(), self.configuration.event_data)
908
909class CookerExit(bb.event.Event):
910 """
911 Notify clients of the Cooker shutdown
912 """
913
914 def __init__(self):
915 bb.event.Event.__init__(self)
916
917class CookerParser:
918 def __init__(self, cooker, filelist, masked):
919 # Internal data
920 self.filelist = filelist
921 self.cooker = cooker
922
923 # Accounting statistics
924 self.parsed = 0
925 self.cached = 0
926 self.error = 0
927 self.masked = masked
928 self.total = len(filelist)
929
930 self.skipped = 0
931 self.virtuals = 0
932
933 # Pointer to the next file to parse
934 self.pointer = 0
935
936 def parse_next(self):
937 if self.pointer < len(self.filelist):
938 f = self.filelist[self.pointer]
939 cooker = self.cooker
940
941 try:
942 fromCache, skipped, virtuals = cooker.bb_cache.loadData(f, cooker.configuration.data, cooker.status)
943 if fromCache:
944 self.cached += 1
945 else:
946 self.parsed += 1
947
948 self.skipped += skipped
949 self.virtuals += virtuals
752 950
753 except IOError, e: 951 except IOError, e:
754 self.bb_cache.remove(f) 952 self.error += 1
953 cooker.bb_cache.remove(f)
755 bb.msg.error(bb.msg.domain.Collection, "opening %s: %s" % (f, e)) 954 bb.msg.error(bb.msg.domain.Collection, "opening %s: %s" % (f, e))
756 pass 955 pass
757 except KeyboardInterrupt: 956 except KeyboardInterrupt:
758 self.bb_cache.sync() 957 cooker.bb_cache.remove(f)
958 cooker.bb_cache.sync()
759 raise 959 raise
760 except Exception, e: 960 except Exception, e:
761 error += 1 961 self.error += 1
762 self.bb_cache.remove(f) 962 cooker.bb_cache.remove(f)
763 bb.msg.error(bb.msg.domain.Collection, "%s while parsing %s" % (e, f)) 963 bb.msg.error(bb.msg.domain.Collection, "%s while parsing %s" % (e, f))
764 except: 964 except:
765 self.bb_cache.remove(f) 965 cooker.bb_cache.remove(f)
766 raise 966 raise
967 finally:
968 bb.event.fire(bb.event.ParseProgress(self.cached, self.parsed, self.skipped, self.masked, self.virtuals, self.error, self.total), cooker.configuration.event_data)
767 969
768 if progressCallback is not None: 970 self.pointer += 1
769 print "\r" # need newline after Handling Bitbake files message
770 bb.msg.note(1, bb.msg.domain.Collection, "Parsing finished. %d cached, %d parsed, %d skipped, %d masked." % ( cached, parsed, skipped, masked ))
771 971
772 self.bb_cache.sync() 972 if self.pointer >= self.total:
973 cooker.bb_cache.sync()
974 if self.error > 0:
975 raise ParsingErrorsFound
976 return False
977 return True
773 978
774 if error > 0:
775 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 @@
1"""
2Python Deamonizing helper
3
4Configurable daemon behaviors:
5
6 1.) The current working directory set to the "/" directory.
7 2.) The current file creation mode mask set to 0.
8 3.) Close all open files (1024).
9 4.) Redirect standard I/O streams to "/dev/null".
10
11A failed call to fork() now raises an exception.
12
13References:
14 1) Advanced Programming in the Unix Environment: W. Richard Stevens
15 2) Unix Programming Frequently Asked Questions:
16 http://www.erlenstar.demon.co.uk/unix/faq_toc.html
17
18Modified to allow a function to be daemonized and return for
19bitbake use by Richard Purdie
20"""
21
22__author__ = "Chad J. Schroeder"
23__copyright__ = "Copyright (C) 2005 Chad J. Schroeder"
24__version__ = "0.2"
25
26# Standard Python modules.
27import os # Miscellaneous OS interfaces.
28import sys # System-specific parameters and functions.
29
30# Default daemon parameters.
31# File mode creation mask of the daemon.
32# For BitBake's children, we do want to inherit the parent umask.
33UMASK = None
34
35# Default maximum for the number of available file descriptors.
36MAXFD = 1024
37
38# The standard I/O file descriptors are redirected to /dev/null by default.
39if (hasattr(os, "devnull")):
40 REDIRECT_TO = os.devnull
41else:
42 REDIRECT_TO = "/dev/null"
43
44def createDaemon(function, logfile):
45 """
46 Detach a process from the controlling terminal and run it in the
47 background as a daemon, returning control to the caller.
48 """
49
50 try:
51 # Fork a child process so the parent can exit. This returns control to
52 # the command-line or shell. It also guarantees that the child will not
53 # be a process group leader, since the child receives a new process ID
54 # and inherits the parent's process group ID. This step is required
55 # to insure that the next call to os.setsid is successful.
56 pid = os.fork()
57 except OSError, e:
58 raise Exception, "%s [%d]" % (e.strerror, e.errno)
59
60 if (pid == 0): # The first child.
61 # To become the session leader of this new session and the process group
62 # leader of the new process group, we call os.setsid(). The process is
63 # also guaranteed not to have a controlling terminal.
64 os.setsid()
65
66 # Is ignoring SIGHUP necessary?
67 #
68 # It's often suggested that the SIGHUP signal should be ignored before
69 # the second fork to avoid premature termination of the process. The
70 # reason is that when the first child terminates, all processes, e.g.
71 # the second child, in the orphaned group will be sent a SIGHUP.
72 #
73 # "However, as part of the session management system, there are exactly
74 # two cases where SIGHUP is sent on the death of a process:
75 #
76 # 1) When the process that dies is the session leader of a session that
77 # is attached to a terminal device, SIGHUP is sent to all processes
78 # in the foreground process group of that terminal device.
79 # 2) When the death of a process causes a process group to become
80 # orphaned, and one or more processes in the orphaned group are
81 # stopped, then SIGHUP and SIGCONT are sent to all members of the
82 # orphaned group." [2]
83 #
84 # The first case can be ignored since the child is guaranteed not to have
85 # a controlling terminal. The second case isn't so easy to dismiss.
86 # The process group is orphaned when the first child terminates and
87 # POSIX.1 requires that every STOPPED process in an orphaned process
88 # group be sent a SIGHUP signal followed by a SIGCONT signal. Since the
89 # second child is not STOPPED though, we can safely forego ignoring the
90 # SIGHUP signal. In any case, there are no ill-effects if it is ignored.
91 #
92 # import signal # Set handlers for asynchronous events.
93 # signal.signal(signal.SIGHUP, signal.SIG_IGN)
94
95 try:
96 # Fork a second child and exit immediately to prevent zombies. This
97 # causes the second child process to be orphaned, making the init
98 # process responsible for its cleanup. And, since the first child is
99 # a session leader without a controlling terminal, it's possible for
100 # it to acquire one by opening a terminal in the future (System V-
101 # based systems). This second fork guarantees that the child is no
102 # longer a session leader, preventing the daemon from ever acquiring
103 # a controlling terminal.
104 pid = os.fork() # Fork a second child.
105 except OSError, e:
106 raise Exception, "%s [%d]" % (e.strerror, e.errno)
107
108 if (pid == 0): # The second child.
109 # We probably don't want the file mode creation mask inherited from
110 # the parent, so we give the child complete control over permissions.
111 if UMASK is not None:
112 os.umask(UMASK)
113 else:
114 # Parent (the first child) of the second child.
115 os._exit(0)
116 else:
117 # exit() or _exit()?
118 # _exit is like exit(), but it doesn't call any functions registered
119 # with atexit (and on_exit) or any registered signal handlers. It also
120 # closes any open file descriptors. Using exit() may cause all stdio
121 # streams to be flushed twice and any temporary files may be unexpectedly
122 # removed. It's therefore recommended that child branches of a fork()
123 # and the parent branch(es) of a daemon use _exit().
124 return
125
126 # Close all open file descriptors. This prevents the child from keeping
127 # open any file descriptors inherited from the parent. There is a variety
128 # of methods to accomplish this task. Three are listed below.
129 #
130 # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum
131 # number of open file descriptors to close. If it doesn't exists, use
132 # the default value (configurable).
133 #
134 # try:
135 # maxfd = os.sysconf("SC_OPEN_MAX")
136 # except (AttributeError, ValueError):
137 # maxfd = MAXFD
138 #
139 # OR
140 #
141 # if (os.sysconf_names.has_key("SC_OPEN_MAX")):
142 # maxfd = os.sysconf("SC_OPEN_MAX")
143 # else:
144 # maxfd = MAXFD
145 #
146 # OR
147 #
148 # Use the getrlimit method to retrieve the maximum file descriptor number
149 # that can be opened by this process. If there is not limit on the
150 # resource, use the default value.
151 #
152 import resource # Resource usage information.
153 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
154 if (maxfd == resource.RLIM_INFINITY):
155 maxfd = MAXFD
156
157 # Iterate through and close all file descriptors.
158# for fd in range(0, maxfd):
159# try:
160# os.close(fd)
161# except OSError: # ERROR, fd wasn't open to begin with (ignored)
162# pass
163
164 # Redirect the standard I/O file descriptors to the specified file. Since
165 # the daemon has no controlling terminal, most daemons redirect stdin,
166 # stdout, and stderr to /dev/null. This is done to prevent side-effects
167 # from reads and writes to the standard I/O file descriptors.
168
169 # This call to open is guaranteed to return the lowest file descriptor,
170 # which will be 0 (stdin), since it was closed above.
171# os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
172
173 # Duplicate standard input to standard output and standard error.
174# os.dup2(0, 1) # standard output (1)
175# os.dup2(0, 2) # standard error (2)
176
177
178 si = file('/dev/null', 'r')
179 so = file(logfile, 'w')
180 se = so
181
182
183 # Replace those fds with our own
184 os.dup2(si.fileno(), sys.stdin.fileno())
185 os.dup2(so.fileno(), sys.stdout.fileno())
186 os.dup2(se.fileno(), sys.stderr.fileno())
187
188 function()
189
190 os._exit(0)
191
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.
37# 37#
38#Based on functions from the base bb module, Copyright 2003 Holger Schurig 38#Based on functions from the base bb module, Copyright 2003 Holger Schurig
39 39
40import sys, os, re, time, types 40import sys, os, re, types
41if sys.argv[0][-5:] == "pydoc": 41if sys.argv[0][-5:] == "pydoc":
42 path = os.path.dirname(os.path.dirname(sys.argv[1])) 42 path = os.path.dirname(os.path.dirname(sys.argv[1]))
43else: 43else:
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.
24 24
25import os, re 25import os, re
26import bb.utils 26import bb.utils
27import pickle
28
29# This is the pid for which we should generate the event. This is set when
30# the runqueue forks off.
31worker_pid = 0
32worker_pipe = None
27 33
28class Event: 34class Event:
29 """Base class for events""" 35 """Base class for events"""
30 type = "Event"
31
32 def __init__(self, d):
33 self._data = d
34
35 def getData(self):
36 return self._data
37
38 def setData(self, data):
39 self._data = data
40 36
41 data = property(getData, setData, None, "data property") 37 def __init__(self):
38 self.pid = worker_pid
42 39
43NotHandled = 0 40NotHandled = 0
44Handled = 1 41Handled = 1
@@ -47,75 +44,83 @@ Registered = 10
47AlreadyRegistered = 14 44AlreadyRegistered = 14
48 45
49# Internal 46# Internal
50_handlers = [] 47_handlers = {}
51_handlers_dict = {} 48_ui_handlers = {}
49_ui_handler_seq = 0
52 50
53def tmpHandler(event): 51def fire(event, d):
54 """Default handler for code events""" 52 """Fire off an Event"""
55 return NotHandled
56 53
57def defaultTmpHandler(): 54 if worker_pid != 0:
58 tmp = "def tmpHandler(e):\n\t\"\"\"heh\"\"\"\n\treturn NotHandled" 55 worker_fire(event, d)
59 comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event.defaultTmpHandler") 56 return
60 return comp
61 57
62def fire(event): 58 for handler in _handlers:
63 """Fire off an Event""" 59 h = _handlers[handler]
64 for h in _handlers: 60 event.data = d
65 if type(h).__name__ == "code": 61 if type(h).__name__ == "code":
66 exec(h) 62 exec(h)
67 if tmpHandler(event) == Handled: 63 tmpHandler(event)
68 return Handled
69 else: 64 else:
70 if h(event) == Handled: 65 h(event)
71 return Handled 66 del event.data
72 return NotHandled 67
68 errors = []
69 for h in _ui_handlers:
70 #print "Sending event %s" % event
71 try:
72 # We use pickle here since it better handles object instances
73 # which xmlrpc's marshaller does not. Events *must* be serializable
74 # by pickle.
75 _ui_handlers[h].event.send((pickle.dumps(event)))
76 except:
77 errors.append(h)
78 for h in errors:
79 del _ui_handlers[h]
80
81def worker_fire(event, d):
82 data = "<event>" + pickle.dumps(event) + "</event>"
83 if os.write(worker_pipe, data) != len (data):
84 print "Error sending event to server (short write)"
85
86def fire_from_worker(event, d):
87 if not event.startswith("<event>") or not event.endswith("</event>"):
88 print "Error, not an event"
89 return
90 event = pickle.loads(event[7:-8])
91 bb.event.fire(event, d)
73 92
74def register(name, handler): 93def register(name, handler):
75 """Register an Event handler""" 94 """Register an Event handler"""
76 95
77 # already registered 96 # already registered
78 if name in _handlers_dict: 97 if name in _handlers:
79 return AlreadyRegistered 98 return AlreadyRegistered
80 99
81 if handler is not None: 100 if handler is not None:
82# handle string containing python code 101 # handle string containing python code
83 if type(handler).__name__ == "str": 102 if type(handler).__name__ == "str":
84 _registerCode(handler) 103 tmp = "def tmpHandler(e):\n%s" % handler
104 comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._registerCode")
105 _handlers[name] = comp
85 else: 106 else:
86 _handlers.append(handler) 107 _handlers[name] = handler
87 108
88 _handlers_dict[name] = 1
89 return Registered 109 return Registered
90 110
91def _registerCode(handlerStr):
92 """Register a 'code' Event.
93 Deprecated interface; call register instead.
94
95 Expects to be passed python code as a string, which will
96 be passed in turn to compile() and then exec(). Note that
97 the code will be within a function, so should have had
98 appropriate tabbing put in place."""
99 tmp = "def tmpHandler(e):\n%s" % handlerStr
100 comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._registerCode")
101# prevent duplicate registration
102 _handlers.append(comp)
103
104def remove(name, handler): 111def remove(name, handler):
105 """Remove an Event handler""" 112 """Remove an Event handler"""
113 _handlers.pop(name)
106 114
107 _handlers_dict.pop(name) 115def register_UIHhandler(handler):
108 if type(handler).__name__ == "str": 116 bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
109 return _removeCode(handler) 117 _ui_handlers[_ui_handler_seq] = handler
110 else: 118 return _ui_handler_seq
111 _handlers.remove(handler)
112 119
113def _removeCode(handlerStr): 120def unregister_UIHhandler(handlerNum):
114 """Remove a 'code' Event handler 121 if handlerNum in _ui_handlers:
115 Deprecated interface; call remove instead.""" 122 del _ui_handlers[handlerNum]
116 tmp = "def tmpHandler(e):\n%s" % handlerStr 123 return
117 comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._removeCode")
118 _handlers.remove(comp)
119 124
120def getName(e): 125def getName(e):
121 """Returns the name of a class or class instance""" 126 """Returns the name of a class or class instance"""
@@ -130,17 +135,17 @@ class ConfigParsed(Event):
130class RecipeParsed(Event): 135class RecipeParsed(Event):
131 """ Recipe Parsing Complete """ 136 """ Recipe Parsing Complete """
132 137
133 def __init__(self, fn, d): 138 def __init__(self, fn):
134 self.fn = fn 139 self.fn = fn
135 Event.__init__(self, d) 140 Event.__init__(self)
136 141
137class StampUpdate(Event): 142class StampUpdate(Event):
138 """Trigger for any adjustment of the stamp files to happen""" 143 """Trigger for any adjustment of the stamp files to happen"""
139 144
140 def __init__(self, targets, stampfns, d): 145 def __init__(self, targets, stampfns):
141 self._targets = targets 146 self._targets = targets
142 self._stampfns = stampfns 147 self._stampfns = stampfns
143 Event.__init__(self, d) 148 Event.__init__(self)
144 149
145 def getStampPrefix(self): 150 def getStampPrefix(self):
146 return self._stampfns 151 return self._stampfns
@@ -151,29 +156,13 @@ class StampUpdate(Event):
151 stampPrefix = property(getStampPrefix) 156 stampPrefix = property(getStampPrefix)
152 targets = property(getTargets) 157 targets = property(getTargets)
153 158
154class PkgBase(Event):
155 """Base class for package events"""
156
157 def __init__(self, t, d):
158 self._pkg = t
159 Event.__init__(self, d)
160
161 def getPkg(self):
162 return self._pkg
163
164 def setPkg(self, pkg):
165 self._pkg = pkg
166
167 pkg = property(getPkg, setPkg, None, "pkg property")
168
169
170class BuildBase(Event): 159class BuildBase(Event):
171 """Base class for bbmake run events""" 160 """Base class for bbmake run events"""
172 161
173 def __init__(self, n, p, c, failures = 0): 162 def __init__(self, n, p, failures = 0):
174 self._name = n 163 self._name = n
175 self._pkgs = p 164 self._pkgs = p
176 Event.__init__(self, c) 165 Event.__init__(self)
177 self._failures = failures 166 self._failures = failures
178 167
179 def getPkgs(self): 168 def getPkgs(self):
@@ -205,33 +194,8 @@ class BuildBase(Event):
205 cfg = property(getCfg, setCfg, None, "cfg property") 194 cfg = property(getCfg, setCfg, None, "cfg property")
206 195
207 196
208class DepBase(PkgBase):
209 """Base class for dependency events"""
210
211 def __init__(self, t, data, d):
212 self._dep = d
213 PkgBase.__init__(self, t, data)
214
215 def getDep(self):
216 return self._dep
217
218 def setDep(self, dep):
219 self._dep = dep
220
221 dep = property(getDep, setDep, None, "dep property")
222
223
224class PkgStarted(PkgBase):
225 """Package build started"""
226 197
227 198
228class PkgFailed(PkgBase):
229 """Package build failed"""
230
231
232class PkgSucceeded(PkgBase):
233 """Package build completed"""
234
235 199
236class BuildStarted(BuildBase): 200class BuildStarted(BuildBase):
237 """bbmake build run started""" 201 """bbmake build run started"""
@@ -241,18 +205,13 @@ class BuildCompleted(BuildBase):
241 """bbmake build run completed""" 205 """bbmake build run completed"""
242 206
243 207
244class UnsatisfiedDep(DepBase):
245 """Unsatisfied Dependency"""
246 208
247 209
248class RecursiveDep(DepBase):
249 """Recursive Dependency"""
250
251class NoProvider(Event): 210class NoProvider(Event):
252 """No Provider for an Event""" 211 """No Provider for an Event"""
253 212
254 def __init__(self, item, data,runtime=False): 213 def __init__(self, item, runtime=False):
255 Event.__init__(self, data) 214 Event.__init__(self)
256 self._item = item 215 self._item = item
257 self._runtime = runtime 216 self._runtime = runtime
258 217
@@ -265,8 +224,8 @@ class NoProvider(Event):
265class MultipleProviders(Event): 224class MultipleProviders(Event):
266 """Multiple Providers""" 225 """Multiple Providers"""
267 226
268 def __init__(self, item, candidates, data, runtime = False): 227 def __init__(self, item, candidates, runtime = False):
269 Event.__init__(self, data) 228 Event.__init__(self)
270 self._item = item 229 self._item = item
271 self._candidates = candidates 230 self._candidates = candidates
272 self._is_runtime = runtime 231 self._is_runtime = runtime
@@ -288,3 +247,29 @@ class MultipleProviders(Event):
288 Get the possible Candidates for a PROVIDER. 247 Get the possible Candidates for a PROVIDER.
289 """ 248 """
290 return self._candidates 249 return self._candidates
250
251class ParseProgress(Event):
252 """
253 Parsing Progress Event
254 """
255
256 def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total):
257 Event.__init__(self)
258 self.cached = cached
259 self.parsed = parsed
260 self.skipped = skipped
261 self.virtuals = virtuals
262 self.masked = masked
263 self.errors = errors
264 self.sofar = cached + parsed
265 self.total = total
266
267class DepTreeGenerated(Event):
268 """
269 Event when a dependency tree has been generated
270 """
271
272 def __init__(self, depgraph):
273 Event.__init__(self)
274 self._depgraph = depgraph
275
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):
99 pd.delDomain("BB_URI_HEADREVS") 99 pd.delDomain("BB_URI_HEADREVS")
100 else: 100 else:
101 bb.msg.fatal(bb.msg.domain.Fetcher, "Invalid SRCREV cache policy of: %s" % srcrev_policy) 101 bb.msg.fatal(bb.msg.domain.Fetcher, "Invalid SRCREV cache policy of: %s" % srcrev_policy)
102
103 for m in methods:
104 if hasattr(m, "init"):
105 m.init(d)
106
102 # Make sure our domains exist 107 # Make sure our domains exist
103 pd.addDomain("BB_URI_HEADREVS") 108 pd.addDomain("BB_URI_HEADREVS")
104 pd.addDomain("BB_URI_LOCALCOUNT") 109 pd.addDomain("BB_URI_LOCALCOUNT")
@@ -467,6 +472,23 @@ class Fetch(object):
467 472
468 srcrev_internal_helper = staticmethod(srcrev_internal_helper) 473 srcrev_internal_helper = staticmethod(srcrev_internal_helper)
469 474
475 def localcount_internal_helper(ud, d):
476 """
477 Return:
478 a) a locked localcount if specified
479 b) None otherwise
480 """
481
482 localcount= None
483 if 'name' in ud.parm:
484 pn = data.getVar("PN", d, 1)
485 localcount = data.getVar("LOCALCOUNT_" + ud.parm['name'], d, 1)
486 if not localcount:
487 localcount = data.getVar("LOCALCOUNT", d, 1)
488 return localcount
489
490 localcount_internal_helper = staticmethod(localcount_internal_helper)
491
470 def try_mirror(d, tarfn): 492 def try_mirror(d, tarfn):
471 """ 493 """
472 Try to use a mirrored version of the sources. We do this 494 Try to use a mirrored version of the sources. We do this
@@ -555,12 +577,7 @@ class Fetch(object):
555 """ 577 """
556 578
557 """ 579 """
558 has_sortable_valid = hasattr(self, "_sortable_revision_valid") 580 if hasattr(self, "_sortable_revision"):
559 has_sortable = hasattr(self, "_sortable_revision")
560
561 if has_sortable and not has_sortable_valid:
562 return self._sortable_revision(url, ud, d)
563 elif has_sortable and self._sortable_revision_valid(url, ud, d):
564 return self._sortable_revision(url, ud, d) 581 return self._sortable_revision(url, ud, d)
565 582
566 pd = persist_data.PersistData(d) 583 pd = persist_data.PersistData(d)
@@ -568,13 +585,24 @@ class Fetch(object):
568 585
569 latest_rev = self._build_revision(url, ud, d) 586 latest_rev = self._build_revision(url, ud, d)
570 last_rev = pd.getValue("BB_URI_LOCALCOUNT", key + "_rev") 587 last_rev = pd.getValue("BB_URI_LOCALCOUNT", key + "_rev")
571 count = pd.getValue("BB_URI_LOCALCOUNT", key + "_count") 588 uselocalcount = bb.data.getVar("BB_LOCALCOUNT_OVERRIDE", d, True) or False
589 count = None
590 if uselocalcount:
591 count = Fetch.localcount_internal_helper(ud, d)
592 if count is None:
593 count = pd.getValue("BB_URI_LOCALCOUNT", key + "_count")
572 594
573 if last_rev == latest_rev: 595 if last_rev == latest_rev:
574 return str(count + "+" + latest_rev) 596 return str(count + "+" + latest_rev)
575 597
598 buildindex_provided = hasattr(self, "_sortable_buildindex")
599 if buildindex_provided:
600 count = self._sortable_buildindex(url, ud, d, latest_rev)
601
576 if count is None: 602 if count is None:
577 count = "0" 603 count = "0"
604 elif uselocalcount or buildindex_provided:
605 count = str(count)
578 else: 606 else:
579 count = str(int(count) + 1) 607 count = str(int(count) + 1)
580 608
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):
41 """ 41 """
42 Check to see if a given url can be fetched with cvs. 42 Check to see if a given url can be fetched with cvs.
43 """ 43 """
44 return ud.type in ['cvs', 'pserver'] 44 return ud.type in ['cvs']
45 45
46 def localpath(self, url, ud, d): 46 def localpath(self, url, ud, d):
47 if not "module" in ud.parm: 47 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
28 28
29class Git(Fetch): 29class Git(Fetch):
30 """Class to fetch a module or modules from git repositories""" 30 """Class to fetch a module or modules from git repositories"""
31 def init(self, d):
32 #
33 # Only enable _sortable revision if the key is set
34 #
35 if bb.data.getVar("BB_GIT_CLONE_FOR_SRCREV", d, True):
36 self._sortable_buildindex = self._sortable_buildindex_disabled
31 def supports(self, url, ud, d): 37 def supports(self, url, ud, d):
32 """ 38 """
33 Check to see if a given url can be fetched with git. 39 Check to see if a given url can be fetched with git.
@@ -58,10 +64,18 @@ class Git(Fetch):
58 if not ud.tag or ud.tag == "master": 64 if not ud.tag or ud.tag == "master":
59 ud.tag = self.latest_revision(url, ud, d) 65 ud.tag = self.latest_revision(url, ud, d)
60 66
67 subdir = ud.parm.get("subpath", "")
68 if subdir != "":
69 if subdir.endswith("/"):
70 subdir = subdir[:-1]
71 subdirpath = os.path.join(ud.path, subdir);
72 else:
73 subdirpath = ud.path;
74
61 if 'fullclone' in ud.parm: 75 if 'fullclone' in ud.parm:
62 ud.localfile = ud.mirrortarball 76 ud.localfile = ud.mirrortarball
63 else: 77 else:
64 ud.localfile = data.expand('git_%s%s_%s.tar.gz' % (ud.host, ud.path.replace('/', '.'), ud.tag), d) 78 ud.localfile = data.expand('git_%s%s_%s.tar.gz' % (ud.host, subdirpath.replace('/', '.'), ud.tag), d)
65 79
66 return os.path.join(data.getVar("DL_DIR", d, True), ud.localfile) 80 return os.path.join(data.getVar("DL_DIR", d, True), ud.localfile)
67 81
@@ -111,10 +125,27 @@ class Git(Fetch):
111 if os.path.exists(codir): 125 if os.path.exists(codir):
112 bb.utils.prunedir(codir) 126 bb.utils.prunedir(codir)
113 127
128 subdir = ud.parm.get("subpath", "")
129 if subdir != "":
130 if subdir.endswith("/"):
131 subdirbase = os.path.basename(subdir[:-1])
132 else:
133 subdirbase = os.path.basename(subdir)
134 else:
135 subdirbase = ""
136
137 if subdir != "":
138 readpathspec = ":%s" % (subdir)
139 codir = os.path.join(codir, "git")
140 coprefix = os.path.join(codir, subdirbase, "")
141 else:
142 readpathspec = ""
143 coprefix = os.path.join(codir, "git", "")
144
114 bb.mkdirhier(codir) 145 bb.mkdirhier(codir)
115 os.chdir(ud.clonedir) 146 os.chdir(ud.clonedir)
116 runfetchcmd("git read-tree %s" % (ud.tag), d) 147 runfetchcmd("git read-tree %s%s" % (ud.tag, readpathspec), d)
117 runfetchcmd("git checkout-index -q -f --prefix=%s -a" % (os.path.join(codir, "git", "")), d) 148 runfetchcmd("git checkout-index -q -f --prefix=%s -a" % (coprefix), d)
118 149
119 os.chdir(codir) 150 os.chdir(codir)
120 bb.msg.note(1, bb.msg.domain.Fetcher, "Creating tarball of git checkout") 151 bb.msg.note(1, bb.msg.domain.Fetcher, "Creating tarball of git checkout")
@@ -154,42 +185,32 @@ class Git(Fetch):
154 def _build_revision(self, url, ud, d): 185 def _build_revision(self, url, ud, d):
155 return ud.tag 186 return ud.tag
156 187
157 def _sortable_revision_valid(self, url, ud, d): 188 def _sortable_buildindex_disabled(self, url, ud, d, rev):
158 return bb.data.getVar("BB_GIT_CLONE_FOR_SRCREV", d, True) or False
159
160 def _sortable_revision(self, url, ud, d):
161 """ 189 """
162 This is only called when _sortable_revision_valid called true 190 Return a suitable buildindex for the revision specified. This is done by counting revisions
163 191 using "git rev-list" which may or may not work in different circumstances.
164 We will have to get the updated revision.
165 """ 192 """
166 193
167 key = "GIT_CACHED_REVISION-%s-%s" % (gitsrcname, ud.tag)
168 if bb.data.getVar(key, d):
169 return bb.data.getVar(key, d)
170
171
172 # Runtime warning on wrongly configured sources
173 if ud.tag == "1":
174 bb.msg.error(1, bb.msg.domain.Fetcher, "SRCREV is '1'. This indicates a configuration error of %s" % url)
175 return "0+1"
176
177 cwd = os.getcwd() 194 cwd = os.getcwd()
178 195
179 # Check if we have the rev already 196 # Check if we have the rev already
197
180 if not os.path.exists(ud.clonedir): 198 if not os.path.exists(ud.clonedir):
181 print "no repo" 199 print "no repo"
182 self.go(None, ud, d) 200 self.go(None, ud, d)
201 if not os.path.exists(ud.clonedir):
202 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))
203 return None
204
183 205
184 os.chdir(ud.clonedir) 206 os.chdir(ud.clonedir)
185 if not self._contains_ref(ud.tag, d): 207 if not self._contains_ref(rev, d):
186 self.go(None, ud, d) 208 self.go(None, ud, d)
187 209
188 output = runfetchcmd("git rev-list %s -- 2> /dev/null | wc -l" % ud.tag, d, quiet=True) 210 output = runfetchcmd("git rev-list %s -- 2> /dev/null | wc -l" % rev, d, quiet=True)
189 os.chdir(cwd) 211 os.chdir(cwd)
190 212
191 sortable_revision = "%s+%s" % (output.split()[0], ud.tag) 213 buildindex = "%s" % output.split()[0]
192 bb.data.setVar(key, sortable_revision, d) 214 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))
193 return sortable_revision 215 return buildindex
194
195 216
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
33class Local(Fetch): 33class Local(Fetch):
34 def supports(self, url, urldata, d): 34 def supports(self, url, urldata, d):
35 """ 35 """
36 Check to see if a given url can be fetched with cvs. 36 Check to see if a given url represents a local fetch.
37 """ 37 """
38 return urldata.type in ['file','patch'] 38 return urldata.type in ['file']
39 39
40 def localpath(self, url, urldata, d): 40 def localpath(self, url, urldata, d):
41 """ 41 """
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):
36 """Class to fetch a module or modules from svk repositories""" 36 """Class to fetch a module or modules from svk repositories"""
37 def supports(self, url, ud, d): 37 def supports(self, url, ud, d):
38 """ 38 """
39 Check to see if a given url can be fetched with cvs. 39 Check to see if a given url can be fetched with svk.
40 """ 40 """
41 return ud.type in ['svk'] 41 return ud.type in ['svk']
42 42
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):
36 """Class to fetch urls via 'wget'""" 36 """Class to fetch urls via 'wget'"""
37 def supports(self, url, ud, d): 37 def supports(self, url, ud, d):
38 """ 38 """
39 Check to see if a given url can be fetched with cvs. 39 Check to see if a given url can be fetched with wget.
40 """ 40 """
41 return ud.type in ['http','https','ftp'] 41 return ud.type in ['http','https','ftp']
42 42
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
22# with this program; if not, write to the Free Software Foundation, Inc., 22# with this program; if not, write to the Free Software Foundation, Inc.,
23# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 23# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 24
25import sys, os, re, bb 25import sys, bb
26from bb import utils, event 26from bb import event
27 27
28debug_level = {} 28debug_level = {}
29 29
@@ -47,9 +47,9 @@ domain = bb.utils.Enum(
47class MsgBase(bb.event.Event): 47class MsgBase(bb.event.Event):
48 """Base class for messages""" 48 """Base class for messages"""
49 49
50 def __init__(self, msg, d ): 50 def __init__(self, msg):
51 self._message = msg 51 self._message = msg
52 event.Event.__init__(self, d) 52 event.Event.__init__(self)
53 53
54class MsgDebug(MsgBase): 54class MsgDebug(MsgBase):
55 """Debug Message""" 55 """Debug Message"""
@@ -97,33 +97,29 @@ def set_debug_domains(domains):
97# 97#
98 98
99def debug(level, domain, msg, fn = None): 99def debug(level, domain, msg, fn = None):
100 bb.event.fire(MsgDebug(msg, None))
101 if not domain: 100 if not domain:
102 domain = 'default' 101 domain = 'default'
103 if debug_level[domain] >= level: 102 if debug_level[domain] >= level:
104 print 'DEBUG: ' + msg 103 bb.event.fire(MsgDebug(msg), None)
105 104
106def note(level, domain, msg, fn = None): 105def note(level, domain, msg, fn = None):
107 bb.event.fire(MsgNote(msg, None))
108 if not domain: 106 if not domain:
109 domain = 'default' 107 domain = 'default'
110 if level == 1 or verbose or debug_level[domain] >= 1: 108 if level == 1 or verbose or debug_level[domain] >= 1:
111 print 'NOTE: ' + msg 109 bb.event.fire(MsgNote(msg), None)
112 110
113def warn(domain, msg, fn = None): 111def warn(domain, msg, fn = None):
114 bb.event.fire(MsgWarn(msg, None)) 112 bb.event.fire(MsgWarn(msg), None)
115 print 'WARNING: ' + msg
116 113
117def error(domain, msg, fn = None): 114def error(domain, msg, fn = None):
118 bb.event.fire(MsgError(msg, None)) 115 bb.event.fire(MsgError(msg), None)
119 print 'ERROR: ' + msg 116 print 'ERROR: ' + msg
120 117
121def fatal(domain, msg, fn = None): 118def fatal(domain, msg, fn = None):
122 bb.event.fire(MsgFatal(msg, None)) 119 bb.event.fire(MsgFatal(msg), None)
123 print 'ERROR: ' + msg 120 print 'FATAL: ' + msg
124 sys.exit(1) 121 sys.exit(1)
125 122
126def plain(msg, fn = None): 123def plain(msg, fn = None):
127 bb.event.fire(MsgPlain(msg, None)) 124 bb.event.fire(MsgPlain(msg), None)
128 print msg
129 125
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):
94 for f in anonfuncs: 94 for f in anonfuncs:
95 code = code + " %s(d)\n" % f 95 code = code + " %s(d)\n" % f
96 data.setVar("__anonfunc", code, d) 96 data.setVar("__anonfunc", code, d)
97 build.exec_func_python("__anonfunc", d) 97 build.exec_func("__anonfunc", d)
98 data.delVar('T', d) 98 data.delVar('T', d)
99 if t: 99 if t:
100 data.setVar('T', t, d) 100 data.setVar('T', t, d)
@@ -114,7 +114,7 @@ def finalise(fn, d):
114 tasklist = data.getVar('__BBTASKS', d) or [] 114 tasklist = data.getVar('__BBTASKS', d) or []
115 bb.build.add_tasks(tasklist, d) 115 bb.build.add_tasks(tasklist, d)
116 116
117 bb.event.fire(bb.event.RecipeParsed(fn, d)) 117 bb.event.fire(bb.event.RecipeParsed(fn), d)
118 118
119 119
120def handle(fn, d, include = 0): 120def handle(fn, d, include = 0):
@@ -185,18 +185,26 @@ def handle(fn, d, include = 0):
185 multi = data.getVar('BBCLASSEXTEND', d, 1) 185 multi = data.getVar('BBCLASSEXTEND', d, 1)
186 if multi: 186 if multi:
187 based = bb.data.createCopy(d) 187 based = bb.data.createCopy(d)
188 else:
189 based = d
190 try:
188 finalise(fn, based) 191 finalise(fn, based)
189 darray = {"": based} 192 except bb.parse.SkipPackage:
190 for cls in multi.split(): 193 bb.data.setVar("__SKIPPED", True, based)
191 pn = data.getVar('PN', d, True) 194 darray = {"": based}
192 based = bb.data.createCopy(d) 195
193 data.setVar('PN', pn + '-' + cls, based) 196 for cls in (multi or "").split():
194 inherit([cls], based) 197 pn = data.getVar('PN', d, True)
198 based = bb.data.createCopy(d)
199 data.setVar('PN', pn + '-' + cls, based)
200 inherit([cls], based)
201 try:
195 finalise(fn, based) 202 finalise(fn, based)
196 darray[cls] = based 203 except bb.parse.SkipPackage:
197 return darray 204 bb.data.setVar("__SKIPPED", True, based)
198 else: 205 darray[cls] = based
199 finalise(fn, d) 206 return darray
207
200 bbpath.pop(0) 208 bbpath.pop(0)
201 if oldfile: 209 if oldfile:
202 bb.data.setVar("FILE", oldfile, d) 210 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+(.+)" )
34__export_regexp__ = re.compile( r"export\s+(.+)" ) 34__export_regexp__ = re.compile( r"export\s+(.+)" )
35 35
36def init(data): 36def init(data):
37 if not bb.data.getVar('TOPDIR', data): 37 topdir = bb.data.getVar('TOPDIR', data)
38 bb.data.setVar('TOPDIR', os.getcwd(), data) 38 if not topdir:
39 topdir = os.getcwd()
40 bb.data.setVar('TOPDIR', topdir, data)
39 if not bb.data.getVar('BBPATH', data): 41 if not bb.data.getVar('BBPATH', data):
40 bb.data.setVar('BBPATH', os.path.join(sys.prefix, 'share', 'bitbake'), data) 42 from pkg_resources import Requirement, resource_filename
43 bitbake = Requirement.parse("bitbake")
44 datadir = resource_filename(bitbake, "../share/bitbake")
45 basedir = resource_filename(bitbake, "..")
46 bb.data.setVar('BBPATH', '%s:%s:%s' % (topdir, datadir, basedir), data)
47
41 48
42def supports(fn, d): 49def supports(fn, d):
43 return localpath(fn, d)[-5:] == ".conf" 50 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 @@
21# with this program; if not, write to the Free Software Foundation, Inc., 21# with this program; if not, write to the Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 23
24import os, re 24import re
25from bb import data, utils 25from bb import data, utils
26import bb 26import bb
27 27
@@ -203,7 +203,7 @@ def _filterProviders(providers, item, cfgData, dataCache):
203 eligible.append(preferred_versions[pn][1]) 203 eligible.append(preferred_versions[pn][1])
204 204
205 # Now add latest verisons 205 # Now add latest verisons
206 for pn in pkg_pn.keys(): 206 for pn in sortpkg_pn.keys():
207 if pn in preferred_versions and preferred_versions[pn][1]: 207 if pn in preferred_versions and preferred_versions[pn][1]:
208 continue 208 continue
209 preferred_versions[pn] = findLatestProvider(pn, cfgData, dataCache, sortpkg_pn[pn][0]) 209 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:
37 """ 37 """
38 Holds statistics on the tasks handled by the associated runQueue 38 Holds statistics on the tasks handled by the associated runQueue
39 """ 39 """
40 def __init__(self): 40 def __init__(self, total):
41 self.completed = 0 41 self.completed = 0
42 self.skipped = 0 42 self.skipped = 0
43 self.failed = 0 43 self.failed = 0
44 self.active = 0
45 self.total = total
44 46
45 def taskFailed(self): 47 def taskFailed(self):
48 self.active = self.active - 1
46 self.failed = self.failed + 1 49 self.failed = self.failed + 1
47 50
48 def taskCompleted(self, number = 1): 51 def taskCompleted(self, number = 1):
52 self.active = self.active - number
49 self.completed = self.completed + number 53 self.completed = self.completed + number
50 54
51 def taskSkipped(self, number = 1): 55 def taskSkipped(self, number = 1):
56 self.active = self.active + number
52 self.skipped = self.skipped + number 57 self.skipped = self.skipped + number
53 58
59 def taskActive(self):
60 self.active = self.active + 1
61
62# These values indicate the next step due to be run in the
63# runQueue state machine
64runQueuePrepare = 2
65runQueueRunInit = 3
66runQueueRunning = 4
67runQueueFailed = 6
68runQueueCleanUp = 7
69runQueueComplete = 8
70runQueueChildProcess = 9
71
54class RunQueueScheduler: 72class RunQueueScheduler:
55 """ 73 """
56 Control the order tasks are scheduled in. 74 Control the order tasks are scheduled in.
@@ -142,9 +160,9 @@ class RunQueue:
142 self.cooker = cooker 160 self.cooker = cooker
143 self.dataCache = dataCache 161 self.dataCache = dataCache
144 self.taskData = taskData 162 self.taskData = taskData
163 self.cfgData = cfgData
145 self.targets = targets 164 self.targets = targets
146 165
147 self.cfgdata = cfgData
148 self.number_tasks = int(bb.data.getVar("BB_NUMBER_THREADS", cfgData, 1) or 1) 166 self.number_tasks = int(bb.data.getVar("BB_NUMBER_THREADS", cfgData, 1) or 1)
149 self.multi_provider_whitelist = (bb.data.getVar("MULTI_PROVIDER_WHITELIST", cfgData, 1) or "").split() 167 self.multi_provider_whitelist = (bb.data.getVar("MULTI_PROVIDER_WHITELIST", cfgData, 1) or "").split()
150 self.scheduler = bb.data.getVar("BB_SCHEDULER", cfgData, 1) or "speed" 168 self.scheduler = bb.data.getVar("BB_SCHEDULER", cfgData, 1) or "speed"
@@ -152,12 +170,13 @@ class RunQueue:
152 self.stampwhitelist = bb.data.getVar("BB_STAMP_WHITELIST", cfgData, 1) or "" 170 self.stampwhitelist = bb.data.getVar("BB_STAMP_WHITELIST", cfgData, 1) or ""
153 171
154 def reset_runqueue(self): 172 def reset_runqueue(self):
155
156 self.runq_fnid = [] 173 self.runq_fnid = []
157 self.runq_task = [] 174 self.runq_task = []
158 self.runq_depends = [] 175 self.runq_depends = []
159 self.runq_revdeps = [] 176 self.runq_revdeps = []
160 177
178 self.state = runQueuePrepare
179
161 def get_user_idstring(self, task): 180 def get_user_idstring(self, task):
162 fn = self.taskData.fn_index[self.runq_fnid[task]] 181 fn = self.taskData.fn_index[self.runq_fnid[task]]
163 taskname = self.runq_task[task] 182 taskname = self.runq_task[task]
@@ -653,6 +672,8 @@ class RunQueue:
653 672
654 #self.dump_data(taskData) 673 #self.dump_data(taskData)
655 674
675 self.state = runQueueRunInit
676
656 def check_stamps(self): 677 def check_stamps(self):
657 unchecked = {} 678 unchecked = {}
658 current = [] 679 current = []
@@ -796,39 +817,51 @@ class RunQueue:
796 (if the abort on failure configuration option isn't set) 817 (if the abort on failure configuration option isn't set)
797 """ 818 """
798 819
799 failures = 0 820 if self.state is runQueuePrepare:
800 while 1: 821 self.prepare_runqueue()
801 failed_fnids = [] 822
802 try: 823 if self.state is runQueueRunInit:
803 self.execute_runqueue_internal() 824 bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue")
804 finally: 825 self.execute_runqueue_initVars()
805 if self.master_process: 826
806 failed_fnids = self.finish_runqueue() 827 if self.state is runQueueRunning:
807 if len(failed_fnids) == 0: 828 self.execute_runqueue_internal()
808 return failures 829
830 if self.state is runQueueCleanUp:
831 self.finish_runqueue()
832
833 if self.state is runQueueFailed:
809 if not self.taskData.tryaltconfigs: 834 if not self.taskData.tryaltconfigs:
810 raise bb.runqueue.TaskFailure(failed_fnids) 835 raise bb.runqueue.TaskFailure(self.failed_fnids)
811 for fnid in failed_fnids: 836 for fnid in self.failed_fnids:
812 #print "Failure: %s %s %s" % (fnid, self.taskData.fn_index[fnid], self.runq_task[fnid])
813 self.taskData.fail_fnid(fnid) 837 self.taskData.fail_fnid(fnid)
814 failures = failures + 1
815 self.reset_runqueue() 838 self.reset_runqueue()
816 self.prepare_runqueue() 839
840 if self.state is runQueueComplete:
841 # All done
842 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))
843 return False
844
845 if self.state is runQueueChildProcess:
846 print "Child process"
847 return False
848
849 # Loop
850 return True
817 851
818 def execute_runqueue_initVars(self): 852 def execute_runqueue_initVars(self):
819 853
820 self.stats = RunQueueStats() 854 self.stats = RunQueueStats(len(self.runq_fnid))
821 855
822 self.active_builds = 0
823 self.runq_buildable = [] 856 self.runq_buildable = []
824 self.runq_running = [] 857 self.runq_running = []
825 self.runq_complete = [] 858 self.runq_complete = []
826 self.build_pids = {} 859 self.build_pids = {}
860 self.build_pipes = {}
827 self.failed_fnids = [] 861 self.failed_fnids = []
828 self.master_process = True
829 862
830 # Mark initial buildable tasks 863 # Mark initial buildable tasks
831 for task in range(len(self.runq_fnid)): 864 for task in range(self.stats.total):
832 self.runq_running.append(0) 865 self.runq_running.append(0)
833 self.runq_complete.append(0) 866 self.runq_complete.append(0)
834 if len(self.runq_depends[task]) == 0: 867 if len(self.runq_depends[task]) == 0:
@@ -836,6 +869,10 @@ class RunQueue:
836 else: 869 else:
837 self.runq_buildable.append(0) 870 self.runq_buildable.append(0)
838 871
872 self.state = runQueueRunning
873
874 event.fire(bb.event.StampUpdate(self.target_pairs, self.dataCache.stamp), self.cfgData)
875
839 def task_complete(self, task): 876 def task_complete(self, task):
840 """ 877 """
841 Mark a task as completed 878 Mark a task as completed
@@ -858,26 +895,32 @@ class RunQueue:
858 taskname = self.runq_task[revdep] 895 taskname = self.runq_task[revdep]
859 bb.msg.debug(1, bb.msg.domain.RunQueue, "Marking task %s (%s, %s) as buildable" % (revdep, fn, taskname)) 896 bb.msg.debug(1, bb.msg.domain.RunQueue, "Marking task %s (%s, %s) as buildable" % (revdep, fn, taskname))
860 897
898 def task_fail(self, task, exitcode):
899 """
900 Called when a task has failed
901 Updates the state engine with the failure
902 """
903 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed with %s" % (task, self.get_user_idstring(task), exitcode))
904 self.stats.taskFailed()
905 fnid = self.runq_fnid[task]
906 self.failed_fnids.append(fnid)
907 bb.event.fire(runQueueTaskFailed(task, self.stats, self), self.cfgData)
908 if self.taskData.abort:
909 self.state = runQueueCleanup
910
861 def execute_runqueue_internal(self): 911 def execute_runqueue_internal(self):
862 """ 912 """
863 Run the tasks in a queue prepared by prepare_runqueue 913 Run the tasks in a queue prepared by prepare_runqueue
864 """ 914 """
865 915
866 bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue") 916 if self.stats.total == 0:
867
868 self.execute_runqueue_initVars()
869
870 if len(self.runq_fnid) == 0:
871 # nothing to do 917 # nothing to do
872 return [] 918 self.state = runQueueCleanup
873
874 def sigint_handler(signum, frame):
875 raise KeyboardInterrupt
876
877 event.fire(bb.event.StampUpdate(self.target_pairs, self.dataCache.stamp, self.cfgdata))
878 919
879 while True: 920 while True:
880 task = self.sched.next() 921 task = None
922 if self.stats.active < self.number_tasks:
923 task = self.sched.next()
881 if task is not None: 924 if task is not None:
882 fn = self.taskData.fn_index[self.runq_fnid[task]] 925 fn = self.taskData.fn_index[self.runq_fnid[task]]
883 926
@@ -885,107 +928,143 @@ class RunQueue:
885 if self.check_stamp_task(task): 928 if self.check_stamp_task(task):
886 bb.msg.debug(2, bb.msg.domain.RunQueue, "Stamp current task %s (%s)" % (task, self.get_user_idstring(task))) 929 bb.msg.debug(2, bb.msg.domain.RunQueue, "Stamp current task %s (%s)" % (task, self.get_user_idstring(task)))
887 self.runq_running[task] = 1 930 self.runq_running[task] = 1
931 self.runq_buildable[task] = 1
888 self.task_complete(task) 932 self.task_complete(task)
889 self.stats.taskCompleted() 933 self.stats.taskCompleted()
890 self.stats.taskSkipped() 934 self.stats.taskSkipped()
891 continue 935 continue
892 936
893 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)))
894 sys.stdout.flush() 937 sys.stdout.flush()
895 sys.stderr.flush() 938 sys.stderr.flush()
896 try: 939 try:
940 pipein, pipeout = os.pipe()
897 pid = os.fork() 941 pid = os.fork()
898 except OSError, e: 942 except OSError, e:
899 bb.msg.fatal(bb.msg.domain.RunQueue, "fork failed: %d (%s)" % (e.errno, e.strerror)) 943 bb.msg.fatal(bb.msg.domain.RunQueue, "fork failed: %d (%s)" % (e.errno, e.strerror))
900 if pid == 0: 944 if pid == 0:
901 # Bypass master process' handling 945 os.close(pipein)
902 self.master_process = False 946 # Save out the PID so that the event can include it the
903 # Stop Ctrl+C being sent to children 947 # events
904 # signal.signal(signal.SIGINT, signal.SIG_IGN) 948 bb.event.worker_pid = os.getpid()
949 bb.event.worker_pipe = pipeout
950
951 self.state = runQueueChildProcess
905 # Make the child the process group leader 952 # Make the child the process group leader
906 os.setpgid(0, 0) 953 os.setpgid(0, 0)
954 # No stdin
907 newsi = os.open('/dev/null', os.O_RDWR) 955 newsi = os.open('/dev/null', os.O_RDWR)
908 os.dup2(newsi, sys.stdin.fileno()) 956 os.dup2(newsi, sys.stdin.fileno())
909 self.cooker.configuration.cmd = taskname[3:] 957
958 bb.event.fire(runQueueTaskStarted(task, self.stats, self), self.cfgData)
959 bb.msg.note(1, bb.msg.domain.RunQueue,
960 "Running task %d of %d (ID: %s, %s)" % (self.stats.completed + self.stats.active + 1,
961 self.stats.total,
962 task,
963 self.get_user_idstring(task)))
964
910 bb.data.setVar("__RUNQUEUE_DO_NOT_USE_EXTERNALLY", self, self.cooker.configuration.data) 965 bb.data.setVar("__RUNQUEUE_DO_NOT_USE_EXTERNALLY", self, self.cooker.configuration.data)
911 try: 966 try:
912 self.cooker.tryBuild(fn) 967 self.cooker.tryBuild(fn, taskname[3:])
913 except bb.build.EventException: 968 except bb.build.EventException:
914 bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed") 969 bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
915 sys.exit(1) 970 os._exit(1)
916 except: 971 except:
917 bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed") 972 bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
918 raise 973 os._exit(1)
919 sys.exit(0) 974 os._exit(0)
975
920 self.build_pids[pid] = task 976 self.build_pids[pid] = task
977 self.build_pipes[pid] = runQueuePipe(pipein, pipeout, self.cfgData)
921 self.runq_running[task] = 1 978 self.runq_running[task] = 1
922 self.active_builds = self.active_builds + 1 979 self.stats.taskActive()
923 if self.active_builds < self.number_tasks: 980 if self.stats.active < self.number_tasks:
924 continue 981 continue
925 if self.active_builds > 0: 982
926 result = os.waitpid(-1, 0) 983 for pipe in self.build_pipes:
927 self.active_builds = self.active_builds - 1 984 self.build_pipes[pipe].read()
985
986 if self.stats.active > 0:
987 result = os.waitpid(-1, os.WNOHANG)
988 if result[0] is 0 and result[1] is 0:
989 return
928 task = self.build_pids[result[0]] 990 task = self.build_pids[result[0]]
991 del self.build_pids[result[0]]
992 self.build_pipes[result[0]].close()
993 del self.build_pipes[result[0]]
929 if result[1] != 0: 994 if result[1] != 0:
930 del self.build_pids[result[0]] 995 self.task_fail(task, result[1])
931 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task))) 996 return
932 self.failed_fnids.append(self.runq_fnid[task])
933 self.stats.taskFailed()
934 if not self.taskData.abort:
935 continue
936 break
937 self.task_complete(task) 997 self.task_complete(task)
938 self.stats.taskCompleted() 998 self.stats.taskCompleted()
939 del self.build_pids[result[0]] 999 bb.event.fire(runQueueTaskCompleted(task, self.stats, self), self.cfgData)
940 continue 1000 continue
1001
1002 if len(self.failed_fnids) != 0:
1003 self.state = runQueueFailed
1004 return
1005
1006 # Sanity Checks
1007 for task in range(self.stats.total):
1008 if self.runq_buildable[task] == 0:
1009 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never buildable!" % task)
1010 if self.runq_running[task] == 0:
1011 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never ran!" % task)
1012 if self.runq_complete[task] == 0:
1013 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task)
1014 self.state = runQueueComplete
941 return 1015 return
942 1016
943 def finish_runqueue(self): 1017 def finish_runqueue_now(self):
1018 bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGINT to remaining %s tasks" % self.stats.active)
1019 for k, v in self.build_pids.iteritems():
1020 try:
1021 os.kill(-k, signal.SIGINT)
1022 except:
1023 pass
1024 for pipe in self.build_pipes:
1025 self.build_pipes[pipe].read()
1026
1027 def finish_runqueue(self, now = False):
1028 self.state = runQueueCleanUp
1029 if now:
1030 self.finish_runqueue_now()
944 try: 1031 try:
945 while self.active_builds > 0: 1032 while self.stats.active > 0:
946 bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % self.active_builds) 1033 bb.event.fire(runQueueExitWait(self.stats.active), self.cfgData)
1034 bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % self.stats.active)
947 tasknum = 1 1035 tasknum = 1
948 for k, v in self.build_pids.iteritems(): 1036 for k, v in self.build_pids.iteritems():
949 bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v), k)) 1037 bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v), k))
950 tasknum = tasknum + 1 1038 tasknum = tasknum + 1
951 result = os.waitpid(-1, 0) 1039 result = os.waitpid(-1, os.WNOHANG)
1040 if result[0] is 0 and result[1] is 0:
1041 return
952 task = self.build_pids[result[0]] 1042 task = self.build_pids[result[0]]
953 if result[1] != 0:
954 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task)))
955 self.failed_fnids.append(self.runq_fnid[task])
956 self.stats.taskFailed()
957 del self.build_pids[result[0]] 1043 del self.build_pids[result[0]]
958 self.active_builds = self.active_builds - 1 1044 self.build_pipes[result[0]].close()
959 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)) 1045 del self.build_pipes[result[0]]
960 return self.failed_fnids 1046 if result[1] != 0:
961 except KeyboardInterrupt: 1047 self.task_fail(task, result[1])
962 bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGINT to remaining %s tasks" % self.active_builds) 1048 else:
963 for k, v in self.build_pids.iteritems(): 1049 self.stats.taskCompleted()
964 try: 1050 bb.event.fire(runQueueTaskCompleted(task, self.stats, self), self.cfgData)
965 os.kill(-k, signal.SIGINT) 1051 except:
966 except: 1052 self.finish_runqueue_now()
967 pass
968 raise 1053 raise
969 1054
970 # Sanity Checks 1055 if len(self.failed_fnids) != 0:
971 for task in range(len(self.runq_fnid)): 1056 self.state = runQueueFailed
972 if self.runq_buildable[task] == 0: 1057 return
973 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never buildable!" % task)
974 if self.runq_running[task] == 0:
975 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never ran!" % task)
976 if self.runq_complete[task] == 0:
977 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task)
978
979 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))
980 1058
981 return self.failed_fnids 1059 self.state = runQueueComplete
1060 return
982 1061
983 def dump_data(self, taskQueue): 1062 def dump_data(self, taskQueue):
984 """ 1063 """
985 Dump some debug information on the internal data structures 1064 Dump some debug information on the internal data structures
986 """ 1065 """
987 bb.msg.debug(3, bb.msg.domain.RunQueue, "run_tasks:") 1066 bb.msg.debug(3, bb.msg.domain.RunQueue, "run_tasks:")
988 for task in range(len(self.runq_fnid)): 1067 for task in range(len(self.runq_task)):
989 bb.msg.debug(3, bb.msg.domain.RunQueue, " (%s)%s - %s: %s Deps %s RevDeps %s" % (task, 1068 bb.msg.debug(3, bb.msg.domain.RunQueue, " (%s)%s - %s: %s Deps %s RevDeps %s" % (task,
990 taskQueue.fn_index[self.runq_fnid[task]], 1069 taskQueue.fn_index[self.runq_fnid[task]],
991 self.runq_task[task], 1070 self.runq_task[task],
@@ -994,7 +1073,7 @@ class RunQueue:
994 self.runq_revdeps[task])) 1073 self.runq_revdeps[task]))
995 1074
996 bb.msg.debug(3, bb.msg.domain.RunQueue, "sorted_tasks:") 1075 bb.msg.debug(3, bb.msg.domain.RunQueue, "sorted_tasks:")
997 for task1 in range(len(self.runq_fnid)): 1076 for task1 in range(len(self.runq_task)):
998 if task1 in self.prio_map: 1077 if task1 in self.prio_map:
999 task = self.prio_map[task1] 1078 task = self.prio_map[task1]
1000 bb.msg.debug(3, bb.msg.domain.RunQueue, " (%s)%s - %s: %s Deps %s RevDeps %s" % (task, 1079 bb.msg.debug(3, bb.msg.domain.RunQueue, " (%s)%s - %s: %s Deps %s RevDeps %s" % (task,
@@ -1005,6 +1084,58 @@ class RunQueue:
1005 self.runq_revdeps[task])) 1084 self.runq_revdeps[task]))
1006 1085
1007 1086
1087class TaskFailure(Exception):
1088 """
1089 Exception raised when a task in a runqueue fails
1090 """
1091 def __init__(self, x):
1092 self.args = x
1093
1094
1095class runQueueExitWait(bb.event.Event):
1096 """
1097 Event when waiting for task processes to exit
1098 """
1099
1100 def __init__(self, remain):
1101 self.remain = remain
1102 self.message = "Waiting for %s active tasks to finish" % remain
1103 bb.event.Event.__init__(self)
1104
1105class runQueueEvent(bb.event.Event):
1106 """
1107 Base runQueue event class
1108 """
1109 def __init__(self, task, stats, rq):
1110 self.taskid = task
1111 self.taskstring = rq.get_user_idstring(task)
1112 self.stats = stats
1113 bb.event.Event.__init__(self)
1114
1115class runQueueTaskStarted(runQueueEvent):
1116 """
1117 Event notifing a task was started
1118 """
1119 def __init__(self, task, stats, rq):
1120 runQueueEvent.__init__(self, task, stats, rq)
1121 self.message = "Running task %s (%d of %d) (%s)" % (task, stats.completed + stats.active + 1, self.stats.total, self.taskstring)
1122
1123class runQueueTaskFailed(runQueueEvent):
1124 """
1125 Event notifing a task failed
1126 """
1127 def __init__(self, task, stats, rq):
1128 runQueueEvent.__init__(self, task, stats, rq)
1129 self.message = "Task %s failed (%s)" % (task, self.taskstring)
1130
1131class runQueueTaskCompleted(runQueueEvent):
1132 """
1133 Event notifing a task completed
1134 """
1135 def __init__(self, task, stats, rq):
1136 runQueueEvent.__init__(self, task, stats, rq)
1137 self.message = "Task %s completed (%s)" % (task, self.taskstring)
1138
1008def check_stamp_fn(fn, taskname, d): 1139def check_stamp_fn(fn, taskname, d):
1009 rq = bb.data.getVar("__RUNQUEUE_DO_NOT_USE_EXTERNALLY", d) 1140 rq = bb.data.getVar("__RUNQUEUE_DO_NOT_USE_EXTERNALLY", d)
1010 fnid = rq.taskData.getfn_id(fn) 1141 fnid = rq.taskData.getfn_id(fn)
@@ -1013,3 +1144,31 @@ def check_stamp_fn(fn, taskname, d):
1013 return rq.check_stamp_task(taskid) 1144 return rq.check_stamp_task(taskid)
1014 return None 1145 return None
1015 1146
1147class runQueuePipe():
1148 """
1149 Abstraction for a pipe between a worker thread and the server
1150 """
1151 def __init__(self, pipein, pipeout, d):
1152 self.fd = pipein
1153 os.close(pipeout)
1154 self.queue = ""
1155 self.d = d
1156
1157 def read(self):
1158 start = len(self.queue)
1159 self.queue = self.queue + os.read(self.fd, 1024)
1160 end = len(self.queue)
1161 index = self.queue.find("</event>")
1162 while index != -1:
1163 bb.event.fire_from_worker(self.queue[:index+8], self.d)
1164 self.queue = self.queue[index+8:]
1165 index = self.queue.find("</event>")
1166 return (end > start)
1167
1168 def close(self):
1169 while self.read():
1170 continue
1171 if len(self.queue) > 0:
1172 print "Warning, worker left partial message"
1173 os.close(self.fd)
1174
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 @@
1import xmlrpc
2import 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 @@
1#
2# BitBake 'dummy' Passthrough Server
3#
4# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
5# Copyright (C) 2006 - 2008 Richard Purdie
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20"""
21 This module implements an xmlrpc server for BitBake.
22
23 Use this by deriving a class from BitBakeXMLRPCServer and then adding
24 methods which you want to "export" via XMLRPC. If the methods have the
25 prefix xmlrpc_, then registering those function will happen automatically,
26 if not, you need to call register_function.
27
28 Use register_idle_function() to add a function which the xmlrpc server
29 calls from within server_forever when no requests are pending. Make sure
30 that those functions are non-blocking or else you will introduce latency
31 in the server's main loop.
32"""
33
34import time
35import bb
36from bb.ui import uievent
37import xmlrpclib
38import pickle
39
40DEBUG = False
41
42from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
43import inspect, select
44
45class BitBakeServerCommands():
46 def __init__(self, server, cooker):
47 self.cooker = cooker
48 self.server = server
49
50 def runCommand(self, command):
51 """
52 Run a cooker command on the server
53 """
54 #print "Running Command %s" % command
55 return self.cooker.command.runCommand(command)
56
57 def terminateServer(self):
58 """
59 Trigger the server to quit
60 """
61 self.server.server_exit()
62 #print "Server (cooker) exitting"
63 return
64
65 def ping(self):
66 """
67 Dummy method which can be used to check the server is still alive
68 """
69 return True
70
71eventQueue = []
72
73class BBUIEventQueue:
74 class event:
75 def __init__(self, parent):
76 self.parent = parent
77 @staticmethod
78 def send(event):
79 bb.server.none.eventQueue.append(pickle.loads(event))
80 @staticmethod
81 def quit():
82 return
83
84 def __init__(self, BBServer):
85 self.eventQueue = bb.server.none.eventQueue
86 self.BBServer = BBServer
87 self.EventHandle = bb.event.register_UIHhandler(self)
88
89 def getEvent(self):
90 if len(self.eventQueue) == 0:
91 return None
92
93 return self.eventQueue.pop(0)
94
95 def waitEvent(self, delay):
96 event = self.getEvent()
97 if event:
98 return event
99 self.BBServer.idle_commands(delay)
100 return self.getEvent()
101
102 def queue_event(self, event):
103 self.eventQueue.append(event)
104
105 def system_quit( self ):
106 bb.event.unregister_UIHhandler(self.EventHandle)
107
108class BitBakeServer():
109 # remove this when you're done with debugging
110 # allow_reuse_address = True
111
112 def __init__(self, cooker):
113 self._idlefuns = {}
114 self.commands = BitBakeServerCommands(self, cooker)
115
116 def register_idle_function(self, function, data):
117 """Register a function to be called while the server is idle"""
118 assert callable(function)
119 self._idlefuns[function] = data
120
121 def idle_commands(self, delay):
122 #print "Idle queue length %s" % len(self._idlefuns)
123 #print "Idle timeout, running idle functions"
124 #if len(self._idlefuns) == 0:
125 nextsleep = delay
126 for function, data in self._idlefuns.items():
127 try:
128 retval = function(self, data, False)
129 #print "Idle function returned %s" % (retval)
130 if retval is False:
131 del self._idlefuns[function]
132 elif retval is True:
133 nextsleep = None
134 elif nextsleep is None:
135 continue
136 elif retval < nextsleep:
137 nextsleep = retval
138 except SystemExit:
139 raise
140 except:
141 import traceback
142 traceback.print_exc()
143 pass
144 if nextsleep is not None:
145 #print "Sleeping for %s (%s)" % (nextsleep, delay)
146 time.sleep(nextsleep)
147
148 def server_exit(self):
149 # Tell idle functions we're exiting
150 for function, data in self._idlefuns.items():
151 try:
152 retval = function(self, data, True)
153 except:
154 pass
155
156class BitbakeServerInfo():
157 def __init__(self, server):
158 self.server = server
159 self.commands = server.commands
160
161class BitBakeServerFork():
162 def __init__(self, serverinfo, command, logfile):
163 serverinfo.forkCommand = command
164 serverinfo.logfile = logfile
165
166class BitBakeServerConnection():
167 def __init__(self, serverinfo):
168 self.server = serverinfo.server
169 self.connection = serverinfo.commands
170 self.events = bb.server.none.BBUIEventQueue(self.server)
171
172 def terminate(self):
173 try:
174 self.events.system_quit()
175 except:
176 pass
177 try:
178 self.connection.terminateServer()
179 except:
180 pass
181
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 @@
1#
2# BitBake XMLRPC Server
3#
4# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
5# Copyright (C) 2006 - 2008 Richard Purdie
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20"""
21 This module implements an xmlrpc server for BitBake.
22
23 Use this by deriving a class from BitBakeXMLRPCServer and then adding
24 methods which you want to "export" via XMLRPC. If the methods have the
25 prefix xmlrpc_, then registering those function will happen automatically,
26 if not, you need to call register_function.
27
28 Use register_idle_function() to add a function which the xmlrpc server
29 calls from within server_forever when no requests are pending. Make sure
30 that those functions are non-blocking or else you will introduce latency
31 in the server's main loop.
32"""
33
34import bb
35import xmlrpclib, sys
36from bb import daemonize
37from bb.ui import uievent
38
39DEBUG = False
40
41from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
42import inspect, select
43
44if sys.hexversion < 0x020600F0:
45 print "Sorry, python 2.6 or later is required for bitbake's XMLRPC mode"
46 sys.exit(1)
47
48class BitBakeServerCommands():
49 def __init__(self, server, cooker):
50 self.cooker = cooker
51 self.server = server
52
53 def registerEventHandler(self, host, port):
54 """
55 Register a remote UI Event Handler
56 """
57 s = xmlrpclib.Server("http://%s:%d" % (host, port), allow_none=True)
58 return bb.event.register_UIHhandler(s)
59
60 def unregisterEventHandler(self, handlerNum):
61 """
62 Unregister a remote UI Event Handler
63 """
64 return bb.event.unregister_UIHhandler(handlerNum)
65
66 def runCommand(self, command):
67 """
68 Run a cooker command on the server
69 """
70 return self.cooker.command.runCommand(command)
71
72 def terminateServer(self):
73 """
74 Trigger the server to quit
75 """
76 self.server.quit = True
77 print "Server (cooker) exitting"
78 return
79
80 def ping(self):
81 """
82 Dummy method which can be used to check the server is still alive
83 """
84 return True
85
86class BitBakeServer(SimpleXMLRPCServer):
87 # remove this when you're done with debugging
88 # allow_reuse_address = True
89
90 def __init__(self, cooker, interface = ("localhost", 0)):
91 """
92 Constructor
93 """
94 SimpleXMLRPCServer.__init__(self, interface,
95 requestHandler=SimpleXMLRPCRequestHandler,
96 logRequests=False, allow_none=True)
97 self._idlefuns = {}
98 self.host, self.port = self.socket.getsockname()
99 #self.register_introspection_functions()
100 commands = BitBakeServerCommands(self, cooker)
101 self.autoregister_all_functions(commands, "")
102
103 def autoregister_all_functions(self, context, prefix):
104 """
105 Convenience method for registering all functions in the scope
106 of this class that start with a common prefix
107 """
108 methodlist = inspect.getmembers(context, inspect.ismethod)
109 for name, method in methodlist:
110 if name.startswith(prefix):
111 self.register_function(method, name[len(prefix):])
112
113 def register_idle_function(self, function, data):
114 """Register a function to be called while the server is idle"""
115 assert callable(function)
116 self._idlefuns[function] = data
117
118 def serve_forever(self):
119 """
120 Serve Requests. Overloaded to honor a quit command
121 """
122 self.quit = False
123 self.timeout = 0 # Run Idle calls for our first callback
124 while not self.quit:
125 #print "Idle queue length %s" % len(self._idlefuns)
126 self.handle_request()
127 #print "Idle timeout, running idle functions"
128 nextsleep = None
129 for function, data in self._idlefuns.items():
130 try:
131 retval = function(self, data, False)
132 if retval is False:
133 del self._idlefuns[function]
134 elif retval is True:
135 nextsleep = 0
136 elif nextsleep is 0:
137 continue
138 elif nextsleep is None:
139 nextsleep = retval
140 elif retval < nextsleep:
141 nextsleep = retval
142 except SystemExit:
143 raise
144 except:
145 import traceback
146 traceback.print_exc()
147 pass
148 if nextsleep is None and len(self._idlefuns) > 0:
149 nextsleep = 0
150 self.timeout = nextsleep
151 # Tell idle functions we're exiting
152 for function, data in self._idlefuns.items():
153 try:
154 retval = function(self, data, True)
155 except:
156 pass
157
158 self.server_close()
159 return
160
161class BitbakeServerInfo():
162 def __init__(self, server):
163 self.host = server.host
164 self.port = server.port
165
166class BitBakeServerFork():
167 def __init__(self, serverinfo, command, logfile):
168 daemonize.createDaemon(command, logfile)
169
170class BitBakeServerConnection():
171 def __init__(self, serverinfo):
172 self.connection = xmlrpclib.Server("http://%s:%s" % (serverinfo.host, serverinfo.port), allow_none=True)
173 self.events = uievent.BBUIEventQueue(self.connection)
174
175 def terminate(self):
176 # Don't wait for server indefinitely
177 import socket
178 socket.setdefaulttimeout(2)
179 try:
180 self.events.system_quit()
181 except:
182 pass
183 try:
184 self.connection.terminateServer()
185 except:
186 pass
187
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:
151 if len( names ) == 0: names = [ globexpr ] 151 if len( names ) == 0: names = [ globexpr ]
152 print "SHELL: Building %s" % ' '.join( names ) 152 print "SHELL: Building %s" % ' '.join( names )
153 153
154 oldcmd = cooker.configuration.cmd
155 cooker.configuration.cmd = cmd
156
157 td = taskdata.TaskData(cooker.configuration.abort) 154 td = taskdata.TaskData(cooker.configuration.abort)
158 localdata = data.createCopy(cooker.configuration.data) 155 localdata = data.createCopy(cooker.configuration.data)
159 data.update_data(localdata) 156 data.update_data(localdata)
@@ -168,7 +165,7 @@ class BitBakeShellCommands:
168 if len(providers) == 0: 165 if len(providers) == 0:
169 raise Providers.NoProvider 166 raise Providers.NoProvider
170 167
171 tasks.append([name, "do_%s" % cooker.configuration.cmd]) 168 tasks.append([name, "do_%s" % cmd])
172 169
173 td.add_unresolved(localdata, cooker.status) 170 td.add_unresolved(localdata, cooker.status)
174 171
@@ -189,7 +186,6 @@ class BitBakeShellCommands:
189 print "ERROR: Couldn't build '%s'" % names 186 print "ERROR: Couldn't build '%s'" % names
190 last_exception = e 187 last_exception = e
191 188
192 cooker.configuration.cmd = oldcmd
193 189
194 build.usage = "<providee>" 190 build.usage = "<providee>"
195 191
@@ -208,6 +204,11 @@ class BitBakeShellCommands:
208 self.build( params, "configure" ) 204 self.build( params, "configure" )
209 configure.usage = "<providee>" 205 configure.usage = "<providee>"
210 206
207 def install( self, params ):
208 """Execute 'install' on a providee"""
209 self.build( params, "install" )
210 install.usage = "<providee>"
211
211 def edit( self, params ): 212 def edit( self, params ):
212 """Call $EDITOR on a providee""" 213 """Call $EDITOR on a providee"""
213 name = params[0] 214 name = params[0]
@@ -240,18 +241,14 @@ class BitBakeShellCommands:
240 bf = completeFilePath( name ) 241 bf = completeFilePath( name )
241 print "SHELL: Calling '%s' on '%s'" % ( cmd, bf ) 242 print "SHELL: Calling '%s' on '%s'" % ( cmd, bf )
242 243
243 oldcmd = cooker.configuration.cmd
244 cooker.configuration.cmd = cmd
245
246 try: 244 try:
247 cooker.buildFile(bf) 245 cooker.buildFile(bf, cmd)
248 except parse.ParseError: 246 except parse.ParseError:
249 print "ERROR: Unable to open or parse '%s'" % bf 247 print "ERROR: Unable to open or parse '%s'" % bf
250 except build.EventException, e: 248 except build.EventException, e:
251 print "ERROR: Couldn't build '%s'" % name 249 print "ERROR: Couldn't build '%s'" % name
252 last_exception = e 250 last_exception = e
253 251
254 cooker.configuration.cmd = oldcmd
255 fileBuild.usage = "<bbfile>" 252 fileBuild.usage = "<bbfile>"
256 253
257 def fileClean( self, params ): 254 def fileClean( self, params ):
@@ -493,7 +490,7 @@ SRC_URI = ""
493 interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version ) 490 interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
494 491
495 def showdata( self, params ): 492 def showdata( self, params ):
496 """Show the parsed metadata for a given providee""" 493 """Execute 'showdata' on a providee"""
497 cooker.showEnvironment(None, params) 494 cooker.showEnvironment(None, params)
498 showdata.usage = "<providee>" 495 showdata.usage = "<providee>"
499 496
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
23# with this program; if not, write to the Free Software Foundation, Inc., 23# with this program; if not, write to the Free Software Foundation, Inc.,
24# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 24# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 25
26from bb import data, event, mkdirhier, utils 26import bb
27import bb, os 27
28def re_match_strings(target, strings):
29 """
30 Whether or not the string 'target' matches
31 any one string of the strings which can be regular expression string
32 """
33 import re
34
35 for name in strings:
36 if (name==target or
37 re.search(name,target)!=None):
38 return True
39 return False
28 40
29class TaskData: 41class TaskData:
30 """ 42 """
@@ -264,7 +276,7 @@ class TaskData:
264 """ 276 """
265 unresolved = [] 277 unresolved = []
266 for target in self.build_names_index: 278 for target in self.build_names_index:
267 if target in dataCache.ignored_dependencies: 279 if re_match_strings(target, dataCache.ignored_dependencies):
268 continue 280 continue
269 if self.build_names_index.index(target) in self.failed_deps: 281 if self.build_names_index.index(target) in self.failed_deps:
270 continue 282 continue
@@ -279,7 +291,7 @@ class TaskData:
279 """ 291 """
280 unresolved = [] 292 unresolved = []
281 for target in self.run_names_index: 293 for target in self.run_names_index:
282 if target in dataCache.ignored_dependencies: 294 if re_match_strings(target, dataCache.ignored_dependencies):
283 continue 295 continue
284 if self.run_names_index.index(target) in self.failed_rdeps: 296 if self.run_names_index.index(target) in self.failed_rdeps:
285 continue 297 continue
@@ -359,7 +371,7 @@ class TaskData:
359 added internally during dependency resolution 371 added internally during dependency resolution
360 """ 372 """
361 373
362 if item in dataCache.ignored_dependencies: 374 if re_match_strings(item, dataCache.ignored_dependencies):
363 return 375 return
364 376
365 if not item in dataCache.providers: 377 if not item in dataCache.providers:
@@ -367,7 +379,7 @@ class TaskData:
367 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))) 379 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)))
368 else: 380 else:
369 bb.msg.note(2, bb.msg.domain.Provider, "Nothing PROVIDES '%s'" % (item)) 381 bb.msg.note(2, bb.msg.domain.Provider, "Nothing PROVIDES '%s'" % (item))
370 bb.event.fire(bb.event.NoProvider(item, cfgData)) 382 bb.event.fire(bb.event.NoProvider(item), cfgData)
371 raise bb.providers.NoProvider(item) 383 raise bb.providers.NoProvider(item)
372 384
373 if self.have_build_target(item): 385 if self.have_build_target(item):
@@ -380,7 +392,7 @@ class TaskData:
380 392
381 if not eligible: 393 if not eligible:
382 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))) 394 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)))
383 bb.event.fire(bb.event.NoProvider(item, cfgData)) 395 bb.event.fire(bb.event.NoProvider(item), cfgData)
384 raise bb.providers.NoProvider(item) 396 raise bb.providers.NoProvider(item)
385 397
386 if len(eligible) > 1 and foundUnique == False: 398 if len(eligible) > 1 and foundUnique == False:
@@ -390,7 +402,7 @@ class TaskData:
390 providers_list.append(dataCache.pkg_fn[fn]) 402 providers_list.append(dataCache.pkg_fn[fn])
391 bb.msg.note(1, bb.msg.domain.Provider, "multiple providers are available for %s (%s);" % (item, ", ".join(providers_list))) 403 bb.msg.note(1, bb.msg.domain.Provider, "multiple providers are available for %s (%s);" % (item, ", ".join(providers_list)))
392 bb.msg.note(1, bb.msg.domain.Provider, "consider defining PREFERRED_PROVIDER_%s" % item) 404 bb.msg.note(1, bb.msg.domain.Provider, "consider defining PREFERRED_PROVIDER_%s" % item)
393 bb.event.fire(bb.event.MultipleProviders(item, providers_list, cfgData)) 405 bb.event.fire(bb.event.MultipleProviders(item, providers_list), cfgData)
394 self.consider_msgs_cache.append(item) 406 self.consider_msgs_cache.append(item)
395 407
396 for fn in eligible: 408 for fn in eligible:
@@ -410,7 +422,7 @@ class TaskData:
410 (takes item names from RDEPENDS/PACKAGES namespace) 422 (takes item names from RDEPENDS/PACKAGES namespace)
411 """ 423 """
412 424
413 if item in dataCache.ignored_dependencies: 425 if re_match_strings(item, dataCache.ignored_dependencies):
414 return 426 return
415 427
416 if self.have_runtime_target(item): 428 if self.have_runtime_target(item):
@@ -420,7 +432,7 @@ class TaskData:
420 432
421 if not all_p: 433 if not all_p:
422 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)) 434 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))
423 bb.event.fire(bb.event.NoProvider(item, cfgData, runtime=True)) 435 bb.event.fire(bb.event.NoProvider(item, runtime=True), cfgData)
424 raise bb.providers.NoRProvider(item) 436 raise bb.providers.NoRProvider(item)
425 437
426 eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache) 438 eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache)
@@ -428,7 +440,7 @@ class TaskData:
428 440
429 if not eligible: 441 if not eligible:
430 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)) 442 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))
431 bb.event.fire(bb.event.NoProvider(item, cfgData, runtime=True)) 443 bb.event.fire(bb.event.NoProvider(item, runtime=True), cfgData)
432 raise bb.providers.NoRProvider(item) 444 raise bb.providers.NoRProvider(item)
433 445
434 if len(eligible) > 1 and numberPreferred == 0: 446 if len(eligible) > 1 and numberPreferred == 0:
@@ -438,7 +450,7 @@ class TaskData:
438 providers_list.append(dataCache.pkg_fn[fn]) 450 providers_list.append(dataCache.pkg_fn[fn])
439 bb.msg.note(2, bb.msg.domain.Provider, "multiple providers are available for runtime %s (%s);" % (item, ", ".join(providers_list))) 451 bb.msg.note(2, bb.msg.domain.Provider, "multiple providers are available for runtime %s (%s);" % (item, ", ".join(providers_list)))
440 bb.msg.note(2, bb.msg.domain.Provider, "consider defining a PREFERRED_PROVIDER entry to match runtime %s" % item) 452 bb.msg.note(2, bb.msg.domain.Provider, "consider defining a PREFERRED_PROVIDER entry to match runtime %s" % item)
441 bb.event.fire(bb.event.MultipleProviders(item,providers_list, cfgData, runtime=True)) 453 bb.event.fire(bb.event.MultipleProviders(item,providers_list, runtime=True), cfgData)
442 self.consider_msgs_cache.append(item) 454 self.consider_msgs_cache.append(item)
443 455
444 if numberPreferred > 1: 456 if numberPreferred > 1:
@@ -448,7 +460,7 @@ class TaskData:
448 providers_list.append(dataCache.pkg_fn[fn]) 460 providers_list.append(dataCache.pkg_fn[fn])
449 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))) 461 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)))
450 bb.msg.note(2, bb.msg.domain.Provider, "consider defining only one PREFERRED_PROVIDER entry to match runtime %s" % item) 462 bb.msg.note(2, bb.msg.domain.Provider, "consider defining only one PREFERRED_PROVIDER entry to match runtime %s" % item)
451 bb.event.fire(bb.event.MultipleProviders(item,providers_list, cfgData, runtime=True)) 463 bb.event.fire(bb.event.MultipleProviders(item,providers_list, runtime=True), cfgData)
452 self.consider_msgs_cache.append(item) 464 self.consider_msgs_cache.append(item)
453 465
454 # run through the list until we find one that we can build 466 # 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 @@
1#
2# BitBake UI Implementation
3#
4# Copyright (C) 2006-2007 Richard Purdie
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License version 2 as
8# published by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
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 @@
1#
2# BitBake UI Implementation
3#
4# Copyright (C) 2006-2007 Richard Purdie
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License version 2 as
8# published by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
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 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2008 Intel Corporation
5#
6# Authored by Rob Bradford <rob@linux.intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23import threading
24import os
25import datetime
26import time
27
28class BuildConfiguration:
29 """ Represents a potential *or* historic *or* concrete build. It
30 encompasses all the things that we need to tell bitbake to do to make it
31 build what we want it to build.
32
33 It also stored the metadata URL and the set of possible machines (and the
34 distros / images / uris for these. Apart from the metdata URL these are
35 not serialised to file (since they may be transient). In some ways this
36 functionality might be shifted to the loader class."""
37
38 def __init__ (self):
39 self.metadata_url = None
40
41 # Tuple of (distros, image, urls)
42 self.machine_options = {}
43
44 self.machine = None
45 self.distro = None
46 self.image = None
47 self.urls = []
48 self.extra_urls = []
49 self.extra_pkgs = []
50
51 def get_machines_model (self):
52 model = gtk.ListStore (gobject.TYPE_STRING)
53 for machine in self.machine_options.keys():
54 model.append ([machine])
55
56 return model
57
58 def get_distro_and_images_models (self, machine):
59 distro_model = gtk.ListStore (gobject.TYPE_STRING)
60
61 for distro in self.machine_options[machine][0]:
62 distro_model.append ([distro])
63
64 image_model = gtk.ListStore (gobject.TYPE_STRING)
65
66 for image in self.machine_options[machine][1]:
67 image_model.append ([image])
68
69 return (distro_model, image_model)
70
71 def get_repos (self):
72 self.urls = self.machine_options[self.machine][2]
73 return self.urls
74
75 # It might be a lot lot better if we stored these in like, bitbake conf
76 # file format.
77 @staticmethod
78 def load_from_file (filename):
79 f = open (filename, "r")
80
81 conf = BuildConfiguration()
82 for line in f.readlines():
83 data = line.split (";")[1]
84 if (line.startswith ("metadata-url;")):
85 conf.metadata_url = data.strip()
86 continue
87 if (line.startswith ("url;")):
88 conf.urls += [data.strip()]
89 continue
90 if (line.startswith ("extra-url;")):
91 conf.extra_urls += [data.strip()]
92 continue
93 if (line.startswith ("machine;")):
94 conf.machine = data.strip()
95 continue
96 if (line.startswith ("distribution;")):
97 conf.distro = data.strip()
98 continue
99 if (line.startswith ("image;")):
100 conf.image = data.strip()
101 continue
102
103 f.close ()
104 return conf
105
106 # Serialise to a file. This is part of the build process and we use this
107 # to be able to repeat a given build (using the same set of parameters)
108 # but also so that we can include the details of the image / machine /
109 # distro in the build manager tree view.
110 def write_to_file (self, filename):
111 f = open (filename, "w")
112
113 lines = []
114
115 if (self.metadata_url):
116 lines += ["metadata-url;%s\n" % (self.metadata_url)]
117
118 for url in self.urls:
119 lines += ["url;%s\n" % (url)]
120
121 for url in self.extra_urls:
122 lines += ["extra-url;%s\n" % (url)]
123
124 if (self.machine):
125 lines += ["machine;%s\n" % (self.machine)]
126
127 if (self.distro):
128 lines += ["distribution;%s\n" % (self.distro)]
129
130 if (self.image):
131 lines += ["image;%s\n" % (self.image)]
132
133 f.writelines (lines)
134 f.close ()
135
136class BuildResult(gobject.GObject):
137 """ Represents an historic build. Perhaps not successful. But it includes
138 things such as the files that are in the directory (the output from the
139 build) as well as a deserialised BuildConfiguration file that is stored in
140 ".conf" in the directory for the build.
141
142 This is GObject so that it can be included in the TreeStore."""
143
144 (STATE_COMPLETE, STATE_FAILED, STATE_ONGOING) = \
145 (0, 1, 2)
146
147 def __init__ (self, parent, identifier):
148 gobject.GObject.__init__ (self)
149 self.date = None
150
151 self.files = []
152 self.status = None
153 self.identifier = identifier
154 self.path = os.path.join (parent, identifier)
155
156 # Extract the date, since the directory name is of the
157 # format build-<year><month><day>-<ordinal> we can easily
158 # pull it out.
159 # TODO: Better to stat a file?
160 (_ , date, revision) = identifier.split ("-")
161 print date
162
163 year = int (date[0:4])
164 month = int (date[4:6])
165 day = int (date[6:8])
166
167 self.date = datetime.date (year, month, day)
168
169 self.conf = None
170
171 # By default builds are STATE_FAILED unless we find a "complete" file
172 # in which case they are STATE_COMPLETE
173 self.state = BuildResult.STATE_FAILED
174 for file in os.listdir (self.path):
175 if (file.startswith (".conf")):
176 conffile = os.path.join (self.path, file)
177 self.conf = BuildConfiguration.load_from_file (conffile)
178 elif (file.startswith ("complete")):
179 self.state = BuildResult.STATE_COMPLETE
180 else:
181 self.add_file (file)
182
183 def add_file (self, file):
184 # Just add the file for now. Don't care about the type.
185 self.files += [(file, None)]
186
187class BuildManagerModel (gtk.TreeStore):
188 """ Model for the BuildManagerTreeView. This derives from gtk.TreeStore
189 but it abstracts nicely what the columns mean and the setup of the columns
190 in the model. """
191
192 (COL_IDENT, COL_DESC, COL_MACHINE, COL_DISTRO, COL_BUILD_RESULT, COL_DATE, COL_STATE) = \
193 (0, 1, 2, 3, 4, 5, 6)
194
195 def __init__ (self):
196 gtk.TreeStore.__init__ (self,
197 gobject.TYPE_STRING,
198 gobject.TYPE_STRING,
199 gobject.TYPE_STRING,
200 gobject.TYPE_STRING,
201 gobject.TYPE_OBJECT,
202 gobject.TYPE_INT64,
203 gobject.TYPE_INT)
204
205class BuildManager (gobject.GObject):
206 """ This class manages the historic builds that have been found in the
207 "results" directory but is also used for starting a new build."""
208
209 __gsignals__ = {
210 'population-finished' : (gobject.SIGNAL_RUN_LAST,
211 gobject.TYPE_NONE,
212 ()),
213 'populate-error' : (gobject.SIGNAL_RUN_LAST,
214 gobject.TYPE_NONE,
215 ())
216 }
217
218 def update_build_result (self, result, iter):
219 # Convert the date into something we can sort by.
220 date = long (time.mktime (result.date.timetuple()))
221
222 # Add a top level entry for the build
223
224 self.model.set (iter,
225 BuildManagerModel.COL_IDENT, result.identifier,
226 BuildManagerModel.COL_DESC, result.conf.image,
227 BuildManagerModel.COL_MACHINE, result.conf.machine,
228 BuildManagerModel.COL_DISTRO, result.conf.distro,
229 BuildManagerModel.COL_BUILD_RESULT, result,
230 BuildManagerModel.COL_DATE, date,
231 BuildManagerModel.COL_STATE, result.state)
232
233 # And then we use the files in the directory as the children for the
234 # top level iter.
235 for file in result.files:
236 self.model.append (iter, (None, file[0], None, None, None, date, -1))
237
238 # This function is called as an idle by the BuildManagerPopulaterThread
239 def add_build_result (self, result):
240 gtk.gdk.threads_enter()
241 self.known_builds += [result]
242
243 self.update_build_result (result, self.model.append (None))
244
245 gtk.gdk.threads_leave()
246
247 def notify_build_finished (self):
248 # This is a bit of a hack. If we have a running build running then we
249 # will have a row in the model in STATE_ONGOING. Find it and make it
250 # as if it was a proper historic build (well, it is completed now....)
251
252 # We need to use the iters here rather than the Python iterator
253 # interface to the model since we need to pass it into
254 # update_build_result
255
256 iter = self.model.get_iter_first()
257
258 while (iter):
259 (ident, state) = self.model.get(iter,
260 BuildManagerModel.COL_IDENT,
261 BuildManagerModel.COL_STATE)
262
263 if state == BuildResult.STATE_ONGOING:
264 result = BuildResult (self.results_directory, ident)
265 self.update_build_result (result, iter)
266 iter = self.model.iter_next(iter)
267
268 def notify_build_succeeded (self):
269 # Write the "complete" file so that when we create the BuildResult
270 # object we put into the model
271
272 complete_file_path = os.path.join (self.cur_build_directory, "complete")
273 f = file (complete_file_path, "w")
274 f.close()
275 self.notify_build_finished()
276
277 def notify_build_failed (self):
278 # Without a "complete" file then this will mark the build as failed:
279 self.notify_build_finished()
280
281 # This function is called as an idle
282 def emit_population_finished_signal (self):
283 gtk.gdk.threads_enter()
284 self.emit ("population-finished")
285 gtk.gdk.threads_leave()
286
287 class BuildManagerPopulaterThread (threading.Thread):
288 def __init__ (self, manager, directory):
289 threading.Thread.__init__ (self)
290 self.manager = manager
291 self.directory = directory
292
293 def run (self):
294 # For each of the "build-<...>" directories ..
295
296 if os.path.exists (self.directory):
297 for directory in os.listdir (self.directory):
298
299 if not directory.startswith ("build-"):
300 continue
301
302 build_result = BuildResult (self.directory, directory)
303 self.manager.add_build_result (build_result)
304
305 gobject.idle_add (BuildManager.emit_population_finished_signal,
306 self.manager)
307
308 def __init__ (self, server, results_directory):
309 gobject.GObject.__init__ (self)
310
311 # The builds that we've found from walking the result directory
312 self.known_builds = []
313
314 # Save out the bitbake server, we need this for issuing commands to
315 # the cooker:
316 self.server = server
317
318 # The TreeStore that we use
319 self.model = BuildManagerModel ()
320
321 # The results directory is where we create (and look for) the
322 # build-<xyz>-<n> directories. We need to populate ourselves from
323 # directory
324 self.results_directory = results_directory
325 self.populate_from_directory (self.results_directory)
326
327 def populate_from_directory (self, directory):
328 thread = BuildManager.BuildManagerPopulaterThread (self, directory)
329 thread.start()
330
331 # Come up with the name for the next build ident by combining "build-"
332 # with the date formatted as yyyymmdd and then an ordinal. We do this by
333 # an optimistic algorithm incrementing the ordinal if we find that it
334 # already exists.
335 def get_next_build_ident (self):
336 today = datetime.date.today ()
337 datestr = str (today.year) + str (today.month) + str (today.day)
338
339 revision = 0
340 test_name = "build-%s-%d" % (datestr, revision)
341 test_path = os.path.join (self.results_directory, test_name)
342
343 while (os.path.exists (test_path)):
344 revision += 1
345 test_name = "build-%s-%d" % (datestr, revision)
346 test_path = os.path.join (self.results_directory, test_name)
347
348 return test_name
349
350 # Take a BuildConfiguration and then try and build it based on the
351 # parameters of that configuration. S
352 def do_build (self, conf):
353 server = self.server
354
355 # Work out the build directory. Note we actually create the
356 # directories here since we need to write the ".conf" file. Otherwise
357 # we could have relied on bitbake's builder thread to actually make
358 # the directories as it proceeds with the build.
359 ident = self.get_next_build_ident ()
360 build_directory = os.path.join (self.results_directory,
361 ident)
362 self.cur_build_directory = build_directory
363 os.makedirs (build_directory)
364
365 conffile = os.path.join (build_directory, ".conf")
366 conf.write_to_file (conffile)
367
368 # Add a row to the model representing this ongoing build. It's kinda a
369 # fake entry. If this build completes or fails then this gets updated
370 # with the real stuff like the historic builds
371 date = long (time.time())
372 self.model.append (None, (ident, conf.image, conf.machine, conf.distro,
373 None, date, BuildResult.STATE_ONGOING))
374 try:
375 server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1])
376 server.runCommand(["setVariable", "MACHINE", conf.machine])
377 server.runCommand(["setVariable", "DISTRO", conf.distro])
378 server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"])
379 server.runCommand(["setVariable", "BBFILES", \
380 """${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""])
381 server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"])
382 server.runCommand(["setVariable", "IPK_FEED_URIS", \
383 " ".join(conf.get_repos())])
384 server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE",
385 build_directory])
386 server.runCommand(["buildTargets", [conf.image], "rootfs"])
387
388 except Exception, e:
389 print e
390
391class BuildManagerTreeView (gtk.TreeView):
392 """ The tree view for the build manager. This shows the historic builds
393 and so forth. """
394
395 # We use this function to control what goes in the cell since we store
396 # the date in the model as seconds since the epoch (for sorting) and so we
397 # need to make it human readable.
398 def date_format_custom_cell_data_func (self, col, cell, model, iter):
399 date = model.get (iter, BuildManagerModel.COL_DATE)[0]
400 datestr = time.strftime("%A %d %B %Y", time.localtime(date))
401 cell.set_property ("text", datestr)
402
403 # This format function controls what goes in the cell. We use this to map
404 # the integer state to a string and also to colourise the text
405 def state_format_custom_cell_data_fun (self, col, cell, model, iter):
406 state = model.get (iter, BuildManagerModel.COL_STATE)[0]
407
408 if (state == BuildResult.STATE_ONGOING):
409 cell.set_property ("text", "Active")
410 cell.set_property ("foreground", "#000000")
411 elif (state == BuildResult.STATE_FAILED):
412 cell.set_property ("text", "Failed")
413 cell.set_property ("foreground", "#ff0000")
414 elif (state == BuildResult.STATE_COMPLETE):
415 cell.set_property ("text", "Complete")
416 cell.set_property ("foreground", "#00ff00")
417 else:
418 cell.set_property ("text", "")
419
420 def __init__ (self):
421 gtk.TreeView.__init__(self)
422
423 # Misc descriptiony thing
424 renderer = gtk.CellRendererText ()
425 col = gtk.TreeViewColumn (None, renderer,
426 text=BuildManagerModel.COL_DESC)
427 self.append_column (col)
428
429 # Machine
430 renderer = gtk.CellRendererText ()
431 col = gtk.TreeViewColumn ("Machine", renderer,
432 text=BuildManagerModel.COL_MACHINE)
433 self.append_column (col)
434
435 # distro
436 renderer = gtk.CellRendererText ()
437 col = gtk.TreeViewColumn ("Distribution", renderer,
438 text=BuildManagerModel.COL_DISTRO)
439 self.append_column (col)
440
441 # date (using a custom function for formatting the cell contents it
442 # takes epoch -> human readable string)
443 renderer = gtk.CellRendererText ()
444 col = gtk.TreeViewColumn ("Date", renderer,
445 text=BuildManagerModel.COL_DATE)
446 self.append_column (col)
447 col.set_cell_data_func (renderer,
448 self.date_format_custom_cell_data_func)
449
450 # For status.
451 renderer = gtk.CellRendererText ()
452 col = gtk.TreeViewColumn ("Status", renderer,
453 text = BuildManagerModel.COL_STATE)
454 self.append_column (col)
455 col.set_cell_data_func (renderer,
456 self.state_format_custom_cell_data_fun)
457
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 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
3<!--Generated with glade3 3.4.5 on Mon Nov 10 12:24:12 2008 -->
4<glade-interface>
5 <widget class="GtkDialog" id="build_dialog">
6 <property name="title" translatable="yes">Start a build</property>
7 <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
8 <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
9 <property name="has_separator">False</property>
10 <child internal-child="vbox">
11 <widget class="GtkVBox" id="dialog-vbox1">
12 <property name="visible">True</property>
13 <property name="spacing">2</property>
14 <child>
15 <widget class="GtkTable" id="build_table">
16 <property name="visible">True</property>
17 <property name="border_width">6</property>
18 <property name="n_rows">7</property>
19 <property name="n_columns">3</property>
20 <property name="column_spacing">5</property>
21 <property name="row_spacing">6</property>
22 <child>
23 <widget class="GtkAlignment" id="status_alignment">
24 <property name="visible">True</property>
25 <property name="left_padding">12</property>
26 <child>
27 <widget class="GtkHBox" id="status_hbox">
28 <property name="spacing">6</property>
29 <child>
30 <widget class="GtkImage" id="status_image">
31 <property name="visible">True</property>
32 <property name="no_show_all">True</property>
33 <property name="xalign">0</property>
34 <property name="stock">gtk-dialog-error</property>
35 </widget>
36 <packing>
37 <property name="expand">False</property>
38 <property name="fill">False</property>
39 </packing>
40 </child>
41 <child>
42 <widget class="GtkLabel" id="status_label">
43 <property name="visible">True</property>
44 <property name="xalign">0</property>
45 <property name="label" translatable="yes">If you see this text something is wrong...</property>
46 <property name="use_markup">True</property>
47 <property name="use_underline">True</property>
48 </widget>
49 <packing>
50 <property name="position">1</property>
51 </packing>
52 </child>
53 </widget>
54 </child>
55 </widget>
56 <packing>
57 <property name="right_attach">3</property>
58 <property name="top_attach">2</property>
59 <property name="bottom_attach">3</property>
60 </packing>
61 </child>
62 <child>
63 <widget class="GtkLabel" id="label2">
64 <property name="visible">True</property>
65 <property name="xalign">0</property>
66 <property name="label" translatable="yes">&lt;b&gt;Build configuration&lt;/b&gt;</property>
67 <property name="use_markup">True</property>
68 </widget>
69 <packing>
70 <property name="right_attach">3</property>
71 <property name="top_attach">3</property>
72 <property name="bottom_attach">4</property>
73 <property name="y_options"></property>
74 </packing>
75 </child>
76 <child>
77 <widget class="GtkComboBox" id="image_combo">
78 <property name="visible">True</property>
79 <property name="sensitive">False</property>
80 </widget>
81 <packing>
82 <property name="left_attach">1</property>
83 <property name="right_attach">2</property>
84 <property name="top_attach">6</property>
85 <property name="bottom_attach">7</property>
86 <property name="y_options"></property>
87 </packing>
88 </child>
89 <child>
90 <widget class="GtkLabel" id="image_label">
91 <property name="visible">True</property>
92 <property name="sensitive">False</property>
93 <property name="xalign">0</property>
94 <property name="xpad">12</property>
95 <property name="label" translatable="yes">Image:</property>
96 </widget>
97 <packing>
98 <property name="top_attach">6</property>
99 <property name="bottom_attach">7</property>
100 <property name="y_options"></property>
101 </packing>
102 </child>
103 <child>
104 <widget class="GtkComboBox" id="distribution_combo">
105 <property name="visible">True</property>
106 <property name="sensitive">False</property>
107 </widget>
108 <packing>
109 <property name="left_attach">1</property>
110 <property name="right_attach">2</property>
111 <property name="top_attach">5</property>
112 <property name="bottom_attach">6</property>
113 <property name="y_options"></property>
114 </packing>
115 </child>
116 <child>
117 <widget class="GtkLabel" id="distribution_label">
118 <property name="visible">True</property>
119 <property name="sensitive">False</property>
120 <property name="xalign">0</property>
121 <property name="xpad">12</property>
122 <property name="label" translatable="yes">Distribution:</property>
123 </widget>
124 <packing>
125 <property name="top_attach">5</property>
126 <property name="bottom_attach">6</property>
127 <property name="y_options"></property>
128 </packing>
129 </child>
130 <child>
131 <widget class="GtkComboBox" id="machine_combo">
132 <property name="visible">True</property>
133 <property name="sensitive">False</property>
134 </widget>
135 <packing>
136 <property name="left_attach">1</property>
137 <property name="right_attach">2</property>
138 <property name="top_attach">4</property>
139 <property name="bottom_attach">5</property>
140 <property name="y_options"></property>
141 </packing>
142 </child>
143 <child>
144 <widget class="GtkLabel" id="machine_label">
145 <property name="visible">True</property>
146 <property name="sensitive">False</property>
147 <property name="xalign">0</property>
148 <property name="xpad">12</property>
149 <property name="label" translatable="yes">Machine:</property>
150 </widget>
151 <packing>
152 <property name="top_attach">4</property>
153 <property name="bottom_attach">5</property>
154 <property name="y_options"></property>
155 </packing>
156 </child>
157 <child>
158 <widget class="GtkButton" id="refresh_button">
159 <property name="visible">True</property>
160 <property name="sensitive">False</property>
161 <property name="can_focus">True</property>
162 <property name="receives_default">True</property>
163 <property name="label" translatable="yes">gtk-refresh</property>
164 <property name="use_stock">True</property>
165 <property name="response_id">0</property>
166 </widget>
167 <packing>
168 <property name="left_attach">2</property>
169 <property name="right_attach">3</property>
170 <property name="top_attach">1</property>
171 <property name="bottom_attach">2</property>
172 <property name="y_options"></property>
173 </packing>
174 </child>
175 <child>
176 <widget class="GtkEntry" id="location_entry">
177 <property name="visible">True</property>
178 <property name="can_focus">True</property>
179 <property name="width_chars">32</property>
180 </widget>
181 <packing>
182 <property name="left_attach">1</property>
183 <property name="right_attach">2</property>
184 <property name="top_attach">1</property>
185 <property name="bottom_attach">2</property>
186 <property name="y_options"></property>
187 </packing>
188 </child>
189 <child>
190 <widget class="GtkLabel" id="label3">
191 <property name="visible">True</property>
192 <property name="xalign">0</property>
193 <property name="xpad">12</property>
194 <property name="label" translatable="yes">Location:</property>
195 </widget>
196 <packing>
197 <property name="top_attach">1</property>
198 <property name="bottom_attach">2</property>
199 <property name="y_options"></property>
200 </packing>
201 </child>
202 <child>
203 <widget class="GtkLabel" id="label1">
204 <property name="visible">True</property>
205 <property name="xalign">0</property>
206 <property name="label" translatable="yes">&lt;b&gt;Repository&lt;/b&gt;</property>
207 <property name="use_markup">True</property>
208 </widget>
209 <packing>
210 <property name="right_attach">3</property>
211 <property name="y_options"></property>
212 </packing>
213 </child>
214 <child>
215 <widget class="GtkAlignment" id="alignment1">
216 <property name="visible">True</property>
217 <child>
218 <placeholder/>
219 </child>
220 </widget>
221 <packing>
222 <property name="left_attach">2</property>
223 <property name="right_attach">3</property>
224 <property name="top_attach">4</property>
225 <property name="bottom_attach">5</property>
226 <property name="y_options"></property>
227 </packing>
228 </child>
229 <child>
230 <widget class="GtkAlignment" id="alignment2">
231 <property name="visible">True</property>
232 <child>
233 <placeholder/>
234 </child>
235 </widget>
236 <packing>
237 <property name="left_attach">2</property>
238 <property name="right_attach">3</property>
239 <property name="top_attach">5</property>
240 <property name="bottom_attach">6</property>
241 <property name="y_options"></property>
242 </packing>
243 </child>
244 <child>
245 <widget class="GtkAlignment" id="alignment3">
246 <property name="visible">True</property>
247 <child>
248 <placeholder/>
249 </child>
250 </widget>
251 <packing>
252 <property name="left_attach">2</property>
253 <property name="right_attach">3</property>
254 <property name="top_attach">6</property>
255 <property name="bottom_attach">7</property>
256 <property name="y_options"></property>
257 </packing>
258 </child>
259 </widget>
260 <packing>
261 <property name="position">1</property>
262 </packing>
263 </child>
264 <child internal-child="action_area">
265 <widget class="GtkHButtonBox" id="dialog-action_area1">
266 <property name="visible">True</property>
267 <property name="layout_style">GTK_BUTTONBOX_END</property>
268 <child>
269 <placeholder/>
270 </child>
271 <child>
272 <placeholder/>
273 </child>
274 <child>
275 <placeholder/>
276 </child>
277 </widget>
278 <packing>
279 <property name="expand">False</property>
280 <property name="pack_type">GTK_PACK_END</property>
281 </packing>
282 </child>
283 </widget>
284 </child>
285 </widget>
286 <widget class="GtkDialog" id="dialog2">
287 <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
288 <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
289 <property name="has_separator">False</property>
290 <child internal-child="vbox">
291 <widget class="GtkVBox" id="dialog-vbox2">
292 <property name="visible">True</property>
293 <property name="spacing">2</property>
294 <child>
295 <widget class="GtkTable" id="table2">
296 <property name="visible">True</property>
297 <property name="border_width">6</property>
298 <property name="n_rows">7</property>
299 <property name="n_columns">3</property>
300 <property name="column_spacing">6</property>
301 <property name="row_spacing">6</property>
302 <child>
303 <widget class="GtkLabel" id="label7">
304 <property name="visible">True</property>
305 <property name="xalign">0</property>
306 <property name="label" translatable="yes">&lt;b&gt;Repositories&lt;/b&gt;</property>
307 <property name="use_markup">True</property>
308 </widget>
309 <packing>
310 <property name="right_attach">3</property>
311 <property name="y_options"></property>
312 </packing>
313 </child>
314 <child>
315 <widget class="GtkAlignment" id="alignment4">
316 <property name="visible">True</property>
317 <property name="xalign">0</property>
318 <property name="left_padding">12</property>
319 <child>
320 <widget class="GtkScrolledWindow" id="scrolledwindow1">
321 <property name="visible">True</property>
322 <property name="can_focus">True</property>
323 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
324 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
325 <child>
326 <widget class="GtkTreeView" id="treeview1">
327 <property name="visible">True</property>
328 <property name="can_focus">True</property>
329 <property name="headers_clickable">True</property>
330 </widget>
331 </child>
332 </widget>
333 </child>
334 </widget>
335 <packing>
336 <property name="right_attach">3</property>
337 <property name="top_attach">2</property>
338 <property name="bottom_attach">3</property>
339 <property name="y_options"></property>
340 </packing>
341 </child>
342 <child>
343 <widget class="GtkEntry" id="entry1">
344 <property name="visible">True</property>
345 <property name="can_focus">True</property>
346 </widget>
347 <packing>
348 <property name="left_attach">1</property>
349 <property name="right_attach">3</property>
350 <property name="top_attach">1</property>
351 <property name="bottom_attach">2</property>
352 <property name="y_options"></property>
353 </packing>
354 </child>
355 <child>
356 <widget class="GtkLabel" id="label9">
357 <property name="visible">True</property>
358 <property name="xalign">0</property>
359 <property name="label" translatable="yes">&lt;b&gt;Additional packages&lt;/b&gt;</property>
360 <property name="use_markup">True</property>
361 </widget>
362 <packing>
363 <property name="right_attach">3</property>
364 <property name="top_attach">4</property>
365 <property name="bottom_attach">5</property>
366 <property name="y_options"></property>
367 </packing>
368 </child>
369 <child>
370 <widget class="GtkAlignment" id="alignment6">
371 <property name="visible">True</property>
372 <property name="xalign">0</property>
373 <property name="xscale">0</property>
374 <child>
375 <widget class="GtkLabel" id="label8">
376 <property name="visible">True</property>
377 <property name="xalign">0</property>
378 <property name="yalign">0</property>
379 <property name="xpad">12</property>
380 <property name="label" translatable="yes">Location: </property>
381 </widget>
382 </child>
383 </widget>
384 <packing>
385 <property name="top_attach">1</property>
386 <property name="bottom_attach">2</property>
387 <property name="y_options"></property>
388 </packing>
389 </child>
390 <child>
391 <widget class="GtkAlignment" id="alignment7">
392 <property name="visible">True</property>
393 <property name="xalign">1</property>
394 <property name="xscale">0</property>
395 <child>
396 <widget class="GtkHButtonBox" id="hbuttonbox1">
397 <property name="visible">True</property>
398 <property name="spacing">5</property>
399 <child>
400 <widget class="GtkButton" id="button7">
401 <property name="visible">True</property>
402 <property name="can_focus">True</property>
403 <property name="receives_default">True</property>
404 <property name="label" translatable="yes">gtk-remove</property>
405 <property name="use_stock">True</property>
406 <property name="response_id">0</property>
407 </widget>
408 </child>
409 <child>
410 <widget class="GtkButton" id="button6">
411 <property name="visible">True</property>
412 <property name="can_focus">True</property>
413 <property name="receives_default">True</property>
414 <property name="label" translatable="yes">gtk-edit</property>
415 <property name="use_stock">True</property>
416 <property name="response_id">0</property>
417 </widget>
418 <packing>
419 <property name="position">1</property>
420 </packing>
421 </child>
422 <child>
423 <widget class="GtkButton" id="button5">
424 <property name="visible">True</property>
425 <property name="can_focus">True</property>
426 <property name="receives_default">True</property>
427 <property name="label" translatable="yes">gtk-add</property>
428 <property name="use_stock">True</property>
429 <property name="response_id">0</property>
430 </widget>
431 <packing>
432 <property name="position">2</property>
433 </packing>
434 </child>
435 </widget>
436 </child>
437 </widget>
438 <packing>
439 <property name="left_attach">1</property>
440 <property name="right_attach">3</property>
441 <property name="top_attach">3</property>
442 <property name="bottom_attach">4</property>
443 <property name="y_options"></property>
444 </packing>
445 </child>
446 <child>
447 <widget class="GtkAlignment" id="alignment5">
448 <property name="visible">True</property>
449 <child>
450 <placeholder/>
451 </child>
452 </widget>
453 <packing>
454 <property name="top_attach">3</property>
455 <property name="bottom_attach">4</property>
456 <property name="y_options"></property>
457 </packing>
458 </child>
459 <child>
460 <widget class="GtkLabel" id="label10">
461 <property name="visible">True</property>
462 <property name="xalign">0</property>
463 <property name="yalign">0</property>
464 <property name="xpad">12</property>
465 <property name="label" translatable="yes">Search:</property>
466 </widget>
467 <packing>
468 <property name="top_attach">5</property>
469 <property name="bottom_attach">6</property>
470 <property name="y_options"></property>
471 </packing>
472 </child>
473 <child>
474 <widget class="GtkEntry" id="entry2">
475 <property name="visible">True</property>
476 <property name="can_focus">True</property>
477 </widget>
478 <packing>
479 <property name="left_attach">1</property>
480 <property name="right_attach">3</property>
481 <property name="top_attach">5</property>
482 <property name="bottom_attach">6</property>
483 <property name="y_options"></property>
484 </packing>
485 </child>
486 <child>
487 <widget class="GtkAlignment" id="alignment8">
488 <property name="visible">True</property>
489 <property name="xalign">0</property>
490 <property name="left_padding">12</property>
491 <child>
492 <widget class="GtkScrolledWindow" id="scrolledwindow2">
493 <property name="visible">True</property>
494 <property name="can_focus">True</property>
495 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
496 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
497 <child>
498 <widget class="GtkTreeView" id="treeview2">
499 <property name="visible">True</property>
500 <property name="can_focus">True</property>
501 <property name="headers_clickable">True</property>
502 </widget>
503 </child>
504 </widget>
505 </child>
506 </widget>
507 <packing>
508 <property name="right_attach">3</property>
509 <property name="top_attach">6</property>
510 <property name="bottom_attach">7</property>
511 <property name="y_options"></property>
512 </packing>
513 </child>
514 </widget>
515 <packing>
516 <property name="position">1</property>
517 </packing>
518 </child>
519 <child internal-child="action_area">
520 <widget class="GtkHButtonBox" id="dialog-action_area2">
521 <property name="visible">True</property>
522 <property name="layout_style">GTK_BUTTONBOX_END</property>
523 <child>
524 <widget class="GtkButton" id="button4">
525 <property name="visible">True</property>
526 <property name="can_focus">True</property>
527 <property name="receives_default">True</property>
528 <property name="label" translatable="yes">gtk-close</property>
529 <property name="use_stock">True</property>
530 <property name="response_id">0</property>
531 </widget>
532 </child>
533 </widget>
534 <packing>
535 <property name="expand">False</property>
536 <property name="pack_type">GTK_PACK_END</property>
537 </packing>
538 </child>
539 </widget>
540 </child>
541 </widget>
542 <widget class="GtkWindow" id="main_window">
543 <child>
544 <widget class="GtkVBox" id="main_window_vbox">
545 <property name="visible">True</property>
546 <child>
547 <widget class="GtkToolbar" id="main_toolbar">
548 <property name="visible">True</property>
549 <child>
550 <widget class="GtkToolButton" id="main_toolbutton_build">
551 <property name="visible">True</property>
552 <property name="label" translatable="yes">Build</property>
553 <property name="stock_id">gtk-execute</property>
554 </widget>
555 <packing>
556 <property name="expand">False</property>
557 </packing>
558 </child>
559 </widget>
560 <packing>
561 <property name="expand">False</property>
562 </packing>
563 </child>
564 <child>
565 <widget class="GtkVPaned" id="vpaned1">
566 <property name="visible">True</property>
567 <property name="can_focus">True</property>
568 <child>
569 <widget class="GtkScrolledWindow" id="results_scrolledwindow">
570 <property name="visible">True</property>
571 <property name="can_focus">True</property>
572 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
573 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
574 <child>
575 <placeholder/>
576 </child>
577 </widget>
578 <packing>
579 <property name="resize">False</property>
580 <property name="shrink">True</property>
581 </packing>
582 </child>
583 <child>
584 <widget class="GtkScrolledWindow" id="progress_scrolledwindow">
585 <property name="visible">True</property>
586 <property name="can_focus">True</property>
587 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
588 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
589 <child>
590 <placeholder/>
591 </child>
592 </widget>
593 <packing>
594 <property name="resize">True</property>
595 <property name="shrink">True</property>
596 </packing>
597 </child>
598 </widget>
599 <packing>
600 <property name="position">1</property>
601 </packing>
602 </child>
603 </widget>
604 </child>
605 </widget>
606</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 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2008 Intel Corporation
5#
6# Authored by Rob Bradford <rob@linux.intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23
24class RunningBuildModel (gtk.TreeStore):
25 (COL_TYPE, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_ACTIVE) = (0, 1, 2, 3, 4, 5)
26 def __init__ (self):
27 gtk.TreeStore.__init__ (self,
28 gobject.TYPE_STRING,
29 gobject.TYPE_STRING,
30 gobject.TYPE_STRING,
31 gobject.TYPE_STRING,
32 gobject.TYPE_STRING,
33 gobject.TYPE_BOOLEAN)
34
35class RunningBuild (gobject.GObject):
36 __gsignals__ = {
37 'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
38 gobject.TYPE_NONE,
39 ()),
40 'build-failed' : (gobject.SIGNAL_RUN_LAST,
41 gobject.TYPE_NONE,
42 ())
43 }
44 pids_to_task = {}
45 tasks_to_iter = {}
46
47 def __init__ (self):
48 gobject.GObject.__init__ (self)
49 self.model = RunningBuildModel()
50
51 def handle_event (self, event):
52 # Handle an event from the event queue, this may result in updating
53 # the model and thus the UI. Or it may be to tell us that the build
54 # has finished successfully (or not, as the case may be.)
55
56 parent = None
57 pid = 0
58 package = None
59 task = None
60
61 # If we have a pid attached to this message/event try and get the
62 # (package, task) pair for it. If we get that then get the parent iter
63 # for the message.
64 if hassattr(event, 'pid'):
65 pid = event.pid
66 if self.pids_to_task.has_key(pid):
67 (package, task) = self.pids_to_task[pid]
68 parent = self.tasks_to_iter[(package, task)]
69
70 if isinstance(event, bb.msg.Msg):
71 # Set a pretty icon for the message based on it's type.
72 if isinstance(event, bb.msg.MsgWarn):
73 icon = "dialog-warning"
74 elif isinstance(event, bb.msg.MsgErr):
75 icon = "dialog-error"
76 else:
77 icon = None
78
79 # Ignore the "Running task i of n .." messages
80 if (event._message.startswith ("Running task")):
81 return
82
83 # Add the message to the tree either at the top level if parent is
84 # None otherwise as a descendent of a task.
85 self.model.append (parent,
86 (event.__name__.split()[-1], # e.g. MsgWarn, MsgError
87 package,
88 task,
89 event._message,
90 icon,
91 False))
92 elif isinstance(event, bb.build.TaskStarted):
93 (package, task) = (event._package, event._task)
94
95 # Save out this PID.
96 self.pids_to_task[pid] = (package,task)
97
98 # Check if we already have this package in our model. If so then
99 # that can be the parent for the task. Otherwise we create a new
100 # top level for the package.
101 if (self.tasks_to_iter.has_key ((package, None))):
102 parent = self.tasks_to_iter[(package, None)]
103 else:
104 parent = self.model.append (None, (None,
105 package,
106 None,
107 "Package: %s" % (package),
108 None,
109 False))
110 self.tasks_to_iter[(package, None)] = parent
111
112 # Because this parent package now has an active child mark it as
113 # such.
114 self.model.set(parent, self.model.COL_ICON, "gtk-execute")
115
116 # Add an entry in the model for this task
117 i = self.model.append (parent, (None,
118 package,
119 task,
120 "Task: %s" % (task),
121 None,
122 False))
123
124 # Save out the iter so that we can find it when we have a message
125 # that we need to attach to a task.
126 self.tasks_to_iter[(package, task)] = i
127
128 # Mark this task as active.
129 self.model.set(i, self.model.COL_ICON, "gtk-execute")
130
131 elif isinstance(event, bb.build.Task):
132
133 if isinstance(event, bb.build.TaskFailed):
134 # Mark the task as failed
135 i = self.tasks_to_iter[(package, task)]
136 self.model.set(i, self.model.COL_ICON, "dialog-error")
137
138 # Mark the parent package as failed
139 i = self.tasks_to_iter[(package, None)]
140 self.model.set(i, self.model.COL_ICON, "dialog-error")
141 else:
142 # Mark the task as inactive
143 i = self.tasks_to_iter[(package, task)]
144 self.model.set(i, self.model.COL_ICON, None)
145
146 # Mark the parent package as inactive
147 i = self.tasks_to_iter[(package, None)]
148 self.model.set(i, self.model.COL_ICON, None)
149
150
151 # Clear the iters and the pids since when the task goes away the
152 # pid will no longer be used for messages
153 del self.tasks_to_iter[(package, task)]
154 del self.pids_to_task[pid]
155
156 elif isinstance(event, bb.event.BuildCompleted):
157 failures = int (event._failures)
158
159 # Emit the appropriate signal depending on the number of failures
160 if (failures > 1):
161 self.emit ("build-failed")
162 else:
163 self.emit ("build-succeeded")
164
165class RunningBuildTreeView (gtk.TreeView):
166 def __init__ (self):
167 gtk.TreeView.__init__ (self)
168
169 # The icon that indicates whether we're building or failed.
170 renderer = gtk.CellRendererPixbuf ()
171 col = gtk.TreeViewColumn ("Status", renderer)
172 col.add_attribute (renderer, "icon-name", 4)
173 self.append_column (col)
174
175 # The message of the build.
176 renderer = gtk.CellRendererText ()
177 col = gtk.TreeViewColumn ("Message", renderer, text=3)
178 self.append_column (col)
179
180
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 @@
1#
2# BitBake Graphical GTK based Dependency Explorer
3#
4# Copyright (C) 2007 Ross Burton
5# Copyright (C) 2007 - 2008 Richard Purdie
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20import gobject
21import gtk
22import threading
23import xmlrpclib
24
25# Package Model
26(COL_PKG_NAME) = (0)
27
28# Dependency Model
29(TYPE_DEP, TYPE_RDEP) = (0, 1)
30(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
31
32class PackageDepView(gtk.TreeView):
33 def __init__(self, model, dep_type, label):
34 gtk.TreeView.__init__(self)
35 self.current = None
36 self.dep_type = dep_type
37 self.filter_model = model.filter_new()
38 self.filter_model.set_visible_func(self._filter)
39 self.set_model(self.filter_model)
40 #self.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
41 self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PACKAGE))
42
43 def _filter(self, model, iter):
44 (this_type, package) = model.get(iter, COL_DEP_TYPE, COL_DEP_PARENT)
45 if this_type != self.dep_type: return False
46 return package == self.current
47
48 def set_current_package(self, package):
49 self.current = package
50 self.filter_model.refilter()
51
52class PackageReverseDepView(gtk.TreeView):
53 def __init__(self, model, label):
54 gtk.TreeView.__init__(self)
55 self.current = None
56 self.filter_model = model.filter_new()
57 self.filter_model.set_visible_func(self._filter)
58 self.set_model(self.filter_model)
59 self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PARENT))
60
61 def _filter(self, model, iter):
62 package = model.get_value(iter, COL_DEP_PACKAGE)
63 return package == self.current
64
65 def set_current_package(self, package):
66 self.current = package
67 self.filter_model.refilter()
68
69class DepExplorer(gtk.Window):
70 def __init__(self):
71 gtk.Window.__init__(self)
72 self.set_title("Dependency Explorer")
73 self.set_default_size(500, 500)
74 self.connect("delete-event", gtk.main_quit)
75
76 # Create the data models
77 self.pkg_model = gtk.ListStore(gobject.TYPE_STRING)
78 self.depends_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)
79
80 pane = gtk.HPaned()
81 pane.set_position(250)
82 self.add(pane)
83
84 # The master list of packages
85 scrolled = gtk.ScrolledWindow()
86 scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
87 scrolled.set_shadow_type(gtk.SHADOW_IN)
88 self.pkg_treeview = gtk.TreeView(self.pkg_model)
89 self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
90 self.pkg_treeview.append_column(gtk.TreeViewColumn("Package", gtk.CellRendererText(), text=COL_PKG_NAME))
91 pane.add1(scrolled)
92 scrolled.add(self.pkg_treeview)
93
94 box = gtk.VBox(homogeneous=True, spacing=4)
95
96 # Runtime Depends
97 scrolled = gtk.ScrolledWindow()
98 scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
99 scrolled.set_shadow_type(gtk.SHADOW_IN)
100 self.rdep_treeview = PackageDepView(self.depends_model, TYPE_RDEP, "Runtime Depends")
101 self.rdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
102 scrolled.add(self.rdep_treeview)
103 box.add(scrolled)
104
105 # Build Depends
106 scrolled = gtk.ScrolledWindow()
107 scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
108 scrolled.set_shadow_type(gtk.SHADOW_IN)
109 self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Build Depends")
110 self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
111 scrolled.add(self.dep_treeview)
112 box.add(scrolled)
113 pane.add2(box)
114
115 # Reverse Depends
116 scrolled = gtk.ScrolledWindow()
117 scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
118 scrolled.set_shadow_type(gtk.SHADOW_IN)
119 self.revdep_treeview = PackageReverseDepView(self.depends_model, "Reverse Depends")
120 self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
121 scrolled.add(self.revdep_treeview)
122 box.add(scrolled)
123 pane.add2(box)
124
125 self.show_all()
126
127 def on_package_activated(self, treeview, path, column, data_col):
128 model = treeview.get_model()
129 package = model.get_value(model.get_iter(path), data_col)
130
131 pkg_path = []
132 def finder(model, path, iter, needle):
133 package = model.get_value(iter, COL_PKG_NAME)
134 if package == needle:
135 pkg_path.append(path)
136 return True
137 else:
138 return False
139 self.pkg_model.foreach(finder, package)
140 if pkg_path:
141 self.pkg_treeview.get_selection().select_path(pkg_path[0])
142 self.pkg_treeview.scroll_to_cell(pkg_path[0])
143
144 def on_cursor_changed(self, selection):
145 (model, it) = selection.get_selected()
146 if iter is None:
147 current_package = None
148 else:
149 current_package = model.get_value(it, COL_PKG_NAME)
150 self.rdep_treeview.set_current_package(current_package)
151 self.dep_treeview.set_current_package(current_package)
152 self.revdep_treeview.set_current_package(current_package)
153
154
155def parse(depgraph, pkg_model, depends_model):
156
157 for package in depgraph["pn"]:
158 pkg_model.set(pkg_model.append(), COL_PKG_NAME, package)
159
160 for package in depgraph["depends"]:
161 for depend in depgraph["depends"][package]:
162 depends_model.set (depends_model.append(),
163 COL_DEP_TYPE, TYPE_DEP,
164 COL_DEP_PARENT, package,
165 COL_DEP_PACKAGE, depend)
166
167 for package in depgraph["rdepends-pn"]:
168 for rdepend in depgraph["rdepends-pn"][package]:
169 depends_model.set (depends_model.append(),
170 COL_DEP_TYPE, TYPE_RDEP,
171 COL_DEP_PARENT, package,
172 COL_DEP_PACKAGE, rdepend)
173
174class ProgressBar(gtk.Window):
175 def __init__(self):
176
177 gtk.Window.__init__(self)
178 self.set_title("Parsing .bb files, please wait...")
179 self.set_default_size(500, 0)
180 self.connect("delete-event", gtk.main_quit)
181
182 self.progress = gtk.ProgressBar()
183 self.add(self.progress)
184 self.show_all()
185
186class gtkthread(threading.Thread):
187 quit = threading.Event()
188 def __init__(self, shutdown):
189 threading.Thread.__init__(self)
190 self.setDaemon(True)
191 self.shutdown = shutdown
192
193 def run(self):
194 gobject.threads_init()
195 gtk.gdk.threads_init()
196 gtk.main()
197 gtkthread.quit.set()
198
199def init(server, eventHandler):
200
201 try:
202 cmdline = server.runCommand(["getCmdLineAction"])
203 if not cmdline or cmdline[0] != "generateDotGraph":
204 print "This UI is only compatible with the -g option"
205 return
206 ret = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
207 if ret != True:
208 print "Couldn't run command! %s" % ret
209 return
210 except xmlrpclib.Fault, x:
211 print "XMLRPC Fault getting commandline:\n %s" % x
212 return
213
214 shutdown = 0
215
216 gtkgui = gtkthread(shutdown)
217 gtkgui.start()
218
219 gtk.gdk.threads_enter()
220 pbar = ProgressBar()
221 dep = DepExplorer()
222 gtk.gdk.threads_leave()
223
224 while True:
225 try:
226 event = eventHandler.waitEvent(0.25)
227 if gtkthread.quit.isSet():
228 break
229
230 if event is None:
231 continue
232 if isinstance(event, bb.event.ParseProgress):
233 x = event.sofar
234 y = event.total
235 if x == y:
236 print("\nParsing finished. %d cached, %d parsed, %d skipped, %d masked, %d errors."
237 % ( event.cached, event.parsed, event.skipped, event.masked, event.errors))
238 pbar.hide()
239 gtk.gdk.threads_enter()
240 pbar.progress.set_fraction(float(x)/float(y))
241 pbar.progress.set_text("%d/%d (%2d %%)" % (x, y, x*100/y))
242 gtk.gdk.threads_leave()
243 continue
244
245 if isinstance(event, bb.event.DepTreeGenerated):
246 gtk.gdk.threads_enter()
247 parse(event._depgraph, dep.pkg_model, dep.depends_model)
248 gtk.gdk.threads_leave()
249
250 if isinstance(event, bb.command.CookerCommandCompleted):
251 continue
252 if isinstance(event, bb.command.CookerCommandFailed):
253 print "Command execution failed: %s" % event.error
254 break
255 if isinstance(event, bb.cooker.CookerExit):
256 break
257
258 continue
259
260 except KeyboardInterrupt:
261 if shutdown == 2:
262 print "\nThird Keyboard Interrupt, exit.\n"
263 break
264 if shutdown == 1:
265 print "\nSecond Keyboard Interrupt, stopping...\n"
266 server.runCommand(["stateStop"])
267 if shutdown == 0:
268 print "\nKeyboard Interrupt, closing down...\n"
269 server.runCommand(["stateShutdown"])
270 shutdown = shutdown + 1
271 pass
272
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 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2008 Intel Corporation
5#
6# Authored by Rob Bradford <rob@linux.intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gobject
22import gtk
23import xmlrpclib
24from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild
25
26def event_handle_idle_func (eventHandler, build):
27
28 # Consume as many messages as we can in the time available to us
29 event = eventHandler.getEvent()
30 while event:
31 build.handle_event (event)
32 event = eventHandler.getEvent()
33
34 return True
35
36class MainWindow (gtk.Window):
37 def __init__ (self):
38 gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL)
39
40 # Setup tree view and the scrolled window
41 scrolled_window = gtk.ScrolledWindow ()
42 self.add (scrolled_window)
43 self.cur_build_tv = RunningBuildTreeView()
44 scrolled_window.add (self.cur_build_tv)
45
46def init (server, eventHandler):
47 gobject.threads_init()
48 gtk.gdk.threads_init()
49
50 window = MainWindow ()
51 window.show_all ()
52
53 # Create the object for the current build
54 running_build = RunningBuild ()
55 window.cur_build_tv.set_model (running_build.model)
56 try:
57 cmdline = server.runCommand(["getCmdLineAction"])
58 print cmdline
59 if not cmdline:
60 return 1
61 ret = server.runCommand(cmdline)
62 if ret != True:
63 print "Couldn't get default commandline! %s" % ret
64 return 1
65 except xmlrpclib.Fault, x:
66 print "XMLRPC Fault getting commandline:\n %s" % x
67 return 1
68
69 # Use a timeout function for probing the event queue to find out if we
70 # have a message waiting for us.
71 gobject.timeout_add (200,
72 event_handle_idle_func,
73 eventHandler,
74 running_build)
75
76 gtk.main()
77
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 @@
1#
2# BitBake (No)TTY UI Implementation
3#
4# Handling output to TTYs or files (no TTY)
5#
6# Copyright (C) 2006-2007 Richard Purdie
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import os
22
23import sys
24import itertools
25import xmlrpclib
26
27parsespin = itertools.cycle( r'|/-\\' )
28
29def init(server, eventHandler):
30
31 # Get values of variables which control our output
32 includelogs = server.runCommand(["getVariable", "BBINCLUDELOGS"])
33 loglines = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
34
35 try:
36 cmdline = server.runCommand(["getCmdLineAction"])
37 #print cmdline
38 if not cmdline:
39 return 1
40 ret = server.runCommand(cmdline)
41 if ret != True:
42 print "Couldn't get default commandline! %s" % ret
43 return 1
44 except xmlrpclib.Fault, x:
45 print "XMLRPC Fault getting commandline:\n %s" % x
46 return 1
47
48 shutdown = 0
49 return_value = 0
50 while True:
51 try:
52 event = eventHandler.waitEvent(0.25)
53 if event is None:
54 continue
55 #print event
56 if isinstance(event, bb.msg.MsgPlain):
57 print event._message
58 continue
59 if isinstance(event, bb.msg.MsgDebug):
60 print 'DEBUG: ' + event._message
61 continue
62 if isinstance(event, bb.msg.MsgNote):
63 print 'NOTE: ' + event._message
64 continue
65 if isinstance(event, bb.msg.MsgWarn):
66 print 'WARNING: ' + event._message
67 continue
68 if isinstance(event, bb.msg.MsgError):
69 return_value = 1
70 print 'ERROR: ' + event._message
71 continue
72 if isinstance(event, bb.msg.MsgFatal):
73 return_value = 1
74 print 'FATAL: ' + event._message
75 break
76 if isinstance(event, bb.build.TaskFailed):
77 return_value = 1
78 logfile = event.logfile
79 if logfile:
80 print "ERROR: Logfile of failure stored in %s." % logfile
81 if 1 or includelogs:
82 print "Log data follows:"
83 f = open(logfile, "r")
84 lines = []
85 while True:
86 l = f.readline()
87 if l == '':
88 break
89 l = l.rstrip()
90 if loglines:
91 lines.append(' | %s' % l)
92 if len(lines) > int(loglines):
93 lines.pop(0)
94 else:
95 print '| %s' % l
96 f.close()
97 if lines:
98 for line in lines:
99 print line
100 if isinstance(event, bb.build.TaskBase):
101 print "NOTE: %s" % event._message
102 continue
103 if isinstance(event, bb.event.ParseProgress):
104 x = event.sofar
105 y = event.total
106 if os.isatty(sys.stdout.fileno()):
107 sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
108 sys.stdout.flush()
109 else:
110 if x == 1:
111 sys.stdout.write("Parsing .bb files, please wait...")
112 sys.stdout.flush()
113 if x == y:
114 sys.stdout.write("done.")
115 sys.stdout.flush()
116 if x == y:
117 print("\nParsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
118 % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors))
119 continue
120
121 if isinstance(event, bb.command.CookerCommandCompleted):
122 break
123 if isinstance(event, bb.command.CookerCommandSetExitCode):
124 return_value = event.exitcode
125 continue
126 if isinstance(event, bb.command.CookerCommandFailed):
127 return_value = 1
128 print "Command execution failed: %s" % event.error
129 break
130 if isinstance(event, bb.cooker.CookerExit):
131 break
132
133 # ignore
134 if isinstance(event, bb.event.BuildStarted):
135 continue
136 if isinstance(event, bb.event.BuildCompleted):
137 continue
138 if isinstance(event, bb.event.MultipleProviders):
139 continue
140 if isinstance(event, bb.runqueue.runQueueEvent):
141 continue
142 if isinstance(event, bb.event.StampUpdate):
143 continue
144 if isinstance(event, bb.event.ConfigParsed):
145 continue
146 if isinstance(event, bb.event.RecipeParsed):
147 continue
148 print "Unknown Event: %s" % event
149
150 except KeyboardInterrupt:
151 if shutdown == 2:
152 print "\nThird Keyboard Interrupt, exit.\n"
153 break
154 if shutdown == 1:
155 print "\nSecond Keyboard Interrupt, stopping...\n"
156 server.runCommand(["stateStop"])
157 if shutdown == 0:
158 print "\nKeyboard Interrupt, closing down...\n"
159 server.runCommand(["stateShutdown"])
160 shutdown = shutdown + 1
161 pass
162 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 @@
1#
2# BitBake Curses UI Implementation
3#
4# Implements an ncurses frontend for the BitBake utility.
5#
6# Copyright (C) 2006 Michael 'Mickey' Lauer
7# Copyright (C) 2006-2007 Richard Purdie
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22"""
23 We have the following windows:
24
25 1.) Main Window: Shows what we are ultimately building and how far we are. Includes status bar
26 2.) Thread Activity Window: Shows one status line for every concurrent bitbake thread.
27 3.) Command Line Window: Contains an interactive command line where you can interact w/ Bitbake.
28
29 Basic window layout is like that:
30
31 |---------------------------------------------------------|
32 | <Main Window> | <Thread Activity Window> |
33 | | 0: foo do_compile complete|
34 | Building Gtk+-2.6.10 | 1: bar do_patch complete |
35 | Status: 60% | ... |
36 | | ... |
37 | | ... |
38 |---------------------------------------------------------|
39 |<Command Line Window> |
40 |>>> which virtual/kernel |
41 |openzaurus-kernel |
42 |>>> _ |
43 |---------------------------------------------------------|
44
45"""
46
47import os, sys, curses, itertools, time
48import bb
49import xmlrpclib
50from bb import ui
51from bb.ui import uihelper
52
53parsespin = itertools.cycle( r'|/-\\' )
54
55X = 0
56Y = 1
57WIDTH = 2
58HEIGHT = 3
59
60MAXSTATUSLENGTH = 32
61
62class NCursesUI:
63 """
64 NCurses UI Class
65 """
66 class Window:
67 """Base Window Class"""
68 def __init__( self, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
69 self.win = curses.newwin( height, width, y, x )
70 self.dimensions = ( x, y, width, height )
71 """
72 if curses.has_colors():
73 color = 1
74 curses.init_pair( color, fg, bg )
75 self.win.bkgdset( ord(' '), curses.color_pair(color) )
76 else:
77 self.win.bkgdset( ord(' '), curses.A_BOLD )
78 """
79 self.erase()
80 self.setScrolling()
81 self.win.noutrefresh()
82
83 def erase( self ):
84 self.win.erase()
85
86 def setScrolling( self, b = True ):
87 self.win.scrollok( b )
88 self.win.idlok( b )
89
90 def setBoxed( self ):
91 self.boxed = True
92 self.win.box()
93 self.win.noutrefresh()
94
95 def setText( self, x, y, text, *args ):
96 self.win.addstr( y, x, text, *args )
97 self.win.noutrefresh()
98
99 def appendText( self, text, *args ):
100 self.win.addstr( text, *args )
101 self.win.noutrefresh()
102
103 def drawHline( self, y ):
104 self.win.hline( y, 0, curses.ACS_HLINE, self.dimensions[WIDTH] )
105 self.win.noutrefresh()
106
107 class DecoratedWindow( Window ):
108 """Base class for windows with a box and a title bar"""
109 def __init__( self, title, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
110 NCursesUI.Window.__init__( self, x+1, y+3, width-2, height-4, fg, bg )
111 self.decoration = NCursesUI.Window( x, y, width, height, fg, bg )
112 self.decoration.setBoxed()
113 self.decoration.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
114 self.setTitle( title )
115
116 def setTitle( self, title ):
117 self.decoration.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
118
119 #-------------------------------------------------------------------------#
120# class TitleWindow( Window ):
121 #-------------------------------------------------------------------------#
122# """Title Window"""
123# def __init__( self, x, y, width, height ):
124# NCursesUI.Window.__init__( self, x, y, width, height )
125# version = bb.__version__
126# title = "BitBake %s" % version
127# credit = "(C) 2003-2007 Team BitBake"
128# #self.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
129# self.win.border()
130# self.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
131# self.setText( 1, 2, credit.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
132
133 #-------------------------------------------------------------------------#
134 class ThreadActivityWindow( DecoratedWindow ):
135 #-------------------------------------------------------------------------#
136 """Thread Activity Window"""
137 def __init__( self, x, y, width, height ):
138 NCursesUI.DecoratedWindow.__init__( self, "Thread Activity", x, y, width, height )
139
140 def setStatus( self, thread, text ):
141 line = "%02d: %s" % ( thread, text )
142 width = self.dimensions[WIDTH]
143 if ( len(line) > width ):
144 line = line[:width-3] + "..."
145 else:
146 line = line.ljust( width )
147 self.setText( 0, thread, line )
148
149 #-------------------------------------------------------------------------#
150 class MainWindow( DecoratedWindow ):
151 #-------------------------------------------------------------------------#
152 """Main Window"""
153 def __init__( self, x, y, width, height ):
154 self.StatusPosition = width - MAXSTATUSLENGTH
155 NCursesUI.DecoratedWindow.__init__( self, None, x, y, width, height )
156 curses.nl()
157
158 def setTitle( self, title ):
159 title = "BitBake %s" % bb.__version__
160 self.decoration.setText( 2, 1, title, curses.A_BOLD )
161 self.decoration.setText( self.StatusPosition - 8, 1, "Status:", curses.A_BOLD )
162
163 def setStatus(self, status):
164 while len(status) < MAXSTATUSLENGTH:
165 status = status + " "
166 self.decoration.setText( self.StatusPosition, 1, status, curses.A_BOLD )
167
168
169 #-------------------------------------------------------------------------#
170 class ShellOutputWindow( DecoratedWindow ):
171 #-------------------------------------------------------------------------#
172 """Interactive Command Line Output"""
173 def __init__( self, x, y, width, height ):
174 NCursesUI.DecoratedWindow.__init__( self, "Command Line Window", x, y, width, height )
175
176 #-------------------------------------------------------------------------#
177 class ShellInputWindow( Window ):
178 #-------------------------------------------------------------------------#
179 """Interactive Command Line Input"""
180 def __init__( self, x, y, width, height ):
181 NCursesUI.Window.__init__( self, x, y, width, height )
182
183# put that to the top again from curses.textpad import Textbox
184# self.textbox = Textbox( self.win )
185# t = threading.Thread()
186# t.run = self.textbox.edit
187# t.start()
188
189 #-------------------------------------------------------------------------#
190 def main(self, stdscr, server, eventHandler):
191 #-------------------------------------------------------------------------#
192 height, width = stdscr.getmaxyx()
193
194 # for now split it like that:
195 # MAIN_y + THREAD_y = 2/3 screen at the top
196 # MAIN_x = 2/3 left, THREAD_y = 1/3 right
197 # CLI_y = 1/3 of screen at the bottom
198 # CLI_x = full
199
200 main_left = 0
201 main_top = 0
202 main_height = ( height / 3 * 2 )
203 main_width = ( width / 3 ) * 2
204 clo_left = main_left
205 clo_top = main_top + main_height
206 clo_height = height - main_height - main_top - 1
207 clo_width = width
208 cli_left = main_left
209 cli_top = clo_top + clo_height
210 cli_height = 1
211 cli_width = width
212 thread_left = main_left + main_width
213 thread_top = main_top
214 thread_height = main_height
215 thread_width = width - main_width
216
217 #tw = self.TitleWindow( 0, 0, width, main_top )
218 mw = self.MainWindow( main_left, main_top, main_width, main_height )
219 taw = self.ThreadActivityWindow( thread_left, thread_top, thread_width, thread_height )
220 clo = self.ShellOutputWindow( clo_left, clo_top, clo_width, clo_height )
221 cli = self.ShellInputWindow( cli_left, cli_top, cli_width, cli_height )
222 cli.setText( 0, 0, "BB>" )
223
224 mw.setStatus("Idle")
225
226 helper = uihelper.BBUIHelper()
227 shutdown = 0
228
229 try:
230 cmdline = server.runCommand(["getCmdLineAction"])
231 if not cmdline:
232 return
233 ret = server.runCommand(cmdline)
234 if ret != True:
235 print "Couldn't get default commandlind! %s" % ret
236 return
237 except xmlrpclib.Fault, x:
238 print "XMLRPC Fault getting commandline:\n %s" % x
239 return
240
241 exitflag = False
242 while not exitflag:
243 try:
244 event = eventHandler.waitEvent(0.25)
245 if not event:
246 continue
247 helper.eventHandler(event)
248 #mw.appendText("%s\n" % event[0])
249 if isinstance(event, bb.build.Task):
250 mw.appendText("NOTE: %s\n" % event._message)
251 if isinstance(event, bb.msg.MsgDebug):
252 mw.appendText('DEBUG: ' + event._message + '\n')
253 if isinstance(event, bb.msg.MsgNote):
254 mw.appendText('NOTE: ' + event._message + '\n')
255 if isinstance(event, bb.msg.MsgWarn):
256 mw.appendText('WARNING: ' + event._message + '\n')
257 if isinstance(event, bb.msg.MsgError):
258 mw.appendText('ERROR: ' + event._message + '\n')
259 if isinstance(event, bb.msg.MsgFatal):
260 mw.appendText('FATAL: ' + event._message + '\n')
261 if isinstance(event, bb.event.ParseProgress):
262 x = event.sofar
263 y = event.total
264 if x == y:
265 mw.setStatus("Idle")
266 mw.appendText("Parsing finished. %d cached, %d parsed, %d skipped, %d masked."
267 % ( event.cached, event.parsed, event.skipped, event.masked ))
268 else:
269 mw.setStatus("Parsing: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
270# if isinstance(event, bb.build.TaskFailed):
271# if event.logfile:
272# if data.getVar("BBINCLUDELOGS", d):
273# bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
274# number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
275# if number_of_lines:
276# os.system('tail -n%s %s' % (number_of_lines, logfile))
277# else:
278# f = open(logfile, "r")
279# while True:
280# l = f.readline()
281# if l == '':
282# break
283# l = l.rstrip()
284# print '| %s' % l
285# f.close()
286# else:
287# bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
288
289 if isinstance(event, bb.command.CookerCommandCompleted):
290 exitflag = True
291 if isinstance(event, bb.command.CookerCommandFailed):
292 mw.appendText("Command execution failed: %s" % event.error)
293 time.sleep(2)
294 exitflag = True
295 if isinstance(event, bb.cooker.CookerExit):
296 exitflag = True
297
298 if helper.needUpdate:
299 activetasks, failedtasks = helper.getTasks()
300 taw.erase()
301 taw.setText(0, 0, "")
302 if activetasks:
303 taw.appendText("Active Tasks:\n")
304 for task in activetasks:
305 taw.appendText(task)
306 if failedtasks:
307 taw.appendText("Failed Tasks:\n")
308 for task in failedtasks:
309 taw.appendText(task)
310
311 curses.doupdate()
312 except KeyboardInterrupt:
313 if shutdown == 2:
314 mw.appendText("Third Keyboard Interrupt, exit.\n")
315 exitflag = True
316 if shutdown == 1:
317 mw.appendText("Second Keyboard Interrupt, stopping...\n")
318 server.runCommand(["stateStop"])
319 if shutdown == 0:
320 mw.appendText("Keyboard Interrupt, closing down...\n")
321 server.runCommand(["stateShutdown"])
322 shutdown = shutdown + 1
323 pass
324
325def init(server, eventHandler):
326 if not os.isatty(sys.stdout.fileno()):
327 print "FATAL: Unable to run 'ncurses' UI without a TTY."
328 return
329 ui = NCursesUI()
330 try:
331 curses.wrapper(ui.main, server, eventHandler)
332 except:
333 import traceback
334 traceback.print_exc()
335
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 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2008 Intel Corporation
5#
6# Authored by Rob Bradford <rob@linux.intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23import gtk.glade
24import threading
25import urllib2
26import os
27
28from bb.ui.crumbs.buildmanager import BuildManager, BuildConfiguration
29from bb.ui.crumbs.buildmanager import BuildManagerTreeView
30
31from bb.ui.crumbs.runningbuild import RunningBuild, RunningBuildTreeView
32
33# The metadata loader is used by the BuildSetupDialog to download the
34# available options to populate the dialog
35class MetaDataLoader(gobject.GObject):
36 """ This class provides the mechanism for loading the metadata (the
37 fetching and parsing) from a given URL. The metadata encompasses details
38 on what machines are available. The distribution and images available for
39 the machine and the the uris to use for building the given machine."""
40 __gsignals__ = {
41 'success' : (gobject.SIGNAL_RUN_LAST,
42 gobject.TYPE_NONE,
43 ()),
44 'error' : (gobject.SIGNAL_RUN_LAST,
45 gobject.TYPE_NONE,
46 (gobject.TYPE_STRING,))
47 }
48
49 # We use these little helper functions to ensure that we take the gdk lock
50 # when emitting the signal. These functions are called as idles (so that
51 # they happen in the gtk / main thread's main loop.
52 def emit_error_signal (self, remark):
53 gtk.gdk.threads_enter()
54 self.emit ("error", remark)
55 gtk.gdk.threads_leave()
56
57 def emit_success_signal (self):
58 gtk.gdk.threads_enter()
59 self.emit ("success")
60 gtk.gdk.threads_leave()
61
62 def __init__ (self):
63 gobject.GObject.__init__ (self)
64
65 class LoaderThread(threading.Thread):
66 """ This class provides an asynchronous loader for the metadata (by
67 using threads and signals). This is useful since the metadata may be
68 at a remote URL."""
69 class LoaderImportException (Exception):
70 pass
71
72 def __init__(self, loader, url):
73 threading.Thread.__init__ (self)
74 self.url = url
75 self.loader = loader
76
77 def run (self):
78 result = {}
79 try:
80 f = urllib2.urlopen (self.url)
81
82 # Parse the metadata format. The format is....
83 # <machine>;<default distro>|<distro>...;<default image>|<image>...;<type##url>|...
84 for line in f.readlines():
85 components = line.split(";")
86 if (len (components) < 4):
87 raise MetaDataLoader.LoaderThread.LoaderImportException
88 machine = components[0]
89 distros = components[1].split("|")
90 images = components[2].split("|")
91 urls = components[3].split("|")
92
93 result[machine] = (distros, images, urls)
94
95 # Create an object representing this *potential*
96 # configuration. It can become concrete if the machine, distro
97 # and image are all chosen in the UI
98 configuration = BuildConfiguration()
99 configuration.metadata_url = self.url
100 configuration.machine_options = result
101 self.loader.configuration = configuration
102
103 # Emit that we've actually got a configuration
104 gobject.idle_add (MetaDataLoader.emit_success_signal,
105 self.loader)
106
107 except MetaDataLoader.LoaderThread.LoaderImportException, e:
108 gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
109 "Repository metadata corrupt")
110 except Exception, e:
111 gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
112 "Unable to download repository metadata")
113 print e
114
115 def try_fetch_from_url (self, url):
116 # Try and download the metadata. Firing a signal if successful
117 thread = MetaDataLoader.LoaderThread(self, url)
118 thread.start()
119
120class BuildSetupDialog (gtk.Dialog):
121 RESPONSE_BUILD = 1
122
123 # A little helper method that just sets the states on the widgets based on
124 # whether we've got good metadata or not.
125 def set_configurable (self, configurable):
126 if (self.configurable == configurable):
127 return
128
129 self.configurable = configurable
130 for widget in self.conf_widgets:
131 widget.set_sensitive (configurable)
132
133 if not configurable:
134 self.machine_combo.set_active (-1)
135 self.distribution_combo.set_active (-1)
136 self.image_combo.set_active (-1)
137
138 # GTK widget callbacks
139 def refresh_button_clicked (self, button):
140 # Refresh button clicked.
141
142 url = self.location_entry.get_chars (0, -1)
143 self.loader.try_fetch_from_url(url)
144
145 def repository_entry_editable_changed (self, entry):
146 if (len (entry.get_chars (0, -1)) > 0):
147 self.refresh_button.set_sensitive (True)
148 else:
149 self.refresh_button.set_sensitive (False)
150 self.clear_status_message()
151
152 # If we were previously configurable we are no longer since the
153 # location entry has been changed
154 self.set_configurable (False)
155
156 def machine_combo_changed (self, combobox):
157 active_iter = combobox.get_active_iter()
158
159 if not active_iter:
160 return
161
162 model = combobox.get_model()
163
164 if model:
165 chosen_machine = model.get (active_iter, 0)[0]
166
167 (distros_model, images_model) = \
168 self.loader.configuration.get_distro_and_images_models (chosen_machine)
169
170 self.distribution_combo.set_model (distros_model)
171 self.image_combo.set_model (images_model)
172
173 # Callbacks from the loader
174 def loader_success_cb (self, loader):
175 self.status_image.set_from_icon_name ("info",
176 gtk.ICON_SIZE_BUTTON)
177 self.status_image.show()
178 self.status_label.set_label ("Repository metadata successfully downloaded")
179
180 # Set the models on the combo boxes based on the models generated from
181 # the configuration that the loader has created
182
183 # We just need to set the machine here, that then determines the
184 # distro and image options. Cunning huh? :-)
185
186 self.configuration = self.loader.configuration
187 model = self.configuration.get_machines_model ()
188 self.machine_combo.set_model (model)
189
190 self.set_configurable (True)
191
192 def loader_error_cb (self, loader, message):
193 self.status_image.set_from_icon_name ("error",
194 gtk.ICON_SIZE_BUTTON)
195 self.status_image.show()
196 self.status_label.set_text ("Error downloading repository metadata")
197 for widget in self.conf_widgets:
198 widget.set_sensitive (False)
199
200 def clear_status_message (self):
201 self.status_image.hide()
202 self.status_label.set_label (
203 """<i>Enter the repository location and press _Refresh</i>""")
204
205 def __init__ (self):
206 gtk.Dialog.__init__ (self)
207
208 # Cancel
209 self.add_button (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
210
211 # Build
212 button = gtk.Button ("_Build", None, True)
213 image = gtk.Image ()
214 image.set_from_stock (gtk.STOCK_EXECUTE,gtk.ICON_SIZE_BUTTON)
215 button.set_image (image)
216 self.add_action_widget (button, BuildSetupDialog.RESPONSE_BUILD)
217 button.show_all ()
218
219 # Pull in *just* the table from the Glade XML data.
220 gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
221 root = "build_table")
222 table = gxml.get_widget ("build_table")
223 self.vbox.pack_start (table, True, False, 0)
224
225 # Grab all the widgets that we need to turn on/off when we refresh...
226 self.conf_widgets = []
227 self.conf_widgets += [gxml.get_widget ("machine_label")]
228 self.conf_widgets += [gxml.get_widget ("distribution_label")]
229 self.conf_widgets += [gxml.get_widget ("image_label")]
230 self.conf_widgets += [gxml.get_widget ("machine_combo")]
231 self.conf_widgets += [gxml.get_widget ("distribution_combo")]
232 self.conf_widgets += [gxml.get_widget ("image_combo")]
233
234 # Grab the status widgets
235 self.status_image = gxml.get_widget ("status_image")
236 self.status_label = gxml.get_widget ("status_label")
237
238 # Grab the refresh button and connect to the clicked signal
239 self.refresh_button = gxml.get_widget ("refresh_button")
240 self.refresh_button.connect ("clicked", self.refresh_button_clicked)
241
242 # Grab the location entry and connect to editable::changed
243 self.location_entry = gxml.get_widget ("location_entry")
244 self.location_entry.connect ("changed",
245 self.repository_entry_editable_changed)
246
247 # Grab the machine combo and hook onto the changed signal. This then
248 # allows us to populate the distro and image combos
249 self.machine_combo = gxml.get_widget ("machine_combo")
250 self.machine_combo.connect ("changed", self.machine_combo_changed)
251
252 # Setup the combo
253 cell = gtk.CellRendererText()
254 self.machine_combo.pack_start(cell, True)
255 self.machine_combo.add_attribute(cell, 'text', 0)
256
257 # Grab the distro and image combos. We need these to populate with
258 # models once the machine is chosen
259 self.distribution_combo = gxml.get_widget ("distribution_combo")
260 cell = gtk.CellRendererText()
261 self.distribution_combo.pack_start(cell, True)
262 self.distribution_combo.add_attribute(cell, 'text', 0)
263
264 self.image_combo = gxml.get_widget ("image_combo")
265 cell = gtk.CellRendererText()
266 self.image_combo.pack_start(cell, True)
267 self.image_combo.add_attribute(cell, 'text', 0)
268
269 # Put the default descriptive text in the status box
270 self.clear_status_message()
271
272 # Mark as non-configurable, this is just greys out the widgets the
273 # user can't yet use
274 self.configurable = False
275 self.set_configurable(False)
276
277 # Show the table
278 table.show_all ()
279
280 # The loader and some signals connected to it to update the status
281 # area
282 self.loader = MetaDataLoader()
283 self.loader.connect ("success", self.loader_success_cb)
284 self.loader.connect ("error", self.loader_error_cb)
285
286 def update_configuration (self):
287 """ A poorly named function but it updates the internal configuration
288 from the widgets. This can make that configuration concrete and can
289 thus be used for building """
290 # Extract the chosen machine from the combo
291 model = self.machine_combo.get_model()
292 active_iter = self.machine_combo.get_active_iter()
293 if (active_iter):
294 self.configuration.machine = model.get(active_iter, 0)[0]
295
296 # Extract the chosen distro from the combo
297 model = self.distribution_combo.get_model()
298 active_iter = self.distribution_combo.get_active_iter()
299 if (active_iter):
300 self.configuration.distro = model.get(active_iter, 0)[0]
301
302 # Extract the chosen image from the combo
303 model = self.image_combo.get_model()
304 active_iter = self.image_combo.get_active_iter()
305 if (active_iter):
306 self.configuration.image = model.get(active_iter, 0)[0]
307
308# This function operates to pull events out from the event queue and then push
309# them into the RunningBuild (which then drives the RunningBuild which then
310# pushes through and updates the progress tree view.)
311#
312# TODO: Should be a method on the RunningBuild class
313def event_handle_timeout (eventHandler, build):
314 # Consume as many messages as we can ...
315 event = eventHandler.getEvent()
316 while event:
317 build.handle_event (event)
318 event = eventHandler.getEvent()
319 return True
320
321class MainWindow (gtk.Window):
322
323 # Callback that gets fired when the user hits a button in the
324 # BuildSetupDialog.
325 def build_dialog_box_response_cb (self, dialog, response_id):
326 conf = None
327 if (response_id == BuildSetupDialog.RESPONSE_BUILD):
328 dialog.update_configuration()
329 print dialog.configuration.machine, dialog.configuration.distro, \
330 dialog.configuration.image
331 conf = dialog.configuration
332
333 dialog.destroy()
334
335 if conf:
336 self.manager.do_build (conf)
337
338 def build_button_clicked_cb (self, button):
339 dialog = BuildSetupDialog ()
340
341 # For some unknown reason Dialog.run causes nice little deadlocks ... :-(
342 dialog.connect ("response", self.build_dialog_box_response_cb)
343 dialog.show()
344
345 def __init__ (self):
346 gtk.Window.__init__ (self)
347
348 # Pull in *just* the main vbox from the Glade XML data and then pack
349 # that inside the window
350 gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
351 root = "main_window_vbox")
352 vbox = gxml.get_widget ("main_window_vbox")
353 self.add (vbox)
354
355 # Create the tree views for the build manager view and the progress view
356 self.build_manager_view = BuildManagerTreeView()
357 self.running_build_view = RunningBuildTreeView()
358
359 # Grab the scrolled windows that we put the tree views into
360 self.results_scrolledwindow = gxml.get_widget ("results_scrolledwindow")
361 self.progress_scrolledwindow = gxml.get_widget ("progress_scrolledwindow")
362
363 # Put the tree views inside ...
364 self.results_scrolledwindow.add (self.build_manager_view)
365 self.progress_scrolledwindow.add (self.running_build_view)
366
367 # Hook up the build button...
368 self.build_button = gxml.get_widget ("main_toolbutton_build")
369 self.build_button.connect ("clicked", self.build_button_clicked_cb)
370
371# I'm not very happy about the current ownership of the RunningBuild. I have
372# my suspicions that this object should be held by the BuildManager since we
373# care about the signals in the manager
374
375def running_build_succeeded_cb (running_build, manager):
376 # Notify the manager that a build has succeeded. This is necessary as part
377 # of the 'hack' that we use for making the row in the model / view
378 # representing the ongoing build change into a row representing the
379 # completed build. Since we know only one build can be running a time then
380 # we can handle this.
381
382 # FIXME: Refactor all this so that the RunningBuild is owned by the
383 # BuildManager. It can then hook onto the signals directly and drive
384 # interesting things it cares about.
385 manager.notify_build_succeeded ()
386 print "build succeeded"
387
388def running_build_failed_cb (running_build, manager):
389 # As above
390 print "build failed"
391 manager.notify_build_failed ()
392
393def init (server, eventHandler):
394 # Initialise threading...
395 gobject.threads_init()
396 gtk.gdk.threads_init()
397
398 main_window = MainWindow ()
399 main_window.show_all ()
400
401 # Set up the build manager stuff in general
402 builds_dir = os.path.join (os.getcwd(), "results")
403 manager = BuildManager (server, builds_dir)
404 main_window.build_manager_view.set_model (manager.model)
405
406 # Do the running build setup
407 running_build = RunningBuild ()
408 main_window.running_build_view.set_model (running_build.model)
409 running_build.connect ("build-succeeded", running_build_succeeded_cb,
410 manager)
411 running_build.connect ("build-failed", running_build_failed_cb, manager)
412
413 # We need to save the manager into the MainWindow so that the toolbar
414 # button can use it.
415 # FIXME: Refactor ?
416 main_window.manager = manager
417
418 # Use a timeout function for probing the event queue to find out if we
419 # have a message waiting for us.
420 gobject.timeout_add (200,
421 event_handle_timeout,
422 eventHandler,
423 running_build)
424
425 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 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
5# Copyright (C) 2006 - 2007 Richard Purdie
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
21"""
22Use this class to fork off a thread to recieve event callbacks from the bitbake
23server and queue them for the UI to process. This process must be used to avoid
24client/server deadlocks.
25"""
26
27import socket, threading, pickle
28from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
29
30class BBUIEventQueue:
31 def __init__(self, BBServer):
32
33 self.eventQueue = []
34 self.eventQueueLock = threading.Lock()
35 self.eventQueueNotify = threading.Event()
36
37 self.BBServer = BBServer
38
39 self.t = threading.Thread()
40 self.t.setDaemon(True)
41 self.t.run = self.startCallbackHandler
42 self.t.start()
43
44 def getEvent(self):
45
46 self.eventQueueLock.acquire()
47
48 if len(self.eventQueue) == 0:
49 self.eventQueueLock.release()
50 return None
51
52 item = self.eventQueue.pop(0)
53
54 if len(self.eventQueue) == 0:
55 self.eventQueueNotify.clear()
56
57 self.eventQueueLock.release()
58 return item
59
60 def waitEvent(self, delay):
61 self.eventQueueNotify.wait(delay)
62 return self.getEvent()
63
64 def queue_event(self, event):
65 self.eventQueueLock.acquire()
66 self.eventQueue.append(pickle.loads(event))
67 self.eventQueueNotify.set()
68 self.eventQueueLock.release()
69
70 def startCallbackHandler(self):
71
72 server = UIXMLRPCServer()
73 self.host, self.port = server.socket.getsockname()
74
75 server.register_function( self.system_quit, "event.quit" )
76 server.register_function( self.queue_event, "event.send" )
77 server.socket.settimeout(1)
78
79 self.EventHandle = self.BBServer.registerEventHandler(self.host, self.port)
80
81 self.server = server
82 while not server.quit:
83 server.handle_request()
84 server.server_close()
85
86 def system_quit( self ):
87 """
88 Shut down the callback thread
89 """
90 try:
91 self.BBServer.unregisterEventHandler(self.EventHandle)
92 except:
93 pass
94 self.server.quit = True
95
96class UIXMLRPCServer (SimpleXMLRPCServer):
97
98 def __init__( self, interface = ("localhost", 0) ):
99 self.quit = False
100 SimpleXMLRPCServer.__init__( self,
101 interface,
102 requestHandler=SimpleXMLRPCRequestHandler,
103 logRequests=False, allow_none=True)
104
105 def get_request(self):
106 while not self.quit:
107 try:
108 sock, addr = self.socket.accept()
109 sock.settimeout(1)
110 return (sock, addr)
111 except socket.timeout:
112 pass
113 return (None,None)
114
115 def close_request(self, request):
116 if request is None:
117 return
118 SimpleXMLRPCServer.close_request(self, request)
119
120 def process_request(self, request, client_address):
121 if request is None:
122 return
123 SimpleXMLRPCServer.process_request(self, request, client_address)
124
125
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 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
5# Copyright (C) 2006 - 2007 Richard Purdie
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20class BBUIHelper:
21 def __init__(self):
22 self.needUpdate = False
23 self.running_tasks = {}
24 self.failed_tasks = {}
25
26 def eventHandler(self, event):
27 if isinstance(event, bb.build.TaskStarted):
28 self.running_tasks["%s %s\n" % (event._package, event._task)] = ""
29 self.needUpdate = True
30 if isinstance(event, bb.build.TaskSucceeded):
31 del self.running_tasks["%s %s\n" % (event._package, event._task)]
32 self.needUpdate = True
33 if isinstance(event, bb.build.TaskFailed):
34 del self.running_tasks["%s %s\n" % (event._package, event._task)]
35 self.failed_tasks["%s %s\n" % (event._package, event._task)] = ""
36 self.needUpdate = True
37
38 # Add runqueue event handling
39 #if isinstance(event, bb.runqueue.runQueueTaskCompleted):
40 # a = 1
41 #if isinstance(event, bb.runqueue.runQueueTaskStarted):
42 # a = 1
43 #if isinstance(event, bb.runqueue.runQueueTaskFailed):
44 # a = 1
45 #if isinstance(event, bb.runqueue.runQueueExitWait):
46 # a = 1
47
48 def getTasks(self):
49 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
21 21
22digits = "0123456789" 22digits = "0123456789"
23ascii_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 23ascii_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
24separators = ".-"
24 25
25import re, fcntl, os 26import re, fcntl, os, types
26 27
27def explode_version(s): 28def explode_version(s):
28 r = [] 29 r = []
@@ -39,12 +40,15 @@ def explode_version(s):
39 r.append(m.group(1)) 40 r.append(m.group(1))
40 s = m.group(2) 41 s = m.group(2)
41 continue 42 continue
43 r.append(s[0])
42 s = s[1:] 44 s = s[1:]
43 return r 45 return r
44 46
45def vercmp_part(a, b): 47def vercmp_part(a, b):
46 va = explode_version(a) 48 va = explode_version(a)
47 vb = explode_version(b) 49 vb = explode_version(b)
50 sa = False
51 sb = False
48 while True: 52 while True:
49 if va == []: 53 if va == []:
50 ca = None 54 ca = None
@@ -56,6 +60,16 @@ def vercmp_part(a, b):
56 cb = vb.pop(0) 60 cb = vb.pop(0)
57 if ca == None and cb == None: 61 if ca == None and cb == None:
58 return 0 62 return 0
63
64 if type(ca) is types.StringType:
65 sa = ca in separators
66 if type(cb) is types.StringType:
67 sb = cb in separators
68 if sa and not sb:
69 return -1
70 if not sa and sb:
71 return 1
72
59 if ca > cb: 73 if ca > cb:
60 return 1 74 return 1
61 if ca < cb: 75 if ca < cb:
@@ -151,7 +165,7 @@ def better_compile(text, file, realfile):
151 165
152 # split the text into lines again 166 # split the text into lines again
153 body = text.split('\n') 167 body = text.split('\n')
154 bb.msg.error(bb.msg.domain.Util, "Error in compiling: ", realfile) 168 bb.msg.error(bb.msg.domain.Util, "Error in compiling python function in: ", realfile)
155 bb.msg.error(bb.msg.domain.Util, "The lines resulting into this error were:") 169 bb.msg.error(bb.msg.domain.Util, "The lines resulting into this error were:")
156 bb.msg.error(bb.msg.domain.Util, "\t%d:%s:'%s'" % (e.lineno, e.__class__.__name__, body[e.lineno-1])) 170 bb.msg.error(bb.msg.domain.Util, "\t%d:%s:'%s'" % (e.lineno, e.__class__.__name__, body[e.lineno-1]))
157 171
@@ -176,7 +190,7 @@ def better_exec(code, context, text, realfile):
176 raise 190 raise
177 191
178 # print the Header of the Error Message 192 # print the Header of the Error Message
179 bb.msg.error(bb.msg.domain.Util, "Error in executing: %s" % realfile) 193 bb.msg.error(bb.msg.domain.Util, "Error in executing python function in: %s" % realfile)
180 bb.msg.error(bb.msg.domain.Util, "Exception:%s Message:%s" % (t,value) ) 194 bb.msg.error(bb.msg.domain.Util, "Exception:%s Message:%s" % (t,value) )
181 195
182 # let us find the line number now 196 # let us find the line number now