diff options
author | Joshua Lock <josh@linux.intel.com> | 2011-07-01 15:58:50 -0700 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2011-07-05 14:40:30 +0100 |
commit | 4cc291c007103c19779f995e852b37dbad122293 (patch) | |
tree | 3447d62ef1ba2eca08137b8e13df58f8a337a453 /bitbake/lib/bb/ui/crumbs | |
parent | 7fc9c3488f7865111ec052d1cab213d216d2b414 (diff) | |
download | poky-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.py | 278 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/crumbs/hig.py | 61 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/crumbs/hobeventhandler.py | 218 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/crumbs/hobprefs.py | 293 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/crumbs/layereditor.py | 136 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/crumbs/runningbuild.py | 12 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/crumbs/tasklistmodel.py | 326 |
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 | |||
21 | import gobject | ||
22 | import copy | ||
23 | import re, os | ||
24 | from bb import data | ||
25 | |||
26 | class 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 | |||
21 | import gobject | ||
22 | import gtk | ||
23 | """ | ||
24 | The following are convenience classes for implementing GNOME HIG compliant | ||
25 | BitBake GUI's | ||
26 | In summary: spacing = 12px, border-width = 6px | ||
27 | """ | ||
28 | |||
29 | class 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 | ||
21 | import gobject | 21 | import gobject |
22 | from bb.ui.crumbs.progress import ProgressBar | ||
23 | 22 | ||
24 | progress_total = 0 | 23 | progress_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 | |||
21 | import gtk | ||
22 | from bb.ui.crumbs.configurator import Configurator | ||
23 | |||
24 | class 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 | |||
21 | import gobject | ||
22 | import gtk | ||
23 | from bb.ui.crumbs.configurator import Configurator | ||
24 | |||
25 | class 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 | ||
48 | class RunningBuild (gobject.GObject): | 48 | class 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 | ||
21 | import gtk | 21 | import gtk |
22 | import gobject | 22 | import gobject |
23 | import re | ||
24 | |||
25 | class 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 | |||
60 | require %s.bb | ||
61 | |||
62 | IMAGE_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 | ||
24 | class TaskListModel(gtk.ListStore): | 76 | class 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) | ||