summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDongxiao Xu <dongxiao.xu@intel.com>2011-11-28 14:32:40 +0800
committerRichard Purdie <richard.purdie@linuxfoundation.org>2012-02-24 18:04:27 +0000
commit656f9a07588cc00704825a78de9649ca4a1552b8 (patch)
tree653c7941689599994d5876162c540fb7ee22736e
parent14df6d53b6856ec78322b9c0ef01e26c0406fe28 (diff)
downloadpoky-656f9a07588cc00704825a78de9649ca4a1552b8.tar.gz
Hob: A new implemetation (v2)
This commit implements a new design for hob Some of the new features: - Friendly new designed GUI. Quick response to user actions. - Two step builds support package generation and image generation. - Support running GUI seprarately from bitbake server. - Recipe/package selection and deselection. - Accurate customization for image contents and size. - Progress bars showing the parsing and build status. - Load/save user configurations from/into templates. (Bitbake rev: 4dacd29f9c957d20f4583330b51e5420f9c3338d) Signed-off-by: Dongxiao Xu <dongxiao.xu@intel.com> Signed-off-by: Shane Wang <shane.wang@intel.com> Signed-off-by: Liming An <limingx.l.an@intel.com> Signed-off-by: Fengxia Hua <fengxia.hua@intel.com> Designed-by: Belen Barros Pena <belen.barros.pena@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--bitbake/lib/bb/build.py10
-rw-r--r--bitbake/lib/bb/runqueue.py12
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/builddetailspage.py110
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/builder.py873
-rw-r--r--bitbake/lib/bb/ui/crumbs/configurator.py346
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig.py589
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobcolor.py35
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobeventhandler.py586
-rw-r--r--bitbake/lib/bb/ui/crumbs/hoblistmodel.py765
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/hobpages.py87
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobprefs.py335
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobwidget.py805
-rw-r--r--bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py358
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/imagedetailspage.py294
-rw-r--r--bitbake/lib/bb/ui/crumbs/layereditor.py153
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/packageselectionpage.py226
-rw-r--r--bitbake/lib/bb/ui/crumbs/progress.py20
-rw-r--r--bitbake/lib/bb/ui/crumbs/progressbar.py52
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/recipeselectionpage.py221
-rw-r--r--bitbake/lib/bb/ui/crumbs/runningbuild.py55
-rw-r--r--bitbake/lib/bb/ui/crumbs/tasklistmodel.py620
-rw-r--r--bitbake/lib/bb/ui/crumbs/template.py180
-rwxr-xr-x[-rw-r--r--]bitbake/lib/bb/ui/hob.py1115
-rw-r--r--bitbake/lib/bb/ui/icons/images/images_display.pngbin0 -> 6898 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/images/images_hover.pngbin0 -> 7051 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/indicators/alert.pngbin0 -> 3954 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/indicators/confirmation.pngbin0 -> 5789 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/indicators/denied.pngbin0 -> 3955 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/indicators/error.pngbin0 -> 6482 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/indicators/issues.pngbin0 -> 4549 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/indicators/refresh.pngbin0 -> 5250 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/indicators/tick.pngbin0 -> 4563 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/info/info_display.pngbin0 -> 4760 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/info/info_hover.pngbin0 -> 4847 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/layers/layers_display.pngbin0 -> 5326 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/layers/layers_hover.pngbin0 -> 5390 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/packages/packages_display.pngbin0 -> 7188 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/packages/packages_hover.pngbin0 -> 7308 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/recipe/recipe_display.pngbin0 -> 4873 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/recipe/recipe_hover.pngbin0 -> 5003 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/settings/settings_display.pngbin0 -> 6076 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/settings/settings_hover.pngbin0 -> 6269 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/templates/templates_display.pngbin0 -> 5651 bytes
-rw-r--r--bitbake/lib/bb/ui/icons/templates/templates_hover.pngbin0 -> 5791 bytes
-rw-r--r--bitbake/lib/bb/ui/uihelper.py42
45 files changed, 5078 insertions, 2811 deletions
diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py
index 1e041a2a3e..e85d7c4f3e 100644
--- a/bitbake/lib/bb/build.py
+++ b/bitbake/lib/bb/build.py
@@ -153,8 +153,6 @@ def exec_func(func, d, dirs = None):
153 bb.utils.mkdirhier(adir) 153 bb.utils.mkdirhier(adir)
154 154
155 ispython = flags.get('python') 155 ispython = flags.get('python')
156 if flags.get('fakeroot') and not flags.get('task'):
157 bb.fatal("Function %s specifies fakeroot but isn't a task?!" % func)
158 156
159 lockflag = flags.get('lockfiles') 157 lockflag = flags.get('lockfiles')
160 if lockflag: 158 if lockflag:
@@ -223,9 +221,9 @@ def exec_func_shell(function, d, runfile, cwd=None):
223 221
224 with open(runfile, 'w') as script: 222 with open(runfile, 'w') as script:
225 script.write('#!/bin/sh -e\n') 223 script.write('#!/bin/sh -e\n')
226 if bb.msg.loggerDefaultVerbose:
227 script.write("set -x\n")
228 data.emit_func(function, script, d) 224 data.emit_func(function, script, d)
225
226 script.write("set -x\n")
229 if cwd: 227 if cwd:
230 script.write("cd %s\n" % cwd) 228 script.write("cd %s\n" % cwd)
231 script.write("%s\n" % function) 229 script.write("%s\n" % function)
@@ -233,6 +231,10 @@ def exec_func_shell(function, d, runfile, cwd=None):
233 os.chmod(runfile, 0775) 231 os.chmod(runfile, 0775)
234 232
235 cmd = runfile 233 cmd = runfile
234 if d.getVarFlag(function, 'fakeroot'):
235 fakerootcmd = d.getVar('FAKEROOT', True)
236 if fakerootcmd:
237 cmd = [fakerootcmd, runfile]
236 238
237 if bb.msg.loggerDefaultVerbose: 239 if bb.msg.loggerDefaultVerbose:
238 logfile = LogTee(logger, sys.stdout) 240 logfile = LogTee(logger, sys.stdout)
diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py
index 3ad0c0aa92..1959007bec 100644
--- a/bitbake/lib/bb/runqueue.py
+++ b/bitbake/lib/bb/runqueue.py
@@ -1173,8 +1173,7 @@ class RunQueueExecute:
1173 logger.critical(str(exc)) 1173 logger.critical(str(exc))
1174 os._exit(1) 1174 os._exit(1)
1175 try: 1175 try:
1176 if not self.cooker.configuration.dry_run: 1176 ret = bb.build.exec_task(fn, taskname, the_data)
1177 ret = bb.build.exec_task(fn, taskname, the_data)
1178 os._exit(ret) 1177 os._exit(ret)
1179 except: 1178 except:
1180 os._exit(1) 1179 os._exit(1)
@@ -1357,6 +1356,12 @@ class RunQueueExecuteTasks(RunQueueExecute):
1357 self.rqdata.get_user_idstring(task)) 1356 self.rqdata.get_user_idstring(task))
1358 self.task_skip(task) 1357 self.task_skip(task)
1359 return True 1358 return True
1359 elif self.cooker.configuration.dry_run:
1360 self.runq_running[task] = 1
1361 self.runq_buildable[task] = 1
1362 self.stats.taskActive()
1363 self.task_complete(task)
1364 return True
1360 1365
1361 taskdep = self.rqdata.dataCache.task_deps[fn] 1366 taskdep = self.rqdata.dataCache.task_deps[fn]
1362 if 'noexec' in taskdep and taskname in taskdep['noexec']: 1367 if 'noexec' in taskdep and taskname in taskdep['noexec']:
@@ -1648,9 +1653,6 @@ class RunQueueExecuteScenequeue(RunQueueExecute):
1648 self.task_skip(task) 1653 self.task_skip(task)
1649 return True 1654 return True
1650 1655
1651 logger.info("Running setscene task %d of %d (%s:%s)" % (self.stats.completed + self.stats.active + self.stats.failed + 1,
1652 self.stats.total, fn, taskname))
1653
1654 startevent = sceneQueueTaskStarted(task, self.stats, self.rq) 1656 startevent = sceneQueueTaskStarted(task, self.stats, self.rq)
1655 bb.event.fire(startevent, self.cfgData) 1657 bb.event.fire(startevent, self.cfgData)
1656 1658
diff --git a/bitbake/lib/bb/ui/crumbs/builddetailspage.py b/bitbake/lib/bb/ui/crumbs/builddetailspage.py
new file mode 100755
index 0000000000..941f1e30b3
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/builddetailspage.py
@@ -0,0 +1,110 @@
1#!/usr/bin/env python
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2012 Intel Corporation
6#
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8# Authored by Shane Wang <shane.wang@intel.com>
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23import gtk
24from bb.ui.crumbs.progressbar import HobProgressBar
25from bb.ui.crumbs.hobwidget import hic
26from bb.ui.crumbs.runningbuild import RunningBuildTreeView
27from bb.ui.crumbs.hobpages import HobPage
28
29#
30# BuildDetailsPage
31#
32
33class BuildDetailsPage (HobPage):
34
35 def __init__(self, builder):
36 super(BuildDetailsPage, self).__init__(builder, "Building ...")
37
38 # create visual elements
39 self.create_visual_elements()
40
41 def create_visual_elements(self):
42 # create visual elements
43 self.vbox = gtk.VBox(False, 15)
44
45 self.progress_box = gtk.HBox(False, 5)
46 self.progress_bar = HobProgressBar()
47 self.progress_box.pack_start(self.progress_bar, expand=True, fill=True)
48 self.stop_button = gtk.LinkButton("Stop the build process", "Stop")
49 self.stop_button.connect("clicked", self.stop_button_clicked_cb)
50 self.progress_box.pack_end(self.stop_button, expand=False, fill=False)
51
52 self.build_tv = RunningBuildTreeView(readonly=True)
53 self.build_tv.set_model(self.builder.handler.build.model)
54 self.scrolled_view = gtk.ScrolledWindow ()
55 self.scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
56 self.scrolled_view.add(self.build_tv)
57
58 self.button_box = gtk.HBox(False, 5)
59 self.back_button = gtk.LinkButton("Go back to Image Configuration screen", "<< Back to image configuration")
60 self.back_button.connect("clicked", self.back_button_clicked_cb)
61 self.button_box.pack_start(self.back_button, expand=False, fill=False)
62
63 def _remove_all_widget(self):
64 children = self.vbox.get_children() or []
65 for child in children:
66 self.vbox.remove(child)
67 children = self.box_group_area.get_children() or []
68 for child in children:
69 self.box_group_area.remove(child)
70 children = self.get_children() or []
71 for child in children:
72 self.remove(child)
73
74 def show_page(self, step):
75 self._remove_all_widget()
76 if step == self.builder.PACKAGE_GENERATING or step == self.builder.FAST_IMAGE_GENERATING:
77 self.title = "Building packages ..."
78 else:
79 self.title = "Building image ..."
80 self.build_details_top = self.add_onto_top_bar(None)
81 self.pack_start(self.build_details_top, expand=False, fill=False)
82 self.pack_start(self.group_align, expand=True, fill=True)
83
84 self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
85
86 self.progress_bar.reset()
87 self.vbox.pack_start(self.progress_box, expand=False, fill=False)
88
89 self.vbox.pack_start(self.scrolled_view, expand=True, fill=True)
90
91 self.box_group_area.pack_end(self.button_box, expand=False, fill=False)
92 self.show_all()
93 self.back_button.hide()
94
95 def update_progress_bar(self, title, fraction, status=True):
96 self.progress_bar.update(fraction)
97 self.progress_bar.set_title(title)
98 self.progress_bar.set_rcstyle(status)
99
100 def back_button_clicked_cb(self, button):
101 self.builder.show_configuration()
102
103 def show_back_button(self):
104 self.back_button.show()
105
106 def stop_button_clicked_cb(self, button):
107 self.builder.stop_build()
108
109 def hide_stop_button(self):
110 self.stop_button.hide()
diff --git a/bitbake/lib/bb/ui/crumbs/builder.py b/bitbake/lib/bb/ui/crumbs/builder.py
new file mode 100755
index 0000000000..007167337f
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/builder.py
@@ -0,0 +1,873 @@
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
24import gtk
25import copy
26import os
27import subprocess
28import shlex
29from bb.ui.crumbs.template import TemplateMgr
30from bb.ui.crumbs.imageconfigurationpage import ImageConfigurationPage
31from bb.ui.crumbs.recipeselectionpage import RecipeSelectionPage
32from bb.ui.crumbs.packageselectionpage import PackageSelectionPage
33from bb.ui.crumbs.builddetailspage import BuildDetailsPage
34from bb.ui.crumbs.imagedetailspage import ImageDetailsPage
35from bb.ui.crumbs.hobwidget import hwc
36from bb.ui.crumbs.hig import CrumbsDialog, BinbDialog, \
37 AdvancedSettingDialog, LayerSelectionDialog, \
38 DeployImageDialog, ImageSelectionDialog
39
40class Configuration:
41 '''Represents the data structure of configuration.'''
42
43 def __init__(self, params):
44 # Settings
45 self.curr_mach = ""
46 self.curr_distro = params["distro"]
47 self.dldir = params["dldir"]
48 self.sstatedir = params["sstatedir"]
49 self.sstatemirror = params["sstatemirror"]
50 self.pmake = params["pmake"]
51 self.bbthread = params["bbthread"]
52 self.curr_package_format = " ".join(params["pclass"].split("package_")).strip()
53 self.image_rootfs_size = params["image_rootfs_size"]
54 self.image_extra_size = params["image_extra_size"]
55 self.image_overhead_factor = params['image_overhead_factor']
56 self.incompat_license = params["incompat_license"]
57 self.curr_sdk_machine = params["sdk_machine"]
58 self.extra_setting = {}
59 self.toolchain_build = False
60 self.image_fstypes = params["image_fstypes"].split()
61 # bblayers.conf
62 self.layers = params["layer"].split()
63 # image/recipes/packages
64 self.selected_image = None
65 self.selected_recipes = []
66 self.selected_packages = []
67
68 def load(self, template):
69 self.curr_mach = template.getVar("MACHINE")
70 self.curr_package_format = " ".join(template.getVar("PACKAGE_CLASSES").split("package_")).strip()
71 self.curr_distro = template.getVar("DISTRO")
72 self.dldir = template.getVar("DL_DIR")
73 self.sstatedir = template.getVar("SSTATE_DIR")
74 self.sstatemirror = template.getVar("SSTATE_MIRROR")
75 self.pmake = int(template.getVar("PARALLEL_MAKE").split()[1])
76 self.bbthread = int(template.getVar("BB_NUMBER_THREAD"))
77 self.image_rootfs_size = int(template.getVar("IMAGE_ROOTFS_SIZE"))
78 self.image_extra_size = int(template.getVar("IMAGE_EXTRA_SPACE"))
79 # image_overhead_factor is read-only.
80 self.incompat_license = template.getVar("INCOMPATIBLE_LICENSE")
81 self.curr_sdk_machine = template.getVar("SDKMACHINE")
82 self.extra_setting = eval(template.getVar("EXTRA_SETTING"))
83 self.toolchain_build = eval(template.getVar("TOOLCHAIN_BUILD"))
84 self.image_fstypes = template.getVar("IMAGE_FSTYPES").split()
85 # bblayers.conf
86 self.layers = template.getVar("BBLAYERS").split()
87 # image/recipes/packages
88 self.selected_image = template.getVar("__SELECTED_IMAGE__")
89 self.selected_recipes = template.getVar("DEPENDS").split()
90 self.selected_packages = template.getVar("IMAGE_INSTALL").split()
91
92 def save(self, template, filename):
93 # bblayers.conf
94 template.setVar("BBLAYERS", " ".join(self.layers))
95 # local.conf
96 template.setVar("MACHINE", self.curr_mach)
97 template.setVar("DISTRO", self.curr_distro)
98 template.setVar("DL_DIR", self.dldir)
99 template.setVar("SSTATE_DIR", self.sstatedir)
100 template.setVar("SSTATE_MIRROR", self.sstatemirror)
101 template.setVar("PARALLEL_MAKE", "-j %s" % self.pmake)
102 template.setVar("BB_NUMBER_THREAD", self.bbthread)
103 template.setVar("PACKAGE_CLASSES", " ".join(["package_" + i for i in self.curr_package_format.split()]))
104 template.setVar("IMAGE_ROOTFS_SIZE", self.image_rootfs_size)
105 template.setVar("IMAGE_EXTRA_SPACE", self.image_extra_size)
106 template.setVar("INCOMPATIBLE_LICENSE", self.incompat_license)
107 template.setVar("SDKMACHINE", self.curr_sdk_machine)
108 template.setVar("EXTRA_SETTING", self.extra_setting)
109 template.setVar("TOOLCHAIN_BUILD", self.toolchain_build)
110 template.setVar("IMAGE_FSTYPES", " ".join(self.image_fstypes).lstrip(" "))
111 # image/recipes/packages
112 self.selected_image = filename
113 template.setVar("__SELECTED_IMAGE__", self.selected_image)
114 template.setVar("DEPENDS", self.selected_recipes)
115 template.setVar("IMAGE_INSTALL", self.selected_packages)
116
117class Parameters:
118 '''Represents other variables like available machines, etc.'''
119
120 def __init__(self, params):
121 # Variables
122 self.all_machines = []
123 self.all_package_formats = []
124 self.all_distros = []
125 self.all_sdk_machines = []
126 self.max_threads = params["max_threads"]
127 self.all_layers = []
128 self.core_base = params["core_base"]
129 self.image_names = []
130 self.image_addr = params["image_addr"]
131 self.image_types = params["image_types"].split()
132
133class Builder(gtk.Window):
134
135 (MACHINE_SELECTION,
136 LAYER_CHANGED,
137 RCPPKGINFO_POPULATING,
138 RCPPKGINFO_POPULATED,
139 RECIPE_SELECTION,
140 PACKAGE_GENERATING,
141 PACKAGE_GENERATED,
142 PACKAGE_SELECTION,
143 FAST_IMAGE_GENERATING,
144 IMAGE_GENERATING,
145 IMAGE_GENERATED,
146 MY_IMAGE_OPENED,
147 BACK,
148 END_NOOP) = range(14)
149
150 (IMAGE_CONFIGURATION,
151 RECIPE_DETAILS,
152 BUILD_DETAILS,
153 PACKAGE_DETAILS,
154 IMAGE_DETAILS,
155 END_TAB) = range(6)
156
157 __step2page__ = {
158 MACHINE_SELECTION : IMAGE_CONFIGURATION,
159 LAYER_CHANGED : IMAGE_CONFIGURATION,
160 RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION,
161 RCPPKGINFO_POPULATED : IMAGE_CONFIGURATION,
162 RECIPE_SELECTION : RECIPE_DETAILS,
163 PACKAGE_GENERATING : BUILD_DETAILS,
164 PACKAGE_GENERATED : PACKAGE_DETAILS,
165 PACKAGE_SELECTION : PACKAGE_DETAILS,
166 FAST_IMAGE_GENERATING : BUILD_DETAILS,
167 IMAGE_GENERATING : BUILD_DETAILS,
168 IMAGE_GENERATED : IMAGE_DETAILS,
169 MY_IMAGE_OPENED : IMAGE_DETAILS,
170 END_NOOP : None,
171 }
172
173 def __init__(self, hobHandler, recipe_model, package_model):
174 super(Builder, self).__init__()
175
176 # handler
177 self.handler = hobHandler
178
179 self.template = None
180
181 # settings
182 params = self.handler.get_parameters()
183 self.configuration = Configuration(params)
184 self.parameters = Parameters(params)
185
186 # build step
187 self.current_step = None
188 self.previous_step = None
189
190 self.stopping = False
191 self.build_succeeded = True
192
193 # recipe model and package model
194 self.recipe_model = recipe_model
195 self.package_model = package_model
196
197 # create visual elements
198 self.create_visual_elements()
199
200 # connect the signals to functions
201 #self.connect("configure-event", self.resize_window_cb)
202 self.connect("delete-event", self.destroy_window_cb)
203 self.recipe_model.connect ("recipe-selection-changed", self.recipelist_changed_cb)
204 self.package_model.connect("package-selection-changed", self.packagelist_changed_cb)
205 self.recipe_model.connect ("recipelist-populated", self.recipelist_populated_cb)
206 self.package_model.connect("packagelist-populated", self.packagelist_populated_cb)
207 self.handler.connect("config-updated", self.handler_config_updated_cb)
208 self.handler.connect("package-formats-updated", self.handler_package_formats_updated_cb)
209 self.handler.connect("layers-updated", self.handler_layers_updated_cb)
210 self.handler.connect("parsing-started", self.handler_parsing_started_cb)
211 self.handler.connect("parsing", self.handler_parsing_cb)
212 self.handler.connect("parsing-completed", self.handler_parsing_completed_cb)
213 self.handler.build.connect("build-started", self.handler_build_started_cb)
214 self.handler.build.connect("build-succeeded", self.handler_build_succeeded_cb)
215 self.handler.build.connect("build-failed", self.handler_build_failed_cb)
216 self.handler.build.connect("task-started", self.handler_task_started_cb)
217 self.handler.connect("generating-data", self.handler_generating_data_cb)
218 self.handler.connect("data-generated", self.handler_data_generated_cb)
219 self.handler.connect("command-succeeded", self.handler_command_succeeded_cb)
220 self.handler.connect("command-failed", self.handler_command_failed_cb)
221
222 self.switch_page(self.MACHINE_SELECTION)
223
224 def create_visual_elements(self):
225 self.set_title("HOB -- Image Creator")
226 self.set_icon_name("applications-development")
227 self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
228 self.set_resizable(True)
229 window_width = self.get_screen().get_width()
230 window_height = self.get_screen().get_height()
231 if window_width >= hwc.MAIN_WIN_WIDTH:
232 window_width = hwc.MAIN_WIN_WIDTH
233 window_height = hwc.MAIN_WIN_HEIGHT
234 else:
235 lbl = "<b>Screen dimension mismatched</b>\nfor better usability and visual effects,"
236 lbl = lbl + " the screen dimension should be 1024x768 or above."
237 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
238 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
239 dialog.run()
240 dialog.destroy()
241 self.set_size_request(window_width, window_height)
242
243 self.vbox = gtk.VBox(False, 0)
244 self.vbox.set_border_width(0)
245 self.add(self.vbox)
246
247 # create pages
248 self.image_configuration_page = ImageConfigurationPage(self)
249 self.recipe_details_page = RecipeSelectionPage(self)
250 self.build_details_page = BuildDetailsPage(self)
251 self.package_details_page = PackageSelectionPage(self)
252 self.image_details_page = ImageDetailsPage(self)
253
254 self.nb = gtk.Notebook()
255 self.nb.set_show_tabs(False)
256 self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION)
257 self.nb.insert_page(self.recipe_details_page, None, self.RECIPE_DETAILS)
258 self.nb.insert_page(self.build_details_page, None, self.BUILD_DETAILS)
259 self.nb.insert_page(self.package_details_page, None, self.PACKAGE_DETAILS)
260 self.nb.insert_page(self.image_details_page, None, self.IMAGE_DETAILS)
261 self.vbox.pack_start(self.nb, expand=True, fill=True)
262
263 self.show_all()
264 self.nb.set_current_page(0)
265
266 def get_split_model(self):
267 return self.handler.split_model
268
269 def load_template(self, path):
270 self.template = TemplateMgr()
271 self.template.load(path)
272 self.configuration.load(self.template)
273
274 if self.get_split_model():
275 if not set(self.configuration.layers) <= set(self.parameters.all_layers):
276 return False
277 else:
278 for layer in self.configuration.layers:
279 if not os.path.exists(layer+'/conf/layer.conf'):
280 return False
281
282 self.switch_page(self.LAYER_CHANGED)
283
284 self.template.destroy()
285 self.template = None
286
287 def save_template(self, path):
288 if path.rfind("/") == -1:
289 filename = "default"
290 path = "."
291 else:
292 filename = path[path.rfind("/") + 1:len(path)]
293 path = path[0:path.rfind("/")]
294
295 self.template = TemplateMgr()
296 self.template.open(filename, path)
297 self.configuration.save(self.template, filename)
298
299 self.template.save()
300 self.template.destroy()
301 self.template = None
302
303 def switch_page(self, next_step):
304 # Main Workflow (Business Logic)
305 self.nb.set_current_page(self.__step2page__[next_step])
306
307 if next_step == self.MACHINE_SELECTION: # init step
308 self.image_configuration_page.show_machine()
309
310 elif next_step == self.LAYER_CHANGED:
311 # after layers is changd by users
312 self.image_configuration_page.show_machine()
313 self.handler.refresh_layers(self.configuration.layers)
314
315 elif next_step == self.RCPPKGINFO_POPULATING:
316 # MACHINE CHANGED action or SETTINGS CHANGED
317 # show the progress bar
318 self.image_configuration_page.show_info_populating()
319 self.generate_recipes()
320
321 elif next_step == self.RCPPKGINFO_POPULATED:
322 self.image_configuration_page.show_info_populated()
323
324 elif next_step == self.RECIPE_SELECTION:
325 pass
326
327 elif next_step == self.PACKAGE_SELECTION:
328 pass
329
330 elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING:
331 # both PACKAGE_GENEATING and FAST_IMAGE_GENERATING share the same page
332 self.build_details_page.show_page(next_step)
333 self.generate_packages()
334
335 elif next_step == self.PACKAGE_GENERATED:
336 pass
337
338 elif next_step == self.IMAGE_GENERATING:
339 # after packages are generated, selected_packages need to
340 # be updated in package_model per selected_image in recipe_model
341 self.build_details_page.show_page(next_step)
342 self.generate_image()
343
344 elif next_step == self.IMAGE_GENERATED:
345 self.image_details_page.show_page(next_step)
346
347 elif next_step == self.MY_IMAGE_OPENED:
348 self.image_details_page.show_page(next_step)
349
350 self.previous_step = self.current_step
351 self.current_step = next_step
352
353 def set_user_config(self):
354 self.handler.init_cooker()
355 # set bb layers
356 self.handler.set_bblayers(self.configuration.layers)
357 # set local configuration
358 self.handler.set_machine(self.configuration.curr_mach)
359 self.handler.set_package_format(self.configuration.curr_package_format)
360 self.handler.set_distro(self.configuration.curr_distro)
361 self.handler.set_dl_dir(self.configuration.dldir)
362 self.handler.set_sstate_dir(self.configuration.sstatedir)
363 self.handler.set_sstate_mirror(self.configuration.sstatemirror)
364 self.handler.set_pmake(self.configuration.pmake)
365 self.handler.set_bbthreads(self.configuration.bbthread)
366 self.handler.set_rootfs_size(self.configuration.image_rootfs_size)
367 self.handler.set_extra_size(self.configuration.image_extra_size)
368 self.handler.set_incompatible_license(self.configuration.incompat_license)
369 self.handler.set_sdk_machine(self.configuration.curr_sdk_machine)
370 self.handler.set_image_fstypes(self.configuration.image_fstypes)
371 self.handler.set_extra_config(self.configuration.extra_setting)
372 self.handler.set_extra_inherit("packageinfo")
373
374 def reset_recipe_model(self):
375 self.recipe_model.reset()
376
377 def reset_package_model(self):
378 self.package_model.reset()
379
380 def update_recipe_model(self, selected_image, selected_recipes):
381 self.recipe_model.set_selected_image(selected_image)
382 self.recipe_model.set_selected_recipes(selected_recipes)
383
384 def update_package_model(self, selected_packages):
385 left = self.package_model.set_selected_packages(selected_packages)
386 self.configuration.selected_packages += left
387
388 def generate_packages(self):
389 # Build packages
390 _, all_recipes = self.recipe_model.get_selected_recipes()
391 self.set_user_config()
392 self.handler.reset_build()
393 self.handler.generate_packages(all_recipes)
394
395 def generate_recipes(self):
396 # Parse recipes
397 self.set_user_config()
398 self.handler.generate_recipes()
399
400 def generate_image(self):
401 # Build image
402 self.set_user_config()
403 all_packages = self.package_model.get_selected_packages()
404 self.handler.reset_build()
405 self.handler.generate_image(all_packages, self.configuration.toolchain_build)
406
407
408 # Callback Functions
409 def handler_config_updated_cb(self, handler, which, values):
410 if which == "distro":
411 self.parameters.all_distros = values
412 elif which == "machine":
413 self.parameters.all_machines = values
414 self.image_configuration_page.update_machine_combo()
415 elif which == "machine-sdk":
416 self.parameters.all_sdk_machines = values
417
418 def handler_package_formats_updated_cb(self, handler, formats):
419 self.parameters.all_package_formats = formats
420
421 def handler_layers_updated_cb(self, handler, layers):
422 self.parameters.all_layers = layers
423
424 def handler_command_succeeded_cb(self, handler, initcmd):
425 if initcmd == self.handler.LAYERS_REFRESH:
426 self.image_configuration_page.switch_machine_combo()
427 elif initcmd in [self.handler.GENERATE_RECIPES,
428 self.handler.GENERATE_PACKAGES,
429 self.handler.GENERATE_IMAGE]:
430 self.handler.request_package_info_async()
431 elif initcmd == self.handler.POPULATE_PACKAGEINFO:
432 if self.current_step == self.FAST_IMAGE_GENERATING:
433 self.switch_page(self.IMAGE_GENERATING)
434 elif self.current_step == self.RCPPKGINFO_POPULATING:
435 self.switch_page(self.RCPPKGINFO_POPULATED)
436 elif self.current_step == self.PACKAGE_GENERATING:
437 self.switch_page(self.PACKAGE_GENERATED)
438 elif self.current_step == self.IMAGE_GENERATING:
439 self.switch_page(self.IMAGE_GENERATED)
440
441 def handler_command_failed_cb(self, handler, msg):
442 lbl = "<b>Error</b>\n"
443 lbl = lbl + "%s\n\n" % msg
444 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
445 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
446 response = dialog.run()
447 dialog.destroy()
448 self.handler.clear_busy()
449 self.configuration.curr_mach = None
450 self.image_configuration_page.switch_machine_combo()
451 self.switch_page(self.MACHINE_SELECTION)
452
453 def window_sensitive(self, sensitive):
454 self.set_sensitive(sensitive)
455 if sensitive:
456 self.get_root_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
457 else:
458 self.get_root_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
459
460
461 def handler_generating_data_cb(self, handler):
462 self.window_sensitive(False)
463
464 def handler_data_generated_cb(self, handler):
465 self.window_sensitive(True)
466
467 def recipelist_populated_cb(self, recipe_model):
468 selected_image = self.configuration.selected_image
469 selected_recipes = self.configuration.selected_recipes[:]
470 selected_packages = self.configuration.selected_packages[:]
471
472 self.recipe_model.image_list_append(selected_image,
473 " ".join(selected_recipes),
474 " ".join(selected_packages))
475
476 self.image_configuration_page.update_image_combo(self.recipe_model, selected_image)
477
478 self.update_recipe_model(selected_image, selected_recipes)
479
480 def packagelist_populated_cb(self, package_model):
481 selected_packages = self.configuration.selected_packages[:]
482 self.update_package_model(selected_packages)
483
484 def recipelist_changed_cb(self, recipe_model):
485 self.recipe_details_page.refresh_selection()
486
487 def packagelist_changed_cb(self, package_model):
488 self.package_details_page.refresh_selection()
489
490 def handler_parsing_started_cb(self, handler, message):
491 if self.current_step != self.RCPPKGINFO_POPULATING:
492 return
493
494 fraction = 0
495 if message["eventname"] == "TreeDataPreparationStarted":
496 fraction = 0.6 + fraction
497 self.image_configuration_page.update_progress_bar(message["title"], fraction)
498
499 def handler_parsing_cb(self, handler, message):
500 if self.current_step != self.RCPPKGINFO_POPULATING:
501 return
502
503 fraction = message["current"] * 1.0/message["total"]
504 if message["eventname"] == "TreeDataPreparationProgress":
505 fraction = 0.6 + 0.4 * fraction
506 else:
507 fraction = 0.6 * fraction
508 self.image_configuration_page.update_progress_bar(message["title"], fraction)
509
510 def handler_parsing_completed_cb(self, handler, message):
511 if self.current_step != self.RCPPKGINFO_POPULATING:
512 return
513
514 if message["eventname"] == "TreeDataPreparationCompleted":
515 fraction = 1.0
516 else:
517 fraction = 0.6
518 self.image_configuration_page.update_progress_bar(message["title"], fraction)
519
520 def handler_build_started_cb(self, running_build):
521 if self.current_step == self.FAST_IMAGE_GENERATING:
522 fraction = 0
523 elif self.current_step == self.IMAGE_GENERATING:
524 if self.previous_step == self.FAST_IMAGE_GENERATING:
525 fraction = 0.9
526 else:
527 fraction = 0
528 elif self.current_step == self.PACKAGE_GENERATING:
529 fraction = 0
530 self.build_details_page.update_progress_bar("Build Started: ", fraction)
531
532 def handler_build_succeeded_cb(self, running_build):
533 self.build_succeeded = True
534 if self.current_step == self.FAST_IMAGE_GENERATING:
535 fraction = 0.9
536 elif self.current_step == self.IMAGE_GENERATING:
537 fraction = 1.0
538 self.parameters.image_names = []
539 linkname = 'hob-image-' + self.configuration.curr_mach
540 for image_type in self.parameters.image_types:
541 linkpath = self.parameters.image_addr + '/' + linkname + '.' + image_type
542 if os.path.exists(linkpath):
543 self.parameters.image_names.append(os.readlink(linkpath))
544 elif self.current_step == self.PACKAGE_GENERATING:
545 fraction = 1.0
546 self.build_details_page.update_progress_bar("Build Completed: ", fraction)
547
548 def handler_build_failed_cb(self, running_build):
549 self.build_succeeded = False
550 if self.current_step == self.FAST_IMAGE_GENERATING:
551 fraction = 0.9
552 elif self.current_step == self.IMAGE_GENERATING:
553 fraction = 1.0
554 elif self.current_step == self.PACKAGE_GENERATING:
555 fraction = 1.0
556 self.build_details_page.update_progress_bar("Build Failed: ", fraction, False)
557 self.build_details_page.show_back_button()
558 self.build_details_page.hide_stop_button()
559 self.handler.build_failed_async()
560 self.stopping = False
561
562 def handler_task_started_cb(self, running_build, message):
563 fraction = message["current"] * 1.0/message["total"]
564 title = "Build packages"
565 if self.current_step == self.FAST_IMAGE_GENERATING:
566 if message["eventname"] == "sceneQueueTaskStarted":
567 fraction = 0.27 * fraction
568 elif message["eventname"] == "runQueueTaskStarted":
569 fraction = 0.27 + 0.63 * fraction
570 elif self.current_step == self.IMAGE_GENERATING:
571 title = "Build image"
572 if self.previous_step == self.FAST_IMAGE_GENERATING:
573 if message["eventname"] == "sceneQueueTaskStarted":
574 fraction = 0.27 + 0.63 + 0.03 * fraction
575 elif message["eventname"] == "runQueueTaskStarted":
576 fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction
577 else:
578 if message["eventname"] == "sceneQueueTaskStarted":
579 fraction = 0.2 * fraction
580 elif message["eventname"] == "runQueueTaskStarted":
581 fraction = 0.2 + 0.8 * fraction
582 elif self.current_step == self.PACKAGE_GENERATING:
583 if message["eventname"] == "sceneQueueTaskStarted":
584 fraction = 0.2 * fraction
585 elif message["eventname"] == "runQueueTaskStarted":
586 fraction = 0.2 + 0.8 * fraction
587 self.build_details_page.update_progress_bar(title + ": ", fraction)
588
589 def destroy_window_cb(self, widget, event):
590 lbl = "<b>Do you really want to exit the Hob image creator?</b>"
591 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
592 dialog.add_button(gtk.STOCK_YES, gtk.RESPONSE_YES)
593 dialog.add_button(gtk.STOCK_NO, gtk.RESPONSE_NO)
594 dialog.set_default_response(gtk.RESPONSE_NO)
595 response = dialog.run()
596 dialog.destroy()
597 if response == gtk.RESPONSE_YES:
598 gtk.main_quit()
599 return False
600 else:
601 return True
602
603 def build_packages(self):
604 _, all_recipes = self.recipe_model.get_selected_recipes()
605 if not all_recipes:
606 lbl = "<b>No selections made</b>\nYou have not made any selections"
607 lbl = lbl + " so there isn't anything to bake at this time."
608 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
609 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
610 dialog.run()
611 dialog.destroy()
612 return
613 self.switch_page(self.PACKAGE_GENERATING)
614
615 def build_image(self):
616 selected_packages = self.package_model.get_selected_packages()
617 if not selected_packages:
618 lbl = "<b>No selections made</b>\nYou have not made any selections"
619 lbl = lbl + " so there isn't anything to bake at this time."
620 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
621 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
622 dialog.run()
623 dialog.destroy()
624 return
625 self.switch_page(self.IMAGE_GENERATING)
626
627 def just_bake(self):
628 selected_image = self.recipe_model.get_selected_image()
629 selected_packages = self.package_model.get_selected_packages() or []
630
631 # If no base image and no selected packages don't build anything
632 if not (selected_packages or selected_image != self.recipe_model.__dummy_image__):
633 lbl = "<b>No selections made</b>\nYou have not made any selections"
634 lbl = lbl + " so there isn't anything to bake at this time."
635 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
636 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
637 dialog.run()
638 dialog.destroy()
639 return
640
641 self.switch_page(self.FAST_IMAGE_GENERATING)
642
643 def show_binb_dialog(self, binb):
644 binb_dialog = BinbDialog("Brought in by:", binb, self)
645 binb_dialog.run()
646 binb_dialog.destroy()
647
648 def show_layer_selection_dialog(self):
649 dialog = LayerSelectionDialog(title = "Layer Selection",
650 layers = copy.deepcopy(self.configuration.layers),
651 all_layers = self.parameters.all_layers,
652 split_model = self.get_split_model(),
653 parent = self,
654 flags = gtk.DIALOG_MODAL
655 | gtk.DIALOG_DESTROY_WITH_PARENT
656 | gtk.DIALOG_NO_SEPARATOR,
657 buttons = (gtk.STOCK_OK, gtk.RESPONSE_YES,
658 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
659 response = dialog.run()
660 if response == gtk.RESPONSE_YES:
661 self.configuration.layers = dialog.layers
662 # DO refresh layers
663 if dialog.layers_changed:
664 self.switch_page(self.LAYER_CHANGED)
665 dialog.destroy()
666
667 def show_load_template_dialog(self):
668 dialog = gtk.FileChooserDialog("Load Template Files", self,
669 gtk.FILE_CHOOSER_ACTION_SAVE,
670 (gtk.STOCK_OPEN, gtk.RESPONSE_YES,
671 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
672 filter = gtk.FileFilter()
673 filter.set_name("HOB Files")
674 filter.add_pattern("*.hob")
675 dialog.add_filter(filter)
676
677 response = dialog.run()
678 if response == gtk.RESPONSE_YES:
679 path = dialog.get_filename()
680 self.load_template(path)
681 dialog.destroy()
682
683 def show_save_template_dialog(self):
684 dialog = gtk.FileChooserDialog("Save Template Files", self,
685 gtk.FILE_CHOOSER_ACTION_SAVE,
686 (gtk.STOCK_SAVE, gtk.RESPONSE_YES,
687 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
688 dialog.set_current_name("hob")
689 response = dialog.run()
690 if response == gtk.RESPONSE_YES:
691 path = dialog.get_filename()
692 self.save_template(path)
693 dialog.destroy()
694
695 def show_load_my_images_dialog(self):
696 dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types,
697 "Open My Images", self,
698 gtk.FILE_CHOOSER_ACTION_SAVE,
699 (gtk.STOCK_OPEN, gtk.RESPONSE_YES,
700 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
701
702 response = dialog.run()
703 if response == gtk.RESPONSE_YES:
704 if not dialog.image_names:
705 lbl = "<b>No selections made</b>\nYou have not made any selections"
706 crumbs_dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
707 crumbs_dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
708 crumbs_dialog.run()
709 crumbs_dialog.destroy()
710 dialog.destroy()
711 return
712
713 self.parameters.image_addr = dialog.image_folder
714 self.parameters.image_names = dialog.image_names[:]
715 self.switch_page(self.MY_IMAGE_OPENED)
716
717 dialog.destroy()
718
719 def show_adv_settings_dialog(self):
720 dialog = AdvancedSettingDialog(title = "Settings",
721 configuration = copy.deepcopy(self.configuration),
722 all_image_types = self.parameters.image_types,
723 all_package_formats = self.parameters.all_package_formats,
724 all_distros = self.parameters.all_distros,
725 all_sdk_machines = self.parameters.all_sdk_machines,
726 max_threads = self.parameters.max_threads,
727 split_model = self.get_split_model(),
728 parent = self,
729 flags = gtk.DIALOG_MODAL
730 | gtk.DIALOG_DESTROY_WITH_PARENT
731 | gtk.DIALOG_NO_SEPARATOR,
732 buttons = ("Save", gtk.RESPONSE_YES,
733 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
734 response = dialog.run()
735 if response == gtk.RESPONSE_YES:
736 self.configuration = dialog.configuration
737 # DO reparse recipes
738 if dialog.settings_changed:
739 if self.configuration.curr_mach == "":
740 self.switch_page(self.MACHINE_SELECTION)
741 else:
742 self.switch_page(self.RCPPKGINFO_POPULATING)
743 dialog.destroy()
744
745 def deploy_image(self, image_name):
746 if not image_name:
747 lbl = "<b>Please select an image to deploy.</b>"
748 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
749 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
750 dialog.run()
751 dialog.destroy()
752 return
753
754 image_path = os.path.join(self.parameters.image_addr, image_name)
755 dialog = DeployImageDialog(title = "Usb Image Maker",
756 image_path = image_path,
757 parent = self,
758 flags = gtk.DIALOG_MODAL
759 | gtk.DIALOG_DESTROY_WITH_PARENT
760 | gtk.DIALOG_NO_SEPARATOR,
761 buttons = ("Close", gtk.RESPONSE_NO,
762 "Make usb image", gtk.RESPONSE_YES))
763 response = dialog.run()
764 dialog.destroy()
765
766 def runqemu_image(self, image_name):
767 if not image_name:
768 lbl = "<b>Please select an image to launch in QEMU.</b>"
769 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
770 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
771 dialog.run()
772 dialog.destroy()
773 return
774
775 dialog = gtk.FileChooserDialog("Load Kernel Files", self,
776 gtk.FILE_CHOOSER_ACTION_SAVE,
777 (gtk.STOCK_OPEN, gtk.RESPONSE_YES,
778 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
779 filter = gtk.FileFilter()
780 filter.set_name("Kernel Files")
781 filter.add_pattern("*.bin")
782 dialog.add_filter(filter)
783
784 dialog.set_current_folder(self.parameters.image_addr)
785
786 response = dialog.run()
787 if response == gtk.RESPONSE_YES:
788 kernel_path = dialog.get_filename()
789 image_path = os.path.join(self.parameters.image_addr, image_name)
790 dialog.destroy()
791
792 if response == gtk.RESPONSE_YES:
793 source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env")
794 tmp_path = os.path.join(os.getcwd(), "tmp")
795 if os.path.exists(image_path) and os.path.exists(kernel_path) \
796 and os.path.exists(source_env_path) and os.path.exists(tmp_path):
797 cmdline = "/usr/bin/xterm -e "
798 cmdline += "\" export OE_TMPDIR=" + tmp_path + "; "
799 cmdline += "source " + source_env_path + " " + os.getcwd() + "; "
800 cmdline += "runqemu " + kernel_path + " " + image_path + "; bash\""
801 subprocess.Popen(shlex.split(cmdline))
802 else:
803 lbl = "<b>Path error</b>\nOne of your paths is wrong,"
804 lbl = lbl + " please make sure the following paths exist:\n"
805 lbl = lbl + "image path:" + image_path + "\n"
806 lbl = lbl + "kernel path:" + kernel_path + "\n"
807 lbl = lbl + "source environment path:" + source_env_path + "\n"
808 lbl = lbl + "tmp path: " + tmp_path + "."
809 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
810 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
811 dialog.run()
812 dialog.destroy()
813
814 def show_packages(self, ask=True):
815 _, selected_recipes = self.recipe_model.get_selected_recipes()
816 if selected_recipes and ask:
817 lbl = "<b>Package list may be incomplete!</b>\nDo you want to build selected recipes"
818 lbl = lbl + " to get a full list (Yes) or just view the existing packages (No)?"
819 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
820 dialog.add_button(gtk.STOCK_YES, gtk.RESPONSE_YES)
821 dialog.add_button(gtk.STOCK_NO, gtk.RESPONSE_NO)
822 dialog.set_default_response(gtk.RESPONSE_YES)
823 response = dialog.run()
824 dialog.destroy()
825 if response == gtk.RESPONSE_YES:
826 self.switch_page(self.PACKAGE_GENERATING)
827 else:
828 self.switch_page(self.PACKAGE_SELECTION)
829 else:
830 self.switch_page(self.PACKAGE_SELECTION)
831
832 def show_recipes(self):
833 self.switch_page(self.RECIPE_SELECTION)
834
835 def initiate_new_build(self):
836 self.configuration.curr_mach = ""
837 self.image_configuration_page.switch_machine_combo()
838 self.switch_page(self.MACHINE_SELECTION)
839
840 def show_configuration(self):
841 self.switch_page(self.RCPPKGINFO_POPULATED)
842
843 def stop_build(self):
844 if self.stopping:
845 lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once,"
846 lbl = lbl + " would you like to 'Force Stop' the build?\n\n"
847 lbl = lbl + "This will stop the build as quickly as possible but may"
848 lbl = lbl + " well leave your build directory in an unusable state"
849 lbl = lbl + " that requires manual steps to fix.\n"
850 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
851 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
852 dialog.add_button("Force Stop", gtk.RESPONSE_YES)
853 else:
854 lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this"
855 lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as"
856 lbl = lbl + " possible but may well leave your build directory in an"
857 lbl = lbl + " unusable state that requires manual steps to fix.\n\n"
858 lbl = lbl + "'Stop' will stop the build as soon as all in"
859 lbl = lbl + " progress build tasks are finished. However if a"
860 lbl = lbl + " lengthy compilation phase is in progress this may take"
861 lbl = lbl + " some time."
862 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
863 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
864 dialog.add_button("Stop", gtk.RESPONSE_OK)
865 dialog.add_button("Force Stop", gtk.RESPONSE_YES)
866 response = dialog.run()
867 dialog.destroy()
868 if response != gtk.RESPONSE_CANCEL:
869 self.stopping = True
870 if response == gtk.RESPONSE_OK:
871 self.handler.cancel_build()
872 elif response == gtk.RESPONSE_YES:
873 self.handler.cancel_build(True)
diff --git a/bitbake/lib/bb/ui/crumbs/configurator.py b/bitbake/lib/bb/ui/crumbs/configurator.py
deleted file mode 100644
index 837ee1ca91..0000000000
--- a/bitbake/lib/bb/ui/crumbs/configurator.py
+++ /dev/null
@@ -1,346 +0,0 @@
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#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gobject
22import copy
23import re, os
24from bb import data
25
26class Configurator(gobject.GObject):
27
28 """
29 A GObject to handle writing modified configuration values back
30 to conf files.
31 """
32 __gsignals__ = {
33 "layers-loaded" : (gobject.SIGNAL_RUN_LAST,
34 gobject.TYPE_NONE,
35 ()),
36 "layers-changed" : (gobject.SIGNAL_RUN_LAST,
37 gobject.TYPE_NONE,
38 ())
39 }
40
41 def __init__(self):
42 gobject.GObject.__init__(self)
43 self.bblayers = None
44 self.enabled_layers = {}
45 self.loaded_layers = {}
46 self.config = {}
47 self.orig_config = {}
48 self.preconf = None
49 self.postconf = None
50
51 # NOTE: cribbed from the cooker...
52 def _parse(self, f, data, include=False):
53 try:
54 return bb.parse.handle(f, data, include)
55 except (IOError, bb.parse.ParseError) as exc:
56 parselog.critical("Unable to parse %s: %s" % (f, exc))
57 sys.exit(1)
58
59 def _loadConf(self, path):
60 def getString(var):
61 return data.getVar(var, True) or ""
62
63 if self.orig_config:
64 del self.orig_config
65 self.orig_config = {}
66
67 data = bb.data.init()
68 data = self._parse(path, data)
69
70 # We only need to care about certain variables
71 mach = getString('MACHINE')
72 if mach and mach != self.config.get('MACHINE', ''):
73 self.config['MACHINE'] = mach
74 sdkmach = getString('SDKMACHINE')
75 if sdkmach and sdkmach != self.config.get('SDKMACHINE', ''):
76 self.config['SDKMACHINE'] = sdkmach
77 distro = getString('DISTRO')
78 if not distro:
79 distro = "defaultsetup"
80 if distro and distro != self.config.get('DISTRO', ''):
81 self.config['DISTRO'] = distro
82 bbnum = getString('BB_NUMBER_THREADS')
83 if bbnum and bbnum != self.config.get('BB_NUMBER_THREADS', ''):
84 self.config['BB_NUMBER_THREADS'] = bbnum
85 pmake = getString('PARALLEL_MAKE')
86 if pmake and pmake != self.config.get('PARALLEL_MAKE', ''):
87 self.config['PARALLEL_MAKE'] = pmake
88 pclass = getString('PACKAGE_CLASSES')
89 if pclass and pclass != self.config.get('PACKAGE_CLASSES', ''):
90 self.config['PACKAGE_CLASSES'] = pclass
91 fstypes = getString('IMAGE_FSTYPES')
92 if fstypes and fstypes != self.config.get('IMAGE_FSTYPES', ''):
93 self.config['IMAGE_FSTYPES'] = fstypes
94
95 # Values which aren't always set in the conf must be explicitly
96 # loaded as empty values for save to work
97 incompat = getString('INCOMPATIBLE_LICENSE')
98 if incompat and incompat != self.config.get('INCOMPATIBLE_LICENSE', ''):
99 self.config['INCOMPATIBLE_LICENSE'] = incompat
100 else:
101 self.config['INCOMPATIBLE_LICENSE'] = ""
102
103 # Non-standard, namespaces, variables for GUI preferences
104 toolchain = getString('HOB_BUILD_TOOLCHAIN')
105 if toolchain and toolchain != self.config.get('HOB_BUILD_TOOLCHAIN', ''):
106 self.config['HOB_BUILD_TOOLCHAIN'] = toolchain
107 header = getString('HOB_BUILD_TOOLCHAIN_HEADERS')
108 if header and header != self.config.get('HOB_BUILD_TOOLCHAIN_HEADERS', ''):
109 self.config['HOB_BUILD_TOOLCHAIN_HEADERS'] = header
110
111 self.orig_config = copy.deepcopy(self.config)
112
113 def setConfVar(self, var, val):
114 self.config[var] = val
115
116 def getConfVar(self, var):
117 if var in self.config:
118 return self.config[var]
119 else:
120 return ""
121
122 def _loadLayerConf(self, path):
123 self.bblayers = path
124 self.enabled_layers = {}
125 self.loaded_layers = {}
126 data = bb.data.init()
127 data = self._parse(self.bblayers, data)
128 layers = (data.getVar('BBLAYERS', True) or "").split()
129 for layer in layers:
130 # TODO: we may be better off calling the layer by its
131 # BBFILE_COLLECTIONS value?
132 name = self._getLayerName(layer)
133 self.loaded_layers[name] = layer
134
135 self.enabled_layers = copy.deepcopy(self.loaded_layers)
136 self.emit("layers-loaded")
137
138 def _addConfigFile(self, path):
139 conffiles = ["local.conf", "hob-pre.conf", "hob-post.conf"]
140 pref, sep, filename = path.rpartition("/")
141
142 if filename == "hob-pre.conf":
143 self.preconf = path
144
145 if filename == "hob-post.conf":
146 self.postconf = path
147
148 if filename in conffiles:
149 self._loadConf(path)
150 elif filename == "bblayers.conf":
151 self._loadLayerConf(path)
152
153 def _splitLayer(self, path):
154 # we only care about the path up to /conf/layer.conf
155 layerpath, conf, end = path.rpartition("/conf/")
156 return layerpath
157
158 def _getLayerName(self, path):
159 # Should this be the collection name?
160 layerpath, sep, name = path.rpartition("/")
161 return name
162
163 def disableLayer(self, layer):
164 if layer in self.enabled_layers:
165 del self.enabled_layers[layer]
166
167 def addLayerConf(self, confpath):
168 layerpath = self._splitLayer(confpath)
169 name = self._getLayerName(layerpath)
170
171 if not layerpath or not name:
172 return None, None
173 elif name not in self.enabled_layers:
174 self.addLayer(name, layerpath)
175 return name, layerpath
176 else:
177 return name, None
178
179 def addLayer(self, name, path):
180 self.enabled_layers[name] = path
181
182 def _isLayerConfDirty(self):
183 # if a different number of layers enabled to what was
184 # loaded, definitely different
185 if len(self.enabled_layers) != len(self.loaded_layers):
186 return True
187
188 for layer in self.loaded_layers:
189 # if layer loaded but no longer present, definitely dirty
190 if layer not in self.enabled_layers:
191 return True
192
193 for layer in self.enabled_layers:
194 # if this layer wasn't present at load, definitely dirty
195 if layer not in self.loaded_layers:
196 return True
197 # if this layers path has changed, definitely dirty
198 if self.enabled_layers[layer] != self.loaded_layers[layer]:
199 return True
200
201 return False
202
203 def _constructLayerEntry(self):
204 """
205 Returns a string representing the new layer selection
206 """
207 layers = self.enabled_layers.copy()
208 # Construct BBLAYERS entry
209 layer_entry = "BBLAYERS = \" \\\n"
210 if 'meta' in layers:
211 layer_entry = layer_entry + " %s \\\n" % layers['meta']
212 del layers['meta']
213 for layer in layers:
214 layer_entry = layer_entry + " %s \\\n" % layers[layer]
215 layer_entry = layer_entry + " \""
216
217 return "".join(layer_entry)
218
219 def writeConfFile(self, conffile, contents):
220 """
221 Make a backup copy of conffile and write a new file in its stead with
222 the lines in the contents list.
223 """
224 # Create a backup of the conf file
225 bkup = "%s~" % conffile
226 os.rename(conffile, bkup)
227
228 # Write the contents list object to the conf file
229 with open(conffile, "w") as new:
230 new.write("".join(contents))
231
232 def updateConf(self, orig_lines, changed_values):
233 new_config_lines = []
234 for var in changed_values:
235 # Convenience function for re.subn(). If the pattern matches
236 # return a string which contains an assignment using the same
237 # assignment operator as the old assignment.
238 def replace_val(matchobj):
239 var = matchobj.group(1) # config variable
240 op = matchobj.group(2) # assignment operator
241 val = changed_values[var] # new config value
242 return "%s %s \"%s\"" % (var, op, val)
243
244 pattern = '^\s*(%s)\s*([+=?.]+)(.*)' % re.escape(var)
245 p = re.compile(pattern)
246 cnt = 0
247 replaced = False
248
249 # Iterate over the local.conf lines and if they are a match
250 # for the pattern comment out the line and append a new line
251 # with the new VAR op "value" entry
252 for line in orig_lines:
253 new_line, replacements = p.subn(replace_val, line)
254 if replacements:
255 orig_lines[cnt] = "#%s" % line
256 new_config_lines.append(new_line)
257 replaced = True
258 cnt = cnt + 1
259
260 if not replaced:
261 new_config_lines.append("%s = \"%s\"\n" % (var, changed_values[var]))
262
263 # Add the modified variables
264 orig_lines.extend(new_config_lines)
265 return orig_lines
266
267 def writeConf(self):
268 pre_vars = ["MACHINE", "SDKMACHINE", "DISTRO",
269 "INCOMPATIBLE_LICENSE"]
270 post_vars = ["BB_NUMBER_THREADS", "PARALLEL_MAKE", "PACKAGE_CLASSES",
271 "IMAGE_FSTYPES", "HOB_BUILD_TOOLCHAIN",
272 "HOB_BUILD_TOOLCHAIN_HEADERS"]
273 pre_values = {}
274 post_values = {}
275 changed_values = {}
276 pre_lines = None
277 post_lines = None
278
279 for var in self.config:
280 val = self.config[var]
281 if self.orig_config.get(var, None) != val:
282 changed_values[var] = val
283
284 if not len(changed_values):
285 return
286
287 for var in changed_values:
288 if var in pre_vars:
289 pre_values[var] = changed_values[var]
290 elif var in post_vars:
291 post_values[var] = changed_values[var]
292
293 with open(self.preconf, 'r') as pre:
294 pre_lines = pre.readlines()
295 pre_lines = self.updateConf(pre_lines, pre_values)
296 if len(pre_lines):
297 self.writeConfFile(self.preconf, pre_lines)
298
299 with open(self.postconf, 'r') as post:
300 post_lines = post.readlines()
301 post_lines = self.updateConf(post_lines, post_values)
302 if len(post_lines):
303 self.writeConfFile(self.postconf, post_lines)
304
305 del self.orig_config
306 self.orig_config = copy.deepcopy(self.config)
307
308 def insertTempBBPath(self, bbpath, bbfiles):
309 # read the original conf into a list
310 with open(self.postconf, 'r') as config:
311 config_lines = config.readlines()
312
313 if bbpath:
314 config_lines.append("BBPATH := \"${BBPATH}:%s\"\n" % bbpath)
315 if bbfiles:
316 config_lines.append("BBFILES := \"${BBFILES} %s\"\n" % bbfiles)
317
318 self.writeConfFile(self.postconf, config_lines)
319
320 def writeLayerConf(self):
321 # If we've not added/removed new layers don't write
322 if not self._isLayerConfDirty():
323 return
324
325 # This pattern should find the existing BBLAYERS
326 pattern = 'BBLAYERS\s=\s\".*\"'
327
328 replacement = self._constructLayerEntry()
329
330 with open(self.bblayers, "r") as f:
331 contents = f.read()
332 p = re.compile(pattern, re.DOTALL)
333 new = p.sub(replacement, contents)
334
335 self.writeConfFile(self.bblayers, new)
336
337 # set loaded_layers for dirtiness tracking
338 self.loaded_layers = copy.deepcopy(self.enabled_layers)
339
340 self.emit("layers-changed")
341
342 def configFound(self, handler, path):
343 self._addConfigFile(path)
344
345 def loadConfig(self, path):
346 self._addConfigFile(path)
diff --git a/bitbake/lib/bb/ui/crumbs/hig.py b/bitbake/lib/bb/ui/crumbs/hig.py
index b3b3c7a72e..89dfe03608 100644
--- a/bitbake/lib/bb/ui/crumbs/hig.py
+++ b/bitbake/lib/bb/ui/crumbs/hig.py
@@ -1,9 +1,11 @@
1# 1#
2# BitBake Graphical GTK User Interface 2# BitBake Graphical GTK User Interface
3# 3#
4# Copyright (C) 2011 Intel Corporation 4# Copyright (C) 2011-2012 Intel Corporation
5# 5#
6# Authored by Joshua Lock <josh@linux.intel.com> 6# Authored by Joshua Lock <josh@linux.intel.com>
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8# Authored by Shane Wang <shane.wang@intel.com>
7# 9#
8# This program is free software; you can redistribute it and/or modify 10# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as 11# it under the terms of the GNU General Public License version 2 as
@@ -18,21 +20,33 @@
18# with this program; if not, write to the Free Software Foundation, Inc., 20# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 22
21import gobject
22import gtk 23import gtk
24import gobject
25import hashlib
26import os
27import re
28import subprocess
29import shlex
30from bb.ui.crumbs.hobcolor import HobColors
31from bb.ui.crumbs.hobwidget import HobWidget
32from bb.ui.crumbs.progressbar import HobProgressBar
33
23""" 34"""
24The following are convenience classes for implementing GNOME HIG compliant 35The following are convenience classes for implementing GNOME HIG compliant
25BitBake GUI's 36BitBake GUI's
26In summary: spacing = 12px, border-width = 6px 37In summary: spacing = 12px, border-width = 6px
27""" 38"""
28 39
40#
41# CrumbsDialog
42#
29class CrumbsDialog(gtk.Dialog): 43class CrumbsDialog(gtk.Dialog):
30 """ 44 """
31 A GNOME HIG compliant dialog widget. 45 A GNOME HIG compliant dialog widget.
32 Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons 46 Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
33 """ 47 """
34 def __init__(self, parent=None, label="", icon=gtk.STOCK_INFO): 48 def __init__(self, parent=None, label="", icon=gtk.STOCK_INFO):
35 gtk.Dialog.__init__(self, "", parent, gtk.DIALOG_DESTROY_WITH_PARENT) 49 super(CrumbsDialog, self).__init__("", parent, gtk.DIALOG_DESTROY_WITH_PARENT)
36 50
37 #self.set_property("has-separator", False) # note: deprecated in 2.22 51 #self.set_property("has-separator", False) # note: deprecated in 2.22
38 52
@@ -59,3 +73,572 @@ class CrumbsDialog(gtk.Dialog):
59 self.label.set_property("yalign", 0.00) 73 self.label.set_property("yalign", 0.00)
60 self.label.show() 74 self.label.show()
61 first_row.add(self.label) 75 first_row.add(self.label)
76
77#
78# Brought-in-by Dialog
79#
80class BinbDialog(gtk.Dialog):
81 """
82 A dialog widget to show "brought in by" info when a recipe/package is clicked.
83 """
84
85 def __init__(self, title, content, parent=None):
86 super(BinbDialog, self).__init__(title, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, None)
87
88 self.set_position(gtk.WIN_POS_MOUSE)
89 self.set_resizable(False)
90 self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.DARK))
91
92 hbox = gtk.HBox(False, 0)
93 self.vbox.pack_start(hbox, expand=False, fill=False, padding=10)
94
95 label = gtk.Label(content)
96 label.set_alignment(0, 0)
97 label.set_line_wrap(True)
98 label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.WHITE))
99
100 hbox.pack_start(label, expand=False, fill=False, padding=10)
101 self.vbox.show_all()
102
103#
104# AdvancedSettings Dialog
105#
106class AdvancedSettingDialog (gtk.Dialog):
107
108 def __init__(self, title, configuration, all_image_types,
109 all_package_formats, all_distros, all_sdk_machines,
110 max_threads, split_model, parent, flags, buttons):
111 super(AdvancedSettingDialog, self).__init__(title, parent, flags, buttons)
112
113 # class members from other objects
114 # bitbake settings from Builder.Configuration
115 self.configuration = configuration
116 self.image_types = all_image_types
117 self.all_package_formats = all_package_formats
118 self.all_distros = all_distros
119 self.all_sdk_machines = all_sdk_machines
120 self.max_threads = max_threads
121 self.split_model = split_model
122
123 # class members for internal use
124 self.pkgfmt_store = None
125 self.distro_combo = None
126 self.dldir_text = None
127 self.sstatedir_text = None
128 self.sstatemirror_text = None
129 self.bb_spinner = None
130 self.pmake_spinner = None
131 self.rootfs_size_spinner = None
132 self.extra_size_spinner = None
133 self.gplv3_checkbox = None
134 self.toolchain_checkbox = None
135 self.setting_store = None
136 self.image_types_checkbuttons = {}
137
138 self.variables = {}
139 self.variables["PACKAGE_FORMAT"] = self.configuration.curr_package_format
140 self.variables["INCOMPATIBLE_LICENSE"] = self.configuration.incompat_license
141 self.variables["IMAGE_FSTYPES"] = self.configuration.image_fstypes
142 self.md5 = hashlib.md5(str(sorted(self.variables.items()))).hexdigest()
143 self.settings_changed = False
144
145 # create visual elements on the dialog
146 self.create_visual_elements()
147 self.connect("response", self.response_cb)
148
149 def create_visual_elements(self):
150 self.set_size_request(500, 500)
151
152 self.nb = gtk.Notebook()
153 self.nb.set_show_tabs(True)
154 self.nb.append_page(self.create_image_types_page(), gtk.Label("Image types"))
155 self.nb.append_page(self.create_output_page(), gtk.Label("Output"))
156 self.nb.append_page(self.create_build_environment_page(), gtk.Label("Build environment"))
157 self.nb.append_page(self.create_others_page(), gtk.Label("Others"))
158 self.nb.set_current_page(0)
159 self.vbox.pack_start(self.nb, expand=True, fill=True)
160 self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True)
161
162 self.show_all()
163
164 def create_image_types_page(self):
165 advanced_vbox = gtk.VBox(False, 15)
166 advanced_vbox.set_border_width(20)
167
168 rows = (len(self.image_types)+1)/2
169 table = gtk.Table(rows + 1, 10, True)
170 advanced_vbox.pack_start(table, expand=False, fill=False)
171
172 tooltip = "Select image file system types that will be used."
173 image = gtk.Image()
174 image.show()
175 image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON)
176 image.set_tooltip_text(tooltip)
177 label = HobWidget.gen_label_widget("<span weight=\"bold\">Select image types:</span>")
178 table.attach(label, 0, 9, 0, 1)
179 table.attach(image, 9, 10, 0, 1)
180
181 i = 1
182 j = 1
183 for image_type in self.image_types:
184 self.image_types_checkbuttons[image_type] = gtk.CheckButton(image_type)
185 self.image_types_checkbuttons[image_type].set_tooltip_text("Build an %s image" % image_type)
186 table.attach(self.image_types_checkbuttons[image_type], j, j + 4, i, i + 1)
187 if image_type in self.configuration.image_fstypes:
188 self.image_types_checkbuttons[image_type].set_active(True)
189 i += 1
190 if i > rows:
191 i = 1
192 j = j + 4
193
194 return advanced_vbox
195
196 def create_output_page(self):
197 advanced_vbox = gtk.VBox(False, 15)
198 advanced_vbox.set_border_width(20)
199
200 sub_vbox = gtk.VBox(False, 5)
201 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
202 label = HobWidget.gen_label_widget("<span weight=\"bold\">Packaging Format:</span>")
203 tooltip = "Select package formats that will be used. "
204 tooltip += "The first format will be used for final image"
205 pkgfmt_widget, self.pkgfmt_store = HobWidget.gen_pkgfmt_widget(self.configuration.curr_package_format, self.all_package_formats, tooltip)
206 sub_vbox.pack_start(label, expand=False, fill=False)
207 sub_vbox.pack_start(pkgfmt_widget, expand=False, fill=False)
208
209 sub_vbox = gtk.VBox(False, 5)
210 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
211 label = HobWidget.gen_label_widget("<span weight=\"bold\">Image Rootfs Size: (MB)</span>")
212 tooltip = "Sets the size of your target image.\nThis is the basic size of your target image, unless your selected package size exceeds this value, or you set value to \"Image Extra Size\"."
213 rootfs_size_widget, self.rootfs_size_spinner = HobWidget.gen_spinner_widget(int(self.configuration.image_rootfs_size*1.0/1024), 0, 1024, tooltip)
214 sub_vbox.pack_start(label, expand=False, fill=False)
215 sub_vbox.pack_start(rootfs_size_widget, expand=False, fill=False)
216
217 sub_vbox = gtk.VBox(False, 5)
218 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
219 label = HobWidget.gen_label_widget("<span weight=\"bold\">Image Extra Size: (MB)</span>")
220 tooltip = "Sets the extra free space of your target image.\nDefaultly, system will reserve 30% of your image size as your free space. If your image contains zypper, it will bring in 50MB more space. The maximum free space is 1024MB."
221 extra_size_widget, self.extra_size_spinner = HobWidget.gen_spinner_widget(int(self.configuration.image_extra_size*1.0/1024), 0, 1024, tooltip)
222 sub_vbox.pack_start(label, expand=False, fill=False)
223 sub_vbox.pack_start(extra_size_widget, expand=False, fill=False)
224
225 self.gplv3_checkbox = gtk.CheckButton("Exclude GPLv3 packages")
226 self.gplv3_checkbox.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image")
227 if "GPLv3" in self.configuration.incompat_license.split():
228 self.gplv3_checkbox.set_active(True)
229 else:
230 self.gplv3_checkbox.set_active(False)
231 advanced_vbox.pack_start(self.gplv3_checkbox, expand=False, fill=False)
232
233 sub_hbox = gtk.HBox(False, 5)
234 advanced_vbox.pack_start(sub_hbox, expand=False, fill=False)
235 self.toolchain_checkbox = gtk.CheckButton("Build Toolchain")
236 self.toolchain_checkbox.set_tooltip_text("Check this box to build the related toolchain with your image")
237 self.toolchain_checkbox.set_active(self.configuration.toolchain_build)
238 sub_hbox.pack_start(self.toolchain_checkbox, expand=False, fill=False)
239
240 tooltip = "This is the Host platform you would like to run the toolchain"
241 sdk_machine_widget, self.sdk_machine_combo = HobWidget.gen_combo_widget(self.configuration.curr_sdk_machine, self.all_sdk_machines, tooltip)
242 sub_hbox.pack_start(sdk_machine_widget, expand=False, fill=False)
243
244 return advanced_vbox
245
246 def create_build_environment_page(self):
247 advanced_vbox = gtk.VBox(False, 15)
248 advanced_vbox.set_border_width(20)
249
250 sub_vbox = gtk.VBox(False, 5)
251 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
252 label = HobWidget.gen_label_widget("<span weight=\"bold\">Select Distro:</span>")
253 tooltip = "This is the Yocto distribution you would like to use"
254 distro_widget, self.distro_combo = HobWidget.gen_combo_widget(self.configuration.curr_distro, self.all_distros, tooltip)
255 sub_vbox.pack_start(label, expand=False, fill=False)
256 sub_vbox.pack_start(distro_widget, expand=False, fill=False)
257
258 sub_vbox = gtk.VBox(False, 5)
259 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
260 label = HobWidget.gen_label_widget("<span weight=\"bold\">BB_NUMBER_THREADS:</span>")
261 tooltip = "Sets the number of threads that bitbake tasks can run simultaneously"
262 bbthread_widget, self.bb_spinner = HobWidget.gen_spinner_widget(self.configuration.bbthread, 1, self.max_threads, tooltip)
263 sub_vbox.pack_start(label, expand=False, fill=False)
264 sub_vbox.pack_start(bbthread_widget, expand=False, fill=False)
265
266 sub_vbox = gtk.VBox(False, 5)
267 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
268 label = HobWidget.gen_label_widget("<span weight=\"bold\">PARALLEL_MAKE:</span>")
269 tooltip = "Sets the make parallism, as known as 'make -j'"
270 pmake_widget, self.pmake_spinner = HobWidget.gen_spinner_widget(self.configuration.pmake, 1, self.max_threads, tooltip)
271 sub_vbox.pack_start(label, expand=False, fill=False)
272 sub_vbox.pack_start(pmake_widget, expand=False, fill=False)
273
274 sub_vbox = gtk.VBox(False, 5)
275 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
276 label = HobWidget.gen_label_widget("<span weight=\"bold\">Set Download Directory:</span>")
277 tooltip = "Select a folder that caches the upstream project source code"
278 dldir_widget, self.dldir_text = HobWidget.gen_entry_widget(self.split_model, self.configuration.dldir, self, tooltip)
279 sub_vbox.pack_start(label, expand=False, fill=False)
280 sub_vbox.pack_start(dldir_widget, expand=False, fill=False)
281
282 sub_vbox = gtk.VBox(False, 5)
283 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
284 label = HobWidget.gen_label_widget("<span weight=\"bold\">Select SSTATE Directory:</span>")
285 tooltip = "Select a folder that caches your prebuilt results"
286 sstatedir_widget, self.sstatedir_text = HobWidget.gen_entry_widget(self.split_model, self.configuration.sstatedir, self, tooltip)
287 sub_vbox.pack_start(label, expand=False, fill=False)
288 sub_vbox.pack_start(sstatedir_widget, expand=False, fill=False)
289
290 sub_vbox = gtk.VBox(False, 5)
291 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
292 label = HobWidget.gen_label_widget("<span weight=\"bold\">Select SSTATE Mirror:</span>")
293 tooltip = "Select the prebuilt mirror that will fasten your build speed"
294 sstatemirror_widget, self.sstatemirror_text = HobWidget.gen_entry_widget(self.split_model, self.configuration.sstatemirror, self, tooltip)
295 sub_vbox.pack_start(label, expand=False, fill=False)
296 sub_vbox.pack_start(sstatemirror_widget, expand=False, fill=False)
297
298 return advanced_vbox
299
300 def create_others_page(self):
301 advanced_vbox = gtk.VBox(False, 15)
302 advanced_vbox.set_border_width(20)
303
304 sub_vbox = gtk.VBox(False, 5)
305 advanced_vbox.pack_start(sub_vbox, expand=True, fill=True)
306 label = HobWidget.gen_label_widget("<span weight=\"bold\">Add your own variables:</span>")
307 tooltip = "This is the key/value pair for your extra settings"
308 setting_widget, self.setting_store = HobWidget.gen_editable_settings(self.configuration.extra_setting, tooltip)
309 sub_vbox.pack_start(label, expand=False, fill=False)
310 sub_vbox.pack_start(setting_widget, expand=True, fill=True)
311
312 return advanced_vbox
313
314 def response_cb(self, dialog, response_id):
315 self.variables = {}
316
317 self.configuration.curr_package_format = ""
318 it = self.pkgfmt_store.get_iter_first()
319 while it:
320 value = self.pkgfmt_store.get_value(it, 2)
321 if value:
322 self.configuration.curr_package_format += (self.pkgfmt_store.get_value(it, 1) + " ")
323 it = self.pkgfmt_store.iter_next(it)
324 self.configuration.curr_package_format = self.configuration.curr_package_format.strip()
325 self.variables["PACKAGE_FORMAT"] = self.configuration.curr_package_format
326
327 self.configuration.curr_distro = self.distro_combo.get_active_text()
328 self.configuration.dldir = self.dldir_text.get_text()
329 self.configuration.sstatedir = self.sstatedir_text.get_text()
330 self.configuration.sstatemirror = self.sstatemirror_text.get_text()
331 self.configuration.bbthread = self.bb_spinner.get_value_as_int()
332 self.configuration.pmake = self.pmake_spinner.get_value_as_int()
333 self.configuration.image_rootfs_size = self.rootfs_size_spinner.get_value_as_int() * 1024
334 self.configuration.image_extra_size = self.extra_size_spinner.get_value_as_int() * 1024
335
336 self.configuration.image_fstypes = []
337 for image_type in self.image_types:
338 if self.image_types_checkbuttons[image_type].get_active():
339 self.configuration.image_fstypes.append(image_type)
340 self.variables["IMAGE_FSTYPES"] = self.configuration.image_fstypes
341
342 if self.gplv3_checkbox.get_active():
343 if "GPLv3" not in self.configuration.incompat_license.split():
344 self.configuration.incompat_license += " GPLv3"
345 else:
346 if "GPLv3" in self.configuration.incompat_license.split():
347 self.configuration.incompat_license = self.configuration.incompat_license.split().remove("GPLv3")
348 self.configuration.incompat_license = " ".join(self.configuration.incompat_license or [])
349 self.configuration.incompat_license = self.configuration.incompat_license.strip()
350 self.variables["INCOMPATIBLE_LICENSE"] = self.configuration.incompat_license
351
352 self.configuration.toolchain_build = self.toolchain_checkbox.get_active()
353
354 self.configuration.extra_setting = {}
355 it = self.setting_store.get_iter_first()
356 while it:
357 key = self.setting_store.get_value(it, 0)
358 value = self.setting_store.get_value(it, 1)
359 self.configuration.extra_setting[key] = value
360 self.variables[key] = value
361 it = self.setting_store.iter_next(it)
362
363 md5 = hashlib.md5(str(sorted(self.variables.items()))).hexdigest()
364 self.settings_changed = (self.md5 != md5)
365
366#
367# DeployImageDialog
368#
369class DeployImageDialog (gtk.Dialog):
370
371 __dummy_usb__ = "--select a usb drive--"
372
373 def __init__(self, title, image_path, parent, flags, buttons):
374 super(DeployImageDialog, self).__init__(title, parent, flags, buttons)
375
376 self.image_path = image_path
377
378 self.create_visual_elements()
379 self.connect("response", self.response_cb)
380
381 def create_visual_elements(self):
382 self.set_border_width(20)
383 self.set_default_size(500, 250)
384
385 label = gtk.Label()
386 label.set_alignment(0.0, 0.5)
387 markup = "<span font_desc='12'>The image to be written into usb drive:</span>"
388 label.set_markup(markup)
389 self.vbox.pack_start(label, expand=False, fill=False, padding=2)
390
391 scroll = gtk.ScrolledWindow()
392 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
393 scroll.set_shadow_type(gtk.SHADOW_IN)
394 tv = gtk.TextView()
395 tv.set_editable(False)
396 tv.set_wrap_mode(gtk.WRAP_WORD)
397 tv.set_cursor_visible(False)
398 buf = gtk.TextBuffer()
399 buf.set_text(self.image_path)
400 tv.set_buffer(buf)
401 scroll.add(tv)
402 self.vbox.pack_start(scroll, expand=True, fill=True)
403
404 self.usb_desc = gtk.Label()
405 self.usb_desc.set_alignment(0.0, 0.5)
406 markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
407 self.usb_desc.set_markup(markup)
408
409 self.usb_combo = gtk.combo_box_new_text()
410 self.usb_combo.connect("changed", self.usb_combo_changed_cb)
411 model = self.usb_combo.get_model()
412 model.clear()
413 self.usb_combo.append_text(self.__dummy_usb__)
414 for usb in self.find_all_usb_devices():
415 self.usb_combo.append_text("/dev/" + usb)
416 self.usb_combo.set_active(0)
417 self.vbox.pack_start(self.usb_combo, expand=True, fill=True)
418 self.vbox.pack_start(self.usb_desc, expand=False, fill=False, padding=2)
419
420 self.progress_bar = HobProgressBar()
421 self.vbox.pack_start(self.progress_bar, expand=False, fill=False)
422 separator = gtk.HSeparator()
423 self.vbox.pack_start(separator, expand=False, fill=True, padding=10)
424
425 self.vbox.show_all()
426 self.progress_bar.hide()
427
428 def popen_read(self, cmd):
429 return os.popen("%s 2>/dev/null" % cmd).read().strip()
430
431 def find_all_usb_devices(self):
432 usb_devs = [ os.readlink(u)
433 for u in self.popen_read('ls /dev/disk/by-id/usb*').split()
434 if not re.search(r'part\d+', u) ]
435 return [ '%s' % u[u.rfind('/')+1:] for u in usb_devs ]
436
437 def get_usb_info(self, dev):
438 return "%s %s" % \
439 (self.popen_read('cat /sys/class/block/%s/device/vendor' % dev),
440 self.popen_read('cat /sys/class/block/%s/device/model' % dev))
441
442 def usb_combo_changed_cb(self, usb_combo):
443 combo_item = self.usb_combo.get_active_text()
444 if not combo_item or combo_item == self.__dummy_usb__:
445 markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
446 self.usb_desc.set_markup(markup)
447 else:
448 markup = "<span font_desc='12'>" + self.get_usb_info(combo_item.lstrip("/dev/")) + "</span>"
449 self.usb_desc.set_markup(markup)
450
451 def response_cb(self, dialog, response_id):
452 if response_id == gtk.RESPONSE_YES:
453 combo_item = self.usb_combo.get_active_text()
454 if combo_item and combo_item != self.__dummy_usb__:
455 cmdline = "/usr/bin/xterm -e "
456 cmdline += "\"sudo dd if=" + self.image_path + " of=" + combo_item + "; bash\""
457 subprocess.Popen(args=shlex.split(cmdline))
458
459 def update_progress_bar(self, title, fraction, status=True):
460 self.progress_bar.update(fraction)
461 self.progress_bar.set_title(title)
462 self.progress_bar.set_rcstyle(status)
463
464 def write_file(self, ifile, ofile):
465 self.progress_bar.reset()
466 self.progress_bar.show()
467
468 f_from = os.open(ifile, os.O_RDONLY)
469 f_to = os.open(ofile, os.O_WRONLY)
470
471 total_size = os.stat(ifile).st_size
472 written_size = 0
473
474 while True:
475 buf = os.read(f_from, 1024*1024)
476 if not buf:
477 break
478 os.write(f_to, buf)
479 written_size += 1024*1024
480 self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size)
481
482 self.update_progress_bar("Writing completed:", 1.0)
483 os.close(f_from)
484 os.close(f_to)
485 self.progress_bar.hide()
486#
487# LayerSelectionDialog
488#
489class LayerSelectionDialog (gtk.Dialog):
490
491 def __init__(self, title, layers, all_layers, split_model,
492 parent, flags, buttons):
493 super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons)
494
495 # class members from other objects
496 self.layers = layers
497 self.all_layers = all_layers
498 self.split_model = split_model
499 self.layers_changed = False
500
501 # class members for internal use
502 self.layer_store = None
503
504 # create visual elements on the dialog
505 self.create_visual_elements()
506 self.connect("response", self.response_cb)
507
508 def create_visual_elements(self):
509 self.set_border_width(20)
510 self.set_default_size(400, 250)
511
512 hbox_top = gtk.HBox()
513 self.set_border_width(12)
514 self.vbox.pack_start(hbox_top, expand=False, fill=False)
515
516 if self.split_model:
517 label = HobWidget.gen_label_widget("<span weight=\"bold\" font_desc='12'>Select Layers:</span>\n(Available layers under '${COREBASE}/layers/' directory)")
518 else:
519 label = HobWidget.gen_label_widget("<span weight=\"bold\" font_desc='12'>Select Layers:</span>")
520 hbox_top.pack_start(label, expand=False, fill=False)
521
522 tooltip = "Layer is a collection of bb files and conf files"
523 image = gtk.Image()
524 image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON)
525 image.set_tooltip_text(tooltip)
526 hbox_top.pack_end(image, expand=False, fill=False)
527
528 layer_widget, self.layer_store = HobWidget.gen_layer_widget(self.split_model, self.layers, self.all_layers, self, None)
529
530 self.vbox.pack_start(layer_widget, expand=True, fill=True)
531
532 separator = gtk.HSeparator()
533 self.vbox.pack_start(separator, False, True, 5)
534 separator.show()
535
536 hbox_button = gtk.HBox()
537 self.vbox.pack_end(hbox_button, expand=False, fill=False)
538 hbox_button.show()
539
540 label = HobWidget.gen_label_widget("<i>'meta' is Core layer for Yocto images</i>\n"
541 "<span weight=\"bold\">Please do not remove it</span>")
542 hbox_button.pack_start(label, expand=False, fill=False)
543
544 self.show_all()
545
546 def response_cb(self, dialog, response_id):
547 model = self.layer_store
548 it = model.get_iter_first()
549 layers = []
550 while it:
551 if self.split_model:
552 inc = model.get_value(it, 1)
553 if inc:
554 layers.append(model.get_value(it, 0))
555 else:
556 layers.append(model.get_value(it, 0))
557 it = model.iter_next(it)
558
559 self.layers_changed = (self.layers != layers)
560 self.layers = layers
561
562class ImageSelectionDialog (gtk.Dialog):
563
564 def __init__(self, image_folder, image_types, title, parent, flags, buttons):
565 super(ImageSelectionDialog, self).__init__(title, parent, flags, buttons)
566 self.connect("response", self.response_cb)
567
568 self.image_folder = image_folder
569 self.image_types = image_types
570 self.image_names = []
571
572 # create visual elements on the dialog
573 self.create_visual_elements()
574
575 self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
576 self.fill_image_store()
577
578 def create_visual_elements(self):
579 self.set_border_width(20)
580 self.set_default_size(600, 300)
581 self.vbox.set_spacing(10)
582
583 hbox = gtk.HBox(False, 10)
584 self.vbox.pack_start(hbox, expand=False, fill=False)
585
586 entry = gtk.Entry()
587 entry.set_text(self.image_folder)
588 table = gtk.Table(1, 10, True)
589 table.set_size_request(560, -1)
590 hbox.pack_start(table, expand=False, fill=False)
591 table.attach(entry, 0, 9, 0, 1)
592 image = gtk.Image()
593 image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON)
594 open_button = gtk.Button()
595 open_button.set_image(image)
596 open_button.connect("clicked", self.select_path_cb, self, entry)
597 table.attach(open_button, 9, 10, 0, 1)
598
599 imgtv_widget, self.imgsel_tv = HobWidget.gen_imgtv_widget(400, 160)
600 self.vbox.pack_start(imgtv_widget, expand=True, fill=True)
601
602 self.show_all()
603
604 def select_path_cb(self, action, parent, entry):
605 dialog = gtk.FileChooserDialog("", parent,
606 gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
607 (gtk.STOCK_OK, gtk.RESPONSE_YES,
608 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
609 response = dialog.run()
610 if response == gtk.RESPONSE_YES:
611 path = dialog.get_filename()
612 entry.set_text(path)
613 self.image_folder = path
614 self.fill_image_store()
615
616 dialog.destroy()
617
618 def fill_image_store(self):
619 self.image_store.clear()
620 imageset = set()
621 for root, dirs, files in os.walk(self.image_folder):
622 for f in files:
623 for image_type in self.image_types:
624 if f.endswith('.' + image_type):
625 imageset.add(f.rsplit('.' + image_type)[0])
626
627 for image in imageset:
628 self.image_store.set(self.image_store.append(), 0, image, 1, False)
629
630 self.imgsel_tv.set_model(self.image_store)
631
632 def response_cb(self, dialog, response_id):
633 self.image_names = []
634 if response_id == gtk.RESPONSE_YES:
635 iter = self.image_store.get_iter_first()
636 while iter:
637 path = self.image_store.get_path(iter)
638 if self.image_store[path][1]:
639 for root, dirs, files in os.walk(self.image_folder):
640 for f in files:
641 if f.startswith(self.image_store[path][0] + '.'):
642 self.image_names.append(f)
643 break
644 iter = self.image_store.iter_next(iter)
diff --git a/bitbake/lib/bb/ui/crumbs/hobcolor.py b/bitbake/lib/bb/ui/crumbs/hobcolor.py
new file mode 100644
index 0000000000..9d67d5c496
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobcolor.py
@@ -0,0 +1,35 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2012 Intel Corporation
5#
6# Authored by Shane Wang <shane.wang@intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21class HobColors:
22 WHITE = "#ffffff"
23 PALE_GREEN = "#aaffaa"
24 ORANGE = "#ff7c24"
25 PALE_RED = "#ffaaaa"
26 GRAY = "#aaaaaa"
27 LIGHT_GRAY = "#dddddd"
28 DARK = "#3c3b37"
29 BLACK = "#000000"
30 LIGHT_ORANGE = "#f7a787"
31
32 OK = WHITE
33 RUNNING = PALE_GREEN
34 WARNING = ORANGE
35 ERROR = PALE_RED
diff --git a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
index ddab987ca8..b071ad4503 100644
--- a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
+++ b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
@@ -4,6 +4,7 @@
4# Copyright (C) 2011 Intel Corporation 4# Copyright (C) 2011 Intel Corporation
5# 5#
6# Authored by Joshua Lock <josh@linux.intel.com> 6# Authored by Joshua Lock <josh@linux.intel.com>
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
7# 8#
8# This program is free software; you can redistribute it and/or modify 9# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as 10# it under the terms of the GNU General Public License version 2 as
@@ -20,10 +21,7 @@
20 21
21import gobject 22import gobject
22import logging 23import logging
23import tempfile 24from bb.ui.crumbs.runningbuild import RunningBuild
24import datetime
25
26progress_total = 0
27 25
28class HobHandler(gobject.GObject): 26class HobHandler(gobject.GObject):
29 27
@@ -31,147 +29,176 @@ class HobHandler(gobject.GObject):
31 This object does BitBake event handling for the hob gui. 29 This object does BitBake event handling for the hob gui.
32 """ 30 """
33 __gsignals__ = { 31 __gsignals__ = {
34 "machines-updated" : (gobject.SIGNAL_RUN_LAST, 32 "layers-updated" : (gobject.SIGNAL_RUN_LAST,
35 gobject.TYPE_NONE, 33 gobject.TYPE_NONE,
36 (gobject.TYPE_PYOBJECT,)), 34 (gobject.TYPE_PYOBJECT,)),
37 "sdk-machines-updated": (gobject.SIGNAL_RUN_LAST, 35 "package-formats-updated" : (gobject.SIGNAL_RUN_LAST,
38 gobject.TYPE_NONE, 36 gobject.TYPE_NONE,
39 (gobject.TYPE_PYOBJECT,)), 37 (gobject.TYPE_PYOBJECT,)),
40 "distros-updated" : (gobject.SIGNAL_RUN_LAST, 38 "config-updated" : (gobject.SIGNAL_RUN_LAST,
41 gobject.TYPE_NONE, 39 gobject.TYPE_NONE,
42 (gobject.TYPE_PYOBJECT,)), 40 (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
43 "package-formats-found" : (gobject.SIGNAL_RUN_LAST, 41 "command-succeeded" : (gobject.SIGNAL_RUN_LAST,
44 gobject.TYPE_NONE, 42 gobject.TYPE_NONE,
45 (gobject.TYPE_PYOBJECT,)), 43 (gobject.TYPE_INT,)),
46 "config-found" : (gobject.SIGNAL_RUN_LAST, 44 "command-failed" : (gobject.SIGNAL_RUN_LAST,
47 gobject.TYPE_NONE, 45 gobject.TYPE_NONE,
48 (gobject.TYPE_STRING,)), 46 (gobject.TYPE_STRING,)),
49 "generating-data" : (gobject.SIGNAL_RUN_LAST, 47 "generating-data" : (gobject.SIGNAL_RUN_LAST,
50 gobject.TYPE_NONE, 48 gobject.TYPE_NONE,
51 ()), 49 ()),
52 "data-generated" : (gobject.SIGNAL_RUN_LAST, 50 "data-generated" : (gobject.SIGNAL_RUN_LAST,
53 gobject.TYPE_NONE, 51 gobject.TYPE_NONE,
54 ()), 52 ()),
55 "fatal-error" : (gobject.SIGNAL_RUN_LAST, 53 "parsing-started" : (gobject.SIGNAL_RUN_LAST,
56 gobject.TYPE_NONE, 54 gobject.TYPE_NONE,
57 (gobject.TYPE_STRING, 55 (gobject.TYPE_PYOBJECT,)),
58 gobject.TYPE_STRING,)), 56 "parsing" : (gobject.SIGNAL_RUN_LAST,
59 "command-failed" : (gobject.SIGNAL_RUN_LAST, 57 gobject.TYPE_NONE,
60 gobject.TYPE_NONE, 58 (gobject.TYPE_PYOBJECT,)),
61 (gobject.TYPE_STRING,)), 59 "parsing-completed" : (gobject.SIGNAL_RUN_LAST,
62 "reload-triggered" : (gobject.SIGNAL_RUN_LAST, 60 gobject.TYPE_NONE,
63 gobject.TYPE_NONE, 61 (gobject.TYPE_PYOBJECT,)),
64 (gobject.TYPE_STRING,
65 gobject.TYPE_STRING,)),
66 } 62 }
67 63
68 (CFG_PATH_LOCAL, CFG_PATH_PRE, CFG_PATH_POST, CFG_PATH_LAYERS, CFG_FILES_DISTRO, CFG_FILES_MACH, CFG_FILES_SDK, FILES_MATCH_CLASS, GENERATE_TGTS, REPARSE_FILES, BUILD_IMAGE) = range(11) 64 (CFG_AVAIL_LAYERS, CFG_PATH_LAYERS, CFG_FILES_DISTRO, CFG_FILES_MACH, CFG_FILES_SDKMACH, FILES_MATCH_CLASS, PARSE_CONFIG, PARSE_BBFILES, GENERATE_TGTS, GENERATE_PACKAGEINFO, BUILD_TARGET_RECIPES, BUILD_TARGET_IMAGE, CMD_END) = range(13)
65 (LAYERS_REFRESH, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO) = range(5)
69 66
70 def __init__(self, taskmodel, server): 67 def __init__(self, server, server_addr, client_addr, recipe_model, package_model):
71 gobject.GObject.__init__(self) 68 super(HobHandler, self).__init__()
72 69
73 self.current_command = None 70 self.build = RunningBuild(sequential=True)
74 self.building = False 71
75 self.build_toolchain = False 72 self.recipe_model = recipe_model
76 self.build_toolchain_headers = False 73 self.package_model = package_model
74
75 self.commands_async = []
77 self.generating = False 76 self.generating = False
78 self.build_queue = []
79 self.current_phase = None 77 self.current_phase = None
80 self.bbpath_ok = False 78 self.building = False
81 self.bbfiles_ok = False 79 self.recipe_queue = []
82 self.build_type = "image" 80 self.package_queue = []
83 self.image_dir = os.path.join(tempfile.gettempdir(), 'hob-images')
84 81
85 self.model = taskmodel
86 self.server = server 82 self.server = server
87 83 self.error_msg = ""
88 deploy_dir = self.server.runCommand(["getVariable", "DEPLOY_DIR"]) 84 self.initcmd = None
89 self.image_out_dir = os.path.join(deploy_dir, "images") 85
90 self.image_output_types = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"]).split(" ") 86 self.split_model = False
91 self.bbpath = self.server.runCommand(["getVariable", "BBPATH"]) 87 if server_addr and client_addr:
92 self.bbfiles = self.server.runCommand(["getVariable", "BBFILES"]) 88 self.split_model = (server_addr != client_addr)
93 89 self.reset_server() # reset server if server was found just now
94 def run_next_command(self): 90 self.server_addr = server_addr
95 if self.current_command and not self.generating: 91
92 def kick(self):
93 import xmlrpclib
94 try:
95 # kick the while thing off
96 if self.split_model:
97 self.commands_async.append(self.CFG_AVAIL_LAYERS)
98 else:
99 self.commands_async.append(self.CFG_PATH_LAYERS)
100 self.commands_async.append(self.CFG_FILES_DISTRO)
101 self.commands_async.append(self.CFG_FILES_MACH)
102 self.commands_async.append(self.CFG_FILES_SDKMACH)
103 self.commands_async.append(self.FILES_MATCH_CLASS)
104 self.run_next_command()
105 return True
106 except xmlrpclib.Fault as x:
107 print("XMLRPC Fault getting commandline:\n %s" % x)
108 return False
109
110 def set_busy(self):
111 if not self.generating:
96 self.emit("generating-data") 112 self.emit("generating-data")
97 self.generating = True 113 self.generating = True
98 114
99 if self.current_command == self.CFG_PATH_LOCAL: 115 def clear_busy(self):
100 self.current_command = self.CFG_PATH_PRE 116 if self.generating:
101 self.server.runCommand(["findConfigFilePath", "hob-pre.conf"]) 117 self.emit("data-generated")
102 elif self.current_command == self.CFG_PATH_PRE: 118 self.generating = False
103 self.current_command = self.CFG_PATH_POST 119
104 self.server.runCommand(["findConfigFilePath", "hob-post.conf"]) 120 def run_next_command(self, initcmd=None):
105 elif self.current_command == self.CFG_PATH_POST: 121 if initcmd != None:
106 self.current_command = self.CFG_PATH_LAYERS 122 self.initcmd = initcmd
123
124 if self.commands_async:
125 self.set_busy()
126 next_command = self.commands_async.pop(0)
127 else:
128 self.clear_busy()
129 if self.initcmd != None:
130 self.emit("command-succeeded", self.initcmd)
131 return
132
133 if next_command == self.CFG_AVAIL_LAYERS:
134 self.server.runCommand(["findCoreBaseFiles", "layers", "conf/layer.conf"])
135 elif next_command == self.CFG_PATH_LAYERS:
107 self.server.runCommand(["findConfigFilePath", "bblayers.conf"]) 136 self.server.runCommand(["findConfigFilePath", "bblayers.conf"])
108 elif self.current_command == self.CFG_PATH_LAYERS: 137 elif next_command == self.CFG_FILES_DISTRO:
109 self.current_command = self.CFG_FILES_DISTRO
110 self.server.runCommand(["findConfigFiles", "DISTRO"]) 138 self.server.runCommand(["findConfigFiles", "DISTRO"])
111 elif self.current_command == self.CFG_FILES_DISTRO: 139 elif next_command == self.CFG_FILES_MACH:
112 self.current_command = self.CFG_FILES_MACH
113 self.server.runCommand(["findConfigFiles", "MACHINE"]) 140 self.server.runCommand(["findConfigFiles", "MACHINE"])
114 elif self.current_command == self.CFG_FILES_MACH: 141 elif next_command == self.CFG_FILES_SDKMACH:
115 self.current_command = self.CFG_FILES_SDK
116 self.server.runCommand(["findConfigFiles", "MACHINE-SDK"]) 142 self.server.runCommand(["findConfigFiles", "MACHINE-SDK"])
117 elif self.current_command == self.CFG_FILES_SDK: 143 elif next_command == self.FILES_MATCH_CLASS:
118 self.current_command = self.FILES_MATCH_CLASS
119 self.server.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"]) 144 self.server.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"])
120 elif self.current_command == self.FILES_MATCH_CLASS: 145 elif next_command == self.PARSE_CONFIG:
121 self.current_command = self.GENERATE_TGTS 146 self.server.runCommand(["parseConfigurationFiles", "", ""])
122 self.server.runCommand(["generateTargetsTree", "classes/image.bbclass"]) 147 elif next_command == self.PARSE_BBFILES:
123 elif self.current_command == self.GENERATE_TGTS: 148 self.server.runCommand(["parseFiles"])
124 if self.generating: 149 elif next_command == self.GENERATE_TGTS:
125 self.emit("data-generated") 150 self.server.runCommand(["generateTargetsTree", "classes/image.bbclass", [], True])
126 self.generating = False 151 elif next_command == self.GENERATE_PACKAGEINFO:
127 self.current_command = None 152 self.server.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"])
128 elif self.current_command == self.REPARSE_FILES: 153 elif next_command == self.BUILD_TARGET_RECIPES:
129 if self.build_queue: 154 self.clear_busy()
130 self.current_command = self.BUILD_IMAGE
131 else:
132 self.current_command = self.CFG_PATH_LAYERS
133 self.server.runCommand(["resetCooker"])
134 self.server.runCommand(["reparseFiles"])
135 elif self.current_command == self.BUILD_IMAGE:
136 if self.generating:
137 self.emit("data-generated")
138 self.generating = False
139 self.building = True 155 self.building = True
140 self.server.runCommand(["buildTargets", self.build_queue, "build"]) 156 self.server.runCommand(["buildTargets", self.recipe_queue, "build"])
141 self.build_queue = [] 157 self.recipe_queue = []
142 self.current_command = None 158 elif next_command == self.BUILD_TARGET_IMAGE:
143 159 self.clear_busy()
144 def handle_event(self, event, running_build, pbar): 160 self.building = True
161 targets = ["hob-image"]
162 self.server.runCommand(["setVariable", "LINGUAS_INSTALL", ""])
163 self.server.runCommand(["setVariable", "PACKAGE_INSTALL", " ".join(self.package_queue)])
164 if self.toolchain_build:
165 pkgs = self.package_queue + [i+'-dev' for i in self.package_queue] + [i+'-dbg' for i in self.package_queue]
166 self.server.runCommand(["setVariable", "TOOLCHAIN_TARGET_TASK", " ".join(pkgs)])
167 targets.append("hob-toolchain")
168 self.server.runCommand(["buildTargets", targets, "build"])
169
170 def handle_event(self, event):
145 if not event: 171 if not event:
146 return 172 return
147 173
148 # If we're running a build, use the RunningBuild event handler
149 if self.building: 174 if self.building:
150 self.current_phase = "building" 175 self.current_phase = "building"
151 running_build.handle_event(event) 176 self.build.handle_event(event)
177
178 if isinstance(event, bb.event.PackageInfo):
179 self.package_model.populate(event._pkginfolist)
180 self.run_next_command()
181
182 elif(isinstance(event, logging.LogRecord)):
183 if event.levelno >= logging.ERROR:
184 self.error_msg += event.msg + '\n'
185
152 elif isinstance(event, bb.event.TargetsTreeGenerated): 186 elif isinstance(event, bb.event.TargetsTreeGenerated):
153 self.current_phase = "data generation" 187 self.current_phase = "data generation"
154 if event._model: 188 if event._model:
155 self.model.populate(event._model) 189 self.recipe_model.populate(event._model)
190 elif isinstance(event, bb.event.CoreBaseFilesFound):
191 self.current_phase = "configuration lookup"
192 paths = event._paths
193 self.emit('layers-updated', paths)
156 elif isinstance(event, bb.event.ConfigFilesFound): 194 elif isinstance(event, bb.event.ConfigFilesFound):
157 self.current_phase = "configuration lookup" 195 self.current_phase = "configuration lookup"
158 var = event._variable 196 var = event._variable
159 if var == "distro": 197 values = event._values
160 distros = event._values 198 values.sort()
161 distros.sort() 199 self.emit("config-updated", var, values)
162 self.emit("distros-updated", distros)
163 elif var == "machine":
164 machines = event._values
165 machines.sort()
166 self.emit("machines-updated", machines)
167 elif var == "machine-sdk":
168 sdk_machines = event._values
169 sdk_machines.sort()
170 self.emit("sdk-machines-updated", sdk_machines)
171 elif isinstance(event, bb.event.ConfigFilePathFound): 200 elif isinstance(event, bb.event.ConfigFilePathFound):
172 self.current_phase = "configuration lookup" 201 self.current_phase = "configuration lookup"
173 path = event._path
174 self.emit("config-found", path)
175 elif isinstance(event, bb.event.FilesMatchingFound): 202 elif isinstance(event, bb.event.FilesMatchingFound):
176 self.current_phase = "configuration lookup" 203 self.current_phase = "configuration lookup"
177 # FIXME: hard coding, should at least be a variable shared between 204 # FIXME: hard coding, should at least be a variable shared between
@@ -183,48 +210,84 @@ class HobHandler(gobject.GObject):
183 fs, sep, format = classname.rpartition("_") 210 fs, sep, format = classname.rpartition("_")
184 formats.append(format) 211 formats.append(format)
185 formats.sort() 212 formats.sort()
186 self.emit("package-formats-found", formats) 213 self.emit("package-formats-updated", formats)
187 elif isinstance(event, bb.command.CommandCompleted): 214 elif isinstance(event, bb.command.CommandCompleted):
188 self.current_phase = None 215 self.current_phase = None
189 self.run_next_command() 216 self.run_next_command()
217
218 elif isinstance(event, bb.event.NoProvider):
219 if event._runtime:
220 r = "R"
221 else:
222 r = ""
223 if event._dependees:
224 self.error_msg += " Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r)
225 else:
226 self.error_msg += " Nothing %sPROVIDES '%s'" % (r, event._item)
227 if event._reasons:
228 for reason in event._reasons:
229 self.error_msg += " %s" % reason
230
231 self.commands_async = []
232 self.emit("command-failed", self.error_msg)
233 self.error_msg = ""
234
190 elif isinstance(event, bb.command.CommandFailed): 235 elif isinstance(event, bb.command.CommandFailed):
191 self.emit("command-failed", event.error) 236 self.commands_async = []
192 elif isinstance(event, bb.event.CacheLoadStarted): 237 if self.error_msg:
193 self.current_phase = "cache loading" 238 self.emit("command-failed", self.error_msg)
194 bb.ui.crumbs.hobeventhandler.progress_total = event.total 239 self.error_msg = ""
195 pbar.set_text("Loading cache: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total)) 240 elif isinstance(event, (bb.event.ParseStarted,
196 elif isinstance(event, bb.event.CacheLoadProgress): 241 bb.event.CacheLoadStarted,
197 self.current_phase = "cache loading" 242 bb.event.TreeDataPreparationStarted,
198 pbar.set_text("Loading cache: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total)) 243 )):
199 elif isinstance(event, bb.event.CacheLoadCompleted): 244 message = {}
200 self.current_phase = None 245 message["eventname"] = bb.event.getName(event)
201 pbar.set_text("Loading...") 246 message["current"] = 0
202 elif isinstance(event, bb.event.ParseStarted): 247 message["total"] = None
203 self.current_phase = "recipe parsing" 248 message["title"] = "Parsing recipes: "
204 if event.total == 0: 249 self.emit("parsing-started", message)
205 return 250 elif isinstance(event, (bb.event.ParseProgress,
206 bb.ui.crumbs.hobeventhandler.progress_total = event.total 251 bb.event.CacheLoadProgress,
207 pbar.set_text("Processing recipes: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total)) 252 bb.event.TreeDataPreparationProgress)):
208 elif isinstance(event, bb.event.ParseProgress): 253 message = {}
209 self.current_phase = "recipe parsing" 254 message["eventname"] = bb.event.getName(event)
210 pbar.set_text("Processing recipes: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total)) 255 message["current"] = event.current
211 elif isinstance(event, bb.event.ParseCompleted): 256 message["total"] = event.total
212 self.current_phase = None 257 message["title"] = "Parsing recipes: "
213 pbar.set_fraction(1.0) 258 self.emit("parsing", message)
214 pbar.set_text("Loading...") 259 elif isinstance(event, (bb.event.ParseCompleted,
215 elif isinstance(event, logging.LogRecord): 260 bb.event.CacheLoadCompleted,
216 format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") 261 bb.event.TreeDataPreparationCompleted)):
217 if event.levelno >= format.CRITICAL: 262 message = {}
218 self.emit("fatal-error", event.getMessage(), self.current_phase) 263 message["eventname"] = bb.event.getName(event)
264 message["current"] = event.total
265 message["total"] = event.total
266 message["title"] = "Parsing recipes: "
267 self.emit("parsing-completed", message)
268
219 return 269 return
220 270
221 def event_handle_idle_func (self, eventHandler, running_build, pbar): 271 def init_cooker(self):
222 # Consume as many messages as we can in the time available to us 272 self.server.runCommand(["initCooker"])
223 event = eventHandler.getEvent() 273
224 while event: 274 def refresh_layers(self, bblayers):
225 self.handle_event(event, running_build, pbar) 275 self.server.runCommand(["initCooker"])
226 event = eventHandler.getEvent() 276 self.server.runCommand(["setVariable", "BBLAYERS", " ".join(bblayers)])
227 return True 277 self.commands_async.append(self.PARSE_CONFIG)
278 self.commands_async.append(self.CFG_FILES_DISTRO)
279 self.commands_async.append(self.CFG_FILES_MACH)
280 self.commands_async.append(self.CFG_FILES_SDKMACH)
281 self.commands_async.append(self.FILES_MATCH_CLASS)
282 self.run_next_command(self.LAYERS_REFRESH)
283
284 def set_extra_inherit(self, bbclass):
285 inherits = self.server.runCommand(["getVariable", "INHERIT"]) or ""
286 inherits = inherits + " " + bbclass
287 self.server.runCommand(["setVariable", "INHERIT", inherits])
288
289 def set_bblayers(self, bblayers):
290 self.server.runCommand(["setVariable", "BBLAYERS", " ".join(bblayers)])
228 291
229 def set_machine(self, machine): 292 def set_machine(self, machine):
230 self.server.runCommand(["setVariable", "MACHINE", machine]) 293 self.server.runCommand(["setVariable", "MACHINE", machine])
@@ -232,62 +295,78 @@ class HobHandler(gobject.GObject):
232 def set_sdk_machine(self, sdk_machine): 295 def set_sdk_machine(self, sdk_machine):
233 self.server.runCommand(["setVariable", "SDKMACHINE", sdk_machine]) 296 self.server.runCommand(["setVariable", "SDKMACHINE", sdk_machine])
234 297
298 def set_image_fstypes(self, image_fstypes):
299 self.server.runCommand(["setVariable", "IMAGE_FSTYPES", " ".join(image_fstypes).lstrip(" ")])
300
235 def set_distro(self, distro): 301 def set_distro(self, distro):
236 self.server.runCommand(["setVariable", "DISTRO", distro]) 302 self.server.runCommand(["setVariable", "DISTRO", distro])
237 303
238 def set_package_format(self, format): 304 def set_package_format(self, format):
239 self.server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_%s" % format]) 305 package_classes = ""
240 306 for pkgfmt in format.split():
241 def reload_data(self, config=None): 307 package_classes += ("package_%s" % pkgfmt + " ")
242 img = self.model.selected_image 308 self.server.runCommand(["setVariable", "PACKAGE_CLASSES", package_classes])
243 selected_packages, _ = self.model.get_selected_packages()
244 self.emit("reload-triggered", img, " ".join(selected_packages))
245 self.current_command = self.REPARSE_FILES
246 self.run_next_command()
247 309
248 def set_bbthreads(self, threads): 310 def set_bbthreads(self, threads):
249 self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", threads]) 311 self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", threads])
250 312
251 def set_pmake(self, threads): 313 def set_pmake(self, threads):
252 pmake = "-j %s" % threads 314 pmake = "-j %s" % threads
253 self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", pmake]) 315 self.server.runCommand(["setVariable", "PARALLEL_MAKE", pmake])
254 316
255 def build_targets(self, tgts, configurator, build_type="image"): 317 def set_dl_dir(self, directory):
256 self.build_type = build_type 318 self.server.runCommand(["setVariable", "DL_DIR", directory])
257 targets = [] 319
258 nbbp = None 320 def set_sstate_dir(self, directory):
259 nbbf = None 321 self.server.runCommand(["setVariable", "SSTATE_DIR", directory])
260 targets.extend(tgts)
261 if self.build_toolchain and self.build_toolchain_headers:
262 targets.append("meta-toolchain-sdk")
263 elif self.build_toolchain:
264 targets.append("meta-toolchain")
265 self.build_queue = targets
266
267 if not self.bbpath_ok:
268 if self.image_dir in self.bbpath.split(":"):
269 self.bbpath_ok = True
270 else:
271 nbbp = self.image_dir
272 322
273 if not self.bbfiles_ok: 323 def set_sstate_mirror(self, url):
274 import re 324 self.server.runCommand(["setVariable", "SSTATE_MIRROR", url])
275 pattern = "%s/\*.bb" % self.image_dir
276 325
277 for files in self.bbfiles.split(" "): 326 def set_extra_size(self, image_extra_size):
278 if re.match(pattern, files): 327 self.server.runCommand(["setVariable", "IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size)])
279 self.bbfiles_ok = True
280 328
281 if not self.bbfiles_ok: 329 def set_rootfs_size(self, image_rootfs_size):
282 nbbf = "%s/*.bb" % self.image_dir 330 self.server.runCommand(["setVariable", "IMAGE_ROOTFS_SIZE", str(image_rootfs_size)])
283 331
284 if nbbp or nbbf: 332 def set_incompatible_license(self, incompat_license):
285 configurator.insertTempBBPath(nbbp, nbbf) 333 self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", incompat_license])
286 self.bbpath_ok = True
287 self.bbfiles_ok = True
288 334
289 self.current_command = self.REPARSE_FILES 335 def set_extra_config(self, extra_setting):
290 self.run_next_command() 336 for key in extra_setting.keys():
337 value = extra_setting[key]
338 self.server.runCommand(["setVariable", key, value])
339
340 def request_package_info_async(self):
341 self.commands_async.append(self.GENERATE_PACKAGEINFO)
342 self.run_next_command(self.POPULATE_PACKAGEINFO)
343
344 def generate_recipes(self):
345 self.commands_async.append(self.PARSE_CONFIG)
346 self.commands_async.append(self.GENERATE_TGTS)
347 self.run_next_command(self.GENERATE_RECIPES)
348
349 def generate_packages(self, tgts):
350 targets = []
351 targets.extend(tgts)
352 self.recipe_queue = targets
353 self.commands_async.append(self.PARSE_CONFIG)
354 self.commands_async.append(self.PARSE_BBFILES)
355 self.commands_async.append(self.BUILD_TARGET_RECIPES)
356 self.run_next_command(self.GENERATE_PACKAGES)
357
358 def generate_image(self, tgts, toolchain_build=False):
359 self.package_queue = tgts
360 self.toolchain_build = toolchain_build
361 self.commands_async.append(self.PARSE_CONFIG)
362 self.commands_async.append(self.PARSE_BBFILES)
363 self.commands_async.append(self.BUILD_TARGET_IMAGE)
364 self.run_next_command(self.GENERATE_IMAGE)
365
366 def build_failed_async(self):
367 self.initcmd = None
368 self.commands_async = []
369 self.building = False
291 370
292 def cancel_build(self, force=False): 371 def cancel_build(self, force=False):
293 if force: 372 if force:
@@ -298,46 +377,83 @@ class HobHandler(gobject.GObject):
298 # leave the workdir in a usable state 377 # leave the workdir in a usable state
299 self.server.runCommand(["stateShutdown"]) 378 self.server.runCommand(["stateShutdown"])
300 379
301 def set_incompatible_license(self, incompatible): 380 def reset_server(self):
302 self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", incompatible]) 381 self.server.runCommand(["resetCooker"])
303 382
304 def toggle_toolchain(self, enabled): 383 def reset_build(self):
305 if self.build_toolchain != enabled: 384 self.build.reset()
306 self.build_toolchain = enabled 385
307 386 def get_parameters(self):
308 def toggle_toolchain_headers(self, enabled): 387 # retrieve the parameters from bitbake
309 if self.build_toolchain_headers != enabled: 388 params = {}
310 self.build_toolchain_headers = enabled 389 params["core_base"] = self.server.runCommand(["getVariable", "COREBASE"]) or ""
311 390 hob_layer = params["core_base"] + "/meta-hob"
312 def set_fstypes(self, fstypes): 391 params["layer"] = (self.server.runCommand(["getVariable", "BBLAYERS"]) or "") + " " + hob_layer
313 self.server.runCommand(["setVariable", "IMAGE_FSTYPES", fstypes]) 392 params["dldir"] = self.server.runCommand(["getVariable", "DL_DIR"]) or ""
314 393 params["machine"] = self.server.runCommand(["getVariable", "MACHINE"]) or ""
315 def add_image_output_type(self, output_type): 394 params["distro"] = self.server.runCommand(["getVariable", "DISTRO"]) or "defaultsetup"
316 if output_type not in self.image_output_types: 395 params["pclass"] = self.server.runCommand(["getVariable", "PACKAGE_CLASSES"]) or ""
317 self.image_output_types.append(output_type) 396 params["sstatedir"] = self.server.runCommand(["getVariable", "SSTATE_DIR"]) or ""
318 fstypes = " ".join(self.image_output_types).lstrip(" ") 397 params["sstatemirror"] = self.server.runCommand(["getVariable", "SSTATE_MIRROR"]) or ""
319 self.set_fstypes(fstypes) 398
320 return self.image_output_types 399 num_threads = self.server.runCommand(["getCpuCount"])
321 400 if not num_threads:
322 def remove_image_output_type(self, output_type): 401 num_threads = 1
323 if output_type in self.image_output_types: 402 max_threads = 65536
324 ind = self.image_output_types.index(output_type) 403 else:
325 self.image_output_types.pop(ind) 404 num_threads = int(num_threads)
326 fstypes = " ".join(self.image_output_types).lstrip(" ") 405 max_threads = 16 * num_threads
327 self.set_fstypes(fstypes) 406 params["max_threads"] = max_threads
328 return self.image_output_types 407
329 408 bbthread = self.server.runCommand(["getVariable", "BB_NUMBER_THREADS"])
330 def get_image_deploy_dir(self): 409 if not bbthread:
331 return self.image_out_dir 410 bbthread = num_threads
332 411 else:
333 def make_temp_dir(self): 412 bbthread = int(bbthread)
334 bb.utils.mkdirhier(self.image_dir) 413 params["bbthread"] = bbthread
335 414
336 def remove_temp_dir(self): 415 pmake = self.server.runCommand(["getVariable", "PARALLEL_MAKE"])
337 bb.utils.remove(self.image_dir, True) 416 if not pmake:
338 417 pmake = num_threads
339 def get_temp_recipe_path(self, name): 418 elif isinstance(pmake, int):
340 timestamp = datetime.date.today().isoformat() 419 pass
341 image_file = "hob-%s-variant-%s.bb" % (name, timestamp) 420 else:
342 recipepath = os.path.join(self.image_dir, image_file) 421 pmake = int(pmake.lstrip("-j "))
343 return recipepath 422 params["pmake"] = pmake
423
424 image_addr = self.server.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or ""
425 if self.server_addr:
426 image_addr = "http://" + self.server_addr + ":" + image_addr
427 params["image_addr"] = image_addr
428
429 image_extra_size = self.server.runCommand(["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"])
430 if not image_extra_size:
431 image_extra_size = 0
432 else:
433 image_extra_size = int(image_extra_size)
434 params["image_extra_size"] = image_extra_size
435
436 image_rootfs_size = self.server.runCommand(["getVariable", "IMAGE_ROOTFS_SIZE"])
437 if not image_rootfs_size:
438 image_rootfs_size = 0
439 else:
440 image_rootfs_size = int(image_rootfs_size)
441 params["image_rootfs_size"] = image_rootfs_size
442
443 image_overhead_factor = self.server.runCommand(["getVariable", "IMAGE_OVERHEAD_FACTOR"])
444 if not image_overhead_factor:
445 image_overhead_factor = 1
446 else:
447 image_overhead_factor = float(image_overhead_factor)
448 params['image_overhead_factor'] = image_overhead_factor
449
450 params["incompat_license"] = self.server.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or ""
451 params["sdk_machine"] = self.server.runCommand(["getVariable", "SDKMACHINE"]) or self.server.runCommand(["getVariable", "SDK_ARCH"]) or ""
452
453 #params["image_types"] = self.server.runCommand(["getVariable", "IMAGE_TYPES"]) or ""
454 params["image_fstypes"] = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"]) or ""
455 """
456 A workaround
457 """
458 params["image_types"] = "jffs2 sum.jffs2 cramfs ext2 ext2.gz ext2.bz2 ext3 ext3.gz ext2.lzma btrfs live squashfs squashfs-lzma ubi tar tar.gz tar.bz2 tar.xz cpio cpio.gz cpio.xz cpio.lzma"
459 return params
diff --git a/bitbake/lib/bb/ui/crumbs/hoblistmodel.py b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py
new file mode 100644
index 0000000000..227ae4b5de
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py
@@ -0,0 +1,765 @@
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# Authored by Shane Wang <shane.wang@intel.com>
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23import gtk
24import gobject
25
26#
27# PackageListModel
28#
29class PackageListModel(gtk.TreeStore):
30 """
31 This class defines an gtk.TreeStore subclass which will convert the output
32 of the bb.event.TargetsTreeGenerated event into a gtk.TreeStore whilst also
33 providing convenience functions to access gtk.TreeModel subclasses which
34 provide filtered views of the data.
35 """
36 (COL_NAME, COL_VER, COL_REV, COL_RNM, COL_SEC, COL_SUM, COL_RDEP, COL_RPROV, COL_SIZE, COL_BINB, COL_INC) = range(11)
37
38 __gsignals__ = {
39 "packagelist-populated" : (gobject.SIGNAL_RUN_LAST,
40 gobject.TYPE_NONE,
41 ()),
42 "package-selection-changed" : (gobject.SIGNAL_RUN_LAST,
43 gobject.TYPE_NONE,
44 ()),
45 }
46
47 def __init__(self):
48
49 self.contents = None
50 self.images = None
51 self.pkgs_size = 0
52 self.pn_path = {}
53 self.pkg_path = {}
54
55 gtk.TreeStore.__init__ (self,
56 gobject.TYPE_STRING,
57 gobject.TYPE_STRING,
58 gobject.TYPE_STRING,
59 gobject.TYPE_STRING,
60 gobject.TYPE_STRING,
61 gobject.TYPE_STRING,
62 gobject.TYPE_STRING,
63 gobject.TYPE_STRING,
64 gobject.TYPE_STRING,
65 gobject.TYPE_STRING,
66 gobject.TYPE_BOOLEAN)
67
68
69 """
70 Find the model path for the item_name
71 Returns the path in the model or None
72 """
73 def find_path_for_item(self, item_name):
74 if item_name not in self.pkg_path.keys():
75 return None
76 else:
77 return self.pkg_path[item_name]
78
79 def find_item_for_path(self, item_path):
80 return self[item_path][self.COL_NAME]
81
82 """
83 Helper function to determine whether an item is an item specified by filter
84 """
85 def tree_model_filter(self, model, it, filter):
86 for key in filter.keys():
87 if model.get_value(it, key) not in filter[key]:
88 return False
89
90 return True
91
92 """
93 Create, if required, and return a filtered gtk.TreeModelSort
94 containing only the items specified by filter
95 """
96 def tree_model(self, filter):
97 model = self.filter_new()
98 model.set_visible_func(self.tree_model_filter, filter)
99
100 sort = gtk.TreeModelSort(model)
101 sort.set_sort_column_id(PackageListModel.COL_NAME, gtk.SORT_ASCENDING)
102 sort.set_default_sort_func(None)
103 return sort
104
105 def convert_vpath_to_path(self, view_model, view_path):
106 # view_model is the model sorted
107 # get the path of the model filtered
108 filtered_model_path = view_model.convert_path_to_child_path(view_path)
109 # get the model filtered
110 filtered_model = view_model.get_model()
111 # get the path of the original model
112 path = filtered_model.convert_path_to_child_path(filtered_model_path)
113 return path
114
115 def convert_path_to_vpath(self, view_model, path):
116 name = self.find_item_for_path(path)
117 it = view_model.get_iter_first()
118 while it:
119 child_it = view_model.iter_children(it)
120 while child_it:
121 view_name = view_model.get_value(child_it, self.COL_NAME)
122 if view_name == name:
123 view_path = view_model.get_path(child_it)
124 return view_path
125 child_it = view_model.iter_next(child_it)
126 it = view_model.iter_next(it)
127 return None
128
129 """
130 The populate() function takes as input the data from a
131 bb.event.PackageInfo event and populates the package list.
132 Once the population is done it emits gsignal packagelist-populated
133 to notify any listeners that the model is ready
134 """
135 def populate(self, pkginfolist):
136 self.clear()
137 self.pkgs_size = 0
138 self.pn_path = {}
139 self.pkg_path = {}
140
141 for pkginfo in pkginfolist:
142 pn = pkginfo['PN']
143 pv = pkginfo['PV']
144 pr = pkginfo['PR']
145 if pn in self.pn_path.keys():
146 pniter = self.get_iter(self.pn_path[pn])
147 else:
148 pniter = self.append(None)
149 self.set(pniter, self.COL_NAME, pn + '-' + pv + '-' + pr,
150 self.COL_INC, False)
151 self.pn_path[pn] = self.get_path(pniter)
152
153 pkg = pkginfo['PKG']
154 pkgv = pkginfo['PKGV']
155 pkgr = pkginfo['PKGR']
156 pkgsize = pkginfo['PKGSIZE_%s' % pkg] if 'PKGSIZE_%s' % pkg in pkginfo.keys() else "0"
157 pkg_rename = pkginfo['PKG_%s' % pkg] if 'PKG_%s' % pkg in pkginfo.keys() else ""
158 section = pkginfo['SECTION_%s' % pkg] if 'SECTION_%s' % pkg in pkginfo.keys() else ""
159 summary = pkginfo['SUMMARY_%s' % pkg] if 'SUMMARY_%s' % pkg in pkginfo.keys() else ""
160 rdep = pkginfo['RDEPENDS_%s' % pkg] if 'RDEPENDS_%s' % pkg in pkginfo.keys() else ""
161 rrec = pkginfo['RRECOMMENDS_%s' % pkg] if 'RRECOMMENDS_%s' % pkg in pkginfo.keys() else ""
162 rprov = pkginfo['RPROVIDES_%s' % pkg] if 'RPROVIDES_%s' % pkg in pkginfo.keys() else ""
163
164 if 'ALLOW_EMPTY_%s' % pkg in pkginfo.keys():
165 allow_empty = pkginfo['ALLOW_EMPTY_%s' % pkg]
166 elif 'ALLOW_EMPTY' in pkginfo.keys():
167 allow_empty = pkginfo['ALLOW_EMPTY']
168 else:
169 allow_empty = ""
170
171 if pkgsize == "0" and not allow_empty:
172 continue
173
174 if len(pkgsize) > 3:
175 size = '%.1f' % (int(pkgsize)*1.0/1024) + ' MB'
176 else:
177 size = pkgsize + ' KB'
178
179 it = self.append(pniter)
180 self.pkg_path[pkg] = self.get_path(it)
181 self.set(it, self.COL_NAME, pkg, self.COL_VER, pkgv,
182 self.COL_REV, pkgr, self.COL_RNM, pkg_rename,
183 self.COL_SEC, section, self.COL_SUM, summary,
184 self.COL_RDEP, rdep + ' ' + rrec,
185 self.COL_RPROV, rprov, self.COL_SIZE, size,
186 self.COL_BINB, "", self.COL_INC, False)
187
188 self.emit("packagelist-populated")
189
190 """
191 Check whether the item at item_path is included or not
192 """
193 def path_included(self, item_path):
194 return self[item_path][self.COL_INC]
195
196 """
197 Update the model, send out the notification.
198 """
199 def selection_change_notification(self):
200 self.emit("package-selection-changed")
201
202 """
203 Mark a certain package as selected.
204 All its dependencies are marked as selected.
205 The recipe provides the package is marked as selected.
206 If user explicitly selects a recipe, all its providing packages are selected
207 """
208 def include_item(self, item_path, binb=""):
209 if self.path_included(item_path):
210 return
211
212 item_name = self[item_path][self.COL_NAME]
213 item_rdep = self[item_path][self.COL_RDEP]
214
215 self[item_path][self.COL_INC] = True
216
217 self.selection_change_notification()
218
219 it = self.get_iter(item_path)
220
221 # If user explicitly selects a recipe, all its providing packages are selected.
222 if not self[item_path][self.COL_VER] and binb == "User Selected":
223 child_it = self.iter_children(it)
224 while child_it:
225 child_path = self.get_path(child_it)
226 child_included = self.path_included(child_path)
227 if not child_included:
228 self.include_item(child_path, binb="User Selected")
229 child_it = self.iter_next(child_it)
230 return
231
232 # The recipe provides the package is also marked as selected
233 parent_it = self.iter_parent(it)
234 if parent_it:
235 parent_path = self.get_path(parent_it)
236 self[parent_path][self.COL_INC] = True
237
238 item_bin = self[item_path][self.COL_BINB].split(', ')
239 if binb and not binb in item_bin:
240 item_bin.append(binb)
241 self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
242
243 if item_rdep:
244 # Ensure all of the items deps are included and, where appropriate,
245 # add this item to their COL_BINB
246 for dep in item_rdep.split(" "):
247 if dep.startswith('('):
248 continue
249 # If the contents model doesn't already contain dep, add it
250 dep_path = self.find_path_for_item(dep)
251 if not dep_path:
252 continue
253 dep_included = self.path_included(dep_path)
254
255 if dep_included and not dep in item_bin:
256 # don't set the COL_BINB to this item if the target is an
257 # item in our own COL_BINB
258 dep_bin = self[dep_path][self.COL_BINB].split(', ')
259 if not item_name in dep_bin:
260 dep_bin.append(item_name)
261 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
262 elif not dep_included:
263 self.include_item(dep_path, binb=item_name)
264
265 """
266 Mark a certain package as de-selected.
267 All other packages that depends on this package are marked as de-selected.
268 If none of the packages provided by the recipe, the recipe should be marked as de-selected.
269 If user explicitly de-select a recipe, all its providing packages are de-selected.
270 """
271 def exclude_item(self, item_path):
272 if not self.path_included(item_path):
273 return
274
275 self[item_path][self.COL_INC] = False
276
277 self.selection_change_notification()
278
279 item_name = self[item_path][self.COL_NAME]
280 item_rdep = self[item_path][self.COL_RDEP]
281 it = self.get_iter(item_path)
282
283 # If user explicitly de-select a recipe, all its providing packages are de-selected.
284 if not self[item_path][self.COL_VER]:
285 child_it = self.iter_children(it)
286 while child_it:
287 child_path = self.get_path(child_it)
288 child_included = self[child_path][self.COL_INC]
289 if child_included:
290 self.exclude_item(child_path)
291 child_it = self.iter_next(child_it)
292 return
293
294 # If none of the packages provided by the recipe, the recipe should be marked as de-selected.
295 parent_it = self.iter_parent(it)
296 peer_iter = self.iter_children(parent_it)
297 enabled = 0
298 while peer_iter:
299 peer_path = self.get_path(peer_iter)
300 if self[peer_path][self.COL_INC]:
301 enabled = 1
302 break
303 peer_iter = self.iter_next(peer_iter)
304 if not enabled:
305 parent_path = self.get_path(parent_it)
306 self[parent_path][self.COL_INC] = False
307
308 # All packages that depends on this package are de-selected.
309 if item_rdep:
310 for dep in item_rdep.split(" "):
311 if dep.startswith('('):
312 continue
313 dep_path = self.find_path_for_item(dep)
314 if not dep_path:
315 continue
316 dep_bin = self[dep_path][self.COL_BINB].split(', ')
317 if item_name in dep_bin:
318 dep_bin.remove(item_name)
319 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
320
321 item_bin = self[item_path][self.COL_BINB].split(', ')
322 if item_bin:
323 for binb in item_bin:
324 binb_path = self.find_path_for_item(binb)
325 if not binb_path:
326 continue
327 self.exclude_item(binb_path)
328
329 """
330 Package model may be incomplete, therefore when calling the
331 set_selected_packages(), some packages will not be set included.
332 Return the un-set packages list.
333 """
334 def set_selected_packages(self, packagelist):
335 left = []
336 for pn in packagelist:
337 if pn in self.pkg_path.keys():
338 path = self.pkg_path[pn]
339 self.include_item(item_path=path,
340 binb="User Selected")
341 else:
342 left.append(pn)
343
344 return left
345
346 def get_selected_packages(self):
347 packagelist = []
348
349 it = self.get_iter_first()
350 while it:
351 child_it = self.iter_children(it)
352 while child_it:
353 if self.get_value(child_it, self.COL_INC):
354 name = self.get_value(child_it, self.COL_NAME)
355 packagelist.append(name)
356 child_it = self.iter_next(child_it)
357 it = self.iter_next(it)
358
359 return packagelist
360
361 """
362 Return the selected package size, unit is KB.
363 """
364 def get_packages_size(self):
365 packages_size = 0
366 it = self.get_iter_first()
367 while it:
368 child_it = self.iter_children(it)
369 while child_it:
370 if self.get_value(child_it, self.COL_INC):
371 str_size = self.get_value(child_it, self.COL_SIZE)
372 if not str_size:
373 continue
374
375 unit = str_size.split()
376 if unit[1] == 'MB':
377 size = float(unit[0])*1024
378 else:
379 size = float(unit[0])
380 packages_size += size
381
382 child_it = self.iter_next(child_it)
383 it = self.iter_next(it)
384 return "%f" % packages_size
385
386 """
387 Empty self.contents by setting the include of each entry to None
388 """
389 def reset(self):
390 self.pkgs_size = 0
391 it = self.get_iter_first()
392 while it:
393 self.set(it, self.COL_INC, False)
394 child_it = self.iter_children(it)
395 while child_it:
396 self.set(child_it,
397 self.COL_INC, False,
398 self.COL_BINB, "")
399 child_it = self.iter_next(child_it)
400 it = self.iter_next(it)
401
402 self.selection_change_notification()
403
404#
405# RecipeListModel
406#
407class RecipeListModel(gtk.ListStore):
408 """
409 This class defines an gtk.ListStore subclass which will convert the output
410 of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
411 providing convenience functions to access gtk.TreeModel subclasses which
412 provide filtered views of the data.
413 """
414 (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_INSTALL, COL_PN) = range(11)
415
416 __dummy_image__ = "--select a base image--"
417
418 __gsignals__ = {
419 "recipelist-populated" : (gobject.SIGNAL_RUN_LAST,
420 gobject.TYPE_NONE,
421 ()),
422 "recipe-selection-changed" : (gobject.SIGNAL_RUN_LAST,
423 gobject.TYPE_NONE,
424 ()),
425 }
426
427 """
428 """
429 def __init__(self):
430 gtk.ListStore.__init__ (self,
431 gobject.TYPE_STRING,
432 gobject.TYPE_STRING,
433 gobject.TYPE_STRING,
434 gobject.TYPE_STRING,
435 gobject.TYPE_STRING,
436 gobject.TYPE_STRING,
437 gobject.TYPE_STRING,
438 gobject.TYPE_BOOLEAN,
439 gobject.TYPE_BOOLEAN,
440 gobject.TYPE_STRING,
441 gobject.TYPE_STRING,
442 gobject.TYPE_STRING)
443
444 """
445 Find the model path for the item_name
446 Returns the path in the model or None
447 """
448 def find_path_for_item(self, item_name):
449 if self.non_target_name(item_name) or item_name not in self.pn_path.keys():
450 return None
451 else:
452 return self.pn_path[item_name]
453
454 def find_item_for_path(self, item_path):
455 return self[item_path][self.COL_NAME]
456
457 """
458 Helper method to determine whether name is a target pn
459 """
460 def non_target_name(self, name):
461 if name and ('-native' in name):
462 return True
463 return False
464
465 """
466 Helper function to determine whether an item is an item specified by filter
467 """
468 def tree_model_filter(self, model, it, filter):
469 name = model.get_value(it, self.COL_NAME)
470 if self.non_target_name(name):
471 return False
472
473 for key in filter.keys():
474 if model.get_value(it, key) not in filter[key]:
475 return False
476
477 return True
478
479 def sort_func(self, model, iter1, iter2):
480 val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
481 val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
482 return val1 > val2
483
484 """
485 Create, if required, and return a filtered gtk.TreeModelSort
486 containing only the items which are items specified by filter
487 """
488 def tree_model(self, filter):
489 model = self.filter_new()
490 model.set_visible_func(self.tree_model_filter, filter)
491
492 sort = gtk.TreeModelSort(model)
493 sort.set_default_sort_func(self.sort_func)
494 return sort
495
496 def convert_vpath_to_path(self, view_model, view_path):
497 filtered_model_path = view_model.convert_path_to_child_path(view_path)
498 filtered_model = view_model.get_model()
499
500 # get the path of the original model
501 path = filtered_model.convert_path_to_child_path(filtered_model_path)
502 return path
503
504 def convert_path_to_vpath(self, view_model, path):
505 it = view_model.get_iter_first()
506 while it:
507 name = self.find_item_for_path(path)
508 view_name = view_model.get_value(it, RecipeListModel.COL_NAME)
509 if view_name == name:
510 view_path = view_model.get_path(it)
511 return view_path
512 it = view_model.iter_next(it)
513 return None
514
515 def map_runtime(self, event_model, runtime, rdep_type, name):
516 if rdep_type not in ['pkg', 'pn'] or runtime not in ['rdepends', 'rrecs']:
517 return
518 package_depends = event_model["%s-%s" % (runtime, rdep_type)].get(name, [])
519 pn_depends = []
520 for package_depend in package_depends:
521 if 'task-' not in package_depend and package_depend in event_model["packages"].keys():
522 pn_depends.append(event_model["packages"][package_depend]["pn"])
523 else:
524 pn_depends.append(package_depend)
525 return list(set(pn_depends))
526
527 def subpkg_populate(self, event_model, pkg, desc, lic, group, atype, pn):
528 pn_depends = self.map_runtime(event_model, "rdepends", "pkg", pkg)
529 pn_depends += self.map_runtime(event_model, "rrecs", "pkg", pkg)
530 self.set(self.append(), self.COL_NAME, pkg, self.COL_DESC, desc,
531 self.COL_LIC, lic, self.COL_GROUP, group,
532 self.COL_DEPS, " ".join(pn_depends), self.COL_BINB, "",
533 self.COL_TYPE, atype, self.COL_INC, False,
534 self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, pn)
535
536 """
537 The populate() function takes as input the data from a
538 bb.event.TargetsTreeGenerated event and populates the RecipeList.
539 Once the population is done it emits gsignal recipelist-populated
540 to notify any listeners that the model is ready
541 """
542 def populate(self, event_model):
543 # First clear the model, in case repopulating
544 self.clear()
545
546 # dummy image for prompt
547 self.set(self.append(), self.COL_NAME, self.__dummy_image__,
548 self.COL_DESC, "",
549 self.COL_LIC, "", self.COL_GROUP, "",
550 self.COL_DEPS, "", self.COL_BINB, "",
551 self.COL_TYPE, "image", self.COL_INC, False,
552 self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, self.__dummy_image__)
553
554 for item in event_model["pn"]:
555 name = item
556 desc = event_model["pn"][item]["description"]
557 lic = event_model["pn"][item]["license"]
558 group = event_model["pn"][item]["section"]
559 install = []
560
561 if ('task-' in name):
562 if ('lib32-' in name or 'lib64-' in name):
563 atype = 'mltask'
564 else:
565 atype = 'task'
566 for pkg in event_model["pn"][name]["packages"]:
567 self.subpkg_populate(event_model, pkg, desc, lic, group, atype, name)
568 continue
569
570 elif ('-image-' in name):
571 atype = 'image'
572 depends = event_model["depends"].get(item, [])
573 rdepends = self.map_runtime(event_model, 'rdepends', 'pn', name)
574 depends = depends + rdepends
575 install = event_model["rdepends-pn"].get(item, [])
576
577 elif ('meta-' in name):
578 atype = 'toolchain'
579
580 elif (name == 'dummy-image' or name == 'dummy-toolchain'):
581 atype = 'dummy'
582
583 else:
584 if ('lib32-' in name or 'lib64-' in name):
585 atype = 'mlrecipe'
586 else:
587 atype = 'recipe'
588 depends = event_model["depends"].get(item, [])
589 depends += self.map_runtime(event_model, 'rdepends', 'pn', item)
590 for pkg in event_model["pn"][name]["packages"]:
591 depends += self.map_runtime(event_model, 'rdepends', 'pkg', item)
592 depends += self.map_runtime(event_model, 'rrecs', 'pkg', item)
593
594 self.set(self.append(), self.COL_NAME, item, self.COL_DESC, desc,
595 self.COL_LIC, lic, self.COL_GROUP, group,
596 self.COL_DEPS, " ".join(depends), self.COL_BINB, "",
597 self.COL_TYPE, atype, self.COL_INC, False,
598 self.COL_IMG, False, self.COL_INSTALL, " ".join(install), self.COL_PN, item)
599
600 self.pn_path = {}
601 it = self.get_iter_first()
602 while it:
603 pn = self.get_value(it, self.COL_NAME)
604 path = self.get_path(it)
605 self.pn_path[pn] = path
606 it = self.iter_next(it)
607
608 self.emit("recipelist-populated")
609
610 """
611 Update the model, send out the notification.
612 """
613 def selection_change_notification(self):
614 self.emit("recipe-selection-changed")
615
616 def path_included(self, item_path):
617 return self[item_path][self.COL_INC]
618
619 """
620 Append a certain image into the combobox
621 """
622 def image_list_append(self, name, deps, install):
623 # check whether a certain image is there
624 if not name or self.find_path_for_item(name):
625 return
626 it = self.append()
627 self.set(it, self.COL_NAME, name, self.COL_DESC, "",
628 self.COL_LIC, "", self.COL_GROUP, "",
629 self.COL_DEPS, deps, self.COL_BINB, "",
630 self.COL_TYPE, "image", self.COL_INC, False,
631 self.COL_IMG, False, self.COL_INSTALL, install,
632 self.COL_PN, name)
633 self.pn_path[name] = self.get_path(it)
634
635 """
636 Add this item, and any of its dependencies, to the image contents
637 """
638 def include_item(self, item_path, binb="", image_contents=False):
639 if self.path_included(item_path):
640 return
641
642 item_name = self[item_path][self.COL_NAME]
643 item_deps = self[item_path][self.COL_DEPS]
644
645 self[item_path][self.COL_INC] = True
646 self.selection_change_notification()
647
648 item_bin = self[item_path][self.COL_BINB].split(', ')
649 if binb and not binb in item_bin:
650 item_bin.append(binb)
651 self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
652
653 # We want to do some magic with things which are brought in by the
654 # base image so tag them as so
655 if image_contents:
656 self[item_path][self.COL_IMG] = True
657
658 if item_deps:
659 # Ensure all of the items deps are included and, where appropriate,
660 # add this item to their COL_BINB
661 for dep in item_deps.split(" "):
662 # If the contents model doesn't already contain dep, add it
663 dep_path = self.find_path_for_item(dep)
664 if not dep_path:
665 continue
666 dep_included = self.path_included(dep_path)
667
668 if dep_included and not dep in item_bin:
669 # don't set the COL_BINB to this item if the target is an
670 # item in our own COL_BINB
671 dep_bin = self[dep_path][self.COL_BINB].split(', ')
672 if not item_name in dep_bin:
673 dep_bin.append(item_name)
674 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
675 elif not dep_included:
676 self.include_item(dep_path, binb=item_name, image_contents=image_contents)
677
678 def exclude_item(self, item_path):
679 if not self.path_included(item_path):
680 return
681
682 self[item_path][self.COL_INC] = False
683
684 self.selection_change_notification()
685
686 item_name = self[item_path][self.COL_NAME]
687 item_deps = self[item_path][self.COL_DEPS]
688 if item_deps:
689 for dep in item_deps.split(" "):
690 dep_path = self.find_path_for_item(dep)
691 if not dep_path:
692 continue
693 dep_bin = self[dep_path][self.COL_BINB].split(', ')
694 if item_name in dep_bin:
695 dep_bin.remove(item_name)
696 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
697
698 item_bin = self[item_path][self.COL_BINB].split(', ')
699 if item_bin:
700 for binb in item_bin:
701 binb_path = self.find_path_for_item(binb)
702 if not binb_path:
703 continue
704 self.exclude_item(binb_path)
705
706 def reset(self):
707 it = self.get_iter_first()
708 while it:
709 self.set(it,
710 self.COL_INC, False,
711 self.COL_BINB, "",
712 self.COL_IMG, False)
713 it = self.iter_next(it)
714
715 self.selection_change_notification()
716
717 """
718 Returns two lists. One of user selected recipes and the other containing
719 all selected recipes
720 """
721 def get_selected_recipes(self):
722 allrecipes = []
723 userrecipes = []
724
725 it = self.get_iter_first()
726 while it:
727 if self.get_value(it, self.COL_INC):
728 name = self.get_value(it, self.COL_PN)
729 type = self.get_value(it, self.COL_TYPE)
730 if type != "image":
731 allrecipes.append(name)
732 sel = "User Selected" in self.get_value(it, self.COL_BINB)
733 if sel:
734 userrecipes.append(name)
735 it = self.iter_next(it)
736
737 return list(set(userrecipes)), list(set(allrecipes))
738
739 def set_selected_recipes(self, recipelist):
740 for pn in recipelist:
741 if pn in self.pn_path.keys():
742 path = self.pn_path[pn]
743 self.include_item(item_path=path,
744 binb="User Selected")
745
746 def get_selected_image(self):
747 it = self.get_iter_first()
748 while it:
749 if self.get_value(it, self.COL_INC):
750 name = self.get_value(it, self.COL_PN)
751 type = self.get_value(it, self.COL_TYPE)
752 if type == "image":
753 sel = "User Selected" in self.get_value(it, self.COL_BINB)
754 if sel:
755 return name
756 it = self.iter_next(it)
757 return None
758
759 def set_selected_image(self, img):
760 if img == None:
761 return
762 path = self.find_path_for_item(img)
763 self.include_item(item_path=path,
764 binb="User Selected",
765 image_contents=True)
diff --git a/bitbake/lib/bb/ui/crumbs/hobpages.py b/bitbake/lib/bb/ui/crumbs/hobpages.py
new file mode 100755
index 0000000000..be76d0db27
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobpages.py
@@ -0,0 +1,87 @@
1#!/usr/bin/env python
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2012 Intel Corporation
6#
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8# Authored by Shane Wang <shane.wang@intel.com>
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23import gtk
24from bb.ui.crumbs.hobcolor import HobColors
25from bb.ui.crumbs.hobwidget import hwc
26
27#
28# HobPage: the super class for all Hob-related pages
29#
30class HobPage (gtk.VBox):
31
32 def __init__(self, builder, title = None):
33 super(HobPage, self).__init__(False, 0)
34 self.builder = builder
35 self.builder_width, self.builder_height = self.builder.size_request()
36
37 if title == None:
38 self.title = "HOB -- Image Creator"
39 else:
40 self.title = title
41
42 self.box_group_area = gtk.VBox(False, 15)
43 self.box_group_area.set_size_request(self.builder_width - 73 - 73, self.builder_height - 88 - 15 - 15)
44 self.group_align = gtk.Alignment(xalign = 0, yalign=0.5, xscale=1, yscale=1)
45 self.group_align.set_padding(15, 15, 73, 73)
46 self.group_align.add(self.box_group_area)
47 self.box_group_area.set_homogeneous(False)
48
49
50 def add_onto_top_bar(self, widget = None, padding = 0):
51 # the top button occupies 1/7 of the page height
52 # setup an event box
53 eventbox = gtk.EventBox()
54 style = eventbox.get_style().copy()
55 style.bg[gtk.STATE_NORMAL] = eventbox.get_colormap().alloc_color(HobColors.LIGHT_GRAY, False, False)
56 eventbox.set_style(style)
57 eventbox.set_size_request(-1, 88)
58
59 hbox = gtk.HBox()
60
61 label = gtk.Label()
62 label.set_markup("<span font_desc=\'14\'>%s</span>" % self.title)
63 hbox.pack_start(label, expand=False, fill=False, padding=20)
64
65 if widget != None:
66 # add the widget in the event box
67 hbox.pack_end(widget, expand=False, fill=False, padding=padding)
68 eventbox.add(hbox)
69
70 return eventbox
71
72 def span_tag(self, px="12px", weight="normal", forground="#1c1c1c"):
73 span_tag = "weight=\'%s\' foreground=\'%s\' font_desc=\'%s\'" % (weight, forground, px)
74 return span_tag
75
76 def append_toolbar_button(self, toolbar, buttonname, icon_disp, icon_hovor, tip, cb):
77 # Create a button and append it on the toolbar according to button name
78 icon = gtk.Image()
79 icon_display = icon_disp
80 icon_hover = icon_hovor
81 pix_buffer = gtk.gdk.pixbuf_new_from_file(icon_display)
82 icon.set_from_pixbuf(pix_buffer)
83 tip_text = tip
84 button = toolbar.append_element(gtk.TOOLBAR_CHILD_RADIOBUTTON, None,
85 buttonname, tip_text, "Private text", icon,
86 cb, None)
87 return toolbar, button
diff --git a/bitbake/lib/bb/ui/crumbs/hobprefs.py b/bitbake/lib/bb/ui/crumbs/hobprefs.py
deleted file mode 100644
index 3f6f128808..0000000000
--- a/bitbake/lib/bb/ui/crumbs/hobprefs.py
+++ /dev/null
@@ -1,335 +0,0 @@
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#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import glib
23from bb.ui.crumbs.configurator import Configurator
24
25class HobPrefs(gtk.Dialog):
26 """
27 """
28 def empty_combo_text(self, combo_text):
29 model = combo_text.get_model()
30 if model:
31 model.clear()
32
33 def output_type_toggled_cb(self, check, handler):
34 ot = check.get_label()
35 enabled = check.get_active()
36 if enabled:
37 self.selected_image_types = handler.add_image_output_type(ot)
38 else:
39 self.selected_image_types = handler.remove_image_output_type(ot)
40
41 self.configurator.setConfVar('IMAGE_FSTYPES', "%s" % " ".join(self.selected_image_types).lstrip(" "))
42 self.reload_required = True
43
44 def sdk_machine_combo_changed_cb(self, combo, handler):
45 sdk_mach = combo.get_active_text()
46 if sdk_mach != self.curr_sdk_mach:
47 self.curr_sdk_mach = sdk_mach
48 self.configurator.setConfVar('SDKMACHINE', sdk_mach)
49 handler.set_sdk_machine(sdk_mach)
50
51 def update_sdk_machines(self, handler, sdk_machines):
52 active = 0
53 # disconnect the signal handler before updating the combo model
54 if self.sdk_machine_handler_id:
55 self.sdk_machine_combo.disconnect(self.sdk_machine_handler_id)
56 self.sdk_machine_handler_id = None
57
58 self.empty_combo_text(self.sdk_machine_combo)
59 for sdk_machine in sdk_machines:
60 self.sdk_machine_combo.append_text(sdk_machine)
61 if sdk_machine == self.curr_sdk_mach:
62 self.sdk_machine_combo.set_active(active)
63 active = active + 1
64
65 self.sdk_machine_handler_id = self.sdk_machine_combo.connect("changed", self.sdk_machine_combo_changed_cb, handler)
66
67 def distro_combo_changed_cb(self, combo, handler):
68 distro = combo.get_active_text()
69 if distro != self.curr_distro:
70 self.curr_distro = distro
71 self.configurator.setConfVar('DISTRO', distro)
72 handler.set_distro(distro)
73 self.reload_required = True
74
75 def update_distros(self, handler, distros):
76 active = 0
77 # disconnect the signal handler before updating combo model
78 if self.distro_handler_id:
79 self.distro_combo.disconnect(self.distro_handler_id)
80 self.distro_handler_id = None
81
82 self.empty_combo_text(self.distro_combo)
83 for distro in distros:
84 self.distro_combo.append_text(distro)
85 if distro == self.curr_distro:
86 self.distro_combo.set_active(active)
87 active = active + 1
88
89 self.distro_handler_id = self.distro_combo.connect("changed", self.distro_combo_changed_cb, handler)
90
91 def package_format_combo_changed_cb(self, combo, handler):
92 package_format = combo.get_active_text()
93 if package_format != self.curr_package_format:
94 self.curr_package_format = package_format
95 self.configurator.setConfVar('PACKAGE_CLASSES', 'package_%s' % package_format)
96 handler.set_package_format(package_format)
97 self.reload_required = True
98
99 def update_package_formats(self, handler, formats):
100 active = 0
101 # disconnect the signal handler before updating the model
102 if self.package_handler_id:
103 self.package_combo.disconnect(self.package_handler_id)
104 self.package_handler_id = None
105
106 self.empty_combo_text(self.package_combo)
107 for format in formats:
108 self.package_combo.append_text(format)
109 if format == self.curr_package_format:
110 self.package_combo.set_active(active)
111 active = active + 1
112
113 self.package_handler_id = self.package_combo.connect("changed", self.package_format_combo_changed_cb, handler)
114
115 def include_gplv3_cb(self, toggle):
116 excluded = toggle.get_active()
117 orig_incompatible = self.configurator.getConfVar('INCOMPATIBLE_LICENSE')
118 new_incompatible = ""
119 if excluded:
120 if not orig_incompatible:
121 new_incompatible = "GPLv3"
122 elif not orig_incompatible.find('GPLv3'):
123 new_incompatible = "%s GPLv3" % orig_incompatible
124 else:
125 new_incompatible = orig_incompatible.replace('GPLv3', '')
126
127 if new_incompatible != orig_incompatible:
128 self.handler.set_incompatible_license(new_incompatible)
129 self.configurator.setConfVar('INCOMPATIBLE_LICENSE', new_incompatible)
130 self.reload_required = True
131
132 def change_bb_threads_cb(self, spinner):
133 val = spinner.get_value_as_int()
134 self.handler.set_bbthreads(val)
135 self.configurator.setConfVar('BB_NUMBER_THREADS', val)
136
137 def change_make_threads_cb(self, spinner):
138 val = spinner.get_value_as_int()
139 self.handler.set_pmake(val)
140 self.configurator.setConfVar('PARALLEL_MAKE', "-j %s" % val)
141
142 def toggle_toolchain_cb(self, check):
143 enabled = check.get_active()
144 toolchain = '0'
145 if enabled:
146 toolchain = '1'
147 self.handler.toggle_toolchain(enabled)
148 self.configurator.setConfVar('HOB_BUILD_TOOLCHAIN', toolchain)
149
150 def toggle_headers_cb(self, check):
151 enabled = check.get_active()
152 headers = '0'
153 if enabled:
154 headers = '1'
155 self.handler.toggle_toolchain_headers(enabled)
156 self.configurator.setConfVar('HOB_BUILD_TOOLCHAIN_HEADERS', headers)
157
158 def set_parent_window(self, parent):
159 self.set_transient_for(parent)
160
161 def write_changes(self):
162 self.configurator.writeConf()
163
164 def prefs_response_cb(self, dialog, response):
165 if self.reload_required:
166 glib.idle_add(self.handler.reload_data)
167 self.reload_required = False
168
169 def __init__(self, configurator, handler, curr_sdk_mach, curr_distro, pclass,
170 pmake, bbthread, selected_image_types, all_image_types,
171 gplv3disabled, build_toolchain, build_toolchain_headers):
172 """
173 """
174 gtk.Dialog.__init__(self, "Preferences", None,
175 gtk.DIALOG_DESTROY_WITH_PARENT,
176 (gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
177
178 self.set_border_width(6)
179 self.vbox.set_property("spacing", 12)
180 self.action_area.set_property("spacing", 12)
181 self.action_area.set_property("border-width", 6)
182
183 self.handler = handler
184 self.configurator = configurator
185
186 self.curr_sdk_mach = curr_sdk_mach
187 self.curr_distro = curr_distro
188 self.curr_package_format = pclass
189 self.pmake = pmake
190 self.bbthread = bbthread
191 self.selected_image_types = selected_image_types.split(" ")
192 self.gplv3disabled = gplv3disabled
193 self.build_toolchain = build_toolchain
194 self.build_toolchain_headers = build_toolchain_headers
195
196 self.reload_required = False
197 self.distro_handler_id = None
198 self.sdk_machine_handler_id = None
199 self.package_handler_id = None
200
201 left = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
202 right = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
203
204 label = gtk.Label()
205 label.set_markup("<b>Policy</b>")
206 label.show()
207 frame = gtk.Frame()
208 frame.set_label_widget(label)
209 frame.set_shadow_type(gtk.SHADOW_NONE)
210 frame.show()
211 self.vbox.pack_start(frame)
212 pbox = gtk.VBox(False, 12)
213 pbox.show()
214 frame.add(pbox)
215 hbox = gtk.HBox(False, 12)
216 hbox.show()
217 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
218 # Distro selector
219 label = gtk.Label("Distribution:")
220 label.show()
221 hbox.pack_start(label, expand=False, fill=False, padding=6)
222 self.distro_combo = gtk.combo_box_new_text()
223 self.distro_combo.set_tooltip_text("Select the Yocto distribution you would like to use")
224 self.distro_combo.show()
225 hbox.pack_start(self.distro_combo, expand=False, fill=False, padding=6)
226 # Exclude GPLv3
227 check = gtk.CheckButton("Exclude GPLv3 packages")
228 check.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image")
229 check.show()
230 check.set_active(self.gplv3disabled)
231 check.connect("toggled", self.include_gplv3_cb)
232 hbox.pack_start(check, expand=False, fill=False, padding=6)
233 hbox = gtk.HBox(False, 12)
234 hbox.show()
235 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
236 # Package format selector
237 label = gtk.Label("Package format:")
238 label.show()
239 hbox.pack_start(label, expand=False, fill=False, padding=6)
240 self.package_combo = gtk.combo_box_new_text()
241 self.package_combo.set_tooltip_text("""The package format is that used in creation
242 of the root filesystem and also dictates the package manager used in your image""")
243 self.package_combo.show()
244 hbox.pack_start(self.package_combo, expand=False, fill=False, padding=6)
245 if all_image_types:
246 # Image output type selector
247 label = gtk.Label("Image output types:")
248 label.show()
249 hbox.pack_start(label, expand=False, fill=False, padding=6)
250 chk_cnt = 3
251 for it in all_image_types.split(" "):
252 chk_cnt = chk_cnt + 1
253 if chk_cnt % 6 == 0:
254 hbox = gtk.HBox(False, 12)
255 hbox.show()
256 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
257 chk = gtk.CheckButton(it)
258 if it in self.selected_image_types:
259 chk.set_active(True)
260 chk.set_tooltip_text("Build an %s image" % it)
261 chk.connect("toggled", self.output_type_toggled_cb, handler)
262 chk.show()
263 hbox.pack_start(chk, expand=False, fill=False, padding=3)
264 # BitBake
265 label = gtk.Label()
266 label.set_markup("<b>BitBake</b>")
267 label.show()
268 frame = gtk.Frame()
269 frame.set_label_widget(label)
270 frame.set_shadow_type(gtk.SHADOW_NONE)
271 frame.show()
272 self.vbox.pack_start(frame)
273 pbox = gtk.VBox(False, 12)
274 pbox.show()
275 frame.add(pbox)
276 hbox = gtk.HBox(False, 12)
277 hbox.show()
278 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
279 label = gtk.Label("BitBake threads:")
280 label.show()
281 # NOTE: may be a good idea in future to intelligently cap the maximum
282 # values but we need more data to make an educated decision, for now
283 # set a high maximum as a value for upper bounds is required by the
284 # gtk.Adjustment
285 spin_max = 30 # seems like a high enough arbitrary number
286 hbox.pack_start(label, expand=False, fill=False, padding=6)
287 bbadj = gtk.Adjustment(value=self.bbthread, lower=1, upper=spin_max, step_incr=1)
288 bbspinner = gtk.SpinButton(adjustment=bbadj, climb_rate=1, digits=0)
289 bbspinner.show()
290 bbspinner.connect("value-changed", self.change_bb_threads_cb)
291 hbox.pack_start(bbspinner, expand=False, fill=False, padding=6)
292 label = gtk.Label("Make threads:")
293 label.show()
294 hbox.pack_start(label, expand=False, fill=False, padding=6)
295 madj = gtk.Adjustment(value=self.pmake, lower=1, upper=spin_max, step_incr=1)
296 makespinner = gtk.SpinButton(adjustment=madj, climb_rate=1, digits=0)
297 makespinner.connect("value-changed", self.change_make_threads_cb)
298 makespinner.show()
299 hbox.pack_start(makespinner, expand=False, fill=False, padding=6)
300 # Toolchain
301 label = gtk.Label()
302 label.set_markup("<b>External Toolchain</b>")
303 label.show()
304 frame = gtk.Frame()
305 frame.set_label_widget(label)
306 frame.set_shadow_type(gtk.SHADOW_NONE)
307 frame.show()
308 self.vbox.pack_start(frame)
309 pbox = gtk.VBox(False, 12)
310 pbox.show()
311 frame.add(pbox)
312 hbox = gtk.HBox(False, 12)
313 hbox.show()
314 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
315 toolcheck = gtk.CheckButton("Build external development toolchain with image")
316 toolcheck.show()
317 toolcheck.set_active(self.build_toolchain)
318 toolcheck.connect("toggled", self.toggle_toolchain_cb)
319 hbox.pack_start(toolcheck, expand=False, fill=False, padding=6)
320 hbox = gtk.HBox(False, 12)
321 hbox.show()
322 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
323 label = gtk.Label("Toolchain host:")
324 label.show()
325 hbox.pack_start(label, expand=False, fill=False, padding=6)
326 self.sdk_machine_combo = gtk.combo_box_new_text()
327 self.sdk_machine_combo.set_tooltip_text("Select the host architecture of the external machine")
328 self.sdk_machine_combo.show()
329 hbox.pack_start(self.sdk_machine_combo, expand=False, fill=False, padding=6)
330 # headerscheck = gtk.CheckButton("Include development headers with toolchain")
331 # headerscheck.show()
332 # headerscheck.set_active(self.build_toolchain_headers)
333 # headerscheck.connect("toggled", self.toggle_headers_cb)
334 # hbox.pack_start(headerscheck, expand=False, fill=False, padding=6)
335 self.connect("response", self.prefs_response_cb)
diff --git a/bitbake/lib/bb/ui/crumbs/hobwidget.py b/bitbake/lib/bb/ui/crumbs/hobwidget.py
new file mode 100644
index 0000000000..890151ddd4
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobwidget.py
@@ -0,0 +1,805 @@
1# BitBake Graphical GTK User Interface
2#
3# Copyright (C) 2011-2012 Intel Corporation
4#
5# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
6# Authored by Shane Wang <shane.wang@intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23import os
24import os.path
25from bb.ui.crumbs.hobcolor import HobColors
26
27class hwc:
28
29 MAIN_WIN_WIDTH = 1024
30 MAIN_WIN_HEIGHT = 700
31
32class hic:
33
34 HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("ui/icons/"))
35
36 ICON_RCIPE_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_display.png'))
37 ICON_RCIPE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_hover.png'))
38 ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_display.png'))
39 ICON_PACKAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_hover.png'))
40 ICON_LAYERS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_display.png'))
41 ICON_LAYERS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_hover.png'))
42 ICON_TEMPLATES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('templates/templates_display.png'))
43 ICON_TEMPLATES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('templates/templates_hover.png'))
44 ICON_IMAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_display.png'))
45 ICON_IMAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_hover.png'))
46 ICON_SETTINGS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_display.png'))
47 ICON_SETTINGS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_hover.png'))
48 ICON_INFO_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png'))
49 ICON_INFO_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_hover.png'))
50 ICON_INDI_CONFIRM_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/confirmation.png'))
51 ICON_INDI_ERROR_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/error.png'))
52
53class HobWidget:
54 @classmethod
55 def resize_widget(cls, screen, widget, widget_width, widget_height):
56 screen_width, screen_height = screen.get_size_request()
57 ratio_height = screen_width * hwc.MAIN_WIN_HEIGHT/hwc.MAIN_WIN_WIDTH
58 if ratio_height < screen_height:
59 screen_height = ratio_height
60 widget_width = widget_width * screen_width/hwc.MAIN_WIN_WIDTH
61 widget_height = widget_height * screen_height/hwc.MAIN_WIN_HEIGHT
62 widget.set_size_request(widget_width, widget_height)
63
64 @classmethod
65 def _toggle_cb(cls, cell, path, model, column):
66 it = model.get_iter(path)
67 val = model.get_value(it, column)
68 val = not val
69 model.set(it, column, val)
70
71 @classmethod
72 def _pkgfmt_up_clicked_cb(cls, button, tree_selection):
73 (model, it) = tree_selection.get_selected()
74 if not it:
75 return
76 path = model.get_path(it)
77 if path[0] <= 0:
78 return
79
80 pre_it = model.get_iter_first()
81 if not pre_it:
82 return
83 else:
84 while model.iter_next(pre_it) :
85 if model.get_value(model.iter_next(pre_it), 1) != model.get_value(it, 1):
86 pre_it = model.iter_next(pre_it)
87 else:
88 break
89
90 cur_index = model.get_value(it, 0)
91 pre_index = cur_index
92 if pre_it:
93 model.set(pre_it, 0, pre_index)
94 cur_index = cur_index - 1
95 model.set(it, 0, cur_index)
96
97 @classmethod
98 def _pkgfmt_down_clicked_cb(cls, button, tree_selection):
99 (model, it) = tree_selection.get_selected()
100 if not it:
101 return
102 next_it = model.iter_next(it)
103 if not next_it:
104 return
105 cur_index = model.get_value(it, 0)
106 next_index = cur_index
107 model.set(next_it, 0, next_index)
108 cur_index = cur_index + 1
109 model.set(it, 0, cur_index)
110
111 @classmethod
112 def _tree_selection_changed_cb(cls, tree_selection, button1, button2):
113 (model, it) = tree_selection.get_selected()
114 inc = model.get_value(it, 2)
115 if inc:
116 button1.set_sensitive(True)
117 button2.set_sensitive(True)
118 else:
119 button1.set_sensitive(False)
120 button2.set_sensitive(False)
121
122 @classmethod
123 def _sort_func(cls, model, iter1, iter2, data):
124 val1 = model.get_value(iter1, 0)
125 val2 = model.get_value(iter2, 0)
126 inc1 = model.get_value(iter1, 2)
127 inc2 = model.get_value(iter2, 2)
128 if inc1 != inc2:
129 return inc2 - inc1
130 else:
131 return val1 - val2
132
133 @classmethod
134 def gen_pkgfmt_widget(cls, curr_package_format, all_package_format, tooltip=""):
135 pkgfmt_hbox = gtk.HBox(False, 15)
136
137 pkgfmt_store = gtk.ListStore(int, str, gobject.TYPE_BOOLEAN)
138 for format in curr_package_format.split():
139 pkgfmt_store.set(pkgfmt_store.append(), 1, format, 2, True)
140 for format in all_package_format:
141 if format not in curr_package_format:
142 pkgfmt_store.set(pkgfmt_store.append(), 1, format, 2, False)
143 pkgfmt_tree = gtk.TreeView(pkgfmt_store)
144 pkgfmt_tree.set_headers_clickable(True)
145 pkgfmt_tree.set_headers_visible(False)
146 tree_selection = pkgfmt_tree.get_selection()
147 tree_selection.set_mode(gtk.SELECTION_SINGLE)
148
149 col = gtk.TreeViewColumn('NO')
150 col.set_sort_column_id(0)
151 col.set_sort_order(gtk.SORT_ASCENDING)
152 col.set_clickable(False)
153 col1 = gtk.TreeViewColumn('TYPE')
154 col1.set_min_width(130)
155 col1.set_max_width(140)
156 col2 = gtk.TreeViewColumn('INCLUDED')
157 col2.set_min_width(60)
158 col2.set_max_width(70)
159 pkgfmt_tree.append_column(col1)
160 pkgfmt_tree.append_column(col2)
161 cell = gtk.CellRendererText()
162 cell1 = gtk.CellRendererText()
163 cell1.set_property('width-chars', 10)
164 cell2 = gtk.CellRendererToggle()
165 cell2.set_property('activatable', True)
166 cell2.connect("toggled", cls._toggle_cb, pkgfmt_store, 2)
167 col.pack_start(cell, True)
168 col1.pack_start(cell1, True)
169 col2.pack_end(cell2, True)
170 col.set_attributes(cell, text=0)
171 col1.set_attributes(cell1, text=1)
172 col2.set_attributes(cell2, active=2)
173
174 pkgfmt_store.set_sort_func(0, cls._sort_func, None)
175 pkgfmt_store.set_sort_column_id(0, gtk.SORT_ASCENDING)
176
177 scroll = gtk.ScrolledWindow()
178 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
179 scroll.set_shadow_type(gtk.SHADOW_IN)
180 scroll.add(pkgfmt_tree)
181 scroll.set_size_request(200,60)
182 pkgfmt_hbox.pack_start(scroll, False, False, 0)
183
184 vbox = gtk.VBox(False, 5)
185 pkgfmt_hbox.pack_start(vbox, False, False, 15)
186
187 up = gtk.Button()
188 image = gtk.Image()
189 image.set_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_MENU)
190 up.set_image(image)
191 up.set_size_request(50,30)
192 up.connect("clicked", cls._pkgfmt_up_clicked_cb, tree_selection)
193 vbox.pack_start(up, False, False, 5)
194
195 down = gtk.Button()
196 image = gtk.Image()
197 image.set_from_stock(gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_MENU)
198 down.set_image(image)
199 down.set_size_request(50,30)
200 down.connect("clicked", cls._pkgfmt_down_clicked_cb, tree_selection)
201 vbox.pack_start(down, False, False, 5)
202 tree_selection.connect("changed", cls._tree_selection_changed_cb, up, down)
203
204 image = gtk.Image()
205 image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON)
206 image.set_tooltip_text(tooltip)
207 pkgfmt_hbox.pack_start(image, expand=False, fill=False)
208
209 pkgfmt_hbox.show_all()
210
211 return pkgfmt_hbox, pkgfmt_store
212
213 @classmethod
214 def gen_combo_widget(cls, curr_item, all_item, tooltip=""):
215 hbox = gtk.HBox(False, 10)
216 combo = gtk.combo_box_new_text()
217 hbox.pack_start(combo, expand=False, fill=False)
218
219 index = 0
220 for item in all_item or []:
221 combo.append_text(item)
222 if item == curr_item:
223 combo.set_active(index)
224 index += 1
225
226 image = gtk.Image()
227 image.show()
228 image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON)
229 image.set_tooltip_text(tooltip)
230
231 hbox.pack_start(image, expand=False, fill=False)
232
233 hbox.show_all()
234
235 return hbox, combo
236
237 @classmethod
238 def gen_label_widget(cls, content):
239 label = gtk.Label()
240 label.set_alignment(0, 0)
241 label.set_markup(content)
242 label.show()
243 return label
244
245 @classmethod
246 def _select_path_cb(cls, action, parent, entry):
247 dialog = gtk.FileChooserDialog("", parent,
248 gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
249 (gtk.STOCK_OK, gtk.RESPONSE_YES,
250 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
251 response = dialog.run()
252 if response == gtk.RESPONSE_YES:
253 path = dialog.get_filename()
254 entry.set_text(path)
255
256 dialog.destroy()
257
258 @classmethod
259 def gen_entry_widget(cls, split_model, content, parent, tooltip=""):
260 hbox = gtk.HBox(False, 10)
261 entry = gtk.Entry()
262 entry.set_text(content)
263
264 if split_model:
265 hbox.pack_start(entry, expand=True, fill=True)
266 else:
267 table = gtk.Table(1, 10, True)
268 hbox.pack_start(table, expand=True, fill=True)
269 table.attach(entry, 0, 9, 0, 1)
270 image = gtk.Image()
271 image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON)
272 open_button = gtk.Button()
273 open_button.set_image(image)
274 open_button.connect("clicked", cls._select_path_cb, parent, entry)
275 table.attach(open_button, 9, 10, 0, 1)
276
277 image = gtk.Image()
278 image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON)
279 image.set_tooltip_text(tooltip)
280 hbox.pack_start(image, expand=False, fill=False)
281
282 hbox.show_all()
283
284 return hbox, entry
285
286 @classmethod
287 def gen_spinner_widget(cls, content, lower, upper, tooltip=""):
288 hbox = gtk.HBox(False, 10)
289 adjust = gtk.Adjustment(value=content, lower=lower, upper=upper, step_incr=1)
290 spinner = gtk.SpinButton(adjustment=adjust, climb_rate=1, digits=0)
291
292 spinner.set_value(content)
293 hbox.pack_start(spinner, expand=False, fill=False)
294
295 image = gtk.Image()
296 image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON)
297 image.set_tooltip_text(tooltip)
298 hbox.pack_start(image, expand=False, fill=False)
299
300 hbox.show_all()
301
302 return hbox, spinner
303
304 @classmethod
305 def conf_error(cls, parent, lbl):
306 dialog = CrumbsDialog(parent, lbl)
307 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
308 response = dialog.run()
309 dialog.destroy()
310
311 @classmethod
312 def _add_layer_cb(cls, action, layer_store, parent):
313 dialog = gtk.FileChooserDialog("Add new layer", parent,
314 gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
315 (gtk.STOCK_OK, gtk.RESPONSE_YES,
316 gtk.STOCK_CANCEL, gtk.RESPONSE_NO))
317 label = gtk.Label("Select the layer you wish to add")
318 label.show()
319 dialog.set_extra_widget(label)
320 response = dialog.run()
321 path = dialog.get_filename()
322 dialog.destroy()
323
324 lbl = "<b>Error</b>\nUnable to load layer <i>%s</i> because " % path
325 if response == gtk.RESPONSE_YES:
326 import os
327 import os.path
328 layers = []
329 it = layer_store.get_iter_first()
330 while it:
331 layers.append(layer_store.get_value(it, 0))
332 it = layer_store.iter_next(it)
333
334 if not path:
335 lbl += "it is an invalid path."
336 elif not os.path.exists(path+"/conf/layer.conf"):
337 lbl += "there is no layer.conf inside the directory."
338 elif path in layers:
339 lbl += "it is already in loaded layers."
340 else:
341 layer_store.append([path])
342 return
343 cls.conf_error(parent, lbl)
344
345 @classmethod
346 def _del_layer_cb(cls, action, tree_selection, layer_store):
347 model, iter = tree_selection.get_selected()
348 if iter:
349 layer_store.remove(iter)
350
351 @classmethod
352 def _toggle_layer_cb(cls, cell, path, layer_store):
353 name = layer_store[path][0]
354 toggle = not layer_store[path][1]
355 layer_store[path][1] = toggle
356
357 @classmethod
358 def gen_layer_widget(cls, split_model, layers, layers_avail, window, tooltip=""):
359 hbox = gtk.HBox(False, 10)
360
361 layer_tv = gtk.TreeView()
362 layer_tv.set_rules_hint(True)
363 layer_tv.set_headers_visible(False)
364 tree_selection = layer_tv.get_selection()
365 tree_selection.set_mode(gtk.SELECTION_SINGLE)
366
367 col0= gtk.TreeViewColumn('Path')
368 cell0 = gtk.CellRendererText()
369 cell0.set_padding(5,2)
370 col0.pack_start(cell0, True)
371 col0.set_attributes(cell0, text=0)
372 layer_tv.append_column(col0)
373
374 scroll = gtk.ScrolledWindow()
375 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
376 scroll.set_shadow_type(gtk.SHADOW_IN)
377 scroll.add(layer_tv)
378
379 table_layer = gtk.Table(2, 10, False)
380 hbox.pack_start(table_layer, expand=True, fill=True)
381
382 if split_model:
383 table_layer.attach(scroll, 0, 10, 0, 2)
384
385 layer_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
386 for layer in layers:
387 layer_store.set(layer_store.append(), 0, layer, 1, True)
388 for layer in layers_avail:
389 if layer not in layers:
390 layer_store.set(layer_store.append(), 0, layer, 1, False)
391
392 col1 = gtk.TreeViewColumn('Included')
393 layer_tv.append_column(col1)
394
395 cell1 = gtk.CellRendererToggle()
396 cell1.connect("toggled", cls._toggle_layer_cb, layer_store)
397 col1.pack_start(cell1, True)
398 col1.set_attributes(cell1, active=1)
399
400 else:
401 table_layer.attach(scroll, 0, 10, 0, 1)
402
403 layer_store = gtk.ListStore(gobject.TYPE_STRING)
404 for layer in layers:
405 layer_store.set(layer_store.append(), 0, layer)
406
407 image = gtk.Image()
408 image.set_from_stock(gtk.STOCK_ADD,gtk.ICON_SIZE_MENU)
409 add_button = gtk.Button()
410 add_button.set_image(image)
411 add_button.connect("clicked", cls._add_layer_cb, layer_store, window)
412 table_layer.attach(add_button, 0, 5, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 0)
413 image = gtk.Image()
414 image.set_from_stock(gtk.STOCK_REMOVE,gtk.ICON_SIZE_MENU)
415 del_button = gtk.Button()
416 del_button.set_image(image)
417 del_button.connect("clicked", cls._del_layer_cb, tree_selection, layer_store)
418 table_layer.attach(del_button, 5, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 0)
419 layer_tv.set_model(layer_store)
420
421 hbox.show_all()
422
423 return hbox, layer_store
424
425 @classmethod
426 def _toggle_single_cb(cls, cell, select_path, treeview, toggle_column):
427 model = treeview.get_model()
428 if not model:
429 return
430 iter = model.get_iter_first()
431 while iter:
432 path = model.get_path(iter)
433 model[path][toggle_column] = False
434 iter = model.iter_next(iter)
435
436 model[select_path][toggle_column] = True
437
438 @classmethod
439 def gen_imgtv_widget(cls, col0_width, col1_width):
440 vbox = gtk.VBox(False, 10)
441
442 imgsel_tv = gtk.TreeView()
443 imgsel_tv.set_rules_hint(True)
444 imgsel_tv.set_headers_visible(False)
445 tree_selection = imgsel_tv.get_selection()
446 tree_selection.set_mode(gtk.SELECTION_SINGLE)
447
448 col0= gtk.TreeViewColumn('Image name')
449 cell0 = gtk.CellRendererText()
450 cell0.set_padding(5,2)
451 col0.pack_start(cell0, True)
452 col0.set_attributes(cell0, text=0)
453 col0.set_max_width(col0_width)
454 col0.set_min_width(col0_width)
455 imgsel_tv.append_column(col0)
456
457 col1= gtk.TreeViewColumn('Select')
458 cell1 = gtk.CellRendererToggle()
459 cell1.set_padding(5,2)
460 cell1.connect("toggled", cls._toggle_single_cb, imgsel_tv, 1)
461 col1.pack_start(cell1, True)
462 col1.set_attributes(cell1, active=1)
463 col1.set_max_width(col1_width)
464 col1.set_min_width(col1_width)
465 imgsel_tv.append_column(col1)
466
467 scroll = gtk.ScrolledWindow()
468 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
469 scroll.set_shadow_type(gtk.SHADOW_IN)
470 scroll.add(imgsel_tv)
471
472 vbox.pack_start(scroll, expand=True, fill=True)
473
474 return vbox, imgsel_tv
475
476 @classmethod
477 def gen_images_widget(cls, col0_width, col1_width, col2_width):
478 vbox = gtk.VBox(False, 10)
479
480 imgsel_tv = gtk.TreeView()
481 imgsel_tv.set_rules_hint(True)
482 imgsel_tv.set_headers_visible(False)
483 tree_selection = imgsel_tv.get_selection()
484 tree_selection.set_mode(gtk.SELECTION_SINGLE)
485
486 col0= gtk.TreeViewColumn('Image name')
487 cell0 = gtk.CellRendererText()
488 cell0.set_padding(5,2)
489 col0.pack_start(cell0, True)
490 col0.set_attributes(cell0, text=0)
491 col0.set_max_width(col0_width)
492 col0.set_min_width(col0_width)
493 imgsel_tv.append_column(col0)
494
495 col1= gtk.TreeViewColumn('Image size')
496 cell1 = gtk.CellRendererText()
497 cell1.set_padding(5,2)
498 col1.pack_start(cell1, True)
499 col1.set_attributes(cell1, text=1)
500 col1.set_max_width(col1_width)
501 col1.set_min_width(col1_width)
502 imgsel_tv.append_column(col1)
503
504 col2= gtk.TreeViewColumn('Select')
505 cell2 = gtk.CellRendererToggle()
506 cell2.set_padding(5,2)
507 cell2.connect("toggled", cls._toggle_single_cb, imgsel_tv, 2)
508 col2.pack_start(cell2, True)
509 col2.set_attributes(cell2, active=2)
510 col2.set_max_width(col2_width)
511 col2.set_min_width(col2_width)
512 imgsel_tv.append_column(col2)
513
514 scroll = gtk.ScrolledWindow()
515 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
516 scroll.set_shadow_type(gtk.SHADOW_IN)
517 scroll.add(imgsel_tv)
518
519 vbox.pack_start(scroll, expand=True, fill=True)
520
521 return vbox, imgsel_tv
522
523 @classmethod
524 def _on_add_item_clicked(cls, button, model):
525 new_item = ["##KEY##", "##VALUE##"]
526
527 iter = model.append()
528 model.set (iter,
529 0, new_item[0],
530 1, new_item[1],
531 )
532
533
534 @classmethod
535 def _on_remove_item_clicked(cls, button, treeview):
536
537 selection = treeview.get_selection()
538 model, iter = selection.get_selected()
539
540 if iter:
541 path = model.get_path(iter)[0]
542 model.remove(iter)
543
544 @classmethod
545 def _on_cell_edited(cls, cell, path_string, new_text, model):
546 it = model.get_iter_from_string(path_string)
547 column = cell.get_data("column")
548 model.set(it, column, new_text)
549
550
551 @classmethod
552 def gen_editable_settings(cls, setting, tooltip=""):
553 setting_hbox = gtk.HBox(False, 10)
554
555 vbox = gtk.VBox(False, 10)
556 setting_hbox.pack_start(vbox, expand=True, fill=True)
557
558 setting_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
559 for key in setting.keys():
560 setting_store.set(setting_store.append(), 0, key, 1, setting[key])
561
562 setting_tree = gtk.TreeView(setting_store)
563 setting_tree.set_headers_visible(True)
564 setting_tree.set_size_request(300, 100)
565
566 col = gtk.TreeViewColumn('Key')
567 col.set_min_width(100)
568 col.set_max_width(150)
569 col.set_resizable(True)
570 col1 = gtk.TreeViewColumn('Value')
571 col1.set_min_width(100)
572 col1.set_max_width(150)
573 col1.set_resizable(True)
574 setting_tree.append_column(col)
575 setting_tree.append_column(col1)
576 cell = gtk.CellRendererText()
577 cell.set_property('width-chars', 10)
578 cell.set_property('editable', True)
579 cell.set_data("column", 0)
580 cell.connect("edited", cls._on_cell_edited, setting_store)
581 cell1 = gtk.CellRendererText()
582 cell1.set_property('width-chars', 10)
583 cell1.set_property('editable', True)
584 cell1.set_data("column", 1)
585 cell1.connect("edited", cls._on_cell_edited, setting_store)
586 col.pack_start(cell, True)
587 col1.pack_end(cell1, True)
588 col.set_attributes(cell, text=0)
589 col1.set_attributes(cell1, text=1)
590
591 scroll = gtk.ScrolledWindow()
592 scroll.set_shadow_type(gtk.SHADOW_IN)
593 scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
594 scroll.add(setting_tree)
595 vbox.pack_start(scroll, expand=True, fill=True)
596
597 # some buttons
598 hbox = gtk.HBox(True, 4)
599 vbox.pack_start(hbox, False, False)
600
601 button = gtk.Button(stock=gtk.STOCK_ADD)
602 button.connect("clicked", cls._on_add_item_clicked, setting_store)
603 hbox.pack_start(button)
604
605 button = gtk.Button(stock=gtk.STOCK_REMOVE)
606 button.connect("clicked", cls._on_remove_item_clicked, setting_tree)
607 hbox.pack_start(button)
608
609 image = gtk.Image()
610 image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON)
611 image.set_tooltip_text(tooltip)
612 setting_hbox.pack_start(image, expand=False, fill=False)
613
614 return setting_hbox, setting_store
615
616class HobViewTable (gtk.VBox):
617 """
618 A VBox to contain the table for different recipe views and package view
619 """
620 def __init__(self, columns, reset_clicked_cb=None, toggled_cb=None):
621 gtk.VBox.__init__(self, False, 6)
622 self.table_tree = gtk.TreeView()
623 self.table_tree.set_headers_visible(True)
624 self.table_tree.set_headers_clickable(True)
625 self.table_tree.set_enable_search(True)
626 self.table_tree.set_search_column(0)
627 self.table_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
628
629 for i in range(len(columns)):
630 col = gtk.TreeViewColumn(columns[i]['col_name'])
631 col.set_clickable(True)
632 col.set_resizable(True)
633 col.set_sort_column_id(columns[i]['col_id'])
634 col.set_min_width(columns[i]['col_min'])
635 col.set_max_width(columns[i]['col_max'])
636 self.table_tree.append_column(col)
637
638 if columns[i]['col_style'] == 'toggle':
639 cell = gtk.CellRendererToggle()
640 cell.set_property('activatable', True)
641 cell.connect("toggled", toggled_cb, self.table_tree)
642 col.pack_end(cell, True)
643 col.set_attributes(cell, active=columns[i]['col_id'])
644 elif columns[i]['col_style'] == 'text':
645 cell = gtk.CellRendererText()
646 col.pack_start(cell, True)
647 col.set_attributes(cell, text=columns[i]['col_id'])
648
649 scroll = gtk.ScrolledWindow()
650 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
651 scroll.set_shadow_type(gtk.SHADOW_IN)
652 scroll.add(self.table_tree)
653 self.pack_start(scroll, True, True, 0)
654
655 hbox = gtk.HBox(False, 5)
656 button = gtk.Button("Reset")
657 button.connect('clicked', reset_clicked_cb)
658 hbox.pack_end(button, False, False, 0)
659
660 self.pack_start(hbox, False, False, 0)
661
662class HobViewBar (gtk.EventBox):
663 """
664 A EventBox with the specified gray background color is associated with a notebook.
665 And the toolbar to simulate the tabs.
666 """
667
668 def __init__(self, notebook):
669 if not notebook:
670 return
671 self.notebook = notebook
672
673 # setup an event box
674 gtk.EventBox.__init__(self)
675 self.set_border_width(2)
676 style = self.get_style().copy()
677 style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color (HobColors.GRAY, False, False)
678 self.set_style(style)
679
680 hbox = gtk.HBox()
681 self.add(hbox)
682
683 # setup a tool bar in the event box
684 self.toolbar = gtk.Toolbar()
685 self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
686 self.toolbar.set_style(gtk.TOOLBAR_TEXT)
687 self.toolbar.set_border_width(5)
688
689 self.toolbuttons = []
690 for index in range(self.notebook.get_n_pages()):
691 child = self.notebook.get_nth_page(index)
692 label = self.notebook.get_tab_label_text(child)
693 tip_text = 'switch to ' + label + ' page'
694 toolbutton = self.toolbar.append_element(gtk.TOOLBAR_CHILD_RADIOBUTTON, None,
695 label, tip_text, "Private text", None,
696 self.toolbutton_cb, index)
697 toolbutton.set_size_request(200, 100)
698 self.toolbuttons.append(toolbutton)
699
700 # set the default current page
701 self.modify_toolbuttons_bg(0)
702 self.notebook.set_current_page(0)
703
704 self.toolbar.append_space()
705
706 # add the tool bar into the event box
707 hbox.pack_start(self.toolbar, expand=False, fill=False)
708
709 self.search = gtk.Entry()
710 self.align = gtk.Alignment(xalign=0.5, yalign=0.5)
711 self.align.add(self.search)
712 hbox.pack_end(self.align, expand=False, fill=False)
713
714 self.label = gtk.Label(" Search: ")
715 self.label.set_alignment(0.5, 0.5)
716 hbox.pack_end(self.label, expand=False, fill=False)
717
718 def toolbutton_cb(self, widget, index):
719 if index >= self.notebook.get_n_pages():
720 return
721 self.notebook.set_current_page(index)
722 self.modify_toolbuttons_bg(index)
723
724 def modify_toolbuttons_bg(self, index):
725 if index >= len(self.toolbuttons):
726 return
727 for i in range(0, len(self.toolbuttons)):
728 toolbutton = self.toolbuttons[i]
729 if i == index:
730 self.modify_toolbutton_bg(toolbutton, True)
731 else:
732 self.modify_toolbutton_bg(toolbutton)
733
734 def modify_toolbutton_bg(self, toolbutton, active=False):
735 if active:
736 toolbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.WHITE))
737 toolbutton.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(HobColors.WHITE))
738 toolbutton.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.WHITE))
739 toolbutton.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.WHITE))
740 else:
741 toolbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.GRAY))
742 toolbutton.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(HobColors.GRAY))
743 toolbutton.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.GRAY))
744 toolbutton.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.GRAY))
745
746class HobXpmLabelButtonBox(gtk.EventBox):
747 """ label: name of buttonbox
748 description: the simple description
749 """
750
751 def __init__(self, display_file="", hover_file="", label="", description=""):
752 gtk.EventBox.__init__(self)
753 self._base_state_flags = gtk.STATE_NORMAL
754 self.set_events(gtk.gdk.MOTION_NOTIFY | gtk.gdk.BUTTON_PRESS | gtk.gdk.EXPOSE)
755
756 self.connect("expose-event", self.cb)
757 self.connect("enter-notify-event", self.pointer_enter_cb)
758 self.connect("leave-notify-event", self.pointer_leave_cb)
759
760 self.icon_hover = gtk.Image()
761 self.icon_hover.set_name("icon_image")
762 if type(hover_file) == str:
763 pixbuf = gtk.gdk.pixbuf_new_from_file(hover_file)
764 self.icon_hover.set_from_pixbuf(pixbuf)
765
766 self.icon_display = gtk.Image()
767 self.icon_display.set_name("icon_image")
768 if type(display_file) == str:
769 pixbuf = gtk.gdk.pixbuf_new_from_file(display_file)
770 self.icon_display.set_from_pixbuf(pixbuf)
771
772 self.tb = gtk.Table(2, 10, True)
773 self.tb.set_row_spacing(1, False)
774 self.tb.set_col_spacing(1, False)
775 self.add(self.tb)
776 self.tb.attach(self.icon_display, 0, 2, 0, 2, 0, 0)
777 self.tb.attach(self.icon_hover, 0, 2, 0, 2, 0, 0)
778
779 lbl = gtk.Label()
780 lbl.set_alignment(0.0, 0.5)
781 lbl.set_markup("<span foreground=\'#1C1C1C\' font_desc=\'18px\'>%s</span>" % label)
782 self.tb.attach(lbl, 2, 10, 0, 1)
783
784 lbl = gtk.Label()
785 lbl.set_alignment(0.0, 0.5)
786 lbl.set_markup("<span foreground=\'#1C1C1C\' font_desc=\'14px\'>%s</span>" % description)
787 self.tb.attach(lbl, 2, 10, 1, 2)
788
789 def pointer_enter_cb(self, *args):
790 #if not self.is_focus():
791 self.set_state(gtk.STATE_PRELIGHT)
792 self._base_state_flags = gtk.STATE_PRELIGHT
793 self.icon_hover.show()
794 self.icon_display.hide()
795
796 def pointer_leave_cb(self, *args):
797 self.set_state(gtk.STATE_NORMAL)
798 self._base_state_flags = gtk.STATE_NORMAL
799 self.icon_display.show()
800 self.icon_hover.hide()
801
802 def cb(self, w,e):
803 """ Hide items - first time """
804 pass
805
diff --git a/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py
new file mode 100644
index 0000000000..d3a9ffd710
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py
@@ -0,0 +1,358 @@
1#!/usr/bin/env python
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2012 Intel Corporation
6#
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8# Authored by Shane Wang <shane.wang@intel.com>
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23import gtk
24import glib
25from bb.ui.crumbs.progressbar import HobProgressBar
26from bb.ui.crumbs.hobcolor import HobColors
27from bb.ui.crumbs.hobwidget import hic, HobXpmLabelButtonBox
28from bb.ui.crumbs.hoblistmodel import RecipeListModel
29from bb.ui.crumbs.hobpages import HobPage
30
31from bb.ui.crumbs.hig import CrumbsDialog, BinbDialog, \
32 AdvancedSettingDialog, LayerSelectionDialog
33
34#
35# ImageConfigurationPage
36#
37class ImageConfigurationPage (HobPage):
38
39 __dummy_machine__ = "--select a machine--"
40
41 def __init__(self, builder):
42 super(ImageConfigurationPage, self).__init__(builder, "Image configuration")
43
44 self.image_combo_id = None
45 self.create_visual_elements()
46
47 def create_visual_elements(self):
48 # create visual elements
49 self.toolbar = gtk.Toolbar()
50 self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
51 self.toolbar.set_style(gtk.TOOLBAR_BOTH)
52
53 _, template_button = self.append_toolbar_button(self.toolbar,
54 "Template",
55 hic.ICON_TEMPLATES_DISPLAY_FILE,
56 hic.ICON_TEMPLATES_HOVER_FILE,
57 "Load a hob building template saved before",
58 self.template_button_clicked_cb)
59 _, my_images_button = self.append_toolbar_button(self.toolbar,
60 "My images",
61 hic.ICON_IMAGES_DISPLAY_FILE,
62 hic.ICON_IMAGES_HOVER_FILE,
63 "Open images built out previously for running or deployment",
64 self.my_images_button_clicked_cb)
65 _, settings_button = self.append_toolbar_button(self.toolbar,
66 "Settings",
67 hic.ICON_SETTINGS_DISPLAY_FILE,
68 hic.ICON_SETTINGS_HOVER_FILE,
69 "Other advanced settings for build",
70 self.settings_button_clicked_cb)
71
72 self.config_top_button = self.add_onto_top_bar(self.toolbar)
73
74 self.gtable = gtk.Table(40, 40, True)
75 self.create_config_machine()
76 self.create_config_baseimg()
77 self.config_build_button = self.create_config_build_button()
78
79 def _remove_all_widget(self):
80 children = self.gtable.get_children() or []
81 for child in children:
82 self.gtable.remove(child)
83 children = self.box_group_area.get_children() or []
84 for child in children:
85 self.box_group_area.remove(child)
86 children = self.get_children() or []
87 for child in children:
88 self.remove(child)
89
90 def _pack_components(self):
91 self._remove_all_widget()
92 self.pack_start(self.config_top_button, expand=False, fill=False)
93 self.pack_start(self.group_align, expand=True, fill=True)
94
95 self.box_group_area.pack_start(self.gtable, expand=True, fill=True)
96 self.box_group_area.pack_end(self.config_build_button, expand=False, fill=False)
97
98 def show_machine(self):
99 self._pack_components()
100 self.set_config_machine_layout()
101 self.show_all()
102 self.progress_bar.reset()
103 self.progress_bar.hide()
104 self.config_build_button.hide_all()
105
106 def update_progress_bar(self, title, fraction, status=True):
107 self.progress_bar.update(fraction)
108 self.progress_bar.set_title(title)
109 self.progress_bar.set_rcstyle(status)
110
111 def show_info_populating(self):
112 self._pack_components()
113 self.set_config_machine_layout()
114 self.show_all()
115 self.config_build_button.hide_all()
116
117 def show_info_populated(self):
118 self._pack_components()
119 self.set_config_machine_layout()
120 self.set_config_baseimg_layout()
121 self.show_all()
122 self.progress_bar.reset()
123 self.progress_bar.hide()
124
125 def create_config_machine(self):
126 self.machine_title = gtk.Label()
127 self.machine_title.set_alignment(0.0, 0.5)
128 mark = "<span %s>Select a machine</span>" % self.span_tag('24px', 'bold')
129 self.machine_title.set_markup(mark)
130
131 self.machine_title_desc = gtk.Label()
132 self.machine_title_desc.set_alignment(0, 0.5)
133 mark = ("<span %s>This is the profile of the target machine for which you"
134 " are building the image.\n</span>") % (self.span_tag(px='14px'))
135 self.machine_title_desc.set_markup(mark)
136
137 self.machine_combo = gtk.combo_box_new_text()
138 self.machine_combo.connect("changed", self.machine_combo_changed_cb)
139
140 icon_file = hic.ICON_LAYERS_DISPLAY_FILE
141 hover_file = hic.ICON_LAYERS_HOVER_FILE
142 self.layer_button = HobXpmLabelButtonBox(icon_file, hover_file,
143 "Layers", "Add support for machines, software, etc")
144 self.layer_button.connect("button-release-event", self.layer_button_clicked_cb)
145
146 icon_file = hic.ICON_INFO_DISPLAY_FILE
147 self.layer_info_icon = gtk.Image()
148 pix_buffer = gtk.gdk.pixbuf_new_from_file(icon_file)
149 self.layer_info_icon.set_from_pixbuf(pix_buffer)
150 markup = "Layers are a powerful mechanism to extend the Yocto Project "
151 markup += "with your own functionality.\n"
152 markup += "For more on layers, check:\n"
153 markup += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
154 markup += "poky-ref-manual.html#usingpoky-changes-layers."
155 self.layer_info_icon.set_tooltip_markup(markup)
156
157 self.progress_bar = HobProgressBar()
158 self.machine_separator = gtk.HSeparator()
159
160 def set_config_machine_layout(self):
161 self.gtable.attach(self.machine_title, 0, 40, 0, 4)
162 self.gtable.attach(self.machine_title_desc, 0, 40, 4, 6)
163 self.gtable.attach(self.machine_combo, 0, 12, 6, 9)
164 self.gtable.attach(self.layer_button, 12, 36, 6, 10)
165 self.gtable.attach(self.layer_info_icon, 36, 40, 6, 9)
166 self.gtable.attach(self.progress_bar, 0, 40, 13, 17)
167 self.gtable.attach(self.machine_separator, 0, 40, 12, 13)
168
169 def create_config_baseimg(self):
170 self.image_title = gtk.Label()
171 self.image_title.set_alignment(0, 1.0)
172 mark = "<span %s>Select a base image</span>" % self.span_tag('24px', 'bold')
173 self.image_title.set_markup(mark)
174
175 self.image_title_desc = gtk.Label()
176 self.image_title_desc.set_alignment(0, 0.5)
177 mark = ("<span %s>Base images are a starting point for the type of image you want. "
178 "You can build them as \n"
179 "they are or customize them to your specific needs.\n</span>") % self.span_tag('14px')
180 self.image_title_desc.set_markup(mark)
181
182 self.image_combo = gtk.combo_box_new_text()
183 self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
184
185 self.image_desc = gtk.Label()
186 self.image_desc.set_alignment(0, 0)
187 self.image_desc.set_line_wrap(True)
188
189 # button to view recipes
190 icon_file = hic.ICON_RCIPE_DISPLAY_FILE
191 hover_file = hic.ICON_RCIPE_HOVER_FILE
192 self.view_recipes_button = HobXpmLabelButtonBox(icon_file, hover_file,
193 "View Recipes", "Add/remove recipes and collections")
194 self.view_recipes_button.connect("button-release-event", self.view_recipes_button_clicked_cb)
195
196 # button to view packages
197 icon_file = hic.ICON_PACKAGES_DISPLAY_FILE
198 hover_file = hic.ICON_PACKAGES_HOVER_FILE
199 self.view_packages_button = HobXpmLabelButtonBox(icon_file, hover_file,
200 "View Packages", "Add/remove packages")
201 self.view_packages_button.connect("button-release-event", self.view_packages_button_clicked_cb)
202
203 self.image_separator = gtk.HSeparator()
204
205 def set_config_baseimg_layout(self):
206 self.gtable.attach(self.image_title, 0, 40, 13, 17)
207 self.gtable.attach(self.image_title_desc, 0, 40, 17, 22)
208 self.gtable.attach(self.image_combo, 0, 12, 22, 25)
209 self.gtable.attach(self.image_desc, 14, 38, 22, 27)
210 self.gtable.attach(self.view_recipes_button, 0, 20, 28, 32)
211 self.gtable.attach(self.view_packages_button, 20, 40, 28, 32)
212 self.gtable.attach(self.image_separator, 0, 40, 35, 36)
213
214 def create_config_build_button(self):
215 # Create the "Build packages" and "Just bake" buttons at the bottom
216 button_box = gtk.HBox(False, 5)
217
218 # create button "Just bake"
219 just_bake_button = gtk.Button()
220 label = gtk.Label()
221 mark = "<span %s>Just bake</span>" % self.span_tag('24px', 'bold')
222 label.set_markup(mark)
223
224 just_bake_button.set_image(label)
225 just_bake_button.set_size_request(205, 49)
226 just_bake_button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.ORANGE))
227 just_bake_button.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.ORANGE))
228 just_bake_button.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.ORANGE))
229 just_bake_button.set_tooltip_text("Build image to get your target image")
230 just_bake_button.set_flags(gtk.CAN_DEFAULT)
231 just_bake_button.grab_default()
232 just_bake_button.connect("clicked", self.just_bake_button_clicked_cb)
233 button_box.pack_end(just_bake_button, expand=False, fill=False)
234
235 label = gtk.Label(" or ")
236 button_box.pack_end(label, expand=False, fill=False)
237
238 # create button "Build Packages"
239 build_packages_button = gtk.LinkButton("Build packages first based on recipe selection "
240 "for late customization on packages for the target image", "Build Packages")
241 build_packages_button.connect("clicked", self.build_packages_button_clicked_cb)
242 button_box.pack_end(build_packages_button, expand=False, fill=False)
243
244 return button_box
245
246 def machine_combo_changed_cb(self, machine_combo):
247 combo_item = machine_combo.get_active_text()
248 if not combo_item or combo_item == self.__dummy_machine__:
249 self.builder.switch_page(self.builder.MACHINE_SELECTION)
250 else:
251 self.builder.configuration.curr_mach = combo_item
252 # Do reparse recipes
253 self.builder.switch_page(self.builder.RCPPKGINFO_POPULATING)
254
255 def update_machine_combo(self):
256 all_machines = [self.__dummy_machine__] + self.builder.parameters.all_machines
257
258 model = self.machine_combo.get_model()
259 model.clear()
260 for machine in all_machines:
261 self.machine_combo.append_text(machine)
262 self.machine_combo.set_active(0)
263
264 def switch_machine_combo(self):
265 model = self.machine_combo.get_model()
266 active = 0
267 while active < len(model):
268 if model[active][0] == self.builder.configuration.curr_mach:
269 self.machine_combo.set_active(active)
270 return
271 active += 1
272 self.machine_combo.set_active(0)
273
274 def image_combo_changed_idle_cb(self, selected_image, selected_recipes, selected_packages):
275 self.builder.update_recipe_model(selected_image, selected_recipes)
276 self.builder.update_package_model(selected_packages)
277 self.builder.window_sensitive(True)
278
279 def image_combo_changed_cb(self, combo):
280 self.builder.window_sensitive(False)
281 selected_image = self.image_combo.get_active_text()
282 if not selected_image:
283 return
284
285 selected_recipes = []
286
287 image_path = self.builder.recipe_model.pn_path[selected_image]
288 image_iter = self.builder.recipe_model.get_iter(image_path)
289 selected_packages = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_INSTALL).split()
290
291 mark = ("<span %s>%s</span>\n") % (self.span_tag('14px'), self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC))
292 self.image_desc.set_markup(mark)
293
294 self.builder.recipe_model.reset()
295 self.builder.package_model.reset()
296
297 glib.idle_add(self.image_combo_changed_idle_cb, selected_image, selected_recipes, selected_packages)
298
299 def _image_combo_connect_signal(self):
300 if not self.image_combo_id:
301 self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
302
303 def _image_combo_disconnect_signal(self):
304 if self.image_combo_id:
305 self.image_combo.disconnect(self.image_combo_id)
306 self.image_combo_id = None
307
308 def update_image_combo(self, recipe_model, selected_image):
309 # Update the image combo according to the images in the recipe_model
310 # populate image combo
311 filter = {RecipeListModel.COL_TYPE : ['image']}
312 image_model = recipe_model.tree_model(filter)
313 active = 0
314 cnt = 0
315
316 it = image_model.get_iter_first()
317 self._image_combo_disconnect_signal()
318 model = self.image_combo.get_model()
319 model.clear()
320 # append and set active
321 while it:
322 path = image_model.get_path(it)
323 image_name = image_model[path][recipe_model.COL_NAME]
324 self.image_combo.append_text(image_name)
325 if image_name == selected_image:
326 active = cnt
327 it = image_model.iter_next(it)
328 cnt = cnt + 1
329 self._image_combo_connect_signal()
330
331 self.image_combo.set_active(-1)
332 self.image_combo.set_active(active)
333
334 def layer_button_clicked_cb(self, event, data):
335 # Create a layer selection dialog
336 self.builder.show_layer_selection_dialog()
337
338 def view_recipes_button_clicked_cb(self, event, data):
339 self.builder.show_recipes()
340
341 def view_packages_button_clicked_cb(self, event, data):
342 self.builder.show_packages()
343
344 def just_bake_button_clicked_cb(self, button):
345 self.builder.just_bake()
346
347 def build_packages_button_clicked_cb(self, button):
348 self.builder.build_packages()
349
350 def template_button_clicked_cb(self, button):
351 self.builder.show_load_template_dialog()
352
353 def my_images_button_clicked_cb(self, button):
354 self.builder.show_load_my_images_dialog()
355
356 def settings_button_clicked_cb(self, button):
357 # Create an advanced settings dialog
358 self.builder.show_adv_settings_dialog()
diff --git a/bitbake/lib/bb/ui/crumbs/imagedetailspage.py b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py
new file mode 100755
index 0000000000..e8419e0ee9
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py
@@ -0,0 +1,294 @@
1#!/usr/bin/env python
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2012 Intel Corporation
6#
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8# Authored by Shane Wang <shane.wang@intel.com>
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23import gobject
24import gtk
25from bb.ui.crumbs.hobcolor import HobColors
26from bb.ui.crumbs.hobwidget import hic, HobWidget
27from bb.ui.crumbs.hobpages import HobPage
28
29#
30# ImageDetailsPage
31#
32class ImageDetailsPage (HobPage):
33
34 class DetailBox (gtk.EventBox):
35 def __init__(self, varlist, vallist, icon = None, button = None, color = HobColors.LIGHT_GRAY):
36 gtk.EventBox.__init__(self)
37
38 # set color
39 style = self.get_style().copy()
40 style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False)
41 self.set_style(style)
42
43 self.hbox = gtk.HBox()
44 self.hbox.set_border_width(15)
45 self.add(self.hbox)
46
47 # pack the icon and the text on the left
48 row = len(varlist)
49 self.table = gtk.Table(row, 20, True)
50 self.table.set_size_request(100, -1)
51 self.hbox.pack_start(self.table, expand=True, fill=True, padding=15)
52
53 colid = 0
54 if icon != None:
55 self.table.attach(icon, colid, colid + 2, 0, 1)
56 colid = colid + 2
57 for line in range(0, row):
58 self.table.attach(self.text2label(varlist[line], vallist[line]), colid, 20, line, line + 1)
59
60 # pack the button on the right
61 if button != None:
62 self.hbox.pack_end(button, expand=False, fill=False)
63
64 def text2label(self, variable, value):
65 # append the name:value to the left box
66 # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard"
67 markup = "<span weight=\'bold\'>%s</span>" % variable
68 markup += "<span weight=\'normal\' foreground=\'#1c1c1c\' font_desc=\'14px\'>%s</span>" % value
69 label = gtk.Label()
70 label.set_alignment(0.0, 0.5)
71 label.set_markup(markup)
72 return label
73
74 def __init__(self, builder):
75 super(ImageDetailsPage, self).__init__(builder, "Image details")
76
77 self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
78 self.create_visual_elements()
79
80 def create_visual_elements(self):
81 # create visual elements
82 # create the toolbar
83 self.toolbar = gtk.Toolbar()
84 self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
85 self.toolbar.set_style(gtk.TOOLBAR_BOTH)
86
87 _, my_images_button = self.append_toolbar_button(self.toolbar,
88 "My images",
89 hic.ICON_IMAGES_DISPLAY_FILE,
90 hic.ICON_IMAGES_HOVER_FILE,
91 "Open images built out previously for running or deployment",
92 self.my_images_button_clicked_cb)
93
94 self.details_top_buttons = self.add_onto_top_bar(self.toolbar)
95
96 def _remove_all_widget(self):
97 children = self.get_children() or []
98 for child in children:
99 self.remove(child)
100 children = self.box_group_area.get_children() or []
101 for child in children:
102 self.box_group_area.remove(child)
103
104 def _size_to_string(self, size):
105 if len(str(int(size))) > 6:
106 size_str = '%.1f' % (size*1.0/(1024*1024)) + ' MB'
107 elif len(str(int(size))) > 3:
108 size_str = '%.1f' % (size*1.0/1024) + ' KB'
109 else:
110 size_str = str(size) + ' B'
111 return size_str
112
113 def show_page(self, step):
114 build_succeeded = (step == self.builder.IMAGE_GENERATED)
115 image_addr = self.builder.parameters.image_addr
116 image_names = self.builder.parameters.image_names
117 if build_succeeded:
118 image_addr = self.builder.parameters.image_addr
119 image_names = self.builder.parameters.image_names
120 machine = self.builder.configuration.curr_mach
121 base_image = self.builder.recipe_model.get_selected_image()
122 layers = self.builder.configuration.layers
123 pkg_num = "%s" % len(self.builder.package_model.get_selected_packages())
124 else:
125 pkg_num = "N/A"
126
127 self._remove_all_widget()
128 self.pack_start(self.details_top_buttons, expand=False, fill=False)
129 self.pack_start(self.group_align, expand=True, fill=True)
130
131 if build_succeeded:
132 # building is the previous step
133 icon = gtk.Image()
134 pixmap_path = hic.ICON_INDI_CONFIRM_FILE
135 color = HobColors.RUNNING
136 pix_buffer = gtk.gdk.pixbuf_new_from_file(pixmap_path)
137 icon.set_from_pixbuf(pix_buffer)
138 varlist = [""]
139 vallist = ["Your image is ready"]
140 build_result = self.DetailBox(varlist=varlist, vallist=vallist, icon=icon, button=None, color=color)
141 self.box_group_area.pack_start(build_result, expand=False, fill=False)
142
143 # Name
144 self.image_store.clear()
145 for image_name in image_names:
146 image_size = self._size_to_string(os.stat(os.path.join(image_addr, image_name)).st_size)
147 self.image_store.set(self.image_store.append(), 0, image_name, 1, image_size, 2, False)
148 images_widget, treeview = HobWidget.gen_images_widget(600, 200, 100)
149 treeview.set_model(self.image_store)
150 self.box_group_area.pack_start(images_widget, expand=False, fill=False)
151
152 # Machine, Base image and Layers
153 layer_num_limit = 15
154 varlist = ["Machine: ", "Base image: ", "Layers: "]
155 vallist = []
156 if build_succeeded:
157 vallist.append(machine)
158 vallist.append(base_image)
159 i = 0
160 for layer in layers:
161 varlist.append(" - ")
162 if i > layer_num_limit:
163 break
164 i += 1
165 vallist.append("")
166 i = 0
167 for layer in layers:
168 if i > layer_num_limit:
169 break
170 elif i == layer_num_limit:
171 vallist.append("and more...")
172 else:
173 vallist.append(layer)
174 i += 1
175
176 edit_config_button = gtk.LinkButton("Changes settings for build", "Edit configuration")
177 edit_config_button.connect("clicked", self.edit_config_button_clicked_cb)
178 setting_detail = self.DetailBox(varlist=varlist, vallist=vallist, icon=None, button=edit_config_button)
179 self.box_group_area.pack_start(setting_detail, expand=False, fill=False)
180
181 # Packages included, and Total image size
182 varlist = ["Packages included: ", "Total image size: "]
183 vallist = []
184 vallist.append(pkg_num)
185 vallist.append(image_size)
186 if build_succeeded:
187 edit_packages_button = gtk.LinkButton("Change package selection for customization", "Edit packages")
188 edit_packages_button.connect("clicked", self.edit_packages_button_clicked_cb)
189 else: # get to this page from "My images"
190 edit_packages_button = None
191 package_detail = self.DetailBox(varlist=varlist, vallist=vallist, icon=None, button=edit_packages_button)
192 self.box_group_area.pack_start(package_detail, expand=False, fill=False)
193 if build_succeeded:
194 buttonlist = ["Build new image", "Save as template", "Run image", "Deploy image"]
195 else: # get to this page from "My images"
196 buttonlist = ["Build new image", "Run image", "Deploy image"]
197 details_bottom_buttons = self.create_bottom_buttons(buttonlist)
198 self.box_group_area.pack_end(details_bottom_buttons, expand=False, fill=False)
199
200 self.show_all()
201
202 def create_bottom_buttons(self, buttonlist):
203 # Create the buttons at the bottom
204 bottom_buttons = gtk.HBox(False, 5)
205 created = False
206
207 # create button "Deploy image"
208 name = "Deploy image"
209 if name in buttonlist:
210 deploy_button = gtk.Button()
211 label = gtk.Label()
212 mark = "<span %s>Deploy image</span>" % self.span_tag('24px', 'bold')
213 label.set_markup(mark)
214 deploy_button.set_image(label)
215 deploy_button.set_size_request(205, 49)
216 deploy_button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.ORANGE))
217 deploy_button.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.ORANGE))
218 deploy_button.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.ORANGE))
219 deploy_button.set_tooltip_text("Deploy image to get your target board")
220 deploy_button.set_flags(gtk.CAN_DEFAULT)
221 deploy_button.grab_default()
222 deploy_button.connect("clicked", self.deploy_button_clicked_cb)
223 bottom_buttons.pack_end(deploy_button, expand=False, fill=False)
224 created = True
225
226 name = "Run image"
227 if name in buttonlist:
228 if created == True:
229 # separator
230 label = gtk.Label(" or ")
231 bottom_buttons.pack_end(label, expand=False, fill=False)
232
233 # create button "Run image"
234 run_button = gtk.LinkButton("Launch and boot the image in the QEMU emulator", "Run image")
235 run_button.connect("clicked", self.run_button_clicked_cb)
236 bottom_buttons.pack_end(run_button, expand=False, fill=False)
237 created = True
238
239 name = "Save as template"
240 if name in buttonlist:
241 if created == True:
242 # separator
243 label = gtk.Label(" or ")
244 bottom_buttons.pack_end(label, expand=False, fill=False)
245
246 # create button "Save as template"
247 save_button = gtk.LinkButton("Save the hob build template for future use", "Save as template")
248 save_button.connect("clicked", self.save_button_clicked_cb)
249 bottom_buttons.pack_end(save_button, expand=False, fill=False)
250 create = True
251
252 name = "Build new image"
253 if name in buttonlist:
254 # create button "Build new image"
255 build_new_button = gtk.LinkButton("Initiate another new build from the beginning", "Build new image")
256 build_new_button.connect("clicked", self.build_new_button_clicked_cb)
257 bottom_buttons.pack_start(build_new_button, expand=False, fill=False)
258
259 return bottom_buttons
260
261 def _get_selected_image(self):
262 image_name = ""
263 iter = self.image_store.get_iter_first()
264 while iter:
265 path = self.image_store.get_path(iter)
266 if self.image_store[path][2]:
267 image_name = self.image_store[path][0]
268 break
269 iter = self.image_store.iter_next(iter)
270
271 return image_name
272
273 def save_button_clicked_cb(self, button):
274 self.builder.show_save_template_dialog()
275
276 def deploy_button_clicked_cb(self, button):
277 image_name = self._get_selected_image()
278 self.builder.deploy_image(image_name)
279
280 def run_button_clicked_cb(self, button):
281 image_name = self._get_selected_image()
282 self.builder.runqemu_image(image_name)
283
284 def build_new_button_clicked_cb(self, button):
285 self.builder.initiate_new_build()
286
287 def edit_config_button_clicked_cb(self, button):
288 self.builder.show_configuration()
289
290 def edit_packages_button_clicked_cb(self, button):
291 self.builder.show_packages(ask=False)
292
293 def my_images_button_clicked_cb(self, button):
294 self.builder.show_load_my_images_dialog()
diff --git a/bitbake/lib/bb/ui/crumbs/layereditor.py b/bitbake/lib/bb/ui/crumbs/layereditor.py
deleted file mode 100644
index f5394a5f52..0000000000
--- a/bitbake/lib/bb/ui/crumbs/layereditor.py
+++ /dev/null
@@ -1,153 +0,0 @@
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#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gobject
22import gtk
23from bb.ui.crumbs.configurator import Configurator
24from bb.ui.crumbs.hig import CrumbsDialog
25
26class LayerEditor(gtk.Dialog):
27 """
28 Gtk+ Widget for enabling and disabling layers.
29 Layers are added through using an open dialog to find the layer.conf
30 Disabled layers are deleted from conf/bblayers.conf
31 """
32 def __init__(self, configurator, parent=None):
33 gtk.Dialog.__init__(self, "Layers", None,
34 gtk.DIALOG_DESTROY_WITH_PARENT,
35 (gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
36
37 # We want to show a little more of the treeview in the default,
38 # emptier, case
39 self.set_size_request(-1, 300)
40 self.set_border_width(6)
41 self.vbox.set_property("spacing", 0)
42 self.action_area.set_property("border-width", 6)
43
44 self.configurator = configurator
45 self.newly_added = {}
46
47 # Label to inform users that meta is enabled but that you can't
48 # disable it as it'd be a *bad* idea
49 msg = "As the core of the build system the <i>meta</i> layer must always be included and therefore can't be viewed or edited here."
50 lbl = gtk.Label()
51 lbl.show()
52 lbl.set_use_markup(True)
53 lbl.set_markup(msg)
54 lbl.set_line_wrap(True)
55 lbl.set_justify(gtk.JUSTIFY_FILL)
56 self.vbox.pack_start(lbl, expand=False, fill=False, padding=6)
57
58 # Create a treeview in which to list layers
59 # ListStore of Name, Path, Enabled
60 self.layer_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
61 self.tv = gtk.TreeView(self.layer_store)
62 self.tv.set_headers_visible(True)
63
64 col0 = gtk.TreeViewColumn('Name')
65 self.tv.append_column(col0)
66 col1 = gtk.TreeViewColumn('Path')
67 self.tv.append_column(col1)
68 col2 = gtk.TreeViewColumn('Enabled')
69 self.tv.append_column(col2)
70
71 cell0 = gtk.CellRendererText()
72 col0.pack_start(cell0, True)
73 col0.set_attributes(cell0, text=0)
74 cell1 = gtk.CellRendererText()
75 col1.pack_start(cell1, True)
76 col1.set_attributes(cell1, text=1)
77 cell2 = gtk.CellRendererToggle()
78 cell2.connect("toggled", self._toggle_layer_cb)
79 col2.pack_start(cell2, True)
80 col2.set_attributes(cell2, active=2)
81
82 self.tv.show()
83 self.vbox.pack_start(self.tv, expand=True, fill=True, padding=0)
84
85 tb = gtk.Toolbar()
86 tb.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)
87 tb.set_style(gtk.TOOLBAR_BOTH)
88 tb.set_tooltips(True)
89 tb.show()
90 icon = gtk.Image()
91 icon.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_SMALL_TOOLBAR)
92 icon.show()
93 tb.insert_item("Add Layer", "Add new layer", None, icon,
94 self._find_layer_cb, None, -1)
95 self.vbox.pack_start(tb, expand=False, fill=False, padding=0)
96
97 def set_parent_window(self, parent):
98 self.set_transient_for(parent)
99
100 def load_current_layers(self, data):
101 for layer, path in self.configurator.enabled_layers.items():
102 if layer != 'meta':
103 self.layer_store.append([layer, path, True])
104
105 def save_current_layers(self):
106 self.configurator.writeLayerConf()
107
108 def _toggle_layer_cb(self, cell, path):
109 name = self.layer_store[path][0]
110 toggle = not self.layer_store[path][2]
111 if toggle:
112 self.configurator.addLayer(name, path)
113 else:
114 self.configurator.disableLayer(name)
115 self.layer_store[path][2] = toggle
116
117 def _find_layer_cb(self, button):
118 self.find_layer(self)
119
120 def find_layer(self, parent):
121 def conf_error(parent, lbl):
122 dialog = CrumbsDialog(parent, lbl)
123 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
124 response = dialog.run()
125 dialog.destroy()
126
127 dialog = gtk.FileChooserDialog("Add new layer", parent,
128 gtk.FILE_CHOOSER_ACTION_OPEN,
129 (gtk.STOCK_CANCEL, gtk.RESPONSE_NO,
130 gtk.STOCK_OPEN, gtk.RESPONSE_YES))
131 label = gtk.Label("Select the layer.conf of the layer you wish to add")
132 label.show()
133 dialog.set_extra_widget(label)
134 response = dialog.run()
135 path = dialog.get_filename()
136 dialog.destroy()
137
138 lbl = "<b>Error</b>\nUnable to load layer <i>%s</i> because " % path
139 if response == gtk.RESPONSE_YES:
140 # FIXME: verify we've actually got a layer conf?
141 if path.endswith("layer.conf"):
142 name, layerpath = self.configurator.addLayerConf(path)
143 if name and layerpath:
144 self.newly_added[name] = layerpath
145 self.layer_store.append([name, layerpath, True])
146 return
147 elif name:
148 return
149 else:
150 lbl += "there was a problem parsing the layer.conf."
151 else:
152 lbl += "it is not a layer.conf file."
153 conf_error(parent, lbl)
diff --git a/bitbake/lib/bb/ui/crumbs/packageselectionpage.py b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py
new file mode 100755
index 0000000000..8a8ab7585e
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py
@@ -0,0 +1,226 @@
1#!/usr/bin/env python
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2012 Intel Corporation
6#
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8# Authored by Shane Wang <shane.wang@intel.com>
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23import gtk
24import glib
25from bb.ui.crumbs.hobcolor import HobColors
26from bb.ui.crumbs.hobwidget import HobViewBar, HobViewTable
27from bb.ui.crumbs.hoblistmodel import PackageListModel
28from bb.ui.crumbs.hobpages import HobPage
29
30#
31# PackageSelectionPage
32#
33class PackageSelectionPage (HobPage):
34
35 pages = [
36 {
37 'name' : 'All packages',
38 'filter' : {},
39 'columns' : [{
40 'col_name' : 'Name',
41 'col_id' : PackageListModel.COL_NAME,
42 'col_style': 'text',
43 'col_min' : 100,
44 'col_max' : 400
45 }, {
46 'col_name' : 'size',
47 'col_id' : PackageListModel.COL_SIZE,
48 'col_style': 'text',
49 'col_min' : 100,
50 'col_max' : 500
51 }, {
52 'col_name' : 'Included',
53 'col_id' : PackageListModel.COL_INC,
54 'col_style': 'toggle',
55 'col_min' : 50,
56 'col_max' : 50
57 }]
58 }, {
59 'name' : 'Included',
60 'filter' : { PackageListModel.COL_INC : [True] },
61 'columns' : [{
62 'col_name' : 'Name',
63 'col_id' : PackageListModel.COL_NAME,
64 'col_style': 'text',
65 'col_min' : 100,
66 'col_max' : 300
67 }, {
68 'col_name' : 'Brought by',
69 'col_id' : PackageListModel.COL_BINB,
70 'col_style': 'text',
71 'col_min' : 100,
72 'col_max' : 350
73 }, {
74 'col_name' : 'size',
75 'col_id' : PackageListModel.COL_SIZE,
76 'col_style': 'text',
77 'col_min' : 100,
78 'col_max' : 300
79 }, {
80 'col_name' : 'Included',
81 'col_id' : PackageListModel.COL_INC,
82 'col_style': 'toggle',
83 'col_min' : 50,
84 'col_max' : 50
85 }]
86 }
87 ]
88
89 def __init__(self, builder):
90 super(PackageSelectionPage, self).__init__(builder, "Package Selection")
91
92 # set invisiable members
93 self.package_model = self.builder.package_model
94
95 # create visual elements
96 self.create_visual_elements()
97
98 def create_visual_elements(self):
99 self.label = gtk.Label("Packages included: 0\nSelected packages size: 0 MB")
100 self.eventbox = self.add_onto_top_bar(self.label, 73)
101 self.pack_start(self.eventbox, expand=False, fill=False)
102 self.pack_start(self.group_align, expand=True, fill=True)
103
104 # set visiable members
105 self.grid = gtk.Table(10, 1, True)
106 self.grid.set_col_spacings(3)
107
108 self.ins = gtk.Notebook()
109 self.ins.set_show_tabs(False)
110 self.tables = [] # we need to modify table when the dialog is shown
111 # append the tab
112 for i in range(len(self.pages)):
113 columns = self.pages[i]['columns']
114 tab = HobViewTable(columns, self.reset_clicked_cb, self.table_toggled_cb)
115 filter = self.pages[i]['filter']
116 tab.table_tree.set_model(self.package_model.tree_model(filter))
117 label = gtk.Label(self.pages[i]['name'])
118 self.ins.append_page(tab, label)
119 self.tables.append(tab)
120
121 self.grid.attach(self.ins, 0, 1, 1, 10, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND, 1, 1)
122 # a black bar associated with the notebook
123 self.topbar = HobViewBar(self.ins)
124 self.grid.attach(self.topbar, 0, 1, 0, 1, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND, 1, 1)
125 # set the search entry for each table
126 for tab in self.tables:
127 tab.table_tree.set_search_entry(self.topbar.search)
128
129 inctab_tree_view = self.tables[len(self.pages)-1].table_tree
130 inctab_tree_selection = inctab_tree_view.get_selection()
131 inctab_tree_selection.connect("changed", self.tree_selection_cb, inctab_tree_view)
132
133 # add all into the dialog
134 self.box_group_area.add(self.grid)
135
136 button_box = gtk.HBox(False, 5)
137 self.box_group_area.pack_start(button_box, expand=False, fill=False)
138
139 self.build_image_button = gtk.Button()
140 label = gtk.Label()
141 mark = "<span %s>Build image</span>" % self.span_tag('24px', 'bold')
142 label.set_markup(mark)
143 self.build_image_button.set_image(label)
144 self.build_image_button.set_size_request(205, 49)
145 self.build_image_button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.ORANGE))
146 self.build_image_button.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.ORANGE))
147 self.build_image_button.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.ORANGE))
148 self.build_image_button.set_tooltip_text("Build image to get your target image")
149 self.build_image_button.set_flags(gtk.CAN_DEFAULT)
150 self.build_image_button.grab_default()
151 self.build_image_button.connect("clicked", self.build_image_clicked_cb)
152 button_box.pack_end(self.build_image_button, expand=False, fill=False)
153
154 self.back_button = gtk.LinkButton("Go back to Image Configuration screen", "<< Back to image configuration")
155 self.back_button.connect("clicked", self.back_button_clicked_cb)
156 button_box.pack_start(self.back_button, expand=False, fill=False)
157
158 def tree_selection_cb(self, tree_selection, tree_view):
159 tree_model = tree_view.get_model()
160 path, column = tree_view.get_cursor()
161 if not path or column == tree_view.get_column(2):
162 return
163
164 it = tree_model.get_iter(path)
165 binb = tree_model.get_value(it, PackageListModel.COL_BINB)
166 if binb:
167 self.builder.show_binb_dialog(binb)
168
169 def build_image_clicked_cb(self, button):
170 self.builder.build_image()
171
172 def back_button_clicked_cb(self, button):
173 self.builder.show_configuration()
174
175 def _expand_all(self):
176 for tab in self.tables:
177 tab.table_tree.expand_all()
178
179 def refresh_selection(self):
180 self._expand_all()
181
182 self.builder.configuration.selected_packages = self.package_model.get_selected_packages()
183 selected_packages_num = len(self.builder.configuration.selected_packages)
184 selected_packages_size = float(self.package_model.get_packages_size())
185 selected_packages_size_str = self._size_to_string(selected_packages_size)
186
187 image_overhead_factor = self.builder.configuration.image_overhead_factor
188 image_rootfs_size = self.builder.configuration.image_rootfs_size
189 image_extra_size = self.builder.configuration.image_extra_size
190 base_size = image_overhead_factor * selected_packages_size
191 image_total_size = max(base_size, image_rootfs_size) + image_extra_size
192 image_total_size_str = self._size_to_string(image_total_size)
193
194 self.label.set_text("Packages included: %s\nSelected packages size: %s\nTotal image size: %s" %
195 (selected_packages_num, selected_packages_size_str, image_total_size_str))
196
197 """
198 Helper function to convert the package size to string format.
199 The unit of size is KB
200 """
201 def _size_to_string(self, size):
202 if len(str(int(size))) > 3:
203 size_str = '%.1f' % (size*1.0/1024) + ' MB'
204 else:
205 size_str = str(size) + ' KB'
206 return size_str
207
208 # Callback functions
209 def reset_clicked_cb(self, button):
210 self.package_model.reset()
211 self.builder.reset_package_model()
212
213 def toggle_item_idle_cb(self, path):
214 if not self.package_model.path_included(path):
215 self.package_model.include_item(item_path=path, binb="User Selected")
216 else:
217 self.package_model.exclude_item(item_path=path)
218
219 self.builder.window_sensitive(True)
220
221 def table_toggled_cb(self, cell, view_path, view_tree):
222 # Click to include a package
223 self.builder.window_sensitive(False)
224 view_model = view_tree.get_model()
225 path = self.package_model.convert_vpath_to_path(view_model, view_path)
226 glib.idle_add(self.toggle_item_idle_cb, path)
diff --git a/bitbake/lib/bb/ui/crumbs/progress.py b/bitbake/lib/bb/ui/crumbs/progress.py
deleted file mode 100644
index 0c7ad963b5..0000000000
--- a/bitbake/lib/bb/ui/crumbs/progress.py
+++ /dev/null
@@ -1,20 +0,0 @@
1import gtk
2
3class ProgressBar(gtk.Dialog):
4 def __init__(self, parent):
5
6 gtk.Dialog.__init__(self, flags=(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT))
7 self.set_title("Parsing metadata, please wait...")
8 self.set_default_size(500, 0)
9 self.set_transient_for(parent)
10 self.progress = gtk.ProgressBar()
11 self.vbox.pack_start(self.progress)
12 self.show_all()
13
14 def update(self, x, y):
15 self.progress.set_fraction(float(x)/float(y))
16 self.progress.set_text("%2d %%" % (x*100/y))
17
18 def pulse(self):
19 self.progress.set_text("Loading...")
20 self.progress.pulse()
diff --git a/bitbake/lib/bb/ui/crumbs/progressbar.py b/bitbake/lib/bb/ui/crumbs/progressbar.py
new file mode 100644
index 0000000000..882d461711
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/progressbar.py
@@ -0,0 +1,52 @@
1# BitBake Graphical GTK User Interface
2#
3# Copyright (C) 2011 Intel Corporation
4#
5# Authored by Shane Wang <shane.wang@intel.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20import gtk
21from bb.ui.crumbs.hobcolor import HobColors
22
23class HobProgressBar (gtk.ProgressBar):
24 def __init__(self):
25 gtk.ProgressBar.__init__(self)
26 self.set_rcstyle(True)
27 self.percentage = 0
28
29 def set_rcstyle(self, status):
30 rcstyle = gtk.RcStyle()
31 rcstyle.fg[2] = gtk.gdk.Color(HobColors.BLACK)
32 if status:
33 rcstyle.bg[3] = gtk.gdk.Color(HobColors.RUNNING)
34 else:
35 rcstyle.bg[3] = gtk.gdk.Color(HobColors.ERROR)
36 self.modify_style(rcstyle)
37
38 def set_title(self, text=None):
39 if not text:
40 text = ""
41 text += " %.0f%%" % self.percentage
42 self.set_text(text)
43
44 def reset(self):
45 self.set_fraction(0)
46 self.set_text("")
47 self.set_rcstyle(True)
48 self.percentage = 0
49
50 def update(self, fraction):
51 self.percentage = int(fraction * 100)
52 self.set_fraction(fraction)
diff --git a/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py b/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py
new file mode 100755
index 0000000000..73b8a1e4ef
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py
@@ -0,0 +1,221 @@
1#!/usr/bin/env python
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2012 Intel Corporation
6#
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8# Authored by Shane Wang <shane.wang@intel.com>
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23import gtk
24import glib
25from bb.ui.crumbs.hobcolor import HobColors
26from bb.ui.crumbs.hobwidget import HobWidget, HobViewBar, HobViewTable
27from bb.ui.crumbs.hoblistmodel import RecipeListModel
28from bb.ui.crumbs.hobpages import HobPage
29
30#
31# RecipeSelectionPage
32#
33class RecipeSelectionPage (HobPage):
34 pages = [
35 {
36 'name' : 'Recipe',
37 'filter' : { RecipeListModel.COL_TYPE : ['recipe'] },
38 'columns' : [{
39 'col_name' : 'Recipe',
40 'col_id' : RecipeListModel.COL_NAME,
41 'col_style': 'text',
42 'col_min' : 100,
43 'col_max' : 400
44 }, {
45 'col_name' : 'License',
46 'col_id' : RecipeListModel.COL_LIC,
47 'col_style': 'text',
48 'col_min' : 100,
49 'col_max' : 400
50 }, {
51 'col_name' : 'Group',
52 'col_id' : RecipeListModel.COL_GROUP,
53 'col_style': 'text',
54 'col_min' : 100,
55 'col_max' : 400
56 }, {
57 'col_name' : 'Included',
58 'col_id' : RecipeListModel.COL_INC,
59 'col_style': 'toggle',
60 'col_min' : 50,
61 'col_max' : 50
62 }]
63 }, {
64 'name' : 'Recipe Collection',
65 'filter' : { RecipeListModel.COL_TYPE : ['task'] },
66 'columns' : [{
67 'col_name' : 'Recipe Collection',
68 'col_id' : RecipeListModel.COL_NAME,
69 'col_style': 'text',
70 'col_min' : 100,
71 'col_max' : 400
72 }, {
73 'col_name' : 'Description',
74 'col_id' : RecipeListModel.COL_DESC,
75 'col_style': 'text',
76 'col_min' : 100,
77 'col_max' : 400
78 }, {
79 'col_name' : 'Included',
80 'col_id' : RecipeListModel.COL_INC,
81 'col_style': 'toggle',
82 'col_min' : 50,
83 'col_max' : 50
84 }]
85 }, {
86 'name' : 'Included',
87 'filter' : { RecipeListModel.COL_INC : [True],
88 RecipeListModel.COL_TYPE : ['recipe', 'task'] },
89 'columns' : [{
90 'col_name' : 'Recipe',
91 'col_id' : RecipeListModel.COL_NAME,
92 'col_style': 'text',
93 'col_min' : 100,
94 'col_max' : 400
95 }, {
96 'col_name' : 'Brought by',
97 'col_id' : RecipeListModel.COL_BINB,
98 'col_style': 'text',
99 'col_min' : 100,
100 'col_max' : 500
101 }, {
102 'col_name' : 'Included',
103 'col_id' : RecipeListModel.COL_INC,
104 'col_style': 'toggle',
105 'col_min' : 50,
106 'col_max' : 50
107 }]
108 }
109 ]
110
111 def __init__(self, builder = None):
112 super(RecipeSelectionPage, self).__init__(builder, "Recipe Selection")
113
114 # set invisiable members
115 self.recipe_model = self.builder.recipe_model
116
117 # create visual elements
118 self.create_visual_elements()
119
120 def create_visual_elements(self):
121 self.label = gtk.Label("Recipes included: %s" % len(self.builder.configuration.selected_recipes))
122 self.eventbox = self.add_onto_top_bar(self.label, 73)
123 self.pack_start(self.eventbox, expand=False, fill=False)
124 self.pack_start(self.group_align, expand=True, fill=True)
125
126 # set visiable members
127 self.grid = gtk.Table(10, 1, True)
128 self.grid.set_col_spacings(3)
129
130 # draw the left part of the window
131 # a notebook
132 self.ins = gtk.Notebook()
133 self.ins.set_show_tabs(False)
134 self.tables = [] # we need modify table when the dialog is shown
135 # append the tabs in order
136 for i in range(len(self.pages)):
137 columns = self.pages[i]['columns']
138 tab = HobViewTable(columns, self.reset_clicked_cb, self.table_toggled_cb)
139 filter = self.pages[i]['filter']
140 tab.table_tree.set_model(self.recipe_model.tree_model(filter))
141 label = gtk.Label(self.pages[i]['name'])
142 self.ins.append_page(tab, label)
143 self.tables.append(tab)
144
145 self.grid.attach(self.ins, 0, 1, 1, 10, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND)
146 # a black bar associated with the notebook
147 self.topbar = HobViewBar(self.ins)
148 self.grid.attach(self.topbar, 0, 1, 0, 1, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND)
149 # set the search entry for each table
150 for tab in self.tables:
151 tab.table_tree.set_search_entry(self.topbar.search)
152
153 inctab_tree_view = self.tables[len(self.pages)-1].table_tree
154 inctab_tree_selection = inctab_tree_view.get_selection()
155 inctab_tree_selection.connect("changed", self.tree_selection_cb, inctab_tree_view)
156
157 # add all into the window
158 self.box_group_area.add(self.grid)
159
160 button_box = gtk.HBox(False, 5)
161 self.box_group_area.pack_end(button_box, expand=False, fill=False)
162
163 self.build_packages_button = gtk.Button()
164 label = gtk.Label()
165 mark = "<span %s>Build packages</span>" % self.span_tag('24px', 'bold')
166 label.set_markup(mark)
167 self.build_packages_button.set_image(label)
168 self.build_packages_button.set_size_request(205, 49)
169 self.build_packages_button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.ORANGE))
170 self.build_packages_button.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.ORANGE))
171 self.build_packages_button.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.ORANGE))
172 self.build_packages_button.set_tooltip_text("Build packages for customization")
173 self.build_packages_button.set_flags(gtk.CAN_DEFAULT)
174 self.build_packages_button.grab_default()
175 self.build_packages_button.connect("clicked", self.build_packages_clicked_cb)
176 button_box.pack_end(self.build_packages_button, expand=False, fill=False)
177
178 self.back_button = gtk.LinkButton("Go back to Image Configuration screen", "<< Back to image configuration")
179 self.back_button.connect("clicked", self.back_button_clicked_cb)
180 button_box.pack_start(self.back_button, expand=False, fill=False)
181
182 def tree_selection_cb(self, tree_selection, tree_view):
183 tree_model = tree_view.get_model()
184 path, column = tree_view.get_cursor()
185 if not path or column == tree_view.get_column(2):
186 return
187
188 it = tree_model.get_iter(path)
189 binb = tree_model.get_value(it, RecipeListModel.COL_BINB)
190 if binb:
191 self.builder.show_binb_dialog(binb)
192
193 def build_packages_clicked_cb(self, button):
194 self.builder.build_packages()
195
196 def back_button_clicked_cb(self, button):
197 self.builder.show_configuration()
198
199 def refresh_selection(self):
200 self.builder.configuration.selected_image = self.recipe_model.get_selected_image()
201 _, self.builder.configuration.selected_recipes = self.recipe_model.get_selected_recipes()
202 self.label.set_text("Recipes included: %s" % len(self.builder.configuration.selected_recipes))
203
204 # Callback functions
205 def reset_clicked_cb(self, button):
206 self.builder.reset_recipe_model()
207
208 def toggle_item_idle_cb(self, path):
209 if not self.recipe_model.path_included(path):
210 self.recipe_model.include_item(item_path=path, binb="User Selected", image_contents=False)
211 else:
212 self.recipe_model.exclude_item(item_path=path)
213
214 self.builder.window_sensitive(True)
215
216 def table_toggled_cb(self, cell, view_path, view_tree):
217 # Click to include a recipe
218 self.builder.window_sensitive(False)
219 view_model = view_tree.get_model()
220 path = self.recipe_model.convert_vpath_to_path(view_model, view_path)
221 glib.idle_add(self.toggle_item_idle_cb, path)
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py
index 4f609fc622..718f692412 100644
--- a/bitbake/lib/bb/ui/crumbs/runningbuild.py
+++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -25,12 +25,7 @@ import logging
25import time 25import time
26import urllib 26import urllib
27import urllib2 27import urllib2
28 28from bb.ui.crumbs.hobcolor import HobColors
29class Colors(object):
30 OK = "#ffffff"
31 RUNNING = "#aaffaa"
32 WARNING ="#f88017"
33 ERROR = "#ffaaaa"
34 29
35class RunningBuildModel (gtk.TreeStore): 30class RunningBuildModel (gtk.TreeStore):
36 (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7) 31 (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7)
@@ -58,7 +53,10 @@ class RunningBuild (gobject.GObject):
58 ()), 53 ()),
59 'build-complete' : (gobject.SIGNAL_RUN_LAST, 54 'build-complete' : (gobject.SIGNAL_RUN_LAST,
60 gobject.TYPE_NONE, 55 gobject.TYPE_NONE,
61 ()) 56 ()),
57 'task-started' : (gobject.SIGNAL_RUN_LAST,
58 gobject.TYPE_NONE,
59 (gobject.TYPE_PYOBJECT,)),
62 } 60 }
63 pids_to_task = {} 61 pids_to_task = {}
64 tasks_to_iter = {} 62 tasks_to_iter = {}
@@ -108,13 +106,13 @@ class RunningBuild (gobject.GObject):
108 106
109 if event.levelno >= logging.ERROR: 107 if event.levelno >= logging.ERROR:
110 icon = "dialog-error" 108 icon = "dialog-error"
111 color = Colors.ERROR 109 color = HobColors.ERROR
112 elif event.levelno >= logging.WARNING: 110 elif event.levelno >= logging.WARNING:
113 icon = "dialog-warning" 111 icon = "dialog-warning"
114 color = Colors.WARNING 112 color = HobColors.WARNING
115 else: 113 else:
116 icon = None 114 icon = None
117 color = Colors.OK 115 color = HobColors.OK
118 116
119 # if we know which package we belong to, we'll append onto its list. 117 # if we know which package we belong to, we'll append onto its list.
120 # otherwise, we'll jump to the top of the master list 118 # otherwise, we'll jump to the top of the master list
@@ -152,7 +150,7 @@ class RunningBuild (gobject.GObject):
152 None, 150 None,
153 "Package: %s" % (package), 151 "Package: %s" % (package),
154 None, 152 None,
155 Colors.OK, 153 HobColors.OK,
156 0)) 154 0))
157 self.tasks_to_iter[(package, None)] = parent 155 self.tasks_to_iter[(package, None)] = parent
158 156
@@ -160,7 +158,7 @@ class RunningBuild (gobject.GObject):
160 # such. 158 # such.
161 # @todo if parent is already in error, don't mark it green 159 # @todo if parent is already in error, don't mark it green
162 self.model.set(parent, self.model.COL_ICON, "gtk-execute", 160 self.model.set(parent, self.model.COL_ICON, "gtk-execute",
163 self.model.COL_COLOR, Colors.RUNNING) 161 self.model.COL_COLOR, HobColors.RUNNING)
164 162
165 # Add an entry in the model for this task 163 # Add an entry in the model for this task
166 i = self.model.append (parent, (None, 164 i = self.model.append (parent, (None,
@@ -168,7 +166,7 @@ class RunningBuild (gobject.GObject):
168 task, 166 task,
169 "Task: %s" % (task), 167 "Task: %s" % (task),
170 "gtk-execute", 168 "gtk-execute",
171 Colors.RUNNING, 169 HobColors.RUNNING,
172 0)) 170 0))
173 171
174 # update the parent's active task count 172 # update the parent's active task count
@@ -179,10 +177,6 @@ class RunningBuild (gobject.GObject):
179 # that we need to attach to a task. 177 # that we need to attach to a task.
180 self.tasks_to_iter[(package, task)] = i 178 self.tasks_to_iter[(package, task)] = i
181 179
182 # If we don't handle these the GUI does not proceed
183 elif isinstance(event, bb.build.TaskInvalid):
184 return
185
186 elif isinstance(event, bb.build.TaskBase): 180 elif isinstance(event, bb.build.TaskBase):
187 current = self.tasks_to_iter[(package, task)] 181 current = self.tasks_to_iter[(package, task)]
188 parent = self.tasks_to_iter[(package, None)] 182 parent = self.tasks_to_iter[(package, None)]
@@ -194,20 +188,20 @@ class RunningBuild (gobject.GObject):
194 if isinstance(event, bb.build.TaskFailed): 188 if isinstance(event, bb.build.TaskFailed):
195 # Mark the task and parent as failed 189 # Mark the task and parent as failed
196 icon = "dialog-error" 190 icon = "dialog-error"
197 color = Colors.ERROR 191 color = HobColors.ERROR
198 192
199 logfile = event.logfile 193 logfile = event.logfile
200 if logfile and os.path.exists(logfile): 194 if logfile and os.path.exists(logfile):
201 with open(logfile) as f: 195 with open(logfile) as f:
202 logdata = f.read() 196 logdata = f.read()
203 self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', Colors.OK, 0)) 197 self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0))
204 198
205 for i in (current, parent): 199 for i in (current, parent):
206 self.model.set(i, self.model.COL_ICON, icon, 200 self.model.set(i, self.model.COL_ICON, icon,
207 self.model.COL_COLOR, color) 201 self.model.COL_COLOR, color)
208 else: 202 else:
209 icon = None 203 icon = None
210 color = Colors.OK 204 color = HobColors.OK
211 205
212 # Mark the task as inactive 206 # Mark the task as inactive
213 self.model.set(current, self.model.COL_ICON, icon, 207 self.model.set(current, self.model.COL_ICON, icon,
@@ -219,7 +213,7 @@ class RunningBuild (gobject.GObject):
219 if self.model.get(parent, self.model.COL_ICON) != 'dialog-error': 213 if self.model.get(parent, self.model.COL_ICON) != 'dialog-error':
220 self.model.set(parent, self.model.COL_ICON, icon) 214 self.model.set(parent, self.model.COL_ICON, icon)
221 if num_active == 0: 215 if num_active == 0:
222 self.model.set(parent, self.model.COL_COLOR, Colors.OK) 216 self.model.set(parent, self.model.COL_COLOR, HobColors.OK)
223 217
224 # Clear the iters and the pids since when the task goes away the 218 # Clear the iters and the pids since when the task goes away the
225 # pid will no longer be used for messages 219 # pid will no longer be used for messages
@@ -234,8 +228,12 @@ class RunningBuild (gobject.GObject):
234 None, 228 None,
235 "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), 229 "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
236 None, 230 None,
237 Colors.OK, 231 HobColors.OK,
238 0)) 232 0))
233 if pbar:
234 pbar.update(0, None, bb.event.getName(event))
235 pbar.set_title()
236
239 elif isinstance(event, bb.event.BuildCompleted): 237 elif isinstance(event, bb.event.BuildCompleted):
240 failures = int (event._failures) 238 failures = int (event._failures)
241 self.model.prepend(None, (None, 239 self.model.prepend(None, (None,
@@ -243,7 +241,7 @@ class RunningBuild (gobject.GObject):
243 None, 241 None,
244 "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), 242 "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
245 None, 243 None,
246 Colors.OK, 244 HobColors.OK,
247 0)) 245 0))
248 246
249 # Emit the appropriate signal depending on the number of failures 247 # Emit the appropriate signal depending on the number of failures
@@ -254,6 +252,8 @@ class RunningBuild (gobject.GObject):
254 # Emit a generic "build-complete" signal for things wishing to 252 # Emit a generic "build-complete" signal for things wishing to
255 # handle when the build is finished 253 # handle when the build is finished
256 self.emit("build-complete") 254 self.emit("build-complete")
255 if pbar:
256 pbar.set_text(event.msg)
257 257
258 elif isinstance(event, bb.command.CommandFailed): 258 elif isinstance(event, bb.command.CommandFailed):
259 if event.error.startswith("Exited with"): 259 if event.error.startswith("Exited with"):
@@ -280,6 +280,15 @@ class RunningBuild (gobject.GObject):
280 pbar.update(event.current, self.progress_total) 280 pbar.update(event.current, self.progress_total)
281 elif isinstance(event, bb.event.ParseCompleted) and pbar: 281 elif isinstance(event, bb.event.ParseCompleted) and pbar:
282 pbar.hide() 282 pbar.hide()
283 #using runqueue events as many as possible to update the progress bar
284 elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
285 message = {}
286 message["eventname"] = bb.event.getName(event)
287 num_of_completed = event.stats.completed + event.stats.failed
288 message["current"] = num_of_completed
289 message["total"] = event.stats.total
290 message["title"] = ""
291 self.emit("task-started", message)
283 292
284 return 293 return
285 294
diff --git a/bitbake/lib/bb/ui/crumbs/tasklistmodel.py b/bitbake/lib/bb/ui/crumbs/tasklistmodel.py
deleted file mode 100644
index 90a7e5459c..0000000000
--- a/bitbake/lib/bb/ui/crumbs/tasklistmodel.py
+++ /dev/null
@@ -1,620 +0,0 @@
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#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23import re
24
25class BuildRep(gobject.GObject):
26
27 def __init__(self, userpkgs, allpkgs, base_image=None):
28 gobject.GObject.__init__(self)
29 self.base_image = base_image
30 self.allpkgs = allpkgs
31 self.userpkgs = userpkgs
32
33 def loadRecipe(self, pathname):
34 contents = []
35 packages = ""
36 base_image = ""
37
38 with open(pathname, 'r') as f:
39 contents = f.readlines()
40
41 pkg_pattern = "^\s*(IMAGE_INSTALL)\s*([+=.?]+)\s*(\".*?\")"
42 img_pattern = "^\s*(require)\s+(\S+.bb)"
43
44 for line in contents:
45 matchpkg = re.search(pkg_pattern, line)
46 matchimg = re.search(img_pattern, line)
47 if matchpkg:
48 packages = packages + matchpkg.group(3).strip('"')
49 if matchimg:
50 base_image = os.path.basename(matchimg.group(2)).split(".")[0]
51
52 self.base_image = base_image
53 self.userpkgs = packages
54
55 def writeRecipe(self, writepath, model):
56 template = """
57# Recipe generated by the HOB
58
59require %s
60
61IMAGE_INSTALL += "%s"
62"""
63
64 empty_template = """
65# Recipe generated by the HOB
66
67inherit core-image
68
69IMAGE_INSTALL = "%s"
70"""
71 if self.base_image and not self.base_image == "empty":
72 meta_path = model.find_image_path(self.base_image)
73 recipe = template % (meta_path, self.userpkgs)
74 else:
75 recipe = empty_template % self.allpkgs
76
77 if os.path.exists(writepath):
78 os.rename(writepath, "%s~" % writepath)
79
80 with open(writepath, 'w') as r:
81 r.write(recipe)
82
83 return writepath
84
85class TaskListModel(gtk.ListStore):
86 """
87 This class defines an gtk.ListStore subclass which will convert the output
88 of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
89 providing convenience functions to access gtk.TreeModel subclasses which
90 provide filtered views of the data.
91 """
92 (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_PATH, COL_PN) = range(11)
93
94 __gsignals__ = {
95 "tasklist-populated" : (gobject.SIGNAL_RUN_LAST,
96 gobject.TYPE_NONE,
97 ()),
98 "contents-changed" : (gobject.SIGNAL_RUN_LAST,
99 gobject.TYPE_NONE,
100 (gobject.TYPE_INT,)),
101 "image-changed" : (gobject.SIGNAL_RUN_LAST,
102 gobject.TYPE_NONE,
103 (gobject.TYPE_STRING,)),
104 }
105
106 """
107 """
108 def __init__(self):
109 self.contents = None
110 self.tasks = None
111 self.packages = None
112 self.images = None
113 self.selected_image = None
114
115 gtk.ListStore.__init__ (self,
116 gobject.TYPE_STRING,
117 gobject.TYPE_STRING,
118 gobject.TYPE_STRING,
119 gobject.TYPE_STRING,
120 gobject.TYPE_STRING,
121 gobject.TYPE_STRING,
122 gobject.TYPE_STRING,
123 gobject.TYPE_BOOLEAN,
124 gobject.TYPE_BOOLEAN,
125 gobject.TYPE_STRING,
126 gobject.TYPE_STRING)
127
128 """
129 Helper method to determine whether name is a target pn
130 """
131 def non_target_name(self, name):
132 if ('-native' in name) or ('-cross' in name) or name.startswith('virtual/'):
133 return True
134 return False
135
136 def contents_changed_cb(self, tree_model, path, it=None):
137 pkg_cnt = self.contents.iter_n_children(None)
138 self.emit("contents-changed", pkg_cnt)
139
140 def contents_model_filter(self, model, it):
141 if not model.get_value(it, self.COL_INC) or model.get_value(it, self.COL_TYPE) == 'image':
142 return False
143 name = model.get_value(it, self.COL_NAME)
144 if self.non_target_name(name):
145 return False
146 else:
147 return True
148
149 """
150 Create, if required, and return a filtered gtk.TreeModel
151 containing only the items which are to be included in the
152 image
153 """
154 def contents_model(self):
155 if not self.contents:
156 self.contents = self.filter_new()
157 self.contents.set_visible_func(self.contents_model_filter)
158 self.contents.connect("row-inserted", self.contents_changed_cb)
159 self.contents.connect("row-deleted", self.contents_changed_cb)
160 return self.contents
161
162 """
163 Helper function to determine whether an item is a task
164 """
165 def task_model_filter(self, model, it):
166 if model.get_value(it, self.COL_TYPE) == 'task':
167 return True
168 else:
169 return False
170
171 """
172 Create, if required, and return a filtered gtk.TreeModel
173 containing only the items which are tasks
174 """
175 def tasks_model(self):
176 if not self.tasks:
177 self.tasks = self.filter_new()
178 self.tasks.set_visible_func(self.task_model_filter)
179 return self.tasks
180
181 """
182 Helper function to determine whether an item is an image
183 """
184 def image_model_filter(self, model, it):
185 if model.get_value(it, self.COL_TYPE) == 'image':
186 return True
187 else:
188 return False
189
190 """
191 Create, if required, and return a filtered gtk.TreeModel
192 containing only the items which are images
193 """
194 def images_model(self):
195 if not self.images:
196 self.images = self.filter_new()
197 self.images.set_visible_func(self.image_model_filter)
198 return self.images
199
200 """
201 Helper function to determine whether an item is a package
202 """
203 def package_model_filter(self, model, it):
204 if model.get_value(it, self.COL_TYPE) != 'package':
205 return False
206 else:
207 name = model.get_value(it, self.COL_NAME)
208 if self.non_target_name(name):
209 return False
210 return True
211
212 """
213 Create, if required, and return a filtered gtk.TreeModel
214 containing only the items which are packages
215 """
216 def packages_model(self):
217 if not self.packages:
218 self.packages = self.filter_new()
219 self.packages.set_visible_func(self.package_model_filter)
220 return self.packages
221
222 """
223 The populate() function takes as input the data from a
224 bb.event.TargetsTreeGenerated event and populates the TaskList.
225 Once the population is done it emits gsignal tasklist-populated
226 to notify any listeners that the model is ready
227 """
228 def populate(self, event_model):
229 # First clear the model, in case repopulating
230 self.clear()
231 for item in event_model["pn"]:
232 atype = 'package'
233 name = item
234 summary = event_model["pn"][item]["summary"]
235 lic = event_model["pn"][item]["license"]
236 group = event_model["pn"][item]["section"]
237 filename = event_model["pn"][item]["filename"]
238 if ('task-' in name):
239 atype = 'task'
240 elif ('-image-' in name):
241 atype = 'image'
242
243 # Create a combined list of build and runtime dependencies and
244 # then remove any duplicate entries and any entries for -dev
245 # packages
246 depends = event_model["depends"].get(item, [])
247 rdepends = event_model["rdepends-pn"].get(item, [])
248 packages = {}
249 for pkg in event_model["packages"]:
250 if event_model["packages"][pkg]["pn"] == name:
251 deps = []
252 deps.extend(depends)
253 deps.extend(event_model["rdepends-pkg"].get(pkg, []))
254 deps.extend(rdepends)
255 deps = self.squish(deps)
256 # rdepends-pn includes pn-dev
257 if ("%s-dev" % item) in deps:
258 deps.remove("%s-dev" % item)
259 # rdepends-on includes pn
260 if pkg in deps:
261 deps.remove(pkg)
262 packages[pkg] = deps
263
264 for p in packages:
265 self.set(self.append(), self.COL_NAME, p, self.COL_DESC, summary,
266 self.COL_LIC, lic, self.COL_GROUP, group,
267 self.COL_DEPS, " ".join(packages[p]), self.COL_BINB, "",
268 self.COL_TYPE, atype, self.COL_INC, False,
269 self.COL_IMG, False, self.COL_PATH, filename,
270 self.COL_PN, item)
271
272 self.emit("tasklist-populated")
273
274 """
275 Load a BuildRep into the model
276 """
277 def load_image_rep(self, rep):
278 # Unset everything
279 it = self.get_iter_first()
280 while it:
281 path = self.get_path(it)
282 self[path][self.COL_INC] = False
283 self[path][self.COL_IMG] = False
284 it = self.iter_next(it)
285
286 # Iterate the images and disable them all
287 it = self.images.get_iter_first()
288 while it:
289 path = self.images.convert_path_to_child_path(self.images.get_path(it))
290 name = self[path][self.COL_NAME]
291 if name == rep.base_image:
292 self.include_item(path, image_contents=True)
293 else:
294 self[path][self.COL_INC] = False
295 it = self.images.iter_next(it)
296
297 # Mark all of the additional packages for inclusion
298 packages = rep.userpkgs.split(" ")
299 it = self.get_iter_first()
300 while it:
301 path = self.get_path(it)
302 name = self[path][self.COL_NAME]
303 if name in packages:
304 self.include_item(path, binb="User Selected")
305 packages.remove(name)
306 it = self.iter_next(it)
307
308 self.emit("image-changed", rep.base_image)
309
310 """
311 squish lst so that it doesn't contain any duplicate entries
312 """
313 def squish(self, lst):
314 seen = {}
315 for l in lst:
316 seen[l] = 1
317 return seen.keys()
318
319 """
320 Mark the item at path as not included
321 NOTE:
322 path should be a gtk.TreeModelPath into self (not a filtered model)
323 """
324 def remove_item_path(self, path):
325 self[path][self.COL_BINB] = ""
326 self[path][self.COL_INC] = False
327
328 """
329 Recursively called to mark the item at opath and any package which
330 depends on it for removal.
331 NOTE: This method dumbly removes user selected packages and since we don't
332 do significant reverse dependency tracking it's easier and simpler to save
333 the items marked as user selected and re-add them once the removal sweep is
334 complete.
335 """
336 def mark(self, opath):
337 usersel = {}
338 removed = []
339
340 it = self.get_iter_first()
341 # The name of the item we're removing, so that we can use it to find
342 # other items which either depend on it, or were brought in by it
343 marked_name = self[opath][self.COL_NAME]
344
345 # Remove the passed item
346 self.remove_item_path(opath)
347
348 # Remove all dependent packages, update binb
349 while it:
350 path = self.get_path(it)
351 it = self.iter_next(it)
352
353 inc = self[path][self.COL_INC]
354 deps = self[path][self.COL_DEPS]
355 binb = self[path][self.COL_BINB].split(', ')
356 itype = self[path][self.COL_TYPE]
357 itname = self[path][self.COL_NAME]
358
359 # We ignore anything that isn't a package
360 if not itype == "package":
361 continue
362
363 # If the user added this item and it's not the item we're removing
364 # we should keep it and its dependencies, the easiest way to do so
365 # is to save its name and re-mark it for inclusion once dependency
366 # processing is complete
367 if "User Selected" in binb:
368 usersel[itname] = self[path][self.COL_IMG]
369
370 # If the iterated item is included and depends on the removed
371 # item it should also be removed.
372 # FIXME: need to ensure partial name matching doesn't happen
373 if inc and marked_name in deps and itname not in removed:
374 # found a dependency, remove it
375 removed.append(itname)
376 self.mark(path)
377
378 # If the iterated item was brought in by the removed (passed) item
379 # try and find an alternative dependee and update the binb column
380 if inc and marked_name in binb:
381 binb.remove(marked_name)
382 self[path][self.COL_BINB] = ', '.join(binb).lstrip(', ')
383
384 # Re-add any removed user selected items
385 for u in usersel:
386 npath = self.find_path_for_item(u)
387 self.include_item(item_path=npath,
388 binb="User Selected",
389 image_contents=usersel[u])
390 """
391 Remove items from contents if the have an empty COL_BINB (brought in by)
392 caused by all packages they are a dependency of being removed.
393 If the item isn't a package we leave it included.
394 """
395 def sweep_up(self):
396 it = self.contents.get_iter_first()
397 while it:
398 binb = self.contents.get_value(it, self.COL_BINB)
399 itype = self.contents.get_value(it, self.COL_TYPE)
400 remove = False
401
402 if itype == 'package' and not binb:
403 oit = self.contents.convert_iter_to_child_iter(it)
404 opath = self.get_path(oit)
405 self.mark(opath)
406 remove = True
407
408 # When we remove a package from the contents model we alter the
409 # model, so continuing to iterate is bad. *Furthermore* it's
410 # likely that the removal has affected an already iterated item
411 # so we should start from the beginning anyway.
412 # Only when we've managed to iterate the entire contents model
413 # without removing any items do we allow the loop to exit.
414 if remove:
415 it = self.contents.get_iter_first()
416 else:
417 it = self.contents.iter_next(it)
418
419 """
420 Check whether the item at item_path is included or not
421 """
422 def contents_includes_path(self, item_path):
423 return self[item_path][self.COL_INC]
424
425 """
426 Add this item, and any of its dependencies, to the image contents
427 """
428 def include_item(self, item_path, binb="", image_contents=False):
429 item_name = self[item_path][self.COL_NAME]
430 item_deps = self[item_path][self.COL_DEPS]
431
432 self[item_path][self.COL_INC] = True
433
434 item_bin = self[item_path][self.COL_BINB].split(', ')
435 if binb and not binb in item_bin:
436 item_bin.append(binb)
437 self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
438
439 # We want to do some magic with things which are brought in by the
440 # base image so tag them as so
441 if image_contents:
442 self[item_path][self.COL_IMG] = True
443 if self[item_path][self.COL_TYPE] == 'image':
444 self.selected_image = item_name
445
446 if item_deps:
447 # Ensure all of the items deps are included and, where appropriate,
448 # add this item to their COL_BINB
449 for dep in item_deps.split(" "):
450 # If the contents model doesn't already contain dep, add it
451 dep_path = self.find_path_for_item(dep)
452 if not dep_path:
453 continue
454 dep_included = self.contents_includes_path(dep_path)
455
456 if dep_included and not dep in item_bin:
457 # don't set the COL_BINB to this item if the target is an
458 # item in our own COL_BINB
459 dep_bin = self[dep_path][self.COL_BINB].split(', ')
460 if not item_name in dep_bin:
461 dep_bin.append(item_name)
462 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
463 elif not dep_included:
464 self.include_item(dep_path, binb=item_name, image_contents=image_contents)
465
466 """
467 Find the model path for the item_name
468 Returns the path in the model or None
469 """
470 def find_path_for_item(self, item_name):
471 # We don't include virtual/* or *-native items in the model so save a
472 # heavy iteration loop by exiting early for these items
473 if self.non_target_name(item_name):
474 return None
475
476 it = self.get_iter_first()
477 while it:
478 if (self.get_value(it, self.COL_NAME) == item_name):
479 return self.get_path(it)
480 else:
481 it = self.iter_next(it)
482 return None
483
484 """
485 Empty self.contents by setting the include of each entry to None
486 """
487 def reset(self):
488 # Deselect images - slightly more complex logic so that we don't
489 # have to iterate all of the contents of the main model, instead
490 # just iterate the images model.
491 if self.selected_image:
492 iit = self.images.get_iter_first()
493 while iit:
494 pit = self.images.convert_iter_to_child_iter(iit)
495 self.set(pit, self.COL_INC, False)
496 iit = self.images.iter_next(iit)
497 self.selected_image = None
498
499 it = self.contents.get_iter_first()
500 while it:
501 oit = self.contents.convert_iter_to_child_iter(it)
502 self.set(oit,
503 self.COL_INC, False,
504 self.COL_BINB, "",
505 self.COL_IMG, False)
506 # As we've just removed the first item...
507 it = self.contents.get_iter_first()
508
509 """
510 Returns two lists. One of user selected packages and the other containing
511 all selected packages
512 """
513 def get_selected_packages(self):
514 allpkgs = []
515 userpkgs = []
516
517 it = self.contents.get_iter_first()
518 while it:
519 sel = "User Selected" in self.contents.get_value(it, self.COL_BINB)
520 name = self.contents.get_value(it, self.COL_NAME)
521 allpkgs.append(name)
522 if sel:
523 userpkgs.append(name)
524 it = self.contents.iter_next(it)
525 return userpkgs, allpkgs
526
527 """
528 Return a squished (uniquified) list of the PN's of all selected items
529 """
530 def get_selected_pn(self):
531 pns = []
532
533 it = self.contents.get_iter_first()
534 while it:
535 if self.contents.get_value(it, self.COL_BINB):
536 pns.append(self.contents.get_value(it, self.COL_PN))
537 it = self.contents.iter_next(it)
538
539 return self.squish(pns)
540
541 def image_contents_removed(self):
542 it = self.get_iter_first()
543 while it:
544 sel = self.get_value(it, self.COL_INC)
545 img = self.get_value(it, self.COL_IMG)
546 if img and not sel:
547 return True
548 it = self.iter_next(it)
549 return False
550
551 def get_build_rep(self):
552 userpkgs, allpkgs = self.get_selected_packages()
553 # If base image contents have been removed start from an empty rootfs
554 if not self.selected_image or self.image_contents_removed():
555 image = "empty"
556 else:
557 image = self.selected_image
558
559 return BuildRep(" ".join(userpkgs), " ".join(allpkgs), image)
560
561 def find_reverse_depends(self, pn):
562 revdeps = []
563 it = self.contents.get_iter_first()
564
565 while it:
566 name = self.contents.get_value(it, self.COL_NAME)
567 itype = self.contents.get_value(it, self.COL_TYPE)
568 deps = self.contents.get_value(it, self.COL_DEPS)
569
570 it = self.contents.iter_next(it)
571
572 if not itype == 'package':
573 continue
574
575 if pn in deps:
576 revdeps.append(name)
577
578 if pn in revdeps:
579 revdeps.remove(pn)
580 return revdeps
581
582 def set_selected_image(self, img):
583 self.selected_image = img
584 path = self.find_path_for_item(img)
585 self.include_item(item_path=path,
586 binb="User Selected",
587 image_contents=True)
588
589 self.emit("image-changed", self.selected_image)
590
591 def set_selected_packages(self, pkglist):
592 selected = pkglist
593 it = self.get_iter_first()
594
595 while it:
596 name = self.get_value(it, self.COL_NAME)
597 if name in pkglist:
598 pkglist.remove(name)
599 path = self.get_path(it)
600 self.include_item(item_path=path,
601 binb="User Selected")
602 if len(pkglist) == 0:
603 return
604 it = self.iter_next(it)
605
606 def find_image_path(self, image):
607 it = self.images.get_iter_first()
608
609 while it:
610 image_name = self.images.get_value(it, self.COL_NAME)
611 if image_name == image:
612 path = self.images.get_value(it, self.COL_PATH)
613 meta_pattern = "(\S*)/(meta*/)(\S*)"
614 meta_match = re.search(meta_pattern, path)
615 if meta_match:
616 _, lyr, bbrel = path.partition(meta_match.group(2))
617 if bbrel:
618 path = bbrel
619 return path
620 it = self.images.iter_next(it)
diff --git a/bitbake/lib/bb/ui/crumbs/template.py b/bitbake/lib/bb/ui/crumbs/template.py
new file mode 100644
index 0000000000..d0283546af
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/template.py
@@ -0,0 +1,180 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011 Intel Corporation
5#
6# Authored by Shane Wang <shane.wang@intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gobject
22import os
23import re
24
25class File(gobject.GObject):
26
27 def __init__(self, pathfilename, suffix):
28 if not pathfilename.endswith(suffix):
29 pathfilename = "%s%s" % (pathfilename, suffix)
30 gobject.GObject.__init__(self)
31 self.pathfilename = pathfilename
32
33 def readFile(self):
34 if not os.path.isfile(self.pathfilename):
35 return None
36 if not os.path.exists(self.pathfilename):
37 return None
38
39 with open(self.pathfilename, 'r') as f:
40 contents = f.readlines()
41 f.close()
42
43 return contents
44
45 def writeFile(self, contents):
46 if os.path.exists(self.pathfilename):
47 orig = "%s.orig" % self.pathfilename
48 if os.path.exists(orig):
49 os.remove(orig)
50 os.rename(self.pathfilename, orig)
51
52 with open(self.pathfilename, 'w') as f:
53 f.write(contents)
54 f.close()
55
56class ConfigFile(File):
57 """
58 This object does save general config file. (say bblayers.conf, or local.conf). Again, it is the base class for other template files and image bb files.
59 """
60 def __init__(self, pathfilename, suffix = None, header = None):
61 if suffix:
62 File.__init__(self, pathfilename, suffix)
63 else:
64 File.__init__(self, pathfilename, ".conf")
65 if header:
66 self.header = header
67 else:
68 self.header = "# Config generated by the HOB\n\n"
69 self.dictionary = {}
70
71 def setVar(self, var, val):
72 if isinstance(val, list):
73 liststr = ""
74 if val:
75 i = 0
76 for value in val:
77 if i < len(val) - 1:
78 liststr += "%s " % value
79 else:
80 liststr += "%s" % value
81 i += 1
82 self.dictionary[var] = liststr
83 else:
84 self.dictionary[var] = val
85
86 def save(self):
87 contents = self.header
88 for var, val in self.dictionary.items():
89 contents += "%s = \"%s\"\n" % (var, val)
90 File.writeFile(self, contents)
91
92class HobTemplateFile(ConfigFile):
93 """
94 This object does save or load hob specific file.
95 """
96 def __init__(self, pathfilename):
97 ConfigFile.__init__(self, pathfilename, ".hob", "# Hob Template generated by the HOB\n\n")
98
99 def getVar(self, var):
100 if var in self.dictionary:
101 return self.dictionary[var]
102 else:
103 return ""
104
105 def load(self):
106 contents = ConfigFile.readFile(self)
107 self.dictionary.clear()
108
109 pattern = "^\s*(\S+)\s*=\s*(\".*?\")"
110
111 for line in contents:
112 match = re.search(pattern, line)
113 if match:
114 var = match.group(1)
115 val = match.group(2).strip('"')
116 self.dictionary[var] = val
117 return self.dictionary
118
119class RecipeFile(ConfigFile):
120 """
121 This object is for image bb file.
122 """
123 def __init__(self, pathfilename):
124 ConfigFile.__init__(self, pathfilename, ".bb", "# Recipe generated by the HOB\n\ninherit core-image\n")
125
126class TemplateMgr(gobject.GObject):
127
128 __gLocalVars__ = ["MACHINE", "PACKAGE_CLASSES", "DISTRO", "DL_DIR", "SSTATE_DIR", "SSTATE_MIRROR", "PARALLEL_MAKE", "BB_NUMBER_THREAD"]
129 __gBBLayersVars__ = ["BBLAYERS"]
130 __gRecipeVars__ = ["DEPENDS", "IMAGE_INSTALL"]
131
132 def __init__(self):
133 gobject.GObject.__init__(self)
134 self.template_hob = None
135 self.bblayers_conf = None
136 self.local_conf = None
137 self.image_bb = None
138
139 def open(self, filename, path):
140 self.template_hob = HobTemplateFile("%s/%s%s%s" % (path, "template-", filename, ".hob"))
141 self.bblayers_conf = ConfigFile("%s/%s%s%s" % (path, "bblayers-", filename, ".conf"))
142 self.local_conf = ConfigFile("%s/%s%s%s" % (path, "local-", filename, ".conf"))
143 self.image_bb = RecipeFile("%s/%s%s%s" % (path, "hob-image-", filename, ".bb"))
144
145 def setVar(self, var, val):
146 if var in TemplateMgr.__gLocalVars__:
147 self.local_conf.setVar(var, val)
148 if var in TemplateMgr.__gBBLayersVars__:
149 self.bblayers_conf.setVar(var, val)
150 if var in TemplateMgr.__gRecipeVars__:
151 self.image_bb.setVar(var, val)
152
153 self.template_hob.setVar(var, val)
154
155 def save(self):
156 self.local_conf.save()
157 self.bblayers_conf.save()
158 self.image_bb.save()
159 self.template_hob.save()
160
161 def load(self, path):
162 self.template_hob = HobTemplateFile(path)
163 self.dictionary = self.template_hob.load()
164
165 def getVar(self, var):
166 return self.template_hob.getVar(var)
167
168 def destroy(self):
169 if self.template_hob:
170 del self.template_hob
171 template_hob = None
172 if self.bblayers_conf:
173 del self.bblayers_conf
174 self.bblayers_conf = None
175 if self.local_conf:
176 del self.local_conf
177 self.local_conf = None
178 if self.image_bb:
179 del self.image_bb
180 self.image_bb = None
diff --git a/bitbake/lib/bb/ui/hob.py b/bitbake/lib/bb/ui/hob.py
index 0fcaad54a7..429bb750dd 100644..100755
--- a/bitbake/lib/bb/ui/hob.py
+++ b/bitbake/lib/bb/ui/hob.py
@@ -1,9 +1,11 @@
1#!/usr/bin/env python
1# 2#
2# BitBake Graphical GTK User Interface 3# BitBake Graphical GTK User Interface
3# 4#
4# Copyright (C) 2011 Intel Corporation 5# Copyright (C) 2011 Intel Corporation
5# 6#
6# Authored by Joshua Lock <josh@linux.intel.com> 7# Authored by Joshua Lock <josh@linux.intel.com>
8# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
7# 9#
8# This program is free software; you can redistribute it and/or modify 10# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as 11# it under the terms of the GNU General Public License version 2 as
@@ -18,1087 +20,58 @@
18# with this program; if not, write to the Free Software Foundation, Inc., 20# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 22
21import glib
22import gobject 23import gobject
23import gtk 24import gtk
24from bb.ui.crumbs.tasklistmodel import TaskListModel, BuildRep 25import sys
26import os
27sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
28try:
29 import bb
30except RuntimeError as exc:
31 sys.exit(str(exc))
32from bb.ui import uihelper
33from bb.ui.crumbs.hoblistmodel import RecipeListModel, PackageListModel
25from bb.ui.crumbs.hobeventhandler import HobHandler 34from bb.ui.crumbs.hobeventhandler import HobHandler
26from bb.ui.crumbs.configurator import Configurator 35from bb.ui.crumbs.builder import Builder
27from bb.ui.crumbs.hobprefs import HobPrefs
28from bb.ui.crumbs.layereditor import LayerEditor
29from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild
30from bb.ui.crumbs.hig import CrumbsDialog
31import xmlrpclib
32import logging
33import Queue
34 36
35extraCaches = ['bb.cache_extra:HobRecipeInfo'] 37extraCaches = ['bb.cache_extra:HobRecipeInfo']
36 38
37class MainWindow (gtk.Window): 39def event_handle_idle_func(eventHandler, hobHandler):
38 40 # Consume as many messages as we can in the time available to us
39 def __init__(self, taskmodel, handler, configurator, prefs, layers, mach): 41 if not eventHandler:
40 gtk.Window.__init__(self)
41 # global state
42 self.curr_mach = mach
43 self.machine_handler_id = None
44 self.image_combo_id = None
45 self.generating = False
46 self.files_to_clean = []
47 self.selected_image = None
48 self.selected_packages = None
49 self.stopping = False
50
51 self.model = taskmodel
52 self.model.connect("tasklist-populated", self.update_model)
53 self.model.connect("image-changed", self.image_changed_string_cb)
54 self.handler = handler
55 self.configurator = configurator
56 self.prefs = prefs
57 self.layers = layers
58 self.save_path = None
59 self.dirty = False
60 self.build_succeeded = False
61
62 self.connect("delete-event", self.destroy_window)
63 self.set_title("Image Creator")
64 self.set_icon_name("applications-development")
65 self.set_default_size(1000, 650)
66
67 self.build = RunningBuild(sequential=True)
68 self.build.connect("build-failed", self.running_build_failed_cb)
69 self.build.connect("build-succeeded", self.running_build_succeeded_cb)
70 self.build.connect("build-started", self.build_started_cb)
71 self.build.connect("build-complete", self.build_complete_cb)
72
73 vbox = gtk.VBox(False, 0)
74 vbox.set_border_width(0)
75 vbox.show()
76 self.add(vbox)
77 self.menu = self.create_menu()
78 vbox.pack_start(self.menu, False)
79 createview = self.create_build_gui()
80 self.back = None
81 self.cancel = None
82 buildview = self.view_build_gui()
83 self.nb = gtk.Notebook()
84 self.nb.append_page(createview)
85 self.nb.append_page(buildview)
86 self.nb.set_current_page(0)
87 self.nb.set_show_tabs(False)
88 vbox.pack_start(self.nb, expand=True, fill=True)
89
90 def destroy_window(self, widget, event):
91 self.quit()
92
93 def menu_quit(self, action):
94 self.quit()
95
96 def quit(self):
97 if self.dirty and len(self.model.contents):
98 question = "Would you like to save your customisations?"
99 dialog = CrumbsDialog(self, question, gtk.STOCK_DIALOG_WARNING)
100 dialog.add_buttons(gtk.STOCK_NO, gtk.RESPONSE_NO,
101 gtk.STOCK_YES, gtk.RESPONSE_YES)
102 resp = dialog.run()
103 dialog.destroy()
104 if resp == gtk.RESPONSE_YES:
105 if not self.save_path:
106 self.get_save_path()
107
108 if self.save_path:
109 self.save_recipe_file()
110 rep = self.model.get_build_rep()
111 rep.writeRecipe(self.save_path, self.model)
112
113 # Prevent the busy cursor being shown after hob exits if quit is called
114 # whilst the busy cursor is set
115 self.set_busy_cursor(False)
116
117 self.handler.remove_temp_dir()
118
119 gtk.main_quit()
120
121 """
122 In the case of a fatal error give the user as much information as possible
123 and then exit.
124 """
125 def fatal_error_cb(self, handler, errormsg, phase):
126 lbl = "<b>Error!</b>\nThere was an unrecoverable error during the"
127 lbl = lbl + " <i>%s</i> phase of BitBake. This must be" % phase
128 lbl = lbl + " rectified before the GUI will function. The error"
129 lbl = lbl + " message which which caused this is:\n\n\"%s\"" % errormsg
130 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_ERROR)
131 dialog.add_button("Exit", gtk.RESPONSE_OK)
132 response = dialog.run()
133 dialog.destroy()
134 self.set_busy_cursor(False)
135 gtk.main_quit()
136
137 def scroll_tv_cb(self, model, path, it, view):
138 view.scroll_to_cell(path)
139
140 def running_build_succeeded_cb(self, running_build):
141 self.build_succeeded = True
142
143 def running_build_failed_cb(self, running_build):
144 self.build_succeeded = False
145
146 def image_changed_string_cb(self, model, new_image):
147 self.selected_image = new_image
148 # disconnect the image combo's signal handler
149 if self.image_combo_id:
150 self.image_combo.disconnect(self.image_combo_id)
151 self.image_combo_id = None
152 cnt = 0
153 it = self.model.images.get_iter_first()
154 while it:
155 path = self.model.images.get_path(it)
156 if self.model.images[path][self.model.COL_NAME] == new_image:
157 self.image_combo.set_active(cnt)
158 break
159 it = self.model.images.iter_next(it)
160 cnt = cnt + 1
161 # Reconnect the signal handler
162 if not self.image_combo_id:
163 self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
164
165 def image_changed_cb(self, combo):
166 model = self.image_combo.get_model()
167 it = self.image_combo.get_active_iter()
168 if it:
169 path = model.get_path(it)
170 # Firstly, deselect the previous image
171 userp, _ = self.model.get_selected_packages()
172 self.model.reset()
173 # Now select the new image and save its path in case we
174 # change the image later
175 self.toggle_package(path, model, image=True)
176 if len(userp):
177 self.model.set_selected_packages(userp)
178 self.selected_image = model[path][self.model.COL_NAME]
179
180 def reload_triggered_cb(self, handler, image, packages):
181 if image:
182 self.selected_image = image
183 if len(packages):
184 self.selected_packages = packages.split()
185
186 def data_generated(self, handler):
187 self.generating = False
188 self.enable_widgets()
189
190 def machine_combo_changed_cb(self, combo, handler):
191 mach = combo.get_active_text()
192 if mach != self.curr_mach:
193 self.curr_mach = mach
194 # Flush this straight to the file as MACHINE is changed
195 # independently of other 'Preferences'
196 self.configurator.setConfVar('MACHINE', mach)
197 self.configurator.writeConf()
198 handler.set_machine(mach)
199 handler.reload_data()
200
201 def update_machines(self, handler, machines):
202 active = 0
203 # disconnect the signal handler before updating the combo model
204 if self.machine_handler_id:
205 self.machine_combo.disconnect(self.machine_handler_id)
206 self.machine_handler_id = None
207
208 model = self.machine_combo.get_model()
209 if model:
210 model.clear()
211
212 for machine in machines:
213 self.machine_combo.append_text(machine)
214 if machine == self.curr_mach:
215 self.machine_combo.set_active(active)
216 active = active + 1
217
218 self.machine_handler_id = self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler)
219
220 def set_busy_cursor(self, busy=True):
221 """
222 Convenience method to set the cursor to a spinner when executing
223 a potentially lengthy process.
224 A busy value of False will set the cursor back to the default
225 left pointer.
226 """
227 if busy:
228 cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
229 else:
230 # TODO: presumably the default cursor is different on RTL
231 # systems. Can we determine the default cursor? Or at least
232 # the cursor which is set before we change it?
233 cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
234 window = self.get_root_window()
235 window.set_cursor(cursor)
236
237 def busy_idle_func(self):
238 if self.generating:
239 self.progress.pulse()
240 return True
241 else:
242 if not self.image_combo_id:
243 self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
244 self.progress.set_text("Loaded")
245 self.progress.set_fraction(0.0)
246 self.set_busy_cursor(False)
247 return False
248
249 def busy(self, handler):
250 self.generating = True
251 self.progress.set_text("Loading...")
252 self.set_busy_cursor()
253 if self.image_combo_id:
254 self.image_combo.disconnect(self.image_combo_id)
255 self.image_combo_id = None
256 self.progress.pulse()
257 gobject.timeout_add (100, self.busy_idle_func)
258 self.disable_widgets()
259
260 def enable_widgets(self):
261 self.menu.set_sensitive(True)
262 self.machine_combo.set_sensitive(True)
263 self.image_combo.set_sensitive(True)
264 self.nb.set_sensitive(True)
265 self.contents_tree.set_sensitive(True)
266
267 def disable_widgets(self):
268 self.menu.set_sensitive(False)
269 self.machine_combo.set_sensitive(False)
270 self.image_combo.set_sensitive(False)
271 self.nb.set_sensitive(False)
272 self.contents_tree.set_sensitive(False)
273
274 def update_model(self, model):
275 # We want the packages model to be alphabetised and sortable so create
276 # a TreeModelSort to use in the view
277 pkgsaz_model = gtk.TreeModelSort(self.model.packages_model())
278 pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING)
279 # Unset default sort func so that we only toggle between A-Z and
280 # Z-A sorting
281 pkgsaz_model.set_default_sort_func(None)
282 self.pkgsaz_tree.set_model(pkgsaz_model)
283
284 self.image_combo.set_model(self.model.images_model())
285 # Without this the image combo is incorrectly sized on first load of the GUI
286 self.image_combo.set_active(0)
287 self.image_combo.set_active(-1)
288
289 if not self.image_combo_id:
290 self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
291
292 # We want the contents to be alphabetised so create a TreeModelSort to
293 # use in the view
294 contents_model = gtk.TreeModelSort(self.model.contents_model())
295 contents_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING)
296 # Unset default sort func so that we only toggle between A-Z and
297 # Z-A sorting
298 contents_model.set_default_sort_func(None)
299 self.contents_tree.set_model(contents_model)
300 self.tasks_tree.set_model(self.model.tasks_model())
301
302 if self.selected_image:
303 if self.image_combo_id:
304 self.image_combo.disconnect(self.image_combo_id)
305 self.image_combo_id = None
306 self.model.set_selected_image(self.selected_image)
307 if not self.image_combo_id:
308 self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
309
310 if self.selected_packages:
311 self.model.set_selected_packages(self.selected_packages)
312
313 def reset_clicked_cb(self, button):
314 lbl = "<b>Reset your selections?</b>\n\nAny new changes you have made will be lost"
315 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
316 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
317 dialog.add_button("Reset", gtk.RESPONSE_OK)
318 response = dialog.run()
319 dialog.destroy()
320 if response == gtk.RESPONSE_OK:
321 self.reset_build()
322 self.search.set_text("")
323 self.selected_image = None
324 return
325
326 def reset_build(self):
327 if self.image_combo_id:
328 self.image_combo.disconnect(self.image_combo_id)
329 self.image_combo_id = None
330 self.image_combo.set_active(-1)
331 self.model.reset()
332 if not self.image_combo_id:
333 self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
334
335 def layers_cb(self, action):
336 resp = self.layers.run()
337 self.layers.save_current_layers()
338 self.layers.hide()
339
340 def add_layer_cb(self, action):
341 self.layers.find_layer(self)
342 self.layers.save_current_layers()
343
344 def preferences_cb(self, action):
345 resp = self.prefs.run()
346 self.prefs.write_changes()
347 self.prefs.hide()
348
349 def about_cb(self, action):
350 about = gtk.AboutDialog()
351 about.set_name("Image Creator")
352 about.set_copyright("Copyright (C) 2011 Intel Corporation")
353 about.set_authors(["Joshua Lock <josh@linux.intel.com>"])
354 about.set_logo_icon_name("applications-development")
355 about.run()
356 about.destroy()
357
358 def save_recipe_file(self):
359 rep = self.model.get_build_rep()
360 rep.writeRecipe(self.save_path, self.model)
361 self.dirty = False
362
363 def get_save_path(self):
364 chooser = gtk.FileChooserDialog(title=None, parent=self,
365 action=gtk.FILE_CHOOSER_ACTION_SAVE,
366 buttons=(gtk.STOCK_CANCEL,
367 gtk.RESPONSE_CANCEL,
368 gtk.STOCK_SAVE,
369 gtk.RESPONSE_OK,))
370 chooser.set_current_name("myimage.bb")
371 response = chooser.run()
372 if response == gtk.RESPONSE_OK:
373 save_path = chooser.get_filename()
374 else:
375 save_path = None
376 chooser.destroy()
377 self.save_path = save_path
378
379 def save_cb(self, action):
380 if not self.save_path:
381 self.get_save_path()
382 if self.save_path:
383 self.save_recipe_file()
384
385 def save_as_cb(self, action):
386 self.get_save_path()
387 if self.save_path:
388 self.save_recipe_file()
389
390 def open_cb(self, action):
391 chooser = gtk.FileChooserDialog(title=None, parent=self,
392 action=gtk.FILE_CHOOSER_ACTION_OPEN,
393 buttons=(gtk.STOCK_CANCEL,
394 gtk.RESPONSE_CANCEL,
395 gtk.STOCK_OPEN,
396 gtk.RESPONSE_OK))
397 response = chooser.run()
398 rep = BuildRep(None, None, None)
399 recipe = chooser.get_filename()
400 if response == gtk.RESPONSE_OK:
401 rep.loadRecipe(recipe)
402 self.save_path = recipe
403 self.model.load_image_rep(rep)
404 self.dirty = False
405 chooser.destroy()
406
407 def bake_clicked_cb(self, button):
408 build_image = True
409
410 rep = self.model.get_build_rep()
411
412 # If no base image and no user selected packages don't build anything
413 if not self.selected_image and not len(rep.userpkgs):
414 lbl = "<b>No selections made</b>\nYou have not made any selections"
415 lbl = lbl + " so there isn't anything to bake at this time."
416 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
417 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
418 dialog.run()
419 dialog.destroy()
420 return
421 # Else if no base image, ask whether to just build packages or whether
422 # to build a rootfs with the selected packages in
423 elif not self.selected_image:
424 lbl = "<b>Build empty image or only packages?</b>\nA base image"
425 lbl = lbl + " has not been selected.\n\'Empty image' will build"
426 lbl = lbl + " an image with only the selected packages as its"
427 lbl = lbl + " contents.\n'Packages Only' will build only the"
428 lbl = lbl + " selected packages, no image will be created"
429 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
430 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
431 dialog.add_button("Empty Image", gtk.RESPONSE_OK)
432 dialog.add_button("Packages Only", gtk.RESPONSE_YES)
433 response = dialog.run()
434 dialog.destroy()
435 if response == gtk.RESPONSE_CANCEL:
436 return
437 elif response == gtk.RESPONSE_YES:
438 build_image = False
439 elif response == gtk.RESPONSE_OK:
440 rep.base_image = "empty"
441
442 # Ensure at least one value is set in IMAGE_FSTYPES.
443 have_selected_fstype = False
444 if (len(self.prefs.selected_image_types) and
445 len(self.prefs.selected_image_types[0])):
446 have_selected_fstype = True
447
448 if build_image and not have_selected_fstype:
449 lbl = "<b>No image output type selected</b>\nThere is no image output"
450 lbl = lbl + " selected for the build. Please set an output image type"
451 lbl = lbl + " in the preferences (Edit -> Preferences)."
452 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
453 dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
454 dialog.run()
455 dialog.destroy()
456 return
457 elif build_image:
458 self.handler.make_temp_dir()
459 recipepath = self.handler.get_temp_recipe_path(rep.base_image)
460 image_name = recipepath.rstrip(".bb")
461 path, sep, image_name = image_name.rpartition("/")
462
463 image = []
464 image.append(image_name)
465
466 rep.writeRecipe(recipepath, self.model)
467 # In the case where we saved the file for the purpose of building
468 # it we should then delete it so that the users workspace doesn't
469 # contain files they haven't explicitly saved there.
470 if not self.save_path:
471 self.files_to_clean.append(recipepath)
472
473 self.handler.build_targets(image, self.configurator)
474 else:
475 self.handler.build_targets(self.model.get_selected_pn(), self.configurator, "packages")
476
477 # Disable parts of the menu which shouldn't be used whilst building
478 self.set_menus_sensitive(False)
479 self.nb.set_current_page(1)
480
481 def set_menus_sensitive(self, sensitive):
482 self.add_layers_action.set_sensitive(sensitive)
483 self.layers_action.set_sensitive(sensitive)
484 self.prefs_action.set_sensitive(sensitive)
485 self.open_action.set_sensitive(sensitive)
486
487 def back_button_clicked_cb(self, button):
488 self.toggle_createview()
489
490 def toggle_createview(self):
491 self.set_menus_sensitive(True)
492 self.build.reset()
493 self.nb.set_current_page(0)
494
495 def build_complete_cb(self, running_build):
496 # Have the handler process BB events again
497 self.handler.building = False
498 self.stopping = False
499 self.back.connect("clicked", self.back_button_clicked_cb)
500 self.back.set_sensitive(True)
501 self.cancel.set_sensitive(False)
502 for f in self.files_to_clean:
503 try:
504 os.remove(f)
505 except OSError:
506 pass
507 self.files_to_clean.remove(f)
508 self.files_to_clean = []
509
510 lbl = "<b>Build completed</b>\n\nClick 'Edit Image' to start another build or 'View Messages' to view the messages output during the build."
511 if self.handler.build_type == "image" and self.build_succeeded:
512 deploy = self.handler.get_image_deploy_dir()
513 lbl = lbl + "\n<a href=\"file://%s\" title=\"%s\">Browse folder of built images</a>." % (deploy, deploy)
514
515 dialog = CrumbsDialog(self, lbl)
516 dialog.add_button("View Messages", gtk.RESPONSE_CANCEL)
517 dialog.add_button("Edit Image", gtk.RESPONSE_OK)
518 response = dialog.run()
519 dialog.destroy()
520 if response == gtk.RESPONSE_OK:
521 self.toggle_createview()
522
523 def build_started_cb(self, running_build):
524 self.back.set_sensitive(False)
525 self.cancel.set_sensitive(True)
526
527 def include_gplv3_cb(self, toggle):
528 excluded = toggle.get_active()
529 self.handler.toggle_gplv3(excluded)
530
531 def change_bb_threads(self, spinner):
532 val = spinner.get_value_as_int()
533 self.handler.set_bbthreads(val)
534
535 def change_make_threads(self, spinner):
536 val = spinner.get_value_as_int()
537 self.handler.set_pmake(val)
538
539 def toggle_toolchain(self, check):
540 enabled = check.get_active()
541 self.handler.toggle_toolchain(enabled)
542
543 def toggle_headers(self, check):
544 enabled = check.get_active()
545 self.handler.toggle_toolchain_headers(enabled)
546
547 def toggle_package_idle_cb(self, opath, image):
548 """
549 As the operations which we're calling on the model can take
550 a significant amount of time (in the order of seconds) during which
551 the GUI is unresponsive as the main loop is blocked perform them in
552 an idle function which at least enables us to set the busy cursor
553 before the UI is blocked giving the appearance of being responsive.
554 """
555 # Whether the item is currently included
556 inc = self.model[opath][self.model.COL_INC]
557 # FIXME: due to inpredictability of the removal of packages we are
558 # temporarily disabling this feature
559 # If the item is already included, mark it for removal then
560 # the sweep_up() method finds affected items and marks them
561 # appropriately
562 # if inc:
563 # self.model.mark(opath)
564 # self.model.sweep_up()
565 # # If the item isn't included, mark it for inclusion
566 # else:
567 if not inc:
568 self.model.include_item(item_path=opath,
569 binb="User Selected",
570 image_contents=image)
571
572 self.set_busy_cursor(False)
573 return False 42 return False
43 event = eventHandler.getEvent()
44 while event:
45 hobHandler.handle_event(event)
46 event = eventHandler.getEvent()
47 return True
48
49def main (server = None, eventHandler = None):
50 bitbake_server = None
51 client_addr = None
52 server_addr = None
53
54 if not eventHandler:
55 helper = uihelper.BBUIHelper()
56 server, eventHandler, server_addr, client_addr = helper.findServerDetails()
57 bitbake_server = server
574 58
575 def toggle_package(self, path, model, image=False):
576 inc = model[path][self.model.COL_INC]
577 # Warn user before removing included packages
578 if inc:
579 # FIXME: due to inpredictability of the removal of packages we are
580 # temporarily disabling this feature
581 return
582 # pn = model[path][self.model.COL_NAME]
583 # revdeps = self.model.find_reverse_depends(pn)
584 # if len(revdeps):
585 # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone and all packages which depend on this will be removed\nPackages which depend on %s include %s." % (pn, pn, ", ".join(revdeps).rstrip(","))
586 # else:
587 # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone." % pn
588 # dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
589 # dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
590 # dialog.add_button("Remove", gtk.RESPONSE_OK)
591 # response = dialog.run()
592 # dialog.destroy()
593 # if response == gtk.RESPONSE_CANCEL:
594 # return
595
596 self.set_busy_cursor()
597 # Convert path to path in original model
598 opath = model.convert_path_to_child_path(path)
599 # This is a potentially length call which can block the
600 # main loop, therefore do the work in an idle func to keep
601 # the UI responsive
602 glib.idle_add(self.toggle_package_idle_cb, opath, image)
603
604 self.dirty = True
605
606 def toggle_include_cb(self, cell, path, tv):
607 model = tv.get_model()
608 self.toggle_package(path, model)
609
610 def toggle_pkg_include_cb(self, cell, path, tv):
611 # there's an extra layer of models in the packages case.
612 sort_model = tv.get_model()
613 cpath = sort_model.convert_path_to_child_path(path)
614 self.toggle_package(cpath, sort_model.get_model())
615
616 def pkgsaz(self):
617 vbox = gtk.VBox(False, 6)
618 vbox.show()
619 self.pkgsaz_tree = gtk.TreeView()
620 self.pkgsaz_tree.set_headers_visible(True)
621 self.pkgsaz_tree.set_headers_clickable(True)
622 self.pkgsaz_tree.set_enable_search(True)
623 self.pkgsaz_tree.set_search_column(0)
624 self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
625
626 col = gtk.TreeViewColumn('Package')
627 col.set_clickable(True)
628 col.set_sort_column_id(self.model.COL_NAME)
629 col.set_min_width(220)
630 col1 = gtk.TreeViewColumn('Description')
631 col1.set_resizable(True)
632 col1.set_min_width(360)
633 col2 = gtk.TreeViewColumn('License')
634 col2.set_resizable(True)
635 col2.set_clickable(True)
636 col2.set_sort_column_id(self.model.COL_LIC)
637 col2.set_min_width(170)
638 col3 = gtk.TreeViewColumn('Group')
639 col3.set_clickable(True)
640 col3.set_sort_column_id(self.model.COL_GROUP)
641 col4 = gtk.TreeViewColumn('Included')
642 col4.set_min_width(80)
643 col4.set_max_width(90)
644 col4.set_sort_column_id(self.model.COL_INC)
645
646 self.pkgsaz_tree.append_column(col)
647 self.pkgsaz_tree.append_column(col1)
648 self.pkgsaz_tree.append_column(col2)
649 self.pkgsaz_tree.append_column(col3)
650 self.pkgsaz_tree.append_column(col4)
651
652 cell = gtk.CellRendererText()
653 cell1 = gtk.CellRendererText()
654 cell1.set_property('width-chars', 20)
655 cell2 = gtk.CellRendererText()
656 cell2.set_property('width-chars', 20)
657 cell3 = gtk.CellRendererText()
658 cell4 = gtk.CellRendererToggle()
659 cell4.set_property('activatable', True)
660 cell4.connect("toggled", self.toggle_pkg_include_cb, self.pkgsaz_tree)
661
662 col.pack_start(cell, True)
663 col1.pack_start(cell1, True)
664 col2.pack_start(cell2, True)
665 col3.pack_start(cell3, True)
666 col4.pack_end(cell4, True)
667
668 col.set_attributes(cell, text=self.model.COL_NAME)
669 col1.set_attributes(cell1, text=self.model.COL_DESC)
670 col2.set_attributes(cell2, text=self.model.COL_LIC)
671 col3.set_attributes(cell3, text=self.model.COL_GROUP)
672 col4.set_attributes(cell4, active=self.model.COL_INC)
673
674 self.pkgsaz_tree.show()
675
676 scroll = gtk.ScrolledWindow()
677 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
678 scroll.set_shadow_type(gtk.SHADOW_IN)
679 scroll.add(self.pkgsaz_tree)
680 vbox.pack_start(scroll, True, True, 0)
681
682 hb = gtk.HBox(False, 0)
683 hb.show()
684 self.search = gtk.Entry()
685 self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear")
686 self.search.connect("icon-release", self.search_entry_clear_cb)
687 self.search.show()
688 self.pkgsaz_tree.set_search_entry(self.search)
689 hb.pack_end(self.search, False, False, 0)
690 label = gtk.Label("Search packages:")
691 label.show()
692 hb.pack_end(label, False, False, 6)
693 vbox.pack_start(hb, False, False, 0)
694
695 return vbox
696
697 def search_entry_clear_cb(self, entry, icon_pos, event):
698 entry.set_text("")
699
700 def tasks(self):
701 vbox = gtk.VBox(False, 6)
702 vbox.show()
703 self.tasks_tree = gtk.TreeView()
704 self.tasks_tree.set_headers_visible(True)
705 self.tasks_tree.set_headers_clickable(False)
706 self.tasks_tree.set_enable_search(True)
707 self.tasks_tree.set_search_column(0)
708 self.tasks_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
709
710 col = gtk.TreeViewColumn('Package Collection')
711 col.set_min_width(430)
712 col1 = gtk.TreeViewColumn('Description')
713 col1.set_min_width(430)
714 col2 = gtk.TreeViewColumn('Include')
715 col2.set_min_width(70)
716 col2.set_max_width(80)
717
718 self.tasks_tree.append_column(col)
719 self.tasks_tree.append_column(col1)
720 self.tasks_tree.append_column(col2)
721
722 cell = gtk.CellRendererText()
723 cell1 = gtk.CellRendererText()
724 cell2 = gtk.CellRendererToggle()
725 cell2.set_property('activatable', True)
726 cell2.connect("toggled", self.toggle_include_cb, self.tasks_tree)
727
728 col.pack_start(cell, True)
729 col1.pack_start(cell1, True)
730 col2.pack_end(cell2, True)
731
732 col.set_attributes(cell, text=self.model.COL_NAME)
733 col1.set_attributes(cell1, text=self.model.COL_DESC)
734 col2.set_attributes(cell2, active=self.model.COL_INC)
735
736 self.tasks_tree.show()
737
738 scroll = gtk.ScrolledWindow()
739 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
740 scroll.set_shadow_type(gtk.SHADOW_IN)
741 scroll.add(self.tasks_tree)
742 vbox.pack_start(scroll, True, True, 0)
743
744 hb = gtk.HBox(False, 0)
745 hb.show()
746 search = gtk.Entry()
747 search.show()
748 self.tasks_tree.set_search_entry(search)
749 hb.pack_end(search, False, False, 0)
750 label = gtk.Label("Search collections:")
751 label.show()
752 hb.pack_end(label, False, False, 6)
753 vbox.pack_start(hb, False, False, 0)
754
755 return vbox
756
757 def cancel_build(self, button):
758 if self.stopping:
759 lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once,"
760 lbl = lbl + " would you like to 'Force Stop' the build?\n\n"
761 lbl = lbl + "This will stop the build as quickly as possible but may"
762 lbl = lbl + " well leave your build directory in an unusable state"
763 lbl = lbl + " that requires manual steps to fix.\n"
764 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
765 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
766 dialog.add_button("Force Stop", gtk.RESPONSE_YES)
767 else:
768 lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this"
769 lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as"
770 lbl = lbl + " possible but may well leave your build directory in an"
771 lbl = lbl + " unusable state that requires manual steps to fix.\n\n"
772 lbl = lbl + "'Stop' will stop the build as soon as all in"
773 lbl = lbl + " progress build tasks are finished. However if a"
774 lbl = lbl + " lengthy compilation phase is in progress this may take"
775 lbl = lbl + " some time."
776 dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
777 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
778 dialog.add_button("Stop", gtk.RESPONSE_OK)
779 dialog.add_button("Force Stop", gtk.RESPONSE_YES)
780 response = dialog.run()
781 dialog.destroy()
782 if response != gtk.RESPONSE_CANCEL:
783 self.stopping = True
784 if response == gtk.RESPONSE_OK:
785 self.handler.cancel_build()
786 elif response == gtk.RESPONSE_YES:
787 self.handler.cancel_build(True)
788
789 def view_build_gui(self):
790 vbox = gtk.VBox(False, 12)
791 vbox.set_border_width(6)
792 vbox.show()
793 build_tv = RunningBuildTreeView(readonly=True)
794 build_tv.show()
795 build_tv.set_model(self.build.model)
796 self.build.model.connect("row-inserted", self.scroll_tv_cb, build_tv)
797 scrolled_view = gtk.ScrolledWindow ()
798 scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
799 scrolled_view.add(build_tv)
800 scrolled_view.show()
801 vbox.pack_start(scrolled_view, expand=True, fill=True)
802 hbox = gtk.HBox(False, 12)
803 hbox.show()
804 vbox.pack_start(hbox, expand=False, fill=False)
805 self.back = gtk.Button("Back")
806 self.back.show()
807 self.back.set_sensitive(False)
808 hbox.pack_start(self.back, expand=False, fill=False)
809 self.cancel = gtk.Button("Stop Build")
810 self.cancel.connect("clicked", self.cancel_build)
811 self.cancel.show()
812 hbox.pack_end(self.cancel, expand=False, fill=False)
813
814 return vbox
815
816 def create_menu(self):
817 menu_items = '''<ui>
818 <menubar name="MenuBar">
819 <menu action="File">
820 <menuitem action="Save"/>
821 <menuitem action="Save As"/>
822 <menuitem action="Open"/>
823 <separator/>
824 <menuitem action="AddLayer" label="Add Layer"/>
825 <separator/>
826 <menuitem action="Quit"/>
827 </menu>
828 <menu action="Edit">
829 <menuitem action="Layers" label="Layers"/>
830 <menuitem action="Preferences"/>
831 </menu>
832 <menu action="Help">
833 <menuitem action="About"/>
834 </menu>
835 </menubar>
836 </ui>'''
837
838 uimanager = gtk.UIManager()
839 accel = uimanager.get_accel_group()
840 self.add_accel_group(accel)
841
842 actions = gtk.ActionGroup('ImageCreator')
843 self.actions = actions
844 actions.add_actions([('Quit', gtk.STOCK_QUIT, None, None, None, self.menu_quit,),
845 ('File', None, '_File'),
846 ('Save', gtk.STOCK_SAVE, None, None, None, self.save_cb),
847 ('Save As', gtk.STOCK_SAVE_AS, None, None, None, self.save_as_cb),
848 ('Edit', None, '_Edit'),
849 ('Help', None, '_Help'),
850 ('About', gtk.STOCK_ABOUT, None, None, None, self.about_cb)])
851
852 self.add_layers_action = gtk.Action('AddLayer', 'Add Layer', None, None)
853 self.add_layers_action.connect("activate", self.add_layer_cb)
854 self.actions.add_action(self.add_layers_action)
855 self.layers_action = gtk.Action('Layers', 'Layers', None, None)
856 self.layers_action.connect("activate", self.layers_cb)
857 self.actions.add_action(self.layers_action)
858 self.prefs_action = gtk.Action('Preferences', 'Preferences', None, None)
859 self.prefs_action.connect("activate", self.preferences_cb)
860 self.actions.add_action(self.prefs_action)
861 self.open_action = gtk.Action('Open', 'Open', None, None)
862 self.open_action.connect("activate", self.open_cb)
863 self.actions.add_action(self.open_action)
864
865 uimanager.insert_action_group(actions, 0)
866 uimanager.add_ui_from_string(menu_items)
867
868 menubar = uimanager.get_widget('/MenuBar')
869 menubar.show_all()
870
871 return menubar
872
873 def info_button_clicked_cb(self, button):
874 info = "We cannot accurately predict the image contents before they are built so instead a best"
875 info = info + " attempt at estimating what the image will contain is listed."
876 dialog = CrumbsDialog(self, info, gtk.STOCK_DIALOG_INFO)
877 dialog.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
878 resp = dialog.run()
879 dialog.destroy()
880
881 def create_build_gui(self):
882 vbox = gtk.VBox(False, 12)
883 vbox.set_border_width(6)
884 vbox.show()
885
886 hbox = gtk.HBox(False, 12)
887 hbox.show()
888 vbox.pack_start(hbox, expand=False, fill=False)
889
890 label = gtk.Label("Machine:")
891 label.show()
892 hbox.pack_start(label, expand=False, fill=False, padding=6)
893 self.machine_combo = gtk.combo_box_new_text()
894 self.machine_combo.show()
895 self.machine_combo.set_tooltip_text("Selects the architecture of the target board for which you would like to build an image.")
896 hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6)
897 label = gtk.Label("Base image:")
898 label.show()
899 hbox.pack_start(label, expand=False, fill=False, padding=6)
900 self.image_combo = gtk.ComboBox()
901 self.image_combo.show()
902 self.image_combo.set_tooltip_text("Selects the image on which to base the created image")
903 image_combo_cell = gtk.CellRendererText()
904 self.image_combo.pack_start(image_combo_cell, True)
905 self.image_combo.add_attribute(image_combo_cell, 'text', self.model.COL_NAME)
906 hbox.pack_start(self.image_combo, expand=False, fill=False, padding=6)
907 self.progress = gtk.ProgressBar()
908 self.progress.set_size_request(250, -1)
909 hbox.pack_end(self.progress, expand=False, fill=False, padding=6)
910
911 ins = gtk.Notebook()
912 vbox.pack_start(ins, expand=True, fill=True)
913 ins.set_show_tabs(True)
914 label = gtk.Label("Packages")
915 label.show()
916 ins.append_page(self.pkgsaz(), tab_label=label)
917 label = gtk.Label("Package Collections")
918 label.show()
919 ins.append_page(self.tasks(), tab_label=label)
920 ins.set_current_page(0)
921 ins.show_all()
922
923 hbox = gtk.HBox(False, 1)
924 hbox.show()
925 label = gtk.Label("Estimated image contents:")
926 self.model.connect("contents-changed", self.update_package_count_cb, label)
927 label.set_property("xalign", 0.00)
928 label.show()
929 hbox.pack_start(label, expand=False, fill=False, padding=6)
930 info = gtk.Button("?")
931 info.set_tooltip_text("What does this mean?")
932 info.show()
933 info.connect("clicked", self.info_button_clicked_cb)
934 hbox.pack_start(info, expand=False, fill=False, padding=6)
935 vbox.pack_start(hbox, expand=False, fill=False, padding=6)
936 con = self.contents()
937 con.show()
938 vbox.pack_start(con, expand=True, fill=True)
939
940 bbox = gtk.HButtonBox()
941 bbox.set_spacing(12)
942 bbox.set_layout(gtk.BUTTONBOX_END)
943 bbox.show()
944 vbox.pack_start(bbox, expand=False, fill=False)
945 reset = gtk.Button("Reset")
946 reset.connect("clicked", self.reset_clicked_cb)
947 reset.show()
948 bbox.add(reset)
949 bake = gtk.Button("Bake")
950 bake.connect("clicked", self.bake_clicked_cb)
951 bake.show()
952 bbox.add(bake)
953
954 return vbox
955
956 def update_package_count_cb(self, model, count, label):
957 lbl = "Estimated image contents (%s packages):" % count
958 label.set_text(lbl)
959
960 def contents(self):
961 self.contents_tree = gtk.TreeView()
962 self.contents_tree.set_headers_visible(True)
963 self.contents_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
964
965 # allow searching in the package column
966 self.contents_tree.set_search_column(0)
967 self.contents_tree.set_enable_search(True)
968
969 col = gtk.TreeViewColumn('Package')
970 col.set_sort_column_id(0)
971 col.set_min_width(430)
972 col1 = gtk.TreeViewColumn('Brought in by')
973 col1.set_resizable(True)
974 col1.set_min_width(430)
975
976 self.contents_tree.append_column(col)
977 self.contents_tree.append_column(col1)
978
979 cell = gtk.CellRendererText()
980 cell1 = gtk.CellRendererText()
981 cell1.set_property('width-chars', 20)
982
983 col.pack_start(cell, True)
984 col1.pack_start(cell1, True)
985
986 col.set_attributes(cell, text=self.model.COL_NAME)
987 col1.set_attributes(cell1, text=self.model.COL_BINB)
988
989 self.contents_tree.show()
990
991 scroll = gtk.ScrolledWindow()
992 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
993 scroll.set_shadow_type(gtk.SHADOW_IN)
994 scroll.add(self.contents_tree)
995
996 return scroll
997
998def main (server, eventHandler):
999 gobject.threads_init() 59 gobject.threads_init()
1000 60
1001 # NOTE: For now we require that the user run with pre and post files to 61 # That indicates whether the Hob and the bitbake server are
1002 # read and store configuration set in the GUI. 62 # running on different machines
1003 # We hope to adjust this long term as tracked in Yocto Bugzilla #1441 63 # recipe model and package model
1004 # http://bugzilla.pokylinux.org/show_bug.cgi?id=1441 64 recipe_model = RecipeListModel()
1005 reqfiles = 0 65 package_model = PackageListModel()
1006 dep_files = server.runCommand(["getVariable", "__depends"]) or set()
1007 dep_files.union(server.runCommand(["getVariable", "__base_depends"]) or set())
1008 for f in dep_files:
1009 if f[0].endswith("hob-pre.conf"):
1010 reqfiles = reqfiles + 1
1011 elif f[0].endswith("hob-post.conf"):
1012 reqfiles = reqfiles + 1
1013 if reqfiles == 2:
1014 break
1015 if reqfiles < 2:
1016 print("""The hob UI requires a pre file named hob-pre.conf and a post
1017file named hob-post.conf to store and read its configuration from. Please run
1018hob with these files, i.e.\n
1019\bitbake -u hob -r conf/hob-pre.conf -R conf/hob-post.conf""")
1020 return
1021 66
1022 taskmodel = TaskListModel() 67 hobHandler = HobHandler(bitbake_server, server_addr, client_addr, recipe_model, package_model)
1023 configurator = Configurator() 68 if hobHandler.kick() == False:
1024 handler = HobHandler(taskmodel, server)
1025 mach = server.runCommand(["getVariable", "MACHINE"])
1026 sdk_mach = server.runCommand(["getVariable", "SDKMACHINE"])
1027 # If SDKMACHINE not set the default SDK_ARCH is used so we
1028 # should represent that in the GUI
1029 if not sdk_mach:
1030 sdk_mach = server.runCommand(["getVariable", "SDK_ARCH"])
1031 distro = server.runCommand(["getVariable", "DISTRO"])
1032 if not distro:
1033 distro = "defaultsetup"
1034 bbthread = server.runCommand(["getVariable", "BB_NUMBER_THREADS"])
1035 if not bbthread:
1036 bbthread = 1
1037 else:
1038 bbthread = int(bbthread)
1039 pmake = server.runCommand(["getVariable", "PARALLEL_MAKE"])
1040 if not pmake:
1041 pmake = 1
1042 else:
1043 # The PARALLEL_MAKE variable will be of the format: "-j 3" and we only
1044 # want a number for the spinner, so strip everything from the variable
1045 # up to and including the space
1046 pmake = int(pmake.lstrip("-j "))
1047
1048 selected_image_types = server.runCommand(["getVariable", "IMAGE_FSTYPES"])
1049 all_image_types = server.runCommand(["getVariable", "IMAGE_TYPES"])
1050
1051 pclasses = server.runCommand(["getVariable", "PACKAGE_CLASSES"]).split(" ")
1052 # NOTE: we're only supporting one value for PACKAGE_CLASSES being set
1053 # this seems OK because we're using the first package format set in
1054 # PACKAGE_CLASSES and that's the package manager used for the rootfs
1055 pkg, sep, pclass = pclasses[0].rpartition("_")
1056
1057 incompatible = server.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"])
1058 gplv3disabled = False
1059 if incompatible and incompatible.lower().find("gplv3") != -1:
1060 gplv3disabled = True
1061
1062 build_toolchain = bool(server.runCommand(["getVariable", "HOB_BUILD_TOOLCHAIN"]))
1063 handler.toggle_toolchain(build_toolchain)
1064 build_headers = bool(server.runCommand(["getVariable", "HOB_BUILD_TOOLCHAIN_HEADERS"]))
1065 handler.toggle_toolchain_headers(build_headers)
1066
1067 prefs = HobPrefs(configurator, handler, sdk_mach, distro, pclass,
1068 pmake, bbthread, selected_image_types, all_image_types,
1069 gplv3disabled, build_toolchain, build_headers)
1070 layers = LayerEditor(configurator, None)
1071 window = MainWindow(taskmodel, handler, configurator, prefs, layers, mach)
1072 prefs.set_parent_window(window)
1073 layers.set_parent_window(window)
1074 window.show_all ()
1075 handler.connect("machines-updated", window.update_machines)
1076 handler.connect("sdk-machines-updated", prefs.update_sdk_machines)
1077 handler.connect("distros-updated", prefs.update_distros)
1078 handler.connect("package-formats-found", prefs.update_package_formats)
1079 handler.connect("generating-data", window.busy)
1080 handler.connect("data-generated", window.data_generated)
1081 handler.connect("reload-triggered", window.reload_triggered_cb)
1082 configurator.connect("layers-loaded", layers.load_current_layers)
1083 configurator.connect("layers-changed", handler.reload_data)
1084 handler.connect("config-found", configurator.configFound)
1085 handler.connect("fatal-error", window.fatal_error_cb)
1086
1087 try:
1088 # kick the while thing off
1089 handler.current_command = handler.CFG_PATH_LOCAL
1090 server.runCommand(["findConfigFilePath", "local.conf"])
1091 except xmlrpclib.Fault:
1092 print("XMLRPC Fault getting commandline:\n %s" % x)
1093 return 1 69 return 1
70 builder = Builder(hobHandler, recipe_model, package_model)
1094 71
1095 # This timeout function regularly probes the event queue to find out if we 72 # This timeout function regularly probes the event queue to find out if we
1096 # have any messages waiting for us. 73 # have any messages waiting for us.
1097 gobject.timeout_add (100, 74 gobject.timeout_add(10, event_handle_idle_func, eventHandler, hobHandler)
1098 handler.event_handle_idle_func,
1099 eventHandler,
1100 window.build,
1101 window.progress)
1102 75
1103 try: 76 try:
1104 gtk.main() 77 gtk.main()
@@ -1107,5 +80,13 @@ hob with these files, i.e.\n
1107 if ioerror.args[0] == 4: 80 if ioerror.args[0] == 4:
1108 pass 81 pass
1109 finally: 82 finally:
1110 server.runCommand(["stateStop"]) 83 hobHandler.cancel_build(force = True)
1111 84
85if __name__ == "__main__":
86 try:
87 ret = main()
88 except Exception:
89 ret = 1
90 import traceback
91 traceback.print_exc(15)
92 sys.exit(ret)
diff --git a/bitbake/lib/bb/ui/icons/images/images_display.png b/bitbake/lib/bb/ui/icons/images/images_display.png
new file mode 100644
index 0000000000..a7f87101af
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/images/images_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/images/images_hover.png b/bitbake/lib/bb/ui/icons/images/images_hover.png
new file mode 100644
index 0000000000..2d9cd99b8e
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/images/images_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/alert.png b/bitbake/lib/bb/ui/icons/indicators/alert.png
new file mode 100644
index 0000000000..d1c6f55a2f
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/alert.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/confirmation.png b/bitbake/lib/bb/ui/icons/indicators/confirmation.png
new file mode 100644
index 0000000000..3a5402d1e3
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/confirmation.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/denied.png b/bitbake/lib/bb/ui/icons/indicators/denied.png
new file mode 100644
index 0000000000..ee35c7defa
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/denied.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/error.png b/bitbake/lib/bb/ui/icons/indicators/error.png
new file mode 100644
index 0000000000..d06a8c151a
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/error.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/issues.png b/bitbake/lib/bb/ui/icons/indicators/issues.png
new file mode 100644
index 0000000000..b0c7461334
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/issues.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/refresh.png b/bitbake/lib/bb/ui/icons/indicators/refresh.png
new file mode 100644
index 0000000000..eb6c419db8
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/refresh.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/tick.png b/bitbake/lib/bb/ui/icons/indicators/tick.png
new file mode 100644
index 0000000000..beaad361c3
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/tick.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/info/info_display.png b/bitbake/lib/bb/ui/icons/info/info_display.png
new file mode 100644
index 0000000000..439587bc8c
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/info/info_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/info/info_hover.png b/bitbake/lib/bb/ui/icons/info/info_hover.png
new file mode 100644
index 0000000000..a1dd89d374
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/info/info_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/layers/layers_display.png b/bitbake/lib/bb/ui/icons/layers/layers_display.png
new file mode 100644
index 0000000000..94f90098fc
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/layers/layers_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/layers/layers_hover.png b/bitbake/lib/bb/ui/icons/layers/layers_hover.png
new file mode 100644
index 0000000000..5e5c3b93ec
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/layers/layers_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/packages/packages_display.png b/bitbake/lib/bb/ui/icons/packages/packages_display.png
new file mode 100644
index 0000000000..bd20c422d8
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/packages/packages_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/packages/packages_hover.png b/bitbake/lib/bb/ui/icons/packages/packages_hover.png
new file mode 100644
index 0000000000..f71db227d5
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/packages/packages_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/recipe/recipe_display.png b/bitbake/lib/bb/ui/icons/recipe/recipe_display.png
new file mode 100644
index 0000000000..838ef00c8d
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/recipe/recipe_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png b/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png
new file mode 100644
index 0000000000..246bfd5b34
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/settings/settings_display.png b/bitbake/lib/bb/ui/icons/settings/settings_display.png
new file mode 100644
index 0000000000..88c464db04
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/settings/settings_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/settings/settings_hover.png b/bitbake/lib/bb/ui/icons/settings/settings_hover.png
new file mode 100644
index 0000000000..d92a0bf2c3
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/settings/settings_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/templates/templates_display.png b/bitbake/lib/bb/ui/icons/templates/templates_display.png
new file mode 100644
index 0000000000..153c7afb62
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/templates/templates_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/templates/templates_hover.png b/bitbake/lib/bb/ui/icons/templates/templates_hover.png
new file mode 100644
index 0000000000..afb7165fe5
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/templates/templates_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/uihelper.py b/bitbake/lib/bb/ui/uihelper.py
index 617d60db82..bbf5135b70 100644
--- a/bitbake/lib/bb/ui/uihelper.py
+++ b/bitbake/lib/bb/ui/uihelper.py
@@ -40,3 +40,45 @@ class BBUIHelper:
40 def getTasks(self): 40 def getTasks(self):
41 self.needUpdate = False 41 self.needUpdate = False
42 return (self.running_tasks, self.failed_tasks) 42 return (self.running_tasks, self.failed_tasks)
43
44 def findServerDetails(self):
45 import sys
46 import optparse
47 from bb.server.xmlrpc import BitbakeServerInfo, BitBakeServerConnection
48 host = ""
49 port = 0
50 bind = ""
51 parser = optparse.OptionParser(
52 usage = """%prog -H host -P port -B bindaddr""")
53
54 parser.add_option("-H", "--host", help = "Bitbake server's IP address",
55 action = "store", dest = "host", default = None)
56
57 parser.add_option("-P", "--port", help = "Bitbake server's Port number",
58 action = "store", dest = "port", default = None)
59
60 parser.add_option("-B", "--bind", help = "Hob2 local bind address",
61 action = "store", dest = "bind", default = None)
62
63 options, args = parser.parse_args(sys.argv)
64 for key, val in options.__dict__.items():
65 if key == 'host' and val:
66 host = val
67 elif key == 'port' and val:
68 port = int(val)
69 elif key == 'bind' and val:
70 bind = val
71
72 if not host or not port or not bind:
73 parser.print_usage()
74 sys.exit(1)
75
76 serverinfo = BitbakeServerInfo(host, port)
77 clientinfo = (bind, 0)
78 connection = BitBakeServerConnection(serverinfo, clientinfo)
79
80 server = connection.connection
81 eventHandler = connection.events
82
83 return server, eventHandler, host, bind
84