diff options
Diffstat (limited to 'bitbake/lib/bb/ui/hob.py')
| -rwxr-xr-x[-rw-r--r--] | bitbake/lib/bb/ui/hob.py | 1115 | 
1 files changed, 48 insertions, 1067 deletions
diff --git a/bitbake/lib/bb/ui/hob.py b/bitbake/lib/bb/ui/hob.py index 0fcaad54a7..429bb750dd 100644..100755 --- a/bitbake/lib/bb/ui/hob.py +++ b/bitbake/lib/bb/ui/hob.py  | |||
| @@ -1,9 +1,11 @@ | |||
| 1 | #!/usr/bin/env python | ||
| 1 | # | 2 | # | 
| 2 | # BitBake Graphical GTK User Interface | 3 | # BitBake Graphical GTK User Interface | 
| 3 | # | 4 | # | 
| 4 | # Copyright (C) 2011 Intel Corporation | 5 | # Copyright (C) 2011 Intel Corporation | 
| 5 | # | 6 | # | 
| 6 | # Authored by Joshua Lock <josh@linux.intel.com> | 7 | # Authored by Joshua Lock <josh@linux.intel.com> | 
| 8 | # Authored by Dongxiao Xu <dongxiao.xu@intel.com> | ||
| 7 | # | 9 | # | 
| 8 | # This program is free software; you can redistribute it and/or modify | 10 | # This program is free software; you can redistribute it and/or modify | 
| 9 | # it under the terms of the GNU General Public License version 2 as | 11 | # it under the terms of the GNU General Public License version 2 as | 
| @@ -18,1087 +20,58 @@ | |||
| 18 | # with this program; if not, write to the Free Software Foundation, Inc., | 20 | # with this program; if not, write to the Free Software Foundation, Inc., | 
| 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 21 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 
| 20 | 22 | ||
| 21 | import glib | ||
| 22 | import gobject | 23 | import gobject | 
| 23 | import gtk | 24 | import gtk | 
| 24 | from bb.ui.crumbs.tasklistmodel import TaskListModel, BuildRep | 25 | import sys | 
| 26 | import os | ||
| 27 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) | ||
| 28 | try: | ||
| 29 | import bb | ||
| 30 | except RuntimeError as exc: | ||
| 31 | sys.exit(str(exc)) | ||
| 32 | from bb.ui import uihelper | ||
| 33 | from bb.ui.crumbs.hoblistmodel import RecipeListModel, PackageListModel | ||
| 25 | from bb.ui.crumbs.hobeventhandler import HobHandler | 34 | from bb.ui.crumbs.hobeventhandler import HobHandler | 
| 26 | from bb.ui.crumbs.configurator import Configurator | 35 | from bb.ui.crumbs.builder import Builder | 
| 27 | from bb.ui.crumbs.hobprefs import HobPrefs | ||
| 28 | from bb.ui.crumbs.layereditor import LayerEditor | ||
| 29 | from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild | ||
| 30 | from bb.ui.crumbs.hig import CrumbsDialog | ||
| 31 | import xmlrpclib | ||
| 32 | import logging | ||
| 33 | import Queue | ||
| 34 | 36 | ||
| 35 | extraCaches = ['bb.cache_extra:HobRecipeInfo'] | 37 | extraCaches = ['bb.cache_extra:HobRecipeInfo'] | 
| 36 | 38 | ||
| 37 | class MainWindow (gtk.Window): | 39 | def event_handle_idle_func(eventHandler, hobHandler): | 
| 38 | 40 | # Consume as many messages as we can in the time available to us | |
| 39 | def __init__(self, taskmodel, handler, configurator, prefs, layers, mach): | 41 | if not eventHandler: | 
| 40 | gtk.Window.__init__(self) | ||
| 41 | # global state | ||
| 42 | self.curr_mach = mach | ||
| 43 | self.machine_handler_id = None | ||
| 44 | self.image_combo_id = None | ||
| 45 | self.generating = False | ||
| 46 | self.files_to_clean = [] | ||
| 47 | self.selected_image = None | ||
| 48 | self.selected_packages = None | ||
| 49 | self.stopping = False | ||
| 50 | |||
| 51 | self.model = taskmodel | ||
| 52 | self.model.connect("tasklist-populated", self.update_model) | ||
| 53 | self.model.connect("image-changed", self.image_changed_string_cb) | ||
| 54 | self.handler = handler | ||
| 55 | self.configurator = configurator | ||
| 56 | self.prefs = prefs | ||
| 57 | self.layers = layers | ||
| 58 | self.save_path = None | ||
| 59 | self.dirty = False | ||
| 60 | self.build_succeeded = False | ||
| 61 | |||
| 62 | self.connect("delete-event", self.destroy_window) | ||
| 63 | self.set_title("Image Creator") | ||
| 64 | self.set_icon_name("applications-development") | ||
| 65 | self.set_default_size(1000, 650) | ||
| 66 | |||
| 67 | self.build = RunningBuild(sequential=True) | ||
| 68 | self.build.connect("build-failed", self.running_build_failed_cb) | ||
| 69 | self.build.connect("build-succeeded", self.running_build_succeeded_cb) | ||
| 70 | self.build.connect("build-started", self.build_started_cb) | ||
| 71 | self.build.connect("build-complete", self.build_complete_cb) | ||
| 72 | |||
| 73 | vbox = gtk.VBox(False, 0) | ||
| 74 | vbox.set_border_width(0) | ||
| 75 | vbox.show() | ||
| 76 | self.add(vbox) | ||
| 77 | self.menu = self.create_menu() | ||
| 78 | vbox.pack_start(self.menu, False) | ||
| 79 | createview = self.create_build_gui() | ||
| 80 | self.back = None | ||
| 81 | self.cancel = None | ||
| 82 | buildview = self.view_build_gui() | ||
| 83 | self.nb = gtk.Notebook() | ||
| 84 | self.nb.append_page(createview) | ||
| 85 | self.nb.append_page(buildview) | ||
| 86 | self.nb.set_current_page(0) | ||
| 87 | self.nb.set_show_tabs(False) | ||
| 88 | vbox.pack_start(self.nb, expand=True, fill=True) | ||
| 89 | |||
| 90 | def destroy_window(self, widget, event): | ||
| 91 | self.quit() | ||
| 92 | |||
| 93 | def menu_quit(self, action): | ||
| 94 | self.quit() | ||
| 95 | |||
| 96 | def quit(self): | ||
| 97 | if self.dirty and len(self.model.contents): | ||
| 98 | question = "Would you like to save your customisations?" | ||
| 99 | dialog = CrumbsDialog(self, question, gtk.STOCK_DIALOG_WARNING) | ||
| 100 | dialog.add_buttons(gtk.STOCK_NO, gtk.RESPONSE_NO, | ||
| 101 | gtk.STOCK_YES, gtk.RESPONSE_YES) | ||
| 102 | resp = dialog.run() | ||
| 103 | dialog.destroy() | ||
| 104 | if resp == gtk.RESPONSE_YES: | ||
| 105 | if not self.save_path: | ||
| 106 | self.get_save_path() | ||
| 107 | |||
| 108 | if self.save_path: | ||
| 109 | self.save_recipe_file() | ||
| 110 | rep = self.model.get_build_rep() | ||
| 111 | rep.writeRecipe(self.save_path, self.model) | ||
| 112 | |||
| 113 | # Prevent the busy cursor being shown after hob exits if quit is called | ||
| 114 | # whilst the busy cursor is set | ||
| 115 | self.set_busy_cursor(False) | ||
| 116 | |||
| 117 | self.handler.remove_temp_dir() | ||
| 118 | |||
| 119 | gtk.main_quit() | ||
| 120 | |||
| 121 | """ | ||
| 122 | In the case of a fatal error give the user as much information as possible | ||
| 123 | and then exit. | ||
| 124 | """ | ||
| 125 | def fatal_error_cb(self, handler, errormsg, phase): | ||
| 126 | lbl = "<b>Error!</b>\nThere was an unrecoverable error during the" | ||
| 127 | lbl = lbl + " <i>%s</i> phase of BitBake. This must be" % phase | ||
| 128 | lbl = lbl + " rectified before the GUI will function. The error" | ||
| 129 | lbl = lbl + " message which which caused this is:\n\n\"%s\"" % errormsg | ||
| 130 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_ERROR) | ||
| 131 | dialog.add_button("Exit", gtk.RESPONSE_OK) | ||
| 132 | response = dialog.run() | ||
| 133 | dialog.destroy() | ||
| 134 | self.set_busy_cursor(False) | ||
| 135 | gtk.main_quit() | ||
| 136 | |||
| 137 | def scroll_tv_cb(self, model, path, it, view): | ||
| 138 | view.scroll_to_cell(path) | ||
| 139 | |||
| 140 | def running_build_succeeded_cb(self, running_build): | ||
| 141 | self.build_succeeded = True | ||
| 142 | |||
| 143 | def running_build_failed_cb(self, running_build): | ||
| 144 | self.build_succeeded = False | ||
| 145 | |||
| 146 | def image_changed_string_cb(self, model, new_image): | ||
| 147 | self.selected_image = new_image | ||
| 148 | # disconnect the image combo's signal handler | ||
| 149 | if self.image_combo_id: | ||
| 150 | self.image_combo.disconnect(self.image_combo_id) | ||
| 151 | self.image_combo_id = None | ||
| 152 | cnt = 0 | ||
| 153 | it = self.model.images.get_iter_first() | ||
| 154 | while it: | ||
| 155 | path = self.model.images.get_path(it) | ||
| 156 | if self.model.images[path][self.model.COL_NAME] == new_image: | ||
| 157 | self.image_combo.set_active(cnt) | ||
| 158 | break | ||
| 159 | it = self.model.images.iter_next(it) | ||
| 160 | cnt = cnt + 1 | ||
| 161 | # Reconnect the signal handler | ||
| 162 | if not self.image_combo_id: | ||
| 163 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
| 164 | |||
| 165 | def image_changed_cb(self, combo): | ||
| 166 | model = self.image_combo.get_model() | ||
| 167 | it = self.image_combo.get_active_iter() | ||
| 168 | if it: | ||
| 169 | path = model.get_path(it) | ||
| 170 | # Firstly, deselect the previous image | ||
| 171 | userp, _ = self.model.get_selected_packages() | ||
| 172 | self.model.reset() | ||
| 173 | # Now select the new image and save its path in case we | ||
| 174 | # change the image later | ||
| 175 | self.toggle_package(path, model, image=True) | ||
| 176 | if len(userp): | ||
| 177 | self.model.set_selected_packages(userp) | ||
| 178 | self.selected_image = model[path][self.model.COL_NAME] | ||
| 179 | |||
| 180 | def reload_triggered_cb(self, handler, image, packages): | ||
| 181 | if image: | ||
| 182 | self.selected_image = image | ||
| 183 | if len(packages): | ||
| 184 | self.selected_packages = packages.split() | ||
| 185 | |||
| 186 | def data_generated(self, handler): | ||
| 187 | self.generating = False | ||
| 188 | self.enable_widgets() | ||
| 189 | |||
| 190 | def machine_combo_changed_cb(self, combo, handler): | ||
| 191 | mach = combo.get_active_text() | ||
| 192 | if mach != self.curr_mach: | ||
| 193 | self.curr_mach = mach | ||
| 194 | # Flush this straight to the file as MACHINE is changed | ||
| 195 | # independently of other 'Preferences' | ||
| 196 | self.configurator.setConfVar('MACHINE', mach) | ||
| 197 | self.configurator.writeConf() | ||
| 198 | handler.set_machine(mach) | ||
| 199 | handler.reload_data() | ||
| 200 | |||
| 201 | def update_machines(self, handler, machines): | ||
| 202 | active = 0 | ||
| 203 | # disconnect the signal handler before updating the combo model | ||
| 204 | if self.machine_handler_id: | ||
| 205 | self.machine_combo.disconnect(self.machine_handler_id) | ||
| 206 | self.machine_handler_id = None | ||
| 207 | |||
| 208 | model = self.machine_combo.get_model() | ||
| 209 | if model: | ||
| 210 | model.clear() | ||
| 211 | |||
| 212 | for machine in machines: | ||
| 213 | self.machine_combo.append_text(machine) | ||
| 214 | if machine == self.curr_mach: | ||
| 215 | self.machine_combo.set_active(active) | ||
| 216 | active = active + 1 | ||
| 217 | |||
| 218 | self.machine_handler_id = self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler) | ||
| 219 | |||
| 220 | def set_busy_cursor(self, busy=True): | ||
| 221 | """ | ||
| 222 | Convenience method to set the cursor to a spinner when executing | ||
| 223 | a potentially lengthy process. | ||
| 224 | A busy value of False will set the cursor back to the default | ||
| 225 | left pointer. | ||
| 226 | """ | ||
| 227 | if busy: | ||
| 228 | cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) | ||
| 229 | else: | ||
| 230 | # TODO: presumably the default cursor is different on RTL | ||
| 231 | # systems. Can we determine the default cursor? Or at least | ||
| 232 | # the cursor which is set before we change it? | ||
| 233 | cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) | ||
| 234 | window = self.get_root_window() | ||
| 235 | window.set_cursor(cursor) | ||
| 236 | |||
| 237 | def busy_idle_func(self): | ||
| 238 | if self.generating: | ||
| 239 | self.progress.pulse() | ||
| 240 | return True | ||
| 241 | else: | ||
| 242 | if not self.image_combo_id: | ||
| 243 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
| 244 | self.progress.set_text("Loaded") | ||
| 245 | self.progress.set_fraction(0.0) | ||
| 246 | self.set_busy_cursor(False) | ||
| 247 | return False | ||
| 248 | |||
| 249 | def busy(self, handler): | ||
| 250 | self.generating = True | ||
| 251 | self.progress.set_text("Loading...") | ||
| 252 | self.set_busy_cursor() | ||
| 253 | if self.image_combo_id: | ||
| 254 | self.image_combo.disconnect(self.image_combo_id) | ||
| 255 | self.image_combo_id = None | ||
| 256 | self.progress.pulse() | ||
| 257 | gobject.timeout_add (100, self.busy_idle_func) | ||
| 258 | self.disable_widgets() | ||
| 259 | |||
| 260 | def enable_widgets(self): | ||
| 261 | self.menu.set_sensitive(True) | ||
| 262 | self.machine_combo.set_sensitive(True) | ||
| 263 | self.image_combo.set_sensitive(True) | ||
| 264 | self.nb.set_sensitive(True) | ||
| 265 | self.contents_tree.set_sensitive(True) | ||
| 266 | |||
| 267 | def disable_widgets(self): | ||
| 268 | self.menu.set_sensitive(False) | ||
| 269 | self.machine_combo.set_sensitive(False) | ||
| 270 | self.image_combo.set_sensitive(False) | ||
| 271 | self.nb.set_sensitive(False) | ||
| 272 | self.contents_tree.set_sensitive(False) | ||
| 273 | |||
| 274 | def update_model(self, model): | ||
| 275 | # We want the packages model to be alphabetised and sortable so create | ||
| 276 | # a TreeModelSort to use in the view | ||
| 277 | pkgsaz_model = gtk.TreeModelSort(self.model.packages_model()) | ||
| 278 | pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) | ||
| 279 | # Unset default sort func so that we only toggle between A-Z and | ||
| 280 | # Z-A sorting | ||
| 281 | pkgsaz_model.set_default_sort_func(None) | ||
| 282 | self.pkgsaz_tree.set_model(pkgsaz_model) | ||
| 283 | |||
| 284 | self.image_combo.set_model(self.model.images_model()) | ||
| 285 | # Without this the image combo is incorrectly sized on first load of the GUI | ||
| 286 | self.image_combo.set_active(0) | ||
| 287 | self.image_combo.set_active(-1) | ||
| 288 | |||
| 289 | if not self.image_combo_id: | ||
| 290 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
| 291 | |||
| 292 | # We want the contents to be alphabetised so create a TreeModelSort to | ||
| 293 | # use in the view | ||
| 294 | contents_model = gtk.TreeModelSort(self.model.contents_model()) | ||
| 295 | contents_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) | ||
| 296 | # Unset default sort func so that we only toggle between A-Z and | ||
| 297 | # Z-A sorting | ||
| 298 | contents_model.set_default_sort_func(None) | ||
| 299 | self.contents_tree.set_model(contents_model) | ||
| 300 | self.tasks_tree.set_model(self.model.tasks_model()) | ||
| 301 | |||
| 302 | if self.selected_image: | ||
| 303 | if self.image_combo_id: | ||
| 304 | self.image_combo.disconnect(self.image_combo_id) | ||
| 305 | self.image_combo_id = None | ||
| 306 | self.model.set_selected_image(self.selected_image) | ||
| 307 | if not self.image_combo_id: | ||
| 308 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
| 309 | |||
| 310 | if self.selected_packages: | ||
| 311 | self.model.set_selected_packages(self.selected_packages) | ||
| 312 | |||
| 313 | def reset_clicked_cb(self, button): | ||
| 314 | lbl = "<b>Reset your selections?</b>\n\nAny new changes you have made will be lost" | ||
| 315 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
| 316 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
| 317 | dialog.add_button("Reset", gtk.RESPONSE_OK) | ||
| 318 | response = dialog.run() | ||
| 319 | dialog.destroy() | ||
| 320 | if response == gtk.RESPONSE_OK: | ||
| 321 | self.reset_build() | ||
| 322 | self.search.set_text("") | ||
| 323 | self.selected_image = None | ||
| 324 | return | ||
| 325 | |||
| 326 | def reset_build(self): | ||
| 327 | if self.image_combo_id: | ||
| 328 | self.image_combo.disconnect(self.image_combo_id) | ||
| 329 | self.image_combo_id = None | ||
| 330 | self.image_combo.set_active(-1) | ||
| 331 | self.model.reset() | ||
| 332 | if not self.image_combo_id: | ||
| 333 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
| 334 | |||
| 335 | def layers_cb(self, action): | ||
| 336 | resp = self.layers.run() | ||
| 337 | self.layers.save_current_layers() | ||
| 338 | self.layers.hide() | ||
| 339 | |||
| 340 | def add_layer_cb(self, action): | ||
| 341 | self.layers.find_layer(self) | ||
| 342 | self.layers.save_current_layers() | ||
| 343 | |||
| 344 | def preferences_cb(self, action): | ||
| 345 | resp = self.prefs.run() | ||
| 346 | self.prefs.write_changes() | ||
| 347 | self.prefs.hide() | ||
| 348 | |||
| 349 | def about_cb(self, action): | ||
| 350 | about = gtk.AboutDialog() | ||
| 351 | about.set_name("Image Creator") | ||
| 352 | about.set_copyright("Copyright (C) 2011 Intel Corporation") | ||
| 353 | about.set_authors(["Joshua Lock <josh@linux.intel.com>"]) | ||
| 354 | about.set_logo_icon_name("applications-development") | ||
| 355 | about.run() | ||
| 356 | about.destroy() | ||
| 357 | |||
| 358 | def save_recipe_file(self): | ||
| 359 | rep = self.model.get_build_rep() | ||
| 360 | rep.writeRecipe(self.save_path, self.model) | ||
| 361 | self.dirty = False | ||
| 362 | |||
| 363 | def get_save_path(self): | ||
| 364 | chooser = gtk.FileChooserDialog(title=None, parent=self, | ||
| 365 | action=gtk.FILE_CHOOSER_ACTION_SAVE, | ||
| 366 | buttons=(gtk.STOCK_CANCEL, | ||
| 367 | gtk.RESPONSE_CANCEL, | ||
| 368 | gtk.STOCK_SAVE, | ||
| 369 | gtk.RESPONSE_OK,)) | ||
| 370 | chooser.set_current_name("myimage.bb") | ||
| 371 | response = chooser.run() | ||
| 372 | if response == gtk.RESPONSE_OK: | ||
| 373 | save_path = chooser.get_filename() | ||
| 374 | else: | ||
| 375 | save_path = None | ||
| 376 | chooser.destroy() | ||
| 377 | self.save_path = save_path | ||
| 378 | |||
| 379 | def save_cb(self, action): | ||
| 380 | if not self.save_path: | ||
| 381 | self.get_save_path() | ||
| 382 | if self.save_path: | ||
| 383 | self.save_recipe_file() | ||
| 384 | |||
| 385 | def save_as_cb(self, action): | ||
| 386 | self.get_save_path() | ||
| 387 | if self.save_path: | ||
| 388 | self.save_recipe_file() | ||
| 389 | |||
| 390 | def open_cb(self, action): | ||
| 391 | chooser = gtk.FileChooserDialog(title=None, parent=self, | ||
| 392 | action=gtk.FILE_CHOOSER_ACTION_OPEN, | ||
| 393 | buttons=(gtk.STOCK_CANCEL, | ||
| 394 | gtk.RESPONSE_CANCEL, | ||
| 395 | gtk.STOCK_OPEN, | ||
| 396 | gtk.RESPONSE_OK)) | ||
| 397 | response = chooser.run() | ||
| 398 | rep = BuildRep(None, None, None) | ||
| 399 | recipe = chooser.get_filename() | ||
| 400 | if response == gtk.RESPONSE_OK: | ||
| 401 | rep.loadRecipe(recipe) | ||
| 402 | self.save_path = recipe | ||
| 403 | self.model.load_image_rep(rep) | ||
| 404 | self.dirty = False | ||
| 405 | chooser.destroy() | ||
| 406 | |||
| 407 | def bake_clicked_cb(self, button): | ||
| 408 | build_image = True | ||
| 409 | |||
| 410 | rep = self.model.get_build_rep() | ||
| 411 | |||
| 412 | # If no base image and no user selected packages don't build anything | ||
| 413 | if not self.selected_image and not len(rep.userpkgs): | ||
| 414 | lbl = "<b>No selections made</b>\nYou have not made any selections" | ||
| 415 | lbl = lbl + " so there isn't anything to bake at this time." | ||
| 416 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) | ||
| 417 | dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) | ||
| 418 | dialog.run() | ||
| 419 | dialog.destroy() | ||
| 420 | return | ||
| 421 | # Else if no base image, ask whether to just build packages or whether | ||
| 422 | # to build a rootfs with the selected packages in | ||
| 423 | elif not self.selected_image: | ||
| 424 | lbl = "<b>Build empty image or only packages?</b>\nA base image" | ||
| 425 | lbl = lbl + " has not been selected.\n\'Empty image' will build" | ||
| 426 | lbl = lbl + " an image with only the selected packages as its" | ||
| 427 | lbl = lbl + " contents.\n'Packages Only' will build only the" | ||
| 428 | lbl = lbl + " selected packages, no image will be created" | ||
| 429 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
| 430 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
| 431 | dialog.add_button("Empty Image", gtk.RESPONSE_OK) | ||
| 432 | dialog.add_button("Packages Only", gtk.RESPONSE_YES) | ||
| 433 | response = dialog.run() | ||
| 434 | dialog.destroy() | ||
| 435 | if response == gtk.RESPONSE_CANCEL: | ||
| 436 | return | ||
| 437 | elif response == gtk.RESPONSE_YES: | ||
| 438 | build_image = False | ||
| 439 | elif response == gtk.RESPONSE_OK: | ||
| 440 | rep.base_image = "empty" | ||
| 441 | |||
| 442 | # Ensure at least one value is set in IMAGE_FSTYPES. | ||
| 443 | have_selected_fstype = False | ||
| 444 | if (len(self.prefs.selected_image_types) and | ||
| 445 | len(self.prefs.selected_image_types[0])): | ||
| 446 | have_selected_fstype = True | ||
| 447 | |||
| 448 | if build_image and not have_selected_fstype: | ||
| 449 | lbl = "<b>No image output type selected</b>\nThere is no image output" | ||
| 450 | lbl = lbl + " selected for the build. Please set an output image type" | ||
| 451 | lbl = lbl + " in the preferences (Edit -> Preferences)." | ||
| 452 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) | ||
| 453 | dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) | ||
| 454 | dialog.run() | ||
| 455 | dialog.destroy() | ||
| 456 | return | ||
| 457 | elif build_image: | ||
| 458 | self.handler.make_temp_dir() | ||
| 459 | recipepath = self.handler.get_temp_recipe_path(rep.base_image) | ||
| 460 | image_name = recipepath.rstrip(".bb") | ||
| 461 | path, sep, image_name = image_name.rpartition("/") | ||
| 462 | |||
| 463 | image = [] | ||
| 464 | image.append(image_name) | ||
| 465 | |||
| 466 | rep.writeRecipe(recipepath, self.model) | ||
| 467 | # In the case where we saved the file for the purpose of building | ||
| 468 | # it we should then delete it so that the users workspace doesn't | ||
| 469 | # contain files they haven't explicitly saved there. | ||
| 470 | if not self.save_path: | ||
| 471 | self.files_to_clean.append(recipepath) | ||
| 472 | |||
| 473 | self.handler.build_targets(image, self.configurator) | ||
| 474 | else: | ||
| 475 | self.handler.build_targets(self.model.get_selected_pn(), self.configurator, "packages") | ||
| 476 | |||
| 477 | # Disable parts of the menu which shouldn't be used whilst building | ||
| 478 | self.set_menus_sensitive(False) | ||
| 479 | self.nb.set_current_page(1) | ||
| 480 | |||
| 481 | def set_menus_sensitive(self, sensitive): | ||
| 482 | self.add_layers_action.set_sensitive(sensitive) | ||
| 483 | self.layers_action.set_sensitive(sensitive) | ||
| 484 | self.prefs_action.set_sensitive(sensitive) | ||
| 485 | self.open_action.set_sensitive(sensitive) | ||
| 486 | |||
| 487 | def back_button_clicked_cb(self, button): | ||
| 488 | self.toggle_createview() | ||
| 489 | |||
| 490 | def toggle_createview(self): | ||
| 491 | self.set_menus_sensitive(True) | ||
| 492 | self.build.reset() | ||
| 493 | self.nb.set_current_page(0) | ||
| 494 | |||
| 495 | def build_complete_cb(self, running_build): | ||
| 496 | # Have the handler process BB events again | ||
| 497 | self.handler.building = False | ||
| 498 | self.stopping = False | ||
| 499 | self.back.connect("clicked", self.back_button_clicked_cb) | ||
| 500 | self.back.set_sensitive(True) | ||
| 501 | self.cancel.set_sensitive(False) | ||
| 502 | for f in self.files_to_clean: | ||
| 503 | try: | ||
| 504 | os.remove(f) | ||
| 505 | except OSError: | ||
| 506 | pass | ||
| 507 | self.files_to_clean.remove(f) | ||
| 508 | self.files_to_clean = [] | ||
| 509 | |||
| 510 | lbl = "<b>Build completed</b>\n\nClick 'Edit Image' to start another build or 'View Messages' to view the messages output during the build." | ||
| 511 | if self.handler.build_type == "image" and self.build_succeeded: | ||
| 512 | deploy = self.handler.get_image_deploy_dir() | ||
| 513 | lbl = lbl + "\n<a href=\"file://%s\" title=\"%s\">Browse folder of built images</a>." % (deploy, deploy) | ||
| 514 | |||
| 515 | dialog = CrumbsDialog(self, lbl) | ||
| 516 | dialog.add_button("View Messages", gtk.RESPONSE_CANCEL) | ||
| 517 | dialog.add_button("Edit Image", gtk.RESPONSE_OK) | ||
| 518 | response = dialog.run() | ||
| 519 | dialog.destroy() | ||
| 520 | if response == gtk.RESPONSE_OK: | ||
| 521 | self.toggle_createview() | ||
| 522 | |||
| 523 | def build_started_cb(self, running_build): | ||
| 524 | self.back.set_sensitive(False) | ||
| 525 | self.cancel.set_sensitive(True) | ||
| 526 | |||
| 527 | def include_gplv3_cb(self, toggle): | ||
| 528 | excluded = toggle.get_active() | ||
| 529 | self.handler.toggle_gplv3(excluded) | ||
| 530 | |||
| 531 | def change_bb_threads(self, spinner): | ||
| 532 | val = spinner.get_value_as_int() | ||
| 533 | self.handler.set_bbthreads(val) | ||
| 534 | |||
| 535 | def change_make_threads(self, spinner): | ||
| 536 | val = spinner.get_value_as_int() | ||
| 537 | self.handler.set_pmake(val) | ||
| 538 | |||
| 539 | def toggle_toolchain(self, check): | ||
| 540 | enabled = check.get_active() | ||
| 541 | self.handler.toggle_toolchain(enabled) | ||
| 542 | |||
| 543 | def toggle_headers(self, check): | ||
| 544 | enabled = check.get_active() | ||
| 545 | self.handler.toggle_toolchain_headers(enabled) | ||
| 546 | |||
| 547 | def toggle_package_idle_cb(self, opath, image): | ||
| 548 | """ | ||
| 549 | As the operations which we're calling on the model can take | ||
| 550 | a significant amount of time (in the order of seconds) during which | ||
| 551 | the GUI is unresponsive as the main loop is blocked perform them in | ||
| 552 | an idle function which at least enables us to set the busy cursor | ||
| 553 | before the UI is blocked giving the appearance of being responsive. | ||
| 554 | """ | ||
| 555 | # Whether the item is currently included | ||
| 556 | inc = self.model[opath][self.model.COL_INC] | ||
| 557 | # FIXME: due to inpredictability of the removal of packages we are | ||
| 558 | # temporarily disabling this feature | ||
| 559 | # If the item is already included, mark it for removal then | ||
| 560 | # the sweep_up() method finds affected items and marks them | ||
| 561 | # appropriately | ||
| 562 | # if inc: | ||
| 563 | # self.model.mark(opath) | ||
| 564 | # self.model.sweep_up() | ||
| 565 | # # If the item isn't included, mark it for inclusion | ||
| 566 | # else: | ||
| 567 | if not inc: | ||
| 568 | self.model.include_item(item_path=opath, | ||
| 569 | binb="User Selected", | ||
| 570 | image_contents=image) | ||
| 571 | |||
| 572 | self.set_busy_cursor(False) | ||
| 573 | return False | 42 | return False | 
| 43 | event = eventHandler.getEvent() | ||
| 44 | while event: | ||
| 45 | hobHandler.handle_event(event) | ||
| 46 | event = eventHandler.getEvent() | ||
| 47 | return True | ||
| 48 | |||
| 49 | def main (server = None, eventHandler = None): | ||
| 50 | bitbake_server = None | ||
| 51 | client_addr = None | ||
| 52 | server_addr = None | ||
| 53 | |||
| 54 | if not eventHandler: | ||
| 55 | helper = uihelper.BBUIHelper() | ||
| 56 | server, eventHandler, server_addr, client_addr = helper.findServerDetails() | ||
| 57 | bitbake_server = server | ||
| 574 | 58 | ||
| 575 | def toggle_package(self, path, model, image=False): | ||
| 576 | inc = model[path][self.model.COL_INC] | ||
| 577 | # Warn user before removing included packages | ||
| 578 | if inc: | ||
| 579 | # FIXME: due to inpredictability of the removal of packages we are | ||
| 580 | # temporarily disabling this feature | ||
| 581 | return | ||
| 582 | # pn = model[path][self.model.COL_NAME] | ||
| 583 | # revdeps = self.model.find_reverse_depends(pn) | ||
| 584 | # if len(revdeps): | ||
| 585 | # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone and all packages which depend on this will be removed\nPackages which depend on %s include %s." % (pn, pn, ", ".join(revdeps).rstrip(",")) | ||
| 586 | # else: | ||
| 587 | # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone." % pn | ||
| 588 | # dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
| 589 | # dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
| 590 | # dialog.add_button("Remove", gtk.RESPONSE_OK) | ||
| 591 | # response = dialog.run() | ||
| 592 | # dialog.destroy() | ||
| 593 | # if response == gtk.RESPONSE_CANCEL: | ||
| 594 | # return | ||
| 595 | |||
| 596 | self.set_busy_cursor() | ||
| 597 | # Convert path to path in original model | ||
| 598 | opath = model.convert_path_to_child_path(path) | ||
| 599 | # This is a potentially length call which can block the | ||
| 600 | # main loop, therefore do the work in an idle func to keep | ||
| 601 | # the UI responsive | ||
| 602 | glib.idle_add(self.toggle_package_idle_cb, opath, image) | ||
| 603 | |||
| 604 | self.dirty = True | ||
| 605 | |||
| 606 | def toggle_include_cb(self, cell, path, tv): | ||
| 607 | model = tv.get_model() | ||
| 608 | self.toggle_package(path, model) | ||
| 609 | |||
| 610 | def toggle_pkg_include_cb(self, cell, path, tv): | ||
| 611 | # there's an extra layer of models in the packages case. | ||
| 612 | sort_model = tv.get_model() | ||
| 613 | cpath = sort_model.convert_path_to_child_path(path) | ||
| 614 | self.toggle_package(cpath, sort_model.get_model()) | ||
| 615 | |||
| 616 | def pkgsaz(self): | ||
| 617 | vbox = gtk.VBox(False, 6) | ||
| 618 | vbox.show() | ||
| 619 | self.pkgsaz_tree = gtk.TreeView() | ||
| 620 | self.pkgsaz_tree.set_headers_visible(True) | ||
| 621 | self.pkgsaz_tree.set_headers_clickable(True) | ||
| 622 | self.pkgsaz_tree.set_enable_search(True) | ||
| 623 | self.pkgsaz_tree.set_search_column(0) | ||
| 624 | self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) | ||
| 625 | |||
| 626 | col = gtk.TreeViewColumn('Package') | ||
| 627 | col.set_clickable(True) | ||
| 628 | col.set_sort_column_id(self.model.COL_NAME) | ||
| 629 | col.set_min_width(220) | ||
| 630 | col1 = gtk.TreeViewColumn('Description') | ||
| 631 | col1.set_resizable(True) | ||
| 632 | col1.set_min_width(360) | ||
| 633 | col2 = gtk.TreeViewColumn('License') | ||
| 634 | col2.set_resizable(True) | ||
| 635 | col2.set_clickable(True) | ||
| 636 | col2.set_sort_column_id(self.model.COL_LIC) | ||
| 637 | col2.set_min_width(170) | ||
| 638 | col3 = gtk.TreeViewColumn('Group') | ||
| 639 | col3.set_clickable(True) | ||
| 640 | col3.set_sort_column_id(self.model.COL_GROUP) | ||
| 641 | col4 = gtk.TreeViewColumn('Included') | ||
| 642 | col4.set_min_width(80) | ||
| 643 | col4.set_max_width(90) | ||
| 644 | col4.set_sort_column_id(self.model.COL_INC) | ||
| 645 | |||
| 646 | self.pkgsaz_tree.append_column(col) | ||
| 647 | self.pkgsaz_tree.append_column(col1) | ||
| 648 | self.pkgsaz_tree.append_column(col2) | ||
| 649 | self.pkgsaz_tree.append_column(col3) | ||
| 650 | self.pkgsaz_tree.append_column(col4) | ||
| 651 | |||
| 652 | cell = gtk.CellRendererText() | ||
| 653 | cell1 = gtk.CellRendererText() | ||
| 654 | cell1.set_property('width-chars', 20) | ||
| 655 | cell2 = gtk.CellRendererText() | ||
| 656 | cell2.set_property('width-chars', 20) | ||
| 657 | cell3 = gtk.CellRendererText() | ||
| 658 | cell4 = gtk.CellRendererToggle() | ||
| 659 | cell4.set_property('activatable', True) | ||
| 660 | cell4.connect("toggled", self.toggle_pkg_include_cb, self.pkgsaz_tree) | ||
| 661 | |||
| 662 | col.pack_start(cell, True) | ||
| 663 | col1.pack_start(cell1, True) | ||
| 664 | col2.pack_start(cell2, True) | ||
| 665 | col3.pack_start(cell3, True) | ||
| 666 | col4.pack_end(cell4, True) | ||
| 667 | |||
| 668 | col.set_attributes(cell, text=self.model.COL_NAME) | ||
| 669 | col1.set_attributes(cell1, text=self.model.COL_DESC) | ||
| 670 | col2.set_attributes(cell2, text=self.model.COL_LIC) | ||
| 671 | col3.set_attributes(cell3, text=self.model.COL_GROUP) | ||
| 672 | col4.set_attributes(cell4, active=self.model.COL_INC) | ||
| 673 | |||
| 674 | self.pkgsaz_tree.show() | ||
| 675 | |||
| 676 | scroll = gtk.ScrolledWindow() | ||
| 677 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | ||
| 678 | scroll.set_shadow_type(gtk.SHADOW_IN) | ||
| 679 | scroll.add(self.pkgsaz_tree) | ||
| 680 | vbox.pack_start(scroll, True, True, 0) | ||
| 681 | |||
| 682 | hb = gtk.HBox(False, 0) | ||
| 683 | hb.show() | ||
| 684 | self.search = gtk.Entry() | ||
| 685 | self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear") | ||
| 686 | self.search.connect("icon-release", self.search_entry_clear_cb) | ||
| 687 | self.search.show() | ||
| 688 | self.pkgsaz_tree.set_search_entry(self.search) | ||
| 689 | hb.pack_end(self.search, False, False, 0) | ||
| 690 | label = gtk.Label("Search packages:") | ||
| 691 | label.show() | ||
| 692 | hb.pack_end(label, False, False, 6) | ||
| 693 | vbox.pack_start(hb, False, False, 0) | ||
| 694 | |||
| 695 | return vbox | ||
| 696 | |||
| 697 | def search_entry_clear_cb(self, entry, icon_pos, event): | ||
| 698 | entry.set_text("") | ||
| 699 | |||
| 700 | def tasks(self): | ||
| 701 | vbox = gtk.VBox(False, 6) | ||
| 702 | vbox.show() | ||
| 703 | self.tasks_tree = gtk.TreeView() | ||
| 704 | self.tasks_tree.set_headers_visible(True) | ||
| 705 | self.tasks_tree.set_headers_clickable(False) | ||
| 706 | self.tasks_tree.set_enable_search(True) | ||
| 707 | self.tasks_tree.set_search_column(0) | ||
| 708 | self.tasks_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) | ||
| 709 | |||
| 710 | col = gtk.TreeViewColumn('Package Collection') | ||
| 711 | col.set_min_width(430) | ||
| 712 | col1 = gtk.TreeViewColumn('Description') | ||
| 713 | col1.set_min_width(430) | ||
| 714 | col2 = gtk.TreeViewColumn('Include') | ||
| 715 | col2.set_min_width(70) | ||
| 716 | col2.set_max_width(80) | ||
| 717 | |||
| 718 | self.tasks_tree.append_column(col) | ||
| 719 | self.tasks_tree.append_column(col1) | ||
| 720 | self.tasks_tree.append_column(col2) | ||
| 721 | |||
| 722 | cell = gtk.CellRendererText() | ||
| 723 | cell1 = gtk.CellRendererText() | ||
| 724 | cell2 = gtk.CellRendererToggle() | ||
| 725 | cell2.set_property('activatable', True) | ||
| 726 | cell2.connect("toggled", self.toggle_include_cb, self.tasks_tree) | ||
| 727 | |||
| 728 | col.pack_start(cell, True) | ||
| 729 | col1.pack_start(cell1, True) | ||
| 730 | col2.pack_end(cell2, True) | ||
| 731 | |||
| 732 | col.set_attributes(cell, text=self.model.COL_NAME) | ||
| 733 | col1.set_attributes(cell1, text=self.model.COL_DESC) | ||
| 734 | col2.set_attributes(cell2, active=self.model.COL_INC) | ||
| 735 | |||
| 736 | self.tasks_tree.show() | ||
| 737 | |||
| 738 | scroll = gtk.ScrolledWindow() | ||
| 739 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | ||
| 740 | scroll.set_shadow_type(gtk.SHADOW_IN) | ||
| 741 | scroll.add(self.tasks_tree) | ||
| 742 | vbox.pack_start(scroll, True, True, 0) | ||
| 743 | |||
| 744 | hb = gtk.HBox(False, 0) | ||
| 745 | hb.show() | ||
| 746 | search = gtk.Entry() | ||
| 747 | search.show() | ||
| 748 | self.tasks_tree.set_search_entry(search) | ||
| 749 | hb.pack_end(search, False, False, 0) | ||
| 750 | label = gtk.Label("Search collections:") | ||
| 751 | label.show() | ||
| 752 | hb.pack_end(label, False, False, 6) | ||
| 753 | vbox.pack_start(hb, False, False, 0) | ||
| 754 | |||
| 755 | return vbox | ||
| 756 | |||
| 757 | def cancel_build(self, button): | ||
| 758 | if self.stopping: | ||
| 759 | lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once," | ||
| 760 | lbl = lbl + " would you like to 'Force Stop' the build?\n\n" | ||
| 761 | lbl = lbl + "This will stop the build as quickly as possible but may" | ||
| 762 | lbl = lbl + " well leave your build directory in an unusable state" | ||
| 763 | lbl = lbl + " that requires manual steps to fix.\n" | ||
| 764 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
| 765 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
| 766 | dialog.add_button("Force Stop", gtk.RESPONSE_YES) | ||
| 767 | else: | ||
| 768 | lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this" | ||
| 769 | lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as" | ||
| 770 | lbl = lbl + " possible but may well leave your build directory in an" | ||
| 771 | lbl = lbl + " unusable state that requires manual steps to fix.\n\n" | ||
| 772 | lbl = lbl + "'Stop' will stop the build as soon as all in" | ||
| 773 | lbl = lbl + " progress build tasks are finished. However if a" | ||
| 774 | lbl = lbl + " lengthy compilation phase is in progress this may take" | ||
| 775 | lbl = lbl + " some time." | ||
| 776 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
| 777 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
| 778 | dialog.add_button("Stop", gtk.RESPONSE_OK) | ||
| 779 | dialog.add_button("Force Stop", gtk.RESPONSE_YES) | ||
| 780 | response = dialog.run() | ||
| 781 | dialog.destroy() | ||
| 782 | if response != gtk.RESPONSE_CANCEL: | ||
| 783 | self.stopping = True | ||
| 784 | if response == gtk.RESPONSE_OK: | ||
| 785 | self.handler.cancel_build() | ||
| 786 | elif response == gtk.RESPONSE_YES: | ||
| 787 | self.handler.cancel_build(True) | ||
| 788 | |||
| 789 | def view_build_gui(self): | ||
| 790 | vbox = gtk.VBox(False, 12) | ||
| 791 | vbox.set_border_width(6) | ||
| 792 | vbox.show() | ||
| 793 | build_tv = RunningBuildTreeView(readonly=True) | ||
| 794 | build_tv.show() | ||
| 795 | build_tv.set_model(self.build.model) | ||
| 796 | self.build.model.connect("row-inserted", self.scroll_tv_cb, build_tv) | ||
| 797 | scrolled_view = gtk.ScrolledWindow () | ||
| 798 | scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) | ||
| 799 | scrolled_view.add(build_tv) | ||
| 800 | scrolled_view.show() | ||
| 801 | vbox.pack_start(scrolled_view, expand=True, fill=True) | ||
| 802 | hbox = gtk.HBox(False, 12) | ||
| 803 | hbox.show() | ||
| 804 | vbox.pack_start(hbox, expand=False, fill=False) | ||
| 805 | self.back = gtk.Button("Back") | ||
| 806 | self.back.show() | ||
| 807 | self.back.set_sensitive(False) | ||
| 808 | hbox.pack_start(self.back, expand=False, fill=False) | ||
| 809 | self.cancel = gtk.Button("Stop Build") | ||
| 810 | self.cancel.connect("clicked", self.cancel_build) | ||
| 811 | self.cancel.show() | ||
| 812 | hbox.pack_end(self.cancel, expand=False, fill=False) | ||
| 813 | |||
| 814 | return vbox | ||
| 815 | |||
| 816 | def create_menu(self): | ||
| 817 | menu_items = '''<ui> | ||
| 818 | <menubar name="MenuBar"> | ||
| 819 | <menu action="File"> | ||
| 820 | <menuitem action="Save"/> | ||
| 821 | <menuitem action="Save As"/> | ||
| 822 | <menuitem action="Open"/> | ||
| 823 | <separator/> | ||
| 824 | <menuitem action="AddLayer" label="Add Layer"/> | ||
| 825 | <separator/> | ||
| 826 | <menuitem action="Quit"/> | ||
| 827 | </menu> | ||
| 828 | <menu action="Edit"> | ||
| 829 | <menuitem action="Layers" label="Layers"/> | ||
| 830 | <menuitem action="Preferences"/> | ||
| 831 | </menu> | ||
| 832 | <menu action="Help"> | ||
| 833 | <menuitem action="About"/> | ||
| 834 | </menu> | ||
| 835 | </menubar> | ||
| 836 | </ui>''' | ||
| 837 | |||
| 838 | uimanager = gtk.UIManager() | ||
| 839 | accel = uimanager.get_accel_group() | ||
| 840 | self.add_accel_group(accel) | ||
| 841 | |||
| 842 | actions = gtk.ActionGroup('ImageCreator') | ||
| 843 | self.actions = actions | ||
| 844 | actions.add_actions([('Quit', gtk.STOCK_QUIT, None, None, None, self.menu_quit,), | ||
| 845 | ('File', None, '_File'), | ||
| 846 | ('Save', gtk.STOCK_SAVE, None, None, None, self.save_cb), | ||
| 847 | ('Save As', gtk.STOCK_SAVE_AS, None, None, None, self.save_as_cb), | ||
| 848 | ('Edit', None, '_Edit'), | ||
| 849 | ('Help', None, '_Help'), | ||
| 850 | ('About', gtk.STOCK_ABOUT, None, None, None, self.about_cb)]) | ||
| 851 | |||
| 852 | self.add_layers_action = gtk.Action('AddLayer', 'Add Layer', None, None) | ||
| 853 | self.add_layers_action.connect("activate", self.add_layer_cb) | ||
| 854 | self.actions.add_action(self.add_layers_action) | ||
| 855 | self.layers_action = gtk.Action('Layers', 'Layers', None, None) | ||
| 856 | self.layers_action.connect("activate", self.layers_cb) | ||
| 857 | self.actions.add_action(self.layers_action) | ||
| 858 | self.prefs_action = gtk.Action('Preferences', 'Preferences', None, None) | ||
| 859 | self.prefs_action.connect("activate", self.preferences_cb) | ||
| 860 | self.actions.add_action(self.prefs_action) | ||
| 861 | self.open_action = gtk.Action('Open', 'Open', None, None) | ||
| 862 | self.open_action.connect("activate", self.open_cb) | ||
| 863 | self.actions.add_action(self.open_action) | ||
| 864 | |||
| 865 | uimanager.insert_action_group(actions, 0) | ||
| 866 | uimanager.add_ui_from_string(menu_items) | ||
| 867 | |||
| 868 | menubar = uimanager.get_widget('/MenuBar') | ||
| 869 | menubar.show_all() | ||
| 870 | |||
| 871 | return menubar | ||
| 872 | |||
| 873 | def info_button_clicked_cb(self, button): | ||
| 874 | info = "We cannot accurately predict the image contents before they are built so instead a best" | ||
| 875 | info = info + " attempt at estimating what the image will contain is listed." | ||
| 876 | dialog = CrumbsDialog(self, info, gtk.STOCK_DIALOG_INFO) | ||
| 877 | dialog.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) | ||
| 878 | resp = dialog.run() | ||
| 879 | dialog.destroy() | ||
| 880 | |||
| 881 | def create_build_gui(self): | ||
| 882 | vbox = gtk.VBox(False, 12) | ||
| 883 | vbox.set_border_width(6) | ||
| 884 | vbox.show() | ||
| 885 | |||
| 886 | hbox = gtk.HBox(False, 12) | ||
| 887 | hbox.show() | ||
| 888 | vbox.pack_start(hbox, expand=False, fill=False) | ||
| 889 | |||
| 890 | label = gtk.Label("Machine:") | ||
| 891 | label.show() | ||
| 892 | hbox.pack_start(label, expand=False, fill=False, padding=6) | ||
| 893 | self.machine_combo = gtk.combo_box_new_text() | ||
| 894 | self.machine_combo.show() | ||
| 895 | self.machine_combo.set_tooltip_text("Selects the architecture of the target board for which you would like to build an image.") | ||
| 896 | hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6) | ||
| 897 | label = gtk.Label("Base image:") | ||
| 898 | label.show() | ||
| 899 | hbox.pack_start(label, expand=False, fill=False, padding=6) | ||
| 900 | self.image_combo = gtk.ComboBox() | ||
| 901 | self.image_combo.show() | ||
| 902 | self.image_combo.set_tooltip_text("Selects the image on which to base the created image") | ||
| 903 | image_combo_cell = gtk.CellRendererText() | ||
| 904 | self.image_combo.pack_start(image_combo_cell, True) | ||
| 905 | self.image_combo.add_attribute(image_combo_cell, 'text', self.model.COL_NAME) | ||
| 906 | hbox.pack_start(self.image_combo, expand=False, fill=False, padding=6) | ||
| 907 | self.progress = gtk.ProgressBar() | ||
| 908 | self.progress.set_size_request(250, -1) | ||
| 909 | hbox.pack_end(self.progress, expand=False, fill=False, padding=6) | ||
| 910 | |||
| 911 | ins = gtk.Notebook() | ||
| 912 | vbox.pack_start(ins, expand=True, fill=True) | ||
| 913 | ins.set_show_tabs(True) | ||
| 914 | label = gtk.Label("Packages") | ||
| 915 | label.show() | ||
| 916 | ins.append_page(self.pkgsaz(), tab_label=label) | ||
| 917 | label = gtk.Label("Package Collections") | ||
| 918 | label.show() | ||
| 919 | ins.append_page(self.tasks(), tab_label=label) | ||
| 920 | ins.set_current_page(0) | ||
| 921 | ins.show_all() | ||
| 922 | |||
| 923 | hbox = gtk.HBox(False, 1) | ||
| 924 | hbox.show() | ||
| 925 | label = gtk.Label("Estimated image contents:") | ||
| 926 | self.model.connect("contents-changed", self.update_package_count_cb, label) | ||
| 927 | label.set_property("xalign", 0.00) | ||
| 928 | label.show() | ||
| 929 | hbox.pack_start(label, expand=False, fill=False, padding=6) | ||
| 930 | info = gtk.Button("?") | ||
| 931 | info.set_tooltip_text("What does this mean?") | ||
| 932 | info.show() | ||
| 933 | info.connect("clicked", self.info_button_clicked_cb) | ||
| 934 | hbox.pack_start(info, expand=False, fill=False, padding=6) | ||
| 935 | vbox.pack_start(hbox, expand=False, fill=False, padding=6) | ||
| 936 | con = self.contents() | ||
| 937 | con.show() | ||
| 938 | vbox.pack_start(con, expand=True, fill=True) | ||
| 939 | |||
| 940 | bbox = gtk.HButtonBox() | ||
| 941 | bbox.set_spacing(12) | ||
| 942 | bbox.set_layout(gtk.BUTTONBOX_END) | ||
| 943 | bbox.show() | ||
| 944 | vbox.pack_start(bbox, expand=False, fill=False) | ||
| 945 | reset = gtk.Button("Reset") | ||
| 946 | reset.connect("clicked", self.reset_clicked_cb) | ||
| 947 | reset.show() | ||
| 948 | bbox.add(reset) | ||
| 949 | bake = gtk.Button("Bake") | ||
| 950 | bake.connect("clicked", self.bake_clicked_cb) | ||
| 951 | bake.show() | ||
| 952 | bbox.add(bake) | ||
| 953 | |||
| 954 | return vbox | ||
| 955 | |||
| 956 | def update_package_count_cb(self, model, count, label): | ||
| 957 | lbl = "Estimated image contents (%s packages):" % count | ||
| 958 | label.set_text(lbl) | ||
| 959 | |||
| 960 | def contents(self): | ||
| 961 | self.contents_tree = gtk.TreeView() | ||
| 962 | self.contents_tree.set_headers_visible(True) | ||
| 963 | self.contents_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) | ||
| 964 | |||
| 965 | # allow searching in the package column | ||
| 966 | self.contents_tree.set_search_column(0) | ||
| 967 | self.contents_tree.set_enable_search(True) | ||
| 968 | |||
| 969 | col = gtk.TreeViewColumn('Package') | ||
| 970 | col.set_sort_column_id(0) | ||
| 971 | col.set_min_width(430) | ||
| 972 | col1 = gtk.TreeViewColumn('Brought in by') | ||
| 973 | col1.set_resizable(True) | ||
| 974 | col1.set_min_width(430) | ||
| 975 | |||
| 976 | self.contents_tree.append_column(col) | ||
| 977 | self.contents_tree.append_column(col1) | ||
| 978 | |||
| 979 | cell = gtk.CellRendererText() | ||
| 980 | cell1 = gtk.CellRendererText() | ||
| 981 | cell1.set_property('width-chars', 20) | ||
| 982 | |||
| 983 | col.pack_start(cell, True) | ||
| 984 | col1.pack_start(cell1, True) | ||
| 985 | |||
| 986 | col.set_attributes(cell, text=self.model.COL_NAME) | ||
| 987 | col1.set_attributes(cell1, text=self.model.COL_BINB) | ||
| 988 | |||
| 989 | self.contents_tree.show() | ||
| 990 | |||
| 991 | scroll = gtk.ScrolledWindow() | ||
| 992 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | ||
| 993 | scroll.set_shadow_type(gtk.SHADOW_IN) | ||
| 994 | scroll.add(self.contents_tree) | ||
| 995 | |||
| 996 | return scroll | ||
| 997 | |||
| 998 | def main (server, eventHandler): | ||
| 999 | gobject.threads_init() | 59 | gobject.threads_init() | 
| 1000 | 60 | ||
| 1001 | # NOTE: For now we require that the user run with pre and post files to | 61 | # That indicates whether the Hob and the bitbake server are | 
| 1002 | # read and store configuration set in the GUI. | 62 | # running on different machines | 
| 1003 | # We hope to adjust this long term as tracked in Yocto Bugzilla #1441 | 63 | # recipe model and package model | 
| 1004 | # http://bugzilla.pokylinux.org/show_bug.cgi?id=1441 | 64 | recipe_model = RecipeListModel() | 
| 1005 | reqfiles = 0 | 65 | package_model = PackageListModel() | 
| 1006 | dep_files = server.runCommand(["getVariable", "__depends"]) or set() | ||
| 1007 | dep_files.union(server.runCommand(["getVariable", "__base_depends"]) or set()) | ||
| 1008 | for f in dep_files: | ||
| 1009 | if f[0].endswith("hob-pre.conf"): | ||
| 1010 | reqfiles = reqfiles + 1 | ||
| 1011 | elif f[0].endswith("hob-post.conf"): | ||
| 1012 | reqfiles = reqfiles + 1 | ||
| 1013 | if reqfiles == 2: | ||
| 1014 | break | ||
| 1015 | if reqfiles < 2: | ||
| 1016 | print("""The hob UI requires a pre file named hob-pre.conf and a post | ||
| 1017 | file named hob-post.conf to store and read its configuration from. Please run | ||
| 1018 | hob with these files, i.e.\n | ||
| 1019 | \bitbake -u hob -r conf/hob-pre.conf -R conf/hob-post.conf""") | ||
| 1020 | return | ||
| 1021 | 66 | ||
| 1022 | taskmodel = TaskListModel() | 67 | hobHandler = HobHandler(bitbake_server, server_addr, client_addr, recipe_model, package_model) | 
| 1023 | configurator = Configurator() | 68 | if hobHandler.kick() == False: | 
| 1024 | handler = HobHandler(taskmodel, server) | ||
| 1025 | mach = server.runCommand(["getVariable", "MACHINE"]) | ||
| 1026 | sdk_mach = server.runCommand(["getVariable", "SDKMACHINE"]) | ||
| 1027 | # If SDKMACHINE not set the default SDK_ARCH is used so we | ||
| 1028 | # should represent that in the GUI | ||
| 1029 | if not sdk_mach: | ||
| 1030 | sdk_mach = server.runCommand(["getVariable", "SDK_ARCH"]) | ||
| 1031 | distro = server.runCommand(["getVariable", "DISTRO"]) | ||
| 1032 | if not distro: | ||
| 1033 | distro = "defaultsetup" | ||
| 1034 | bbthread = server.runCommand(["getVariable", "BB_NUMBER_THREADS"]) | ||
| 1035 | if not bbthread: | ||
| 1036 | bbthread = 1 | ||
| 1037 | else: | ||
| 1038 | bbthread = int(bbthread) | ||
| 1039 | pmake = server.runCommand(["getVariable", "PARALLEL_MAKE"]) | ||
| 1040 | if not pmake: | ||
| 1041 | pmake = 1 | ||
| 1042 | else: | ||
| 1043 | # The PARALLEL_MAKE variable will be of the format: "-j 3" and we only | ||
| 1044 | # want a number for the spinner, so strip everything from the variable | ||
| 1045 | # up to and including the space | ||
| 1046 | pmake = int(pmake.lstrip("-j ")) | ||
| 1047 | |||
| 1048 | selected_image_types = server.runCommand(["getVariable", "IMAGE_FSTYPES"]) | ||
| 1049 | all_image_types = server.runCommand(["getVariable", "IMAGE_TYPES"]) | ||
| 1050 | |||
| 1051 | pclasses = server.runCommand(["getVariable", "PACKAGE_CLASSES"]).split(" ") | ||
| 1052 | # NOTE: we're only supporting one value for PACKAGE_CLASSES being set | ||
| 1053 | # this seems OK because we're using the first package format set in | ||
| 1054 | # PACKAGE_CLASSES and that's the package manager used for the rootfs | ||
| 1055 | pkg, sep, pclass = pclasses[0].rpartition("_") | ||
| 1056 | |||
| 1057 | incompatible = server.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) | ||
| 1058 | gplv3disabled = False | ||
| 1059 | if incompatible and incompatible.lower().find("gplv3") != -1: | ||
| 1060 | gplv3disabled = True | ||
| 1061 | |||
| 1062 | build_toolchain = bool(server.runCommand(["getVariable", "HOB_BUILD_TOOLCHAIN"])) | ||
| 1063 | handler.toggle_toolchain(build_toolchain) | ||
| 1064 | build_headers = bool(server.runCommand(["getVariable", "HOB_BUILD_TOOLCHAIN_HEADERS"])) | ||
| 1065 | handler.toggle_toolchain_headers(build_headers) | ||
| 1066 | |||
| 1067 | prefs = HobPrefs(configurator, handler, sdk_mach, distro, pclass, | ||
| 1068 | pmake, bbthread, selected_image_types, all_image_types, | ||
| 1069 | gplv3disabled, build_toolchain, build_headers) | ||
| 1070 | layers = LayerEditor(configurator, None) | ||
| 1071 | window = MainWindow(taskmodel, handler, configurator, prefs, layers, mach) | ||
| 1072 | prefs.set_parent_window(window) | ||
| 1073 | layers.set_parent_window(window) | ||
| 1074 | window.show_all () | ||
| 1075 | handler.connect("machines-updated", window.update_machines) | ||
| 1076 | handler.connect("sdk-machines-updated", prefs.update_sdk_machines) | ||
| 1077 | handler.connect("distros-updated", prefs.update_distros) | ||
| 1078 | handler.connect("package-formats-found", prefs.update_package_formats) | ||
| 1079 | handler.connect("generating-data", window.busy) | ||
| 1080 | handler.connect("data-generated", window.data_generated) | ||
| 1081 | handler.connect("reload-triggered", window.reload_triggered_cb) | ||
| 1082 | configurator.connect("layers-loaded", layers.load_current_layers) | ||
| 1083 | configurator.connect("layers-changed", handler.reload_data) | ||
| 1084 | handler.connect("config-found", configurator.configFound) | ||
| 1085 | handler.connect("fatal-error", window.fatal_error_cb) | ||
| 1086 | |||
| 1087 | try: | ||
| 1088 | # kick the while thing off | ||
| 1089 | handler.current_command = handler.CFG_PATH_LOCAL | ||
| 1090 | server.runCommand(["findConfigFilePath", "local.conf"]) | ||
| 1091 | except xmlrpclib.Fault: | ||
| 1092 | print("XMLRPC Fault getting commandline:\n %s" % x) | ||
| 1093 | return 1 | 69 | return 1 | 
| 70 | builder = Builder(hobHandler, recipe_model, package_model) | ||
| 1094 | 71 | ||
| 1095 | # This timeout function regularly probes the event queue to find out if we | 72 | # This timeout function regularly probes the event queue to find out if we | 
| 1096 | # have any messages waiting for us. | 73 | # have any messages waiting for us. | 
| 1097 | gobject.timeout_add (100, | 74 | gobject.timeout_add(10, event_handle_idle_func, eventHandler, hobHandler) | 
| 1098 | handler.event_handle_idle_func, | ||
| 1099 | eventHandler, | ||
| 1100 | window.build, | ||
| 1101 | window.progress) | ||
| 1102 | 75 | ||
| 1103 | try: | 76 | try: | 
| 1104 | gtk.main() | 77 | gtk.main() | 
| @@ -1107,5 +80,13 @@ hob with these files, i.e.\n | |||
| 1107 | if ioerror.args[0] == 4: | 80 | if ioerror.args[0] == 4: | 
| 1108 | pass | 81 | pass | 
| 1109 | finally: | 82 | finally: | 
| 1110 | server.runCommand(["stateStop"]) | 83 | hobHandler.cancel_build(force = True) | 
| 1111 | 84 | ||
| 85 | if __name__ == "__main__": | ||
| 86 | try: | ||
| 87 | ret = main() | ||
| 88 | except Exception: | ||
| 89 | ret = 1 | ||
| 90 | import traceback | ||
| 91 | traceback.print_exc(15) | ||
| 92 | sys.exit(ret) | ||
