diff options
author | Dongxiao Xu <dongxiao.xu@intel.com> | 2011-11-28 14:32:40 +0800 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2012-02-24 17:51:20 +0000 |
commit | 4dacd29f9c957d20f4583330b51e5420f9c3338d (patch) | |
tree | ea13a7e1e08e68d2c6aaeafe34892a9603717307 /lib/bb/ui/hob.py | |
parent | 82482aae6f311c994275fb0b6b32d954bbfc78c3 (diff) | |
download | bitbake-4dacd29f9c957d20f4583330b51e5420f9c3338d.tar.gz |
Hob: A new implemetation (v2)
This commit implements a new design for hob
Some of the new features:
- Friendly new designed GUI. Quick response to user actions.
- Two step builds support package generation and image generation.
- Support running GUI seprarately from bitbake server.
- Recipe/package selection and deselection.
- Accurate customization for image contents and size.
- Progress bars showing the parsing and build status.
- Load/save user configurations from/into templates.
Signed-off-by: Dongxiao Xu <dongxiao.xu@intel.com>
Signed-off-by: Shane Wang <shane.wang@intel.com>
Signed-off-by: Liming An <limingx.l.an@intel.com>
Signed-off-by: Fengxia Hua <fengxia.hua@intel.com>
Designed-by: Belen Barros Pena <belen.barros.pena@intel.com>
Diffstat (limited to 'lib/bb/ui/hob.py')
-rwxr-xr-x[-rw-r--r--] | lib/bb/ui/hob.py | 1115 |
1 files changed, 48 insertions, 1067 deletions
diff --git a/lib/bb/ui/hob.py b/lib/bb/ui/hob.py index 0fcaad54a..429bb750d 100644..100755 --- a/lib/bb/ui/hob.py +++ b/lib/bb/ui/hob.py @@ -1,9 +1,11 @@ +#!/usr/bin/env python # # BitBake Graphical GTK User Interface # # Copyright (C) 2011 Intel Corporation # # Authored by Joshua Lock <josh@linux.intel.com> +# Authored by Dongxiao Xu <dongxiao.xu@intel.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as @@ -18,1087 +20,58 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import glib import gobject import gtk -from bb.ui.crumbs.tasklistmodel import TaskListModel, BuildRep +import sys +import os +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) +try: + import bb +except RuntimeError as exc: + sys.exit(str(exc)) +from bb.ui import uihelper +from bb.ui.crumbs.hoblistmodel import RecipeListModel, PackageListModel from bb.ui.crumbs.hobeventhandler import HobHandler -from bb.ui.crumbs.configurator import Configurator -from bb.ui.crumbs.hobprefs import HobPrefs -from bb.ui.crumbs.layereditor import LayerEditor -from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild -from bb.ui.crumbs.hig import CrumbsDialog -import xmlrpclib -import logging -import Queue +from bb.ui.crumbs.builder import Builder extraCaches = ['bb.cache_extra:HobRecipeInfo'] -class MainWindow (gtk.Window): - - def __init__(self, taskmodel, handler, configurator, prefs, layers, mach): - gtk.Window.__init__(self) - # global state - self.curr_mach = mach - self.machine_handler_id = None - self.image_combo_id = None - self.generating = False - self.files_to_clean = [] - self.selected_image = None - self.selected_packages = None - self.stopping = False - - self.model = taskmodel - self.model.connect("tasklist-populated", self.update_model) - self.model.connect("image-changed", self.image_changed_string_cb) - self.handler = handler - self.configurator = configurator - self.prefs = prefs - self.layers = layers - self.save_path = None - self.dirty = False - self.build_succeeded = False - - self.connect("delete-event", self.destroy_window) - self.set_title("Image Creator") - self.set_icon_name("applications-development") - self.set_default_size(1000, 650) - - self.build = RunningBuild(sequential=True) - self.build.connect("build-failed", self.running_build_failed_cb) - self.build.connect("build-succeeded", self.running_build_succeeded_cb) - self.build.connect("build-started", self.build_started_cb) - self.build.connect("build-complete", self.build_complete_cb) - - vbox = gtk.VBox(False, 0) - vbox.set_border_width(0) - vbox.show() - self.add(vbox) - self.menu = self.create_menu() - vbox.pack_start(self.menu, False) - createview = self.create_build_gui() - self.back = None - self.cancel = None - buildview = self.view_build_gui() - self.nb = gtk.Notebook() - self.nb.append_page(createview) - self.nb.append_page(buildview) - self.nb.set_current_page(0) - self.nb.set_show_tabs(False) - vbox.pack_start(self.nb, expand=True, fill=True) - - def destroy_window(self, widget, event): - self.quit() - - def menu_quit(self, action): - self.quit() - - def quit(self): - if self.dirty and len(self.model.contents): - question = "Would you like to save your customisations?" - dialog = CrumbsDialog(self, question, gtk.STOCK_DIALOG_WARNING) - dialog.add_buttons(gtk.STOCK_NO, gtk.RESPONSE_NO, - gtk.STOCK_YES, gtk.RESPONSE_YES) - resp = dialog.run() - dialog.destroy() - if resp == gtk.RESPONSE_YES: - if not self.save_path: - self.get_save_path() - - if self.save_path: - self.save_recipe_file() - rep = self.model.get_build_rep() - rep.writeRecipe(self.save_path, self.model) - - # Prevent the busy cursor being shown after hob exits if quit is called - # whilst the busy cursor is set - self.set_busy_cursor(False) - - self.handler.remove_temp_dir() - - gtk.main_quit() - - """ - In the case of a fatal error give the user as much information as possible - and then exit. - """ - def fatal_error_cb(self, handler, errormsg, phase): - lbl = "<b>Error!</b>\nThere was an unrecoverable error during the" - lbl = lbl + " <i>%s</i> phase of BitBake. This must be" % phase - lbl = lbl + " rectified before the GUI will function. The error" - lbl = lbl + " message which which caused this is:\n\n\"%s\"" % errormsg - dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_ERROR) - dialog.add_button("Exit", gtk.RESPONSE_OK) - response = dialog.run() - dialog.destroy() - self.set_busy_cursor(False) - gtk.main_quit() - - def scroll_tv_cb(self, model, path, it, view): - view.scroll_to_cell(path) - - def running_build_succeeded_cb(self, running_build): - self.build_succeeded = True - - def running_build_failed_cb(self, running_build): - self.build_succeeded = False - - def image_changed_string_cb(self, model, new_image): - self.selected_image = new_image - # disconnect the image combo's signal handler - if self.image_combo_id: - self.image_combo.disconnect(self.image_combo_id) - self.image_combo_id = None - cnt = 0 - it = self.model.images.get_iter_first() - while it: - path = self.model.images.get_path(it) - if self.model.images[path][self.model.COL_NAME] == new_image: - self.image_combo.set_active(cnt) - break - it = self.model.images.iter_next(it) - cnt = cnt + 1 - # Reconnect the signal handler - if not self.image_combo_id: - self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) - - def image_changed_cb(self, combo): - model = self.image_combo.get_model() - it = self.image_combo.get_active_iter() - if it: - path = model.get_path(it) - # Firstly, deselect the previous image - userp, _ = self.model.get_selected_packages() - self.model.reset() - # Now select the new image and save its path in case we - # change the image later - self.toggle_package(path, model, image=True) - if len(userp): - self.model.set_selected_packages(userp) - self.selected_image = model[path][self.model.COL_NAME] - - def reload_triggered_cb(self, handler, image, packages): - if image: - self.selected_image = image - if len(packages): - self.selected_packages = packages.split() - - def data_generated(self, handler): - self.generating = False - self.enable_widgets() - - def machine_combo_changed_cb(self, combo, handler): - mach = combo.get_active_text() - if mach != self.curr_mach: - self.curr_mach = mach - # Flush this straight to the file as MACHINE is changed - # independently of other 'Preferences' - self.configurator.setConfVar('MACHINE', mach) - self.configurator.writeConf() - handler.set_machine(mach) - handler.reload_data() - - def update_machines(self, handler, machines): - active = 0 - # disconnect the signal handler before updating the combo model - if self.machine_handler_id: - self.machine_combo.disconnect(self.machine_handler_id) - self.machine_handler_id = None - - model = self.machine_combo.get_model() - if model: - model.clear() - - for machine in machines: - self.machine_combo.append_text(machine) - if machine == self.curr_mach: - self.machine_combo.set_active(active) - active = active + 1 - - self.machine_handler_id = self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler) - - def set_busy_cursor(self, busy=True): - """ - Convenience method to set the cursor to a spinner when executing - a potentially lengthy process. - A busy value of False will set the cursor back to the default - left pointer. - """ - if busy: - cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) - else: - # TODO: presumably the default cursor is different on RTL - # systems. Can we determine the default cursor? Or at least - # the cursor which is set before we change it? - cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) - window = self.get_root_window() - window.set_cursor(cursor) - - def busy_idle_func(self): - if self.generating: - self.progress.pulse() - return True - else: - if not self.image_combo_id: - self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) - self.progress.set_text("Loaded") - self.progress.set_fraction(0.0) - self.set_busy_cursor(False) - return False - - def busy(self, handler): - self.generating = True - self.progress.set_text("Loading...") - self.set_busy_cursor() - if self.image_combo_id: - self.image_combo.disconnect(self.image_combo_id) - self.image_combo_id = None - self.progress.pulse() - gobject.timeout_add (100, self.busy_idle_func) - self.disable_widgets() - - def enable_widgets(self): - self.menu.set_sensitive(True) - self.machine_combo.set_sensitive(True) - self.image_combo.set_sensitive(True) - self.nb.set_sensitive(True) - self.contents_tree.set_sensitive(True) - - def disable_widgets(self): - self.menu.set_sensitive(False) - self.machine_combo.set_sensitive(False) - self.image_combo.set_sensitive(False) - self.nb.set_sensitive(False) - self.contents_tree.set_sensitive(False) - - def update_model(self, model): - # We want the packages model to be alphabetised and sortable so create - # a TreeModelSort to use in the view - pkgsaz_model = gtk.TreeModelSort(self.model.packages_model()) - pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) - # Unset default sort func so that we only toggle between A-Z and - # Z-A sorting - pkgsaz_model.set_default_sort_func(None) - self.pkgsaz_tree.set_model(pkgsaz_model) - - self.image_combo.set_model(self.model.images_model()) - # Without this the image combo is incorrectly sized on first load of the GUI - self.image_combo.set_active(0) - self.image_combo.set_active(-1) - - if not self.image_combo_id: - self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) - - # We want the contents to be alphabetised so create a TreeModelSort to - # use in the view - contents_model = gtk.TreeModelSort(self.model.contents_model()) - contents_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) - # Unset default sort func so that we only toggle between A-Z and - # Z-A sorting - contents_model.set_default_sort_func(None) - self.contents_tree.set_model(contents_model) - self.tasks_tree.set_model(self.model.tasks_model()) - - if self.selected_image: - if self.image_combo_id: - self.image_combo.disconnect(self.image_combo_id) - self.image_combo_id = None - self.model.set_selected_image(self.selected_image) - if not self.image_combo_id: - self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) - - if self.selected_packages: - self.model.set_selected_packages(self.selected_packages) - - def reset_clicked_cb(self, button): - lbl = "<b>Reset your selections?</b>\n\nAny new changes you have made will be lost" - dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) - dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) - dialog.add_button("Reset", gtk.RESPONSE_OK) - response = dialog.run() - dialog.destroy() - if response == gtk.RESPONSE_OK: - self.reset_build() - self.search.set_text("") - self.selected_image = None - return - - def reset_build(self): - if self.image_combo_id: - self.image_combo.disconnect(self.image_combo_id) - self.image_combo_id = None - self.image_combo.set_active(-1) - self.model.reset() - if not self.image_combo_id: - self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) - - def layers_cb(self, action): - resp = self.layers.run() - self.layers.save_current_layers() - self.layers.hide() - - def add_layer_cb(self, action): - self.layers.find_layer(self) - self.layers.save_current_layers() - - def preferences_cb(self, action): - resp = self.prefs.run() - self.prefs.write_changes() - self.prefs.hide() - - def about_cb(self, action): - about = gtk.AboutDialog() - about.set_name("Image Creator") - about.set_copyright("Copyright (C) 2011 Intel Corporation") - about.set_authors(["Joshua Lock <josh@linux.intel.com>"]) - about.set_logo_icon_name("applications-development") - about.run() - about.destroy() - - def save_recipe_file(self): - rep = self.model.get_build_rep() - rep.writeRecipe(self.save_path, self.model) - self.dirty = False - - def get_save_path(self): - chooser = gtk.FileChooserDialog(title=None, parent=self, - action=gtk.FILE_CHOOSER_ACTION_SAVE, - buttons=(gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, - gtk.RESPONSE_OK,)) - chooser.set_current_name("myimage.bb") - response = chooser.run() - if response == gtk.RESPONSE_OK: - save_path = chooser.get_filename() - else: - save_path = None - chooser.destroy() - self.save_path = save_path - - def save_cb(self, action): - if not self.save_path: - self.get_save_path() - if self.save_path: - self.save_recipe_file() - - def save_as_cb(self, action): - self.get_save_path() - if self.save_path: - self.save_recipe_file() - - def open_cb(self, action): - chooser = gtk.FileChooserDialog(title=None, parent=self, - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=(gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - response = chooser.run() - rep = BuildRep(None, None, None) - recipe = chooser.get_filename() - if response == gtk.RESPONSE_OK: - rep.loadRecipe(recipe) - self.save_path = recipe - self.model.load_image_rep(rep) - self.dirty = False - chooser.destroy() - - def bake_clicked_cb(self, button): - build_image = True - - rep = self.model.get_build_rep() - - # If no base image and no user selected packages don't build anything - if not self.selected_image and not len(rep.userpkgs): - lbl = "<b>No selections made</b>\nYou have not made any selections" - lbl = lbl + " so there isn't anything to bake at this time." - dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) - dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) - dialog.run() - dialog.destroy() - return - # Else if no base image, ask whether to just build packages or whether - # to build a rootfs with the selected packages in - elif not self.selected_image: - lbl = "<b>Build empty image or only packages?</b>\nA base image" - lbl = lbl + " has not been selected.\n\'Empty image' will build" - lbl = lbl + " an image with only the selected packages as its" - lbl = lbl + " contents.\n'Packages Only' will build only the" - lbl = lbl + " selected packages, no image will be created" - dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) - dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) - dialog.add_button("Empty Image", gtk.RESPONSE_OK) - dialog.add_button("Packages Only", gtk.RESPONSE_YES) - response = dialog.run() - dialog.destroy() - if response == gtk.RESPONSE_CANCEL: - return - elif response == gtk.RESPONSE_YES: - build_image = False - elif response == gtk.RESPONSE_OK: - rep.base_image = "empty" - - # Ensure at least one value is set in IMAGE_FSTYPES. - have_selected_fstype = False - if (len(self.prefs.selected_image_types) and - len(self.prefs.selected_image_types[0])): - have_selected_fstype = True - - if build_image and not have_selected_fstype: - lbl = "<b>No image output type selected</b>\nThere is no image output" - lbl = lbl + " selected for the build. Please set an output image type" - lbl = lbl + " in the preferences (Edit -> Preferences)." - dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) - dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) - dialog.run() - dialog.destroy() - return - elif build_image: - self.handler.make_temp_dir() - recipepath = self.handler.get_temp_recipe_path(rep.base_image) - image_name = recipepath.rstrip(".bb") - path, sep, image_name = image_name.rpartition("/") - - image = [] - image.append(image_name) - - rep.writeRecipe(recipepath, self.model) - # In the case where we saved the file for the purpose of building - # it we should then delete it so that the users workspace doesn't - # contain files they haven't explicitly saved there. - if not self.save_path: - self.files_to_clean.append(recipepath) - - self.handler.build_targets(image, self.configurator) - else: - self.handler.build_targets(self.model.get_selected_pn(), self.configurator, "packages") - - # Disable parts of the menu which shouldn't be used whilst building - self.set_menus_sensitive(False) - self.nb.set_current_page(1) - - def set_menus_sensitive(self, sensitive): - self.add_layers_action.set_sensitive(sensitive) - self.layers_action.set_sensitive(sensitive) - self.prefs_action.set_sensitive(sensitive) - self.open_action.set_sensitive(sensitive) - - def back_button_clicked_cb(self, button): - self.toggle_createview() - - def toggle_createview(self): - self.set_menus_sensitive(True) - self.build.reset() - self.nb.set_current_page(0) - - def build_complete_cb(self, running_build): - # Have the handler process BB events again - self.handler.building = False - self.stopping = False - self.back.connect("clicked", self.back_button_clicked_cb) - self.back.set_sensitive(True) - self.cancel.set_sensitive(False) - for f in self.files_to_clean: - try: - os.remove(f) - except OSError: - pass - self.files_to_clean.remove(f) - self.files_to_clean = [] - - lbl = "<b>Build completed</b>\n\nClick 'Edit Image' to start another build or 'View Messages' to view the messages output during the build." - if self.handler.build_type == "image" and self.build_succeeded: - deploy = self.handler.get_image_deploy_dir() - lbl = lbl + "\n<a href=\"file://%s\" title=\"%s\">Browse folder of built images</a>." % (deploy, deploy) - - dialog = CrumbsDialog(self, lbl) - dialog.add_button("View Messages", gtk.RESPONSE_CANCEL) - dialog.add_button("Edit Image", gtk.RESPONSE_OK) - response = dialog.run() - dialog.destroy() - if response == gtk.RESPONSE_OK: - self.toggle_createview() - - def build_started_cb(self, running_build): - self.back.set_sensitive(False) - self.cancel.set_sensitive(True) - - def include_gplv3_cb(self, toggle): - excluded = toggle.get_active() - self.handler.toggle_gplv3(excluded) - - def change_bb_threads(self, spinner): - val = spinner.get_value_as_int() - self.handler.set_bbthreads(val) - - def change_make_threads(self, spinner): - val = spinner.get_value_as_int() - self.handler.set_pmake(val) - - def toggle_toolchain(self, check): - enabled = check.get_active() - self.handler.toggle_toolchain(enabled) - - def toggle_headers(self, check): - enabled = check.get_active() - self.handler.toggle_toolchain_headers(enabled) - - def toggle_package_idle_cb(self, opath, image): - """ - As the operations which we're calling on the model can take - a significant amount of time (in the order of seconds) during which - the GUI is unresponsive as the main loop is blocked perform them in - an idle function which at least enables us to set the busy cursor - before the UI is blocked giving the appearance of being responsive. - """ - # Whether the item is currently included - inc = self.model[opath][self.model.COL_INC] - # FIXME: due to inpredictability of the removal of packages we are - # temporarily disabling this feature - # If the item is already included, mark it for removal then - # the sweep_up() method finds affected items and marks them - # appropriately - # if inc: - # self.model.mark(opath) - # self.model.sweep_up() - # # If the item isn't included, mark it for inclusion - # else: - if not inc: - self.model.include_item(item_path=opath, - binb="User Selected", - image_contents=image) - - self.set_busy_cursor(False) +def event_handle_idle_func(eventHandler, hobHandler): + # Consume as many messages as we can in the time available to us + if not eventHandler: return False + event = eventHandler.getEvent() + while event: + hobHandler.handle_event(event) + event = eventHandler.getEvent() + return True + +def main (server = None, eventHandler = None): + bitbake_server = None + client_addr = None + server_addr = None + + if not eventHandler: + helper = uihelper.BBUIHelper() + server, eventHandler, server_addr, client_addr = helper.findServerDetails() + bitbake_server = server - def toggle_package(self, path, model, image=False): - inc = model[path][self.model.COL_INC] - # Warn user before removing included packages - if inc: - # FIXME: due to inpredictability of the removal of packages we are - # temporarily disabling this feature - return - # pn = model[path][self.model.COL_NAME] - # revdeps = self.model.find_reverse_depends(pn) - # if len(revdeps): - # 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(",")) - # else: - # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone." % pn - # dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) - # dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) - # dialog.add_button("Remove", gtk.RESPONSE_OK) - # response = dialog.run() - # dialog.destroy() - # if response == gtk.RESPONSE_CANCEL: - # return - - self.set_busy_cursor() - # Convert path to path in original model - opath = model.convert_path_to_child_path(path) - # This is a potentially length call which can block the - # main loop, therefore do the work in an idle func to keep - # the UI responsive - glib.idle_add(self.toggle_package_idle_cb, opath, image) - - self.dirty = True - - def toggle_include_cb(self, cell, path, tv): - model = tv.get_model() - self.toggle_package(path, model) - - def toggle_pkg_include_cb(self, cell, path, tv): - # there's an extra layer of models in the packages case. - sort_model = tv.get_model() - cpath = sort_model.convert_path_to_child_path(path) - self.toggle_package(cpath, sort_model.get_model()) - - def pkgsaz(self): - vbox = gtk.VBox(False, 6) - vbox.show() - self.pkgsaz_tree = gtk.TreeView() - self.pkgsaz_tree.set_headers_visible(True) - self.pkgsaz_tree.set_headers_clickable(True) - self.pkgsaz_tree.set_enable_search(True) - self.pkgsaz_tree.set_search_column(0) - self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) - - col = gtk.TreeViewColumn('Package') - col.set_clickable(True) - col.set_sort_column_id(self.model.COL_NAME) - col.set_min_width(220) - col1 = gtk.TreeViewColumn('Description') - col1.set_resizable(True) - col1.set_min_width(360) - col2 = gtk.TreeViewColumn('License') - col2.set_resizable(True) - col2.set_clickable(True) - col2.set_sort_column_id(self.model.COL_LIC) - col2.set_min_width(170) - col3 = gtk.TreeViewColumn('Group') - col3.set_clickable(True) - col3.set_sort_column_id(self.model.COL_GROUP) - col4 = gtk.TreeViewColumn('Included') - col4.set_min_width(80) - col4.set_max_width(90) - col4.set_sort_column_id(self.model.COL_INC) - - self.pkgsaz_tree.append_column(col) - self.pkgsaz_tree.append_column(col1) - self.pkgsaz_tree.append_column(col2) - self.pkgsaz_tree.append_column(col3) - self.pkgsaz_tree.append_column(col4) - - cell = gtk.CellRendererText() - cell1 = gtk.CellRendererText() - cell1.set_property('width-chars', 20) - cell2 = gtk.CellRendererText() - cell2.set_property('width-chars', 20) - cell3 = gtk.CellRendererText() - cell4 = gtk.CellRendererToggle() - cell4.set_property('activatable', True) - cell4.connect("toggled", self.toggle_pkg_include_cb, self.pkgsaz_tree) - - col.pack_start(cell, True) - col1.pack_start(cell1, True) - col2.pack_start(cell2, True) - col3.pack_start(cell3, True) - col4.pack_end(cell4, True) - - col.set_attributes(cell, text=self.model.COL_NAME) - col1.set_attributes(cell1, text=self.model.COL_DESC) - col2.set_attributes(cell2, text=self.model.COL_LIC) - col3.set_attributes(cell3, text=self.model.COL_GROUP) - col4.set_attributes(cell4, active=self.model.COL_INC) - - self.pkgsaz_tree.show() - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) - scroll.set_shadow_type(gtk.SHADOW_IN) - scroll.add(self.pkgsaz_tree) - vbox.pack_start(scroll, True, True, 0) - - hb = gtk.HBox(False, 0) - hb.show() - self.search = gtk.Entry() - self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear") - self.search.connect("icon-release", self.search_entry_clear_cb) - self.search.show() - self.pkgsaz_tree.set_search_entry(self.search) - hb.pack_end(self.search, False, False, 0) - label = gtk.Label("Search packages:") - label.show() - hb.pack_end(label, False, False, 6) - vbox.pack_start(hb, False, False, 0) - - return vbox - - def search_entry_clear_cb(self, entry, icon_pos, event): - entry.set_text("") - - def tasks(self): - vbox = gtk.VBox(False, 6) - vbox.show() - self.tasks_tree = gtk.TreeView() - self.tasks_tree.set_headers_visible(True) - self.tasks_tree.set_headers_clickable(False) - self.tasks_tree.set_enable_search(True) - self.tasks_tree.set_search_column(0) - self.tasks_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) - - col = gtk.TreeViewColumn('Package Collection') - col.set_min_width(430) - col1 = gtk.TreeViewColumn('Description') - col1.set_min_width(430) - col2 = gtk.TreeViewColumn('Include') - col2.set_min_width(70) - col2.set_max_width(80) - - self.tasks_tree.append_column(col) - self.tasks_tree.append_column(col1) - self.tasks_tree.append_column(col2) - - cell = gtk.CellRendererText() - cell1 = gtk.CellRendererText() - cell2 = gtk.CellRendererToggle() - cell2.set_property('activatable', True) - cell2.connect("toggled", self.toggle_include_cb, self.tasks_tree) - - col.pack_start(cell, True) - col1.pack_start(cell1, True) - col2.pack_end(cell2, True) - - col.set_attributes(cell, text=self.model.COL_NAME) - col1.set_attributes(cell1, text=self.model.COL_DESC) - col2.set_attributes(cell2, active=self.model.COL_INC) - - self.tasks_tree.show() - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) - scroll.set_shadow_type(gtk.SHADOW_IN) - scroll.add(self.tasks_tree) - vbox.pack_start(scroll, True, True, 0) - - hb = gtk.HBox(False, 0) - hb.show() - search = gtk.Entry() - search.show() - self.tasks_tree.set_search_entry(search) - hb.pack_end(search, False, False, 0) - label = gtk.Label("Search collections:") - label.show() - hb.pack_end(label, False, False, 6) - vbox.pack_start(hb, False, False, 0) - - return vbox - - def cancel_build(self, button): - if self.stopping: - lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once," - lbl = lbl + " would you like to 'Force Stop' the build?\n\n" - lbl = lbl + "This will stop the build as quickly as possible but may" - lbl = lbl + " well leave your build directory in an unusable state" - lbl = lbl + " that requires manual steps to fix.\n" - dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) - dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) - dialog.add_button("Force Stop", gtk.RESPONSE_YES) - else: - lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this" - lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as" - lbl = lbl + " possible but may well leave your build directory in an" - lbl = lbl + " unusable state that requires manual steps to fix.\n\n" - lbl = lbl + "'Stop' will stop the build as soon as all in" - lbl = lbl + " progress build tasks are finished. However if a" - lbl = lbl + " lengthy compilation phase is in progress this may take" - lbl = lbl + " some time." - dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) - dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) - dialog.add_button("Stop", gtk.RESPONSE_OK) - dialog.add_button("Force Stop", gtk.RESPONSE_YES) - response = dialog.run() - dialog.destroy() - if response != gtk.RESPONSE_CANCEL: - self.stopping = True - if response == gtk.RESPONSE_OK: - self.handler.cancel_build() - elif response == gtk.RESPONSE_YES: - self.handler.cancel_build(True) - - def view_build_gui(self): - vbox = gtk.VBox(False, 12) - vbox.set_border_width(6) - vbox.show() - build_tv = RunningBuildTreeView(readonly=True) - build_tv.show() - build_tv.set_model(self.build.model) - self.build.model.connect("row-inserted", self.scroll_tv_cb, build_tv) - scrolled_view = gtk.ScrolledWindow () - scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrolled_view.add(build_tv) - scrolled_view.show() - vbox.pack_start(scrolled_view, expand=True, fill=True) - hbox = gtk.HBox(False, 12) - hbox.show() - vbox.pack_start(hbox, expand=False, fill=False) - self.back = gtk.Button("Back") - self.back.show() - self.back.set_sensitive(False) - hbox.pack_start(self.back, expand=False, fill=False) - self.cancel = gtk.Button("Stop Build") - self.cancel.connect("clicked", self.cancel_build) - self.cancel.show() - hbox.pack_end(self.cancel, expand=False, fill=False) - - return vbox - - def create_menu(self): - menu_items = '''<ui> - <menubar name="MenuBar"> - <menu action="File"> - <menuitem action="Save"/> - <menuitem action="Save As"/> - <menuitem action="Open"/> - <separator/> - <menuitem action="AddLayer" label="Add Layer"/> - <separator/> - <menuitem action="Quit"/> - </menu> - <menu action="Edit"> - <menuitem action="Layers" label="Layers"/> - <menuitem action="Preferences"/> - </menu> - <menu action="Help"> - <menuitem action="About"/> - </menu> - </menubar> - </ui>''' - - uimanager = gtk.UIManager() - accel = uimanager.get_accel_group() - self.add_accel_group(accel) - - actions = gtk.ActionGroup('ImageCreator') - self.actions = actions - actions.add_actions([('Quit', gtk.STOCK_QUIT, None, None, None, self.menu_quit,), - ('File', None, '_File'), - ('Save', gtk.STOCK_SAVE, None, None, None, self.save_cb), - ('Save As', gtk.STOCK_SAVE_AS, None, None, None, self.save_as_cb), - ('Edit', None, '_Edit'), - ('Help', None, '_Help'), - ('About', gtk.STOCK_ABOUT, None, None, None, self.about_cb)]) - - self.add_layers_action = gtk.Action('AddLayer', 'Add Layer', None, None) - self.add_layers_action.connect("activate", self.add_layer_cb) - self.actions.add_action(self.add_layers_action) - self.layers_action = gtk.Action('Layers', 'Layers', None, None) - self.layers_action.connect("activate", self.layers_cb) - self.actions.add_action(self.layers_action) - self.prefs_action = gtk.Action('Preferences', 'Preferences', None, None) - self.prefs_action.connect("activate", self.preferences_cb) - self.actions.add_action(self.prefs_action) - self.open_action = gtk.Action('Open', 'Open', None, None) - self.open_action.connect("activate", self.open_cb) - self.actions.add_action(self.open_action) - - uimanager.insert_action_group(actions, 0) - uimanager.add_ui_from_string(menu_items) - - menubar = uimanager.get_widget('/MenuBar') - menubar.show_all() - - return menubar - - def info_button_clicked_cb(self, button): - info = "We cannot accurately predict the image contents before they are built so instead a best" - info = info + " attempt at estimating what the image will contain is listed." - dialog = CrumbsDialog(self, info, gtk.STOCK_DIALOG_INFO) - dialog.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) - resp = dialog.run() - dialog.destroy() - - def create_build_gui(self): - vbox = gtk.VBox(False, 12) - vbox.set_border_width(6) - vbox.show() - - hbox = gtk.HBox(False, 12) - hbox.show() - vbox.pack_start(hbox, expand=False, fill=False) - - label = gtk.Label("Machine:") - label.show() - hbox.pack_start(label, expand=False, fill=False, padding=6) - self.machine_combo = gtk.combo_box_new_text() - self.machine_combo.show() - self.machine_combo.set_tooltip_text("Selects the architecture of the target board for which you would like to build an image.") - hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6) - label = gtk.Label("Base image:") - label.show() - hbox.pack_start(label, expand=False, fill=False, padding=6) - self.image_combo = gtk.ComboBox() - self.image_combo.show() - self.image_combo.set_tooltip_text("Selects the image on which to base the created image") - image_combo_cell = gtk.CellRendererText() - self.image_combo.pack_start(image_combo_cell, True) - self.image_combo.add_attribute(image_combo_cell, 'text', self.model.COL_NAME) - hbox.pack_start(self.image_combo, expand=False, fill=False, padding=6) - self.progress = gtk.ProgressBar() - self.progress.set_size_request(250, -1) - hbox.pack_end(self.progress, expand=False, fill=False, padding=6) - - ins = gtk.Notebook() - vbox.pack_start(ins, expand=True, fill=True) - ins.set_show_tabs(True) - label = gtk.Label("Packages") - label.show() - ins.append_page(self.pkgsaz(), tab_label=label) - label = gtk.Label("Package Collections") - label.show() - ins.append_page(self.tasks(), tab_label=label) - ins.set_current_page(0) - ins.show_all() - - hbox = gtk.HBox(False, 1) - hbox.show() - label = gtk.Label("Estimated image contents:") - self.model.connect("contents-changed", self.update_package_count_cb, label) - label.set_property("xalign", 0.00) - label.show() - hbox.pack_start(label, expand=False, fill=False, padding=6) - info = gtk.Button("?") - info.set_tooltip_text("What does this mean?") - info.show() - info.connect("clicked", self.info_button_clicked_cb) - hbox.pack_start(info, expand=False, fill=False, padding=6) - vbox.pack_start(hbox, expand=False, fill=False, padding=6) - con = self.contents() - con.show() - vbox.pack_start(con, expand=True, fill=True) - - bbox = gtk.HButtonBox() - bbox.set_spacing(12) - bbox.set_layout(gtk.BUTTONBOX_END) - bbox.show() - vbox.pack_start(bbox, expand=False, fill=False) - reset = gtk.Button("Reset") - reset.connect("clicked", self.reset_clicked_cb) - reset.show() - bbox.add(reset) - bake = gtk.Button("Bake") - bake.connect("clicked", self.bake_clicked_cb) - bake.show() - bbox.add(bake) - - return vbox - - def update_package_count_cb(self, model, count, label): - lbl = "Estimated image contents (%s packages):" % count - label.set_text(lbl) - - def contents(self): - self.contents_tree = gtk.TreeView() - self.contents_tree.set_headers_visible(True) - self.contents_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) - - # allow searching in the package column - self.contents_tree.set_search_column(0) - self.contents_tree.set_enable_search(True) - - col = gtk.TreeViewColumn('Package') - col.set_sort_column_id(0) - col.set_min_width(430) - col1 = gtk.TreeViewColumn('Brought in by') - col1.set_resizable(True) - col1.set_min_width(430) - - self.contents_tree.append_column(col) - self.contents_tree.append_column(col1) - - cell = gtk.CellRendererText() - cell1 = gtk.CellRendererText() - cell1.set_property('width-chars', 20) - - col.pack_start(cell, True) - col1.pack_start(cell1, True) - - col.set_attributes(cell, text=self.model.COL_NAME) - col1.set_attributes(cell1, text=self.model.COL_BINB) - - self.contents_tree.show() - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) - scroll.set_shadow_type(gtk.SHADOW_IN) - scroll.add(self.contents_tree) - - return scroll - -def main (server, eventHandler): gobject.threads_init() - # NOTE: For now we require that the user run with pre and post files to - # read and store configuration set in the GUI. - # We hope to adjust this long term as tracked in Yocto Bugzilla #1441 - # http://bugzilla.pokylinux.org/show_bug.cgi?id=1441 - reqfiles = 0 - dep_files = server.runCommand(["getVariable", "__depends"]) or set() - dep_files.union(server.runCommand(["getVariable", "__base_depends"]) or set()) - for f in dep_files: - if f[0].endswith("hob-pre.conf"): - reqfiles = reqfiles + 1 - elif f[0].endswith("hob-post.conf"): - reqfiles = reqfiles + 1 - if reqfiles == 2: - break - if reqfiles < 2: - print("""The hob UI requires a pre file named hob-pre.conf and a post -file named hob-post.conf to store and read its configuration from. Please run -hob with these files, i.e.\n -\bitbake -u hob -r conf/hob-pre.conf -R conf/hob-post.conf""") - return + # That indicates whether the Hob and the bitbake server are + # running on different machines + # recipe model and package model + recipe_model = RecipeListModel() + package_model = PackageListModel() - taskmodel = TaskListModel() - configurator = Configurator() - handler = HobHandler(taskmodel, server) - mach = server.runCommand(["getVariable", "MACHINE"]) - sdk_mach = server.runCommand(["getVariable", "SDKMACHINE"]) - # If SDKMACHINE not set the default SDK_ARCH is used so we - # should represent that in the GUI - if not sdk_mach: - sdk_mach = server.runCommand(["getVariable", "SDK_ARCH"]) - distro = server.runCommand(["getVariable", "DISTRO"]) - if not distro: - distro = "defaultsetup" - bbthread = server.runCommand(["getVariable", "BB_NUMBER_THREADS"]) - if not bbthread: - bbthread = 1 - else: - bbthread = int(bbthread) - pmake = server.runCommand(["getVariable", "PARALLEL_MAKE"]) - if not pmake: - pmake = 1 - else: - # The PARALLEL_MAKE variable will be of the format: "-j 3" and we only - # want a number for the spinner, so strip everything from the variable - # up to and including the space - pmake = int(pmake.lstrip("-j ")) - - selected_image_types = server.runCommand(["getVariable", "IMAGE_FSTYPES"]) - all_image_types = server.runCommand(["getVariable", "IMAGE_TYPES"]) - - pclasses = server.runCommand(["getVariable", "PACKAGE_CLASSES"]).split(" ") - # NOTE: we're only supporting one value for PACKAGE_CLASSES being set - # this seems OK because we're using the first package format set in - # PACKAGE_CLASSES and that's the package manager used for the rootfs - pkg, sep, pclass = pclasses[0].rpartition("_") - - incompatible = server.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) - gplv3disabled = False - if incompatible and incompatible.lower().find("gplv3") != -1: - gplv3disabled = True - - build_toolchain = bool(server.runCommand(["getVariable", "HOB_BUILD_TOOLCHAIN"])) - handler.toggle_toolchain(build_toolchain) - build_headers = bool(server.runCommand(["getVariable", "HOB_BUILD_TOOLCHAIN_HEADERS"])) - handler.toggle_toolchain_headers(build_headers) - - prefs = HobPrefs(configurator, handler, sdk_mach, distro, pclass, - pmake, bbthread, selected_image_types, all_image_types, - gplv3disabled, build_toolchain, build_headers) - layers = LayerEditor(configurator, None) - window = MainWindow(taskmodel, handler, configurator, prefs, layers, mach) - prefs.set_parent_window(window) - layers.set_parent_window(window) - window.show_all () - handler.connect("machines-updated", window.update_machines) - handler.connect("sdk-machines-updated", prefs.update_sdk_machines) - handler.connect("distros-updated", prefs.update_distros) - handler.connect("package-formats-found", prefs.update_package_formats) - handler.connect("generating-data", window.busy) - handler.connect("data-generated", window.data_generated) - handler.connect("reload-triggered", window.reload_triggered_cb) - configurator.connect("layers-loaded", layers.load_current_layers) - configurator.connect("layers-changed", handler.reload_data) - handler.connect("config-found", configurator.configFound) - handler.connect("fatal-error", window.fatal_error_cb) - - try: - # kick the while thing off - handler.current_command = handler.CFG_PATH_LOCAL - server.runCommand(["findConfigFilePath", "local.conf"]) - except xmlrpclib.Fault: - print("XMLRPC Fault getting commandline:\n %s" % x) + hobHandler = HobHandler(bitbake_server, server_addr, client_addr, recipe_model, package_model) + if hobHandler.kick() == False: return 1 + builder = Builder(hobHandler, recipe_model, package_model) # This timeout function regularly probes the event queue to find out if we # have any messages waiting for us. - gobject.timeout_add (100, - handler.event_handle_idle_func, - eventHandler, - window.build, - window.progress) + gobject.timeout_add(10, event_handle_idle_func, eventHandler, hobHandler) try: gtk.main() @@ -1107,5 +80,13 @@ hob with these files, i.e.\n if ioerror.args[0] == 4: pass finally: - server.runCommand(["stateStop"]) + hobHandler.cancel_build(force = True) +if __name__ == "__main__": + try: + ret = main() + except Exception: + ret = 1 + import traceback + traceback.print_exc(15) + sys.exit(ret) |