diff options
author | Richard Purdie <rpurdie@linux.intel.com> | 2010-01-20 18:46:02 +0000 |
---|---|---|
committer | Richard Purdie <rpurdie@linux.intel.com> | 2010-01-20 18:46:02 +0000 |
commit | 22c29d8651668195f72e2f6a8e059d625eb511c3 (patch) | |
tree | dd1dd43f0ec47a9964c8a766eb8b3ad75cf51a64 /bitbake/lib/bb | |
parent | 1bfd6edef9db9c9175058ae801d1b601e4f15263 (diff) | |
download | poky-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')
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 | ||
28 | from bb import data, fetch, event, mkdirhier, utils | 28 | from bb import data, event, mkdirhier, utils |
29 | import bb, os | 29 | import 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 |
39 | class FuncFailed(Exception): | 39 | class 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 | ||
42 | class EventException(Exception): | 46 | class 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 | ||
69 | class TaskFailed(TaskBase): | 75 | class 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 | ||
72 | class InvalidTask(TaskBase): | 82 | class 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 | ||
130 | def 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 | |||
203 | def 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 | ||
142 | def exec_func_shell(func, d, flags): | 226 | def 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 | ||
267 | def exec_task(task, d): | 271 | def 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 | """ | ||
2 | BitBake 'Command' module | ||
3 | |||
4 | Provide 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 | """ | ||
23 | The bitbake server takes 'commands' from its UI/commandline. | ||
24 | Commands are either synchronous or asynchronous. | ||
25 | Async commands return data to the client in the form of events. | ||
26 | Sync commands must only return data through the function return value | ||
27 | and must not trigger events, directly or indirectly. | ||
28 | Commands are queued in a CommandQueue | ||
29 | """ | ||
30 | |||
31 | import bb | ||
32 | |||
33 | async_cmds = {} | ||
34 | sync_cmds = {} | ||
35 | |||
36 | class 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 | |||
103 | class 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 | |||
148 | class 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 | # | ||
246 | class CookerCommandCompleted(bb.event.Event): | ||
247 | """ | ||
248 | Cooker command completed | ||
249 | """ | ||
250 | def __init__(self): | ||
251 | bb.event.Event.__init__(self) | ||
252 | |||
253 | |||
254 | class 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 | |||
262 | class 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 @@ | |||
25 | import sys, os, getopt, glob, copy, os.path, re, time | 25 | import sys, os, getopt, glob, copy, os.path, re, time |
26 | import bb | 26 | import bb |
27 | from bb import utils, data, parse, event, cache, providers, taskdata, runqueue | 27 | from bb import utils, data, parse, event, cache, providers, taskdata, runqueue |
28 | from bb import command | ||
29 | import bb.server.xmlrpc | ||
28 | import itertools, sre_constants | 30 | import itertools, sre_constants |
29 | 31 | ||
30 | parsespin = itertools.cycle( r'|/-\\' ) | 32 | class MultipleMatches(Exception): |
33 | """ | ||
34 | Exception raised when multiple file matches are found | ||
35 | """ | ||
36 | |||
37 | class ParsingErrorsFound(Exception): | ||
38 | """ | ||
39 | Exception raised when parsing errors are found | ||
40 | """ | ||
41 | |||
42 | class NothingToBuild(Exception): | ||
43 | """ | ||
44 | Exception raised when there is nothing to build | ||
45 | """ | ||
46 | |||
47 | |||
48 | # Different states cooker can be in | ||
49 | cookerClean = 1 | ||
50 | cookerParsing = 2 | ||
51 | cookerParsed = 3 | ||
52 | |||
53 | # Different action states the cooker can be in | ||
54 | cookerRun = 1 # Cooker is running normally | ||
55 | cookerShutdown = 2 # Active tasks should be brought to a controlled stop | ||
56 | cookerStop = 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 | |||
909 | class 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 | |||
917 | class 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 | """ | ||
2 | Python Deamonizing helper | ||
3 | |||
4 | Configurable 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 | |||
11 | A failed call to fork() now raises an exception. | ||
12 | |||
13 | References: | ||
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 | |||
18 | Modified to allow a function to be daemonized and return for | ||
19 | bitbake 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. | ||
27 | import os # Miscellaneous OS interfaces. | ||
28 | import 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. | ||
33 | UMASK = None | ||
34 | |||
35 | # Default maximum for the number of available file descriptors. | ||
36 | MAXFD = 1024 | ||
37 | |||
38 | # The standard I/O file descriptors are redirected to /dev/null by default. | ||
39 | if (hasattr(os, "devnull")): | ||
40 | REDIRECT_TO = os.devnull | ||
41 | else: | ||
42 | REDIRECT_TO = "/dev/null" | ||
43 | |||
44 | def 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 | ||
40 | import sys, os, re, time, types | 40 | import sys, os, re, types |
41 | if sys.argv[0][-5:] == "pydoc": | 41 | if 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])) |
43 | else: | 43 | else: |
diff --git a/bitbake/lib/bb/event.py b/bitbake/lib/bb/event.py index 9d7341f878..7251d78715 100644 --- a/bitbake/lib/bb/event.py +++ b/bitbake/lib/bb/event.py | |||
@@ -24,21 +24,18 @@ BitBake build tools. | |||
24 | 24 | ||
25 | import os, re | 25 | import os, re |
26 | import bb.utils | 26 | import bb.utils |
27 | import pickle | ||
28 | |||
29 | # This is the pid for which we should generate the event. This is set when | ||
30 | # the runqueue forks off. | ||
31 | worker_pid = 0 | ||
32 | worker_pipe = None | ||
27 | 33 | ||
28 | class Event: | 34 | class 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 | ||
43 | NotHandled = 0 | 40 | NotHandled = 0 |
44 | Handled = 1 | 41 | Handled = 1 |
@@ -47,75 +44,83 @@ Registered = 10 | |||
47 | AlreadyRegistered = 14 | 44 | AlreadyRegistered = 14 |
48 | 45 | ||
49 | # Internal | 46 | # Internal |
50 | _handlers = [] | 47 | _handlers = {} |
51 | _handlers_dict = {} | 48 | _ui_handlers = {} |
49 | _ui_handler_seq = 0 | ||
52 | 50 | ||
53 | def tmpHandler(event): | 51 | def fire(event, d): |
54 | """Default handler for code events""" | 52 | """Fire off an Event""" |
55 | return NotHandled | ||
56 | 53 | ||
57 | def 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 | ||
62 | def 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 | |||
81 | def 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 | |||
86 | def 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 | ||
74 | def register(name, handler): | 93 | def 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 | ||
91 | def _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 | |||
104 | def remove(name, handler): | 111 | def remove(name, handler): |
105 | """Remove an Event handler""" | 112 | """Remove an Event handler""" |
113 | _handlers.pop(name) | ||
106 | 114 | ||
107 | _handlers_dict.pop(name) | 115 | def 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 | ||
113 | def _removeCode(handlerStr): | 120 | def 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 | ||
120 | def getName(e): | 125 | def 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): | |||
130 | class RecipeParsed(Event): | 135 | class 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 | ||
137 | class StampUpdate(Event): | 142 | class 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 | ||
154 | class 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 | |||
170 | class BuildBase(Event): | 159 | class 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 | ||
208 | class 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 | |||
224 | class PkgStarted(PkgBase): | ||
225 | """Package build started""" | ||
226 | 197 | ||
227 | 198 | ||
228 | class PkgFailed(PkgBase): | ||
229 | """Package build failed""" | ||
230 | |||
231 | |||
232 | class PkgSucceeded(PkgBase): | ||
233 | """Package build completed""" | ||
234 | |||
235 | 199 | ||
236 | class BuildStarted(BuildBase): | 200 | class 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 | ||
244 | class UnsatisfiedDep(DepBase): | ||
245 | """Unsatisfied Dependency""" | ||
246 | 208 | ||
247 | 209 | ||
248 | class RecursiveDep(DepBase): | ||
249 | """Recursive Dependency""" | ||
250 | |||
251 | class NoProvider(Event): | 210 | class 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): | |||
265 | class MultipleProviders(Event): | 224 | class 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 | |||
251 | class 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 | |||
267 | class 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 | ||
29 | class Git(Fetch): | 29 | class 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 | |||
33 | class Local(Fetch): | 33 | class 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 | ||
25 | import sys, os, re, bb | 25 | import sys, bb |
26 | from bb import utils, event | 26 | from bb import event |
27 | 27 | ||
28 | debug_level = {} | 28 | debug_level = {} |
29 | 29 | ||
@@ -47,9 +47,9 @@ domain = bb.utils.Enum( | |||
47 | class MsgBase(bb.event.Event): | 47 | class 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 | ||
54 | class MsgDebug(MsgBase): | 54 | class MsgDebug(MsgBase): |
55 | """Debug Message""" | 55 | """Debug Message""" |
@@ -97,33 +97,29 @@ def set_debug_domains(domains): | |||
97 | # | 97 | # |
98 | 98 | ||
99 | def debug(level, domain, msg, fn = None): | 99 | def 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 | ||
106 | def note(level, domain, msg, fn = None): | 105 | def 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 | ||
113 | def warn(domain, msg, fn = None): | 111 | def 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 | ||
117 | def error(domain, msg, fn = None): | 114 | def 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 | ||
121 | def fatal(domain, msg, fn = None): | 118 | def 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 | ||
126 | def plain(msg, fn = None): | 123 | def 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 | ||
120 | def handle(fn, d, include = 0): | 120 | def 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 | ||
36 | def init(data): | 36 | def 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 | ||
42 | def supports(fn, d): | 49 | def 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 | ||
24 | import os, re | 24 | import re |
25 | from bb import data, utils | 25 | from bb import data, utils |
26 | import bb | 26 | import 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 | ||
64 | runQueuePrepare = 2 | ||
65 | runQueueRunInit = 3 | ||
66 | runQueueRunning = 4 | ||
67 | runQueueFailed = 6 | ||
68 | runQueueCleanUp = 7 | ||
69 | runQueueComplete = 8 | ||
70 | runQueueChildProcess = 9 | ||
71 | |||
54 | class RunQueueScheduler: | 72 | class 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 | ||
1087 | class 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 | |||
1095 | class 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 | |||
1105 | class 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 | |||
1115 | class 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 | |||
1123 | class 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 | |||
1131 | class 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 | |||
1008 | def check_stamp_fn(fn, taskname, d): | 1139 | def 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 | ||
1147 | class 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 @@ | |||
1 | import xmlrpc | ||
2 | import none | ||
diff --git a/bitbake/lib/bb/server/none.py b/bitbake/lib/bb/server/none.py new file mode 100644 index 0000000000..ebda111582 --- /dev/null +++ b/bitbake/lib/bb/server/none.py | |||
@@ -0,0 +1,181 @@ | |||
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 | |||
34 | import time | ||
35 | import bb | ||
36 | from bb.ui import uievent | ||
37 | import xmlrpclib | ||
38 | import pickle | ||
39 | |||
40 | DEBUG = False | ||
41 | |||
42 | from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler | ||
43 | import inspect, select | ||
44 | |||
45 | class 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 | |||
71 | eventQueue = [] | ||
72 | |||
73 | class 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 | |||
108 | class 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 | |||
156 | class BitbakeServerInfo(): | ||
157 | def __init__(self, server): | ||
158 | self.server = server | ||
159 | self.commands = server.commands | ||
160 | |||
161 | class BitBakeServerFork(): | ||
162 | def __init__(self, serverinfo, command, logfile): | ||
163 | serverinfo.forkCommand = command | ||
164 | serverinfo.logfile = logfile | ||
165 | |||
166 | class 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 | |||
34 | import bb | ||
35 | import xmlrpclib, sys | ||
36 | from bb import daemonize | ||
37 | from bb.ui import uievent | ||
38 | |||
39 | DEBUG = False | ||
40 | |||
41 | from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler | ||
42 | import inspect, select | ||
43 | |||
44 | if sys.hexversion < 0x020600F0: | ||
45 | print "Sorry, python 2.6 or later is required for bitbake's XMLRPC mode" | ||
46 | sys.exit(1) | ||
47 | |||
48 | class 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 | |||
86 | class 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 | |||
161 | class BitbakeServerInfo(): | ||
162 | def __init__(self, server): | ||
163 | self.host = server.host | ||
164 | self.port = server.port | ||
165 | |||
166 | class BitBakeServerFork(): | ||
167 | def __init__(self, serverinfo, command, logfile): | ||
168 | daemonize.createDaemon(command, logfile) | ||
169 | |||
170 | class 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 | ||
26 | from bb import data, event, mkdirhier, utils | 26 | import bb |
27 | import bb, os | 27 | |
28 | def 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 | ||
29 | class TaskData: | 41 | class 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 | |||
21 | import gtk | ||
22 | import gobject | ||
23 | import threading | ||
24 | import os | ||
25 | import datetime | ||
26 | import time | ||
27 | |||
28 | class 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 | |||
136 | class 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 | |||
187 | class 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 | |||
205 | class 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 | |||
391 | class 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"><b>Build configuration</b></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"><b>Repository</b></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"><b>Repositories</b></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"><b>Additional packages</b></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 | |||
21 | import gtk | ||
22 | import gobject | ||
23 | |||
24 | class 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 | |||
35 | class 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 | |||
165 | class 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 | |||
20 | import gobject | ||
21 | import gtk | ||
22 | import threading | ||
23 | import 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 | |||
32 | class 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 | |||
52 | class 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 | |||
69 | class 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 | |||
155 | def 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 | |||
174 | class 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 | |||
186 | class 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 | |||
199 | def 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 | |||
21 | import gobject | ||
22 | import gtk | ||
23 | import xmlrpclib | ||
24 | from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild | ||
25 | |||
26 | def 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 | |||
36 | class 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 | |||
46 | def 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 | |||
21 | import os | ||
22 | |||
23 | import sys | ||
24 | import itertools | ||
25 | import xmlrpclib | ||
26 | |||
27 | parsespin = itertools.cycle( r'|/-\\' ) | ||
28 | |||
29 | def 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 | |||
47 | import os, sys, curses, itertools, time | ||
48 | import bb | ||
49 | import xmlrpclib | ||
50 | from bb import ui | ||
51 | from bb.ui import uihelper | ||
52 | |||
53 | parsespin = itertools.cycle( r'|/-\\' ) | ||
54 | |||
55 | X = 0 | ||
56 | Y = 1 | ||
57 | WIDTH = 2 | ||
58 | HEIGHT = 3 | ||
59 | |||
60 | MAXSTATUSLENGTH = 32 | ||
61 | |||
62 | class 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 | |||
325 | def 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 | |||
21 | import gtk | ||
22 | import gobject | ||
23 | import gtk.glade | ||
24 | import threading | ||
25 | import urllib2 | ||
26 | import os | ||
27 | |||
28 | from bb.ui.crumbs.buildmanager import BuildManager, BuildConfiguration | ||
29 | from bb.ui.crumbs.buildmanager import BuildManagerTreeView | ||
30 | |||
31 | from 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 | ||
35 | class 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 | |||
120 | class 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 | ||
313 | def 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 | |||
321 | class 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 | |||
375 | def 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 | |||
388 | def running_build_failed_cb (running_build, manager): | ||
389 | # As above | ||
390 | print "build failed" | ||
391 | manager.notify_build_failed () | ||
392 | |||
393 | def 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 | """ | ||
22 | Use this class to fork off a thread to recieve event callbacks from the bitbake | ||
23 | server and queue them for the UI to process. This process must be used to avoid | ||
24 | client/server deadlocks. | ||
25 | """ | ||
26 | |||
27 | import socket, threading, pickle | ||
28 | from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler | ||
29 | |||
30 | class 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 | |||
96 | class 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 | |||
20 | class 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 | ||
22 | digits = "0123456789" | 22 | digits = "0123456789" |
23 | ascii_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | 23 | ascii_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
24 | separators = ".-" | ||
24 | 25 | ||
25 | import re, fcntl, os | 26 | import re, fcntl, os, types |
26 | 27 | ||
27 | def explode_version(s): | 28 | def 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 | ||
45 | def vercmp_part(a, b): | 47 | def 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 |