hob: providing details about process state through porgress bar
[bitbake.git] / lib / bb / ui / crumbs / hobeventhandler.py
1 #
2 # BitBake Graphical GTK User Interface
3 #
4 # Copyright (C) 2011        Intel Corporation
5 #
6 # Authored by Joshua Lock <josh@linux.intel.com>
7 # Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License version 2 as
11 # published by the Free Software Foundation.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along
19 # with this program; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22 import gobject
23 import logging
24 from bb.ui.crumbs.runningbuild import RunningBuild
25
26 class HobHandler(gobject.GObject):
27
28     """
29     This object does BitBake event handling for the hob gui.
30     """
31     __gsignals__ = {
32          "package-formats-updated" : (gobject.SIGNAL_RUN_LAST,
33                                       gobject.TYPE_NONE,
34                                      (gobject.TYPE_PYOBJECT,)),
35          "config-updated"          : (gobject.SIGNAL_RUN_LAST,
36                                       gobject.TYPE_NONE,
37                                      (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
38          "command-succeeded"       : (gobject.SIGNAL_RUN_LAST,
39                                       gobject.TYPE_NONE,
40                                      (gobject.TYPE_INT,)),
41          "command-failed"          : (gobject.SIGNAL_RUN_LAST,
42                                       gobject.TYPE_NONE,
43                                      (gobject.TYPE_STRING,)),
44          "sanity-failed"           : (gobject.SIGNAL_RUN_LAST,
45                                       gobject.TYPE_NONE,
46                                      (gobject.TYPE_STRING, gobject.TYPE_INT)),
47          "generating-data"         : (gobject.SIGNAL_RUN_LAST,
48                                       gobject.TYPE_NONE,
49                                      ()),
50          "data-generated"          : (gobject.SIGNAL_RUN_LAST,
51                                       gobject.TYPE_NONE,
52                                      ()),
53          "parsing-started"         : (gobject.SIGNAL_RUN_LAST,
54                                       gobject.TYPE_NONE,
55                                      (gobject.TYPE_PYOBJECT,)),
56          "parsing"                 : (gobject.SIGNAL_RUN_LAST,
57                                       gobject.TYPE_NONE,
58                                      (gobject.TYPE_PYOBJECT,)),
59          "parsing-completed"       : (gobject.SIGNAL_RUN_LAST,
60                                       gobject.TYPE_NONE,
61                                      (gobject.TYPE_PYOBJECT,)),
62          "recipe-populated"        : (gobject.SIGNAL_RUN_LAST,
63                                       gobject.TYPE_NONE,
64                                      ()),
65          "package-populated"       : (gobject.SIGNAL_RUN_LAST,
66                                       gobject.TYPE_NONE,
67                                      ()),
68          "network-passed"          : (gobject.SIGNAL_RUN_LAST,
69                                       gobject.TYPE_NONE,
70                                      ()),
71          "network-failed"          : (gobject.SIGNAL_RUN_LAST,
72                                       gobject.TYPE_NONE,
73                                      ()),
74     }
75
76     (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK, NETWORK_TEST) = range(7)
77     (SUB_PATH_LAYERS, SUB_FILES_DISTRO, SUB_FILES_MACH, SUB_FILES_SDKMACH, SUB_MATCH_CLASS, SUB_PARSE_CONFIG, SUB_SANITY_CHECK,
78      SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE, SUB_NETWORK_TEST) = range(12)
79
80     def __init__(self, server, recipe_model, package_model):
81         super(HobHandler, self).__init__()
82
83         self.build = RunningBuild(sequential=True)
84
85         self.recipe_model = recipe_model
86         self.package_model = package_model
87
88         self.commands_async = []
89         self.generating = False
90         self.current_phase = None
91         self.building = False
92         self.recipe_queue = []
93         self.package_queue = []
94
95         self.server = server
96         self.error_msg = ""
97         self.initcmd = None
98
99     def set_busy(self):
100         if not self.generating:
101             self.emit("generating-data")
102             self.generating = True
103
104     def clear_busy(self):
105         if self.generating:
106             self.emit("data-generated")
107             self.generating = False
108
109     def runCommand(self, commandline):
110         try:
111             result = self.server.runCommand(commandline)
112             result_str = str(result)
113             if (result_str.startswith("Busy (") or
114                     result_str == "No such command"):
115                 raise Exception('%s has failed with output "%s". ' %
116                         (str(commandline), result_str) +
117                         "We recommend that you restart Hob.")
118             return result
119         except Exception as e:
120             self.commands_async = []
121             self.clear_busy()
122             self.emit("command-failed", "Hob Exception - %s" % (str(e)))
123             return None
124
125     def run_next_command(self, initcmd=None):
126         if initcmd != None:
127             self.initcmd = initcmd
128
129         if self.commands_async:
130             self.set_busy()
131             next_command = self.commands_async.pop(0)
132         else:
133             self.clear_busy()
134             if self.initcmd != None:
135                 self.emit("command-succeeded", self.initcmd)
136             return
137
138         if next_command == self.SUB_PATH_LAYERS:
139             self.runCommand(["findConfigFilePath", "bblayers.conf"])
140         elif next_command == self.SUB_FILES_DISTRO:
141             self.runCommand(["findConfigFiles", "DISTRO"])
142         elif next_command == self.SUB_FILES_MACH:
143             self.runCommand(["findConfigFiles", "MACHINE"])
144         elif next_command == self.SUB_FILES_SDKMACH:
145             self.runCommand(["findConfigFiles", "MACHINE-SDK"])
146         elif next_command == self.SUB_MATCH_CLASS:
147             self.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"])
148         elif next_command == self.SUB_PARSE_CONFIG:
149             self.runCommand(["parseConfigurationFiles", "", ""])
150         elif next_command == self.SUB_GNERATE_TGTS:
151             self.runCommand(["generateTargetsTree", "classes/image.bbclass", []])
152         elif next_command == self.SUB_GENERATE_PKGINFO:
153             self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"])
154         elif next_command == self.SUB_SANITY_CHECK:
155             self.runCommand(["triggerEvent", "bb.event.SanityCheck()"])
156         elif next_command == self.SUB_NETWORK_TEST:
157             self.runCommand(["triggerEvent", "bb.event.NetworkTest()"])
158         elif next_command == self.SUB_BUILD_RECIPES:
159             self.clear_busy()
160             self.building = True
161             self.runCommand(["buildTargets", self.recipe_queue, self.default_task])
162             self.recipe_queue = []
163         elif next_command == self.SUB_BUILD_IMAGE:
164             self.clear_busy()
165             self.building = True
166             targets = [self.image]
167             if self.package_queue:
168                 self.runCommand(["setVariable", "LINGUAS_INSTALL", ""])
169                 self.runCommand(["setVariable", "PACKAGE_INSTALL", " ".join(self.package_queue)])
170             if self.toolchain_packages:
171                 self.runCommand(["setVariable", "TOOLCHAIN_TARGET_TASK", " ".join(self.toolchain_packages)])
172                 targets.append(self.toolchain)
173             self.runCommand(["buildTargets", targets, self.default_task])
174
175     def display_error(self):
176         self.clear_busy()
177         self.emit("command-failed", self.error_msg)
178         self.error_msg = ""
179         if self.building:
180             self.building = False
181
182     def handle_event(self, event):
183         if not event:
184             return
185         if self.building:
186             self.current_phase = "building"
187             self.build.handle_event(event)
188
189         if isinstance(event, bb.event.PackageInfo):
190             self.package_model.populate(event._pkginfolist)
191             self.emit("package-populated")
192             self.run_next_command()
193
194         elif isinstance(event, bb.event.SanityCheckPassed):
195             self.run_next_command()
196
197         elif isinstance(event, bb.event.SanityCheckFailed):
198             self.emit("sanity-failed", event._msg, event._network_error)
199
200         elif isinstance(event, logging.LogRecord):
201             if not self.building:
202                 if event.levelno >= logging.ERROR:
203                     formatter = bb.msg.BBLogFormatter()
204                     msg = formatter.format(event)
205                     self.error_msg += msg + '\n'
206
207         elif isinstance(event, bb.event.TargetsTreeGenerated):
208             self.current_phase = "data generation"
209             if event._model:
210                 self.recipe_model.populate(event._model)
211                 self.emit("recipe-populated")
212         elif isinstance(event, bb.event.ConfigFilesFound):
213             self.current_phase = "configuration lookup"
214             var = event._variable
215             values = event._values
216             values.sort()
217             self.emit("config-updated", var, values)
218         elif isinstance(event, bb.event.ConfigFilePathFound):
219             self.current_phase = "configuration lookup"
220         elif isinstance(event, bb.event.FilesMatchingFound):
221             self.current_phase = "configuration lookup"
222             # FIXME: hard coding, should at least be a variable shared between
223             # here and the caller
224             if event._pattern == "rootfs_":
225                 formats = []
226                 for match in event._matches:
227                     classname, sep, cls = match.rpartition(".")
228                     fs, sep, format = classname.rpartition("_")
229                     formats.append(format)
230                 formats.sort()
231                 self.emit("package-formats-updated", formats)
232         elif isinstance(event, bb.command.CommandCompleted):
233             self.current_phase = None
234             self.run_next_command()
235         elif isinstance(event, bb.command.CommandFailed):
236             self.commands_async = []
237             self.display_error()
238         elif isinstance(event, (bb.event.ParseStarted,
239                  bb.event.CacheLoadStarted,
240                  bb.event.TreeDataPreparationStarted,
241                  )):
242             message = {}
243             message["eventname"] = bb.event.getName(event)
244             message["current"] = 0
245             message["total"] = None
246             message["title"] = "Parsing recipes"
247             self.emit("parsing-started", message)
248         elif isinstance(event, (bb.event.ParseProgress,
249                 bb.event.CacheLoadProgress,
250                 bb.event.TreeDataPreparationProgress)):
251             message = {}
252             message["eventname"] = bb.event.getName(event)
253             message["current"] = event.current
254             message["total"] = event.total
255             message["title"] = "Parsing recipes"
256             self.emit("parsing", message)
257         elif isinstance(event, (bb.event.ParseCompleted,
258                 bb.event.CacheLoadCompleted,
259                 bb.event.TreeDataPreparationCompleted)):
260             message = {}
261             message["eventname"] = bb.event.getName(event)
262             message["current"] = event.total
263             message["total"] = event.total
264             message["title"] = "Parsing recipes"
265             self.emit("parsing-completed", message)
266         elif isinstance(event, bb.event.NetworkTestFailed):
267             self.emit("network-failed")
268             self.run_next_command()
269         elif isinstance(event, bb.event.NetworkTestPassed):
270             self.emit("network-passed")
271             self.run_next_command()
272
273         if self.error_msg and not self.commands_async:
274             self.display_error()
275
276         return
277
278     def init_cooker(self):
279         self.runCommand(["initCooker"])
280
281     def set_extra_inherit(self, bbclass):
282         inherits = self.runCommand(["getVariable", "INHERIT"]) or ""
283         inherits = inherits + " " + bbclass
284         self.runCommand(["setVariable", "INHERIT", inherits])
285
286     def set_bblayers(self, bblayers):
287         self.runCommand(["setVariable", "BBLAYERS_HOB", " ".join(bblayers)])
288
289     def set_machine(self, machine):
290         if machine:
291             self.runCommand(["setVariable", "MACHINE_HOB", machine])
292
293     def set_sdk_machine(self, sdk_machine):
294         self.runCommand(["setVariable", "SDKMACHINE_HOB", sdk_machine])
295
296     def set_image_fstypes(self, image_fstypes):
297         self.runCommand(["setVariable", "IMAGE_FSTYPES", image_fstypes])
298
299     def set_distro(self, distro):
300         self.runCommand(["setVariable", "DISTRO_HOB", distro])
301
302     def set_package_format(self, format):
303         package_classes = ""
304         for pkgfmt in format.split():
305             package_classes += ("package_%s" % pkgfmt + " ")
306         self.runCommand(["setVariable", "PACKAGE_CLASSES_HOB", package_classes])
307
308     def set_bbthreads(self, threads):
309         self.runCommand(["setVariable", "BB_NUMBER_THREADS_HOB", threads])
310
311     def set_pmake(self, threads):
312         pmake = "-j %s" % threads
313         self.runCommand(["setVariable", "PARALLEL_MAKE_HOB", pmake])
314
315     def set_dl_dir(self, directory):
316         self.runCommand(["setVariable", "DL_DIR_HOB", directory])
317
318     def set_sstate_dir(self, directory):
319         self.runCommand(["setVariable", "SSTATE_DIR_HOB", directory])
320
321     def set_sstate_mirrors(self, url):
322         self.runCommand(["setVariable", "SSTATE_MIRRORS_HOB", url])
323
324     def set_extra_size(self, image_extra_size):
325         self.runCommand(["setVariable", "IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size)])
326
327     def set_rootfs_size(self, image_rootfs_size):
328         self.runCommand(["setVariable", "IMAGE_ROOTFS_SIZE", str(image_rootfs_size)])
329
330     def set_incompatible_license(self, incompat_license):
331         self.runCommand(["setVariable", "INCOMPATIBLE_LICENSE_HOB", incompat_license])
332
333     def set_extra_config(self, extra_setting):
334         for key in extra_setting.keys():
335             value = extra_setting[key]
336             self.runCommand(["setVariable", key, value])
337
338     def set_config_filter(self, config_filter):
339         self.runCommand(["setConfFilter", config_filter])
340
341     def set_http_proxy(self, http_proxy):
342         self.runCommand(["setVariable", "http_proxy", http_proxy])
343
344     def set_https_proxy(self, https_proxy):
345         self.runCommand(["setVariable", "https_proxy", https_proxy])
346
347     def set_ftp_proxy(self, ftp_proxy):
348         self.runCommand(["setVariable", "ftp_proxy", ftp_proxy])
349
350     def set_git_proxy(self, host, port):
351         self.runCommand(["setVariable", "GIT_PROXY_HOST", host])
352         self.runCommand(["setVariable", "GIT_PROXY_PORT", port])
353
354     def set_cvs_proxy(self, host, port):
355         self.runCommand(["setVariable", "CVS_PROXY_HOST", host])
356         self.runCommand(["setVariable", "CVS_PROXY_PORT", port])
357
358     def request_package_info(self):
359         self.commands_async.append(self.SUB_GENERATE_PKGINFO)
360         self.run_next_command(self.POPULATE_PACKAGEINFO)
361
362     def trigger_sanity_check(self):
363         self.commands_async.append(self.SUB_SANITY_CHECK)
364         self.run_next_command(self.SANITY_CHECK)
365
366     def trigger_network_test(self):
367         self.commands_async.append(self.SUB_NETWORK_TEST)
368         self.run_next_command(self.NETWORK_TEST)
369
370     def generate_configuration(self):
371         self.commands_async.append(self.SUB_PARSE_CONFIG)
372         self.commands_async.append(self.SUB_PATH_LAYERS)
373         self.commands_async.append(self.SUB_FILES_DISTRO)
374         self.commands_async.append(self.SUB_FILES_MACH)
375         self.commands_async.append(self.SUB_FILES_SDKMACH)
376         self.commands_async.append(self.SUB_MATCH_CLASS)
377         self.run_next_command(self.GENERATE_CONFIGURATION)
378
379     def generate_recipes(self):
380         self.commands_async.append(self.SUB_PARSE_CONFIG)
381         self.commands_async.append(self.SUB_GNERATE_TGTS)
382         self.run_next_command(self.GENERATE_RECIPES)
383
384     def generate_packages(self, tgts, default_task="build"):
385         targets = []
386         targets.extend(tgts)
387         self.recipe_queue = targets
388         self.default_task = default_task
389         self.commands_async.append(self.SUB_PARSE_CONFIG)
390         self.commands_async.append(self.SUB_BUILD_RECIPES)
391         self.run_next_command(self.GENERATE_PACKAGES)
392
393     def generate_image(self, image, toolchain, image_packages=[], toolchain_packages=[], default_task="build"):
394         self.image = image
395         self.toolchain = toolchain
396         self.package_queue = image_packages
397         self.toolchain_packages = toolchain_packages
398         self.default_task = default_task
399         self.commands_async.append(self.SUB_PARSE_CONFIG)
400         self.commands_async.append(self.SUB_BUILD_IMAGE)
401         self.run_next_command(self.GENERATE_IMAGE)
402
403     def build_succeeded_async(self):
404         self.building = False
405
406     def build_failed_async(self):
407         self.initcmd = None
408         self.commands_async = []
409         self.building = False
410
411     def cancel_parse(self):
412         self.runCommand(["stateStop"])
413
414     def cancel_build(self, force=False):
415         if force:
416             # Force the cooker to stop as quickly as possible
417             self.runCommand(["stateStop"])
418         else:
419             # Wait for tasks to complete before shutting down, this helps
420             # leave the workdir in a usable state
421             self.runCommand(["stateShutdown"])
422
423     def reset_build(self):
424         self.build.reset()
425
426     def get_logfile(self):
427         return self.server.runCommand(["getVariable", "BB_CONSOLELOG"])
428
429     def _remove_redundant(self, string):
430         ret = []
431         for i in string.split():
432             if i not in ret:
433                 ret.append(i)
434         return " ".join(ret)
435
436     def get_parameters(self):
437         # retrieve the parameters from bitbake
438         params = {}
439         params["core_base"] = self.runCommand(["getVariable", "COREBASE"]) or ""
440         hob_layer = params["core_base"] + "/meta-hob"
441         params["layer"] = self.runCommand(["getVariable", "BBLAYERS"]) or ""
442         if hob_layer not in params["layer"].split():
443             params["layer"] += (" " + hob_layer)
444         params["dldir"] = self.runCommand(["getVariable", "DL_DIR"]) or ""
445         params["machine"] = self.runCommand(["getVariable", "MACHINE"]) or ""
446         params["distro"] = self.runCommand(["getVariable", "DISTRO"]) or "defaultsetup"
447         params["pclass"] = self.runCommand(["getVariable", "PACKAGE_CLASSES"]) or ""
448         params["sstatedir"] = self.runCommand(["getVariable", "SSTATE_DIR"]) or ""
449         params["sstatemirror"] = self.runCommand(["getVariable", "SSTATE_MIRRORS"]) or ""
450
451         num_threads = self.runCommand(["getCpuCount"])
452         if not num_threads:
453             num_threads = 1
454             max_threads = 65536
455         else:
456             try:
457                 num_threads = int(num_threads)
458                 max_threads = 16 * num_threads
459             except:
460                 num_threads = 1
461                 max_threads = 65536
462         params["max_threads"] = max_threads
463
464         bbthread = self.runCommand(["getVariable", "BB_NUMBER_THREADS"])
465         if not bbthread:
466             bbthread = num_threads
467         else:
468             try:
469                 bbthread = int(bbthread)
470             except:
471                 bbthread = num_threads
472         params["bbthread"] = bbthread
473
474         pmake = self.runCommand(["getVariable", "PARALLEL_MAKE"])
475         if not pmake:
476             pmake = num_threads
477         elif isinstance(pmake, int):
478             pass
479         else:
480             try:
481                 pmake = int(pmake.lstrip("-j "))
482             except:
483                 pmake = num_threads
484         params["pmake"] = "-j %s" % pmake
485
486         params["image_addr"] = self.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or ""
487
488         image_extra_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"])
489         if not image_extra_size:
490             image_extra_size = 0
491         else:
492             try:
493                 image_extra_size = int(image_extra_size)
494             except:
495                 image_extra_size = 0
496         params["image_extra_size"] = image_extra_size
497
498         image_rootfs_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_SIZE"])
499         if not image_rootfs_size:
500             image_rootfs_size = 0
501         else:
502             try:
503                 image_rootfs_size = int(image_rootfs_size)
504             except:
505                 image_rootfs_size = 0
506         params["image_rootfs_size"] = image_rootfs_size
507
508         image_overhead_factor = self.runCommand(["getVariable", "IMAGE_OVERHEAD_FACTOR"])
509         if not image_overhead_factor:
510             image_overhead_factor = 1
511         else:
512             try:
513                 image_overhead_factor = float(image_overhead_factor)
514             except:
515                 image_overhead_factor = 1
516         params['image_overhead_factor'] = image_overhead_factor
517
518         params["incompat_license"] = self._remove_redundant(self.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "")
519         params["sdk_machine"] = self.runCommand(["getVariable", "SDKMACHINE"]) or self.runCommand(["getVariable", "SDK_ARCH"]) or ""
520
521         params["image_fstypes"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "")
522
523         params["image_types"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_TYPES"]) or "")
524
525         params["conf_version"] = self.runCommand(["getVariable", "CONF_VERSION"]) or ""
526         params["lconf_version"] = self.runCommand(["getVariable", "LCONF_VERSION"]) or ""
527
528         params["runnable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "")
529         params["runnable_machine_patterns"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "")
530         params["deployable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "")
531         params["kernel_image_type"] = self.runCommand(["getVariable", "KERNEL_IMAGETYPE"]) or ""
532         params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or ""
533         params["distro_version"] = self.runCommand(["getVariable", "DISTRO_VERSION"]) or ""
534         params["target_os"] = self.runCommand(["getVariable", "TARGET_OS"]) or ""
535         params["target_arch"] = self.runCommand(["getVariable", "TARGET_ARCH"]) or ""
536         params["tune_pkgarch"] = self.runCommand(["getVariable", "TUNE_PKGARCH"])  or ""
537         params["bb_version"] = self.runCommand(["getVariable", "BB_MIN_VERSION"]) or ""
538
539         params["default_task"] = self.runCommand(["getVariable", "BB_DEFAULT_TASK"]) or "build"
540
541         params["git_proxy_host"] = self.runCommand(["getVariable", "GIT_PROXY_HOST"]) or ""
542         params["git_proxy_port"] = self.runCommand(["getVariable", "GIT_PROXY_PORT"]) or ""
543
544         params["http_proxy"] = self.runCommand(["getVariable", "http_proxy"]) or ""
545         params["ftp_proxy"] = self.runCommand(["getVariable", "ftp_proxy"]) or ""
546         params["https_proxy"] = self.runCommand(["getVariable", "https_proxy"]) or ""
547
548         params["cvs_proxy_host"] = self.runCommand(["getVariable", "CVS_PROXY_HOST"]) or ""
549         params["cvs_proxy_port"] = self.runCommand(["getVariable", "CVS_PROXY_PORT"]) or ""
550
551         params["image_white_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_WHITE_PATTERN"]) or ""
552         params["image_black_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_BLACK_PATTERN"]) or ""
553         return params