Hob: Build selected image if not customized
[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 import bb.ui.crumbs.utils
41
42 class Configuration:
43     '''Represents the data structure of configuration.'''
44
45     def __init__(self, params):
46         # Settings
47         self.curr_mach = ""
48         self.curr_distro = params["distro"]
49         self.dldir = params["dldir"]
50         self.sstatedir = params["sstatedir"]
51         self.sstatemirror = params["sstatemirror"]
52         self.pmake = int(params["pmake"].split()[1])
53         self.bbthread = params["bbthread"]
54         self.curr_package_format = " ".join(params["pclass"].split("package_")).strip()
55         self.image_rootfs_size = params["image_rootfs_size"]
56         self.image_extra_size = params["image_extra_size"]
57         self.image_overhead_factor = params['image_overhead_factor']
58         self.incompat_license = params["incompat_license"]
59         self.curr_sdk_machine = params["sdk_machine"]
60         self.conf_version = params["conf_version"]
61         self.lconf_version = params["lconf_version"]
62         self.extra_setting = {}
63         self.toolchain_build = False
64         self.image_fstypes = params["image_fstypes"]
65         # bblayers.conf
66         self.layers = params["layer"].split()
67         # image/recipes/packages
68         self.selected_image = None
69         self.selected_recipes = []
70         self.selected_packages = []
71
72         self.user_selected_packages = []
73
74         # proxy settings
75         self.all_proxy = params["all_proxy"]
76         self.http_proxy = params["http_proxy"]
77         self.ftp_proxy = params["ftp_proxy"]
78         self.https_proxy = params["https_proxy"]
79         self.git_proxy_host = params["git_proxy_host"]
80         self.git_proxy_port = params["git_proxy_port"]
81         self.cvs_proxy_host = params["cvs_proxy_host"]
82         self.cvs_proxy_port = params["cvs_proxy_port"]
83
84     def update(self, params):
85         self.curr_distro = params["distro"]
86         self.dldir = params["dldir"]
87         self.sstatedir = params["sstatedir"]
88         self.sstatemirror = params["sstatemirror"]
89         self.pmake = int(params["pmake"].split()[1])
90         self.bbthread = params["bbthread"]
91         self.curr_package_format = " ".join(params["pclass"].split("package_")).strip()
92         self.image_rootfs_size = params["image_rootfs_size"]
93         self.image_extra_size = params["image_extra_size"]
94         self.image_overhead_factor = params['image_overhead_factor']
95         self.incompat_license = params["incompat_license"]
96         self.curr_sdk_machine = params["sdk_machine"]
97         self.conf_version = params["conf_version"]
98         self.lconf_version = params["lconf_version"]
99         self.image_fstypes = params["image_fstypes"]
100         self.tune_arch = params["tune_arch"]
101         self.bb_version = params["bb_version"]
102         self.target_arch = params["target_arch"]
103         self.target_os = params["target_os"]
104         self.distro_version = params["distro_version"]
105         self.tune_pkgarch = params["tune_pkgarch"]
106         # bblayers.conf
107         self.layers = params["layer"].split()
108
109     def load(self, template):
110         self.curr_mach = template.getVar("MACHINE")
111         self.curr_package_format = " ".join(template.getVar("PACKAGE_CLASSES").split("package_")).strip()
112         self.curr_distro = template.getVar("DISTRO")
113         self.dldir = template.getVar("DL_DIR")
114         self.sstatedir = template.getVar("SSTATE_DIR")
115         self.sstatemirror = template.getVar("SSTATE_MIRROR")
116         try:
117             self.pmake = int(template.getVar("PARALLEL_MAKE").split()[1])
118         except:
119             pass
120         try:
121             self.bbthread = int(template.getVar("BB_NUMBER_THREADS"))
122         except:
123             pass
124         try:
125             self.image_rootfs_size = int(template.getVar("IMAGE_ROOTFS_SIZE"))
126         except:
127             pass
128         try:
129             self.image_extra_size = int(template.getVar("IMAGE_EXTRA_SPACE"))
130         except:
131             pass
132         # image_overhead_factor is read-only.
133         self.incompat_license = template.getVar("INCOMPATIBLE_LICENSE")
134         self.curr_sdk_machine = template.getVar("SDKMACHINE")
135         self.conf_version = template.getVar("CONF_VERSION")
136         self.lconf_version = template.getVar("LCONF_VERSION")
137         self.extra_setting = eval(template.getVar("EXTRA_SETTING"))
138         self.toolchain_build = eval(template.getVar("TOOLCHAIN_BUILD"))
139         self.image_fstypes = template.getVar("IMAGE_FSTYPES")
140         # bblayers.conf
141         self.layers = template.getVar("BBLAYERS").split()
142         # image/recipes/packages
143         self.selected_image = template.getVar("__SELECTED_IMAGE__")
144         self.selected_recipes = template.getVar("DEPENDS").split()
145         self.selected_packages = template.getVar("IMAGE_INSTALL").split()
146         # proxy
147         self.all_proxy = template.getVar("all_proxy")
148         self.http_proxy = template.getVar("http_proxy")
149         self.ftp_proxy = template.getVar("ftp_proxy")
150         self.https_proxy = template.getVar("https_proxy")
151         self.git_proxy_host = template.getVar("GIT_PROXY_HOST")
152         self.git_proxy_port = template.getVar("GIT_PROXY_PORT")
153         self.cvs_proxy_host = template.getVar("CVS_PROXY_HOST")
154         self.cvs_proxy_port = template.getVar("CVS_PROXY_PORT")
155
156     def save(self, template):
157         # bblayers.conf
158         template.setVar("BBLAYERS", " ".join(self.layers))
159         # local.conf
160         template.setVar("MACHINE", self.curr_mach)
161         template.setVar("DISTRO", self.curr_distro)
162         template.setVar("DL_DIR", self.dldir)
163         template.setVar("SSTATE_DIR", self.sstatedir)
164         template.setVar("SSTATE_MIRROR", self.sstatemirror)
165         template.setVar("PARALLEL_MAKE", "-j %s" % self.pmake)
166         template.setVar("BB_NUMBER_THREADS", self.bbthread)
167         template.setVar("PACKAGE_CLASSES", " ".join(["package_" + i for i in self.curr_package_format.split()]))
168         template.setVar("IMAGE_ROOTFS_SIZE", self.image_rootfs_size)
169         template.setVar("IMAGE_EXTRA_SPACE", self.image_extra_size)
170         template.setVar("INCOMPATIBLE_LICENSE", self.incompat_license)
171         template.setVar("SDKMACHINE", self.curr_sdk_machine)
172         template.setVar("CONF_VERSION", self.conf_version)
173         template.setVar("LCONF_VERSION", self.lconf_version)
174         template.setVar("EXTRA_SETTING", self.extra_setting)
175         template.setVar("TOOLCHAIN_BUILD", self.toolchain_build)
176         template.setVar("IMAGE_FSTYPES", self.image_fstypes)
177         # image/recipes/packages
178         template.setVar("__SELECTED_IMAGE__", self.selected_image)
179         template.setVar("DEPENDS", self.selected_recipes)
180         template.setVar("IMAGE_INSTALL", self.user_selected_packages)
181         # proxy
182         template.setVar("all_proxy", self.all_proxy)
183         template.setVar("http_proxy", self.http_proxy)
184         template.setVar("ftp_proxy", self.ftp_proxy)
185         template.setVar("https_proxy", self.https_proxy)
186         template.setVar("GIT_PROXY_HOST", self.git_proxy_host)
187         template.setVar("GIT_PROXY_PORT", self.git_proxy_port)
188         template.setVar("CVS_PROXY_HOST", self.cvs_proxy_host)
189         template.setVar("CVS_PROXY_PORT", self.cvs_proxy_port)
190
191 class Parameters:
192     '''Represents other variables like available machines, etc.'''
193
194     def __init__(self, params):
195         # Variables
196         self.all_machines = []
197         self.all_package_formats = []
198         self.all_distros = []
199         self.all_sdk_machines = []
200         self.max_threads = params["max_threads"]
201         self.all_layers = []
202         self.core_base = params["core_base"]
203         self.image_names = []
204         self.image_addr = params["image_addr"]
205         self.image_types = params["image_types"].split()
206         self.runnable_image_types = params["runnable_image_types"].split()
207         self.runnable_machine_patterns = params["runnable_machine_patterns"].split()
208         self.deployable_image_types = params["deployable_image_types"].split()
209         self.tmpdir = params["tmpdir"]
210         self.enable_proxy = False
211
212 def hob_conf_filter(fn, data):
213     if fn.endswith("/local.conf"):
214         distro = data.getVar("DISTRO_HOB")
215         if distro:
216             if distro != "defaultsetup":
217                 data.setVar("DISTRO", distro)
218             else:
219                 data.delVar("DISTRO")
220
221         keys = ["MACHINE_HOB", "SDKMACHINE_HOB", "PACKAGE_CLASSES_HOB", \
222                 "BB_NUMBER_THREADS_HOB", "PARALLEL_MAKE_HOB", "DL_DIR_HOB", \
223                 "SSTATE_DIR_HOB", "SSTATE_MIRROR_HOB", "INCOMPATIBLE_LICENSE_HOB"]
224         for key in keys:
225             var_hob = data.getVar(key)
226             if var_hob:
227                 data.setVar(key.split("_HOB")[0], var_hob)
228         return
229
230     if fn.endswith("/bblayers.conf"):
231         layers = data.getVar("BBLAYERS_HOB")
232         if layers:
233             data.setVar("BBLAYERS", layers)
234         return
235
236 class Builder(gtk.Window):
237
238     (MACHINE_SELECTION,
239      RCPPKGINFO_POPULATING,
240      RCPPKGINFO_POPULATED,
241      BASEIMG_SELECTED,
242      RECIPE_SELECTION,
243      PACKAGE_GENERATING,
244      PACKAGE_GENERATED,
245      PACKAGE_SELECTION,
246      FAST_IMAGE_GENERATING,
247      IMAGE_GENERATING,
248      IMAGE_GENERATED,
249      MY_IMAGE_OPENED,
250      BACK,
251      END_NOOP) = range(14)
252
253     (IMAGE_CONFIGURATION,
254      RECIPE_DETAILS,
255      BUILD_DETAILS,
256      PACKAGE_DETAILS,
257      IMAGE_DETAILS,
258      END_TAB) = range(6)
259
260     __step2page__ = {
261         MACHINE_SELECTION     : IMAGE_CONFIGURATION,
262         RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION,
263         RCPPKGINFO_POPULATED  : IMAGE_CONFIGURATION,
264         BASEIMG_SELECTED      : IMAGE_CONFIGURATION,
265         RECIPE_SELECTION      : RECIPE_DETAILS,
266         PACKAGE_GENERATING    : BUILD_DETAILS,
267         PACKAGE_GENERATED     : PACKAGE_DETAILS,
268         PACKAGE_SELECTION     : PACKAGE_DETAILS,
269         FAST_IMAGE_GENERATING : BUILD_DETAILS,
270         IMAGE_GENERATING      : BUILD_DETAILS,
271         IMAGE_GENERATED       : IMAGE_DETAILS,
272         MY_IMAGE_OPENED       : IMAGE_DETAILS,
273         END_NOOP              : None,
274     }
275
276     def __init__(self, hobHandler, recipe_model, package_model):
277         super(Builder, self).__init__()
278
279         self.hob_image = "hob-image"
280         self.hob_toolchain = "hob-toolchain"
281
282         # handler
283         self.handler = hobHandler
284
285         self.template = None
286
287         # build step
288         self.current_step = None
289         self.previous_step = None
290
291         self.stopping = False
292
293         # recipe model and package model
294         self.recipe_model = recipe_model
295         self.package_model = package_model
296
297         # Indicate whether user has customized the image
298         self.customized = False
299
300         # Indicate whether the UI is working
301         self.sensitive = True
302
303         # create visual elements
304         self.create_visual_elements()
305
306         # connect the signals to functions
307         self.connect("delete-event", self.destroy_window_cb)
308         self.recipe_model.connect ("recipe-selection-changed",  self.recipelist_changed_cb)
309         self.package_model.connect("package-selection-changed", self.packagelist_changed_cb)
310         self.handler.connect("config-updated",           self.handler_config_updated_cb)
311         self.handler.connect("package-formats-updated",  self.handler_package_formats_updated_cb)
312         self.handler.connect("parsing-started",          self.handler_parsing_started_cb)
313         self.handler.connect("parsing",                  self.handler_parsing_cb)
314         self.handler.connect("parsing-completed",        self.handler_parsing_completed_cb)
315         self.handler.build.connect("build-started",      self.handler_build_started_cb)
316         self.handler.build.connect("build-succeeded",    self.handler_build_succeeded_cb)
317         self.handler.build.connect("build-failed",       self.handler_build_failed_cb)
318         self.handler.build.connect("task-started",       self.handler_task_started_cb)
319         self.handler.build.connect("log-error",          self.handler_build_failure_cb)
320         self.handler.connect("generating-data",          self.handler_generating_data_cb)
321         self.handler.connect("data-generated",           self.handler_data_generated_cb)
322         self.handler.connect("command-succeeded",        self.handler_command_succeeded_cb)
323         self.handler.connect("command-failed",           self.handler_command_failed_cb)
324
325         self.handler.set_config_filter(hob_conf_filter)
326
327         self.initiate_new_build_async()
328
329     def create_visual_elements(self):
330         self.set_title("Hob")
331         self.set_icon_name("applications-development")
332         self.set_resizable(True)
333         window_width = self.get_screen().get_width()
334         window_height = self.get_screen().get_height()
335         if window_width >= hwc.MAIN_WIN_WIDTH:
336             window_width = hwc.MAIN_WIN_WIDTH
337             window_height = hwc.MAIN_WIN_HEIGHT
338         self.set_size_request(window_width, window_height)
339
340         self.vbox = gtk.VBox(False, 0)
341         self.vbox.set_border_width(0)
342         self.add(self.vbox)
343
344         # create pages
345         self.image_configuration_page = ImageConfigurationPage(self)
346         self.recipe_details_page      = RecipeSelectionPage(self)
347         self.build_details_page       = BuildDetailsPage(self)
348         self.package_details_page     = PackageSelectionPage(self)
349         self.image_details_page       = ImageDetailsPage(self)
350
351         self.nb = gtk.Notebook()
352         self.nb.set_show_tabs(False)
353         self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION)
354         self.nb.insert_page(self.recipe_details_page,      None, self.RECIPE_DETAILS)
355         self.nb.insert_page(self.build_details_page,       None, self.BUILD_DETAILS)
356         self.nb.insert_page(self.package_details_page,     None, self.PACKAGE_DETAILS)
357         self.nb.insert_page(self.image_details_page,       None, self.IMAGE_DETAILS)
358         self.vbox.pack_start(self.nb, expand=True, fill=True)
359
360         self.show_all()
361         self.nb.set_current_page(0)
362
363     def initiate_new_build_async(self):
364         self.switch_page(self.MACHINE_SELECTION)
365         self.handler.init_cooker()
366         self.handler.set_extra_inherit("image_types")
367         self.handler.parse_config()
368
369     def update_config_async(self):
370         self.switch_page(self.MACHINE_SELECTION)
371         self.set_user_config()
372         self.handler.parse_generate_configuration()
373
374     def populate_recipe_package_info_async(self):
375         self.switch_page(self.RCPPKGINFO_POPULATING)
376         # Parse recipes
377         self.set_user_config()
378         self.handler.generate_recipes()
379
380     def generate_packages_async(self):
381         self.switch_page(self.PACKAGE_GENERATING)
382         # Build packages
383         _, all_recipes = self.recipe_model.get_selected_recipes()
384         self.set_user_config()
385         self.handler.reset_build()
386         self.handler.generate_packages(all_recipes)
387
388     def fast_generate_image_async(self):
389         self.switch_page(self.FAST_IMAGE_GENERATING)
390         # Build packages
391         _, all_recipes = self.recipe_model.get_selected_recipes()
392         self.set_user_config()
393         self.handler.reset_build()
394         self.handler.generate_packages(all_recipes)
395
396     def generate_image_async(self):
397         self.switch_page(self.IMAGE_GENERATING)
398         self.handler.reset_build()
399         # Build image
400         self.set_user_config()
401         toolchain_packages = []
402         if self.configuration.toolchain_build:
403             toolchain_packages = self.package_model.get_selected_packages_toolchain()
404         if self.configuration.selected_image == self.recipe_model.__dummy_image__:
405             packages = self.package_model.get_selected_packages()
406             image = self.hob_image
407         else:
408             packages = []
409             image = self.configuration.selected_image
410         self.handler.generate_image(image,
411                                     self.hob_toolchain,
412                                     packages,
413                                     toolchain_packages)
414
415     def get_parameters_sync(self):
416         return self.handler.get_parameters()
417
418     def request_package_info_async(self):
419         self.handler.request_package_info()
420
421     def cancel_build_sync(self, force=False):
422         self.handler.cancel_build(force)
423
424     def generate_configuration_async(self):
425         self.handler.generate_configuration()
426
427     def cancel_parse_sync(self):
428         self.handler.cancel_parse()
429
430     def load_template(self, path):
431         self.template = TemplateMgr()
432         self.template.load(path)
433         self.configuration.load(self.template)
434
435         for layer in self.configuration.layers:
436             if not os.path.exists(layer+'/conf/layer.conf'):
437                 return False
438
439         self.update_config_async()
440
441         self.template.destroy()
442         self.template = None
443
444     def save_template(self, path):
445         if path.rfind("/") == -1:
446             filename = "default"
447             path = "."
448         else:
449             filename = path[path.rfind("/") + 1:len(path)]
450             path = path[0:path.rfind("/")]
451
452         self.template = TemplateMgr()
453         self.template.open(filename, path)
454         self.configuration.save(self.template)
455
456         self.template.save()
457         self.template.destroy()
458         self.template = None
459
460     def switch_page(self, next_step):
461         # Main Workflow (Business Logic)
462         self.nb.set_current_page(self.__step2page__[next_step])
463
464         if next_step == self.MACHINE_SELECTION: # init step
465             self.image_configuration_page.show_machine()
466
467         elif next_step == self.RCPPKGINFO_POPULATING:
468             # MACHINE CHANGED action or SETTINGS CHANGED
469             # show the progress bar
470             self.image_configuration_page.show_info_populating()
471
472         elif next_step == self.RCPPKGINFO_POPULATED:
473             self.image_configuration_page.show_info_populated()
474
475         elif next_step == self.BASEIMG_SELECTED:
476             self.image_configuration_page.show_baseimg_selected()
477
478         elif next_step == self.RECIPE_SELECTION:
479             pass
480
481         elif next_step == self.PACKAGE_SELECTION:
482             pass
483
484         elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING:
485             # both PACKAGE_GENEATING and FAST_IMAGE_GENERATING share the same page
486             self.build_details_page.show_page(next_step)
487
488         elif next_step == self.PACKAGE_GENERATED:
489             pass
490
491         elif next_step == self.IMAGE_GENERATING:
492             # after packages are generated, selected_packages need to
493             # be updated in package_model per selected_image in recipe_model
494             self.build_details_page.show_page(next_step)
495
496         elif next_step == self.IMAGE_GENERATED:
497             self.image_details_page.show_page(next_step)
498
499         elif next_step == self.MY_IMAGE_OPENED:
500             self.image_details_page.show_page(next_step)
501
502         self.previous_step = self.current_step
503         self.current_step = next_step
504
505     def set_user_config(self):
506         self.handler.init_cooker()
507         # set bb layers
508         self.handler.set_bblayers(self.configuration.layers)
509         # set local configuration
510         self.handler.set_machine(self.configuration.curr_mach)
511         self.handler.set_package_format(self.configuration.curr_package_format)
512         self.handler.set_distro(self.configuration.curr_distro)
513         self.handler.set_dl_dir(self.configuration.dldir)
514         self.handler.set_sstate_dir(self.configuration.sstatedir)
515         self.handler.set_sstate_mirror(self.configuration.sstatemirror)
516         self.handler.set_pmake(self.configuration.pmake)
517         self.handler.set_bbthreads(self.configuration.bbthread)
518         self.handler.set_rootfs_size(self.configuration.image_rootfs_size)
519         self.handler.set_extra_size(self.configuration.image_extra_size)
520         self.handler.set_incompatible_license(self.configuration.incompat_license)
521         self.handler.set_sdk_machine(self.configuration.curr_sdk_machine)
522         self.handler.set_image_fstypes(self.configuration.image_fstypes)
523         self.handler.set_extra_config(self.configuration.extra_setting)
524         self.handler.set_extra_inherit("packageinfo")
525         self.handler.set_extra_inherit("image_types")
526         # set proxies
527         if self.parameters.enable_proxy:
528             self.handler.set_http_proxy(self.configuration.http_proxy)
529             self.handler.set_https_proxy(self.configuration.https_proxy)
530             self.handler.set_ftp_proxy(self.configuration.ftp_proxy)
531             self.handler.set_all_proxy(self.configuration.all_proxy)
532             self.handler.set_git_proxy(self.configuration.git_proxy_host, self.configuration.git_proxy_port)
533             self.handler.set_cvs_proxy(self.configuration.cvs_proxy_host, self.configuration.cvs_proxy_port)
534
535     def update_recipe_model(self, selected_image, selected_recipes):
536         self.recipe_model.set_selected_image(selected_image)
537         self.recipe_model.set_selected_recipes(selected_recipes)
538
539     def update_package_model(self, selected_packages):
540         left = self.package_model.set_selected_packages(selected_packages)
541         self.configuration.selected_packages += left
542
543     # Callback Functions
544     def handler_config_updated_cb(self, handler, which, values):
545         if which == "distro":
546             self.parameters.all_distros = values
547         elif which == "machine":
548             self.parameters.all_machines = values
549             self.image_configuration_page.update_machine_combo()
550         elif which == "machine-sdk":
551             self.parameters.all_sdk_machines = values
552
553     def handler_package_formats_updated_cb(self, handler, formats):
554         self.parameters.all_package_formats = formats
555
556     def handler_command_succeeded_cb(self, handler, initcmd):
557         if initcmd == self.handler.PARSE_CONFIG:
558             # settings
559             params = self.get_parameters_sync()
560             self.configuration = Configuration(params)
561             self.parameters = Parameters(params)
562             self.generate_configuration_async()
563         elif initcmd == self.handler.GENERATE_CONFIGURATION:
564             params = self.get_parameters_sync()
565             self.configuration.update(params)
566             self.image_configuration_page.switch_machine_combo()
567         elif initcmd in [self.handler.GENERATE_RECIPES,
568                          self.handler.GENERATE_PACKAGES,
569                          self.handler.GENERATE_IMAGE]:
570             params = self.get_parameters_sync()
571             self.configuration.update(params)
572             self.request_package_info_async()
573         elif initcmd == self.handler.POPULATE_PACKAGEINFO:
574             if self.current_step == self.RCPPKGINFO_POPULATING:
575                 self.switch_page(self.RCPPKGINFO_POPULATED)
576                 self.rcppkglist_populated()
577                 return
578
579             self.rcppkglist_populated()
580             if self.current_step == self.FAST_IMAGE_GENERATING:
581                 self.generate_image_async()
582             elif self.current_step == self.PACKAGE_GENERATING:
583                 self.switch_page(self.PACKAGE_GENERATED)
584             elif self.current_step == self.IMAGE_GENERATING:
585                 self.switch_page(self.IMAGE_GENERATED)
586
587     def handler_command_failed_cb(self, handler, msg):
588         if msg:
589             msg = msg.replace("your local.conf", "Settings")
590             lbl = "<b>Error</b>\n"
591             lbl = lbl + "%s\n\n" % msg
592             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR)
593             button = dialog.add_button("Close", gtk.RESPONSE_OK)
594             HobButton.style_button(button)
595             response = dialog.run()
596             dialog.destroy()
597         self.configuration.curr_mach = ""
598         self.image_configuration_page.switch_machine_combo()
599         self.switch_page(self.MACHINE_SELECTION)
600
601     def window_sensitive(self, sensitive):
602         self.image_configuration_page.machine_combo.set_sensitive(sensitive)
603         self.image_configuration_page.image_combo.set_sensitive(sensitive)
604         self.image_configuration_page.layer_button.set_sensitive(sensitive)
605         self.image_configuration_page.layer_info_icon.set_sensitive(sensitive)
606         self.image_configuration_page.toolbar.set_sensitive(sensitive)
607         self.image_configuration_page.view_recipes_button.set_sensitive(sensitive)
608         self.image_configuration_page.view_packages_button.set_sensitive(sensitive)
609         self.image_configuration_page.config_build_button.set_sensitive(sensitive)
610
611         self.recipe_details_page.set_sensitive(sensitive)
612         self.package_details_page.set_sensitive(sensitive)
613         self.build_details_page.set_sensitive(sensitive)
614         self.image_details_page.set_sensitive(sensitive)
615
616         if sensitive:
617             self.get_root_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
618         else:
619             self.get_root_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
620         self.sensitive = sensitive
621
622
623     def handler_generating_data_cb(self, handler):
624         self.window_sensitive(False)
625
626     def handler_data_generated_cb(self, handler):
627         self.window_sensitive(True)
628
629     def rcppkglist_populated(self):
630         selected_image = self.configuration.selected_image
631         selected_recipes = self.configuration.selected_recipes[:]
632         selected_packages = self.configuration.selected_packages[:]
633
634         self.image_configuration_page.update_image_combo(self.recipe_model, selected_image)
635         self.image_configuration_page.update_image_desc(selected_image)
636         self.update_recipe_model(selected_image, selected_recipes)
637         self.update_package_model(selected_packages)
638
639     def recipelist_changed_cb(self, recipe_model):
640         self.recipe_details_page.refresh_selection()
641
642     def packagelist_changed_cb(self, package_model):
643         self.package_details_page.refresh_selection()
644
645     def handler_parsing_started_cb(self, handler, message):
646         if self.current_step != self.RCPPKGINFO_POPULATING:
647             return
648
649         fraction = 0
650         if message["eventname"] == "TreeDataPreparationStarted":
651             fraction = 0.6 + fraction
652             self.image_configuration_page.stop_button.set_sensitive(False)
653         else:
654             self.image_configuration_page.stop_button.set_sensitive(True)
655
656         self.image_configuration_page.update_progress_bar(message["title"], fraction)
657
658     def handler_parsing_cb(self, handler, message):
659         if self.current_step != self.RCPPKGINFO_POPULATING:
660             return
661
662         fraction = message["current"] * 1.0/message["total"]
663         if message["eventname"] == "TreeDataPreparationProgress":
664             fraction = 0.6 + 0.4 * fraction
665         else:
666             fraction = 0.6 * fraction
667         self.image_configuration_page.update_progress_bar(message["title"], fraction)
668
669     def handler_parsing_completed_cb(self, handler, message):
670         if self.current_step != self.RCPPKGINFO_POPULATING:
671             return
672
673         if message["eventname"] == "TreeDataPreparationCompleted":
674             fraction = 1.0
675         else:
676             fraction = 0.6
677         self.image_configuration_page.update_progress_bar(message["title"], fraction)
678
679     def handler_build_started_cb(self, running_build):
680         if self.current_step == self.FAST_IMAGE_GENERATING:
681             fraction = 0
682         elif self.current_step == self.IMAGE_GENERATING:
683             if self.previous_step == self.FAST_IMAGE_GENERATING:
684                 fraction = 0.9
685             else:
686                 fraction = 0
687         elif self.current_step == self.PACKAGE_GENERATING:
688             fraction = 0
689         self.build_details_page.update_progress_bar("Build Started: ", fraction)
690         self.build_details_page.show_configurations(self.configuration)
691
692     def build_succeeded(self):
693         if self.current_step == self.FAST_IMAGE_GENERATING:
694             fraction = 0.9
695         elif self.current_step == self.IMAGE_GENERATING:
696             fraction = 1.0
697             self.parameters.image_names = []
698             selected_image = self.recipe_model.get_selected_image()
699             if selected_image == self.recipe_model.__dummy_image__:
700                 linkname = 'hob-image-' + self.configuration.curr_mach
701             else:
702                 linkname = selected_image + '-' + self.configuration.curr_mach
703             for image_type in self.parameters.image_types:
704                 linkpath = self.parameters.image_addr + '/' + linkname + '.' + image_type
705                 if os.path.exists(linkpath):
706                     self.parameters.image_names.append(os.readlink(linkpath))
707         elif self.current_step == self.PACKAGE_GENERATING:
708             fraction = 1.0
709         self.build_details_page.update_progress_bar("Build Completed: ", fraction)
710         self.handler.build_succeeded_async()
711         self.stopping = False
712
713     def build_failed(self):
714         if self.stopping:
715             status = "stop"
716             message = "Build stopped: "
717             fraction = self.build_details_page.progress_bar.get_fraction()
718         else:
719             if self.current_step == self.FAST_IMAGE_GENERATING:
720                 fraction = 0.9
721             elif self.current_step == self.IMAGE_GENERATING:
722                 fraction = 1.0
723             elif self.current_step == self.PACKAGE_GENERATING:
724                 fraction = 1.0
725             status = "fail"
726             message = "Build failed: "
727         self.build_details_page.update_progress_bar(message, fraction, status)
728         self.build_details_page.show_back_button()
729         self.build_details_page.hide_stop_button()
730         self.handler.build_failed_async()
731         self.stopping = False
732
733     def handler_build_succeeded_cb(self, running_build):
734         if not self.stopping:
735             self.build_succeeded()
736         else:
737             self.build_failed()
738
739
740     def handler_build_failed_cb(self, running_build):
741         self.build_failed()
742
743     def handler_task_started_cb(self, running_build, message): 
744         fraction = message["current"] * 1.0/message["total"]
745         title = "Build packages"
746         if self.current_step == self.FAST_IMAGE_GENERATING:
747             if message["eventname"] == "sceneQueueTaskStarted":
748                 fraction = 0.27 * fraction
749             elif message["eventname"] == "runQueueTaskStarted":
750                 fraction = 0.27 + 0.63 * fraction
751         elif self.current_step == self.IMAGE_GENERATING:
752             title = "Build image"
753             if self.previous_step == self.FAST_IMAGE_GENERATING:
754                 if message["eventname"] == "sceneQueueTaskStarted":
755                     fraction = 0.27 + 0.63 + 0.03 * fraction
756                 elif message["eventname"] == "runQueueTaskStarted":
757                     fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction
758             else:
759                 if message["eventname"] == "sceneQueueTaskStarted":
760                     fraction = 0.2 * fraction
761                 elif message["eventname"] == "runQueueTaskStarted":
762                     fraction = 0.2 + 0.8 * fraction
763         elif self.current_step == self.PACKAGE_GENERATING:
764             if message["eventname"] == "sceneQueueTaskStarted":
765                 fraction = 0.2 * fraction
766             elif message["eventname"] == "runQueueTaskStarted":
767                 fraction = 0.2 + 0.8 * fraction
768         self.build_details_page.update_progress_bar(title + ": ", fraction)
769         self.build_details_page.update_build_status(message["current"], message["total"], message["task"])
770
771     def handler_build_failure_cb(self, running_build):
772         self.build_details_page.show_issues()
773
774     def destroy_window_cb(self, widget, event):
775         if not self.sensitive:
776             return True
777         lbl = "<b>Do you really want to exit the Hob image creator?</b>"
778         dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
779         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
780         HobAltButton.style_button(button)
781         button = dialog.add_button("Exit Hob", gtk.RESPONSE_YES)
782         HobButton.style_button(button)
783         dialog.set_default_response(gtk.RESPONSE_YES)
784         response = dialog.run()
785         dialog.destroy()
786         if response == gtk.RESPONSE_YES:
787             gtk.main_quit()
788             return False
789         else:
790             return True
791
792     def build_packages(self):
793         _, all_recipes = self.recipe_model.get_selected_recipes()
794         if not all_recipes:
795             lbl = "<b>No selections made</b>\nYou have not made any selections"
796             lbl = lbl + " so there isn't anything to bake at this time."
797             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
798             button = dialog.add_button("Close", gtk.RESPONSE_OK)
799             HobButton.style_button(button)
800             dialog.run()
801             dialog.destroy()
802             return
803         self.generate_packages_async()
804
805     def build_image(self):
806         selected_packages = self.package_model.get_selected_packages()
807         if not selected_packages:      
808             lbl = "<b>No selections made</b>\nYou have not made any selections"
809             lbl = lbl + " so there isn't anything to bake at this time."
810             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
811             button = dialog.add_button("Close", gtk.RESPONSE_OK)
812             HobButton.style_button(button)
813             dialog.run()
814             dialog.destroy()
815             return
816         self.generate_image_async()
817
818     def just_bake(self):
819         selected_image = self.recipe_model.get_selected_image()
820         selected_packages = self.package_model.get_selected_packages() or []
821
822         # If no base image and no selected packages don't build anything
823         if not (selected_packages or selected_image != self.recipe_model.__dummy_image__):
824             lbl = "<b>No selections made</b>\nYou have not made any selections"
825             lbl = lbl + " so there isn't anything to bake at this time."
826             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
827             button = dialog.add_button("Close", gtk.RESPONSE_OK)
828             HobButton.style_button(button)
829             dialog.run()
830             dialog.destroy()
831             return
832
833         self.fast_generate_image_async()
834
835     def show_binb_dialog(self, binb):
836         markup = "<b>Brought in by:</b>\n%s" % binb
837         ptip = PersistentTooltip(markup)
838
839         ptip.show()
840
841     def show_layer_selection_dialog(self):
842         dialog = LayerSelectionDialog(title = "Layers",
843                      layers = copy.deepcopy(self.configuration.layers),
844                      all_layers = self.parameters.all_layers,
845                      parent = self,
846                      flags = gtk.DIALOG_MODAL
847                          | gtk.DIALOG_DESTROY_WITH_PARENT
848                          | gtk.DIALOG_NO_SEPARATOR)
849         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
850         HobAltButton.style_button(button)
851         button = dialog.add_button("OK", gtk.RESPONSE_YES)
852         HobButton.style_button(button)
853         response = dialog.run()
854         if response == gtk.RESPONSE_YES:
855             self.configuration.layers = dialog.layers
856             # DO refresh layers
857             if dialog.layers_changed:
858                 self.update_config_async()
859         dialog.destroy()
860
861     def show_load_template_dialog(self):
862         dialog = gtk.FileChooserDialog("Load Template Files", self,
863                                        gtk.FILE_CHOOSER_ACTION_OPEN)
864         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
865         HobAltButton.style_button(button)
866         button = dialog.add_button("Open", gtk.RESPONSE_YES)
867         HobButton.style_button(button)
868         filter = gtk.FileFilter()
869         filter.set_name("Hob Files")
870         filter.add_pattern("*.hob")
871         dialog.add_filter(filter)
872
873         response = dialog.run()
874         path = None
875         if response == gtk.RESPONSE_YES:
876             path = dialog.get_filename()
877         dialog.destroy()
878         return response == gtk.RESPONSE_YES, path
879
880     def show_save_template_dialog(self):
881         dialog = gtk.FileChooserDialog("Save Template Files", self,
882                                        gtk.FILE_CHOOSER_ACTION_SAVE)
883         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
884         HobAltButton.style_button(button)
885         button = dialog.add_button("Save", gtk.RESPONSE_YES)
886         HobButton.style_button(button)
887         dialog.set_current_name("hob")
888         response = dialog.run()
889         if response == gtk.RESPONSE_YES:
890             path = dialog.get_filename()
891             self.save_template(path)
892         dialog.destroy()
893
894     def show_load_my_images_dialog(self):
895         dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types,
896                                       "Open My Images", self,
897                                        gtk.FILE_CHOOSER_ACTION_SAVE)
898         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
899         HobAltButton.style_button(button)
900         button = dialog.add_button("Open", gtk.RESPONSE_YES)
901         HobButton.style_button(button)
902         response = dialog.run()
903         if response == gtk.RESPONSE_YES:
904             if not dialog.image_names:
905                 lbl = "<b>No selections made</b>\nYou have not made any selections"
906                 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
907                 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
908                 HobButton.style_button(button)
909                 crumbs_dialog.run()
910                 crumbs_dialog.destroy()
911                 dialog.destroy()
912                 return
913
914             self.parameters.image_addr = dialog.image_folder
915             self.parameters.image_names = dialog.image_names[:]
916             self.switch_page(self.MY_IMAGE_OPENED)
917
918         dialog.destroy()
919
920     def show_adv_settings_dialog(self):
921         dialog = AdvancedSettingDialog(title = "Settings",
922             configuration = copy.deepcopy(self.configuration),
923             all_image_types = self.parameters.image_types,
924             all_package_formats = self.parameters.all_package_formats,
925             all_distros = self.parameters.all_distros,
926             all_sdk_machines = self.parameters.all_sdk_machines,
927             max_threads = self.parameters.max_threads,
928             enable_proxy = self.parameters.enable_proxy,
929             parent = self,
930             flags = gtk.DIALOG_MODAL
931                     | gtk.DIALOG_DESTROY_WITH_PARENT
932                     | gtk.DIALOG_NO_SEPARATOR)
933         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
934         HobAltButton.style_button(button)
935         button = dialog.add_button("Save", gtk.RESPONSE_YES)
936         HobButton.style_button(button)
937         response = dialog.run()
938         settings_changed = False
939         if response == gtk.RESPONSE_YES:
940             self.parameters.enable_proxy = dialog.enable_proxy
941             self.configuration = dialog.configuration
942             settings_changed = dialog.settings_changed
943         dialog.destroy()
944         return response == gtk.RESPONSE_YES, settings_changed
945
946     def reparse_post_adv_settings(self):
947         # DO reparse recipes
948         if not self.configuration.curr_mach:
949             self.update_config_async()
950         else:
951             self.populate_recipe_package_info_async()
952
953     def deploy_image(self, image_name):
954         if not image_name:
955             lbl = "<b>Please select an image to deploy.</b>"
956             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
957             button = dialog.add_button("Close", gtk.RESPONSE_OK)
958             HobButton.style_button(button)
959             dialog.run()
960             dialog.destroy()
961             return
962
963         image_path = os.path.join(self.parameters.image_addr, image_name)
964         dialog = DeployImageDialog(title = "Usb Image Maker",
965             image_path = image_path,
966             parent = self,
967             flags = gtk.DIALOG_MODAL
968                     | gtk.DIALOG_DESTROY_WITH_PARENT
969                     | gtk.DIALOG_NO_SEPARATOR)
970         button = dialog.add_button("Close", gtk.RESPONSE_NO)
971         HobAltButton.style_button(button)
972         button = dialog.add_button("Make usb image", gtk.RESPONSE_YES)
973         HobButton.style_button(button)
974         response = dialog.run()
975         dialog.destroy()
976
977     def runqemu_image(self, image_name):
978         if not image_name:
979             lbl = "<b>Please select an image to launch in QEMU.</b>"
980             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
981             button = dialog.add_button("Close", gtk.RESPONSE_OK)
982             HobButton.style_button(button)
983             dialog.run()
984             dialog.destroy()
985             return
986
987         dialog = gtk.FileChooserDialog("Load Kernel Files", self,
988                                        gtk.FILE_CHOOSER_ACTION_SAVE)
989         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
990         HobAltButton.style_button(button)
991         button = dialog.add_button("Open", gtk.RESPONSE_YES)
992         HobButton.style_button(button)
993         filter = gtk.FileFilter()
994         filter.set_name("Kernel Files")
995         filter.add_pattern("*.bin")
996         dialog.add_filter(filter)
997
998         dialog.set_current_folder(self.parameters.image_addr)
999
1000         response = dialog.run()
1001         if response == gtk.RESPONSE_YES:
1002             kernel_path = dialog.get_filename()
1003             image_path = os.path.join(self.parameters.image_addr, image_name)
1004         dialog.destroy()
1005
1006         if response == gtk.RESPONSE_YES:
1007             source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env")
1008             tmp_path = self.parameters.tmpdir
1009             cmdline = bb.ui.crumbs.utils.which_terminal()
1010             if os.path.exists(image_path) and os.path.exists(kernel_path) \
1011                and os.path.exists(source_env_path) and os.path.exists(tmp_path) \
1012                and cmdline:
1013                 cmdline += "\' bash -c \"export OE_TMPDIR=" + tmp_path + "; "
1014                 cmdline += "source " + source_env_path + " " + os.getcwd() + "; "
1015                 cmdline += "runqemu " + kernel_path + " " + image_path + "\"\'"
1016                 subprocess.Popen(shlex.split(cmdline))
1017             else:
1018                 lbl = "<b>Path error</b>\nOne of your paths is wrong,"
1019                 lbl = lbl + " please make sure the following paths exist:\n"
1020                 lbl = lbl + "image path:" + image_path + "\n"
1021                 lbl = lbl + "kernel path:" + kernel_path + "\n"
1022                 lbl = lbl + "source environment path:" + source_env_path + "\n"
1023                 lbl = lbl + "tmp path: " + tmp_path + "."
1024                 lbl = lbl + "You may be missing either xterm or vte for terminal services."
1025                 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR)
1026                 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1027                 HobButton.style_button(button)
1028                 dialog.run()
1029                 dialog.destroy()
1030
1031     def show_packages(self, ask=True):
1032         _, selected_recipes = self.recipe_model.get_selected_recipes()
1033         if selected_recipes and ask:
1034             lbl = "<b>Package list may be incomplete!</b>\nDo you want to build selected recipes"
1035             lbl = lbl + " to get a full list or just view the existing packages?"
1036             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1037             button = dialog.add_button("View packages", gtk.RESPONSE_NO)
1038             HobAltButton.style_button(button)
1039             button = dialog.add_button("Build packages", gtk.RESPONSE_YES)
1040             HobButton.style_button(button)
1041             dialog.set_default_response(gtk.RESPONSE_YES)
1042             response = dialog.run()
1043             dialog.destroy()
1044             if response == gtk.RESPONSE_YES:
1045                 self.generate_packages_async()
1046             else:
1047                 self.switch_page(self.PACKAGE_SELECTION)
1048         else:
1049             self.switch_page(self.PACKAGE_SELECTION)
1050
1051     def show_recipes(self):
1052         self.switch_page(self.RECIPE_SELECTION)
1053
1054     def show_configuration(self):
1055         self.switch_page(self.BASEIMG_SELECTED)
1056
1057     def stop_build(self):
1058         if self.stopping:
1059             lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once,"
1060             lbl = lbl + " would you like to 'Force Stop' the build?\n\n"
1061             lbl = lbl + "This will stop the build as quickly as possible but may"
1062             lbl = lbl + " well leave your build directory in an  unusable state"
1063             lbl = lbl + " that requires manual steps to fix.\n"
1064             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
1065             button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
1066             HobAltButton.style_button(button)
1067             button = dialog.add_button("Force Stop", gtk.RESPONSE_YES)
1068             HobButton.style_button(button)
1069         else:
1070             lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this"
1071             lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as"
1072             lbl = lbl + " possible but may well leave your build directory in an"
1073             lbl = lbl + " unusable state that requires manual steps to fix.\n\n"
1074             lbl = lbl + "'Stop' will stop the build as soon as all in"
1075             lbl = lbl + " progress build tasks are finished. However if a"
1076             lbl = lbl + " lengthy compilation phase is in progress this may take"
1077             lbl = lbl + " some time."
1078             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
1079             button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
1080             HobAltButton.style_button(button)
1081             button = dialog.add_button("Stop", gtk.RESPONSE_OK)
1082             HobAltButton.style_button(button)
1083             button = dialog.add_button("Force Stop", gtk.RESPONSE_YES)
1084             HobButton.style_button(button)
1085         response = dialog.run()
1086         dialog.destroy()
1087         if response != gtk.RESPONSE_CANCEL:
1088             self.stopping = True
1089         if response == gtk.RESPONSE_OK:
1090             self.cancel_build_sync()
1091         elif response == gtk.RESPONSE_YES:
1092             self.cancel_build_sync(True)