hob: providing details about process state through porgress bar
[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 glib
25 import gtk, gobject
26 import copy
27 import os
28 import subprocess
29 import shlex
30 import re
31 import logging
32 import sys
33 from bb.ui.crumbs.template import TemplateMgr
34 from bb.ui.crumbs.imageconfigurationpage import ImageConfigurationPage
35 from bb.ui.crumbs.recipeselectionpage import RecipeSelectionPage
36 from bb.ui.crumbs.packageselectionpage import PackageSelectionPage
37 from bb.ui.crumbs.builddetailspage import BuildDetailsPage
38 from bb.ui.crumbs.imagedetailspage import ImageDetailsPage
39 from bb.ui.crumbs.sanitycheckpage import SanityCheckPage
40 from bb.ui.crumbs.hobwidget import hwc, HobButton, HobAltButton
41 from bb.ui.crumbs.hig import CrumbsMessageDialog, ImageSelectionDialog, \
42                              AdvancedSettingDialog, SimpleSettingsDialog, \
43                              LayerSelectionDialog, DeployImageDialog
44 from bb.ui.crumbs.persistenttooltip import PersistentTooltip
45 import bb.ui.crumbs.utils
46
47 hobVer = 20120808
48
49 class Configuration:
50     '''Represents the data structure of configuration.'''
51
52     @classmethod
53     def parse_proxy_string(cls, proxy):
54         pattern = "^\s*((http|https|ftp|git|cvs)://)?((\S+):(\S+)@)?([^\s:]+)(:(\d+))?/?"
55         match = re.search(pattern, proxy)
56         if match:
57             return match.group(2), match.group(4), match.group(5), match.group(6), match.group(8)
58         else:
59             return None, None, None, "", ""
60
61     @classmethod
62     def make_host_string(cls, prot, user, passwd, host, default_prot=""):
63         if host == None or host == "":
64             return ""
65
66         passwd = passwd or ""
67
68         if user != None and user != "":
69             if prot == None or prot == "":
70                 prot = default_prot
71             return prot + "://" + user + ":" + passwd + "@" + host
72         else:
73             if prot == None or prot == "":
74                 return host
75             else:
76                 return prot + "://" + host
77
78     @classmethod
79     def make_port_string(cls, port):
80         port = port or ""
81         return port
82
83     @classmethod
84     def make_proxy_string(cls, prot, user, passwd, host, port, default_prot=""):
85         if host == None or host == "":# or port == None or port == "":
86             return ""
87
88         return Configuration.make_host_string(prot, user, passwd, host, default_prot) + (":" + Configuration.make_port_string(port) if port else "")
89
90     def __init__(self):
91         self.curr_mach = ""
92         self.selected_image = None
93         # settings
94         self.curr_distro = ""
95         self.dldir = self.sstatedir = self.sstatemirror = ""
96         self.pmake = self.bbthread = 0
97         self.curr_package_format = ""
98         self.image_rootfs_size = self.image_extra_size = 0
99         self.image_overhead_factor = 1
100         self.incompat_license = ""
101         self.curr_sdk_machine = ""
102         self.conf_version = self.lconf_version = ""
103         self.extra_setting = {}
104         self.toolchain_build = False
105         self.image_fstypes = ""
106         # bblayers.conf
107         self.layers = []
108         # image/recipes/packages
109         self.clear_selection()
110
111         self.user_selected_packages = []
112
113         self.default_task = "build"
114
115         # proxy settings
116         self.enable_proxy = None
117         self.same_proxy = False
118         self.proxies = {
119             "http"  : [None, None, None, "", ""],  # protocol : [prot, user, passwd, host, port]
120             "https" : [None, None, None, "", ""],
121             "ftp"   : [None, None, None, "", ""],
122             "git"   : [None, None, None, "", ""],
123             "cvs"   : [None, None, None, "", ""],
124         }
125
126     def clear_selection(self):
127         self.selected_recipes = []
128         self.selected_packages = []
129         self.initial_selected_image = None
130         self.initial_selected_packages = []
131         self.initial_user_selected_packages = []
132
133     def split_proxy(self, protocol, proxy):
134         entry = []
135         prot, user, passwd, host, port = Configuration.parse_proxy_string(proxy)
136         entry.append(prot)
137         entry.append(user)
138         entry.append(passwd)
139         entry.append(host)
140         entry.append(port)
141         self.proxies[protocol] = entry
142
143     def combine_proxy(self, protocol):
144         entry = self.proxies[protocol]
145         return Configuration.make_proxy_string(entry[0], entry[1], entry[2], entry[3], entry[4], protocol)
146
147     def combine_host_only(self, protocol):
148         entry = self.proxies[protocol]
149         return Configuration.make_host_string(entry[0], entry[1], entry[2], entry[3], protocol)
150
151     def combine_port_only(self, protocol):
152         entry = self.proxies[protocol]
153         return Configuration.make_port_string(entry[4])
154
155     def update(self, params):
156         # settings
157         self.curr_distro = params["distro"]
158         self.dldir = params["dldir"]
159         self.sstatedir = params["sstatedir"]
160         self.sstatemirror = params["sstatemirror"]
161         self.pmake = int(params["pmake"].split()[1])
162         self.bbthread = params["bbthread"]
163         self.curr_package_format = " ".join(params["pclass"].split("package_")).strip()
164         self.image_rootfs_size = params["image_rootfs_size"]
165         self.image_extra_size = params["image_extra_size"]
166         self.image_overhead_factor = params['image_overhead_factor']
167         self.incompat_license = params["incompat_license"]
168         self.curr_sdk_machine = params["sdk_machine"]
169         self.conf_version = params["conf_version"]
170         self.lconf_version = params["lconf_version"]
171         self.image_fstypes = params["image_fstypes"]
172         # self.extra_setting/self.toolchain_build
173         # bblayers.conf
174         self.layers = params["layer"].split()
175         self.default_task = params["default_task"]
176
177         # proxy settings
178         self.enable_proxy = params["http_proxy"] != "" or params["https_proxy"] != "" or params["ftp_proxy"] != "" \
179             or params["git_proxy_host"] != "" or params["git_proxy_port"] != ""                                    \
180             or params["cvs_proxy_host"] != "" or params["cvs_proxy_port"] != ""
181         self.split_proxy("http", params["http_proxy"])
182         self.split_proxy("https", params["https_proxy"])
183         self.split_proxy("ftp", params["ftp_proxy"])
184         self.split_proxy("git", params["git_proxy_host"] + ":" + params["git_proxy_port"])
185         self.split_proxy("cvs", params["cvs_proxy_host"] + ":" + params["cvs_proxy_port"])
186
187     def load(self, template):
188         self.curr_mach = template.getVar("MACHINE")
189         self.curr_package_format = " ".join(template.getVar("PACKAGE_CLASSES").split("package_")).strip()
190         self.curr_distro = template.getVar("DISTRO")
191         self.dldir = template.getVar("DL_DIR")
192         self.sstatedir = template.getVar("SSTATE_DIR")
193         self.sstatemirror = template.getVar("SSTATE_MIRRORS")
194         try:
195             self.pmake = int(template.getVar("PARALLEL_MAKE").split()[1])
196         except:
197             pass
198         try:
199             self.bbthread = int(template.getVar("BB_NUMBER_THREADS"))
200         except:
201             pass
202         try:
203             self.image_rootfs_size = int(template.getVar("IMAGE_ROOTFS_SIZE"))
204         except:
205             pass
206         try:
207             self.image_extra_size = int(template.getVar("IMAGE_EXTRA_SPACE"))
208         except:
209             pass
210         # image_overhead_factor is read-only.
211         self.incompat_license = template.getVar("INCOMPATIBLE_LICENSE")
212         self.curr_sdk_machine = template.getVar("SDKMACHINE")
213         self.conf_version = template.getVar("CONF_VERSION")
214         self.lconf_version = template.getVar("LCONF_VERSION")
215         self.extra_setting = eval(template.getVar("EXTRA_SETTING"))
216         self.toolchain_build = eval(template.getVar("TOOLCHAIN_BUILD"))
217         self.image_fstypes = template.getVar("IMAGE_FSTYPES")
218         # bblayers.conf
219         self.layers = template.getVar("BBLAYERS").split()
220         # image/recipes/packages
221         self.selected_image = template.getVar("__SELECTED_IMAGE__")
222         self.selected_recipes = template.getVar("DEPENDS").split()
223         self.selected_packages = template.getVar("IMAGE_INSTALL").split()
224         # proxy
225         self.enable_proxy = eval(template.getVar("enable_proxy"))
226         self.same_proxy = eval(template.getVar("use_same_proxy"))
227         self.split_proxy("http", template.getVar("http_proxy"))
228         self.split_proxy("https", template.getVar("https_proxy"))
229         self.split_proxy("ftp", template.getVar("ftp_proxy"))
230         self.split_proxy("git", template.getVar("GIT_PROXY_HOST") + ":" + template.getVar("GIT_PROXY_PORT"))
231         self.split_proxy("cvs", template.getVar("CVS_PROXY_HOST") + ":" + template.getVar("CVS_PROXY_PORT"))
232
233     def save(self, template, defaults=False):
234         template.setVar("VERSION", "%s" % hobVer)
235         # bblayers.conf
236         template.setVar("BBLAYERS", " ".join(self.layers))
237         # local.conf
238         if not defaults:
239             template.setVar("MACHINE", self.curr_mach)
240         template.setVar("DISTRO", self.curr_distro)
241         template.setVar("DL_DIR", self.dldir)
242         template.setVar("SSTATE_DIR", self.sstatedir)
243         template.setVar("SSTATE_MIRRORS", self.sstatemirror)
244         template.setVar("PARALLEL_MAKE", "-j %s" % self.pmake)
245         template.setVar("BB_NUMBER_THREADS", self.bbthread)
246         template.setVar("PACKAGE_CLASSES", " ".join(["package_" + i for i in self.curr_package_format.split()]))
247         template.setVar("IMAGE_ROOTFS_SIZE", self.image_rootfs_size)
248         template.setVar("IMAGE_EXTRA_SPACE", self.image_extra_size)
249         template.setVar("INCOMPATIBLE_LICENSE", self.incompat_license)
250         template.setVar("SDKMACHINE", self.curr_sdk_machine)
251         template.setVar("CONF_VERSION", self.conf_version)
252         template.setVar("LCONF_VERSION", self.lconf_version)
253         template.setVar("EXTRA_SETTING", self.extra_setting)
254         template.setVar("TOOLCHAIN_BUILD", self.toolchain_build)
255         template.setVar("IMAGE_FSTYPES", self.image_fstypes)
256         if not defaults:
257             # image/recipes/packages
258             template.setVar("__SELECTED_IMAGE__", self.selected_image)
259             template.setVar("DEPENDS", self.selected_recipes)
260             template.setVar("IMAGE_INSTALL", self.user_selected_packages)
261         # proxy
262         template.setVar("enable_proxy", self.enable_proxy)
263         template.setVar("use_same_proxy", self.same_proxy)
264         template.setVar("http_proxy", self.combine_proxy("http"))
265         template.setVar("https_proxy", self.combine_proxy("https"))
266         template.setVar("ftp_proxy", self.combine_proxy("ftp"))
267         template.setVar("GIT_PROXY_HOST", self.combine_host_only("git"))
268         template.setVar("GIT_PROXY_PORT", self.combine_port_only("git"))
269         template.setVar("CVS_PROXY_HOST", self.combine_host_only("cvs"))
270         template.setVar("CVS_PROXY_PORT", self.combine_port_only("cvs"))
271
272     def __str__(self):
273         s = "VERSION: '%s', BBLAYERS: '%s', MACHINE: '%s', DISTRO: '%s', DL_DIR: '%s'," % \
274             (hobVer, " ".join(self.layers), self.curr_mach, self.curr_distro, self.dldir )
275         s += "SSTATE_DIR: '%s', SSTATE_MIRROR: '%s', PARALLEL_MAKE: '-j %s', BB_NUMBER_THREADS: '%s', PACKAGE_CLASSES: '%s', " % \
276             (self.sstatedir, self.sstatemirror, self.pmake, self.bbthread, " ".join(["package_" + i for i in self.curr_package_format.split()]))
277         s += "IMAGE_ROOTFS_SIZE: '%s', IMAGE_EXTRA_SPACE: '%s', INCOMPATIBLE_LICENSE: '%s', SDKMACHINE: '%s', CONF_VERSION: '%s', " % \
278             (self.image_rootfs_size, self.image_extra_size, self.incompat_license, self.curr_sdk_machine, self.conf_version)
279         s += "LCONF_VERSION: '%s', EXTRA_SETTING: '%s', TOOLCHAIN_BUILD: '%s', IMAGE_FSTYPES: '%s', __SELECTED_IMAGE__: '%s', " % \
280             (self.lconf_version, self.extra_setting, self.toolchain_build, self.image_fstypes, self.selected_image)
281         s += "DEPENDS: '%s', IMAGE_INSTALL: '%s', enable_proxy: '%s', use_same_proxy: '%s', http_proxy: '%s', " % \
282             (self.selected_recipes, self.user_selected_packages, self.enable_proxy, self.same_proxy, self.combine_proxy("http"))
283         s += "https_proxy: '%s', ftp_proxy: '%s', GIT_PROXY_HOST: '%s', GIT_PROXY_PORT: '%s', CVS_PROXY_HOST: '%s', CVS_PROXY_PORT: '%s'" % \
284             (self.combine_proxy("https"), self.combine_proxy("ftp"),self.combine_host_only("git"), self.combine_port_only("git"),
285              self.combine_host_only("cvs"), self.combine_port_only("cvs"))
286         return s
287
288 class Parameters:
289     '''Represents other variables like available machines, etc.'''
290
291     def __init__(self):
292         # Variables
293         self.max_threads = 65535
294         self.core_base = ""
295         self.image_addr = ""
296         self.image_types = []
297         self.runnable_image_types = []
298         self.runnable_machine_patterns = []
299         self.deployable_image_types = []
300         self.tmpdir = ""
301
302         self.all_machines = []
303         self.all_package_formats = []
304         self.all_distros = []
305         self.all_sdk_machines = []
306         self.all_layers = []
307         self.image_names = []
308         self.image_white_pattern = ""
309         self.image_black_pattern = ""
310
311         # for build log to show
312         self.bb_version = ""
313         self.target_arch = ""
314         self.target_os = ""
315         self.distro_version = ""
316         self.tune_pkgarch = ""
317
318     def update(self, params):
319         self.max_threads = params["max_threads"]
320         self.core_base = params["core_base"]
321         self.image_addr = params["image_addr"]
322         self.image_types = params["image_types"].split()
323         self.runnable_image_types = params["runnable_image_types"].split()
324         self.runnable_machine_patterns = params["runnable_machine_patterns"].split()
325         self.deployable_image_types = params["deployable_image_types"].split()
326         self.tmpdir = params["tmpdir"]
327         self.image_white_pattern = params["image_white_pattern"]
328         self.image_black_pattern = params["image_black_pattern"]
329         self.kernel_image_type = params["kernel_image_type"]
330         # for build log to show
331         self.bb_version = params["bb_version"]
332         self.target_arch = params["target_arch"]
333         self.target_os = params["target_os"]
334         self.distro_version = params["distro_version"]
335         self.tune_pkgarch = params["tune_pkgarch"]
336
337 def hob_conf_filter(fn, data):
338     if fn.endswith("/local.conf"):
339         distro = data.getVar("DISTRO_HOB")
340         if distro:
341             if distro != "defaultsetup":
342                 data.setVar("DISTRO", distro)
343             else:
344                 data.delVar("DISTRO")
345
346         keys = ["MACHINE_HOB", "SDKMACHINE_HOB", "PACKAGE_CLASSES_HOB", \
347                 "BB_NUMBER_THREADS_HOB", "PARALLEL_MAKE_HOB", "DL_DIR_HOB", \
348                 "SSTATE_DIR_HOB", "SSTATE_MIRRORS_HOB", "INCOMPATIBLE_LICENSE_HOB"]
349         for key in keys:
350             var_hob = data.getVar(key)
351             if var_hob:
352                 data.setVar(key.split("_HOB")[0], var_hob)
353         return
354
355     if fn.endswith("/bblayers.conf"):
356         layers = data.getVar("BBLAYERS_HOB")
357         if layers:
358             data.setVar("BBLAYERS", layers)
359         return
360
361 class Builder(gtk.Window):
362
363     (INITIAL_CHECKS,
364      MACHINE_SELECTION,
365      RCPPKGINFO_POPULATING,
366      RCPPKGINFO_POPULATED,
367      BASEIMG_SELECTED,
368      RECIPE_SELECTION,
369      PACKAGE_GENERATING,
370      PACKAGE_GENERATED,
371      PACKAGE_SELECTION,
372      FAST_IMAGE_GENERATING,
373      IMAGE_GENERATING,
374      IMAGE_GENERATED,
375      MY_IMAGE_OPENED,
376      BACK,
377      END_NOOP) = range(15)
378
379     (SANITY_CHECK,
380      IMAGE_CONFIGURATION,
381      RECIPE_DETAILS,
382      BUILD_DETAILS,
383      PACKAGE_DETAILS,
384      IMAGE_DETAILS,
385      END_TAB) = range(7)
386
387     __step2page__ = {
388         INITIAL_CHECKS        : SANITY_CHECK,
389         MACHINE_SELECTION     : IMAGE_CONFIGURATION,
390         RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION,
391         RCPPKGINFO_POPULATED  : IMAGE_CONFIGURATION,
392         BASEIMG_SELECTED      : IMAGE_CONFIGURATION,
393         RECIPE_SELECTION      : RECIPE_DETAILS,
394         PACKAGE_GENERATING    : BUILD_DETAILS,
395         PACKAGE_GENERATED     : PACKAGE_DETAILS,
396         PACKAGE_SELECTION     : PACKAGE_DETAILS,
397         FAST_IMAGE_GENERATING : BUILD_DETAILS,
398         IMAGE_GENERATING      : BUILD_DETAILS,
399         IMAGE_GENERATED       : IMAGE_DETAILS,
400         MY_IMAGE_OPENED       : IMAGE_DETAILS,
401         END_NOOP              : None,
402     }
403
404     SANITY_CHECK_MIN_DISPLAY_TIME = 5
405
406     def __init__(self, hobHandler, recipe_model, package_model):
407         super(Builder, self).__init__()
408
409         self.hob_image = "hob-image"
410         self.hob_toolchain = "hob-toolchain"
411
412         # handler
413         self.handler = hobHandler
414
415         self.template = None
416
417         # logger
418         self.logger = logging.getLogger("BitBake")
419         self.consolelog = None
420         self.current_logfile = None
421
422         # configuration and parameters
423         self.configuration = Configuration()
424         self.parameters = Parameters()
425
426         # build step
427         self.current_step = None
428         self.previous_step = None
429
430         self.stopping = False
431
432         # recipe model and package model
433         self.recipe_model = recipe_model
434         self.package_model = package_model
435
436         # Indicate whether user has customized the image
437         self.customized = False
438
439         # Indicate whether the UI is working
440         self.sensitive = True
441
442         # create visual elements
443         self.create_visual_elements()
444
445         # connect the signals to functions
446         self.connect("delete-event", self.destroy_window_cb)
447         self.recipe_model.connect ("recipe-selection-changed",  self.recipelist_changed_cb)
448         self.package_model.connect("package-selection-changed", self.packagelist_changed_cb)
449         self.handler.connect("config-updated",           self.handler_config_updated_cb)
450         self.handler.connect("package-formats-updated",  self.handler_package_formats_updated_cb)
451         self.handler.connect("parsing-started",          self.handler_parsing_started_cb)
452         self.handler.connect("parsing",                  self.handler_parsing_cb)
453         self.handler.connect("parsing-completed",        self.handler_parsing_completed_cb)
454         self.handler.build.connect("build-started",      self.handler_build_started_cb)
455         self.handler.build.connect("build-succeeded",    self.handler_build_succeeded_cb)
456         self.handler.build.connect("build-failed",       self.handler_build_failed_cb)
457         self.handler.build.connect("build-aborted",      self.handler_build_aborted_cb)
458         self.handler.build.connect("task-started",       self.handler_task_started_cb)
459         self.handler.build.connect("log-error",          self.handler_build_failure_cb)
460         self.handler.build.connect("log",                self.handler_build_log_cb)
461         self.handler.build.connect("no-provider",        self.handler_no_provider_cb)
462         self.handler.connect("generating-data",          self.handler_generating_data_cb)
463         self.handler.connect("data-generated",           self.handler_data_generated_cb)
464         self.handler.connect("command-succeeded",        self.handler_command_succeeded_cb)
465         self.handler.connect("command-failed",           self.handler_command_failed_cb)
466         self.handler.connect("sanity-failed",            self.handler_sanity_failed_cb)
467         self.handler.connect("recipe-populated",         self.handler_recipe_populated_cb)
468         self.handler.connect("package-populated",        self.handler_package_populated_cb)
469
470         self.handler.set_config_filter(hob_conf_filter)
471
472         self.initiate_new_build_async()
473
474     def create_visual_elements(self):
475         self.set_title("Hob")
476         self.set_icon_name("applications-development")
477         self.set_resizable(True)
478
479         try:
480             window_width = self.get_screen().get_width()
481             window_height = self.get_screen().get_height()
482         except AttributeError:
483             print "Please set DISPLAY variable before running Hob."
484             sys.exit(1)
485
486         if window_width >= hwc.MAIN_WIN_WIDTH:
487             window_width = hwc.MAIN_WIN_WIDTH
488             window_height = hwc.MAIN_WIN_HEIGHT
489         self.set_size_request(window_width, window_height)
490
491         self.vbox = gtk.VBox(False, 0)
492         self.vbox.set_border_width(0)
493         self.add(self.vbox)
494
495         # create pages
496         self.image_configuration_page = ImageConfigurationPage(self)
497         self.recipe_details_page      = RecipeSelectionPage(self)
498         self.build_details_page       = BuildDetailsPage(self)
499         self.package_details_page     = PackageSelectionPage(self)
500         self.image_details_page       = ImageDetailsPage(self)
501         self.sanity_check_page        = SanityCheckPage(self)
502         self.display_sanity_check = False
503         self.sanity_check_post_func = False
504         self.had_network_error = False
505
506         self.nb = gtk.Notebook()
507         self.nb.set_show_tabs(False)
508         self.nb.insert_page(self.sanity_check_page,        None, self.SANITY_CHECK)
509         self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION)
510         self.nb.insert_page(self.recipe_details_page,      None, self.RECIPE_DETAILS)
511         self.nb.insert_page(self.build_details_page,       None, self.BUILD_DETAILS)
512         self.nb.insert_page(self.package_details_page,     None, self.PACKAGE_DETAILS)
513         self.nb.insert_page(self.image_details_page,       None, self.IMAGE_DETAILS)
514         self.vbox.pack_start(self.nb, expand=True, fill=True)
515
516         self.show_all()
517         self.nb.set_current_page(0)
518
519     def sanity_check_timeout(self):
520         # The minimum time for showing the 'sanity check' page has passe
521         # If someone set the 'sanity_check_post_step' meanwhile, execute it now
522         self.display_sanity_check = False
523         if self.sanity_check_post_func:
524           temp = self.sanity_check_post_func
525           self.sanity_check_post_func = None
526           temp()
527         return False
528
529     def show_sanity_check_page(self):
530         # This window must stay on screen for at least 5 seconds, according to the design document
531         self.nb.set_current_page(self.SANITY_CHECK)
532         self.sanity_check_post_step = None
533         self.display_sanity_check = True
534         self.sanity_check_page.start()
535         gobject.timeout_add(self.SANITY_CHECK_MIN_DISPLAY_TIME * 1000, self.sanity_check_timeout)
536
537     def execute_after_sanity_check(self, func):
538         if not self.display_sanity_check:
539           func()
540         else:
541           sanity_check_post_func = func
542
543     def generate_configuration(self):
544         self.show_sanity_check_page()
545         self.handler.generate_configuration()
546
547     def initiate_new_build_async(self):
548         self.switch_page(self.MACHINE_SELECTION)
549         if self.load_template(TemplateMgr.convert_to_template_pathfilename("default", ".hob/")) == False:
550             self.show_sanity_check_page()
551             self.handler.init_cooker()
552             self.handler.set_extra_inherit("image_types")
553             self.generate_configuration()
554
555     def update_config_async(self):
556         self.switch_page(self.MACHINE_SELECTION)
557         self.set_user_config()
558         self.generate_configuration()
559
560     def sanity_check(self):
561         self.handler.trigger_sanity_check()
562
563     def populate_recipe_package_info_async(self):
564         self.switch_page(self.RCPPKGINFO_POPULATING)
565         # Parse recipes
566         self.set_user_config()
567         self.handler.generate_recipes()
568
569     def generate_packages_async(self, log = False):
570         self.switch_page(self.PACKAGE_GENERATING)
571         if log:
572             self.current_logfile = self.handler.get_logfile()
573             self.do_log(self.current_logfile)
574         # Build packages
575         _, all_recipes = self.recipe_model.get_selected_recipes()
576         self.set_user_config()
577         self.handler.reset_build()
578         self.handler.generate_packages(all_recipes, self.configuration.default_task)
579
580     def restore_initial_selected_packages(self):
581         self.package_model.set_selected_packages(self.configuration.initial_user_selected_packages, True)
582         self.package_model.set_selected_packages(self.configuration.initial_selected_packages)
583         for package in self.configuration.selected_packages:
584             if package not in self.configuration.initial_selected_packages:
585                 self.package_model.exclude_item(self.package_model.find_path_for_item(package))
586
587     def fast_generate_image_async(self, log = False):
588         self.switch_page(self.FAST_IMAGE_GENERATING)
589         if log:
590             self.current_logfile = self.handler.get_logfile()
591             self.do_log(self.current_logfile)
592         # Build packages
593         _, all_recipes = self.recipe_model.get_selected_recipes()
594         self.set_user_config()
595         self.handler.reset_build()
596         self.handler.generate_packages(all_recipes, self.configuration.default_task)
597
598     def generate_image_async(self, cont = False):
599         self.switch_page(self.IMAGE_GENERATING)
600         self.handler.reset_build()
601         if not cont:
602             self.current_logfile = self.handler.get_logfile()
603             self.do_log(self.current_logfile)
604         # Build image
605         self.set_user_config()
606         toolchain_packages = []
607         if self.configuration.toolchain_build:
608             toolchain_packages = self.package_model.get_selected_packages_toolchain()
609         if self.configuration.selected_image == self.recipe_model.__custom_image__:
610             packages = self.package_model.get_selected_packages()
611             image = self.hob_image
612         else:
613             packages = []
614             image = self.configuration.selected_image
615         self.handler.generate_image(image,
616                                     self.hob_toolchain,
617                                     packages,
618                                     toolchain_packages,
619                                     self.configuration.default_task)
620
621     def get_parameters_sync(self):
622         return self.handler.get_parameters()
623
624     def request_package_info_async(self):
625         self.handler.request_package_info()
626
627     def cancel_build_sync(self, force=False):
628         self.handler.cancel_build(force)
629
630     def cancel_parse_sync(self):
631         self.handler.cancel_parse()
632
633     def load_template(self, path):
634         if not os.path.isfile(path):
635             return False
636
637         self.template = TemplateMgr()
638         # check compatibility
639         tempVer = self.template.getVersion(path)
640         if not tempVer or int(tempVer) < hobVer:
641             self.template.destroy()
642             self.template = None
643             return False
644
645         try:
646             self.template.load(path)
647             self.configuration.load(self.template)
648         except Exception as e:
649             self.show_error_dialog("Hob Exception - %s" % (str(e)))
650             self.reset()
651         finally:
652             self.template.destroy()
653             self.template = None
654
655         for layer in self.configuration.layers:
656             if not os.path.exists(layer+'/conf/layer.conf'):
657                 return False
658
659         self.save_defaults() # remember layers and settings
660         self.update_config_async()
661         return True
662
663     def save_template(self, path, defaults=False):
664         if path.rfind("/") == -1:
665             filename = "default"
666             path = "."
667         else:
668             filename = path[path.rfind("/") + 1:len(path)]
669             path = path[0:path.rfind("/")]
670
671         self.template = TemplateMgr()
672         try:
673             self.template.open(filename, path)
674             self.configuration.save(self.template, defaults)
675
676             self.template.save()
677         except Exception as e:
678             self.show_error_dialog("Hob Exception - %s" % (str(e)))
679             self.reset()
680         finally:
681             self.template.destroy()
682             self.template = None
683
684     def save_defaults(self):
685         if not os.path.exists(".hob/"):
686             os.mkdir(".hob/")
687         self.save_template(".hob/default", True)
688
689     def switch_page(self, next_step):
690         # Main Workflow (Business Logic)
691         self.nb.set_current_page(self.__step2page__[next_step])
692
693         if next_step == self.MACHINE_SELECTION: # init step
694             self.image_configuration_page.show_machine()
695
696         elif next_step == self.RCPPKGINFO_POPULATING:
697             # MACHINE CHANGED action or SETTINGS CHANGED
698             # show the progress bar
699             self.image_configuration_page.show_info_populating()
700
701         elif next_step == self.RCPPKGINFO_POPULATED:
702             self.image_configuration_page.show_info_populated()
703
704         elif next_step == self.BASEIMG_SELECTED:
705             self.image_configuration_page.show_baseimg_selected()
706
707         elif next_step == self.RECIPE_SELECTION:
708             if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
709                 self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.ALL)
710             else:
711                 self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.INCLUDED)
712
713         elif next_step == self.PACKAGE_SELECTION:
714             self.configuration.initial_selected_packages = self.configuration.selected_packages
715             self.configuration.initial_user_selected_packages = self.configuration.user_selected_packages
716             self.package_details_page.set_title("Edit packages")
717             if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
718                 self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL)
719             else:
720                 self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED)
721             self.package_details_page.show_page(self.current_logfile)
722
723
724         elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING:
725             # both PACKAGE_GENERATING and FAST_IMAGE_GENERATING share the same page
726             self.build_details_page.show_page(next_step)
727
728         elif next_step == self.PACKAGE_GENERATED:
729             self.package_details_page.set_title("Step 2 of 2: Edit packages")
730             if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
731                 self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL)
732             else:
733                 self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED)
734             self.package_details_page.show_page(self.current_logfile)
735
736         elif next_step == self.IMAGE_GENERATING:
737             # after packages are generated, selected_packages need to
738             # be updated in package_model per selected_image in recipe_model
739             self.build_details_page.show_page(next_step)
740
741         elif next_step == self.IMAGE_GENERATED:
742             self.image_details_page.show_page(next_step)
743
744         elif next_step == self.MY_IMAGE_OPENED:
745             self.image_details_page.show_page(next_step)
746
747         self.previous_step = self.current_step
748         self.current_step = next_step
749
750     def set_user_config_proxies(self):
751         if self.configuration.enable_proxy == True:
752             self.handler.set_http_proxy(self.configuration.combine_proxy("http"))
753             self.handler.set_https_proxy(self.configuration.combine_proxy("https"))
754             self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp"))
755             self.handler.set_git_proxy(self.configuration.combine_host_only("git"), self.configuration.combine_port_only("git"))
756             self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs"))
757         elif self.configuration.enable_proxy == False:
758             self.handler.set_http_proxy("")
759             self.handler.set_https_proxy("")
760             self.handler.set_ftp_proxy("")
761             self.handler.set_git_proxy("", "")
762             self.handler.set_cvs_proxy("", "")
763
764     def set_user_config(self):
765         self.handler.init_cooker()
766         # set bb layers
767         self.handler.set_bblayers(self.configuration.layers)
768         # set local configuration
769         self.handler.set_machine(self.configuration.curr_mach)
770         self.handler.set_package_format(self.configuration.curr_package_format)
771         self.handler.set_distro(self.configuration.curr_distro)
772         self.handler.set_dl_dir(self.configuration.dldir)
773         self.handler.set_sstate_dir(self.configuration.sstatedir)
774         self.handler.set_sstate_mirrors(self.configuration.sstatemirror)
775         self.handler.set_pmake(self.configuration.pmake)
776         self.handler.set_bbthreads(self.configuration.bbthread)
777         self.handler.set_rootfs_size(self.configuration.image_rootfs_size)
778         self.handler.set_extra_size(self.configuration.image_extra_size)
779         self.handler.set_incompatible_license(self.configuration.incompat_license)
780         self.handler.set_sdk_machine(self.configuration.curr_sdk_machine)
781         self.handler.set_image_fstypes(self.configuration.image_fstypes)
782         self.handler.set_extra_config(self.configuration.extra_setting)
783         self.handler.set_extra_inherit("packageinfo")
784         self.handler.set_extra_inherit("image_types")
785         self.set_user_config_proxies()
786
787     def update_recipe_model(self, selected_image, selected_recipes):
788         self.recipe_model.set_selected_image(selected_image)
789         self.recipe_model.set_selected_recipes(selected_recipes)
790
791     def update_package_model(self, selected_packages, user_selected_packages=None):
792         if user_selected_packages:
793             left = self.package_model.set_selected_packages(user_selected_packages, True)
794             self.configuration.user_selected_packages += left
795         left = self.package_model.set_selected_packages(selected_packages)
796         self.configuration.selected_packages += left
797
798     def update_configuration_parameters(self, params):
799         if params:
800             self.configuration.update(params)
801             self.parameters.update(params)
802
803     def reset(self):
804         self.configuration.curr_mach = ""
805         self.configuration.clear_selection()
806         self.image_configuration_page.switch_machine_combo()
807         self.switch_page(self.MACHINE_SELECTION)
808
809     # Callback Functions
810     def handler_config_updated_cb(self, handler, which, values):
811         if which == "distro":
812             self.parameters.all_distros = values
813         elif which == "machine":
814             self.parameters.all_machines = values
815             self.image_configuration_page.update_machine_combo()
816         elif which == "machine-sdk":
817             self.parameters.all_sdk_machines = values
818
819     def handler_package_formats_updated_cb(self, handler, formats):
820         self.parameters.all_package_formats = formats
821
822     def switch_to_image_configuration_helper(self):
823         self.sanity_check_page.stop()
824         self.switch_page(self.IMAGE_CONFIGURATION)
825         self.image_configuration_page.switch_machine_combo()
826
827     def show_network_error_dialog_helper(self):
828         self.sanity_check_page.stop()
829         self.show_network_error_dialog()
830
831     def handler_command_succeeded_cb(self, handler, initcmd):
832         if initcmd == self.handler.GENERATE_CONFIGURATION:
833             if not self.configuration.curr_mach:
834                 self.configuration.curr_mach = self.handler.runCommand(["getVariable", "HOB_MACHINE"]) or ""
835             self.update_configuration_parameters(self.get_parameters_sync())
836             self.sanity_check()
837         elif initcmd == self.handler.SANITY_CHECK:
838             if self.had_network_error:
839                 self.had_network_error = False
840                 self.execute_after_sanity_check(self.show_network_error_dialog_helper)
841             else:
842                 # Switch to the 'image configuration' page now, but we might need
843                 # to wait for the minimum display time of the sanity check page
844                 self.execute_after_sanity_check(self.switch_to_image_configuration_helper)
845         elif initcmd in [self.handler.GENERATE_RECIPES,
846                          self.handler.GENERATE_PACKAGES,
847                          self.handler.GENERATE_IMAGE]:
848             self.update_configuration_parameters(self.get_parameters_sync())
849             self.request_package_info_async()
850         elif initcmd == self.handler.POPULATE_PACKAGEINFO:
851             if self.current_step == self.RCPPKGINFO_POPULATING:
852                 self.switch_page(self.RCPPKGINFO_POPULATED)
853                 self.rcppkglist_populated()
854                 return
855
856             self.rcppkglist_populated()
857             if self.current_step == self.FAST_IMAGE_GENERATING:
858                 self.generate_image_async(True)
859
860     def show_error_dialog(self, msg):
861         lbl = "<b>Hob found an error</b>\n"
862         dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR, msg)
863         button = dialog.add_button("Close", gtk.RESPONSE_OK)
864         HobButton.style_button(button)
865         response = dialog.run()
866         dialog.destroy()
867
868     def show_network_error_dialog(self):
869         lbl = "<b>Hob cannot connect to the network</b>\n"
870         msg = "Please check your network connection. If you are using a proxy server, please make sure it is configured correctly."
871         lbl = lbl + "%s\n\n" % glib.markup_escape_text(msg)
872         dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR)
873         button = dialog.add_button("Close", gtk.RESPONSE_OK)
874         HobButton.style_button(button)
875         button = dialog.add_button("Proxy settings", gtk.RESPONSE_CANCEL)
876         HobButton.style_button(button)
877         res = dialog.run()
878         dialog.destroy()
879         if res == gtk.RESPONSE_CANCEL:
880             res, settings_changed = self.show_simple_settings_dialog(SimpleSettingsDialog.PROXIES_PAGE_ID)
881             if not res:
882                 return
883             if settings_changed:
884                 self.reparse_post_adv_settings()
885
886     def handler_command_failed_cb(self, handler, msg):
887         if msg:
888             self.show_error_dialog(msg)
889         self.reset()
890
891     def handler_sanity_failed_cb(self, handler, msg, network_error):
892         self.reset()
893         if network_error:
894             # Mark this in an internal field. The "network error" dialog will be
895             # shown later, when a SanityCheckPassed event will be handled
896             # (as sent by sanity.bbclass)
897             self.had_network_error = True
898         else:
899             msg = msg.replace("your local.conf", "Settings")
900             self.show_error_dialog(msg)
901             self.reset()
902
903     def window_sensitive(self, sensitive):
904         self.image_configuration_page.machine_combo.set_sensitive(sensitive)
905         self.image_configuration_page.machine_combo.child.set_sensitive(sensitive)
906         self.image_configuration_page.image_combo.set_sensitive(sensitive)
907         self.image_configuration_page.image_combo.child.set_sensitive(sensitive)
908         self.image_configuration_page.layer_button.set_sensitive(sensitive)
909         self.image_configuration_page.layer_info_icon.set_sensitive(sensitive)
910         self.image_configuration_page.toolbar.set_sensitive(sensitive)
911         self.image_configuration_page.view_adv_configuration_button.set_sensitive(sensitive)
912         self.image_configuration_page.config_build_button.set_sensitive(sensitive)
913
914         self.recipe_details_page.set_sensitive(sensitive)
915         self.package_details_page.set_sensitive(sensitive)
916         self.build_details_page.set_sensitive(sensitive)
917         self.image_details_page.set_sensitive(sensitive)
918
919         if sensitive:
920             self.window.set_cursor(None)
921         else:
922             self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
923         self.sensitive = sensitive
924
925
926     def handler_generating_data_cb(self, handler):
927         self.window_sensitive(False)
928
929     def handler_data_generated_cb(self, handler):
930         self.window_sensitive(True)
931
932     def rcppkglist_populated(self):
933         selected_image = self.configuration.selected_image
934         selected_recipes = self.configuration.selected_recipes[:]
935         selected_packages = self.configuration.selected_packages[:]
936         user_selected_packages = self.configuration.user_selected_packages[:]
937
938         self.image_configuration_page.update_image_combo(self.recipe_model, selected_image)
939         self.image_configuration_page.update_image_desc()
940         self.update_recipe_model(selected_image, selected_recipes)
941         self.update_package_model(selected_packages, user_selected_packages)
942
943     def recipelist_changed_cb(self, recipe_model):
944         self.recipe_details_page.refresh_selection()
945
946     def packagelist_changed_cb(self, package_model):
947         self.package_details_page.refresh_selection()
948
949     def handler_recipe_populated_cb(self, handler):
950         self.image_configuration_page.update_progress_bar("Populating recipes", 0.99)
951
952     def handler_package_populated_cb(self, handler):
953         self.image_configuration_page.update_progress_bar("Populating packages", 1.0)
954
955     def handler_parsing_started_cb(self, handler, message):
956         if self.current_step != self.RCPPKGINFO_POPULATING:
957             return
958
959         fraction = 0
960         if message["eventname"] == "TreeDataPreparationStarted":
961             fraction = 0.6 + fraction
962             self.image_configuration_page.stop_button.set_sensitive(False)
963             self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
964         else:
965             self.image_configuration_page.stop_button.set_sensitive(True)
966             self.image_configuration_page.update_progress_bar(message["title"], fraction)
967
968     def handler_parsing_cb(self, handler, message):
969         if self.current_step != self.RCPPKGINFO_POPULATING:
970             return
971
972         fraction = message["current"] * 1.0/message["total"]
973         if message["eventname"] == "TreeDataPreparationProgress":
974             fraction = 0.6 + 0.38 * fraction
975             self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
976         else:
977             fraction = 0.6 * fraction
978             self.image_configuration_page.update_progress_bar(message["title"], fraction)
979
980     def handler_parsing_completed_cb(self, handler, message):
981         if self.current_step != self.RCPPKGINFO_POPULATING:
982             return
983
984         if message["eventname"] == "TreeDataPreparationCompleted":
985             fraction = 0.98
986         else:
987             fraction = 0.6
988         self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
989
990     def handler_build_started_cb(self, running_build):
991         if self.current_step == self.FAST_IMAGE_GENERATING:
992             fraction = 0
993         elif self.current_step == self.IMAGE_GENERATING:
994             if self.previous_step == self.FAST_IMAGE_GENERATING:
995                 fraction = 0.9
996             else:
997                 fraction = 0
998         elif self.current_step == self.PACKAGE_GENERATING:
999             fraction = 0
1000         self.build_details_page.update_progress_bar("Build Started: ", fraction)
1001         self.build_details_page.show_configurations(self.configuration, self.parameters)
1002
1003     def build_succeeded(self):
1004         if self.current_step == self.FAST_IMAGE_GENERATING:
1005             fraction = 0.9
1006         elif self.current_step == self.IMAGE_GENERATING:
1007             fraction = 1.0
1008             self.parameters.image_names = []
1009             selected_image = self.recipe_model.get_selected_image()
1010             if selected_image == self.recipe_model.__custom_image__:
1011                 linkname = 'hob-image-' + self.configuration.curr_mach
1012             else:
1013                 linkname = selected_image + '-' + self.configuration.curr_mach
1014             image_extension = self.get_image_extension()
1015             for image_type in self.parameters.image_types:
1016                 if image_type in image_extension:
1017                     real_types = image_extension[image_type]
1018                 else:
1019                     real_types = [image_type]
1020                 for real_image_type in real_types:
1021                     linkpath = self.parameters.image_addr + '/' + linkname + '.' + real_image_type
1022                     if os.path.exists(linkpath):
1023                         self.parameters.image_names.append(os.readlink(linkpath))
1024         elif self.current_step == self.PACKAGE_GENERATING:
1025             fraction = 1.0
1026         self.build_details_page.update_progress_bar("Build Completed: ", fraction)
1027         self.handler.build_succeeded_async()
1028         self.stopping = False
1029
1030         if self.current_step == self.PACKAGE_GENERATING:
1031             self.switch_page(self.PACKAGE_GENERATED)
1032         elif self.current_step == self.IMAGE_GENERATING:
1033             self.switch_page(self.IMAGE_GENERATED)
1034
1035     def build_failed(self):
1036         if self.stopping:
1037             status = "stop"
1038             message = "Build stopped: "
1039             fraction = self.build_details_page.progress_bar.get_fraction()
1040             stop_to_next_edit = ""
1041             if self.current_step == self.FAST_IMAGE_GENERATING:
1042                 stop_to_next_edit = "image configuration"
1043             elif self.current_step == self.IMAGE_GENERATING:
1044                 if self.previous_step == self.FAST_IMAGE_GENERATING:
1045                     stop_to_next_edit = "image configuration"
1046                 else:
1047                     stop_to_next_edit = "packages"
1048             elif self.current_step == self.PACKAGE_GENERATING:
1049                 stop_to_next_edit = "recipes"
1050             button = self.build_details_page.show_stop_page(stop_to_next_edit.split(' ')[0])
1051             self.set_default(button)
1052         else:
1053             fail_to_next_edit = ""
1054             if self.current_step == self.FAST_IMAGE_GENERATING:
1055                 fail_to_next_edit = "image configuration"
1056                 fraction = 0.9
1057             elif self.current_step == self.IMAGE_GENERATING:
1058                 if self.previous_step == self.FAST_IMAGE_GENERATING:
1059                     fail_to_next_edit = "image configuration"
1060                 else:
1061                     fail_to_next_edit = "packages"
1062                 fraction = 1.0
1063             elif self.current_step == self.PACKAGE_GENERATING:
1064                 fail_to_next_edit = "recipes"
1065                 fraction = 1.0
1066             self.build_details_page.show_fail_page(fail_to_next_edit.split(' ')[0])
1067             status = "fail"
1068             message = "Build failed: "
1069         self.build_details_page.update_progress_bar(message, fraction, status)
1070         self.build_details_page.show_back_button()
1071         self.build_details_page.hide_stop_button()
1072         self.handler.build_failed_async()
1073         self.stopping = False
1074
1075     def handler_build_succeeded_cb(self, running_build):
1076         if not self.stopping:
1077             self.build_succeeded()
1078         else:
1079             self.build_failed()
1080
1081
1082     def handler_build_failed_cb(self, running_build):
1083         self.build_failed()
1084
1085     def handler_build_aborted_cb(self, running_build):
1086         self.build_failed()
1087
1088     def handler_no_provider_cb(self, running_build, msg):
1089         dialog = CrumbsMessageDialog(self, glib.markup_escape_text(msg), gtk.STOCK_DIALOG_INFO)
1090         button = dialog.add_button("Close", gtk.RESPONSE_OK)
1091         HobButton.style_button(button)
1092         dialog.run()
1093         dialog.destroy()
1094         self.build_failed()
1095
1096     def handler_task_started_cb(self, running_build, message): 
1097         fraction = message["current"] * 1.0/message["total"]
1098         title = "Build packages"
1099         if self.current_step == self.FAST_IMAGE_GENERATING:
1100             if message["eventname"] == "sceneQueueTaskStarted":
1101                 fraction = 0.27 * fraction
1102             elif message["eventname"] == "runQueueTaskStarted":
1103                 fraction = 0.27 + 0.63 * fraction
1104         elif self.current_step == self.IMAGE_GENERATING:
1105             title = "Build image"
1106             if self.previous_step == self.FAST_IMAGE_GENERATING:
1107                 if message["eventname"] == "sceneQueueTaskStarted":
1108                     fraction = 0.27 + 0.63 + 0.03 * fraction
1109                 elif message["eventname"] == "runQueueTaskStarted":
1110                     fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction
1111             else:
1112                 if message["eventname"] == "sceneQueueTaskStarted":
1113                     fraction = 0.2 * fraction
1114                 elif message["eventname"] == "runQueueTaskStarted":
1115                     fraction = 0.2 + 0.8 * fraction
1116         elif self.current_step == self.PACKAGE_GENERATING:
1117             if message["eventname"] == "sceneQueueTaskStarted":
1118                 fraction = 0.2 * fraction
1119             elif message["eventname"] == "runQueueTaskStarted":
1120                 fraction = 0.2 + 0.8 * fraction
1121         self.build_details_page.update_progress_bar(title + ": ", fraction)
1122         self.build_details_page.update_build_status(message["current"], message["total"], message["task"])
1123
1124     def handler_build_failure_cb(self, running_build):
1125         self.build_details_page.show_issues()
1126
1127     def handler_build_log_cb(self, running_build, func, obj):
1128         if hasattr(self.logger, func):
1129             getattr(self.logger, func)(obj)
1130
1131     def destroy_window_cb(self, widget, event):
1132         if not self.sensitive:
1133             return True
1134         elif self.handler.building:
1135             self.stop_build()
1136             return True
1137         else:
1138             gtk.main_quit()
1139
1140     def build_packages(self):
1141         _, all_recipes = self.recipe_model.get_selected_recipes()
1142         if not all_recipes:
1143             lbl = "<b>No selections made</b>\nYou have not made any selections"
1144             lbl = lbl + " so there isn't anything to bake at this time."
1145             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1146             button = dialog.add_button("Close", gtk.RESPONSE_OK)
1147             HobButton.style_button(button)
1148             dialog.run()
1149             dialog.destroy()
1150             return
1151         self.generate_packages_async(True)
1152
1153     def build_image(self):
1154         selected_packages = self.package_model.get_selected_packages()
1155         if not selected_packages:      
1156             lbl = "<b>No selections made</b>\nYou have not made any selections"
1157             lbl = lbl + " so there isn't anything to bake at this time."
1158             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1159             button = dialog.add_button("Close", gtk.RESPONSE_OK)
1160             HobButton.style_button(button)
1161             dialog.run()
1162             dialog.destroy()
1163             return
1164         self.generate_image_async(True)
1165
1166     def just_bake(self):
1167         selected_image = self.recipe_model.get_selected_image()
1168         selected_packages = self.package_model.get_selected_packages() or []
1169
1170         # If no base image and no selected packages don't build anything
1171         if not (selected_packages or selected_image != self.recipe_model.__custom_image__):
1172             lbl = "<b>No selections made</b>\nYou have not made any selections"
1173             lbl = lbl + " so there isn't anything to bake at this time."
1174             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1175             button = dialog.add_button("Close", gtk.RESPONSE_OK)
1176             HobButton.style_button(button)
1177             dialog.run()
1178             dialog.destroy()
1179             return
1180
1181         self.fast_generate_image_async(True)
1182
1183     def show_binb_dialog(self, binb):
1184         markup = "<b>Brought in by:</b>\n%s" % binb
1185         ptip = PersistentTooltip(markup, self)
1186
1187         ptip.show()
1188
1189     def show_layer_selection_dialog(self):
1190         dialog = LayerSelectionDialog(title = "Layers",
1191                      layers = copy.deepcopy(self.configuration.layers),
1192                      all_layers = self.parameters.all_layers,
1193                      parent = self,
1194                      flags = gtk.DIALOG_MODAL
1195                          | gtk.DIALOG_DESTROY_WITH_PARENT
1196                          | gtk.DIALOG_NO_SEPARATOR)
1197         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1198         HobAltButton.style_button(button)
1199         button = dialog.add_button("OK", gtk.RESPONSE_YES)
1200         HobButton.style_button(button)
1201         response = dialog.run()
1202         if response == gtk.RESPONSE_YES:
1203             self.configuration.layers = dialog.layers
1204             self.save_defaults() # remember layers
1205             # DO refresh layers
1206             if dialog.layers_changed:
1207                 self.update_config_async()
1208         dialog.destroy()
1209
1210     def show_load_template_dialog(self):
1211         dialog = gtk.FileChooserDialog("Load Template Files", self,
1212                                        gtk.FILE_CHOOSER_ACTION_OPEN)
1213         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1214         HobAltButton.style_button(button)
1215         button = dialog.add_button("Open", gtk.RESPONSE_YES)
1216         HobButton.style_button(button)
1217         filter = gtk.FileFilter()
1218         filter.set_name("Hob Files")
1219         filter.add_pattern("*.hob")
1220         dialog.add_filter(filter)
1221
1222         response = dialog.run()
1223         path = None
1224         if response == gtk.RESPONSE_YES:
1225             path = dialog.get_filename()
1226         dialog.destroy()
1227         return response == gtk.RESPONSE_YES, path
1228
1229     def show_save_template_dialog(self):
1230         dialog = gtk.FileChooserDialog("Save Template Files", self,
1231                                        gtk.FILE_CHOOSER_ACTION_SAVE)
1232         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1233         HobAltButton.style_button(button)
1234         button = dialog.add_button("Save", gtk.RESPONSE_YES)
1235         HobButton.style_button(button)
1236         dialog.set_current_name("hob")
1237         response = dialog.run()
1238         if response == gtk.RESPONSE_YES:
1239             path = dialog.get_filename()
1240             self.save_template(path)
1241         dialog.destroy()
1242
1243     def get_image_extension(self):
1244         image_extension = {}
1245         for type in self.parameters.image_types:
1246             ext = self.handler.runCommand(["getVariable", "IMAGE_EXTENSION_%s" % type])
1247             if ext:
1248                 image_extension[type] = ext.split(' ')
1249
1250         return image_extension
1251
1252     def show_load_my_images_dialog(self):
1253         image_extension = self.get_image_extension()
1254         dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types,
1255                                       "Open My Images", self,
1256                                        gtk.FILE_CHOOSER_ACTION_SAVE, None,
1257                                        image_extension)
1258         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1259         HobAltButton.style_button(button)
1260         button = dialog.add_button("Open", gtk.RESPONSE_YES)
1261         HobButton.style_button(button)
1262         response = dialog.run()
1263         if response == gtk.RESPONSE_YES:
1264             if not dialog.image_names:
1265                 lbl = "<b>No selections made</b>\nYou have not made any selections"
1266                 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1267                 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
1268                 HobButton.style_button(button)
1269                 crumbs_dialog.run()
1270                 crumbs_dialog.destroy()
1271                 dialog.destroy()
1272                 return
1273
1274             self.parameters.image_addr = dialog.image_folder
1275             self.parameters.image_names = dialog.image_names[:]
1276             self.switch_page(self.MY_IMAGE_OPENED)
1277
1278         dialog.destroy()
1279
1280     def show_adv_settings_dialog(self, tab=None):
1281         dialog = AdvancedSettingDialog(title = "Advanced configuration",
1282             configuration = copy.deepcopy(self.configuration),
1283             all_image_types = self.parameters.image_types,
1284             all_package_formats = self.parameters.all_package_formats,
1285             all_distros = self.parameters.all_distros,
1286             all_sdk_machines = self.parameters.all_sdk_machines,
1287             max_threads = self.parameters.max_threads,
1288             parent = self,
1289             flags = gtk.DIALOG_MODAL
1290                     | gtk.DIALOG_DESTROY_WITH_PARENT
1291                     | gtk.DIALOG_NO_SEPARATOR)
1292         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1293         HobAltButton.style_button(button)
1294         button = dialog.add_button("Save", gtk.RESPONSE_YES)
1295         HobButton.style_button(button)
1296         dialog.set_save_button(button)
1297         response = dialog.run()
1298         settings_changed = False
1299         if response == gtk.RESPONSE_YES:
1300             self.configuration = dialog.configuration
1301             self.save_defaults() # remember settings
1302             settings_changed = dialog.settings_changed
1303         dialog.destroy()
1304         return response == gtk.RESPONSE_YES, settings_changed
1305
1306     def show_simple_settings_dialog(self, tab=None):
1307         dialog = SimpleSettingsDialog(title = "Settings",
1308             configuration = copy.deepcopy(self.configuration),
1309             all_image_types = self.parameters.image_types,
1310             all_package_formats = self.parameters.all_package_formats,
1311             all_distros = self.parameters.all_distros,
1312             all_sdk_machines = self.parameters.all_sdk_machines,
1313             max_threads = self.parameters.max_threads,
1314             parent = self,
1315             flags = gtk.DIALOG_MODAL
1316                     | gtk.DIALOG_DESTROY_WITH_PARENT
1317                     | gtk.DIALOG_NO_SEPARATOR,
1318             handler = self.handler)
1319         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1320         HobAltButton.style_button(button)
1321         button = dialog.add_button("Save", gtk.RESPONSE_YES)
1322         HobButton.style_button(button)
1323         if tab:
1324             dialog.switch_to_page(tab)
1325         response = dialog.run()
1326         settings_changed = False
1327         if response == gtk.RESPONSE_YES:
1328             self.configuration = dialog.configuration
1329             self.save_defaults() # remember settings
1330             settings_changed = dialog.settings_changed
1331             if dialog.proxy_settings_changed:
1332                 self.set_user_config_proxies()
1333         elif dialog.proxy_test_ran:
1334             # The user might have modified the proxies in the "Proxy"
1335             # tab, which in turn made the proxy settings modify in bb.
1336             # If "Cancel" was pressed, restore the previous proxy
1337             # settings inside bb.
1338             self.set_user_config_proxies()
1339         dialog.destroy()
1340         return response == gtk.RESPONSE_YES, settings_changed
1341
1342     def reparse_post_adv_settings(self):
1343         if not self.configuration.curr_mach:
1344             self.update_config_async()
1345         else:
1346             self.configuration.clear_selection()
1347             # DO reparse recipes
1348             self.populate_recipe_package_info_async()
1349
1350     def deploy_image(self, image_name):
1351         if not image_name:
1352             lbl = "<b>Please select an image to deploy.</b>"
1353             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1354             button = dialog.add_button("Close", gtk.RESPONSE_OK)
1355             HobButton.style_button(button)
1356             dialog.run()
1357             dialog.destroy()
1358             return
1359
1360         image_path = os.path.join(self.parameters.image_addr, image_name)
1361         dialog = DeployImageDialog(title = "Usb Image Maker",
1362             image_path = image_path,
1363             parent = self,
1364             flags = gtk.DIALOG_MODAL
1365                     | gtk.DIALOG_DESTROY_WITH_PARENT
1366                     | gtk.DIALOG_NO_SEPARATOR)
1367         button = dialog.add_button("Close", gtk.RESPONSE_NO)
1368         HobAltButton.style_button(button)
1369         button = dialog.add_button("Make usb image", gtk.RESPONSE_YES)
1370         HobButton.style_button(button)
1371         response = dialog.run()
1372         dialog.destroy()
1373
1374     def show_load_kernel_dialog(self):
1375         dialog = gtk.FileChooserDialog("Load Kernel Files", self,
1376                                        gtk.FILE_CHOOSER_ACTION_SAVE)
1377         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1378         HobAltButton.style_button(button)
1379         button = dialog.add_button("Open", gtk.RESPONSE_YES)
1380         HobButton.style_button(button)
1381         filter = gtk.FileFilter()
1382         filter.set_name("Kernel Files")
1383         filter.add_pattern("*.bin")
1384         dialog.add_filter(filter)
1385
1386         dialog.set_current_folder(self.parameters.image_addr)
1387
1388         response = dialog.run()
1389         kernel_path = ""
1390         if response == gtk.RESPONSE_YES:
1391             kernel_path = dialog.get_filename()
1392
1393         dialog.destroy()
1394
1395         return kernel_path
1396
1397     def runqemu_image(self, image_name, kernel_name):
1398         if not image_name or not kernel_name:
1399             lbl = "<b>Please select an %s to launch in QEMU.</b>" % ("kernel" if image_name else "image")
1400             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1401             button = dialog.add_button("Close", gtk.RESPONSE_OK)
1402             HobButton.style_button(button)
1403             dialog.run()
1404             dialog.destroy()
1405             return
1406
1407         kernel_path = os.path.join(self.parameters.image_addr, kernel_name)
1408         image_path = os.path.join(self.parameters.image_addr, image_name)
1409
1410         source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env")
1411         tmp_path = self.parameters.tmpdir
1412         cmdline = bb.ui.crumbs.utils.which_terminal()
1413         if os.path.exists(image_path) and os.path.exists(kernel_path) \
1414            and os.path.exists(source_env_path) and os.path.exists(tmp_path) \
1415            and cmdline:
1416             cmdline += "\' bash -c \"export OE_TMPDIR=" + tmp_path + "; "
1417             cmdline += "source " + source_env_path + " " + os.getcwd() + "; "
1418             cmdline += "runqemu " + kernel_path + " " + image_path + "\"\'"
1419             subprocess.Popen(shlex.split(cmdline))
1420         else:
1421             lbl = "<b>Path error</b>\nOne of your paths is wrong,"
1422             lbl = lbl + " please make sure the following paths exist:\n"
1423             lbl = lbl + "image path:" + image_path + "\n"
1424             lbl = lbl + "kernel path:" + kernel_path + "\n"
1425             lbl = lbl + "source environment path:" + source_env_path + "\n"
1426             lbl = lbl + "tmp path: " + tmp_path + "."
1427             lbl = lbl + "You may be missing either xterm or vte for terminal services."
1428             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR)
1429             button = dialog.add_button("Close", gtk.RESPONSE_OK)
1430             HobButton.style_button(button)
1431             dialog.run()
1432             dialog.destroy()
1433
1434     def show_packages(self, ask=True):
1435         _, selected_recipes = self.recipe_model.get_selected_recipes()
1436         if selected_recipes and ask:
1437             lbl = "<b>Package list may be incomplete!</b>\nDo you want to build selected recipes"
1438             lbl = lbl + " to get a full list or just view the existing packages?"
1439             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1440             button = dialog.add_button("View packages", gtk.RESPONSE_NO)
1441             HobAltButton.style_button(button)
1442             button = dialog.add_button("Build packages", gtk.RESPONSE_YES)
1443             HobButton.style_button(button)
1444             dialog.set_default_response(gtk.RESPONSE_YES)
1445             response = dialog.run()
1446             dialog.destroy()
1447             if response == gtk.RESPONSE_YES:
1448                 self.generate_packages_async(True)
1449             else:
1450                 self.switch_page(self.PACKAGE_SELECTION)
1451         else:
1452             self.switch_page(self.PACKAGE_SELECTION)
1453
1454     def show_recipes(self):
1455         self.switch_page(self.RECIPE_SELECTION)
1456
1457     def show_image_details(self):
1458         self.switch_page(self.IMAGE_GENERATED)
1459
1460     def show_configuration(self):
1461         self.switch_page(self.BASEIMG_SELECTED)
1462
1463     def stop_build(self):
1464         if self.stopping:
1465             lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once,"
1466             lbl = lbl + " would you like to 'Force Stop' the build?\n\n"
1467             lbl = lbl + "This will stop the build as quickly as possible but may"
1468             lbl = lbl + " well leave your build directory in an  unusable state"
1469             lbl = lbl + " that requires manual steps to fix.\n"
1470             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
1471             button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
1472             HobAltButton.style_button(button)
1473             button = dialog.add_button("Force Stop", gtk.RESPONSE_YES)
1474             HobButton.style_button(button)
1475         else:
1476             lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this"
1477             lbl = lbl + " build?\n\n'Stop' will stop the build as soon as all in"
1478             lbl = lbl + " progress build tasks are finished. However if a"
1479             lbl = lbl + " lengthy compilation phase is in progress this may take"
1480             lbl = lbl + " some time.\n\n"
1481             lbl = lbl + "'Force Stop' will stop the build as quickly as"
1482             lbl = lbl + " possible but may well leave your build directory in an"
1483             lbl = lbl + " unusable state that requires manual steps to fix."
1484             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
1485             button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
1486             HobAltButton.style_button(button)
1487             button = dialog.add_button("Force stop", gtk.RESPONSE_YES)
1488             HobAltButton.style_button(button)
1489             button = dialog.add_button("Stop", gtk.RESPONSE_OK)
1490             HobButton.style_button(button)
1491         response = dialog.run()
1492         dialog.destroy()
1493         if response != gtk.RESPONSE_CANCEL:
1494             self.stopping = True
1495         if response == gtk.RESPONSE_OK:
1496             self.build_details_page.progress_bar.set_stop_title("Stopping the build....")
1497             self.build_details_page.progress_bar.set_rcstyle("stop")
1498             self.cancel_build_sync()
1499         elif response == gtk.RESPONSE_YES:
1500             self.cancel_build_sync(True)
1501
1502     def do_log(self, consolelogfile = None):
1503         if consolelogfile:
1504             bb.utils.mkdirhier(os.path.dirname(consolelogfile))
1505             if self.consolelog:
1506                 self.logger.removeHandler(self.consolelog)
1507                 self.consolelog = None
1508             self.consolelog = logging.FileHandler(consolelogfile)
1509             bb.msg.addDefaultlogFilter(self.consolelog)
1510             format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
1511             self.consolelog.setFormatter(format)
1512
1513             self.logger.addHandler(self.consolelog)