summaryrefslogtreecommitdiffstats
path: root/bitbake-dev/lib/bb/ui/puccho.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake-dev/lib/bb/ui/puccho.py')
-rw-r--r--bitbake-dev/lib/bb/ui/puccho.py426
1 files changed, 426 insertions, 0 deletions
diff --git a/bitbake-dev/lib/bb/ui/puccho.py b/bitbake-dev/lib/bb/ui/puccho.py
new file mode 100644
index 0000000000..a6a613f1cf
--- /dev/null
+++ b/bitbake-dev/lib/bb/ui/puccho.py
@@ -0,0 +1,426 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2008 Intel Corporation
5#
6# Authored by Rob Bradford <rob@linux.intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23import gtk.glade
24import threading
25import urllib2
26import os
27import datetime
28
29from bb.ui.crumbs.buildmanager import BuildManager, BuildConfiguration
30from bb.ui.crumbs.buildmanager import BuildManagerTreeView
31
32from bb.ui.crumbs.runningbuild import RunningBuild, RunningBuildTreeView
33
34# The metadata loader is used by the BuildSetupDialog to download the
35# available options to populate the dialog
36class MetaDataLoader(gobject.GObject):
37 """ This class provides the mechanism for loading the metadata (the
38 fetching and parsing) from a given URL. The metadata encompasses details
39 on what machines are available. The distribution and images available for
40 the machine and the the uris to use for building the given machine."""
41 __gsignals__ = {
42 'success' : (gobject.SIGNAL_RUN_LAST,
43 gobject.TYPE_NONE,
44 ()),
45 'error' : (gobject.SIGNAL_RUN_LAST,
46 gobject.TYPE_NONE,
47 (gobject.TYPE_STRING,))
48 }
49
50 # We use these little helper functions to ensure that we take the gdk lock
51 # when emitting the signal. These functions are called as idles (so that
52 # they happen in the gtk / main thread's main loop.
53 def emit_error_signal (self, remark):
54 gtk.gdk.threads_enter()
55 self.emit ("error", remark)
56 gtk.gdk.threads_leave()
57
58 def emit_success_signal (self):
59 gtk.gdk.threads_enter()
60 self.emit ("success")
61 gtk.gdk.threads_leave()
62
63 def __init__ (self):
64 gobject.GObject.__init__ (self)
65
66 class LoaderThread(threading.Thread):
67 """ This class provides an asynchronous loader for the metadata (by
68 using threads and signals). This is useful since the metadata may be
69 at a remote URL."""
70 class LoaderImportException (Exception):
71 pass
72
73 def __init__(self, loader, url):
74 threading.Thread.__init__ (self)
75 self.url = url
76 self.loader = loader
77
78 def run (self):
79 result = {}
80 try:
81 f = urllib2.urlopen (self.url)
82
83 # Parse the metadata format. The format is....
84 # <machine>;<default distro>|<distro>...;<default image>|<image>...;<type##url>|...
85 for line in f.readlines():
86 components = line.split(";")
87 if (len (components) < 4):
88 raise MetaDataLoader.LoaderThread.LoaderImportException
89 machine = components[0]
90 distros = components[1].split("|")
91 images = components[2].split("|")
92 urls = components[3].split("|")
93
94 result[machine] = (distros, images, urls)
95
96 # Create an object representing this *potential*
97 # configuration. It can become concrete if the machine, distro
98 # and image are all chosen in the UI
99 configuration = BuildConfiguration()
100 configuration.metadata_url = self.url
101 configuration.machine_options = result
102 self.loader.configuration = configuration
103
104 # Emit that we've actually got a configuration
105 gobject.idle_add (MetaDataLoader.emit_success_signal,
106 self.loader)
107
108 except MetaDataLoader.LoaderThread.LoaderImportException, e:
109 gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
110 "Repository metadata corrupt")
111 except Exception, e:
112 gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
113 "Unable to download repository metadata")
114 print e
115
116 def try_fetch_from_url (self, url):
117 # Try and download the metadata. Firing a signal if successful
118 thread = MetaDataLoader.LoaderThread(self, url)
119 thread.start()
120
121class BuildSetupDialog (gtk.Dialog):
122 RESPONSE_BUILD = 1
123
124 # A little helper method that just sets the states on the widgets based on
125 # whether we've got good metadata or not.
126 def set_configurable (self, configurable):
127 if (self.configurable == configurable):
128 return
129
130 self.configurable = configurable
131 for widget in self.conf_widgets:
132 widget.set_sensitive (configurable)
133
134 if not configurable:
135 self.machine_combo.set_active (-1)
136 self.distribution_combo.set_active (-1)
137 self.image_combo.set_active (-1)
138
139 # GTK widget callbacks
140 def refresh_button_clicked (self, button):
141 # Refresh button clicked.
142
143 url = self.location_entry.get_chars (0, -1)
144 self.loader.try_fetch_from_url(url)
145
146 def repository_entry_editable_changed (self, entry):
147 if (len (entry.get_chars (0, -1)) > 0):
148 self.refresh_button.set_sensitive (True)
149 else:
150 self.refresh_button.set_sensitive (False)
151 self.clear_status_message()
152
153 # If we were previously configurable we are no longer since the
154 # location entry has been changed
155 self.set_configurable (False)
156
157 def machine_combo_changed (self, combobox):
158 active_iter = combobox.get_active_iter()
159
160 if not active_iter:
161 return
162
163 model = combobox.get_model()
164
165 if model:
166 chosen_machine = model.get (active_iter, 0)[0]
167
168 (distros_model, images_model) = \
169 self.loader.configuration.get_distro_and_images_models (chosen_machine)
170
171 self.distribution_combo.set_model (distros_model)
172 self.image_combo.set_model (images_model)
173
174 # Callbacks from the loader
175 def loader_success_cb (self, loader):
176 self.status_image.set_from_icon_name ("info",
177 gtk.ICON_SIZE_BUTTON)
178 self.status_image.show()
179 self.status_label.set_label ("Repository metadata successfully downloaded")
180
181 # Set the models on the combo boxes based on the models generated from
182 # the configuration that the loader has created
183
184 # We just need to set the machine here, that then determines the
185 # distro and image options. Cunning huh? :-)
186
187 self.configuration = self.loader.configuration
188 model = self.configuration.get_machines_model ()
189 self.machine_combo.set_model (model)
190
191 self.set_configurable (True)
192
193 def loader_error_cb (self, loader, message):
194 self.status_image.set_from_icon_name ("error",
195 gtk.ICON_SIZE_BUTTON)
196 self.status_image.show()
197 self.status_label.set_text ("Error downloading repository metadata")
198 for widget in self.conf_widgets:
199 widget.set_sensitive (False)
200
201 def clear_status_message (self):
202 self.status_image.hide()
203 self.status_label.set_label (
204 """<i>Enter the repository location and press _Refresh</i>""")
205
206 def __init__ (self):
207 gtk.Dialog.__init__ (self)
208
209 # Cancel
210 self.add_button (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
211
212 # Build
213 button = gtk.Button ("_Build", None, True)
214 image = gtk.Image ()
215 image.set_from_stock (gtk.STOCK_EXECUTE,gtk.ICON_SIZE_BUTTON)
216 button.set_image (image)
217 self.add_action_widget (button, BuildSetupDialog.RESPONSE_BUILD)
218 button.show_all ()
219
220 # Pull in *just* the table from the Glade XML data.
221 gxml = gtk.glade.XML ("bitbake-dev/lib/bb/ui/crumbs/puccho.glade",
222 root = "build_table")
223 table = gxml.get_widget ("build_table")
224 self.vbox.pack_start (table, True, False, 0)
225
226 # Grab all the widgets that we need to turn on/off when we refresh...
227 self.conf_widgets = []
228 self.conf_widgets += [gxml.get_widget ("machine_label")]
229 self.conf_widgets += [gxml.get_widget ("distribution_label")]
230 self.conf_widgets += [gxml.get_widget ("image_label")]
231 self.conf_widgets += [gxml.get_widget ("machine_combo")]
232 self.conf_widgets += [gxml.get_widget ("distribution_combo")]
233 self.conf_widgets += [gxml.get_widget ("image_combo")]
234
235 # Grab the status widgets
236 self.status_image = gxml.get_widget ("status_image")
237 self.status_label = gxml.get_widget ("status_label")
238
239 # Grab the refresh button and connect to the clicked signal
240 self.refresh_button = gxml.get_widget ("refresh_button")
241 self.refresh_button.connect ("clicked", self.refresh_button_clicked)
242
243 # Grab the location entry and connect to editable::changed
244 self.location_entry = gxml.get_widget ("location_entry")
245 self.location_entry.connect ("changed",
246 self.repository_entry_editable_changed)
247
248 # Grab the machine combo and hook onto the changed signal. This then
249 # allows us to populate the distro and image combos
250 self.machine_combo = gxml.get_widget ("machine_combo")
251 self.machine_combo.connect ("changed", self.machine_combo_changed)
252
253 # Setup the combo
254 cell = gtk.CellRendererText()
255 self.machine_combo.pack_start(cell, True)
256 self.machine_combo.add_attribute(cell, 'text', 0)
257
258 # Grab the distro and image combos. We need these to populate with
259 # models once the machine is chosen
260 self.distribution_combo = gxml.get_widget ("distribution_combo")
261 cell = gtk.CellRendererText()
262 self.distribution_combo.pack_start(cell, True)
263 self.distribution_combo.add_attribute(cell, 'text', 0)
264
265 self.image_combo = gxml.get_widget ("image_combo")
266 cell = gtk.CellRendererText()
267 self.image_combo.pack_start(cell, True)
268 self.image_combo.add_attribute(cell, 'text', 0)
269
270 # Put the default descriptive text in the status box
271 self.clear_status_message()
272
273 # Mark as non-configurable, this is just greys out the widgets the
274 # user can't yet use
275 self.configurable = False
276 self.set_configurable(False)
277
278 # Show the table
279 table.show_all ()
280
281 # The loader and some signals connected to it to update the status
282 # area
283 self.loader = MetaDataLoader()
284 self.loader.connect ("success", self.loader_success_cb)
285 self.loader.connect ("error", self.loader_error_cb)
286
287 def update_configuration (self):
288 """ A poorly named function but it updates the internal configuration
289 from the widgets. This can make that configuration concrete and can
290 thus be used for building """
291 # Extract the chosen machine from the combo
292 model = self.machine_combo.get_model()
293 active_iter = self.machine_combo.get_active_iter()
294 if (active_iter):
295 self.configuration.machine = model.get(active_iter, 0)[0]
296
297 # Extract the chosen distro from the combo
298 model = self.distribution_combo.get_model()
299 active_iter = self.distribution_combo.get_active_iter()
300 if (active_iter):
301 self.configuration.distro = model.get(active_iter, 0)[0]
302
303 # Extract the chosen image from the combo
304 model = self.image_combo.get_model()
305 active_iter = self.image_combo.get_active_iter()
306 if (active_iter):
307 self.configuration.image = model.get(active_iter, 0)[0]
308
309# This function operates to pull events out from the event queue and then push
310# them into the RunningBuild (which then drives the RunningBuild which then
311# pushes through and updates the progress tree view.)
312#
313# TODO: Should be a method on the RunningBuild class
314def event_handle_timeout (eventHandler, build):
315 # Consume as many messages as we can ...
316 event = eventHandler.getEvent()
317 while event:
318 build.handle_event (event)
319 event = eventHandler.getEvent()
320 return True
321
322class MainWindow (gtk.Window):
323
324 # Callback that gets fired when the user hits a button in the
325 # BuildSetupDialog.
326 def build_dialog_box_response_cb (self, dialog, response_id):
327 conf = None
328 if (response_id == BuildSetupDialog.RESPONSE_BUILD):
329 dialog.update_configuration()
330 print dialog.configuration.machine, dialog.configuration.distro, \
331 dialog.configuration.image
332 conf = dialog.configuration
333
334 dialog.destroy()
335
336 if conf:
337 self.manager.do_build (conf)
338
339 def build_button_clicked_cb (self, button):
340 dialog = BuildSetupDialog ()
341
342 # For some unknown reason Dialog.run causes nice little deadlocks ... :-(
343 dialog.connect ("response", self.build_dialog_box_response_cb)
344 dialog.show()
345
346 def __init__ (self):
347 gtk.Window.__init__ (self)
348
349 # Pull in *just* the main vbox from the Glade XML data and then pack
350 # that inside the window
351 gxml = gtk.glade.XML ("bitbake-dev/lib/bb/ui/crumbs/puccho.glade",
352 root = "main_window_vbox")
353 vbox = gxml.get_widget ("main_window_vbox")
354 self.add (vbox)
355
356 # Create the tree views for the build manager view and the progress view
357 self.build_manager_view = BuildManagerTreeView()
358 self.running_build_view = RunningBuildTreeView()
359
360 # Grab the scrolled windows that we put the tree views into
361 self.results_scrolledwindow = gxml.get_widget ("results_scrolledwindow")
362 self.progress_scrolledwindow = gxml.get_widget ("progress_scrolledwindow")
363
364 # Put the tree views inside ...
365 self.results_scrolledwindow.add (self.build_manager_view)
366 self.progress_scrolledwindow.add (self.running_build_view)
367
368 # Hook up the build button...
369 self.build_button = gxml.get_widget ("main_toolbutton_build")
370 self.build_button.connect ("clicked", self.build_button_clicked_cb)
371
372# I'm not very happy about the current ownership of the RunningBuild. I have
373# my suspicions that this object should be held by the BuildManager since we
374# care about the signals in the manager
375
376def running_build_succeeded_cb (running_build, manager):
377 # Notify the manager that a build has succeeded. This is necessary as part
378 # of the 'hack' that we use for making the row in the model / view
379 # representing the ongoing build change into a row representing the
380 # completed build. Since we know only one build can be running a time then
381 # we can handle this.
382
383 # FIXME: Refactor all this so that the RunningBuild is owned by the
384 # BuildManager. It can then hook onto the signals directly and drive
385 # interesting things it cares about.
386 manager.notify_build_succeeded ()
387 print "build succeeded"
388
389def running_build_failed_cb (running_build, manager):
390 # As above
391 print "build failed"
392 manager.notify_build_failed ()
393
394def init (server, eventHandler):
395 # Initialise threading...
396 gobject.threads_init()
397 gtk.gdk.threads_init()
398
399 main_window = MainWindow ()
400 main_window.show_all ()
401
402 # Set up the build manager stuff in general
403 builds_dir = os.path.join (os.getcwd(), "results")
404 manager = BuildManager (server, builds_dir)
405 main_window.build_manager_view.set_model (manager.model)
406
407 # Do the running build setup
408 running_build = RunningBuild ()
409 main_window.running_build_view.set_model (running_build.model)
410 running_build.connect ("build-succeeded", running_build_succeeded_cb,
411 manager)
412 running_build.connect ("build-failed", running_build_failed_cb, manager)
413
414 # We need to save the manager into the MainWindow so that the toolbar
415 # button can use it.
416 # FIXME: Refactor ?
417 main_window.manager = manager
418
419 # Use a timeout function for probing the event queue to find out if we
420 # have a message waiting for us.
421 gobject.timeout_add (200,
422 event_handle_timeout,
423 eventHandler,
424 running_build)
425
426 gtk.main()