summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/ui/crumbs
diff options
context:
space:
mode:
authorJoshua Lock <josh@linux.intel.com>2011-07-01 15:58:50 -0700
committerRichard Purdie <richard.purdie@linuxfoundation.org>2011-07-05 14:40:30 +0100
commit4cc291c007103c19779f995e852b37dbad122293 (patch)
tree3447d62ef1ba2eca08137b8e13df58f8a337a453 /bitbake/lib/bb/ui/crumbs
parent7fc9c3488f7865111ec052d1cab213d216d2b414 (diff)
downloadpoky-4cc291c007103c19779f995e852b37dbad122293.tar.gz
hob: re-designed interaction and implementation
Highlights include: * Atempted GNOME HIG compliance * Simplified UI and interaction model * Sorting and type to find in tree views * Preferences dialog to modify local settings * Dialog to add and remove layers * Search in packages list * Save/Load image recipes The build model has been changed, hob will attempt to build all dependent packages of an image and then use the buildFile server method to build the created image. (Bitbake rev: 48e64acaae4a741b9f5630f426fb4e6142755c2c) Signed-off-by: Joshua Lock <josh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/bb/ui/crumbs')
-rw-r--r--bitbake/lib/bb/ui/crumbs/configurator.py278
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig.py61
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobeventhandler.py218
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobprefs.py293
-rw-r--r--bitbake/lib/bb/ui/crumbs/layereditor.py136
-rw-r--r--bitbake/lib/bb/ui/crumbs/runningbuild.py12
-rw-r--r--bitbake/lib/bb/ui/crumbs/tasklistmodel.py326
7 files changed, 1199 insertions, 125 deletions
diff --git a/bitbake/lib/bb/ui/crumbs/configurator.py b/bitbake/lib/bb/ui/crumbs/configurator.py
new file mode 100644
index 0000000000..b694143d4c
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/configurator.py
@@ -0,0 +1,278 @@
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.local = None
44 self.bblayers = None
45 self.enabled_layers = {}
46 self.loaded_layers = {}
47 self.config = {}
48 self.orig_config = {}
49
50 # NOTE: cribbed from the cooker...
51 def _parse(self, f, data, include=False):
52 try:
53 return bb.parse.handle(f, data, include)
54 except (IOError, bb.parse.ParseError) as exc:
55 parselog.critical("Unable to parse %s: %s" % (f, exc))
56 sys.exit(1)
57
58 def _loadLocalConf(self, path):
59 def getString(var):
60 return bb.data.getVar(var, data, True) or ""
61
62 self.local = path
63
64 if self.orig_config:
65 del self.orig_config
66 self.orig_config = {}
67
68 data = bb.data.init()
69 data = self._parse(self.local, data)
70
71 # We only need to care about certain variables
72 mach = getString('MACHINE')
73 if mach and mach != self.config.get('MACHINE', ''):
74 self.config['MACHINE'] = mach
75 sdkmach = getString('SDKMACHINE')
76 if sdkmach and sdkmach != self.config.get('SDKMACHINE', ''):
77 self.config['SDKMACHINE'] = sdkmach
78 distro = getString('DISTRO')
79 if distro and distro != self.config.get('DISTRO', ''):
80 self.config['DISTRO'] = distro
81 bbnum = getString('BB_NUMBER_THREADS')
82 if bbnum and bbnum != self.config.get('BB_NUMBER_THREADS', ''):
83 self.config['BB_NUMBER_THREADS'] = bbnum
84 pmake = getString('PARALLEL_MAKE')
85 if pmake and pmake != self.config.get('PARALLEL_MAKE', ''):
86 self.config['PARALLEL_MAKE'] = pmake
87 incompat = getString('INCOMPATIBLE_LICENSE')
88 if incompat and incompat != self.config.get('INCOMPATIBLE_LICENSE', ''):
89 self.config['INCOMPATIBLE_LICENSE'] = incompat
90 pclass = getString('PACKAGE_CLASSES')
91 if pclass and pclass != self.config.get('PACKAGE_CLASSES', ''):
92 self.config['PACKAGE_CLASSES'] = pclass
93
94 self.orig_config = copy.deepcopy(self.config)
95
96 def setLocalConfVar(self, var, val):
97 if var in self.config:
98 self.config[var] = val
99
100 def _loadLayerConf(self, path):
101 self.bblayers = path
102 self.enabled_layers = {}
103 self.loaded_layers = {}
104 data = bb.data.init()
105 data = self._parse(self.bblayers, data)
106 layers = (bb.data.getVar('BBLAYERS', data, True) or "").split()
107 for layer in layers:
108 # TODO: we may be better off calling the layer by its
109 # BBFILE_COLLECTIONS value?
110 name = self._getLayerName(layer)
111 self.loaded_layers[name] = layer
112
113 self.enabled_layers = copy.deepcopy(self.loaded_layers)
114 self.emit("layers-loaded")
115
116 def _addConfigFile(self, path):
117 pref, sep, filename = path.rpartition("/")
118 if filename == "local.conf" or filename == "hob.local.conf":
119 self._loadLocalConf(path)
120 elif filename == "bblayers.conf":
121 self._loadLayerConf(path)
122
123 def _splitLayer(self, path):
124 # we only care about the path up to /conf/layer.conf
125 layerpath, conf, end = path.rpartition("/conf/")
126 return layerpath
127
128 def _getLayerName(self, path):
129 # Should this be the collection name?
130 layerpath, sep, name = path.rpartition("/")
131 return name
132
133 def disableLayer(self, layer):
134 if layer in self.enabled_layers:
135 del self.enabled_layers[layer]
136
137 def addLayerConf(self, confpath):
138 layerpath = self._splitLayer(confpath)
139 name = self._getLayerName(layerpath)
140 if name not in self.enabled_layers:
141 self.addLayer(name, layerpath)
142 return name, layerpath
143
144 def addLayer(self, name, path):
145 self.enabled_layers[name] = path
146
147 def _isLayerConfDirty(self):
148 # if a different number of layers enabled to what was
149 # loaded, definitely different
150 if len(self.enabled_layers) != len(self.loaded_layers):
151 return True
152
153 for layer in self.loaded_layers:
154 # if layer loaded but no longer present, definitely dirty
155 if layer not in self.enabled_layers:
156 return True
157
158 for layer in self.enabled_layers:
159 # if this layer wasn't present at load, definitely dirty
160 if layer not in self.loaded_layers:
161 return True
162 # if this layers path has changed, definitely dirty
163 if self.enabled_layers[layer] != self.loaded_layers[layer]:
164 return True
165
166 return False
167
168 def _constructLayerEntry(self):
169 """
170 Returns a string representing the new layer selection
171 """
172 layers = self.enabled_layers.copy()
173 # Construct BBLAYERS entry
174 layer_entry = "BBLAYERS = \" \\\n"
175 if 'meta' in layers:
176 layer_entry = layer_entry + " %s \\\n" % layers['meta']
177 del layers['meta']
178 for layer in layers:
179 layer_entry = layer_entry + " %s \\\n" % layers[layer]
180 layer_entry = layer_entry + " \""
181
182 return "".join(layer_entry)
183
184 def writeLocalConf(self):
185 # Dictionary containing only new or modified variables
186 changed_values = {}
187 for var in self.config:
188 val = self.config[var]
189 if self.orig_config.get(var, None) != val:
190 changed_values[var] = val
191
192 if not len(changed_values):
193 return
194
195 # Create a backup of the local.conf
196 bkup = "%s~" % self.local
197 os.rename(self.local, bkup)
198
199 # read the original conf into a list
200 with open(bkup, 'r') as config:
201 config_lines = config.readlines()
202
203 new_config_lines = ["\n"]
204 for var in changed_values:
205 # Convenience function for re.subn(). If the pattern matches
206 # return a string which contains an assignment using the same
207 # assignment operator as the old assignment.
208 def replace_val(matchobj):
209 var = matchobj.group(1) # config variable
210 op = matchobj.group(2) # assignment operator
211 val = changed_values[var] # new config value
212 return "%s %s \"%s\"" % (var, op, val)
213
214 pattern = '^\s*(%s)\s*([+=?.]+)(.*)' % re.escape(var)
215 p = re.compile(pattern)
216 cnt = 0
217 replaced = False
218
219 # Iterate over the local.conf lines and if they are a match
220 # for the pattern comment out the line and append a new line
221 # with the new VAR op "value" entry
222 for line in config_lines:
223 new_line, replacements = p.subn(replace_val, line)
224 if replacements:
225 config_lines[cnt] = "#%s" % line
226 new_config_lines.append(new_line)
227 replaced = True
228 cnt = cnt + 1
229
230 if not replaced:
231 new_config_lines.append("%s = \"%s\"" % (var, changed_values[var]))
232
233 # Add the modified variables
234 config_lines.extend(new_config_lines)
235
236 # Write the updated lines list object to the local.conf
237 with open(self.local, "w") as n:
238 n.write("".join(config_lines))
239
240 del self.orig_config
241 self.orig_config = copy.deepcopy(self.config)
242
243 def writeLayerConf(self):
244 # If we've not added/removed new layers don't write
245 if not self._isLayerConfDirty():
246 return
247
248 # This pattern should find the existing BBLAYERS
249 pattern = 'BBLAYERS\s=\s\".*\"'
250
251 # Backup the users bblayers.conf
252 bkup = "%s~" % self.bblayers
253 os.rename(self.bblayers, bkup)
254
255 replacement = self._constructLayerEntry()
256
257 with open(bkup, "r") as f:
258 contents = f.read()
259 p = re.compile(pattern, re.DOTALL)
260 new = p.sub(replacement, contents)
261
262 with open(self.bblayers, "w") as n:
263 n.write(new)
264
265 # At some stage we should remove the backup we've created
266 # though we should probably verify it first
267 #os.remove(bkup)
268
269 # set loaded_layers for dirtiness tracking
270 self.loaded_layers = copy.deepcopy(self.enabled_layers)
271
272 self.emit("layers-changed")
273
274 def configFound(self, handler, path):
275 self._addConfigFile(path)
276
277 def loadConfig(self, path):
278 self._addConfigFile(path)
diff --git a/bitbake/lib/bb/ui/crumbs/hig.py b/bitbake/lib/bb/ui/crumbs/hig.py
new file mode 100644
index 0000000000..b3b3c7a72e
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig.py
@@ -0,0 +1,61 @@
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
23"""
24The following are convenience classes for implementing GNOME HIG compliant
25BitBake GUI's
26In summary: spacing = 12px, border-width = 6px
27"""
28
29class CrumbsDialog(gtk.Dialog):
30 """
31 A GNOME HIG compliant dialog widget.
32 Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
33 """
34 def __init__(self, parent=None, label="", icon=gtk.STOCK_INFO):
35 gtk.Dialog.__init__(self, "", parent, gtk.DIALOG_DESTROY_WITH_PARENT)
36
37 #self.set_property("has-separator", False) # note: deprecated in 2.22
38
39 self.set_border_width(6)
40 self.vbox.set_property("spacing", 12)
41 self.action_area.set_property("spacing", 12)
42 self.action_area.set_property("border-width", 6)
43
44 first_row = gtk.HBox(spacing=12)
45 first_row.set_property("border-width", 6)
46 first_row.show()
47 self.vbox.add(first_row)
48
49 self.icon = gtk.Image()
50 self.icon.set_from_stock(icon, gtk.ICON_SIZE_DIALOG)
51 self.icon.set_property("yalign", 0.00)
52 self.icon.show()
53 first_row.add(self.icon)
54
55 self.label = gtk.Label()
56 self.label.set_use_markup(True)
57 self.label.set_line_wrap(True)
58 self.label.set_markup(label)
59 self.label.set_property("yalign", 0.00)
60 self.label.show()
61 first_row.add(self.label)
diff --git a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
index c474491d6a..fa79e0c7a2 100644
--- a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
+++ b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
@@ -19,7 +19,6 @@
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 20
21import gobject 21import gobject
22from bb.ui.crumbs.progress import ProgressBar
23 22
24progress_total = 0 23progress_total = 0
25 24
@@ -29,46 +28,78 @@ class HobHandler(gobject.GObject):
29 This object does BitBake event handling for the hob gui. 28 This object does BitBake event handling for the hob gui.
30 """ 29 """
31 __gsignals__ = { 30 __gsignals__ = {
32 "machines-updated" : (gobject.SIGNAL_RUN_LAST, 31 "machines-updated" : (gobject.SIGNAL_RUN_LAST,
33 gobject.TYPE_NONE, 32 gobject.TYPE_NONE,
34 (gobject.TYPE_PYOBJECT,)), 33 (gobject.TYPE_PYOBJECT,)),
35 "distros-updated" : (gobject.SIGNAL_RUN_LAST, 34 "sdk-machines-updated": (gobject.SIGNAL_RUN_LAST,
36 gobject.TYPE_NONE, 35 gobject.TYPE_NONE,
37 (gobject.TYPE_PYOBJECT,)), 36 (gobject.TYPE_PYOBJECT,)),
38 "generating-data" : (gobject.SIGNAL_RUN_LAST, 37 "distros-updated" : (gobject.SIGNAL_RUN_LAST,
39 gobject.TYPE_NONE, 38 gobject.TYPE_NONE,
40 ()), 39 (gobject.TYPE_PYOBJECT,)),
41 "data-generated" : (gobject.SIGNAL_RUN_LAST, 40 "package-formats-found" : (gobject.SIGNAL_RUN_LAST,
42 gobject.TYPE_NONE, 41 gobject.TYPE_NONE,
43 ()) 42 (gobject.TYPE_PYOBJECT,)),
43 "config-found" : (gobject.SIGNAL_RUN_LAST,
44 gobject.TYPE_NONE,
45 (gobject.TYPE_STRING,)),
46 "generating-data" : (gobject.SIGNAL_RUN_LAST,
47 gobject.TYPE_NONE,
48 ()),
49 "data-generated" : (gobject.SIGNAL_RUN_LAST,
50 gobject.TYPE_NONE,
51 ()),
52 "error" : (gobject.SIGNAL_RUN_LAST,
53 gobject.TYPE_NONE,
54 (gobject.TYPE_STRING,)),
55 "build-complete" : (gobject.SIGNAL_RUN_LAST,
56 gobject.TYPE_NONE,
57 ()),
58 "reload-triggered" : (gobject.SIGNAL_RUN_LAST,
59 gobject.TYPE_NONE,
60 (gobject.TYPE_STRING,
61 gobject.TYPE_STRING)),
44 } 62 }
45 63
46 def __init__(self, taskmodel, server): 64 def __init__(self, taskmodel, server):
47 gobject.GObject.__init__(self) 65 gobject.GObject.__init__(self)
66 self.current_command = None
67 self.building = None
68 self.gplv3_excluded = False
69 self.build_toolchain = False
70 self.build_toolchain_headers = False
71 self.generating = False
72 self.build_queue = []
48 73
49 self.model = taskmodel 74 self.model = taskmodel
50 self.server = server 75 self.server = server
51 self.current_command = None
52 self.building = False
53 76
54 self.command_map = { 77 self.command_map = {
55 "findConfigFilesDistro" : ("findConfigFiles", "MACHINE", "findConfigFilesMachine"), 78 "findConfigFilePathLocal" : ("findConfigFilePath", ["hob.local.conf"], "findConfigFilePathHobLocal"),
56 "findConfigFilesMachine" : ("generateTargetsTree", "classes/image.bbclass", None), 79 "findConfigFilePathHobLocal" : ("findConfigFilePath", ["bblayers.conf"], "findConfigFilePathLayers"),
57 "generateTargetsTree" : (None, None, None), 80 "findConfigFilePathLayers" : ("findConfigFiles", ["DISTRO"], "findConfigFilesDistro"),
81 "findConfigFilesDistro" : ("findConfigFiles", ["MACHINE"], "findConfigFilesMachine"),
82 "findConfigFilesMachine" : ("findConfigFiles", ["MACHINE-SDK"], "findConfigFilesSdkMachine"),
83 "findConfigFilesSdkMachine" : ("findFilesMatchingInDir", ["rootfs_", "classes"], "findFilesMatchingPackage"),
84 "findFilesMatchingPackage" : ("generateTargetsTree", ["classes/image.bbclass"], None),
85 "generateTargetsTree" : (None, [], None),
58 } 86 }
59 87
60 def run_next_command(self): 88 def run_next_command(self):
61 # FIXME: this is ugly and I *will* replace it 89 # FIXME: this is ugly and I *will* replace it
62 if self.current_command: 90 if self.current_command:
91 if not self.generating:
92 self.emit("generating-data")
93 self.generating = True
63 next_cmd = self.command_map[self.current_command] 94 next_cmd = self.command_map[self.current_command]
64 command = next_cmd[0] 95 command = next_cmd[0]
65 argument = next_cmd[1] 96 argument = next_cmd[1]
66 self.current_command = next_cmd[2] 97 self.current_command = next_cmd[2]
67 if command == "generateTargetsTree": 98 args = [command]
68 self.emit("generating-data") 99 args.extend(argument)
69 self.server.runCommand([command, argument]) 100 self.server.runCommand(args)
70 101
71 def handle_event(self, event, running_build, pbar=None): 102 def handle_event(self, event, running_build, pbar):
72 if not event: 103 if not event:
73 return 104 return
74 105
@@ -77,9 +108,9 @@ class HobHandler(gobject.GObject):
77 running_build.handle_event(event) 108 running_build.handle_event(event)
78 elif isinstance(event, bb.event.TargetsTreeGenerated): 109 elif isinstance(event, bb.event.TargetsTreeGenerated):
79 self.emit("data-generated") 110 self.emit("data-generated")
111 self.generating = False
80 if event._model: 112 if event._model:
81 self.model.populate(event._model) 113 self.model.populate(event._model)
82
83 elif isinstance(event, bb.event.ConfigFilesFound): 114 elif isinstance(event, bb.event.ConfigFilesFound):
84 var = event._variable 115 var = event._variable
85 if var == "distro": 116 if var == "distro":
@@ -90,28 +121,44 @@ class HobHandler(gobject.GObject):
90 machines = event._values 121 machines = event._values
91 machines.sort() 122 machines.sort()
92 self.emit("machines-updated", machines) 123 self.emit("machines-updated", machines)
93 124 elif var == "machine-sdk":
125 sdk_machines = event._values
126 sdk_machines.sort()
127 self.emit("sdk-machines-updated", sdk_machines)
128 elif isinstance(event, bb.event.ConfigFilePathFound):
129 path = event._path
130 self.emit("config-found", path)
131 elif isinstance(event, bb.event.FilesMatchingFound):
132 # FIXME: hard coding, should at least be a variable shared between
133 # here and the caller
134 if event._pattern == "rootfs_":
135 formats = []
136 for match in event._matches:
137 classname, sep, cls = match.rpartition(".")
138 fs, sep, format = classname.rpartition("_")
139 formats.append(format)
140 formats.sort()
141 self.emit("package-formats-found", formats)
94 elif isinstance(event, bb.command.CommandCompleted): 142 elif isinstance(event, bb.command.CommandCompleted):
95 self.run_next_command() 143 self.run_next_command()
96 elif isinstance(event, bb.event.CacheLoadStarted) and pbar: 144 elif isinstance(event, bb.command.CommandFailed):
97 pbar.set_title("Loading cache") 145 self.emit("error", event.error)
146 elif isinstance(event, bb.event.CacheLoadStarted):
98 bb.ui.crumbs.hobeventhandler.progress_total = event.total 147 bb.ui.crumbs.hobeventhandler.progress_total = event.total
99 pbar.update(0, bb.ui.crumbs.hobeventhandler.progress_total) 148 pbar.set_text("Loading cache: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total))
100 elif isinstance(event, bb.event.CacheLoadProgress) and pbar: 149 elif isinstance(event, bb.event.CacheLoadProgress):
101 pbar.update(event.current, bb.ui.crumbs.hobeventhandler.progress_total) 150 pbar.set_text("Loading cache: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total))
102 elif isinstance(event, bb.event.CacheLoadCompleted) and pbar: 151 elif isinstance(event, bb.event.CacheLoadCompleted):
103 pbar.update(bb.ui.crumbs.hobeventhandler.progress_total, bb.ui.crumbs.hobeventhandler.progress_total) 152 pbar.set_text("Loading cache: %s/%s" % (bb.ui.crumbs.hobeventhandler.progress_total, bb.ui.crumbs.hobeventhandler.progress_total))
104 elif isinstance(event, bb.event.ParseStarted) and pbar: 153 elif isinstance(event, bb.event.ParseStarted):
105 if event.total == 0: 154 if event.total == 0:
106 return 155 return
107 pbar.set_title("Processing recipes")
108 bb.ui.crumbs.hobeventhandler.progress_total = event.total 156 bb.ui.crumbs.hobeventhandler.progress_total = event.total
109 pbar.update(0, bb.ui.crumbs.hobeventhandler.progress_total) 157 pbar.set_text("Processing recipes: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total))
110 elif isinstance(event, bb.event.ParseProgress) and pbar: 158 elif isinstance(event, bb.event.ParseProgress):
111 pbar.update(event.current, bb.ui.crumbs.hobeventhandler.progress_total) 159 pbar.set_text("Processing recipes: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total))
112 elif isinstance(event, bb.event.ParseCompleted) and pbar: 160 elif isinstance(event, bb.event.ParseCompleted):
113 pbar.hide() 161 pbar.set_fraction(1.0)
114
115 return 162 return
116 163
117 def event_handle_idle_func (self, eventHandler, running_build, pbar): 164 def event_handle_idle_func (self, eventHandler, running_build, pbar):
@@ -124,16 +171,95 @@ class HobHandler(gobject.GObject):
124 171
125 def set_machine(self, machine): 172 def set_machine(self, machine):
126 self.server.runCommand(["setVariable", "MACHINE", machine]) 173 self.server.runCommand(["setVariable", "MACHINE", machine])
127 self.current_command = "findConfigFilesMachine" 174
128 self.run_next_command() 175 def set_sdk_machine(self, sdk_machine):
176 self.server.runCommand(["setVariable", "SDKMACHINE", sdk_machine])
129 177
130 def set_distro(self, distro): 178 def set_distro(self, distro):
131 self.server.runCommand(["setVariable", "DISTRO", distro]) 179 self.server.runCommand(["setVariable", "DISTRO", distro])
132 180
133 def run_build(self, targets): 181 def set_package_format(self, format):
134 self.building = True 182 self.server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_%s" % format])
183
184 def reload_data(self, config=None):
185 img = self.model.selected_image
186 selected_packages, _ = self.model.get_selected_packages()
187 self.emit("reload-triggered", img, " ".join(selected_packages))
188 self.server.runCommand(["reparseFiles"])
189 self.current_command = "findConfigFilePathLayers"
190 self.run_next_command()
191
192 def set_bbthreads(self, threads):
193 self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", threads])
194
195 def set_pmake(self, threads):
196 pmake = "-j %s" % threads
197 self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", pmake])
198
199 def run_build(self, tgts):
200 self.building = "image"
201 targets = []
202 targets.append(tgts)
203 if self.build_toolchain and self.build_toolchain_headers:
204 targets = ["meta-toolchain-sdk"] + targets
205 elif self.build_toolchain:
206 targets = ["meta-toolchain"] + targets
135 self.server.runCommand(["buildTargets", targets, "build"]) 207 self.server.runCommand(["buildTargets", targets, "build"])
136 208
137 def cancel_build(self): 209 def build_packages(self, pkgs):
138 # Note: this may not be the right way to stop an in-progress build 210 self.building = "packages"
139 self.server.runCommand(["stateStop"]) 211 if 'meta-toolchain' in self.build_queue:
212 self.build_queue.remove('meta-toolchain')
213 pkgs.extend('meta-toolchain')
214 self.server.runCommand(["buildTargets", pkgs, "build"])
215
216 def build_file(self, image):
217 self.building = "image"
218 self.server.runCommand(["buildFile", image, "build"])
219
220 def cancel_build(self, force=False):
221 if force:
222 # Force the cooker to stop as quickly as possible
223 self.server.runCommand(["stateStop"])
224 else:
225 # Wait for tasks to complete before shutting down, this helps
226 # leave the workdir in a usable state
227 self.server.runCommand(["stateShutdown"])
228
229 def toggle_gplv3(self, excluded):
230 if self.gplv3_excluded != excluded:
231 self.gplv3_excluded = excluded
232 if excluded:
233 self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", "GPLv3"])
234 else:
235 self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", ""])
236
237 def toggle_toolchain(self, enabled):
238 if self.build_toolchain != enabled:
239 self.build_toolchain = enabled
240
241 def toggle_toolchain_headers(self, enabled):
242 if self.build_toolchain_headers != enabled:
243 self.build_toolchain_headers = enabled
244
245 def queue_image_recipe_path(self, path):
246 self.build_queue.append(path)
247
248 def build_complete_cb(self, running_build):
249 if len(self.build_queue) > 0:
250 next = self.build_queue.pop(0)
251 if next.endswith('.bb'):
252 self.build_file(next)
253 self.building = 'image'
254 self.build_file(next)
255 else:
256 self.build_packages(next.split(" "))
257 else:
258 self.building = None
259 self.emit("build-complete")
260
261 def set_image_output_type(self, output_type):
262 self.server.runCommand(["setVariable", "IMAGE_FSTYPES", output_type])
263
264 def get_image_deploy_dir(self):
265 return self.server.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"])
diff --git a/bitbake/lib/bb/ui/crumbs/hobprefs.py b/bitbake/lib/bb/ui/crumbs/hobprefs.py
new file mode 100644
index 0000000000..f186410720
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobprefs.py
@@ -0,0 +1,293 @@
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
22from bb.ui.crumbs.configurator import Configurator
23
24class HobPrefs(gtk.Dialog):
25 """
26 """
27 def empty_combo_text(self, combo_text):
28 model = combo_text.get_model()
29 if model:
30 model.clear()
31
32 def output_type_changed_cb(self, combo, handler):
33 ot = combo.get_active_text()
34 if ot != self.curr_output_type:
35 self.curr_output_type = ot
36 handler.set_image_output_type(ot)
37
38 def sdk_machine_combo_changed_cb(self, combo, handler):
39 sdk_mach = combo.get_active_text()
40 if sdk_mach != self.curr_sdk_mach:
41 self.curr_sdk_mach = sdk_mach
42 self.configurator.setLocalConfVar('SDKMACHINE', sdk_mach)
43 handler.set_sdk_machine(sdk_mach)
44
45 def update_sdk_machines(self, handler, sdk_machines):
46 active = 0
47 # disconnect the signal handler before updating the combo model
48 if self.sdk_machine_handler_id:
49 self.sdk_machine_combo.disconnect(self.sdk_machine_handler_id)
50 self.sdk_machine_handler_id = None
51
52 self.empty_combo_text(self.sdk_machine_combo)
53 for sdk_machine in sdk_machines:
54 self.sdk_machine_combo.append_text(sdk_machine)
55 if sdk_machine == self.curr_sdk_mach:
56 self.sdk_machine_combo.set_active(active)
57 active = active + 1
58
59 self.sdk_machine_handler_id = self.sdk_machine_combo.connect("changed", self.sdk_machine_combo_changed_cb, handler)
60
61 def distro_combo_changed_cb(self, combo, handler):
62 distro = combo.get_active_text()
63 if distro != self.curr_distro:
64 self.curr_distro = distro
65 self.configurator.setLocalConfVar('DISTRO', distro)
66 handler.set_distro(distro)
67 self.reload_required = True
68
69 def update_distros(self, handler, distros):
70 active = 0
71 # disconnect the signal handler before updating combo model
72 if self.distro_handler_id:
73 self.distro_combo.disconnect(self.distro_handler_id)
74 self.distro_handler_id = None
75
76 self.empty_combo_text(self.distro_combo)
77 for distro in distros:
78 self.distro_combo.append_text(distro)
79 if distro == self.curr_distro:
80 self.distro_combo.set_active(active)
81 active = active + 1
82
83 self.distro_handler_id = self.distro_combo.connect("changed", self.distro_combo_changed_cb, handler)
84
85 def package_format_combo_changed_cb(self, combo, handler):
86 package_format = combo.get_active_text()
87 if package_format != self.curr_package_format:
88 self.curr_package_format = package_format
89 self.configurator.setLocalConfVar('PACKAGE_CLASSES', 'package_%s' % package_format)
90 handler.set_package_format(package_format)
91
92 def update_package_formats(self, handler, formats):
93 active = 0
94 # disconnect the signal handler before updating the model
95 if self.package_handler_id:
96 self.package_combo.disconnect(self.package_handler_id)
97 self.package_handler_id = None
98
99 self.empty_combo_text(self.package_combo)
100 for format in formats:
101 self.package_combo.append_text(format)
102 if format == self.curr_package_format:
103 self.package_combo.set_active(active)
104 active = active + 1
105
106 self.package_handler_id = self.package_combo.connect("changed", self.package_format_combo_changed_cb, handler)
107
108 def include_gplv3_cb(self, toggle):
109 excluded = toggle.get_active()
110 self.handler.toggle_gplv3(excluded)
111 if excluded:
112 self.configurator.setLocalConfVar('INCOMPATIBLE_LICENSE', 'GPLv3')
113 else:
114 self.configurator.setLocalConfVar('INCOMPATIBLE_LICENSE', '')
115 self.reload_required = True
116
117 def change_bb_threads_cb(self, spinner):
118 val = spinner.get_value_as_int()
119 self.handler.set_bbthreads(val)
120 self.configurator.setLocalConfVar('BB_NUMBER_THREADS', val)
121
122 def change_make_threads_cb(self, spinner):
123 val = spinner.get_value_as_int()
124 self.handler.set_pmake(val)
125 self.configurator.setLocalConfVar('PARALLEL_MAKE', "-j %s" % val)
126
127 def toggle_toolchain_cb(self, check):
128 enabled = check.get_active()
129 self.handler.toggle_toolchain(enabled)
130
131 def toggle_headers_cb(self, check):
132 enabled = check.get_active()
133 self.handler.toggle_toolchain_headers(enabled)
134
135 def set_parent_window(self, parent):
136 self.set_transient_for(parent)
137
138 def write_changes(self):
139 self.configurator.writeLocalConf()
140
141 def prefs_response_cb(self, dialog, response):
142 if self.reload_required:
143 glib.idle_add(self.handler.reload_data)
144
145 def __init__(self, configurator, handler, curr_sdk_mach, curr_distro, pclass,
146 cpu_cnt, pmake, bbthread, image_types):
147 """
148 """
149 gtk.Dialog.__init__(self, "Preferences", None,
150 gtk.DIALOG_DESTROY_WITH_PARENT,
151 (gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
152
153 self.set_border_width(6)
154 self.vbox.set_property("spacing", 12)
155 self.action_area.set_property("spacing", 12)
156 self.action_area.set_property("border-width", 6)
157
158 self.handler = handler
159 self.configurator = configurator
160
161 self.curr_sdk_mach = curr_sdk_mach
162 self.curr_distro = curr_distro
163 self.curr_package_format = pclass
164 self.curr_output_type = None
165 self.cpu_cnt = cpu_cnt
166 self.pmake = pmake
167 self.bbthread = bbthread
168 self.reload_required = False
169 self.distro_handler_id = None
170 self.sdk_machine_handler_id = None
171 self.package_handler_id = None
172
173 left = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
174 right = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
175
176 label = gtk.Label()
177 label.set_markup("<b>Policy</b>")
178 label.show()
179 frame = gtk.Frame()
180 frame.set_label_widget(label)
181 frame.set_shadow_type(gtk.SHADOW_NONE)
182 frame.show()
183 self.vbox.pack_start(frame)
184 pbox = gtk.VBox(False, 12)
185 pbox.show()
186 frame.add(pbox)
187 hbox = gtk.HBox(False, 12)
188 hbox.show()
189 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
190 # Distro selector
191 label = gtk.Label("Distribution:")
192 label.show()
193 hbox.pack_start(label, expand=False, fill=False, padding=6)
194 self.distro_combo = gtk.combo_box_new_text()
195 self.distro_combo.set_tooltip_text("Select the Yocto distribution you would like to use")
196 self.distro_combo.show()
197 hbox.pack_start(self.distro_combo, expand=False, fill=False, padding=6)
198 # Exclude GPLv3
199 check = gtk.CheckButton("Exclude GPLv3 packages")
200 check.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image")
201 check.show()
202 check.connect("toggled", self.include_gplv3_cb)
203 hbox.pack_start(check, expand=False, fill=False, padding=6)
204 hbox = gtk.HBox(False, 12)
205 hbox.show()
206 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
207 # Package format selector
208 label = gtk.Label("Package format:")
209 label.show()
210 hbox.pack_start(label, expand=False, fill=False, padding=6)
211 self.package_combo = gtk.combo_box_new_text()
212 self.package_combo.set_tooltip_text("Select the package format you would like to use in your image")
213 self.package_combo.show()
214 hbox.pack_start(self.package_combo, expand=False, fill=False, padding=6)
215 # Image output type selector
216 label = gtk.Label("Image output type:")
217 label.show()
218 hbox.pack_start(label, expand=False, fill=False, padding=6)
219 output_combo = gtk.combo_box_new_text()
220 if image_types:
221 for it in image_types.split(" "):
222 output_combo.append_text(it)
223 output_combo.connect("changed", self.output_type_changed_cb, handler)
224 else:
225 output_combo.set_sensitive(False)
226 output_combo.show()
227 hbox.pack_start(output_combo)
228 # BitBake
229 label = gtk.Label()
230 label.set_markup("<b>BitBake</b>")
231 label.show()
232 frame = gtk.Frame()
233 frame.set_label_widget(label)
234 frame.set_shadow_type(gtk.SHADOW_NONE)
235 frame.show()
236 self.vbox.pack_start(frame)
237 pbox = gtk.VBox(False, 12)
238 pbox.show()
239 frame.add(pbox)
240 hbox = gtk.HBox(False, 12)
241 hbox.show()
242 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
243 label = gtk.Label("BitBake threads:")
244 label.show()
245 spin_max = 9 #self.cpu_cnt * 3
246 hbox.pack_start(label, expand=False, fill=False, padding=6)
247 bbadj = gtk.Adjustment(value=self.bbthread, lower=1, upper=spin_max, step_incr=1)
248 bbspinner = gtk.SpinButton(adjustment=bbadj, climb_rate=1, digits=0)
249 bbspinner.show()
250 bbspinner.connect("value-changed", self.change_bb_threads_cb)
251 hbox.pack_start(bbspinner, expand=False, fill=False, padding=6)
252 label = gtk.Label("Make threads:")
253 label.show()
254 hbox.pack_start(label, expand=False, fill=False, padding=6)
255 madj = gtk.Adjustment(value=self.pmake, lower=1, upper=spin_max, step_incr=1)
256 makespinner = gtk.SpinButton(adjustment=madj, climb_rate=1, digits=0)
257 makespinner.connect("value-changed", self.change_make_threads_cb)
258 makespinner.show()
259 hbox.pack_start(makespinner, expand=False, fill=False, padding=6)
260 # Toolchain
261 label = gtk.Label()
262 label.set_markup("<b>External Toolchain</b>")
263 label.show()
264 frame = gtk.Frame()
265 frame.set_label_widget(label)
266 frame.set_shadow_type(gtk.SHADOW_NONE)
267 frame.show()
268 self.vbox.pack_start(frame)
269 pbox = gtk.VBox(False, 12)
270 pbox.show()
271 frame.add(pbox)
272 hbox = gtk.HBox(False, 12)
273 hbox.show()
274 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
275 toolcheck = gtk.CheckButton("Build external development toolchain with image")
276 toolcheck.show()
277 toolcheck.connect("toggled", self.toggle_toolchain_cb)
278 hbox.pack_start(toolcheck, expand=False, fill=False, padding=6)
279 hbox = gtk.HBox(False, 12)
280 hbox.show()
281 pbox.pack_start(hbox, expand=False, fill=False, padding=6)
282 label = gtk.Label("Toolchain host:")
283 label.show()
284 hbox.pack_start(label, expand=False, fill=False, padding=6)
285 self.sdk_machine_combo = gtk.combo_box_new_text()
286 self.sdk_machine_combo.set_tooltip_text("Select the host architecture of the external machine")
287 self.sdk_machine_combo.show()
288 hbox.pack_start(self.sdk_machine_combo, expand=False, fill=False, padding=6)
289 headerscheck = gtk.CheckButton("Include development headers with toolchain")
290 headerscheck.show()
291 headerscheck.connect("toggled", self.toggle_headers_cb)
292 hbox.pack_start(headerscheck, expand=False, fill=False, padding=6)
293 self.connect("response", self.prefs_response_cb)
diff --git a/bitbake/lib/bb/ui/crumbs/layereditor.py b/bitbake/lib/bb/ui/crumbs/layereditor.py
new file mode 100644
index 0000000000..76a2eb536f
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/layereditor.py
@@ -0,0 +1,136 @@
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
24
25class LayerEditor(gtk.Dialog):
26 """
27 Gtk+ Widget for enabling and disabling layers.
28 Layers are added through using an open dialog to find the layer.conf
29 Disabled layers are deleted from conf/bblayers.conf
30 """
31 def __init__(self, configurator, parent=None):
32 gtk.Dialog.__init__(self, "Layers", None,
33 gtk.DIALOG_DESTROY_WITH_PARENT,
34 (gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
35
36 # We want to show a little more of the treeview in the default,
37 # emptier, case
38 self.set_size_request(-1, 300)
39 self.set_border_width(6)
40 self.vbox.set_property("spacing", 0)
41 self.action_area.set_property("border-width", 6)
42
43 self.configurator = configurator
44 self.newly_added = {}
45
46 # Label to inform users that meta is enabled but that you can't
47 # disable it as it'd be a *bad* idea
48 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."
49 lbl = gtk.Label()
50 lbl.show()
51 lbl.set_use_markup(True)
52 lbl.set_markup(msg)
53 lbl.set_line_wrap(True)
54 lbl.set_justify(gtk.JUSTIFY_FILL)
55 self.vbox.pack_start(lbl, expand=False, fill=False, padding=6)
56
57 # Create a treeview in which to list layers
58 # ListStore of Name, Path, Enabled
59 self.layer_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
60 self.tv = gtk.TreeView(self.layer_store)
61 self.tv.set_headers_visible(True)
62
63 col0 = gtk.TreeViewColumn('Name')
64 self.tv.append_column(col0)
65 col1 = gtk.TreeViewColumn('Path')
66 self.tv.append_column(col1)
67 col2 = gtk.TreeViewColumn('Enabled')
68 self.tv.append_column(col2)
69
70 cell0 = gtk.CellRendererText()
71 col0.pack_start(cell0, True)
72 col0.set_attributes(cell0, text=0)
73 cell1 = gtk.CellRendererText()
74 col1.pack_start(cell1, True)
75 col1.set_attributes(cell1, text=1)
76 cell2 = gtk.CellRendererToggle()
77 cell2.connect("toggled", self._toggle_layer_cb)
78 col2.pack_start(cell2, True)
79 col2.set_attributes(cell2, active=2)
80
81 self.tv.show()
82 self.vbox.pack_start(self.tv, expand=True, fill=True, padding=0)
83
84 tb = gtk.Toolbar()
85 tb.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)
86 tb.set_style(gtk.TOOLBAR_BOTH)
87 tb.set_tooltips(True)
88 tb.show()
89 icon = gtk.Image()
90 icon.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_SMALL_TOOLBAR)
91 icon.show()
92 tb.insert_item("Add Layer", "Add new layer", None, icon,
93 self._find_layer_cb, None, -1)
94 self.vbox.pack_start(tb, expand=False, fill=False, padding=0)
95
96 def set_parent_window(self, parent):
97 self.set_transient_for(parent)
98
99 def load_current_layers(self, data):
100 for layer, path in self.configurator.enabled_layers.items():
101 if layer != 'meta':
102 self.layer_store.append([layer, path, True])
103
104 def save_current_layers(self):
105 self.configurator.writeLayerConf()
106
107 def _toggle_layer_cb(self, cell, path):
108 name = self.layer_store[path][0]
109 toggle = not self.layer_store[path][2]
110 if toggle:
111 self.configurator.addLayer(name, path)
112 else:
113 self.configurator.disableLayer(name)
114 self.layer_store[path][2] = toggle
115
116 def _find_layer_cb(self, button):
117 self.find_layer(self)
118
119 def find_layer(self, parent):
120 dialog = gtk.FileChooserDialog("Add new layer", parent,
121 gtk.FILE_CHOOSER_ACTION_OPEN,
122 (gtk.STOCK_CANCEL, gtk.RESPONSE_NO,
123 gtk.STOCK_OPEN, gtk.RESPONSE_YES))
124 label = gtk.Label("Select the layer.conf of the layer you wish to add")
125 label.show()
126 dialog.set_extra_widget(label)
127 response = dialog.run()
128 path = dialog.get_filename()
129 dialog.destroy()
130
131 if response == gtk.RESPONSE_YES:
132 # FIXME: verify we've actually got a layer conf?
133 if path.endswith(".conf"):
134 name, layerpath = self.configurator.addLayerConf(path)
135 self.newly_added[name] = layerpath
136 self.layer_store.append([name, layerpath, True])
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py
index 70fd57effc..bdab34040c 100644
--- a/bitbake/lib/bb/ui/crumbs/runningbuild.py
+++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -47,12 +47,18 @@ class RunningBuildModel (gtk.TreeStore):
47 47
48class RunningBuild (gobject.GObject): 48class RunningBuild (gobject.GObject):
49 __gsignals__ = { 49 __gsignals__ = {
50 'build-started' : (gobject.SIGNAL_RUN_LAST,
51 gobject.TYPE_NONE,
52 ()),
50 'build-succeeded' : (gobject.SIGNAL_RUN_LAST, 53 'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
51 gobject.TYPE_NONE, 54 gobject.TYPE_NONE,
52 ()), 55 ()),
53 'build-failed' : (gobject.SIGNAL_RUN_LAST, 56 'build-failed' : (gobject.SIGNAL_RUN_LAST,
54 gobject.TYPE_NONE, 57 gobject.TYPE_NONE,
55 ()) 58 ()),
59 'build-complete' : (gobject.SIGNAL_RUN_LAST,
60 gobject.TYPE_NONE,
61 ())
56 } 62 }
57 pids_to_task = {} 63 pids_to_task = {}
58 tasks_to_iter = {} 64 tasks_to_iter = {}
@@ -201,6 +207,7 @@ class RunningBuild (gobject.GObject):
201 207
202 elif isinstance(event, bb.event.BuildStarted): 208 elif isinstance(event, bb.event.BuildStarted):
203 209
210 self.emit("build-started")
204 self.model.prepend(None, (None, 211 self.model.prepend(None, (None,
205 None, 212 None,
206 None, 213 None,
@@ -218,6 +225,9 @@ class RunningBuild (gobject.GObject):
218 Colors.OK, 225 Colors.OK,
219 0)) 226 0))
220 227
228 # Emit a generic "build-complete" signal for things wishing to
229 # handle when the build is finished
230 self.emit("build-complete")
221 # Emit the appropriate signal depending on the number of failures 231 # Emit the appropriate signal depending on the number of failures
222 if (failures >= 1): 232 if (failures >= 1):
223 self.emit ("build-failed") 233 self.emit ("build-failed")
diff --git a/bitbake/lib/bb/ui/crumbs/tasklistmodel.py b/bitbake/lib/bb/ui/crumbs/tasklistmodel.py
index a83a176ddc..d9829861bb 100644
--- a/bitbake/lib/bb/ui/crumbs/tasklistmodel.py
+++ b/bitbake/lib/bb/ui/crumbs/tasklistmodel.py
@@ -20,6 +20,58 @@
20 20
21import gtk 21import gtk
22import gobject 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*(\"\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 # FIXME: Need a better way to determine meta_path...
57 template = """
58# Recipe generated by the HOB
59
60require %s.bb
61
62IMAGE_INSTALL += "%s"
63"""
64 meta_path = model.find_image_path(self.base_image)
65
66 recipe = template % (meta_path, self.userpkgs)
67
68 if os.path.exists(writepath):
69 os.rename(writepath, "%s~" % writepath)
70
71 with open(writepath, 'w') as r:
72 r.write(recipe)
73
74 return writepath
23 75
24class TaskListModel(gtk.ListStore): 76class TaskListModel(gtk.ListStore):
25 """ 77 """
@@ -28,12 +80,18 @@ class TaskListModel(gtk.ListStore):
28 providing convenience functions to access gtk.TreeModel subclasses which 80 providing convenience functions to access gtk.TreeModel subclasses which
29 provide filtered views of the data. 81 provide filtered views of the data.
30 """ 82 """
31 (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC) = range(8) 83 (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_PATH) = range(10)
32 84
33 __gsignals__ = { 85 __gsignals__ = {
34 "tasklist-populated" : (gobject.SIGNAL_RUN_LAST, 86 "tasklist-populated" : (gobject.SIGNAL_RUN_LAST,
35 gobject.TYPE_NONE, 87 gobject.TYPE_NONE,
36 ()) 88 ()),
89 "contents-changed" : (gobject.SIGNAL_RUN_LAST,
90 gobject.TYPE_NONE,
91 (gobject.TYPE_INT,)),
92 "image-changed" : (gobject.SIGNAL_RUN_LAST,
93 gobject.TYPE_NONE,
94 (gobject.TYPE_STRING,)),
37 } 95 }
38 96
39 """ 97 """
@@ -43,6 +101,7 @@ class TaskListModel(gtk.ListStore):
43 self.tasks = None 101 self.tasks = None
44 self.packages = None 102 self.packages = None
45 self.images = None 103 self.images = None
104 self.selected_image = None
46 105
47 gtk.ListStore.__init__ (self, 106 gtk.ListStore.__init__ (self,
48 gobject.TYPE_STRING, 107 gobject.TYPE_STRING,
@@ -52,7 +111,22 @@ class TaskListModel(gtk.ListStore):
52 gobject.TYPE_STRING, 111 gobject.TYPE_STRING,
53 gobject.TYPE_STRING, 112 gobject.TYPE_STRING,
54 gobject.TYPE_STRING, 113 gobject.TYPE_STRING,
55 gobject.TYPE_BOOLEAN) 114 gobject.TYPE_BOOLEAN,
115 gobject.TYPE_BOOLEAN,
116 gobject.TYPE_STRING)
117
118 def contents_changed_cb(self, tree_model, path, it=None):
119 pkg_cnt = self.contents.iter_n_children(None)
120 self.emit("contents-changed", pkg_cnt)
121
122 def contents_model_filter(self, model, it):
123 if not model.get_value(it, self.COL_INC) or model.get_value(it, self.COL_TYPE) == 'image':
124 return False
125 name = model.get_value(it, self.COL_NAME)
126 if name.endswith('-native') or name.endswith('-cross'):
127 return False
128 else:
129 return True
56 130
57 """ 131 """
58 Create, if required, and return a filtered gtk.TreeModel 132 Create, if required, and return a filtered gtk.TreeModel
@@ -62,7 +136,9 @@ class TaskListModel(gtk.ListStore):
62 def contents_model(self): 136 def contents_model(self):
63 if not self.contents: 137 if not self.contents:
64 self.contents = self.filter_new() 138 self.contents = self.filter_new()
65 self.contents.set_visible_column(self.COL_INC) 139 self.contents.set_visible_func(self.contents_model_filter)
140 self.contents.connect("row-inserted", self.contents_changed_cb)
141 self.contents.connect("row-deleted", self.contents_changed_cb)
66 return self.contents 142 return self.contents
67 143
68 """ 144 """
@@ -107,10 +183,10 @@ class TaskListModel(gtk.ListStore):
107 Helper function to determine whether an item is a package 183 Helper function to determine whether an item is a package
108 """ 184 """
109 def package_model_filter(self, model, it): 185 def package_model_filter(self, model, it):
110 if model.get_value(it, self.COL_TYPE) == 'package': 186 if model.get_value(it, self.COL_TYPE) != 'package':
111 return True
112 else:
113 return False 187 return False
188 else:
189 return True
114 190
115 """ 191 """
116 Create, if required, and return a filtered gtk.TreeModel 192 Create, if required, and return a filtered gtk.TreeModel
@@ -129,33 +205,78 @@ class TaskListModel(gtk.ListStore):
129 to notify any listeners that the model is ready 205 to notify any listeners that the model is ready
130 """ 206 """
131 def populate(self, event_model): 207 def populate(self, event_model):
208 # First clear the model, in case repopulating
209 self.clear()
132 for item in event_model["pn"]: 210 for item in event_model["pn"]:
133 atype = 'package' 211 atype = 'package'
134 name = item 212 name = item
135 summary = event_model["pn"][item]["summary"] 213 summary = event_model["pn"][item]["summary"]
136 license = event_model["pn"][item]["license"] 214 lic = event_model["pn"][item]["license"]
137 group = event_model["pn"][item]["section"] 215 group = event_model["pn"][item]["section"]
138 216 filename = event_model["pn"][item]["filename"]
139 depends = event_model["depends"].get(item, "") 217 depends = event_model["depends"].get(item, "")
140 rdepends = event_model["rdepends-pn"].get(item, "") 218 rdepends = event_model["rdepends-pn"].get(item, "")
141 depends = depends + rdepends 219 if rdepends:
220 for rdep in rdepends:
221 if event_model["packages"].get(rdep, ""):
222 pn = event_model["packages"][rdep].get("pn", "")
223 if pn:
224 depends.append(pn)
225
142 self.squish(depends) 226 self.squish(depends)
143 deps = " ".join(depends) 227 deps = " ".join(depends)
144 228
145 if name.count('task-') > 0: 229 if name.count('task-') > 0:
146 atype = 'task' 230 atype = 'task'
147 elif name.count('-image-') > 0: 231 elif name.count('-image-') > 0:
148 atype = 'image' 232 atype = 'image'
149 233
150 self.set(self.append(), self.COL_NAME, name, self.COL_DESC, summary, 234 self.set(self.append(), self.COL_NAME, name, self.COL_DESC, summary,
151 self.COL_LIC, license, self.COL_GROUP, group, 235 self.COL_LIC, lic, self.COL_GROUP, group,
152 self.COL_DEPS, deps, self.COL_BINB, "", 236 self.COL_DEPS, deps, self.COL_BINB, "",
153 self.COL_TYPE, atype, self.COL_INC, False) 237 self.COL_TYPE, atype, self.COL_INC, False,
154 238 self.COL_IMG, False, self.COL_PATH, filename)
239
155 self.emit("tasklist-populated") 240 self.emit("tasklist-populated")
156 241
157 """ 242 """
158 squish lst so that it doesn't contain any duplicates 243 Load a BuildRep into the model
244 """
245 def load_image_rep(self, rep):
246 # Unset everything
247 it = self.get_iter_first()
248 while it:
249 path = self.get_path(it)
250 self[path][self.COL_INC] = False
251 self[path][self.COL_IMG] = False
252 it = self.iter_next(it)
253
254 # Iterate the images and disable them all
255 it = self.images.get_iter_first()
256 while it:
257 path = self.images.convert_path_to_child_path(self.images.get_path(it))
258 name = self[path][self.COL_NAME]
259 if name == rep.base_image:
260 self.include_item(path, image_contents=True)
261 else:
262 self[path][self.COL_INC] = False
263 it = self.images.iter_next(it)
264
265 # Mark all of the additional packages for inclusion
266 packages = rep.packages.split(" ")
267 it = self.get_iter_first()
268 while it:
269 path = self.get_path(it)
270 name = self[path][self.COL_NAME]
271 if name in packages:
272 self.include_item(path)
273 packages.remove(name)
274 it = self.iter_next(it)
275
276 self.emit("image-changed", rep.base_image)
277
278 """
279 squish lst so that it doesn't contain any duplicate entries
159 """ 280 """
160 def squish(self, lst): 281 def squish(self, lst):
161 seen = {} 282 seen = {}
@@ -173,56 +294,59 @@ class TaskListModel(gtk.ListStore):
173 self[path][self.COL_INC] = False 294 self[path][self.COL_INC] = False
174 295
175 """ 296 """
297 recursively called to mark the item at opath and any package which
298 depends on it for removal
176 """ 299 """
177 def mark(self, path): 300 def mark(self, opath):
178 name = self[path][self.COL_NAME]
179 it = self.get_iter_first()
180 removals = [] 301 removals = []
181 #print("Removing %s" % name) 302 it = self.get_iter_first()
303 name = self[opath][self.COL_NAME]
182 304
183 self.remove_item_path(path) 305 self.remove_item_path(opath)
184 306
185 # Remove all dependent packages, update binb 307 # Remove all dependent packages, update binb
186 while it: 308 while it:
187 path = self.get_path(it) 309 path = self.get_path(it)
188 # FIXME: need to ensure partial name matching doesn't happen, regexp? 310 inc = self[path][self.COL_INC]
189 if self[path][self.COL_INC] and self[path][self.COL_DEPS].count(name): 311 deps = self[path][self.COL_DEPS]
190 #print("%s depended on %s, marking for removal" % (self[path][self.COL_NAME], name)) 312 binb = self[path][self.COL_BINB]
313
314 # FIXME: need to ensure partial name matching doesn't happen
315 if inc and deps.count(name):
191 # found a dependency, remove it 316 # found a dependency, remove it
192 self.mark(path) 317 self.mark(path)
193 if self[path][self.COL_INC] and self[path][self.COL_BINB].count(name): 318 if inc and binb.count(name):
194 binb = self.find_alt_dependency(self[path][self.COL_NAME]) 319 bib = self.find_alt_dependency(name)
195 #print("%s was brought in by %s, binb set to %s" % (self[path][self.COL_NAME], name, binb)) 320 self[path][self.COL_BINB] = bib
196 self[path][self.COL_BINB] = binb 321
197 it = self.iter_next(it) 322 it = self.iter_next(it)
198 323
199 """ 324 """
325 Remove items from contents if the have an empty COL_BINB (brought in by)
326 caused by all packages they are a dependency of being removed.
327 If the item isn't a package we leave it included.
200 """ 328 """
201 def sweep_up(self): 329 def sweep_up(self):
330 model = self.contents
202 removals = [] 331 removals = []
203 it = self.get_iter_first() 332 it = self.contents.get_iter_first()
204 333
205 while it: 334 while it:
206 path = self.get_path(it) 335 binb = model.get_value(it, self.COL_BINB)
207 binb = self[path][self.COL_BINB] 336 itype = model.get_value(it, self.COL_TYPE)
208 if binb == "" or binb is None: 337
209 #print("Sweeping up %s" % self[path][self.COL_NAME]) 338 if itype == 'package' and not binb:
210 if not path in removals: 339 opath = model.convert_path_to_child_path(model.get_path(it))
211 removals.extend(path) 340 if not opath in removals:
212 it = self.iter_next(it) 341 removals.extend(opath)
342
343 it = model.iter_next(it)
213 344
214 while removals: 345 while removals:
215 path = removals.pop() 346 path = removals.pop()
216 self.mark(path) 347 self.mark(path)
217 348
218 """ 349 """
219 Remove an item from the contents
220 """
221 def remove_item(self, path):
222 self.mark(path)
223 self.sweep_up()
224
225 """
226 Find the name of an item in the image contents which depends on the item 350 Find the name of an item in the image contents which depends on the item
227 at contents_path returns either an item name (str) or None 351 at contents_path returns either an item name (str) or None
228 NOTE: 352 NOTE:
@@ -238,18 +362,11 @@ class TaskListModel(gtk.ListStore):
238 inc = self[path][self.COL_INC] 362 inc = self[path][self.COL_INC]
239 if itname != name and inc and deps.count(name) > 0: 363 if itname != name and inc and deps.count(name) > 0:
240 # if this item depends on the item, return this items name 364 # if this item depends on the item, return this items name
241 #print("%s depends on %s" % (itname, name))
242 return itname 365 return itname
243 it = self.iter_next(it) 366 it = self.iter_next(it)
244 return "" 367 return ""
245 368
246 """ 369 """
247 Convert a path in self to a path in the filtered contents model
248 """
249 def contents_path_for_path(self, path):
250 return self.contents.convert_child_path_to_path(path)
251
252 """
253 Check the self.contents gtk.TreeModel for an item 370 Check the self.contents gtk.TreeModel for an item
254 where COL_NAME matches item_name 371 where COL_NAME matches item_name
255 Returns True if a match is found, False otherwise 372 Returns True if a match is found, False otherwise
@@ -266,25 +383,30 @@ class TaskListModel(gtk.ListStore):
266 """ 383 """
267 Add this item, and any of its dependencies, to the image contents 384 Add this item, and any of its dependencies, to the image contents
268 """ 385 """
269 def include_item(self, item_path, binb=""): 386 def include_item(self, item_path, binb="", image_contents=False):
270 name = self[item_path][self.COL_NAME] 387 name = self[item_path][self.COL_NAME]
271 deps = self[item_path][self.COL_DEPS] 388 deps = self[item_path][self.COL_DEPS]
272 cur_inc = self[item_path][self.COL_INC] 389 cur_inc = self[item_path][self.COL_INC]
273 #print("Adding %s for %s dependency" % (name, binb))
274 if not cur_inc: 390 if not cur_inc:
275 self[item_path][self.COL_INC] = True 391 self[item_path][self.COL_INC] = True
276 self[item_path][self.COL_BINB] = binb 392 self[item_path][self.COL_BINB] = binb
393 # We want to do some magic with things which are brought in by the base
394 # image so tag them as so
395 if image_contents:
396 self[item_path][self.COL_IMG] = True
397 if self[item_path][self.COL_TYPE] == 'image':
398 self.selected_image = name
399
277 if deps: 400 if deps:
278 #print("Dependencies of %s are %s" % (name, deps))
279 # add all of the deps and set their binb to this item 401 # add all of the deps and set their binb to this item
280 for dep in deps.split(" "): 402 for dep in deps.split(" "):
281 # FIXME: this skipping virtuals can't be right? Unless we choose only to show target
282 # packages? In which case we should handle this server side...
283 # If the contents model doesn't already contain dep, add it 403 # If the contents model doesn't already contain dep, add it
284 if not dep.startswith("virtual") and not self.contents_includes_name(dep): 404 # We only care to show things which will end up in the
405 # resultant image, so filter cross and native recipes
406 if not self.contents_includes_name(dep) and not dep.endswith("-native") and not dep.endswith("-cross"):
285 path = self.find_path_for_item(dep) 407 path = self.find_path_for_item(dep)
286 if path: 408 if path:
287 self.include_item(path, name) 409 self.include_item(path, name, image_contents)
288 else: 410 else:
289 pass 411 pass
290 412
@@ -317,30 +439,78 @@ class TaskListModel(gtk.ListStore):
317 it = self.contents.get_iter_first() 439 it = self.contents.get_iter_first()
318 440
319 """ 441 """
320 Returns True if one of the selected tasks is an image, False otherwise 442 Returns two lists. One of user selected packages and the other containing
443 all selected packages
321 """ 444 """
322 def targets_contains_image(self): 445 def get_selected_packages(self):
323 it = self.images.get_iter_first() 446 allpkgs = []
447 userpkgs = []
448
449 it = self.contents.get_iter_first()
324 while it: 450 while it:
325 path = self.images.get_path(it) 451 sel = self.contents.get_value(it, self.COL_BINB) == "User Selected"
326 inc = self.images[path][self.COL_INC] 452 name = self.contents.get_value(it, self.COL_NAME)
327 if inc: 453 allpkgs.append(name)
328 return True 454 if sel:
329 it = self.images.iter_next(it) 455 userpkgs.append(name)
330 return False 456 it = self.contents.iter_next(it)
457 return userpkgs, allpkgs
331 458
332 """ 459 def get_build_rep(self):
333 Return a list of all selected items which are not -native or -cross 460 userpkgs, allpkgs = self.get_selected_packages()
334 """ 461 image = self.selected_image
335 def get_targets(self): 462
336 tasks = [] 463 return BuildRep(" ".join(userpkgs), " ".join(allpkgs), image)
337 464
465 def find_reverse_depends(self, pn):
466 revdeps = []
338 it = self.contents.get_iter_first() 467 it = self.contents.get_iter_first()
468
339 while it: 469 while it:
340 path = self.contents.get_path(it) 470 if self.contents.get_value(it, self.COL_DEPS).count(pn) != 0:
341 name = self.contents[path][self.COL_NAME] 471 revdeps.append(self.contents.get_value(it, self.COL_NAME))
342 stype = self.contents[path][self.COL_TYPE]
343 if not name.count('-native') and not name.count('-cross'):
344 tasks.append(name)
345 it = self.contents.iter_next(it) 472 it = self.contents.iter_next(it)
346 return tasks 473
474 if pn in revdeps:
475 revdeps.remove(pn)
476 return revdeps
477
478 def set_selected_image(self, img):
479 self.selected_image = img
480 path = self.find_path_for_item(img)
481 self.include_item(item_path=path,
482 binb="User Selected",
483 image_contents=True)
484
485 self.emit("image-changed", self.selected_image)
486
487 def set_selected_packages(self, pkglist):
488 selected = pkglist
489 it = self.get_iter_first()
490
491 while it:
492 name = self.get_value(it, self.COL_NAME)
493 if name in pkglist:
494 pkglist.remove(name)
495 path = self.get_path(it)
496 self.include_item(item_path=path,
497 binb="User Selected")
498 if len(pkglist) == 0:
499 return
500 it = self.iter_next(it)
501
502 def find_image_path(self, image):
503 it = self.images.get_iter_first()
504
505 while it:
506 image_name = self.images.get_value(it, self.COL_NAME)
507 if image_name == image:
508 path = self.images.get_value(it, self.COL_PATH)
509 meta_pattern = "(\S*)/(meta*/)(\S*)"
510 meta_match = re.search(meta_pattern, path)
511 if meta_match:
512 _, lyr, bbrel = path.partition(meta_match.group(2))
513 if bbrel:
514 path = bbrel
515 return path
516 it = self.images.iter_next(it)