lib/bb/ui/crumbs: tweak build status display
[bitbake.git] / lib / bb / ui / crumbs / builder.py
1 #!/usr/bin/env python
2 #
3 # BitBake Graphical GTK User Interface
4 #
5 # Copyright (C) 2011-2012   Intel Corporation
6 #
7 # Authored by Joshua Lock <josh@linux.intel.com>
8 # Authored by Dongxiao Xu <dongxiao.xu@intel.com>
9 # Authored by Shane Wang <shane.wang@intel.com>
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License version 2 as
13 # published by the Free Software Foundation.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License along
21 # with this program; if not, write to the Free Software Foundation, Inc.,
22 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24 import gtk
25 import copy
26 import os
27 import subprocess
28 import shlex
29 from bb.ui.crumbs.template import TemplateMgr
30 from bb.ui.crumbs.imageconfigurationpage import ImageConfigurationPage
31 from bb.ui.crumbs.recipeselectionpage import RecipeSelectionPage
32 from bb.ui.crumbs.packageselectionpage import PackageSelectionPage
33 from bb.ui.crumbs.builddetailspage import BuildDetailsPage
34 from bb.ui.crumbs.imagedetailspage import ImageDetailsPage
35 from bb.ui.crumbs.hobwidget import hwc, HobButton, HobAltButton
36 from bb.ui.crumbs.hig import CrumbsMessageDialog, ImageSelectionDialog, \
37                              AdvancedSettingDialog, LayerSelectionDialog, \
38                              DeployImageDialog
39 from bb.ui.crumbs.persistenttooltip import PersistentTooltip
40
41 class Configuration:
42     '''Represents the data structure of configuration.'''
43
44     def __init__(self, params):
45         # Settings
46         self.curr_mach = ""
47         self.curr_distro = params["distro"]
48         self.dldir = params["dldir"]
49         self.sstatedir = params["sstatedir"]
50         self.sstatemirror = params["sstatemirror"]
51         self.pmake = params["pmake"]
52         self.bbthread = params["bbthread"]
53         self.curr_package_format = " ".join(params["pclass"].split("package_")).strip()
54         self.image_rootfs_size = params["image_rootfs_size"]
55         self.image_extra_size = params["image_extra_size"]
56         self.image_overhead_factor = params['image_overhead_factor']
57         self.incompat_license = params["incompat_license"]
58         self.curr_sdk_machine = params["sdk_machine"]
59         self.conf_version = params["conf_version"]
60         self.lconf_version = params["lconf_version"]
61         self.extra_setting = {}
62         self.toolchain_build = False
63         self.image_fstypes = params["image_fstypes"].split()
64         # bblayers.conf
65         self.layers = params["layer"].split()
66         # image/recipes/packages
67         self.selected_image = None
68         self.selected_recipes = []
69         self.selected_packages = []
70
71     def load(self, template):
72         self.curr_mach = template.getVar("MACHINE")
73         self.curr_package_format = " ".join(template.getVar("PACKAGE_CLASSES").split("package_")).strip()
74         self.curr_distro = template.getVar("DISTRO")
75         self.dldir = template.getVar("DL_DIR")
76         self.sstatedir = template.getVar("SSTATE_DIR")
77         self.sstatemirror = template.getVar("SSTATE_MIRROR")
78         self.pmake = int(template.getVar("PARALLEL_MAKE").split()[1])
79         self.bbthread = int(template.getVar("BB_NUMBER_THREADS"))
80         self.image_rootfs_size = int(template.getVar("IMAGE_ROOTFS_SIZE"))
81         self.image_extra_size = int(template.getVar("IMAGE_EXTRA_SPACE"))
82         # image_overhead_factor is read-only.
83         self.incompat_license = template.getVar("INCOMPATIBLE_LICENSE")
84         self.curr_sdk_machine = template.getVar("SDKMACHINE")
85         self.conf_version = template.getVar("CONF_VERSION")
86         self.lconf_version = template.getVar("LCONF_VERSION")
87         self.extra_setting = eval(template.getVar("EXTRA_SETTING"))
88         self.toolchain_build = eval(template.getVar("TOOLCHAIN_BUILD"))
89         self.image_fstypes = template.getVar("IMAGE_FSTYPES").split()
90         # bblayers.conf
91         self.layers = template.getVar("BBLAYERS").split()
92         # image/recipes/packages
93         self.selected_image = template.getVar("__SELECTED_IMAGE__")
94         self.selected_recipes = template.getVar("DEPENDS").split()
95         self.selected_packages = template.getVar("IMAGE_INSTALL").split()
96
97     def save(self, template, filename):
98         # bblayers.conf
99         template.setVar("BBLAYERS", " ".join(self.layers))
100         # local.conf
101         template.setVar("MACHINE", self.curr_mach)
102         template.setVar("DISTRO", self.curr_distro)
103         template.setVar("DL_DIR", self.dldir)
104         template.setVar("SSTATE_DIR", self.sstatedir)
105         template.setVar("SSTATE_MIRROR", self.sstatemirror)
106         template.setVar("PARALLEL_MAKE", "-j %s" % self.pmake)
107         template.setVar("BB_NUMBER_THREADS", self.bbthread)
108         template.setVar("PACKAGE_CLASSES", " ".join(["package_" + i for i in self.curr_package_format.split()]))
109         template.setVar("IMAGE_ROOTFS_SIZE", self.image_rootfs_size)
110         template.setVar("IMAGE_EXTRA_SPACE", self.image_extra_size)
111         template.setVar("INCOMPATIBLE_LICENSE", self.incompat_license)
112         template.setVar("SDKMACHINE", self.curr_sdk_machine)
113         template.setVar("CONF_VERSION", self.conf_version)
114         template.setVar("LCONF_VERSION", self.lconf_version)
115         template.setVar("EXTRA_SETTING", self.extra_setting)
116         template.setVar("TOOLCHAIN_BUILD", self.toolchain_build)
117         template.setVar("IMAGE_FSTYPES", " ".join(self.image_fstypes).lstrip(" "))
118         # image/recipes/packages
119         self.selected_image = filename
120         template.setVar("__SELECTED_IMAGE__", self.selected_image)
121         template.setVar("DEPENDS", self.selected_recipes)
122         template.setVar("IMAGE_INSTALL", self.selected_packages)
123
124 class Parameters:
125     '''Represents other variables like available machines, etc.'''
126
127     def __init__(self, params):
128         # Variables
129         self.all_machines = []
130         self.all_package_formats = []
131         self.all_distros = []
132         self.all_sdk_machines = []
133         self.max_threads = params["max_threads"]
134         self.all_layers = []
135         self.core_base = params["core_base"]
136         self.image_names = []
137         self.image_addr = params["image_addr"]
138         self.image_types = params["image_types"].split()
139         self.runnable_image_types = params["runnable_image_types"].split()
140         self.runnable_machine_patterns = params["runnable_machine_patterns"].split()
141         self.deployable_image_types = params["deployable_image_types"].split()
142         self.tmpdir = params["tmpdir"]
143
144 class Builder(gtk.Window):
145
146     (MACHINE_SELECTION,
147      LAYER_CHANGED,
148      RCPPKGINFO_POPULATING,
149      RCPPKGINFO_POPULATED,
150      BASEIMG_SELECTED,
151      RECIPE_SELECTION,
152      PACKAGE_GENERATING,
153      PACKAGE_GENERATED,
154      PACKAGE_SELECTION,
155      FAST_IMAGE_GENERATING,
156      IMAGE_GENERATING,
157      IMAGE_GENERATED,
158      MY_IMAGE_OPENED,
159      BACK,
160      END_NOOP) = range(15)
161
162     (IMAGE_CONFIGURATION,
163      RECIPE_DETAILS,
164      BUILD_DETAILS,
165      PACKAGE_DETAILS,
166      IMAGE_DETAILS,
167      END_TAB) = range(6)
168
169     __step2page__ = {
170         MACHINE_SELECTION     : IMAGE_CONFIGURATION,
171         LAYER_CHANGED         : IMAGE_CONFIGURATION,
172         RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION,
173         RCPPKGINFO_POPULATED  : IMAGE_CONFIGURATION,
174         BASEIMG_SELECTED      : IMAGE_CONFIGURATION,
175         RECIPE_SELECTION      : RECIPE_DETAILS,
176         PACKAGE_GENERATING    : BUILD_DETAILS,
177         PACKAGE_GENERATED     : PACKAGE_DETAILS,
178         PACKAGE_SELECTION     : PACKAGE_DETAILS,
179         FAST_IMAGE_GENERATING : BUILD_DETAILS,
180         IMAGE_GENERATING      : BUILD_DETAILS,
181         IMAGE_GENERATED       : IMAGE_DETAILS,
182         MY_IMAGE_OPENED       : IMAGE_DETAILS,
183         END_NOOP              : None,
184     }
185
186     def __init__(self, hobHandler, recipe_model, package_model):
187         super(Builder, self).__init__()
188
189         # handler
190         self.handler = hobHandler
191
192         self.template = None
193
194         # build step
195         self.current_step = None
196         self.previous_step = None
197
198         self.stopping = False
199
200         # recipe model and package model
201         self.recipe_model = recipe_model
202         self.package_model = package_model
203
204         # create visual elements
205         self.create_visual_elements()
206
207         # connect the signals to functions
208         self.connect("delete-event", self.destroy_window_cb)
209         self.recipe_model.connect ("recipe-selection-changed",  self.recipelist_changed_cb)
210         self.package_model.connect("package-selection-changed", self.packagelist_changed_cb)
211         self.handler.connect("config-updated",           self.handler_config_updated_cb)
212         self.handler.connect("package-formats-updated",  self.handler_package_formats_updated_cb)
213         self.handler.connect("layers-updated",           self.handler_layers_updated_cb)
214         self.handler.connect("parsing-started",          self.handler_parsing_started_cb)
215         self.handler.connect("parsing",                  self.handler_parsing_cb)
216         self.handler.connect("parsing-completed",        self.handler_parsing_completed_cb)
217         self.handler.build.connect("build-started",      self.handler_build_started_cb)
218         self.handler.build.connect("build-succeeded",    self.handler_build_succeeded_cb)
219         self.handler.build.connect("build-failed",       self.handler_build_failed_cb)
220         self.handler.build.connect("task-started",       self.handler_task_started_cb)
221         self.handler.build.connect("log-error",          self.handler_build_failure_cb)
222         self.handler.connect("generating-data",          self.handler_generating_data_cb)
223         self.handler.connect("data-generated",           self.handler_data_generated_cb)
224         self.handler.connect("command-succeeded",        self.handler_command_succeeded_cb)
225         self.handler.connect("command-failed",           self.handler_command_failed_cb)
226
227         self.handler.init_cooker()
228         self.handler.set_extra_inherit("image_types")
229         self.handler.parse_config()
230
231         self.switch_page(self.MACHINE_SELECTION)
232
233     def create_visual_elements(self):
234         self.set_title("Hob")
235         self.set_icon_name("applications-development")
236         self.set_resizable(True)
237         window_width = self.get_screen().get_width()
238         window_height = self.get_screen().get_height()
239         if window_width >= hwc.MAIN_WIN_WIDTH:
240             window_width = hwc.MAIN_WIN_WIDTH
241             window_height = hwc.MAIN_WIN_HEIGHT
242         self.set_size_request(window_width, window_height)
243
244         self.vbox = gtk.VBox(False, 0)
245         self.vbox.set_border_width(0)
246         self.add(self.vbox)
247
248         # create pages
249         self.image_configuration_page = ImageConfigurationPage(self)
250         self.recipe_details_page      = RecipeSelectionPage(self)
251         self.build_details_page       = BuildDetailsPage(self)
252         self.package_details_page     = PackageSelectionPage(self)
253         self.image_details_page       = ImageDetailsPage(self)
254
255         self.nb = gtk.Notebook()
256         self.nb.set_show_tabs(False)
257         self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION)
258         self.nb.insert_page(self.recipe_details_page,      None, self.RECIPE_DETAILS)
259         self.nb.insert_page(self.build_details_page,       None, self.BUILD_DETAILS)
260         self.nb.insert_page(self.package_details_page,     None, self.PACKAGE_DETAILS)
261         self.nb.insert_page(self.image_details_page,       None, self.IMAGE_DETAILS)
262         self.vbox.pack_start(self.nb, expand=True, fill=True)
263
264         self.show_all()
265         self.nb.set_current_page(0)
266
267     def load_template(self, path):
268         self.template = TemplateMgr()
269         self.template.load(path)
270         self.configuration.load(self.template)
271
272         for layer in self.configuration.layers:
273             if not os.path.exists(layer+'/conf/layer.conf'):
274                 return False
275
276         self.switch_page(self.LAYER_CHANGED)
277
278         self.template.destroy()
279         self.template = None
280
281     def save_template(self, path):
282         if path.rfind("/") == -1:
283             filename = "default"
284             path = "."
285         else:
286             filename = path[path.rfind("/") + 1:len(path)]
287             path = path[0:path.rfind("/")]
288
289         self.template = TemplateMgr()
290         self.template.open(filename, path)
291         self.configuration.save(self.template, filename)
292
293         self.template.save()
294         self.template.destroy()
295         self.template = None
296
297     def switch_page(self, next_step):
298         # Main Workflow (Business Logic)
299         self.nb.set_current_page(self.__step2page__[next_step])
300
301         if next_step == self.MACHINE_SELECTION: # init step
302             self.image_configuration_page.show_machine()
303
304         elif next_step == self.LAYER_CHANGED:
305             # after layers is changd by users
306             self.image_configuration_page.show_machine()
307             self.handler.refresh_layers(self.configuration.layers)
308
309         elif next_step == self.RCPPKGINFO_POPULATING:
310             # MACHINE CHANGED action or SETTINGS CHANGED
311             # show the progress bar
312             self.image_configuration_page.show_info_populating()
313             self.generate_recipes()
314
315         elif next_step == self.RCPPKGINFO_POPULATED:
316             self.image_configuration_page.show_info_populated()
317
318         elif next_step == self.BASEIMG_SELECTED:
319             self.image_configuration_page.show_baseimg_selected()
320
321         elif next_step == self.RECIPE_SELECTION:
322             pass
323
324         elif next_step == self.PACKAGE_SELECTION:
325             pass
326
327         elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING:
328             # both PACKAGE_GENEATING and FAST_IMAGE_GENERATING share the same page
329             self.build_details_page.show_page(next_step)
330             self.generate_packages()
331
332         elif next_step == self.PACKAGE_GENERATED:
333             pass
334
335         elif next_step == self.IMAGE_GENERATING:
336             # after packages are generated, selected_packages need to
337             # be updated in package_model per selected_image in recipe_model
338             self.build_details_page.show_page(next_step)
339             self.generate_image()
340
341         elif next_step == self.IMAGE_GENERATED:
342             self.image_details_page.show_page(next_step)
343
344         elif next_step == self.MY_IMAGE_OPENED:
345             self.image_details_page.show_page(next_step)
346
347         self.previous_step = self.current_step
348         self.current_step = next_step
349
350     def set_user_config(self):
351         self.handler.init_cooker()
352         # set bb layers
353         self.handler.set_bblayers(self.configuration.layers)
354         # set local configuration
355         self.handler.set_machine(self.configuration.curr_mach)
356         self.handler.set_package_format(self.configuration.curr_package_format)
357         self.handler.set_distro(self.configuration.curr_distro)
358         self.handler.set_dl_dir(self.configuration.dldir)
359         self.handler.set_sstate_dir(self.configuration.sstatedir)
360         self.handler.set_sstate_mirror(self.configuration.sstatemirror)
361         self.handler.set_pmake(self.configuration.pmake)
362         self.handler.set_bbthreads(self.configuration.bbthread)
363         self.handler.set_rootfs_size(self.configuration.image_rootfs_size)
364         self.handler.set_extra_size(self.configuration.image_extra_size)
365         self.handler.set_incompatible_license(self.configuration.incompat_license)
366         self.handler.set_sdk_machine(self.configuration.curr_sdk_machine)
367         self.handler.set_image_fstypes(self.configuration.image_fstypes)
368         self.handler.set_extra_config(self.configuration.extra_setting)
369         self.handler.set_extra_inherit("packageinfo")
370
371     def update_recipe_model(self, selected_image, selected_recipes):
372         self.recipe_model.set_selected_image(selected_image)
373         self.recipe_model.set_selected_recipes(selected_recipes)
374
375     def update_package_model(self, selected_packages):
376         left = self.package_model.set_selected_packages(selected_packages)
377         self.configuration.selected_packages += left
378
379     def generate_packages(self):
380         # Build packages
381         _, all_recipes = self.recipe_model.get_selected_recipes()
382         self.set_user_config()
383         self.handler.reset_build()
384         self.handler.generate_packages(all_recipes)
385
386     def generate_recipes(self):
387         # Parse recipes
388         self.set_user_config()
389         self.handler.generate_recipes()
390
391     def generate_image(self):
392         # Build image
393         self.set_user_config()
394         all_packages = self.package_model.get_selected_packages()
395         self.handler.reset_build()
396         self.handler.generate_image(all_packages, self.configuration.toolchain_build)
397
398
399     # Callback Functions
400     def handler_config_updated_cb(self, handler, which, values):
401         if which == "distro":
402             self.parameters.all_distros = values
403         elif which == "machine":
404             self.parameters.all_machines = values
405             self.image_configuration_page.update_machine_combo()
406         elif which == "machine-sdk":
407             self.parameters.all_sdk_machines = values
408
409     def handler_package_formats_updated_cb(self, handler, formats):
410         self.parameters.all_package_formats = formats
411
412     def handler_layers_updated_cb(self, handler, layers):
413         self.parameters.all_layers = layers
414
415     def handler_command_succeeded_cb(self, handler, initcmd):
416         if initcmd == self.handler.PARSE_CONFIG:
417             # settings
418             params = self.handler.get_parameters()
419             self.configuration = Configuration(params)
420             self.parameters = Parameters(params)
421             self.handler.generate_configuration()
422         elif initcmd == self.handler.GENERATE_CONFIGURATION:
423             self.image_configuration_page.switch_machine_combo()
424         elif initcmd in [self.handler.GENERATE_RECIPES,
425                          self.handler.GENERATE_PACKAGES,
426                          self.handler.GENERATE_IMAGE]:
427             self.handler.request_package_info_async()
428         elif initcmd == self.handler.POPULATE_PACKAGEINFO:
429             if self.current_step == self.RCPPKGINFO_POPULATING:
430                 self.switch_page(self.RCPPKGINFO_POPULATED)
431                 self.rcppkglist_populated()
432                 return
433
434             self.rcppkglist_populated()
435             if self.current_step == self.FAST_IMAGE_GENERATING:
436                 self.switch_page(self.IMAGE_GENERATING)
437             elif self.current_step == self.PACKAGE_GENERATING:
438                 self.switch_page(self.PACKAGE_GENERATED)
439             elif self.current_step == self.IMAGE_GENERATING:
440                 self.switch_page(self.IMAGE_GENERATED)
441
442     def handler_command_failed_cb(self, handler, msg):
443         if msg:
444             lbl = "<b>Error</b>\n"
445             lbl = lbl + "%s\n\n" % msg
446             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
447             button = dialog.add_button("Close", gtk.RESPONSE_OK)
448             HobButton.style_button(button)
449             response = dialog.run()
450             dialog.destroy()
451         self.handler.clear_busy()
452         self.configuration.curr_mach = None
453         self.image_configuration_page.switch_machine_combo()
454         self.switch_page(self.MACHINE_SELECTION)
455
456     def window_sensitive(self, sensitive):
457         self.image_configuration_page.machine_combo.set_sensitive(sensitive)
458         self.image_configuration_page.image_combo.set_sensitive(sensitive)
459         self.image_configuration_page.layer_button.set_sensitive(sensitive)
460         self.image_configuration_page.layer_info_icon.set_sensitive(sensitive)
461         self.image_configuration_page.toolbar.set_sensitive(sensitive)
462         self.image_configuration_page.view_recipes_button.set_sensitive(sensitive)
463         self.image_configuration_page.view_packages_button.set_sensitive(sensitive)
464         self.image_configuration_page.config_build_button.set_sensitive(sensitive)
465
466         self.recipe_details_page.set_sensitive(sensitive)
467         self.package_details_page.set_sensitive(sensitive)
468         self.build_details_page.set_sensitive(sensitive)
469         self.image_details_page.set_sensitive(sensitive)
470
471         if sensitive:
472             self.get_root_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
473         else:
474             self.get_root_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
475
476
477     def handler_generating_data_cb(self, handler):
478         self.window_sensitive(False)
479
480     def handler_data_generated_cb(self, handler):
481         self.window_sensitive(True)
482
483     def rcppkglist_populated(self):
484         selected_image = self.configuration.selected_image
485         selected_recipes = self.configuration.selected_recipes[:]
486         selected_packages = self.configuration.selected_packages[:]
487
488         self.recipe_model.image_list_append(selected_image,
489                                             " ".join(selected_recipes),
490                                             " ".join(selected_packages))
491
492         self.image_configuration_page.update_image_combo(self.recipe_model, selected_image)
493         self.update_recipe_model(selected_image, selected_recipes)
494         self.update_package_model(selected_packages)
495
496     def recipelist_changed_cb(self, recipe_model):
497         self.recipe_details_page.refresh_selection()
498
499     def packagelist_changed_cb(self, package_model):
500         self.package_details_page.refresh_selection()
501
502     def handler_parsing_started_cb(self, handler, message):
503         if self.current_step != self.RCPPKGINFO_POPULATING:
504             return
505
506         fraction = 0
507         if message["eventname"] == "TreeDataPreparationStarted":
508             fraction = 0.6 + fraction
509             self.image_configuration_page.stop_button.set_sensitive(False)
510         else:
511             self.image_configuration_page.stop_button.set_sensitive(True)
512
513         self.image_configuration_page.update_progress_bar(message["title"], fraction)
514
515     def handler_parsing_cb(self, handler, message):
516         if self.current_step != self.RCPPKGINFO_POPULATING:
517             return
518
519         fraction = message["current"] * 1.0/message["total"]
520         if message["eventname"] == "TreeDataPreparationProgress":
521             fraction = 0.6 + 0.4 * fraction
522         else:
523             fraction = 0.6 * fraction
524         self.image_configuration_page.update_progress_bar(message["title"], fraction)
525
526     def handler_parsing_completed_cb(self, handler, message):
527         if self.current_step != self.RCPPKGINFO_POPULATING:
528             return
529
530         if message["eventname"] == "TreeDataPreparationCompleted":
531             fraction = 1.0
532         else:
533             fraction = 0.6
534         self.image_configuration_page.update_progress_bar(message["title"], fraction)
535
536     def handler_build_started_cb(self, running_build):
537         if self.current_step == self.FAST_IMAGE_GENERATING:
538             fraction = 0
539         elif self.current_step == self.IMAGE_GENERATING:
540             if self.previous_step == self.FAST_IMAGE_GENERATING:
541                 fraction = 0.9
542             else:
543                 fraction = 0
544         elif self.current_step == self.PACKAGE_GENERATING:
545             fraction = 0
546         self.build_details_page.update_progress_bar("Build Started: ", fraction)
547         self.build_details_page.reset_build_status()
548         self.build_details_page.reset_issues()
549
550     def build_succeeded(self):
551         if self.current_step == self.FAST_IMAGE_GENERATING:
552             fraction = 0.9
553         elif self.current_step == self.IMAGE_GENERATING:
554             fraction = 1.0
555             self.parameters.image_names = []
556             linkname = 'hob-image-' + self.configuration.curr_mach
557             for image_type in self.parameters.image_types:
558                 linkpath = self.parameters.image_addr + '/' + linkname + '.' + image_type
559                 if os.path.exists(linkpath):
560                     self.parameters.image_names.append(os.readlink(linkpath))
561         elif self.current_step == self.PACKAGE_GENERATING:
562             fraction = 1.0
563         self.build_details_page.update_progress_bar("Build Completed: ", fraction)
564         self.stopping = False
565
566     def build_failed(self):
567         if self.current_step == self.FAST_IMAGE_GENERATING:
568             fraction = 0.9
569         elif self.current_step == self.IMAGE_GENERATING:
570             fraction = 1.0
571         elif self.current_step == self.PACKAGE_GENERATING:
572             fraction = 1.0
573         self.build_details_page.update_progress_bar("Build Failed: ", fraction, False)
574         self.build_details_page.show_back_button()
575         self.build_details_page.hide_stop_button()
576         self.handler.build_failed_async()
577         self.stopping = False
578
579     def handler_build_succeeded_cb(self, running_build):
580         if not self.stopping:
581             self.build_succeeded()
582         else:
583             self.build_failed()
584
585
586     def handler_build_failed_cb(self, running_build):
587         self.build_failed()
588
589     def handler_task_started_cb(self, running_build, message): 
590         fraction = message["current"] * 1.0/message["total"]
591         title = "Build packages"
592         if self.current_step == self.FAST_IMAGE_GENERATING:
593             if message["eventname"] == "sceneQueueTaskStarted":
594                 fraction = 0.27 * fraction
595             elif message["eventname"] == "runQueueTaskStarted":
596                 fraction = 0.27 + 0.63 * fraction
597         elif self.current_step == self.IMAGE_GENERATING:
598             title = "Build image"
599             if self.previous_step == self.FAST_IMAGE_GENERATING:
600                 if message["eventname"] == "sceneQueueTaskStarted":
601                     fraction = 0.27 + 0.63 + 0.03 * fraction
602                 elif message["eventname"] == "runQueueTaskStarted":
603                     fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction
604             else:
605                 if message["eventname"] == "sceneQueueTaskStarted":
606                     fraction = 0.2 * fraction
607                 elif message["eventname"] == "runQueueTaskStarted":
608                     fraction = 0.2 + 0.8 * fraction
609         elif self.current_step == self.PACKAGE_GENERATING:
610             if message["eventname"] == "sceneQueueTaskStarted":
611                 fraction = 0.2 * fraction
612             elif message["eventname"] == "runQueueTaskStarted":
613                 fraction = 0.2 + 0.8 * fraction
614         self.build_details_page.update_progress_bar(title + ": ", fraction)
615         self.build_details_page.update_build_status(message["current"], message["total"], message["task"])
616
617     def handler_build_failure_cb(self, running_build):
618         self.build_details_page.show_issues()
619
620     def destroy_window_cb(self, widget, event):
621         lbl = "<b>Do you really want to exit the Hob image creator?</b>"
622         dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
623         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
624         HobAltButton.style_button(button)
625         button = dialog.add_button("Exit Hob", gtk.RESPONSE_YES)
626         HobButton.style_button(button)
627         dialog.set_default_response(gtk.RESPONSE_YES)
628         response = dialog.run()
629         dialog.destroy()
630         if response == gtk.RESPONSE_YES:
631             gtk.main_quit()
632             return False
633         else:
634             return True
635
636     def build_packages(self):
637         _, all_recipes = self.recipe_model.get_selected_recipes()
638         if not all_recipes:
639             lbl = "<b>No selections made</b>\nYou have not made any selections"
640             lbl = lbl + " so there isn't anything to bake at this time."
641             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
642             button = dialog.add_button("Close", gtk.RESPONSE_OK)
643             HobButton.style_button(button)
644             dialog.run()
645             dialog.destroy()
646             return
647         self.switch_page(self.PACKAGE_GENERATING)
648
649     def build_image(self):
650         selected_packages = self.package_model.get_selected_packages()
651         if not selected_packages:      
652             lbl = "<b>No selections made</b>\nYou have not made any selections"
653             lbl = lbl + " so there isn't anything to bake at this time."
654             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
655             button = dialog.add_button("Close", gtk.RESPONSE_OK)
656             HobButton.style_button(button)
657             dialog.run()
658             dialog.destroy()
659             return
660         self.switch_page(self.IMAGE_GENERATING)
661
662     def just_bake(self):
663         selected_image = self.recipe_model.get_selected_image()
664         selected_packages = self.package_model.get_selected_packages() or []
665
666         # If no base image and no selected packages don't build anything
667         if not (selected_packages or selected_image != self.recipe_model.__dummy_image__):
668             lbl = "<b>No selections made</b>\nYou have not made any selections"
669             lbl = lbl + " so there isn't anything to bake at this time."
670             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
671             button = dialog.add_button("Close", gtk.RESPONSE_OK)
672             HobButton.style_button(button)
673             dialog.run()
674             dialog.destroy()
675             return
676
677         self.switch_page(self.FAST_IMAGE_GENERATING)
678
679     def show_binb_dialog(self, binb):
680         markup = "<b>Brought in by:</b>\n%s" % binb
681         ptip = PersistentTooltip(markup)
682
683         ptip.show()
684
685     def show_layer_selection_dialog(self):
686         dialog = LayerSelectionDialog(title = "Layers",
687                      layers = copy.deepcopy(self.configuration.layers),
688                      all_layers = self.parameters.all_layers,
689                      parent = self,
690                      flags = gtk.DIALOG_MODAL
691                          | gtk.DIALOG_DESTROY_WITH_PARENT
692                          | gtk.DIALOG_NO_SEPARATOR)
693         button = dialog.add_button("Close", gtk.RESPONSE_YES)
694         HobButton.style_button(button)
695         response = dialog.run()
696         if response == gtk.RESPONSE_YES:
697             self.configuration.layers = dialog.layers
698             # DO refresh layers
699             if dialog.layers_changed:
700                 self.switch_page(self.LAYER_CHANGED)
701         dialog.destroy()
702
703     def show_load_template_dialog(self):
704         dialog = gtk.FileChooserDialog("Load Template Files", self,
705                                        gtk.FILE_CHOOSER_ACTION_OPEN)
706         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
707         HobAltButton.style_button(button)
708         button = dialog.add_button("Open", gtk.RESPONSE_YES)
709         HobButton.style_button(button)
710         filter = gtk.FileFilter()
711         filter.set_name("Hob Files")
712         filter.add_pattern("*.hob")
713         dialog.add_filter(filter)
714
715         response = dialog.run()
716         if response == gtk.RESPONSE_YES:
717             path = dialog.get_filename()
718             self.load_template(path)
719         dialog.destroy()
720
721     def show_save_template_dialog(self):
722         dialog = gtk.FileChooserDialog("Save Template Files", self,
723                                        gtk.FILE_CHOOSER_ACTION_SAVE)
724         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
725         HobAltButton.style_button(button)
726         button = dialog.add_button("Save", gtk.RESPONSE_YES)
727         HobButton.style_button(button)
728         dialog.set_current_name("hob")
729         response = dialog.run()
730         if response == gtk.RESPONSE_YES:
731             path = dialog.get_filename()
732             self.save_template(path)
733         dialog.destroy()
734
735     def show_load_my_images_dialog(self):
736         dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types,
737                                       "Open My Images", self,
738                                        gtk.FILE_CHOOSER_ACTION_SAVE)
739         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
740         HobAltButton.style_button(button)
741         button = dialog.add_button("Open", gtk.RESPONSE_YES)
742         HobButton.style_button(button)
743         response = dialog.run()
744         if response == gtk.RESPONSE_YES:
745             if not dialog.image_names:
746                 lbl = "<b>No selections made</b>\nYou have not made any selections"
747                 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
748                 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
749                 HobButton.style_button(button)
750                 crumbs_dialog.run()
751                 crumbs_dialog.destroy()
752                 dialog.destroy()
753                 return
754
755             self.parameters.image_addr = dialog.image_folder
756             self.parameters.image_names = dialog.image_names[:]
757             self.switch_page(self.MY_IMAGE_OPENED)
758
759         dialog.destroy()
760
761     def show_adv_settings_dialog(self):
762         dialog = AdvancedSettingDialog(title = "Settings",
763             configuration = copy.deepcopy(self.configuration),
764             all_image_types = self.parameters.image_types,
765             all_package_formats = self.parameters.all_package_formats,
766             all_distros = self.parameters.all_distros,
767             all_sdk_machines = self.parameters.all_sdk_machines,
768             max_threads = self.parameters.max_threads,
769             parent = self,
770             flags = gtk.DIALOG_MODAL
771                     | gtk.DIALOG_DESTROY_WITH_PARENT
772                     | gtk.DIALOG_NO_SEPARATOR)
773         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
774         HobAltButton.style_button(button)
775         button = dialog.add_button("Save", gtk.RESPONSE_YES)
776         HobButton.style_button(button)
777         response = dialog.run()
778         if response == gtk.RESPONSE_YES:
779             self.configuration = dialog.configuration
780             # DO reparse recipes
781             if dialog.settings_changed:
782                 if self.configuration.curr_mach == "":
783                     self.switch_page(self.MACHINE_SELECTION)
784                 else:
785                     self.switch_page(self.RCPPKGINFO_POPULATING)
786         dialog.destroy()
787
788     def deploy_image(self, image_name):
789         if not image_name:
790             lbl = "<b>Please select an image to deploy.</b>"
791             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
792             button = dialog.add_button("Close", gtk.RESPONSE_OK)
793             HobButton.style_button(button)
794             dialog.run()
795             dialog.destroy()
796             return
797
798         image_path = os.path.join(self.parameters.image_addr, image_name)
799         dialog = DeployImageDialog(title = "Usb Image Maker",
800             image_path = image_path,
801             parent = self,
802             flags = gtk.DIALOG_MODAL
803                     | gtk.DIALOG_DESTROY_WITH_PARENT
804                     | gtk.DIALOG_NO_SEPARATOR)
805         button = dialog.add_button("Close", gtk.RESPONSE_NO)
806         HobAltButton.style_button(button)
807         button = dialog.add_button("Make usb image", gtk.RESPONSE_YES)
808         HobButton.style_button(button)
809         response = dialog.run()
810         dialog.destroy()
811
812     def runqemu_image(self, image_name):
813         if not image_name:
814             lbl = "<b>Please select an image to launch in QEMU.</b>"
815             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
816             button = dialog.add_button("Close", gtk.RESPONSE_OK)
817             HobButton.style_button(button)
818             dialog.run()
819             dialog.destroy()
820             return
821
822         dialog = gtk.FileChooserDialog("Load Kernel Files", self,
823                                        gtk.FILE_CHOOSER_ACTION_SAVE)
824         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
825         HobAltButton.style_button(button)
826         button = dialog.add_button("Open", gtk.RESPONSE_YES)
827         HobButton.style_button(button)
828         filter = gtk.FileFilter()
829         filter.set_name("Kernel Files")
830         filter.add_pattern("*.bin")
831         dialog.add_filter(filter)
832
833         dialog.set_current_folder(self.parameters.image_addr)
834
835         response = dialog.run()
836         if response == gtk.RESPONSE_YES:
837             kernel_path = dialog.get_filename()
838             image_path = os.path.join(self.parameters.image_addr, image_name)
839         dialog.destroy()
840
841         if response == gtk.RESPONSE_YES:
842             source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env")
843             tmp_path = self.parameters.tmpdir
844             if os.path.exists(image_path) and os.path.exists(kernel_path) \
845                and os.path.exists(source_env_path) and os.path.exists(tmp_path):
846                 cmdline = "/usr/bin/xterm -e "
847                 cmdline += "\" export OE_TMPDIR=" + tmp_path + "; "
848                 cmdline += "source " + source_env_path + " " + os.getcwd() + "; "
849                 cmdline += "runqemu " + kernel_path + " " + image_path + "; bash\""
850                 subprocess.Popen(shlex.split(cmdline))
851             else:
852                 lbl = "<b>Path error</b>\nOne of your paths is wrong,"
853                 lbl = lbl + " please make sure the following paths exist:\n"
854                 lbl = lbl + "image path:" + image_path + "\n"
855                 lbl = lbl + "kernel path:" + kernel_path + "\n"
856                 lbl = lbl + "source environment path:" + source_env_path + "\n"
857                 lbl = lbl + "tmp path: " + tmp_path + "."
858                 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
859                 button = dialog.add_button("Close", gtk.RESPONSE_OK)
860                 HobButton.style_button(button)
861                 dialog.run()
862                 dialog.destroy()
863
864     def show_packages(self, ask=True):
865         _, selected_recipes = self.recipe_model.get_selected_recipes()
866         if selected_recipes and ask:
867             lbl = "<b>Package list may be incomplete!</b>\nDo you want to build selected recipes"
868             lbl = lbl + " to get a full list or just view the existing packages?"
869             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
870             button = dialog.add_button("View packages", gtk.RESPONSE_NO)
871             HobAltButton.style_button(button)
872             button = dialog.add_button("Build packages", gtk.RESPONSE_YES)
873             HobButton.style_button(button)
874             dialog.set_default_response(gtk.RESPONSE_YES)
875             response = dialog.run()
876             dialog.destroy()
877             if response == gtk.RESPONSE_YES:
878                 self.switch_page(self.PACKAGE_GENERATING)
879             else:
880                 self.switch_page(self.PACKAGE_SELECTION)
881         else:
882             self.switch_page(self.PACKAGE_SELECTION)
883
884     def show_recipes(self):
885         self.switch_page(self.RECIPE_SELECTION)
886
887     def initiate_new_build(self):
888         self.configuration.curr_mach = ""
889         self.image_configuration_page.switch_machine_combo()
890         self.switch_page(self.MACHINE_SELECTION)
891
892     def show_configuration(self):
893         self.switch_page(self.BASEIMG_SELECTED)
894
895     def stop_parse(self):
896         self.handler.cancel_parse()
897
898     def stop_build(self):
899         if self.stopping:
900             lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once,"
901             lbl = lbl + " would you like to 'Force Stop' the build?\n\n"
902             lbl = lbl + "This will stop the build as quickly as possible but may"
903             lbl = lbl + " well leave your build directory in an  unusable state"
904             lbl = lbl + " that requires manual steps to fix.\n"
905             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
906             button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
907             HobAltButton.style_button(button)
908             button = dialog.add_button("Force Stop", gtk.RESPONSE_YES)
909             HobButton.style_button(button)
910         else:
911             lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this"
912             lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as"
913             lbl = lbl + " possible but may well leave your build directory in an"
914             lbl = lbl + " unusable state that requires manual steps to fix.\n\n"
915             lbl = lbl + "'Stop' will stop the build as soon as all in"
916             lbl = lbl + " progress build tasks are finished. However if a"
917             lbl = lbl + " lengthy compilation phase is in progress this may take"
918             lbl = lbl + " some time."
919             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
920             button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
921             HobAltButton.style_button(button)
922             button = dialog.add_button("Stop", gtk.RESPONSE_OK)
923             HobAltButton.style_button(button)
924             button = dialog.add_button("Force Stop", gtk.RESPONSE_YES)
925             HobButton.style_button(button)
926         response = dialog.run()
927         dialog.destroy()
928         if response != gtk.RESPONSE_CANCEL:
929             self.stopping = True
930         if response == gtk.RESPONSE_OK:
931             self.handler.cancel_build()
932         elif response == gtk.RESPONSE_YES:
933             self.handler.cancel_build(True)